Saltar al contenido principal
@botmesh/mcp es un servidor MCP (Model Context Protocol) por stdio. Le da a un agente de código (o a un editor) las piezas deterministas del proyecto para escribir, validar, compilar, dibujar, lintear, migrar y comparar un documento de flujo sin arrancar un bot: sin teléfono, sin credenciales, sin red. Cada herramienta es una función de sus argumentos. El servidor no tiene dependencias de terceros (el transporte JSON-RPC está implementado en el paquete), nunca evalúa código y nunca escribe en disco.

Arrancarlo

Los clientes MCP lo arrancan con npx; no hay que instalar nada antes:

Registrarlo en tu cliente

Claude Code:
o, compartido con el proyecto, .mcp.json:
Cursor (.cursor/mcp.json) y Claude Desktop (claude_desktop_config.json) usan la misma forma mcpServers. VS Code (.vscode/mcp.json):
En Windows, un cliente que no puede arrancar npx directamente necesita "command": "cmd", "args": ["/c", "npx", "-y", "@botmesh/mcp"].

Herramientas de documentación

--docs <dir> (o BOTMESH_DOCS_DIR; el nombre anterior, BOTWA_DOCS_DIR, se sigue leyendo) apunta a un árbol Markdown local, como la carpeta docs/ de un clon del repositorio; las guías no van dentro del paquete. Sin él las dos herramientas de docs no se anuncian en tools/list, así que un cliente nunca ve una herramienta que no puede funcionar.

Desde el repositorio

En VS Code el repo ya trae .vscode/mcp.json: ejecuta pnpm build una vez y arranca el servidor botmesh desde la vista MCP.

Por programa

La entrada principal (createMcpServer, serveStream, runStdio, las revisiones del protocolo, la identidad del servidor y EXAMPLE_FLOW_DOCUMENT) sigue semver; el framing JSON-RPC, el catálogo de herramientas y la búsqueda en la documentación viven en @botmesh/mcp/internal, sin garantía semver.

Herramientas

flow_analyze ejecuta las mismas describeFlows() y lintFlows() que usan bot.describe() y bot.lint(), y flow_* envuelve @botmesh/flow-schema, así que el servidor no puede contradecir al runtime ni al gate de despliegue.

«Válido» o «inválido» es un resultado, no un error

Un documento inválido es una llamada correcta cuyo payload dice valid: false con los problemas:
isError: true queda reservado para la petición (argumentos ausentes o mal tipados, una versión que no se puede migrar, una ruta fuera del directorio de docs), así que un agente distingue «mi documento necesita trabajo» de «mi llamada estaba mal». Los argumentos se comprueban contra el inputSchema que anuncia tools/list antes de ejecutar la herramienta: una clave desconocida, una obligatoria que falta, un valor fuera de un enum o de un rango (format, direction, target hasta la versión actual del documento, limit de 1 a 25) o un valor anidado a más de 64 niveles falla con una frase que lo nombra, por ejemplo invalid arguments for flow_validate: unknown argument "doc" (accepted: "document"). Los textos de error nunca llevan rutas absolutas: una página que no existe contesta page not found: guide/x.md.

Protocolo

  • initialize responde con capabilities: { tools: { listChanged: false } }, la versión del paquete y un bloque instructions con las reglas caras de equivocar (la API v1 no existe, los documentos son datos, un handler no debe hacer await de la conversación que inicia, los ids son opacos).
  • Revisiones soportadas: 2025-06-18, 2025-03-26, 2024-11-05. A un cliente que pide otra se le contesta 2025-06-18 y él decide qué hacer.
  • Cada herramienta lleva annotations (readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false), así que un cliente puede ejecutarlas sin pedir confirmación.
  • ping, tools/list y tools/call; cualquier otra cosa (resources/*, prompts/*, logging/setLevel) contesta -32601, porque esas capabilities no se anuncian.
  • Las notificaciones nunca se contestan (JSON-RPC lo prohíbe), incluida notifications/initialized.
  • Una respuesta enviada por el cliente (un mensaje con result o error y sin method) se ignora; nunca se contesta.
  • Un mensaje JSON por línea. Una línea malformada contesta -32700, una línea más larga que maxMessageLength (16 Mi caracteres por defecto, DEFAULT_MAX_MESSAGE_LENGTH) contesta -32600 sin acumularse en memoria, y la sesión sigue. Un array JSON es un batch (revisión 2025-03-26): se contesta con un array de respuestas, sin las notificaciones.
  • Por stdout solo salen mensajes del protocolo; los diagnósticos van a stderr. Si el cliente cierra la tubería, el servidor deja de escribir y termina cuando acaba stdin, sin un EPIPE sin manejar. Un directorio --docs que no existe se avisa por stderr al arrancar.

Búsqueda en la documentación

Con --docs, docs_search indexa cada .md del directorio salvo la salida generada (api/, dist/, .vitepress/) y las notas de diseño internas (superpowers/), y las parte en secciones por heading (un # dentro de un bloque de código no es un heading). El ranking es determinista y deliberadamente simple: términos sin acentos ni mayúsculas, frecuencia inversa de documento para que un identificador raro (appendLog) gane a una palabra que aparece en todas las páginas (durabilidad), presencia en vez de conteos brutos, doble peso si el término está en el heading, bonus por cobertura y por frase exacta. Las páginas en inglés y en español se indexan juntas, así que una consulta alcanza a las dos. docs_page solo lee archivos .md y rechaza cualquier ruta que se salga del directorio de docs.

Tests

pnpm --filter @botmesh/mcp test cubre el framing (mensajes partidos entre chunks, \r\n, una última línea sin salto, chunks en UTF-8, batches, líneas demasiado largas, un fuzz con semilla de chunks crudos), la comprobación de argumentos, cada herramienta con entrada válida e inválida, los 14 tipos de paso, la resolución de subflows, la migración de un documento sin versión, una búsqueda bilingüe sobre un árbol temporal (incluido el rechazo de ../) y la sincronización de la versión con package.json.