From ac6e7d00f24d25647a526fe2576d4f5d379f34fc Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 7 Feb 2026 22:29:51 +0900 Subject: [PATCH] fix(mcp-loader): also read ~/.claude/.mcp.json for CLI-managed user MCP config PR #1616 replaced ~/.claude/.mcp.json with ~/.claude.json but both paths should be read: - ~/.claude.json: user/local scope MCP settings (mcpServers field) - ~/.claude/.mcp.json: CLI-managed MCP servers (claude mcp add) Fixes #814 --- .../claude-code-mcp-loader/loader.test.ts | 53 +++++++++++++++++++ src/features/claude-code-mcp-loader/loader.ts | 2 + 2 files changed, 55 insertions(+) diff --git a/src/features/claude-code-mcp-loader/loader.test.ts b/src/features/claude-code-mcp-loader/loader.test.ts index 8faf6571..8a1b9f6e 100644 --- a/src/features/claude-code-mcp-loader/loader.test.ts +++ b/src/features/claude-code-mcp-loader/loader.test.ts @@ -192,4 +192,57 @@ describe("getSystemMcpServerNames", () => { rmSync(userConfigPath, { force: true }) } }) + + it("reads both ~/.claude.json and ~/.claude/.mcp.json for user scope", async () => { + // given: simulate both user-level config files + const userClaudeJson = join(TEST_DIR, ".claude.json") + const claudeDir = join(TEST_DIR, ".claude") + const claudeDirMcpJson = join(claudeDir, ".mcp.json") + + mkdirSync(claudeDir, { recursive: true }) + + // ~/.claude.json has server-a + writeFileSync(userClaudeJson, JSON.stringify({ + mcpServers: { + "server-from-claude-json": { + command: "npx", + args: ["server-a"], + }, + }, + })) + + // ~/.claude/.mcp.json has server-b (CLI-managed) + writeFileSync(claudeDirMcpJson, JSON.stringify({ + mcpServers: { + "server-from-mcp-json": { + command: "npx", + args: ["server-b"], + }, + }, + })) + + const originalCwd = process.cwd() + process.chdir(TEST_DIR) + + try { + mock.module("os", () => ({ + homedir: () => TEST_DIR, + tmpdir, + })) + + // Also mock getClaudeConfigDir to point to our test .claude dir + mock.module("../../shared", () => ({ + getClaudeConfigDir: () => claudeDir, + })) + + const { getSystemMcpServerNames } = await import("./loader") + const names = getSystemMcpServerNames() + + // Both sources should be merged + expect(names.has("server-from-claude-json")).toBe(true) + expect(names.has("server-from-mcp-json")).toBe(true) + } finally { + process.chdir(originalCwd) + } + }) }) diff --git a/src/features/claude-code-mcp-loader/loader.ts b/src/features/claude-code-mcp-loader/loader.ts index 7e0f5da7..754b71a9 100644 --- a/src/features/claude-code-mcp-loader/loader.ts +++ b/src/features/claude-code-mcp-loader/loader.ts @@ -17,10 +17,12 @@ interface McpConfigPath { } function getMcpConfigPaths(): McpConfigPath[] { + const claudeConfigDir = getClaudeConfigDir() const cwd = process.cwd() return [ { path: join(homedir(), ".claude.json"), scope: "user" }, + { path: join(claudeConfigDir, ".mcp.json"), scope: "user" }, { path: join(cwd, ".mcp.json"), scope: "project" }, { path: join(cwd, ".claude", ".mcp.json"), scope: "local" }, ]