Run it
MCP clients start it withnpx; nothing to install up front:
Register it in your client
Claude Code:.mcp.json:
.cursor/mcp.json) and Claude Desktop (claude_desktop_config.json) use the same mcpServers shape. VS Code (.vscode/mcp.json):
npx directly needs "command": "cmd", "args": ["/c", "npx", "-y", "@botmesh/mcp"].
Documentation tools
--docs <dir> (or BOTMESH_DOCS_DIR; the pre-rename BOTWA_DOCS_DIR is still read) points at a local Markdown tree, such as the docs/ folder of a clone of the repository; the guides are not bundled in the package. Without it the two docs tools are not advertised in tools/list, so a client never sees a tool that cannot work.
From a checkout
.vscode/mcp.json: run pnpm build once, then start the botmesh server from the MCP view.
Programmatically
createMcpServer, serveStream, runStdio, the protocol revisions, the server identity and EXAMPLE_FLOW_DOCUMENT) is covered by semver; the JSON-RPC framing, the tool catalogue and the docs search live in @botmesh/mcp/internal, with no semver guarantee.
Tools
flow_analyze runs the same describeFlows() and lintFlows() that bot.describe() and bot.lint() use, and flow_* wraps @botmesh/flow-schema, so the server can never disagree with the runtime or the deployment gate.
Valid or invalid is a result, not an error
An invalid document is a successful call whose payload saysvalid: false with the issues:
isError: true is reserved for the request (missing or mistyped arguments, an unmigratable version, a path outside the docs root), so an agent can tell “my document needs work” from “my call was wrong”.
The arguments are checked against the inputSchema that tools/list advertises, before the tool runs: an unknown key, a missing required one, a value outside an enum or a range (format, direction, target up to the current document version, limit from 1 to 25) or a value nested more than 64 levels deep fails with one sentence that names it, for example invalid arguments for flow_validate: unknown argument "doc" (accepted: "document"). Error texts never carry absolute paths: a missing page answers page not found: guide/x.md.
Protocol
initializeanswers withcapabilities: { tools: { listChanged: false } }, the package version and aninstructionsblock that states the invariants that are expensive to get wrong (the v1 API does not exist, documents are data, a handler must notawaita conversation it starts, ids are opaque).- Supported revisions:
2025-06-18,2025-03-26,2024-11-05. A client asking for another one is answered with2025-06-18and decides what to do. - Every tool carries
annotations(readOnlyHint: true,destructiveHint: false,idempotentHint: true,openWorldHint: false), so a client may run them without asking for confirmation. ping,tools/listandtools/call; anything else (resources/*,prompts/*,logging/setLevel) answers-32601, because those capabilities are not advertised.- Notifications are never answered (JSON-RPC forbids it), including
notifications/initialized. - A response sent by the client (a message with
resultorerrorand nomethod) is ignored, never answered. - One JSON message per line. A malformed line answers
-32700, a line longer thanmaxMessageLength(16 Mi characters by default,DEFAULT_MAX_MESSAGE_LENGTH) answers-32600without being buffered, and the session continues. A JSON array is a batch (revision2025-03-26): it is answered with an array of responses, notifications left out. - Nothing but protocol messages is written to stdout; diagnostics go to stderr. If the client closes the pipe, the server stops writing and exits when stdin ends, without an unhandled
EPIPE. A--docsdirectory that does not exist is reported on stderr at start-up.
Documentation search
With--docs, docs_search indexes every .md under the directory except generated output (api/, dist/, .vitepress/) and the internal design notes (superpowers/), and splits them into heading-delimited sections (a # inside a fenced code block is not a heading).
Ranking is deterministic and deliberately simple: accent- and case-insensitive terms, inverse document frequency so a rare identifier (appendLog) outranks a word that appears on every page (durabilidad), presence instead of raw counts, a doubled weight for heading hits, a coverage bonus and an exact-phrase bonus. English and Spanish pages are indexed together, so one query reaches both.
docs_page only reads .md files and rejects any path that escapes the docs root.
Tests
pnpm --filter @botmesh/mcp test covers the framing (messages split across chunks, \r\n, a last line without a newline, UTF-8 chunks, batches, oversized lines, a seeded fuzz of raw chunks), the argument checks, every tool with valid and invalid input, the 14 step kinds, subflow resolution, migration of a versionless document, a bilingual search over a temporary tree (including ../ rejection) and the version lockstep with package.json.