mcp-hal9002

mcp-hal9002 logo

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.py applicazione GTK4/VTE con un controllo locale via socket Unix
  • src/gnome_vte_mcp/server.py MCP server basato su FastMCP che parla con la GUI
  • src/gnome_vte_mcp/control.py protocollo 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-hal9002 avvia 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:

  1. apri questo workspace in VS Code
  2. assicurati che .venv esista e contenga le dipendenze del progetto
  3. apri Chat e conferma la trust prompt del server MCP quando VS Code la mostra
  4. se i tool non compaiono subito, esegui MCP: List Servers oppure MCP: 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 condivisa
  • gui_status() restituisce lo stato senza creare nuove istanze
  • close_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 grezzo
  • exec_command(...) scrive il testo nel terminale e, di default, resta bloccato indefinitamente finche l'utente non preme Enter manualmente nella GUI; il parametro newline resta ignorato in questa modalita per compatibilita
  • exec_command(..., auto_submit=True) invia anche Enter da solo, ma e volutamente ristretto a piccoli comandi read-only e senza shell syntax complessa
  • dopo eventuali modifiche manuali nella GUI, premi Enter tu per sbloccare davvero exec_command(...) e inviare il comando alla shell
  • per comandi che aprono una sessione interattiva delegata come ssh, arca o 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 e read_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_sequence restituito da exec_command(...) e restituisce command, cwd, cwd_after, started_at, finished_at, duration_seconds, exit_code e text; se timeout e omesso o <= 0, l'attesa e indefinita
  • wait_for_prompt(...) serve per sessioni interattive delegate come ssh: aspetta che il transcript smetta di crescere per un breve intervallo e che l'ultima riga assomigli a un prompt
  • read_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 renderizzato
  • target="tab" cattura solo il widget Vte.Terminal della tab attiva o della tab indicata con tab_id, escludendo header bar e tab bar
  • target="tab-container" cattura il container GTK della tab, utile se serve includere il bordo o eventuali scrollbar
  • path permette di scegliere il file PNG di destinazione
  • diagnostic_overlay=True annota lo screenshot con una griglia e i bounds del target per window, tab e tab-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:

  • title esplicito, 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

  1. aggiungere eventi di output incrementale e subscription
  2. introdurre persistenza dello stato tab/sessioni
  3. migliorare il modello UI con veri tab drag-and-drop e split view
  4. aggiungere policy locali per limitare i comandi consentiti
  5. usare gli screenshot e i sidecar JSON per confronti UI before/after automatizzabili
S
Description
GTK4/VTE terminal workspace with MCP control plane and UI screenshots
Readme
473 KiB
Languages
Python 100%