first commit
This commit is contained in:
116
src/mcp.ts
Normal file
116
src/mcp.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import type { CodexOptions } from "@openai/codex-sdk";
|
||||
import {
|
||||
createMcpHandlerShell,
|
||||
listMcpHandlers,
|
||||
registerMcpHandler,
|
||||
resolveServerWithHandler,
|
||||
type McpHandlerBusinessLogic,
|
||||
type McpHandlerBusinessLogicInput,
|
||||
type McpHandlerInput,
|
||||
type McpHandlerResult,
|
||||
type McpHandlerShellOptions,
|
||||
type McpHandlerUtils,
|
||||
type McpServerHandler,
|
||||
} from "./mcp/handlers.js";
|
||||
import type {
|
||||
LoadedMcpConfig,
|
||||
McpLoadContext,
|
||||
SharedMcpConfigFile,
|
||||
} from "./mcp/types.js";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function readConfigFile(pathFromEnv: string | undefined): {
|
||||
config?: SharedMcpConfigFile;
|
||||
sourcePath?: string;
|
||||
} {
|
||||
const explicitPath = pathFromEnv?.trim();
|
||||
const candidatePath = explicitPath || "./mcp.config.json";
|
||||
const resolvedPath = resolve(process.cwd(), candidatePath);
|
||||
|
||||
if (!existsSync(resolvedPath)) {
|
||||
if (explicitPath) {
|
||||
throw new Error(`MCP config file not found: ${resolvedPath}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
return { config: parsed as SharedMcpConfigFile, sourcePath: resolvedPath };
|
||||
}
|
||||
|
||||
export function loadMcpConfigFromEnv(context: McpLoadContext = {}): LoadedMcpConfig {
|
||||
const { config, sourcePath } = readConfigFile(process.env.MCP_CONFIG_PATH);
|
||||
if (!config) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const codexServers: NonNullable<CodexOptions["config"]> = {};
|
||||
const claudeServers: NonNullable<LoadedMcpConfig["claudeMcpServers"]> = {};
|
||||
const resolvedHandlers: Record<string, string> = {};
|
||||
|
||||
for (const [serverName, server] of Object.entries(config.servers ?? {})) {
|
||||
const resolved = resolveServerWithHandler({
|
||||
serverName,
|
||||
server,
|
||||
context,
|
||||
fullConfig: config,
|
||||
});
|
||||
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<CodexOptions["config"]>)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...(codexConfig ? { codexConfig } : {}),
|
||||
...(Object.keys(claudeWithOverrides).length > 0
|
||||
? { claudeMcpServers: claudeWithOverrides }
|
||||
: {}),
|
||||
...(sourcePath ? { sourcePath } : {}),
|
||||
...(Object.keys(resolvedHandlers).length > 0 ? { resolvedHandlers } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export { createMcpHandlerShell, listMcpHandlers, registerMcpHandler };
|
||||
export type {
|
||||
LoadedMcpConfig,
|
||||
McpHandlerBusinessLogic,
|
||||
McpHandlerBusinessLogicInput,
|
||||
McpHandlerInput,
|
||||
McpHandlerResult,
|
||||
McpHandlerShellOptions,
|
||||
McpHandlerUtils,
|
||||
McpLoadContext,
|
||||
McpServerHandler,
|
||||
};
|
||||
Reference in New Issue
Block a user