MCP host plumbing: lazy-spawn manager, mock server, and agent loop wiring
Gadoes builds the foundation the entire MCP feature set sits on: a singleton process manager for stdio-launched MCP servers, an in-process mock server for unit tests, and the tool-dispatch wiring in the existing assistant loop. Three commits, roughly 1,300 lines of new code, plus a large package-lock addition for `@modelcontextprotocol/sdk`.
Chunk 1 - McpClientManager. A lazy-spawn singleton at backend/src/lib/mcp/clientManager.ts. Servers aren't started until the first callTool() or listTools() call. Key behavioural details: 15-minute idle TTL with auto-dispose, per-server request queue (max 10 concurrent), exponential backoff on reconnect, and a permanently_failed state after 3 consecutive spawn failures. dispose() runs on SIGTERM/SIGINT. The matching types.ts defines McpServerConfig, McpToolDefinition, McpServerState, McpCallResult, McpHealthResult.
One gotcha: clientManager.ts imports a circuitBreaker module from Chunk 18, but circuitBreaker.ts was never committed to this branch - it lived untracked until the cleanup commit e29591e. On a clean checkout, the build will not link without that file. Take e29591e (or cherry-pick circuitBreaker.ts and circuitBreaker.test.ts directly) before trying to build.
Chunk 2 - Mock MCP server. backend/src/__mocks__/mockMcpServer.ts uses @modelcontextprotocol/sdk's InMemoryTransport to run a real MCP server in-process. Tests configure scripted responses via respondWith(toolName, response) and call cleanup() when done. No child processes, no port allocation. This is the infrastructure that makes the per-source adapter tests work.
Chunk 3 - Agent loop wiring. chatTools.ts gets isMcpTool(), parseMcpToolName(), and mcpToolToOpenAI(). The naming convention is mcp__{serverName}__{toolName}. runToolCalls() dispatches matching tool calls to McpClientManager.callTool(), emitting an mcp_tool_call_start SSE event before the call so the UI can show a spinner. Open-circuit sources are removed from activeTools and an unavailability note is appended to the system prompt. runLLMStream() accepts mcpServerTools and openCircuitSources parameters.
Package changes: @modelcontextprotocol/sdk@^1.29.0 as a runtime dependency, @modelcontextprotocol/server-filesystem and vitest@^4.1.5 as dev dependencies. The package-lock diff is 3,200+ lines.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?