GTK4/VTE terminal workspace with MCP control plane and UI screenshots
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Matteo Benedetto 37e7517b62 feat: Enhance server functionality with detailed command execution instructions and improve test report documentation 6 days ago
.github feat: Enhance MCP HAL9002 with command tracking and GUI integration 6 days ago
.vscode feat: Enhance MCP HAL9002 with command tracking and GUI integration 6 days ago
assets Update logo prompt to killall human 6 days ago
src/gnome_vte_mcp feat: Enhance server functionality with detailed command execution instructions and improve test report documentation 6 days ago
.gitignore Rename project to mcp-hal9002 7 days ago
README.md feat: Add user-level configuration for mcp-hal9002 in VS Code and update workspace setup 6 days ago
TEST_REPORT.md feat: Enhance server functionality with detailed command execution instructions and improve test report documentation 6 days ago
pyproject.toml feat: Enhance MCP HAL9002 with command tracking and GUI integration 6 days ago

README.md

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

Il nome e un omaggio a HAL 9000, il computer di bordo dell'astronave Discovery One in 2001: Odissea nello Spazio (regia di Stanley Kubrick, sceneggiatura di Arthur C. Clarke, 1968). HAL e un'intelligenza artificiale che governa e controlla in modo autonomo tutti i sistemi della nave — esattamente come mcp-hal9002 e un agente che controlla il terminale tramite MCP. Il numero e aggiornato da 9000 a 9002, come a indicare una versione successiva, piu obbediente e (si spera) priva di derive omicide. Il logo riprende l'iconico occhio rosso di HAL.

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

Configurazione a livello utente (raccomandata, tramite uvx)

Per abilitare mcp-hal9002 in tutti i workspace VS Code senza dover aprire questo repository, aggiungilo al file di configurazione MCP utente:

Linux/macOS: ~/.config/Code/User/mcp.json
Windows: %APPDATA%\Code\User\mcp.json

Da repository Git (non richiede installazione locale):

{
  "servers": {
    "mcpHal9002": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "--from",
        "git+https://git.enne2.net/enne2/mcp-hal9002.git",
        "mcp-hal9002"
      ]
    }
  }
}

Se il pacchetto viene pubblicato su PyPI, la versione semplificata sara:

{
  "servers": {
    "mcpHal9002": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcp-hal9002"]
    }
  }
}

Dopo aver salvato il file, esegui MCP: List Servers in VS Code per rilevare la nuova configurazione, oppure attiva chat.mcp.autostart in settings per il riavvio automatico.

Configurazione di workspace (sviluppo locale)

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

Autore

Matteo Benedetto — Enne2

Computer engineer, systems designer and architect, eclectic. Working in aerospace industry (e-geos S.p.A.), Italy.

© 2025–2026 Matteo Benedetto