v1.15.0-mcp-multi: multi-server MCP client + stdio transport + config file + tool globs
Generalizes the v1.14.1 single-server Context7 PoC into a multi-server MCP client registry with per-server graceful degradation. JSON config at /data/mcp.json (bind-mounted alongside AGENTS.md) matches opencode's mcpServers schema shape. Config file missing = no MCP (opt-in by presence). Two transports: Streamable HTTP (remote servers like Context7) and stdio (local subprocess servers like codecontext). Stdio spawns a persistent child via the SDK's StdioClientTransport; shutdown hook closes all transports. Tool prefix generalized from context7_<name> to <serverName>_<toolName> with a toolToServer reverse map for dispatch routing. AGENTS.md tools: field now supports glob patterns (context7_*, !web_*) via matchToolGlob — last-match- wins with ! deny prefix. Replaces exact-match .includes() in stream-phase.ts. refreshToolNames() in agents.ts rebuilds the DEFAULT_TOOLS snapshot after appendMcpTools so agents without explicit tools: lists see MCP tools — reviewer caught that the module-load-time snapshot would permanently exclude late-registered tools. Read-only invariant: readOnlyHint === false rejected at discovery. Result size capped at 5MB. v1.14.1 env vars removed — superseded by config file. Default data/mcp.json ships with Context7 disabled. 363/363 server tests passing. No schema changes, no frontend changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
87
openspec/changes/v1.15-mcp-multi/tasks.md
Normal file
87
openspec/changes/v1.15-mcp-multi/tasks.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# v1.15.0-mcp-multi tasks
|
||||
|
||||
## B1 — Backups
|
||||
|
||||
- [ ] `mcp-client.ts`, `config.ts`, `index.ts`, `agents.ts`, `mcp-client.test.ts`
|
||||
|
||||
## B2 — MCP config file schema + loader
|
||||
|
||||
- [ ] NEW `apps/server/src/services/mcp-config.ts` (~50 lines)
|
||||
- [ ] Zod schema for `mcp.json`: `McpServerConfig` with `type`, `url/command/args/env`, `headers`, `enabled`
|
||||
- [ ] `loadMcpConfig(configPath: string, log): McpServerConfig[]` — reads JSON, validates, returns enabled servers
|
||||
- [ ] Graceful: file missing → log info, return empty array (no MCP)
|
||||
- [ ] Graceful: parse error → log warn with details, return empty array
|
||||
|
||||
## B3 — Config.ts: replace MCP env vars
|
||||
|
||||
- [ ] Remove `MCP_CONTEXT7_URL` and `MCP_CONTEXT7_API_KEY` from Zod schema
|
||||
- [ ] Add `MCP_CONFIG_PATH: z.string().optional()` (no default — opt-in)
|
||||
|
||||
## B4 — Refactor mcp-client.ts to multi-server registry
|
||||
|
||||
- [ ] Replace module-level singleton with `Map<serverName, {client, transport, tools}>`
|
||||
- [ ] `initialize(servers: McpServerConfig[], log)` — iterate servers, connect each, discover tools, wrap with `<serverName>_<toolName>` prefix, apply read-only guard
|
||||
- [ ] Streamable HTTP transport: reuse existing pattern from v1.14.1
|
||||
- [ ] Stdio transport: use `@modelcontextprotocol/sdk`'s `StdioClientTransport` (check SDK exports; fallback to `child_process.spawn` + NDJSON if SDK doesn't expose it)
|
||||
- [ ] `callTool(prefixedName, args)` — extract server name from prefix, route to correct client
|
||||
- [ ] `getTools()` — return all tools from all servers, flattened
|
||||
- [ ] `getMcpServers()` — return status of each server (name, type, toolCount, connected)
|
||||
- [ ] Per-server graceful degradation: catch per-server errors, log, skip; continue with others
|
||||
- [ ] `shutdown()` — kill stdio child processes, close HTTP clients
|
||||
- [ ] `app.addHook('onClose')` calls shutdown
|
||||
|
||||
## B5 — Startup wiring (index.ts)
|
||||
|
||||
- [ ] Read config: `const mcpConfigPath = config.MCP_CONFIG_PATH ?? '/data/mcp.json'`
|
||||
- [ ] `const mcpServers = loadMcpConfig(mcpConfigPath, app.log)`
|
||||
- [ ] `await mcpClient.initialize(mcpServers, app.log)`
|
||||
- [ ] `appendMcpTools(mcpClient.getTools())`
|
||||
- [ ] Log summary: "mcp: N servers connected, M tools registered"
|
||||
- [ ] `app.addHook('onClose', () => mcpClient.shutdown())`
|
||||
|
||||
## B6 — AGENTS.md glob patterns
|
||||
|
||||
- [ ] `apps/server/src/services/agents.ts` — in tool whitelist validation, skip validation for entries containing `*` (can't validate against runtime-discovered tools)
|
||||
- [ ] NEW helper `matchToolGlob(toolName: string, patterns: string[]): boolean` — supports `*` wildcard and `!` deny prefix, last-match-wins
|
||||
- [ ] Wire into `executeStreamPhase` (stream-phase.ts) where agent tools are filtered: replace exact-match `.includes()` with `matchToolGlob()`
|
||||
- [ ] Export `matchToolGlob` for test access
|
||||
|
||||
## B7 — Example config file
|
||||
|
||||
- [ ] NEW `data/mcp.json` with Context7 entry (enabled: true, with URL, no API key)
|
||||
- [ ] Comment in the file noting it's bind-mounted at `/data/mcp.json` inside the container
|
||||
|
||||
## B8 — Tests
|
||||
|
||||
- [ ] Update `mcp-client.test.ts` for multi-server wrapping (tools from two servers, prefix routing)
|
||||
- [ ] Test: server A fails, server B succeeds — only B's tools registered
|
||||
- [ ] Test: callTool routes to correct server by prefix
|
||||
- [ ] Test: shutdown kills stdio transports
|
||||
- [ ] NEW `apps/server/src/services/__tests__/mcp-glob.test.ts`
|
||||
- [ ] Test: exact match ("grep" matches "grep")
|
||||
- [ ] Test: wildcard ("context7_*" matches "context7_query-docs")
|
||||
- [ ] Test: deny ("!web_*" excludes "web_search")
|
||||
- [ ] Test: last-match-wins ("*" then "!web_*" → web tools excluded)
|
||||
- [ ] Test: empty pattern list → nothing matches (agent gets no tools — same as current behavior for explicit whitelists)
|
||||
|
||||
## B9 — Verification
|
||||
|
||||
- [ ] `npx tsc --noEmit -p apps/server` — 0 errors
|
||||
- [ ] `pnpm -C apps/server test` — all passing
|
||||
- [ ] `pnpm -C apps/web build` — green (no web changes)
|
||||
|
||||
## B10 — Deploy + smoke
|
||||
|
||||
- [ ] Create `/data/mcp.json` on the host with Context7 enabled
|
||||
- [ ] Update docker-compose bind mount if needed (data/ already mounted)
|
||||
- [ ] `docker compose up --build -d`
|
||||
- [ ] Check logs for multi-server init
|
||||
- [ ] Live-smoke: Context7 tool call from chat
|
||||
- [ ] Disable Context7 in config, restart, confirm zero MCP tools
|
||||
|
||||
## B11 — Docs + tag
|
||||
|
||||
- [ ] `CHANGELOG.md` entry
|
||||
- [ ] `boocode_roadmap.md` retrospective bullet on v1.15 section
|
||||
- [ ] `CLAUDE.md` — update MCP references
|
||||
- [ ] Commit, tag `v1.15.0-mcp-multi`, push, rebuild
|
||||
Reference in New Issue
Block a user