Refactor pipeline policies, MCP registry, and unified config/runtime
This commit is contained in:
@@ -1,5 +1,42 @@
|
||||
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { CodexConfigObject, SharedMcpServer, Transport } from "./types.js";
|
||||
import type {
|
||||
CodexConfigObject,
|
||||
SharedMcpConfigFile,
|
||||
SharedMcpServer,
|
||||
Transport,
|
||||
} from "./types.js";
|
||||
|
||||
function mergeHeaders(server: SharedMcpServer): Record<string, string> | undefined {
|
||||
const merged = {
|
||||
...(server.http_headers ?? {}),
|
||||
...(server.headers ?? {}),
|
||||
};
|
||||
|
||||
return Object.keys(merged).length > 0 ? merged : undefined;
|
||||
}
|
||||
|
||||
export function normalizeSharedMcpServer(server: SharedMcpServer): SharedMcpServer {
|
||||
const { headers: _headers, http_headers: _httpHeaders, ...rest } = server;
|
||||
const normalizedHeaders = mergeHeaders(server);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
...(normalizedHeaders ? { headers: normalizedHeaders } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeSharedMcpConfigFile(config: SharedMcpConfigFile): SharedMcpConfigFile {
|
||||
const normalizedServers: Record<string, SharedMcpServer> = {};
|
||||
|
||||
for (const [serverName, server] of Object.entries(config.servers ?? {})) {
|
||||
normalizedServers[serverName] = normalizeSharedMcpServer(server);
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
...(Object.keys(normalizedServers).length > 0 ? { servers: normalizedServers } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function inferTransport(server: SharedMcpServer): Transport {
|
||||
if (server.type) {
|
||||
@@ -10,6 +47,7 @@ export function inferTransport(server: SharedMcpServer): Transport {
|
||||
|
||||
export function toCodexServerConfig(serverName: string, server: SharedMcpServer): CodexConfigObject {
|
||||
const type = inferTransport(server);
|
||||
const headers = mergeHeaders(server);
|
||||
|
||||
if (type === "stdio" && !server.command) {
|
||||
throw new Error(`Shared MCP server "${serverName}" requires "command" for stdio transport.`);
|
||||
@@ -38,9 +76,8 @@ export function toCodexServerConfig(serverName: string, server: SharedMcpServer)
|
||||
if (server.bearer_token_env_var) {
|
||||
config.bearer_token_env_var = server.bearer_token_env_var;
|
||||
}
|
||||
const httpHeaders = server.http_headers ?? server.headers;
|
||||
if (httpHeaders) {
|
||||
config.http_headers = httpHeaders;
|
||||
if (headers) {
|
||||
config.http_headers = headers;
|
||||
}
|
||||
if (server.env_http_headers) config.env_http_headers = server.env_http_headers;
|
||||
if (server.env_vars) config.env_vars = server.env_vars;
|
||||
@@ -50,6 +87,7 @@ export function toCodexServerConfig(serverName: string, server: SharedMcpServer)
|
||||
|
||||
export function toClaudeServerConfig(serverName: string, server: SharedMcpServer): McpServerConfig {
|
||||
const type = inferTransport(server);
|
||||
const headers = mergeHeaders(server);
|
||||
|
||||
if (type === "stdio") {
|
||||
if (!server.command) {
|
||||
@@ -70,7 +108,6 @@ export function toClaudeServerConfig(serverName: string, server: SharedMcpServer
|
||||
return {
|
||||
type,
|
||||
url: server.url,
|
||||
...(server.headers ? { headers: server.headers } : {}),
|
||||
...(headers ? { headers } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -127,107 +127,121 @@ function applyEnabledByDefault(input: McpHandlerBusinessLogicInput): McpHandlerR
|
||||
: input.baseResult;
|
||||
}
|
||||
|
||||
const context7Handler = createMcpHandlerShell({
|
||||
id: "context7",
|
||||
description:
|
||||
"Dedicated extension point for Context7 policy/behavior. Business logic belongs in applyBusinessLogic.",
|
||||
matches: (input) => isNamedLike(input, ["context7"]),
|
||||
applyBusinessLogic: applyEnabledByDefault,
|
||||
});
|
||||
|
||||
const claudeTaskMasterHandler = createMcpHandlerShell({
|
||||
id: "claude-task-master",
|
||||
description:
|
||||
"Dedicated extension point for Claude Task Master policy/behavior. Business logic belongs in applyBusinessLogic.",
|
||||
matches: (input) =>
|
||||
isNamedLike(input, ["claude-task-master", "task-master", "taskmaster"]),
|
||||
applyBusinessLogic: applyEnabledByDefault,
|
||||
});
|
||||
|
||||
const genericHandler: McpServerHandler = {
|
||||
id: "generic",
|
||||
description: "Default passthrough mapping for project-specific MCP servers.",
|
||||
matches: () => true,
|
||||
resolve: ({ serverName, server, utils: localUtils }) =>
|
||||
createDefaultResult({ serverName, server, localUtils }),
|
||||
};
|
||||
|
||||
const handlerRegistry = new Map<string, McpServerHandler>();
|
||||
const handlerOrder: string[] = [];
|
||||
|
||||
function installBuiltinHandlers(): void {
|
||||
registerMcpHandler(context7Handler);
|
||||
registerMcpHandler(claudeTaskMasterHandler);
|
||||
registerMcpHandler(genericHandler);
|
||||
}
|
||||
|
||||
export function registerMcpHandler(handler: McpServerHandler): void {
|
||||
if (handlerRegistry.has(handler.id)) {
|
||||
handlerRegistry.set(handler.id, handler);
|
||||
return;
|
||||
}
|
||||
handlerRegistry.set(handler.id, handler);
|
||||
handlerOrder.push(handler.id);
|
||||
}
|
||||
|
||||
export function listMcpHandlers(): McpServerHandler[] {
|
||||
return handlerOrder
|
||||
.map((id) => handlerRegistry.get(id))
|
||||
.filter((handler): handler is McpServerHandler => Boolean(handler));
|
||||
}
|
||||
|
||||
function resolveHandler(serverName: string, server: SharedMcpServer): McpServerHandler {
|
||||
if (server.handler) {
|
||||
const explicit = handlerRegistry.get(server.handler);
|
||||
if (!explicit) {
|
||||
throw new Error(
|
||||
`Unknown MCP handler "${server.handler}" configured for server "${serverName}".`,
|
||||
);
|
||||
}
|
||||
return explicit;
|
||||
}
|
||||
|
||||
for (const id of handlerOrder) {
|
||||
const handler = handlerRegistry.get(id);
|
||||
if (!handler || id === "generic") {
|
||||
continue;
|
||||
}
|
||||
if (handler.matches({ serverName, server })) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
const fallback = handlerRegistry.get("generic");
|
||||
if (!fallback) {
|
||||
throw new Error('No MCP fallback handler registered. Expected handler id "generic".');
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function resolveServerWithHandler(input: {
|
||||
serverName: string;
|
||||
server: SharedMcpServer;
|
||||
context: McpLoadContext;
|
||||
fullConfig: SharedMcpConfigFile;
|
||||
}): McpHandlerResult & { handlerId: string } {
|
||||
const { serverName, server, context, fullConfig } = input;
|
||||
const handler = resolveHandler(serverName, server);
|
||||
const handlerConfig = {
|
||||
...(fullConfig.handlerSettings?.[handler.id] ?? {}),
|
||||
...(server.handlerOptions ?? {}),
|
||||
};
|
||||
const result = handler.resolve({
|
||||
serverName,
|
||||
server,
|
||||
context,
|
||||
handlerConfig,
|
||||
fullConfig,
|
||||
utils,
|
||||
function createBuiltinHandlers(): McpServerHandler[] {
|
||||
const context7Handler = createMcpHandlerShell({
|
||||
id: "context7",
|
||||
description:
|
||||
"Dedicated extension point for Context7 policy/behavior. Business logic belongs in applyBusinessLogic.",
|
||||
matches: (input) => isNamedLike(input, ["context7"]),
|
||||
applyBusinessLogic: applyEnabledByDefault,
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
handlerId: handler.id,
|
||||
|
||||
const claudeTaskMasterHandler = createMcpHandlerShell({
|
||||
id: "claude-task-master",
|
||||
description:
|
||||
"Dedicated extension point for Claude Task Master policy/behavior. Business logic belongs in applyBusinessLogic.",
|
||||
matches: (input) =>
|
||||
isNamedLike(input, ["claude-task-master", "task-master", "taskmaster"]),
|
||||
applyBusinessLogic: applyEnabledByDefault,
|
||||
});
|
||||
|
||||
const genericHandler: McpServerHandler = {
|
||||
id: "generic",
|
||||
description: "Default passthrough mapping for project-specific MCP servers.",
|
||||
matches: () => true,
|
||||
resolve: ({ serverName, server, utils: localUtils }) =>
|
||||
createDefaultResult({ serverName, server, localUtils }),
|
||||
};
|
||||
|
||||
return [context7Handler, claudeTaskMasterHandler, genericHandler];
|
||||
}
|
||||
|
||||
installBuiltinHandlers();
|
||||
export class McpRegistry {
|
||||
private readonly handlerRegistry = new Map<string, McpServerHandler>();
|
||||
private readonly handlerOrder: string[] = [];
|
||||
|
||||
constructor(input?: { handlers?: McpServerHandler[] }) {
|
||||
for (const handler of input?.handlers ?? []) {
|
||||
this.register(handler);
|
||||
}
|
||||
}
|
||||
|
||||
register(handler: McpServerHandler): void {
|
||||
if (this.handlerRegistry.has(handler.id)) {
|
||||
this.handlerRegistry.set(handler.id, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlerRegistry.set(handler.id, handler);
|
||||
this.handlerOrder.push(handler.id);
|
||||
}
|
||||
|
||||
listHandlers(): McpServerHandler[] {
|
||||
return this.handlerOrder
|
||||
.map((id) => this.handlerRegistry.get(id))
|
||||
.filter((handler): handler is McpServerHandler => Boolean(handler));
|
||||
}
|
||||
|
||||
resolveServerWithHandler(input: {
|
||||
serverName: string;
|
||||
server: SharedMcpServer;
|
||||
context: McpLoadContext;
|
||||
fullConfig: SharedMcpConfigFile;
|
||||
}): McpHandlerResult & { handlerId: string } {
|
||||
const { serverName, server, context, fullConfig } = input;
|
||||
const handler = this.resolveHandler(serverName, server);
|
||||
const handlerConfig = {
|
||||
...(fullConfig.handlerSettings?.[handler.id] ?? {}),
|
||||
...(server.handlerOptions ?? {}),
|
||||
};
|
||||
|
||||
const result = handler.resolve({
|
||||
serverName,
|
||||
server,
|
||||
context,
|
||||
handlerConfig,
|
||||
fullConfig,
|
||||
utils,
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
handlerId: handler.id,
|
||||
};
|
||||
}
|
||||
|
||||
private resolveHandler(serverName: string, server: SharedMcpServer): McpServerHandler {
|
||||
if (server.handler) {
|
||||
const explicit = this.handlerRegistry.get(server.handler);
|
||||
if (!explicit) {
|
||||
throw new Error(
|
||||
`Unknown MCP handler "${server.handler}" configured for server "${serverName}".`,
|
||||
);
|
||||
}
|
||||
return explicit;
|
||||
}
|
||||
|
||||
for (const id of this.handlerOrder) {
|
||||
const handler = this.handlerRegistry.get(id);
|
||||
if (!handler || id === "generic") {
|
||||
continue;
|
||||
}
|
||||
if (handler.matches({ serverName, server })) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
const fallback = this.handlerRegistry.get("generic");
|
||||
if (!fallback) {
|
||||
throw new Error('No MCP fallback handler registered. Expected handler id "generic".');
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefaultMcpRegistry(): McpRegistry {
|
||||
return new McpRegistry({
|
||||
handlers: createBuiltinHandlers(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user