import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { STDIO_DEFAULT_MAX_BUFFER_SIZE, type McpRequestContext, } from "@modelcontextprotocol/server"; import { RECORDED_MCP_CATALOG_SHA256, type RecordedMcpCatalog, } from "../recordedMcpCatalog.js"; import type { AppConfig } from "../config/types.js"; import { resolveMcpInputSchemaProfile, type McpInputSchemaProfile, } from "../config/mcpInputSchemaProfile.js"; import { DeferredEvidenceMcpServer } from "../server/DeferredEvidenceMcpServer.js"; import { ToolResultDelivery } from "../server/ToolResultDelivery.js"; import { createLogger } from "../logger.js"; import { reportMcpShutdownFailure } from "./shutdown.js"; import type { McpServerHandle, RuntimeDependencies } from "./types.js"; import { MCP_CONNECTION_LOST, SERVER_START_FAILED } from "./messages.js"; import { OwnedMcpServers } from "./OwnedMcpServers.js"; /** Verify the build-owned artifact before trusting its exact typed projection. */ const readCatalog = (profile: McpInputSchemaProfile): RecordedMcpCatalog => { const bytes = readFileSync( new URL( profile === "full" ? "../mcp-catalog-full.json" : "../mcp-catalog.json", import.meta.url, ), ); if ( createHash("sha256").update(bytes).digest("hex") !== RECORDED_MCP_CATALOG_SHA256?.[profile] ) throw new Error(SERVER_START_FAILED); // The digest binds these bytes to the same compiled producer and SDK converter. const catalog: unknown = JSON.parse(bytes.toString("utf8")); if ( typeof catalog !== "object" || catalog === null || Reflect.get(catalog, "version") !== 1 ) throw new Error(SERVER_START_FAILED); return catalog as RecordedMcpCatalog; }; const initializeRuntime = async ( server: DeferredEvidenceMcpServer, dependencies: RuntimeDependencies, context: McpRequestContext, config: AppConfig, startedAt: string, ): Promise => { let shutdown: (() => Promise) | undefined; let closeSession: (() => Promise) | undefined; let sessionClosed = false; server.ownRuntimeCleanup(async () => { if (shutdown !== undefined) return shutdown(); const results = await Promise.allSettled([ server.closePartialRuntime(), (async () => { if (!sessionClosed && closeSession !== undefined) { await closeSession(); sessionClosed = true; } })(), ]); const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : [], ); if (failures.length > 0) throw new AggregateError(failures, "MCP startup cleanup failed"); }); const [{ runEager }, { createServer }] = await Promise.all([ import("./eagerRuntime.js"), import("../server/createServer.js"), ]); const result = await runEager( { ...dependencies, // The outer SDK transport already owns and frames this stream. Eager // materialization owns providers only, never another copy of raw input. stdin: undefined, registerShutdown: () => () => undefined, captureShutdown: (close) => { shutdown = close; }, createServer: (source, options) => { if (source.kind === "session") closeSession = async () => { const result = await source.session.close({ retainProviderDocuments: true, }); if (!result.ok) throw result.error; }; return createServer(source, { ...options, mcpServer: server, startedAt, }); }, serve: (factory) => { let close: (() => Promise) | undefined; server.materialize(() => { const configured = factory(context); if (configured !== server) throw new Error(SERVER_START_FAILED); close = configured.close.bind(configured); }); return { close: () => close?.() ?? Promise.resolve() }; }, }, config, ); if (result !== 0 || shutdown === undefined) throw new Error(SERVER_START_FAILED); }; /** Own every SDK-created server, including discarded discovery instances. */ export const runDeferred = async ( dependencies: RuntimeDependencies, config: AppConfig, ): Promise => { const serverLogger = createLogger("mcp", config.logLevel).child({ layer: "server", }); const profile = resolveMcpInputSchemaProfile(config.mcpInputSchemaProfile); let firstCatalog: RecordedMcpCatalog | undefined = readCatalog(profile); const servers = new OwnedMcpServers(); let handle: McpServerHandle | undefined; let attempt: Promise | undefined; let unregister: () => void = () => undefined; let closing = false; let finishServing!: () => void; const serving = new Promise((resolve) => { finishServing = resolve; }); const close = (): Promise => { closing = true; attempt ??= Promise.resolve() .then(async () => { // A transport published after shutdown begins is still ours to close. await serving; const ownedHandle = handle; const handleResults = Promise.allSettled( ownedHandle === undefined ? [] : [ Promise.resolve() .then(() => ownedHandle.close()) .then(() => { handle = undefined; }), ], ); // Servers published before the serving handle settles join this owner; // closeAll seals further publication before taking its snapshot. const [handles, instances] = await Promise.all([ handleResults, servers.closeAll(), ]); const failures = [...handles, ...instances].flatMap((result) => result.status === "rejected" ? [result.reason] : [], ); if (failures.length > 0) throw new AggregateError(failures, "MCP cleanup failed"); unregister(); }) .catch((cause) => { attempt = undefined; throw cause; }); return attempt; }; const requestClose = () => { void close().catch((cause) => reportMcpShutdownFailure(cause, dependencies, serverLogger), ); }; try { // Keep cleanup reachable even if serve creates a server and then throws. unregister = dependencies.registerShutdown(requestClose); dependencies.captureShutdown?.(close); try { handle = await dependencies.serve( (context) => { const server = servers.create(() => { const catalog = firstCatalog ?? readCatalog(profile); firstCatalog = undefined; const startedAt = new Date().toISOString(); const server = new DeferredEvidenceMcpServer( catalog, new ToolResultDelivery( config.mcpMaxResponseBytes ?? STDIO_DEFAULT_MAX_BUFFER_SIZE, ), (target) => initializeRuntime( target, dependencies, context, config, startedAt, ).catch((cause) => { dependencies.setExitCode(1); throw cause; }), ); return server; }); const previous = server.server.onclose; server.server.onclose = () => { previous?.(); requestClose(); }; return server; }, { onerror: () => { serverLogger.error(MCP_CONNECTION_LOST); dependencies.writeStderr(`${MCP_CONNECTION_LOST}\n`); }, }, ); } finally { finishServing(); } } catch (cause) { finishServing(); serverLogger.error( { error: cause instanceof Error ? cause.message : String(cause) }, SERVER_START_FAILED, ); try { await close(); } catch (cleanupCause) { throw new AggregateError( [cause, cleanupCause], "MCP startup and cleanup failed", ); } dependencies.writeStderr(`${SERVER_START_FAILED}\n`); return 1; } if (closing) await close(); return 0; };