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 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-12 11:28:56 +08:00
1 parent d3a7fd745e
commit a5cff792e3
2 files changed
+40 -7

No files matched your search

+18 -5
View File
@@ -13,16 +13,24 @@ function getSettingsPath(): string {
return path.join(os.homedir(), '.claude', 'settings.json');
}
function readSettings(): Record<string, unknown> {
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<string, unknown> {
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<string, unknown> {
return readJsonFile(getSettingsPath());
}
function writeSettings(settings: Record<string, unknown>): void {
const settingsPath = getSettingsPath();
const dir = path.dirname(settingsPath);
@@ -35,7 +43,12 @@ function writeSettings(settings: Record<string, unknown>): void {
export async function GET(): Promise<NextResponse<MCPConfigResponse | ErrorResponse>> {
try {
const settings = readSettings();
const mcpServers = (settings.mcpServers || {}) as Record<string, MCPServerConfig>;
const userConfig = readJsonFile(getUserConfigPath());
// Merge: ~/.claude/settings.json takes precedence over ~/.claude.json
const mcpServers = {
...((userConfig.mcpServers || {}) as Record<string, MCPServerConfig>),
...((settings.mcpServers || {}) as Record<string, MCPServerConfig>),
};
return NextResponse.json({ mcpServers });
} catch (error) {
return NextResponse.json(
+22 -2
View File
@@ -283,8 +283,28 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
};
}
if (mcpServers && Object.keys(mcpServers).length > 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<string, MCPServerConfig> = {};
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