Harden MCP schema and wire Claude OAuth/token handling

This commit is contained in:
2026-02-23 14:48:01 -05:00
parent ef2a25b5fb
commit 62e2491cde
14 changed files with 770 additions and 56 deletions

View File

@@ -20,12 +20,9 @@ import type {
McpLoadContext,
SharedMcpConfigFile,
} from "./mcp/types.js";
import { parseMcpConfig } from "./mcp/types.js";
import type { ToolClearancePolicy } from "./security/schemas.js";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readConfigFile(configPath: string): {
config?: SharedMcpConfigFile;
sourcePath?: string;
@@ -41,17 +38,32 @@ function readConfigFile(configPath: string): {
}
const rawText = readFileSync(resolvedPath, "utf8");
const parsed = JSON.parse(rawText) as unknown;
if (!isRecord(parsed)) {
throw new Error(`MCP config file must contain a JSON object: ${resolvedPath}`);
}
const parsed = parseMcpConfig(JSON.parse(rawText) as unknown);
return {
config: normalizeSharedMcpConfigFile(parsed as SharedMcpConfigFile),
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 {
@@ -64,16 +76,22 @@ export function loadMcpConfigFromEnv(
config?: Readonly<AppConfig>;
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(runtimeConfig.mcp.configPath);
if (!config) {
return {};
}
if (context.providerHint === "claude" || context.providerHint === "both") {
warnClaudeUnsupportedSharedFields(config, warn);
}
const codexServers: NonNullable<CodexOptions["config"]> = {};
const claudeServers: NonNullable<LoadedMcpConfig["claudeMcpServers"]> = {};
const resolvedHandlers: Record<string, string> = {};