amico-run(#112): B5 — real MCP stdio facade for amico mcp-serve - #126
amico-run(#112): B5 — real MCP stdio facade for amico mcp-serve#126aarontrowbridge wants to merge 1 commit into
amico mcp-serve#126Conversation
Land the OPTIONAL MCP facade (spec §7.3): `amico mcp-serve` now stands up a real @modelcontextprotocol/sdk stdio transport that exposes the spine verbs as MCP tools. One impl, two transports — tools/call dispatches to the SAME verb function (verbs.ts) the CLI uses; the CLI and MCP paths never fork. - add @modelcontextprotocol/sdk@^1.29.0 to packages/amico-run - mcp_serve.ts: createMcpServer() wires list-tools → verb↔tool mapping and call-tool → callMcpTool (the shared verb spine); serve() attaches the stdio transport and shuts down cleanly on client disconnect / stdin EOF - amico.ts: mcp-serve is real (usage + header updated); --list still renders the mapping transport-free - tests: mcp_serve.test.ts — pure mapping, direct dispatch, an in-memory Client↔Server round-trip, and a REAL stdio round-trip against the built bundle; amico.test.ts no-flag test updated to the real (EOF-terminated) server S31 AMENDMENT (governance — needs Jack review): test/s31.test.ts carves the `modelcontextprotocol` ban out for EXACTLY ONE file (src/mcp_serve.ts), mirroring spec C's single-file SolveSpec carve-out. Only the MCP-SDK pattern is lifted, and only for that file — mcp_serve.ts stays under the HTTP/fetch bans (stdio, never network), and every other src file stays under the full ban. Two new assertions pin the carve-out to one file so it cannot silently widen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Retargeted from The old base is now fully merged into (Part of a sweep over the stacked |
|
@aarontrowbridge — rebased this onto current But the rebase surfaced something that isn't in this PR's design — it appeared because
|
Closes #112. Stacks on #118 (B1,
b1-amico-cli-router) — base this PR's diff on that branch; merge #118 first.What this lands
The OPTIONAL MCP facade (spec-20260708-112732 §7.3). B1 shipped
amico mcp-serveas an SDK-free stub; B5 wires the real@modelcontextprotocol/sdkstdio transport. The spine verbs (catalog/vault/device/note) are exposed as MCP tools, and atools/calldispatches to the same verb function the CLI uses — one impl, two transports, no fork.Verb ↔ MCP-tool mapping
tools/list)VerbinSPINE_VERBS→ one MCP toolamico_<name>,description = verb.summary,inputSchema={ argv: string[] }(viaverbToMcpTool/listMcpTools)tools/call)amico_<name>→callMcpTool(name, argv)→ the sameverb.run(argv)the CLI dispatches; the verb's JSON result is returned as an MCP text block, non-zero exit →isErrorserve()attachesStdioServerTransport;--liststill renders the mapping transport-freecreateMcpServer()registers both request handlers against the shared spine and attaches no transport, so it's exercised over an in-memoryClient↔Serverpair in tests;serve()adds stdio and shuts down cleanly on client disconnect / stdin EOF (the SDK'sStdioServerTransportwatches stdindata/erroronly, not EOF, soserve()closes the server on stdin end to avoid a hang).dist/amico.jsbundles the SDK (620 kb);dist/amico-run.jsstays SDK-free (340 kb) —cli.tsnever imports the facade.This is the key review item. The fork's S31 guard (
packages/amico-run/test/s31.test.ts) bans the literalmodelcontextprotocol(plus HTTP/fetch) anywhere insrc/. Landing a real MCP transport requires lifting that ban — done here as a narrow, named carve-out, mirroring the precedent where spec C amended the S31 SolveSpec ban with a single named carve-out (amico-run = launch gate) rather than a blanket removal.Exactly what changed in
s31.test.ts:/modelcontextprotocol/ipattern is pulled into a namedMCP_SDK_PATTERNconst and an allowlistMCP_SDK_ALLOWED = new Set(["mcp_serve.ts"]);MCP_SDK_PATTERNfor onlymcp_serve.ts(if (re === MCP_SDK_PATTERN && MCP_SDK_ALLOWED.has(f)) continue;);mcp_serve.tsis still subject to the HTTP (node:https?) andfetch(bans (the transport is stdio, never network) and the physics-flag bans; every othersrc/file stays under the full ban including MCP;No carve-out for orchestrator/harness code. The no-MCP/HTTP guard remains in full force everywhere except the one facade module.
Tests
pnpm --filter @amicode/amico-run test→ 18 files, 128 tests pass;pnpm typecheckclean. Newtest/mcp_serve.test.ts(6 tests): pure mapping, directcallMcpTooldispatch, an in-memoryClient↔Serverround-trip, and a real stdio round-trip spawning the builtamico mcp-servebundle viaStdioClientTransport(provestools/list+tools/callend-to-end over actual stdio). Amendeds31.test.ts(3 tests) and updatedamico.test.ts(14 tests, no-flag test now drives the real EOF-terminated server).Do not merge
Awaiting Jack's governance sign-off on the S31 amendment, and #118 (B1) first.
🤖 Generated with Claude Code