MCP pour les agents de programmation

text mcp : laissez les agents de programmation naviguer, appuyer sur des touches et lire les frames via le Model Context Protocol.

text mcp exécute votre répertoire app/ sans interface graphique, dans le même processus, et le sert via le Model Context Protocol pour que les agents de programmation puissent l’utiliser sans DevTools. L’agent navigue, appuie sur des touches, lit les frames en texte brut et appelle les gestionnaires de route.

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

text mcp nécessite Bun. Lancé avec Node, il se relance avec Bun si bun figure dans votre PATH, et échoue avec TX019 sinon. Les clients MCP le lancent pour vous, vous avez donc rarement à le lancer manuellement.

Configuration

Chaque client lance text mcp depuis le répertoire de votre projet. Utilisez bunx --bun pour que les fichiers de route s’exécutent en 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

Ou enregistrez cette configuration dans le dépôt pour l’équipe :

.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"] }  }}

Pour un projet dans un sous-répertoire, ajoutez le chemin à la suite : ["--bun", "text", "mcp", "apps/console"].

Outils

OutilDescription
list_routesPages et gestionnaires de route dans app/, et emplacement actuel
navigateAjouter ou remplacer un chemin, ou aller en arrière (back) / en avant (forward)
read_frameLa frame en texte, avec la route, la taille, le focus et le curseur. styles: true ajoute les couleurs et attributs de chaque span
pressCombinaisons de touches : "down", "ctrl+k", "shift+tab", "tab tab enter", "down*3"
typeSaisir du texte avec une touche par caractère (jusqu’à 2000 ; utiliser paste au-delà). submit: true appuie ensuite sur Enter
pasteUn événement de collage, comme ⌘V
scrollDéfilement à la molette de rows lignes (et cols colonnes). Un <ScrollBox> défile à la molette sans avoir le focus clavier
resizeDéfinir la taille de la grille en colonnes et en lignes
inspect_treeL’arbre des composants avec leurs limites à l’écran, et tous les identifiants pouvant recevoir le focus
findLimites de chaque correspondance et composant situé en dessous
wait_forAttendre qu’un texte apparaisse ou disparaisse, ou que l’application soit inactive
logsSorties console.* capturées et erreurs React
fetchAppeler un gestionnaire route.ts dans le même processus. Les corps sont lus jusqu’à maxBytes ; les flux d’événements pendant streamMs, puis annulés
reloadRéimporter l’application après modification des fichiers

Les outils de saisie renvoient la nouvelle frame, l’agent n’a donc pas besoin d’un appel distinct à read_frame après chaque touche. La frame actuelle et la liste des routes sont aussi disponibles via les ressources text://frame et text://routes.

Un échange typique :

→ 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  …

Frames et inactivité

Avant de lire une frame, chaque outil attend que l’application soit inactive : aucune frame planifiée, aucun fetch en cours, et aucun commit ni aucune frame depuis 50 ms. L’agent voit donc le résultat de sa saisie, pas une frame au milieu d’une mise à jour.

Un <Spinner> ou un graphique actualisé une fois par seconde reste considéré comme inactif entre les actualisations, les pages avec des animations restent donc lisibles. Une application qui effectue un rendu plus souvent que toutes les 50 ms ne devient jamais inactive. Les lectures abandonnent après 1,5 seconde (settleMs modifie ce délai), émettent la dernière frame et la marquent busy. Sans texte, wait_for attend l’inactivité avec son propre quietMs et renvoie une erreur si l’inactivité ne survient jamais.

Les coordonnées sont exprimées en cellules et commencent à zéro : x désigne la colonne, y la ligne. Les caractères larges, comme les caractères CJK et les emoji, occupent deux colonnes.

Erreurs et journaux

stdout transporte le protocole. Les sorties console.* de votre application, les appels parasites à process.stdout.write et les erreurs React sont dirigés vers logs, avec une numérotation pour qu’un agent puisse demander uniquement les nouvelles entrées avec since. Si des erreurs ont été journalisées depuis la dernière lecture, l’en-tête de la frame suivante le signale.

Les échecs sont renvoyés sous forme d’erreurs d’outil, sans jamais provoquer de plantage. Un outil qui dépasse son délai est annulé avant l’exécution de l’appel suivant, un appel lent ne saisit donc jamais de texte dans la frame de l’appel suivant. Les erreurs avec un code Text.js le conservent, par exemple TX007 pour une touche inconnue. Si l’application ne se charge pas, chaque outil en explique la raison. Corrigez le fichier et appelez reload.

Options

OptionDescription
--path <pathname>Première route (par défaut /)
--cols / --rowsTaille de la grille (par défaut : viewport dans text.config.ts, sinon 100×32)
--theme <name>Préréglage de thème
--no-configIgnorer text.config.ts
--verboseÉcrire aussi les journaux capturés sur stderr

Fonctionnement

text mcp exécute votre application comme text run : chaque module de route est importé au préalable, App Router utilise un historique en mémoire, et les appels fetch("/api/…") de même origine exécutent vos gestionnaires dans le même processus. Les frames sont envoyées vers une grille de cellules hors écran plutôt que vers un terminal. Les pages avec rendu côté serveur (export const runtime = "server") et leurs composants async function sont rendus dans le même processus, comme avec text dev, et la frame est lue une fois leur exécution terminée.

reload démonte l’application, supprime tous les modules du projet du cache de modules de Bun (y compris les fichiers en dehors de app/), analyse à nouveau app/ et remonte l’application à l’emplacement actuel.

Pour intégrer le serveur dans votre propre processus, ou utiliser un autre transport :

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();

Limites

  • L’application s’exécute dans Bun, pas dans un navigateur. window et document ne sont pas définis, comme dans text run.
  • Chaque serveur exécute une application avec une grille. Chaque client MCP lance son propre serveur.
  • text mcp exécute sa propre copie sans interface graphique de l’application. Il ne peut pas se connecter à un onglet de navigateur ouvert sous text dev.
Modifier cette page sur GitHub