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>
4.4 KiB
4.4 KiB
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:McpServerConfigwithtype,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_URLandMCP_CONTEXT7_API_KEYfrom 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'sStdioClientTransport(check SDK exports; fallback tochild_process.spawn+ NDJSON if SDK doesn't expose it) callTool(prefixedName, args)— extract server name from prefix, route to correct clientgetTools()— return all tools from all servers, flattenedgetMcpServers()— 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 clientsapp.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()withmatchToolGlob() - Export
matchToolGlobfor test access
B7 — Example config file
- NEW
data/mcp.jsonwith Context7 entry (enabled: true, with URL, no API key) - Comment in the file noting it's bind-mounted at
/data/mcp.jsoninside the container
B8 — Tests
- Update
mcp-client.test.tsfor 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 errorspnpm -C apps/server test— all passingpnpm -C apps/web build— green (no web changes)
B10 — Deploy + smoke
- Create
/data/mcp.jsonon 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.mdentryboocode_roadmap.mdretrospective bullet on v1.15 sectionCLAUDE.md— update MCP references- Commit, tag
v1.15.0-mcp-multi, push, rebuild