Skip to content

<objective> Build the MCP stdio server over the shared dispatcher with revision 2026-07-28 probe-plus-initialize handling.

Purpose: Deliver the MCP half of TOOL-05 per P5-D2 and P5-D9, in files owned only by this plan, holding no tool logic. Output: Server module plus its test file. </objective>

<execution_context> @~/.claude/gsd-core/workflows/execute-plan.md @~/.claude/gsd-core/templates/summary.md </execution_context>

<context> @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/05-omp-plugin-mcp-and-acp-surfaces/05-CONTEXT.md @docs/integration/mcp.md </context>

<tasks>

<task type="tdd"> <name>Task 1: stdio framing with stdout purity (P5-D2, P5-D9)</name> <files>src/mcp/server.ts, test/mcp/server.test.ts</files> <read_first> - docs/integration/mcp.md Q2 (newline-delimited JSON-RPC, no embedded newlines, stdout carries only MCP messages, stderr for logs, shutdown by closing stdin) - docs/integration/mcp.md Q5 (from plan 05-02; the revision handling this task implements) - src/core/errors.ts lines 6-30 (ERROR_CODES the refusal envelope draws from; no code added) </read_first> <behavior> - Test (TOOL-05): a well-formed request written to the input stream yields exactly one newline-terminated JSON-RPC message on stdout and nothing else - Test (TOOL-05): server logging and malformed-input diagnostics go to stderr, never stdout - Test (TOOL-05): closing stdin shuts the server down without hanging - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then implement the stdio framing in src/mcp/server.ts per P5-D2: split stdin on newlines into JSON-RPC messages, reject embedded-newline payloads, serialize each reply as one newline-terminated JSON object on stdout, write all diagnostics to stderr, and stop reading when stdin closes. Use only node:readline or node:stream plus JSON; add no npm dependency. Open the module with a header comment naming P5-D2 and P5-D9. (Implements the P5-D2 framing half.)</action> <acceptance_criteria> - test/mcp/server.test.ts asserts one-line-per-message stdout purity, stderr-only diagnostics, and close-on-stdin-end - package.json gains no dependency </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/mcp/server.test.js</automated> </verify> <done>Framing green with stdout purity proven.</done> </task>

<task type="tdd"> <name>Task 2: Revision handshake and six-tool routing over the dispatcher (P5-D9, P5-D11)</name> <files>src/mcp/server.ts, test/mcp/server.test.ts</files> <read_first> - src/mcp/server.ts (from Task 1 of this plan; the framing the handshake and routing ride on) - docs/integration/mcp.md Q1 (server/discover probe first, initialize fallback, backward compatibility) and Q5 (the recorded handling) - src/tools/dispatch.ts lines 109-148 (call and the six ToolCall branches to route to) - src/contract/validate.ts lines 62-76 (ToolCall map shape carried unchanged) - test/support/fake-tools.ts lines 153-200 (makeHarness dispatcher the test drives) </read_first> <behavior> - Test (TOOL-05): the server answers the server/discover probe and the initialize handshake per the Q5 record - Test (TOOL-05): each of the six tools routes to ToolDispatcher.call with the ToolCall map unchanged (asserted on the dispatcher's recorded call order from the harness) - Test (TOOL-05): a dispatcher refusal surfaces the catalogue code in the reply envelope without server-added logic - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then implement the handshake and routing in src/mcp/server.ts per P5-D9 and P5-D11: answer the server/discover probe for modern clients and the initialize handshake for initialize-based clients exactly as mcp.md Q5 records; expose the six tools as the server's tool surface with their tool schemas drawn from the frozen tools schema shape; convert each incoming tool request into the ToolCall map unchanged and return ToolDispatcher.call's ToolOutcome inside the reply envelope, mapping a refusal to the catalogue code with no added decision. The module imports the dispatcher type only and calls call, nothing deeper. (Implements P5-D9 handshake and the P5-D11 routing half.)</action> <acceptance_criteria> - test/mcp/server.test.ts asserts probe plus initialize answers, unchanged ToolCall routing for all six tools, and code-faithful refusal envelopes - src/mcp/server.ts calls no dispatcher member except call </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/mcp/server.test.js</automated> </verify> <done>Handshake and six-tool routing green over one dispatcher.</done> </task>

<task type="tdd"> <name>Task 3: Stdio-client-shaped end-to-end drive (P5-D1, TOOL-05)</name> <files>src/mcp/server.ts, test/mcp/server.test.ts</files> <read_first> - src/mcp/server.ts (from Tasks 1-2 of this plan; the server the driver talks to) - test/support/fake-tools.ts lines 21-35 (ADVISORY and REQUIRED policies the two-mode drive uses) - src/core/status.ts lines 38-54 (displayStatus the status reply carries) </read_first> <behavior> - Test (TOOL-05): a stdio-client-shaped driver (writes request lines, reads reply lines) completes a status call and a write call end to end against the harness dispatcher - Test (TOOL-05): the status reply carries the shared displayStatus label, not a server-invented label - Test titles cite TOOL-05 </behavior> <action>Write the failing tests first per behavior, then add the client-shaped drive to test/mcp/server.test.ts per P5-D1: wire the server to in-memory stdin/stdout streams, speak to it as a stdio MCP client would (probe or initialize first, then tool requests), and assert the replies match the dispatcher's own ToolOutcome values from the harness. Cover status (label proves the shared mapping) and one mutating tool. The server module gains no new behavior in this task; only the test drives it end to end. (Implements the P5-D1 client-compatibility half in-process.)</action> <acceptance_criteria> - The driver test completes probe-or-initialize plus two tool calls over in-memory stdio with no real process - Reply payloads equal the harness dispatcher's own outcomes </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/mcp/server.test.js</automated> </verify> <done>Client-shaped drive green with dispatcher-equal replies.</done> </task>

</tasks>

<threat_model>

Trust Boundaries ​

BoundaryDescription
stdin bytes -> JSON-RPC parserUntrusted client bytes cross into the server; malformed lines are rejected to stderr with no stdout pollution
tool request -> dispatcherParams cross into ToolDispatcher.call unchanged; schema validation still runs first inside the pipeline

STRIDE Threat Register ​

Threat IDCategoryComponentSeverityDispositionMitigation Plan
T-05-05T (Tampering)src/mcp/server.tshighmitigateThe surface forwards the ToolCall map unchanged and calls only ToolDispatcher.call, so no client input can skip schema, gate, lock, or audit stages (P5-D11); the routing test proves the map is unchanged
T-05-06I (Information Disclosure)src/mcp/server.tsmediummitigateStdout carries only MCP messages and diagnostics go to stderr, so logs and malformed-input detail cannot leak into the client channel (P5-D2); the purity test pins this
</threat_model>

<verification> npx tsc -p tsconfig.json && node --test dist/test/mcp/server.test.js; grep test/mcp/server.test.ts for TOOL-05 in test titles. </verification>

<success_criteria> Stdio framing with stdout purity, probe-plus-initialize handling, six-tool routing with unchanged maps, and a client-shaped drive all green with no tool logic in the surface. </success_criteria>

Artifacts this phase produces (contribution of this plan) ​

  • src/mcp/server.ts (new): stdio JSON-RPC 2.0 server with 2026-07-28 probe-plus-initialize handling, routing all six tools to ToolDispatcher.call (P5-D2, P5-D9, P5-D11).
  • test/mcp/server.test.ts (new): TOOL-05 framing, handshake, routing, refusal, and client-shaped drive cases.

<output> Create .planning/phases/05-omp-plugin-mcp-and-acp-surfaces/05-03-SUMMARY.md when done </output>

AGPL-3.0-only. Copyright (C) 2026 Spectrum Web Co/swcstudiospace.