Files
msd-core/docs/how-to/connect-msd-mcp-server.md
Jakub Zych 6cfa0c55d2 refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity
Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes,
cline, codebuddy and pi end to end: capability descriptors, installer branches
and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters,
hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi
migrations, Kimi payload normalization in the hook guards, dead hostBehaviors
vocabulary, launcher home probes, fixtures, runtime-specific tests and the
prose that presented them as supported.

Installer output for the six kept runtimes is byte-identical to before the
prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and
read-injection-scanner are left in place pending a decision.
2026-10-06 20:02:40 +02:00

181 lines
7.1 KiB
Markdown

# How to connect a host to the MSD companion MCP server
This guide shows you how to make a MCP-capable host (Claude Code, Codex,
OpenCode, VS Code, Antigravity CLI, Cursor) drive
MSD — run MSD
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:
`msd_invoke_command`, `msd_read_state`, `msd_write_state`. The server also
serves a read-only **catalog** of MSD's own content — workflows and
references as MCP resources, and the `/msd-*` commands as MCP prompts — so a
host can browse and pull that content directly instead of shelling out to the
CLI. (For the tool contracts, see the reference section below; for *why* this
server exists and its trust model, see
[ADR-1239](../adr/1239-msd-embeddable-orchestration-engine.md) and the
[capability trust model](../explanation/capability-trust-model.md).)
## 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.
```jsonc
{
"msd": {
"command": "npx",
"args": ["-y", "@golem15/msd-core", "msd-mcp-server"],
"cwd": "/abs/path/to/your/project"
}
}
```
- **Claude Code / Codex / Cursor** — under the host's
`mcpServers` object (project or user config).
- **VS Code** — in the workspace MCP servers list.
- **Antigravity** — under the `mcpServers` block of its standalone
`mcp_config.json` profile (not embedded in `settings.json`) — global at
`~/.gemini/antigravity/mcp_config.json` (or the sibling
`antigravity-ide`/`antigravity-cli` dir MSD resolved into), project-local at
`.agents/mcp_config.json`. MSD's installer configures this entry
automatically (`--antigravity` installs).
- **OpenCode** — under the `mcp` key (**not** `mcpServers`), in
`~/.config/opencode/opencode.jsonc` (global) or `./opencode.json`
(project). The entry shape also differs — see below.
Set `cwd` to the project whose `.planning/` you want MSD to manage — the server
resolves state paths against it.
### OpenCode entry shape
OpenCode uses a
`type`/`command`/`timeout` entry under the `mcp` key instead of the generic
`command`/`args`/`cwd` form above:
```jsonc
{
"mcp": {
"msd": {
"type": "local",
"command": ["npx", "-y", "@golem15/msd-core", "msd-mcp-server"],
"timeout": 10000
}
}
}
```
## 2. Restart the host
On startup the host performs the MCP `initialize` handshake. The response
advertises `tools`, `resources`, and `prompts` capabilities, so the three MSD
tools become callable and the host can also list the served catalog
(resources and prompts) described below. The server never advertises
`resources.subscribe` or `listChanged` — the catalog is fixed for the life of
the server process, so there is nothing to subscribe to.
## 3. Verify
Ask the host to read an existing planning file:
```jsonc
{ "name": "msd_read_state", "arguments": { "path": "/abs/path/to/your/project/.planning/STATE.md" } }
```
It returns the file's contents. `msd_invoke_command` takes
`{family, subcommand, args}` and returns the command-routing hub's structured
result (the same shape `msd-tools` produces).
## 4. Browse the catalog (resources and prompts)
The server also exposes MSD's own content tree as MCP resources and the
`commands/msd/*.md` command set as MCP prompts. This is additive: the
file-copy install (the default for every runtime) is unchanged, and the
catalog only adds a way for an MCP-capable host to read the same content
directly over the protocol.
### List and read a resource
List available resources (paginated — ask the host to follow `nextCursor`
until it is absent):
```jsonc
{ "name": "resources/list", "arguments": { "cursor": null } }
```
Each entry has a `msd://<segment>/<relpath>` URI, where `<segment>` is
`workflows`, `references`, or `commands`, and `<relpath>` is the file's path
within that segment (for example, `msd://workflows/plan-phase.md`,
`msd://references/untrusted-input-boundary.md`, or
`msd://commands/plan-phase.md`). Commands appear in both surfaces: read one as
a resource to get its raw markdown, or get it as a prompt to have the host
treat it as an invocable message. Read one by URI:
```jsonc
{ "name": "resources/read", "arguments": { "uri": "msd://workflows/plan-phase.md" } }
```
An unknown or unrecognized URI (including any path-traversal or absolute-path
attempt) returns a JSON-RPC error rather than an empty or partial result.
### List and get a prompt
```jsonc
{ "name": "prompts/list", "arguments": {} }
```
Each entry is keyed by its bare command name — `plan-phase`, not a path. Get
one:
```jsonc
{ "name": "prompts/get", "arguments": { "name": "plan-phase" } }
```
An unknown prompt name returns a JSON-RPC error. `prompts/get` accepts an
`arguments` object but ignores it — no shipped command template takes
injected arguments today.
### Composed vs. verbatim content
Workflow resources (`msd://workflows/…`) are served **composed** — with
`<!-- msd:section -->` markers stripped — exactly as the installed file tree
gets them, while reference and command content is served **verbatim**, because
some reference and command docs use that marker syntax as a documented example
rather than a real marker.
## If something does not work
- **`command not found: msd-mcp-server`** — invoke via `npx` as shown above, or
install the package globally first (`npm i -g @golem15/msd-core`).
- **`msd_read_state` fails with ENOENT** — the path is resolved literally; pass
an absolute path under the project's `.planning/`.
- **The host lists no MSD tools** — confirm the server starts in isolation:
`npx @golem15/msd-core msd-mcp-server` then send an `initialize` request on
stdin; it writes a `protocolVersion` response and exits on EOF.
- **You manage multiple projects** — register one `msd` entry per project with a
distinct name and `cwd`; the server is stateless across projects.
## Reference — the three tools
| Tool | Arguments | Returns |
|------|-----------|---------|
| `msd_invoke_command` | `{family: string, subcommand: string, args?: unknown[]}` | the command-routing hub result (`{ok, …}`) as JSON text |
| `msd_read_state` | `{path: string}` | the file contents as text |
| `msd_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.
## Reference — the served catalog
| Method | Arguments | Returns |
|--------|-----------|---------|
| `resources/list` | `{cursor?: string}` | `{resources: [{uri, name, title, description, mimeType}], nextCursor?: string}` |
| `resources/read` | `{uri: string}` | `{contents: [{uri, mimeType, text}]}` |
| `prompts/list` | `{}` | `{prompts: [{name, title, description}]}` |
| `prompts/get` | `{name: string, arguments?: object}` | `{description, messages: [{role: "user", content: {type: "text", text}}]}` |
Errors from the catalog (unknown URI, unknown prompt name, malformed cursor,
a refused path-traversal attempt) are returned as JSON-RPC protocol errors,
not MCP tool errors — unlike the three tools above.