MCP per agenti di programmazione

text mcp: consenti agli agenti di programmazione di navigare, premere tasti e leggere frame tramite il Model Context Protocol.

text mcp esegue la tua directory app/ in modalità headless, nello stesso processo, e la espone tramite il Model Context Protocol, così gli agenti di programmazione possono usarla senza DevTools. L’agente naviga, preme tasti, legge i frame come testo semplice e chiama i route handler.

bunx --bun text mcp              # serve ./app over stdiobunx --bun text mcp apps/console # another project

text mcp richiede Bun. Se avviato con Node, si riesegue con Bun quando bun è nel tuo PATH, altrimenti fallisce con TX019. I client MCP lo avviano per te, quindi raramente lo esegui a mano.

Configurazione

Ogni client avvia text mcp dalla directory del tuo progetto. Usa bunx --bun per eseguire i file delle route come TypeScript.

OpenCode

opencode.jsonjson
{  "$schema": "https://opencode.ai/config.json",  "mcp": {    "textjs": {      "type": "local",      "command": ["bunx", "--bun", "text", "mcp"],      "enabled": true    }  }}

Claude Code

claude mcp add textjs -- bunx --bun text mcp

Oppure includilo in un commit per il team:

.mcp.jsonjson
{  "mcpServers": {    "textjs": { "command": "bunx", "args": ["--bun", "text", "mcp"] }  }}

Cursor

.cursor/mcp.jsonjson
{  "mcpServers": {    "textjs": { "command": "bunx", "args": ["--bun", "text", "mcp"] }  }}

VS Code

.vscode/mcp.jsonjson
{  "servers": {    "textjs": { "type": "stdio", "command": "bunx", "args": ["--bun", "text", "mcp"] }  }}

Per un progetto in una sottodirectory, accoda il percorso: ["--bun", "text", "mcp", "apps/console"].

Strumenti

StrumentoDescrizione
list_routesPagine e route handler in app/, e posizione corrente
navigateAggiunta o sostituzione di un percorso, o navigazione back / forward
read_frameFrame come testo, con route, dimensioni, focus e cursore. styles: true aggiunge colori e attributi per ogni span
pressCombinazioni di tasti: "down", "ctrl+k", "shift+tab", "tab tab enter", "down*3"
typeDigitazione del testo, un tasto per carattere (fino a 2000; usa paste per quantità maggiori). submit: true preme Enter al termine
pasteUn evento di incolla, come ⌘V
scrollScorrimento con la rotellina del mouse di rows (e cols). Un <ScrollBox> scorre con la rotellina senza focus da tastiera
resizeImpostazione delle dimensioni della griglia in colonne e righe
inspect_treeAlbero dei componenti con limiti sullo schermo e ogni id che può ricevere il focus
findLimiti di ogni corrispondenza e componente sottostante
wait_forAttesa della comparsa o scomparsa di testo, o dell’inattività dell’app
logsOutput console.* acquisito ed errori React
fetchChiamata di un handler route.ts nello stesso processo. I corpi vengono letti fino a maxBytes; i flussi di eventi per streamMs, poi annullati
reloadReimportazione dell’app dopo la modifica dei file

Gli strumenti di input restituiscono il nuovo frame, quindi l’agente non ha bisogno di un read_frame separato dopo ogni tasto. Il frame corrente e l’elenco delle route sono disponibili anche come risorse text://frame e text://routes.

Uno scambio tipico:

→ navigate { "path": "/deployments" }→ press { "keys": "down down enter" }← route: /deployments/dpl_6m3ty8  (route /deployments/[id])  title: "Deployment dpl_6m3ty8 · Edge Console"  size: 100×32  frame: 6  idle  focus: _txr_c_ ScrollBox [4,12 92×18]    ▼ Edge Console   Overview   Deployments   Logs   Settings    ← Deployments  /  dpl_6m3ty8   Ready    Production  …

Frame e inattività

Prima di leggere un frame, ogni strumento attende che l’app sia inattiva: nessun frame pianificato, nessun fetch in corso e nessun commit o frame da 50 ms. Così l’agente vede il risultato del proprio input, non un frame a metà aggiornamento.

Un <Spinner> o un grafico che si aggiorna una volta al secondo conta comunque come inattivo tra gli aggiornamenti, quindi le pagine con animazioni restano leggibili. Un’app che esegue il rendering più spesso di ogni 50 ms non diventa mai inattiva. Le letture smettono di attendere dopo 1,5 secondi (settleMs modifica questo valore), emettono l’ultimo frame e lo contrassegnano come busy. wait_for senza testo attende l’inattività, con il proprio quietMs, e restituisce un errore se l’inattività non arriva mai.

Le coordinate sono celle con indice a base zero: x è la colonna, y la riga. I caratteri larghi, come CJK ed emoji, occupano due colonne.

Errori e log

stdout trasporta il protocollo. L’output console.* della tua app, le chiamate isolate a process.stdout.write e gli errori React finiscono invece in logs, numerati in modo che un agente possa richiedere solo le nuove voci con since. Quando sono stati registrati errori dall’ultima lettura, l’intestazione del frame successivo lo segnala.

I fallimenti vengono restituiti come errori degli strumenti, mai come arresti anomali. Uno strumento che supera il tempo limite viene annullato prima dell’esecuzione della chiamata successiva, quindi una chiamata lenta non digita mai nel frame della chiamata successiva. Gli errori con un codice Text.js lo mantengono, per esempio TX007 per un tasto sconosciuto. Se l’app non si carica, ogni strumento spiega perché. Correggi il file e chiama reload.

Opzioni

OpzioneDescrizione
--path <pathname>Prima route (predefinita /)
--cols / --rowsDimensioni della griglia (predefinite: viewport in text.config.ts, altrimenti 100×32)
--theme <name>Preimpostazione del tema
--no-configIgnora text.config.ts
--verboseScrive i log acquisiti anche su stderr

Funzionamento

text mcp esegue la tua app come text run: ogni modulo di route viene importato in anticipo, l’App Router usa una cronologia in memoria e le chiamate fetch("/api/…") alla stessa origine eseguono i tuoi handler nello stesso processo. I frame finiscono in una griglia di celle fuori schermo invece che in un terminale. Le pagine renderizzate lato server (export const runtime = "server") e i loro componenti async function eseguono il rendering nello stesso processo, come in text dev, e il frame viene letto una volta completate le operazioni asincrone.

reload smonta l’app, rimuove ogni modulo del progetto dalla cache dei moduli di Bun (inclusi i file esterni ad app/), scansiona di nuovo app/ e rimonta l’app nella posizione corrente.

Per incorporare il server nel tuo processo o usare un altro trasporto:

import { createTextMcpServer } from "@textjs/app/mcp";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";const { server, controller } = createTextMcpServer({ root: process.cwd() });await server.connect(new StdioServerTransport());void controller.start();

Limiti

  • L’app viene eseguita in Bun, non in un browser. window e document non sono definiti, come in text run.
  • Ogni server esegue un’app con una griglia. Ogni client MCP avvia il proprio server.
  • text mcp esegue una propria copia headless dell’app. Non può collegarsi a una scheda del browser aperta con text dev.
Modifica questa pagina su GitHub