import { existsSync, readFileSync } from "node:fs"; import { isAbsolute, resolve } from "node:path"; import type { CodexOptions } from "@openai/codex-sdk"; import { getConfig, type AppConfig } from "./config.js"; import { normalizeSharedMcpConfigFile } from "./mcp/converters.js"; import { createDefaultMcpRegistry, createMcpHandlerShell, type McpHandlerBusinessLogic, type McpHandlerBusinessLogicInput, type McpHandlerInput, type McpHandlerResult, type McpHandlerShellOptions, type McpHandlerUtils, McpRegistry, type McpServerHandler, } from "./mcp/handlers.js"; import type { LoadedMcpConfig, McpLoadContext, SharedMcpConfigFile, } from "./mcp/types.js"; import { parseMcpConfig } from "./mcp/types.js"; import type { ToolClearancePolicy } from "./security/schemas.js"; function readConfigFile(input: { configPath: string; workingDirectory?: string; }): { config?: SharedMcpConfigFile; sourcePath?: string; } { const candidatePath = input.configPath.trim() || "./mcp.config.json"; const resolvedPath = isAbsolute(candidatePath) ? candidatePath : resolve(input.workingDirectory ?? process.cwd(), candidatePath); if (!existsSync(resolvedPath)) { if (candidatePath !== "./mcp.config.json") { throw new Error(`MCP config file not found: ${resolvedPath}`); } return {}; } const rawText = readFileSync(resolvedPath, "utf8"); const parsed = parseMcpConfig(JSON.parse(rawText) as unknown); return { config: normalizeSharedMcpConfigFile(parsed), sourcePath: resolvedPath, }; } function warnClaudeUnsupportedSharedFields( config: SharedMcpConfigFile, warn: (message: string) => void, ): void { for (const [serverName, server] of Object.entries(config.servers ?? {})) { if (server.enabled_tools) { warn( `[WARN] MCP field 'enabled_tools' is not supported by the Claude adapter and will be ignored (server: "${serverName}").`, ); } if (server.startup_timeout_sec !== undefined || server.tool_timeout_sec !== undefined) { warn( `[WARN] MCP field 'timeouts' is not supported by the Claude adapter and will be ignored (server: "${serverName}").`, ); } } } const defaultMcpRegistry = createDefaultMcpRegistry(); export function getDefaultMcpRegistry(): McpRegistry { return defaultMcpRegistry; } export function loadMcpConfigFromEnv( context: McpLoadContext = {}, options?: { config?: Readonly; registry?: McpRegistry; toolClearance?: ToolClearancePolicy; warn?: (message: string) => void; }, ): LoadedMcpConfig { const runtimeConfig = options?.config ?? getConfig(); const registry = options?.registry ?? defaultMcpRegistry; const warn = options?.warn ?? ((message: string) => console.warn(message)); const { config, sourcePath } = readConfigFile({ configPath: runtimeConfig.mcp.configPath, workingDirectory: context.workingDirectory, }); if (!config) { return {}; } if (context.providerHint === "claude" || context.providerHint === "both") { warnClaudeUnsupportedSharedFields(config, warn); } const codexServers: NonNullable = {}; const claudeServers: NonNullable = {}; const resolvedHandlers: Record = {}; for (const [serverName, server] of Object.entries(config.servers ?? {})) { const resolved = registry.resolveServerWithHandler({ serverName, server, context, fullConfig: config, toolClearance: options?.toolClearance, }); resolvedHandlers[serverName] = resolved.handlerId; if (resolved.enabled === false) { continue; } if (resolved.codex) { codexServers[serverName] = resolved.codex; } if (resolved.claude) { claudeServers[serverName] = resolved.claude; } } const codexWithOverrides = { ...codexServers, ...(config.codex?.mcp_servers ?? {}), }; const claudeWithOverrides = { ...claudeServers, ...(config.claude?.mcpServers ?? {}), }; const codexConfig = Object.keys(codexWithOverrides).length > 0 ? ({ mcp_servers: codexWithOverrides } as NonNullable) : undefined; return { ...(codexConfig ? { codexConfig } : {}), ...(Object.keys(claudeWithOverrides).length > 0 ? { claudeMcpServers: claudeWithOverrides } : {}), ...(sourcePath ? { sourcePath } : {}), ...(Object.keys(resolvedHandlers).length > 0 ? { resolvedHandlers } : {}), }; } export function registerMcpHandler(handler: McpServerHandler): void { defaultMcpRegistry.register(handler); } export function listMcpHandlers(): McpServerHandler[] { return defaultMcpRegistry.listHandlers(); } export { createDefaultMcpRegistry, createMcpHandlerShell, McpRegistry }; export type { LoadedMcpConfig, McpHandlerBusinessLogic, McpHandlerBusinessLogicInput, McpHandlerInput, McpHandlerResult, McpHandlerShellOptions, McpHandlerUtils, McpLoadContext, McpServerHandler, };