Files
msd-core/docs/how-to/connect-gsd-mcp-server.md
Tom Boucher 41193a44bd feat(#1681): ADR-1239 Phase C-2 — gsd-mcp-server bin entry + lifecycle test [slice 3b] (#1810)
* feat(#1681): ADR-1239 Phase C-2 — gsd-mcp-server bin entry + lifecycle test [slice 3b]

Phase 4 slice 3b (closes #1681). The companion MCP server bin entry so any
MCP-consuming host connects via 'npx gsd-mcp-server' (or its bin on PATH) and
gets GSD command (point 1) + state IO (point 5) with no bespoke plugin.

- gsd-core/bin/gsd-mcp-server.cjs: #!/usr/bin/env node shim requiring
  ./lib/mcp-server.cjs + runServer({stdin, stdout}); non-zero exit on fatal
  error (justified n/no-process-exit disable). Mirrors gsd-tools.cjs.
- package.json: add 'gsd-mcp-server' bin entry.
- tests/gsd-mcp-server-bin.test.cjs: 3 process-lifecycle tests — initialize +
  tools/list round-trip + clean exit, malformed-line -> parse error + server
  keeps running, empty stdin -> clean exit. Synchronous spawnSync (bounded;
  server exits on stdin EOF, no orphan).

Phase 4 trust-gate (#1806) + loader wiring (#1808) + server module (#1809) +
this bin/lifecycle slice = all of #1681's deliverables. Concrete host binding ->
Phase 5 (#1682). npm-integrity + eslint + security + inventory all clean.

* docs(#1681)+chore(changeset): how-to for the companion MCP server + Added fragment

docs/how-to/connect-gsd-mcp-server.md — Diataxis how-to guide for connecting
any MCP-capable host to gsd-mcp-server: goal-oriented flow (add config → restart
→ verify), real-world per-host conditionals, troubleshooting, and a trimmed
reference table. Explanation/reference linked out (ADR-1239, capability-trust-
model) per Diataxis boundary rules rather than mixed in.

.changeset/humble-seals-rest.md — type: Added (first user-reachable surface of
the epic: a new bin command). The how-to doc satisfies the docs-required gate.

* fix(#1681): move gsd-mcp-server shim to top-level bin/ (out of the runtime-copied tree)

The shim at gsd-core/bin/gsd-mcp-server.cjs was inside the tree the installer
copies into every runtime config dir, so it leaked into all 16 runtimes and
broke golden-install-parity. The MCP server is a PACKAGE bin the host spawns
(npx gsd-mcp-server), not a per-runtime artifact — so it belongs at top-level
bin/ alongside install.js (which is also never copied into a runtime config).

- gsd-core/bin/gsd-mcp-server.cjs -> bin/gsd-mcp-server.js (require path now
  ../gsd-core/bin/lib/mcp-server.cjs).
- package.json: bin entry -> bin/gsd-mcp-server.js.
- tests/gsd-mcp-server-bin.test.cjs: SHIM path updated.
- eslint.config.mjs: add bin/gsd-mcp-server.js to the bin/install.js block
  (drops the n/no-process-exit disable — the n plugin isn't loaded for that
  block, so the disable referenced an undefined rule).

golden-install-parity 16/16 restored; lifecycle + unit tests green; eslint 0;
lint:ci all ok.
2026-06-28 14:27:43 -04:00

3.0 KiB

How to connect a host to the GSD companion MCP server

This guide shows you how to make a MCP-capable host (Claude Code, Codex, OpenCode, VS Code, Gemini CLI, Cursor, Cline, Hermes) drive GSD — run GSD commands and read/write .planning/ state — through the companion MCP server, with no bespoke plugin.

Once connected, three tools appear in the host alongside its others: gsd_invoke_command, gsd_read_state, gsd_write_state. (For the tool contracts, see the reference section below; for why this server exists and its trust model, see ADR-1239 and the capability trust model.)

1. Add the server to your host's MCP config

The entry shape is the same everywhere; only the config file and key differ by host.

{
  "gsd": {
    "command": "npx",
    "args": ["-y", "@opengsd/gsd-core", "gsd-mcp-server"],
    "cwd": "/abs/path/to/your/project"
  }
}
  • Claude Code / Codex / OpenCode / Cursor / Cline / Hermes — under the host's mcpServers object (project or user config).
  • VS Code — in the workspace MCP servers list.
  • Gemini CLI — under its mcpServers block.

Set cwd to the project whose .planning/ you want GSD to manage — the server resolves state paths against it.

2. Restart the host

On startup the host performs the MCP initialize handshake, lists tools, and the three GSD tools become callable.

3. Verify

Ask the host to read an existing planning file:

{ "name": "gsd_read_state", "arguments": { "path": "/abs/path/to/your/project/.planning/STATE.md" } }

It returns the file's contents. gsd_invoke_command takes {family, subcommand, args} and returns the command-routing hub's structured result (the same shape gsd-tools produces).

If something does not work

  • command not found: gsd-mcp-server — invoke via npx as shown above, or install the package globally first (npm i -g @opengsd/gsd-core).
  • gsd_read_state fails with ENOENT — the path is resolved literally; pass an absolute path under the project's .planning/.
  • The host lists no GSD tools — confirm the server starts in isolation: npx @opengsd/gsd-core gsd-mcp-server then send an initialize request on stdin; it writes a protocolVersion response and exits on EOF.
  • You manage multiple projects — register one gsd entry per project with a distinct name and cwd; the server is stateless across projects.

Reference — the three tools

Tool Arguments Returns
gsd_invoke_command {family: string, subcommand: string, args?: unknown[]} the command-routing hub result ({ok, …}) as JSON text
gsd_read_state {path: string} the file contents as text
gsd_write_state {path: string, content: string} {ok: true, path} as JSON text

Errors from a tool are returned as MCP tool errors (isError: true), not as JSON-RPC protocol errors — the host surfaces them in its normal tool-failure UX.