2026-02-07 20:06:58 +09:00
|
|
|
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"
|
2026-01-02 10:55:54 +09:00
|
|
|
import { mkdirSync, writeFileSync, rmSync } from "fs"
|
|
|
|
|
import { join } from "path"
|
2026-02-07 20:06:58 +09:00
|
|
|
import { tmpdir } from "os"
|
2026-01-02 10:55:54 +09:00
|
|
|
|
|
|
|
|
const TEST_DIR = join(tmpdir(), "mcp-loader-test-" + Date.now())
|
2026-02-08 15:31:32 +09:00
|
|
|
const TEST_HOME = join(TEST_DIR, "home")
|
2026-01-02 10:55:54 +09:00
|
|
|
|
|
|
|
|
describe("getSystemMcpServerNames", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mkdirSync(TEST_DIR, { recursive: true })
|
2026-02-08 15:31:32 +09:00
|
|
|
mkdirSync(TEST_HOME, { recursive: true })
|
|
|
|
|
mock.module("os", () => ({
|
|
|
|
|
homedir: () => TEST_HOME,
|
|
|
|
|
tmpdir,
|
|
|
|
|
}))
|
|
|
|
|
mock.module("../../shared", () => ({
|
|
|
|
|
getClaudeConfigDir: () => join(TEST_HOME, ".claude"),
|
|
|
|
|
}))
|
2026-01-02 10:55:54 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
2026-02-08 17:34:47 +09:00
|
|
|
mock.restore()
|
2026-01-02 10:55:54 +09:00
|
|
|
rmSync(TEST_DIR, { recursive: true, force: true })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("returns empty set when no .mcp.json files exist", async () => {
|
2026-02-01 16:47:50 +09:00
|
|
|
// given
|
2026-01-02 10:55:54 +09:00
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-01 16:47:50 +09:00
|
|
|
// when
|
2026-01-02 10:55:54 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-01 16:47:50 +09:00
|
|
|
// then
|
2026-01-02 10:55:54 +09:00
|
|
|
expect(names).toBeInstanceOf(Set)
|
|
|
|
|
expect(names.size).toBe(0)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("returns server names from project .mcp.json", async () => {
|
2026-02-01 16:47:50 +09:00
|
|
|
// given
|
2026-01-02 10:55:54 +09:00
|
|
|
const mcpConfig = {
|
|
|
|
|
mcpServers: {
|
|
|
|
|
playwright: {
|
|
|
|
|
command: "npx",
|
|
|
|
|
args: ["@playwright/mcp@latest"],
|
|
|
|
|
},
|
|
|
|
|
sqlite: {
|
|
|
|
|
command: "uvx",
|
|
|
|
|
args: ["mcp-server-sqlite"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(mcpConfig))
|
|
|
|
|
|
|
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-01 16:47:50 +09:00
|
|
|
// when
|
2026-01-02 10:55:54 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-01 16:47:50 +09:00
|
|
|
// then
|
2026-01-02 10:55:54 +09:00
|
|
|
expect(names.has("playwright")).toBe(true)
|
|
|
|
|
expect(names.has("sqlite")).toBe(true)
|
|
|
|
|
expect(names.size).toBe(2)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("returns server names from .claude/.mcp.json", async () => {
|
2026-02-01 16:47:50 +09:00
|
|
|
// given
|
2026-01-02 10:55:54 +09:00
|
|
|
mkdirSync(join(TEST_DIR, ".claude"), { recursive: true })
|
|
|
|
|
const mcpConfig = {
|
|
|
|
|
mcpServers: {
|
|
|
|
|
memory: {
|
|
|
|
|
command: "npx",
|
|
|
|
|
args: ["-y", "@anthropic-ai/mcp-server-memory"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
writeFileSync(join(TEST_DIR, ".claude", ".mcp.json"), JSON.stringify(mcpConfig))
|
|
|
|
|
|
|
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-01 16:47:50 +09:00
|
|
|
// when
|
2026-01-02 10:55:54 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-01 16:47:50 +09:00
|
|
|
// then
|
2026-01-02 10:55:54 +09:00
|
|
|
expect(names.has("memory")).toBe(true)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("excludes disabled MCP servers", async () => {
|
2026-02-01 16:47:50 +09:00
|
|
|
// given
|
2026-01-02 10:55:54 +09:00
|
|
|
const mcpConfig = {
|
|
|
|
|
mcpServers: {
|
|
|
|
|
playwright: {
|
|
|
|
|
command: "npx",
|
|
|
|
|
args: ["@playwright/mcp@latest"],
|
|
|
|
|
disabled: true,
|
|
|
|
|
},
|
|
|
|
|
active: {
|
|
|
|
|
command: "npx",
|
|
|
|
|
args: ["some-mcp"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify(mcpConfig))
|
|
|
|
|
|
|
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-01 16:47:50 +09:00
|
|
|
// when
|
2026-01-02 10:55:54 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-01 16:47:50 +09:00
|
|
|
// then
|
2026-01-02 10:55:54 +09:00
|
|
|
expect(names.has("playwright")).toBe(false)
|
|
|
|
|
expect(names.has("active")).toBe(true)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-07 20:01:16 +09:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-07 20:06:58 +09:00
|
|
|
it("reads user-level MCP config from ~/.claude.json", async () => {
|
|
|
|
|
// given
|
2026-02-08 15:31:32 +09:00
|
|
|
const userConfigPath = join(TEST_HOME, ".claude.json")
|
2026-02-07 20:06:58 +09:00
|
|
|
const userMcpConfig = {
|
|
|
|
|
mcpServers: {
|
|
|
|
|
"user-server": {
|
|
|
|
|
command: "npx",
|
|
|
|
|
args: ["user-mcp-server"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-02-08 15:31:32 +09:00
|
|
|
writeFileSync(userConfigPath, JSON.stringify(userMcpConfig))
|
2026-02-07 20:01:16 +09:00
|
|
|
|
2026-02-07 20:06:58 +09:00
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
2026-02-07 20:01:16 +09:00
|
|
|
|
2026-02-07 20:06:58 +09:00
|
|
|
try {
|
2026-02-08 15:31:32 +09:00
|
|
|
// when
|
2026-02-07 20:06:58 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-08 15:31:32 +09:00
|
|
|
// then
|
2026-02-07 20:06:58 +09:00
|
|
|
expect(names.has("user-server")).toBe(true)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-02-07 22:29:51 +09:00
|
|
|
|
|
|
|
|
it("reads both ~/.claude.json and ~/.claude/.mcp.json for user scope", async () => {
|
2026-02-08 15:31:32 +09:00
|
|
|
// given
|
|
|
|
|
const claudeDir = join(TEST_HOME, ".claude")
|
2026-02-07 22:29:51 +09:00
|
|
|
mkdirSync(claudeDir, { recursive: true })
|
|
|
|
|
|
2026-02-08 15:31:32 +09:00
|
|
|
writeFileSync(join(TEST_HOME, ".claude.json"), JSON.stringify({
|
2026-02-07 22:29:51 +09:00
|
|
|
mcpServers: {
|
2026-02-08 15:31:32 +09:00
|
|
|
"server-from-claude-json": { command: "npx", args: ["server-a"] },
|
2026-02-07 22:29:51 +09:00
|
|
|
},
|
|
|
|
|
}))
|
|
|
|
|
|
2026-02-08 15:31:32 +09:00
|
|
|
writeFileSync(join(claudeDir, ".mcp.json"), JSON.stringify({
|
2026-02-07 22:29:51 +09:00
|
|
|
mcpServers: {
|
2026-02-08 15:31:32 +09:00
|
|
|
"server-from-mcp-json": { command: "npx", args: ["server-b"] },
|
2026-02-07 22:29:51 +09:00
|
|
|
},
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const originalCwd = process.cwd()
|
|
|
|
|
process.chdir(TEST_DIR)
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-08 15:31:32 +09:00
|
|
|
// when
|
2026-02-07 22:29:51 +09:00
|
|
|
const { getSystemMcpServerNames } = await import("./loader")
|
|
|
|
|
const names = getSystemMcpServerNames()
|
|
|
|
|
|
2026-02-08 15:31:32 +09:00
|
|
|
// then
|
2026-02-07 22:29:51 +09:00
|
|
|
expect(names.has("server-from-claude-json")).toBe(true)
|
|
|
|
|
expect(names.has("server-from-mcp-json")).toBe(true)
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-01-02 10:55:54 +09:00
|
|
|
})
|