Files
oh-my-opencode/src/plugin-handlers/mcp-config-handler.ts
T
YeonGyu-Kim 649a83d046 fix(mcp): user config overrides Claude Code .mcp.json with collision warning
Previously, Claude Code's .mcp.json would silently override OpenCode user
config when MCP server names collided. This was unexpected behavior since
users expect their explicit OpenCode configuration to take precedence.

Changes:
1. Swapped merge order: Claude Code .mcp.json is now merged BEFORE user
   config, so user config wins on collision
2. Added warning log when user config overrides a Claude Code MCP server:
   'warning: MCP server X from user config overrides Claude Code .mcp.json'
3. Added comprehensive tests for collision scenarios

Fixes #2946
2026-04-02 13:32:11 +09:00

70 lines
1.9 KiB
TypeScript

import type { OhMyOpenCodeConfig } from "../config";
import { loadMcpConfigs } from "../features/claude-code-mcp-loader";
import { createBuiltinMcps } from "../mcp";
import type { PluginComponents } from "./plugin-components-loader";
import { log } from "../shared";
type McpEntry = Record<string, unknown>;
function captureUserDisabledMcps(
userMcp: Record<string, unknown> | undefined
): Set<string> {
const disabled = new Set<string>();
if (!userMcp) return disabled;
for (const [name, value] of Object.entries(userMcp)) {
if (
value &&
typeof value === "object" &&
"enabled" in value &&
(value as McpEntry).enabled === false
) {
disabled.add(name);
}
}
return disabled;
}
export async function applyMcpConfig(params: {
config: Record<string, unknown>;
pluginConfig: OhMyOpenCodeConfig;
pluginComponents: PluginComponents;
}): Promise<void> {
const disabledMcps = params.pluginConfig.disabled_mcps ?? [];
const userMcp = params.config.mcp as Record<string, unknown> | undefined;
const userDisabledMcps = captureUserDisabledMcps(userMcp);
const mcpResult = params.pluginConfig.claude_code?.mcp ?? true
? await loadMcpConfigs(disabledMcps)
: { servers: {} };
if (userMcp) {
for (const name of Object.keys(userMcp)) {
if (name in mcpResult.servers) {
log(`warning: MCP server "${name}" from user config overrides Claude Code .mcp.json`);
}
}
}
const merged = {
...createBuiltinMcps(disabledMcps, params.pluginConfig),
...mcpResult.servers,
...(userMcp ?? {}),
...params.pluginComponents.mcpServers,
} as Record<string, McpEntry>;
for (const name of userDisabledMcps) {
if (merged[name]) {
merged[name] = { ...merged[name], enabled: false };
}
}
const disabledSet = new Set(disabledMcps);
for (const name of disabledSet) {
delete merged[name];
}
params.config.mcp = merged;
}