# v1.14.1-mcp-poc — single-server MCP client proof-of-concept Validate the MCP-client loop end-to-end against one real MCP server (Context7) before committing to the full opencode `mcp/index.ts` port at v1.15. Small, throwaway-if-needed. ## Why BooCode's tool registry (`ALL_TOOLS` in `tools.ts`) is static — tools are hardcoded TypeScript modules. MCP is the protocol for dynamic tool discovery. Wiring one real MCP server end-to-end proves: tool-discovery → tool-list → tool-call → result-render → context-budget accounting all hold. If Context7 works, any MCP server will work via the same plumbing. ## Scope ### S1. Install `@modelcontextprotocol/sdk` New dependency in `apps/server/package.json`. The official TypeScript MCP client SDK (MIT). Provides `Client`, `StreamableHTTPClientTransport`, tool-call/result types. ### S2. New service: `apps/server/src/services/mcp-client.ts` Singleton MCP client that: 1. Connects to Context7 at `MCP_CONTEXT7_URL` (default `https://mcp.context7.com/mcp`) via Streamable HTTP transport. 2. Optional `MCP_CONTEXT7_API_KEY` env var passed as a header. 3. On `initialize()`: calls `tools/list`, wraps each MCP tool as a `ToolDef`, prefixes names with `context7_` to avoid collisions with BooCode's native tools. 4. **Read-only invariant guard:** rejects any tool whose `annotations?.readOnly` is explicitly `false`. Tools with `readOnly: true` or no `annotations` field are accepted (fail-open on read-only, since most MCP tools don't set annotations yet — Context7's tools don't). 5. `callTool(name, args)` → calls the MCP server's `tools/call` endpoint and returns the result content. 6. `getTools(): ToolDef[]` → returns the discovered tools wrapped as BooCode `ToolDef` objects. 7. Graceful degradation: if the MCP server is unreachable at startup, log a warning and expose zero MCP tools. BooCode functions normally with its native tools. ### S3. Config extension `apps/server/src/config.ts` gains two optional env vars: - `MCP_CONTEXT7_URL` (string, default `https://mcp.context7.com/mcp`) - `MCP_CONTEXT7_API_KEY` (string, optional) ### S4. Tool registration `apps/server/src/services/tools.ts` — after building `ALL_TOOLS` from native tools, append MCP-discovered tools from `mcpClient.getTools()`. The alpha-sort at the end of `ALL_TOOLS` construction covers both native and MCP tools. `TOOLS_BY_NAME` map includes MCP tools. MCP tools are registered with `category: 'read_only'` (per the read-only invariant guard in S2). ### S5. Tool dispatch `apps/server/src/services/inference/tool-phase.ts` `executeToolCall` already dispatches via `TOOLS_BY_NAME[toolName].execute(...)`. MCP tools' `execute` function calls `mcpClient.callTool(name, args)` — the dispatch is transparent to the rest of the inference loop. No changes to `executeToolCall` needed. ### S6. MCP tool result → BooCode format MCP `tools/call` returns `{ content: [{type: 'text', text: string}, ...] }`. BooCode's `executeToolCall` expects a string or JSON-serializable output. The `execute` wrapper in the ToolDef extracts `content[0].text` (or joins multiple content blocks with `\n`). If the MCP server returns an error, the wrapper returns `{error: true, output: errorMessage}` matching BooCode's existing error-result shape. ### S7. Startup initialization `apps/server/src/index.ts` — after `applySchema()` and before route registration, call `mcpClient.initialize()`. If `MCP_CONTEXT7_URL` is not set (or empty), skip initialization entirely (MCP is opt-in). Log the number of discovered tools on success. Tool registration (S4) must happen AFTER MCP initialization, since `getTools()` returns the discovered tools. Current flow: `ALL_TOOLS` is a module-level constant. This needs to change to a lazy-init pattern — either a function that returns the tool list (called once at startup after MCP init), or a mutable array that MCP tools get appended to during startup. ### S8. Agent tool whitelist interaction MCP tools are prefixed `context7_*`. Existing agents' `tools:` whitelists don't include MCP tool names — so MCP tools are only available to the default agent (no agent selected, which gets ALL_TOOLS). To make MCP tools available to specific agents, their AGENTS.md `tools:` list would need to include `context7_*` names. For the PoC, this is fine — the default agent (most common) gets MCP tools. ## Non-goals - No stdio transport. Context7 is HTTP-only. - No OAuth. Context7 uses an API key header. - No multiple servers. One hardcoded server (Context7). - No per-agent MCP server allow/deny. All agents that don't have a `tools:` whitelist get MCP tools. - No per-session MCP toggle. If configured, MCP tools are always available. - No UI changes. MCP tools surface in the tool list the model sees; results render as normal tool-result parts. - No schema changes. MCP state is in-memory only. ## Hard rules - No git commit/push. Sam commits. - Read-only invariant: reject any MCP tool with `readOnly: false`. - Graceful degradation: MCP server down → zero MCP tools, BooCode works normally. - One new dep only: `@modelcontextprotocol/sdk`. - Alpha-sort of ALL_TOOLS preserved (v1.13.3 prompt-cache invariant). ## Files expected to touch - `apps/server/package.json` — add `@modelcontextprotocol/sdk` - `pnpm-lock.yaml` — auto-updated - `apps/server/src/config.ts` — `MCP_CONTEXT7_URL`, `MCP_CONTEXT7_API_KEY` - `apps/server/src/services/mcp-client.ts` — NEW, ~100 lines - `apps/server/src/services/tools.ts` — lazy-init or append MCP tools to ALL_TOOLS - `apps/server/src/index.ts` — call `mcpClient.initialize()` at startup - `apps/server/src/services/__tests__/mcp-client.test.ts` — NEW, unit tests for tool wrapping + read-only guard ## Estimate ~150 LoC. The MCP SDK handles the protocol; BooCode's job is wrapping discovered tools as ToolDefs and routing calls through the SDK client. ## Smoke plan 1. Set `MCP_CONTEXT7_URL=https://mcp.context7.com/mcp` in `.env` (or docker-compose env). 2. Restart boocode container. 3. Check logs: should see "mcp: initialized Context7, discovered N tools" (or similar). 4. Open a chat with no agent selected. Send "What does the `streamText` function do in the AI SDK? Use context7 to look it up." 5. Confirm: model calls `context7_resolve-library-id` then `context7_query-docs` (or whatever Context7's tool names are after prefixing). 6. Confirm: tool results render normally in the chat. 7. Without `MCP_CONTEXT7_URL` set: restart, confirm BooCode starts normally with zero MCP tools.