# v1.14.1-mcp-poc tasks ## B1 — Backups - [ ] `apps/server/src/services/tools.ts` - [ ] `apps/server/src/config.ts` - [ ] `apps/server/src/index.ts` ## B2 — Install `@modelcontextprotocol/sdk` - [ ] `pnpm -C apps/server add @modelcontextprotocol/sdk` - [ ] Verify `pnpm -C apps/server build` still works after install - [ ] Note the installed version ## B3 — Config extension - [ ] `apps/server/src/config.ts` — add `MCP_CONTEXT7_URL` (string, optional, default `https://mcp.context7.com/mcp`) - [ ] `apps/server/src/config.ts` — add `MCP_CONTEXT7_API_KEY` (string, optional) - [ ] Both via Zod `.optional()` with `.default()` for the URL ## B4 — MCP client service - [ ] NEW `apps/server/src/services/mcp-client.ts` - [ ] Import `Client`, `StreamableHTTPClientTransport` from `@modelcontextprotocol/sdk/client` - [ ] `initialize(config, log)` — connect to Context7, call `tools/list`, wrap each as ToolDef, apply read-only guard - [ ] `callTool(name, args)` — call MCP server `tools/call`, extract text content, return as string - [ ] `getTools()` — return wrapped ToolDef[] - [ ] `isInitialized()` — boolean - [ ] Read-only guard: skip tools with `annotations?.readOnly === false`; accept all others - [ ] Graceful degradation: catch connection errors, log warning, expose zero tools - [ ] Tool name prefixing: `context7_` - [ ] ToolDef wrapping: map MCP inputSchema (JSONSchema) to ToolJsonSchema `function.parameters`; use `z.any()` for Zod inputSchema (MCP already validated on the server side) - [ ] Execute wrapper: strip `context7_` prefix before calling MCP, join result content blocks with `\n` ## B5 — Tool registration (lazy-init) - [ ] `apps/server/src/services/tools.ts` — convert `ALL_TOOLS` from a module-level constant to a lazy-initialized array - [ ] Add `initializeTools(mcpTools: ToolDef[])` function that builds the final sorted list - [ ] `TOOLS_BY_NAME`, `READ_ONLY_TOOL_NAMES` derived from the initialized list - [ ] Ensure all existing callers of `ALL_TOOLS` / `TOOLS_BY_NAME` still work (they import from tools.ts — verify the export shape) - [ ] OR simpler: keep ALL_TOOLS as-is (native tools), add `appendMcpTools(tools)` that mutates + re-sorts + rebuilds TOOLS_BY_NAME. Less clean but less invasive. ## B6 — Startup wiring - [ ] `apps/server/src/index.ts` — after `applySchema()`, before route registration: - If `config.MCP_CONTEXT7_URL` is set: `await mcpClient.initialize(config, app.log)` - `appendMcpTools(mcpClient.getTools())` (or equivalent) - Log tool count - [ ] If URL not set: skip, log "mcp: Context7 not configured, skipping" ## B7 — Verification - [ ] `npx tsc --noEmit -p apps/server` — 0 errors - [ ] `pnpm -C apps/server test` — all existing tests pass (MCP client is startup-only; tests don't initialize it) - [ ] `pnpm -C apps/web build` — green (no web changes) ## B8 — Unit tests - [ ] NEW `apps/server/src/services/__tests__/mcp-client.test.ts` - [ ] Test: tool wrapping produces correct ToolDef shape (name, description, jsonSchema, execute fn) - [ ] Test: read-only guard rejects tools with `readOnly: false` - [ ] Test: read-only guard accepts tools with `readOnly: true` or no annotations - [ ] Test: name prefixing — `resolve-library-id` → `context7_resolve-library-id` - [ ] Test: result extraction — single text content block → string; multiple → joined with `\n` - [ ] Test: error result — MCP error → `{error: true, output: ...}` shape ## B9 — Deploy + smoke - [ ] Add `MCP_CONTEXT7_URL=https://mcp.context7.com/mcp` to docker-compose env (or .env) - [ ] `docker compose up --build -d` - [ ] Check logs for MCP initialization message - [ ] Live-smoke: send a chat asking about AI SDK docs via Context7 - [ ] Verify tool calls + results render normally ## B10 — Docs + tag - [ ] `CHANGELOG.md` entry - [ ] `boocode_roadmap.md` retrospective bullet - [ ] `CLAUDE.md` — mention MCP client in the tools/services section - [ ] Commit, tag `v1.14.1-mcp-poc`, push, rebuild