MCP для агентов программирования
text mcp: дайте агентам программирования возможность переходить по приложению, нажимать клавиши и читать кадры через Model Context Protocol.
text mcp запускает ваш каталог app/ без графического интерфейса, внутри процесса, и предоставляет к нему доступ по протоколу Model Context Protocol, чтобы агенты для написания кода могли использовать его без DevTools. Агент перемещается по маршрутам, нажимает клавиши, читает кадры как обычный текст и вызывает обработчики маршрутов.
bunx --bun text mcp # serve ./app over stdiobunx --bun text mcp apps/console # another projectДля text mcp нужен Bun. При запуске через Node он перезапускает себя через Bun, если bun есть в вашем PATH, а иначе завершается с ошибкой TX019. Клиенты MCP запускают его за вас, поэтому вручную вы запускаете его редко.
Настройка
Каждый клиент запускает text mcp из каталога вашего проекта. Используйте bunx --bun, чтобы файлы маршрутов выполнялись как TypeScript.
OpenCode
{ "$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Или добавьте конфигурацию в репозиторий для команды:
{ "mcpServers": { "textjs": { "command": "bunx", "args": ["--bun", "text", "mcp"] } }}Cursor
{ "mcpServers": { "textjs": { "command": "bunx", "args": ["--bun", "text", "mcp"] } }}VS Code
{ "servers": { "textjs": { "type": "stdio", "command": "bunx", "args": ["--bun", "text", "mcp"] } }}Для проекта в подкаталоге допишите путь: ["--bun", "text", "mcp", "apps/console"].
Инструменты
| Инструмент | Описание |
|---|---|
list_routes | Страницы и обработчики маршрутов в app/, а также текущее местоположение |
navigate | Добавление или замена пути, переход back / forward |
read_frame | Кадр в виде текста с маршрутом, размером, фокусом и курсором. styles: true добавляет цвета и атрибуты для каждого span |
press | Сочетания клавиш: "down", "ctrl+k", "shift+tab", "tab tab enter", "down*3" |
type | Ввод текста с нажатием клавиши для каждого символа (до 2000; для большего объёма используйте paste). submit: true нажимает Enter после ввода |
paste | Одно событие вставки, как ⌘V |
scroll | Прокрутка колёсиком мыши на rows (и cols). <ScrollBox> прокручивается колёсиком без клавиатурного фокуса |
resize | Установка размера сетки в столбцах и строках |
inspect_tree | Дерево компонентов с границами на экране и всеми id элементов, способных получить фокус |
find | Границы каждого совпадения и компонент под ним |
wait_for | Ожидание появления или исчезновения текста либо перехода приложения в состояние покоя |
logs | Перехваченный вывод console.* и ошибки React |
fetch | Вызов обработчика route.ts внутри процесса. Тела читаются до maxBytes; потоки событий читаются в течение streamMs, затем отменяются |
reload | Повторный импорт приложения после изменения файлов |
Инструменты ввода возвращают новый кадр, поэтому агенту не нужен отдельный вызов read_frame после каждой клавиши. Текущий кадр и список маршрутов доступны и как ресурсы text://frame и text://routes.
Типичный обмен:
→ 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 …Кадры и состояние покоя
Прежде чем прочитать кадр, любой инструмент ждёт перехода приложения в состояние покоя: нет запланированного кадра, нет выполняющегося fetch, а за последние 50 мс не было ни коммита, ни кадра. Поэтому агент видит результат своего ввода, а не кадр посреди обновления.
<Spinner> или график, обновляющийся раз в секунду, всё равно считается находящимся в покое между обновлениями, поэтому страницы с анимацией остаются читаемыми. Приложение, которое рендерится чаще, чем раз в 50 мс, никогда не переходит в состояние покоя. Через 1,5 с чтение прекращает ожидание (settleMs меняет этот срок), выводит последний кадр и помечает его как busy. wait_for без текста ждёт состояния покоя со своим значением quietMs и возвращает ошибку, если покой так и не наступает.
Координаты задаются в ячейках с отсчётом от нуля: x — столбец, y — строка. Широкие символы, такие как CJK и эмодзи, занимают два столбца.
Ошибки и журналы
stdout передаёт данные протокола. Вывод console.* вашего приложения, посторонние вызовы process.stdout.write и ошибки React вместо этого попадают в logs с нумерацией, чтобы агент мог запросить только новые записи через since. Если с момента последнего чтения были записаны ошибки, заголовок следующего кадра сообщает об этом.
Сбои возвращаются как ошибки инструментов и не приводят к аварийному завершению. Инструмент, превысивший время ожидания, отменяется до запуска следующего вызова, поэтому медленный вызов никогда не вводит текст в кадр следующего вызова. Ошибки с кодом Text.js сохраняют его, например TX007 для неизвестной клавиши. Если приложение не загружается, каждый инструмент объясняет причину. Исправьте файл и вызовите reload.
Параметры
| Параметр | Описание |
|---|---|
--path <pathname> | Начальный маршрут (по умолчанию /) |
--cols / --rows | Размер сетки (по умолчанию: viewport в text.config.ts, иначе 100×32) |
--theme <name> | Предустановка темы |
--no-config | Игнорирование text.config.ts |
--verbose | Запись перехваченных журналов ещё и в stderr |
Принцип работы
text mcp запускает ваше приложение так же, как text run: все модули маршрутов импортируются заранее, App Router использует историю в памяти, а вызовы fetch("/api/…") к тому же источнику запускают ваши обработчики внутри процесса. Кадры отправляются во внеэкранную сетку ячеек вместо терминала. Отрендеренные на сервере страницы (export const runtime = "server") и их компоненты async function рендерятся внутри процесса, как в text dev, и кадр считывается после их завершения.
reload размонтирует приложение, удаляет все модули проекта из кеша модулей Bun (включая файлы вне app/), повторно сканирует app/ и монтирует приложение в текущем местоположении.
Чтобы встроить сервер в собственный процесс или использовать другой транспорт:
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();Ограничения
- Приложение работает в Bun, а не в браузере.
windowиdocumentне определены, как и вtext run. - Каждый сервер запускает одно приложение с одной сеткой. Каждый клиент MCP запускает собственный сервер.
text mcpзапускает собственную копию приложения без графического интерфейса. Он не может подключиться к вкладке браузера, открытой черезtext dev.