From 1760367a256651c41a764aa535c4c3a31f0a1896 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 7 Feb 2026 20:01:16 +0900 Subject: [PATCH] fix(mcp-loader): read user-level MCP config from ~/.claude.json (#814) --- .../claude-code-mcp-loader/loader.test.ts | 95 +++++++++++++------ src/features/claude-code-mcp-loader/loader.ts | 4 +- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/features/claude-code-mcp-loader/loader.test.ts b/src/features/claude-code-mcp-loader/loader.test.ts index 7281273a2..67de9127e 100644 --- a/src/features/claude-code-mcp-loader/loader.test.ts +++ b/src/features/claude-code-mcp-loader/loader.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test" import { mkdirSync, writeFileSync, rmSync } from "fs" import { join } from "path" -import { tmpdir } from "os" +import { tmpdir, homedir } from "os" const TEST_DIR = join(tmpdir(), "mcp-loader-test-" + Date.now()) @@ -126,37 +126,70 @@ describe("getSystemMcpServerNames", () => { } }) - it("merges server names from multiple .mcp.json files", async () => { - // given - mkdirSync(join(TEST_DIR, ".claude"), { recursive: true }) - - const projectMcp = { - mcpServers: { - playwright: { command: "npx", args: ["@playwright/mcp@latest"] }, - }, - } - const localMcp = { - mcpServers: { - memory: { command: "npx", args: ["-y", "@anthropic-ai/mcp-server-memory"] }, - }, - } - - writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(projectMcp)) - writeFileSync(join(TEST_DIR, ".claude", ".mcp.json"), JSON.stringify(localMcp)) + it("merges server names from multiple .mcp.json files", async () => { + // given + mkdirSync(join(TEST_DIR, ".claude"), { recursive: true }) + + const projectMcp = { + mcpServers: { + playwright: { command: "npx", args: ["@playwright/mcp@latest"] }, + }, + } + const localMcp = { + mcpServers: { + memory: { command: "npx", args: ["-y", "@anthropic-ai/mcp-server-memory"] }, + }, + } + + writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(projectMcp)) + writeFileSync(join(TEST_DIR, ".claude", ".mcp.json"), JSON.stringify(localMcp)) - const originalCwd = process.cwd() - process.chdir(TEST_DIR) + const originalCwd = process.cwd() + process.chdir(TEST_DIR) - try { - // when - const { getSystemMcpServerNames } = await import("./loader") - const names = getSystemMcpServerNames() + try { + // when + const { getSystemMcpServerNames } = await import("./loader") + const names = getSystemMcpServerNames() - // then - expect(names.has("playwright")).toBe(true) - expect(names.has("memory")).toBe(true) - } finally { - process.chdir(originalCwd) - } - }) + // then + expect(names.has("playwright")).toBe(true) + expect(names.has("memory")).toBe(true) + } finally { + process.chdir(originalCwd) + } + }) + + it("reads user-level MCP config from ~/.claude.json", async () => { + // given + const userConfigPath = join(homedir(), ".claude.json") + const userMcpConfig = { + mcpServers: { + "user-server": { + command: "npx", + args: ["user-mcp-server"], + }, + }, + } + + // Create user config if it doesn't exist + const originalCwd = process.cwd() + process.chdir(TEST_DIR) + + try { + // Write user config temporarily + writeFileSync(userConfigPath, JSON.stringify(userMcpConfig)) + + // when + const { getSystemMcpServerNames } = await import("./loader") + const names = getSystemMcpServerNames() + + // then + expect(names.has("user-server")).toBe(true) + } finally { + process.chdir(originalCwd) + // Clean up user config + rmSync(userConfigPath, { force: true }) + } + }) }) diff --git a/src/features/claude-code-mcp-loader/loader.ts b/src/features/claude-code-mcp-loader/loader.ts index 76a2f1945..7e0f5da76 100644 --- a/src/features/claude-code-mcp-loader/loader.ts +++ b/src/features/claude-code-mcp-loader/loader.ts @@ -1,5 +1,6 @@ import { existsSync, readFileSync } from "fs" import { join } from "path" +import { homedir } from "os" import { getClaudeConfigDir } from "../../shared" import type { ClaudeCodeMcpConfig, @@ -16,11 +17,10 @@ interface McpConfigPath { } function getMcpConfigPaths(): McpConfigPath[] { - const claudeConfigDir = getClaudeConfigDir() const cwd = process.cwd() return [ - { path: join(claudeConfigDir, ".mcp.json"), scope: "user" }, + { path: join(homedir(), ".claude.json"), scope: "user" }, { path: join(cwd, ".mcp.json"), scope: "project" }, { path: join(cwd, ".claude", ".mcp.json"), scope: "local" }, ]