Merge pull request #1616 from code-yeongyu/fix/814-user-mcp-config

fix(mcp-loader): read user-level MCP config from ~/.claude.json (#814)
This commit is contained in:
YeonGyu-Kim 2026-02-07 20:09:53 +09:00 committed by GitHub
commit 2c394cd497
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 33 deletions

View File

@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test" import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"
import { mkdirSync, writeFileSync, rmSync } from "fs" import { mkdirSync, writeFileSync, rmSync } from "fs"
import { join } from "path" import { join } from "path"
import { tmpdir } from "os" import { tmpdir } from "os"
@ -126,37 +126,70 @@ describe("getSystemMcpServerNames", () => {
} }
}) })
it("merges server names from multiple .mcp.json files", async () => { it("merges server names from multiple .mcp.json files", async () => {
// given // given
mkdirSync(join(TEST_DIR, ".claude"), { recursive: true }) mkdirSync(join(TEST_DIR, ".claude"), { recursive: true })
const projectMcp = { const projectMcp = {
mcpServers: { mcpServers: {
playwright: { command: "npx", args: ["@playwright/mcp@latest"] }, playwright: { command: "npx", args: ["@playwright/mcp@latest"] },
}, },
} }
const localMcp = { const localMcp = {
mcpServers: { mcpServers: {
memory: { command: "npx", args: ["-y", "@anthropic-ai/mcp-server-memory"] }, memory: { command: "npx", args: ["-y", "@anthropic-ai/mcp-server-memory"] },
}, },
} }
writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(projectMcp)) writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(projectMcp))
writeFileSync(join(TEST_DIR, ".claude", ".mcp.json"), JSON.stringify(localMcp)) writeFileSync(join(TEST_DIR, ".claude", ".mcp.json"), JSON.stringify(localMcp))
const originalCwd = process.cwd() const originalCwd = process.cwd()
process.chdir(TEST_DIR) process.chdir(TEST_DIR)
try { try {
// when // when
const { getSystemMcpServerNames } = await import("./loader") const { getSystemMcpServerNames } = await import("./loader")
const names = getSystemMcpServerNames() const names = getSystemMcpServerNames()
// then // then
expect(names.has("playwright")).toBe(true) expect(names.has("playwright")).toBe(true)
expect(names.has("memory")).toBe(true) expect(names.has("memory")).toBe(true)
} finally { } finally {
process.chdir(originalCwd) process.chdir(originalCwd)
} }
}) })
it("reads user-level MCP config from ~/.claude.json", async () => {
// given
const userConfigPath = join(TEST_DIR, ".claude.json")
const userMcpConfig = {
mcpServers: {
"user-server": {
command: "npx",
args: ["user-mcp-server"],
},
},
}
const originalCwd = process.cwd()
process.chdir(TEST_DIR)
try {
mock.module("os", () => ({
homedir: () => TEST_DIR,
tmpdir,
}))
writeFileSync(userConfigPath, JSON.stringify(userMcpConfig))
const { getSystemMcpServerNames } = await import("./loader")
const names = getSystemMcpServerNames()
expect(names.has("user-server")).toBe(true)
} finally {
process.chdir(originalCwd)
rmSync(userConfigPath, { force: true })
}
})
}) })

View File

@ -1,5 +1,6 @@
import { existsSync, readFileSync } from "fs" import { existsSync, readFileSync } from "fs"
import { join } from "path" import { join } from "path"
import { homedir } from "os"
import { getClaudeConfigDir } from "../../shared" import { getClaudeConfigDir } from "../../shared"
import type { import type {
ClaudeCodeMcpConfig, ClaudeCodeMcpConfig,
@ -16,11 +17,10 @@ interface McpConfigPath {
} }
function getMcpConfigPaths(): McpConfigPath[] { function getMcpConfigPaths(): McpConfigPath[] {
const claudeConfigDir = getClaudeConfigDir()
const cwd = process.cwd() const cwd = process.cwd()
return [ 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, ".mcp.json"), scope: "project" },
{ path: join(cwd, ".claude", ".mcp.json"), scope: "local" }, { path: join(cwd, ".claude", ".mcp.json"), scope: "local" },
] ]