From a5cff792e3419c6b48c54e08c672f3ea27d65785 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Thu, 12 Feb 2026 11:28:56 +0800 Subject: [PATCH] fix: read MCP servers from both ~/.claude.json and ~/.claude/settings.json Claude CLI stores user-scoped MCP servers in ~/.claude.json, while CodePilot was only reading from ~/.claude/settings.json. Now both the MCP list API and streamClaude merge configs from both sources. Co-Authored-By: Claude Opus 4.6 --- src/app/api/plugins/mcp/route.ts | 23 ++++++++++++++++++----- src/lib/claude-client.ts | 24 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/app/api/plugins/mcp/route.ts b/src/app/api/plugins/mcp/route.ts index 4228376..18f968b 100644 --- a/src/app/api/plugins/mcp/route.ts +++ b/src/app/api/plugins/mcp/route.ts @@ -13,16 +13,24 @@ function getSettingsPath(): string { return path.join(os.homedir(), '.claude', 'settings.json'); } -function readSettings(): Record { - const settingsPath = getSettingsPath(); - if (!fs.existsSync(settingsPath)) return {}; +// ~/.claude.json — Claude CLI stores user-scoped MCP servers here +function getUserConfigPath(): string { + return path.join(os.homedir(), '.claude.json'); +} + +function readJsonFile(filePath: string): Record { + if (!fs.existsSync(filePath)) return {}; try { - return JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { return {}; } } +function readSettings(): Record { + return readJsonFile(getSettingsPath()); +} + function writeSettings(settings: Record): void { const settingsPath = getSettingsPath(); const dir = path.dirname(settingsPath); @@ -35,7 +43,12 @@ function writeSettings(settings: Record): void { export async function GET(): Promise> { try { const settings = readSettings(); - const mcpServers = (settings.mcpServers || {}) as Record; + const userConfig = readJsonFile(getUserConfigPath()); + // Merge: ~/.claude/settings.json takes precedence over ~/.claude.json + const mcpServers = { + ...((userConfig.mcpServers || {}) as Record), + ...((settings.mcpServers || {}) as Record), + }; return NextResponse.json({ mcpServers }); } catch (error) { return NextResponse.json( diff --git a/src/lib/claude-client.ts b/src/lib/claude-client.ts index cbc0526..d39c719 100644 --- a/src/lib/claude-client.ts +++ b/src/lib/claude-client.ts @@ -283,8 +283,28 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream 0) { - queryOptions.mcpServers = toSdkMcpConfig(mcpServers); + // Load MCP servers: use passed-in config, or auto-read from config files + let effectiveMcpServers = mcpServers; + if (!effectiveMcpServers || Object.keys(effectiveMcpServers).length === 0) { + try { + const userConfigPath = path.join(os.homedir(), '.claude.json'); + const settingsPath = path.join(os.homedir(), '.claude', 'settings.json'); + let merged: Record = {}; + if (fs.existsSync(userConfigPath)) { + const userConfig = JSON.parse(fs.readFileSync(userConfigPath, 'utf-8')); + if (userConfig.mcpServers) merged = { ...merged, ...userConfig.mcpServers }; + } + if (fs.existsSync(settingsPath)) { + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + if (settings.mcpServers) merged = { ...merged, ...settings.mcpServers }; + } + if (Object.keys(merged).length > 0) effectiveMcpServers = merged; + } catch { + // ignore config read errors + } + } + if (effectiveMcpServers && Object.keys(effectiveMcpServers).length > 0) { + queryOptions.mcpServers = toSdkMcpConfig(effectiveMcpServers); } // Resume session if we have an SDK session ID from a previous conversation turn