mcp-hal9002
mcp-hal9002 e un terminal workspace GTK4 + VTE controllabile via MCP, con GUI locale, control plane Unix socket e strumenti per screenshot/debug UI.
Il nome definitivo del progetto, del pacchetto distribuibile e delle CLI e allineato alla cartella root: mcp-hal9002.
Obiettivo
Il progetto evita di dipendere da API non ufficiali di GNOME Terminal. Invece espone direttamente una GUI propria, che supporta:
- apertura tab
- elenco tab
- focus tab
- invio comandi a una shell esistente
- lettura dello scrollback della tab
- screenshot della finestra, del contenuto VTE della tab, o del container della tab per debug UI/UX
- chiusura tab
Architettura
src/gnome_vte_mcp/gui.pyapplicazione GTK4/VTE con un controllo locale via socket Unixsrc/gnome_vte_mcp/server.pyMCP server basato suFastMCPche parla con la GUIsrc/gnome_vte_mcp/control.pyprotocollo client per il socket locale
Il server MCP lancia automaticamente la GUI se non e gia in esecuzione.
La GUI e considerata una finestra gestita dal server MCP: il flusso previsto e aprirla, portarla in primo piano e chiuderla tramite tool MCP, non tramite una CLI separata.
Uso con uvx
Entrypoint principali:
mcp-hal9002avvia il server MCP
Esempi locali dal checkout:
uvx --from . mcp-hal9002
Esempio da repository Git:
uvx --from git+https://git.enne2.net/enne2/mcp-hal9002.git mcp-hal9002
Uso diretto in VS Code con Copilot
Il workspace include una configurazione [ .vscode/mcp.json ] che registra mcp-hal9002 come server MCP stdio per GitHub Copilot usando il Python del virtualenv locale e PYTHONPATH=${workspaceFolder}/src.
Nel workspace e presente anche [ .vscode/settings.json ] con chat.mcp.autostart=true, cosi VS Code puo riavviare automaticamente il server quando la configurazione cambia.
Per usarlo in Copilot:
- apri questo workspace in VS Code
- assicurati che
.venvesista e contenga le dipendenze del progetto - apri Chat e conferma la trust prompt del server MCP quando VS Code la mostra
- se i tool non compaiono subito, esegui
MCP: List ServersoppureMCP: Reset Cached Tools
Requisiti
Nel sistema devono essere disponibili:
- Python 3.12+
- package Python
pycairo - PyGObject (
gi) - GTK4
- VTE con binding GI (
Vte 3.91) - package Python
mcp
In questa macchina il runtime necessario risulta disponibile.
Debug locale della GUI
La GUI continua ad avere un modulo eseguibile per debug e sviluppo, ma il percorso normale e pilotarla dal server MCP con open_gui() e chiuderla con close_gui().
PYTHONPATH=src python3 -m gnome_vte_mcp.gui
Per usare un socket custom:
PYTHONPATH=src python3 -m gnome_vte_mcp.gui --socket /tmp/mcp-hal9002-demo.sock
Avvio del server MCP
PYTHONPATH=src python3 -m gnome_vte_mcp.server
Variabile opzionale:
export MCP_HAL9002_SOCKET=/tmp/mcp-hal9002-demo.sock
La variabile legacy GNOME_VTE_MCP_SOCKET resta accettata per compatibilita.
Tool MCP disponibili
gui_status()open_gui()open_tab(title=None, cwd=None, command=None)list_tabs()focus_tab(tab_id)exec_command(tab_id, command, newline=True, auto_submit=False, poll_interval=0.1)read_tab(tab_id, last_n_lines=200)read_last_command_result(tab_id)wait_for_command(delay_seconds)wait_for_running_command(tab_id, timeout=None, poll_interval=0.1)wait_for_command_result(tab_id, after_sequence=None, timeout=None, poll_interval=0.1)wait_for_prompt(tab_id, timeout=None, poll_interval=0.1, idle_seconds=0.4, prompt_pattern=None)capture_screenshot(target="window", tab_id=None, path=None, diagnostic_overlay=False)close_tab(tab_id)close_gui()
Lifecycle GUI:
open_gui()avvia la GUI se manca e porta in primo piano la finestra condivisagui_status()restituisce lo stato senza creare nuove istanzeclose_gui()chiude la finestra condivisa se e in esecuzione- tutti gli altri tool che richiedono la GUI continuano a riusare la stessa istanza attraverso il socket locale
Per la lettura testuale dei comandi:
read_tab(...)restituisce lo scrollback recente, utile per debugging grezzoexec_command(...)scrive il testo nel terminale e, di default, resta bloccato indefinitamente finche l'utente non premeEntermanualmente nella GUI; il parametronewlineresta ignorato in questa modalita per compatibilitaexec_command(..., auto_submit=True)invia ancheEnterda solo, ma e volutamente ristretto a piccoli comandi read-only e senza shell syntax complessa- dopo eventuali modifiche manuali nella GUI, premi
Entertu per sbloccare davveroexec_command(...)e inviare il comando alla shell - per comandi che aprono una sessione interattiva delegata come
ssh,arcao una subshell, la tab non viene trattata come bloccata: puoi continuare a scrivere nuovi comandi nella stessa sessione senza aspettare il ritorno della shell locale wait_for_command(delay_seconds)non osserva il terminale e non prova a capire se il comando e finito: e solo una pausa sincrona esplicita, utile quando il loop eread_tab(...)-> attesa arbitraria ->read_tab(...)wait_for_running_command(...)aspetta il completamento del comando che e gia in esecuzione nella tab quando il terminale e occupato e non puoi ancora inviare un nuovo comando- se la tab e dentro una sessione interattiva delegata,
wait_for_running_command(...)fallisce esplicitamente perche il completamento tracciato tornera disponibile solo quando esci da quella sessione wait_for_command_result(...)aspetta in modo bloccante il completamento del comando dopo l'after_sequencerestituito daexec_command(...)e restituiscecommand,cwd,cwd_after,started_at,finished_at,duration_seconds,exit_codeetext; setimeoute omesso o<= 0, l'attesa e indefinitawait_for_prompt(...)serve per sessioni interattive delegate comessh: aspetta che il transcript smetta di crescere per un breve intervallo e che l'ultima riga assomigli a un promptread_last_command_result(tab_id)restituisce l'ultimo comando completato con gli stessi metadati temporali e di path
Per capture_screenshot:
target="window"prova a catturare l'intera finestra, inclusa la titlebar GTK; se la piattaforma non lo consente ripiega sul contenuto renderizzatotarget="tab"cattura solo il widgetVte.Terminaldella tab attiva o della tab indicata contab_id, escludendo header bar e tab bartarget="tab-container"cattura il container GTK della tab, utile se serve includere il bordo o eventuali scrollbarpathpermette di scegliere il file PNG di destinazionediagnostic_overlay=Trueannota lo screenshot con una griglia e i bounds del target perwindow,tabetab-container
Il tool restituisce un payload JSON serializzabile con path, dimensioni e metadati del capture, cosi il bridge MCP non dipende da tipi immagine custom.
Per ogni screenshot viene salvato anche un file JSON sidecar con metadati di debug UI, tra cui:
- widget target effettivo
- allocazione del widget rispetto alla finestra
- renderer GSK usato per la cattura
- dettagli di superficie e scala
- stato dell'overlay diagnostico richiesto/applicato
Primo avvio e onboarding
La prima tab mostra un piccolo pannello di onboarding centrato sopra il terminale, con un riepilogo delle azioni MCP disponibili e del workspace corrente.
Il pannello sparisce automaticamente:
- alla prima pressione di tasto dentro il terminale
- al primo
exec_command(...)inviato via MCP oppure al primo comando eseguito manualmente
Titoli tab dinamici
I titoli delle tab sono derivati in modo compatto da:
titleesplicito, se fornito e non generico- comando iniziale o ultimo comando eseguito
cwd, quando non c'e un comando significativo
Questo rende il StackSwitcher piu compatto e piu utile quando ci sono piu tab aperte.
Limiti attuali
- non salva ancora layout o sessioni persistenti
- non gestisce ancora rename tab automatico da processo o cwd
- il canale di controllo locale non ha ancora autenticazione aggiuntiva oltre ai permessi del socket Unix
- il recupero testo usa un transcript locale della shell, ma non implementa ancora stream incrementale o eventi
- la cattura screenshot dipende dal fatto che il widget sia già renderizzato nella sessione grafica corrente
- se una GUI gia avviata resta in esecuzione, i test del control plane continueranno a usare quel processo: dopo modifiche a screenshot o titlebar conviene riavviare la GUI prima di ritestare
Prossimi passi naturali
- aggiungere eventi di output incrementale e subscription
- introdurre persistenza dello stato tab/sessioni
- migliorare il modello UI con veri tab drag-and-drop e split view
- aggiungere policy locali per limitare i comandi consentiti
- usare gli screenshot e i sidecar JSON per confronti UI before/after automatizzabili