import { Effect, type Cause } from "effect";import { createMcpHandler, isLegacyRequest, McpServer, WebStandardStreamableHTTPServerTransport, type McpHttpHandler,} from "@modelcontextprotocol/server";import { serveStdio } from "@modelcontextprotocol/server/stdio"; import { defaultMcpResource, jsonRpcErrorBody, mcpModernDisabledResponse, mcpResourceKey, type McpResource,} from "@executor-js/host-mcp";import { appsEnabledForClientCapabilities, buildMcpServer, clientCapabilitiesFromRequest, mcpRequestStateBindingFromBody, requestBodyFromRequest, type ExecutorMcpToolConfig,} from "@executor-js/host-mcp/tool-server";import { approvalUrlForRequest, decodeResumeResponse, formatResumeAcknowledgement, readArtifactsEnabled, readElicitationMode,} from "@executor-js/host-mcp/browser-approval";import { makeInProcessBrowserApprovalStore } from "@executor-js/host-mcp/browser-approval-store";import { formatPausedExecution, type ExecutionEngine, type ResumeResponse,} from "@executor-js/execution"; import { startIntegrationsRefresh } from "./integrations"; type AnyExecutionEngine = ExecutionEngine<Cause.YieldableError>; // ---------------------------------------------------------------------------// Streamable HTTP handler// --------------------------------------------------------------------------- export type McpRequestHandler = { readonly handleRequest: (request: Request) => Promise<Response>; /** GET `/api/mcp-sessions/:id/executions/:id` — paused detail for the console. */ readonly handlePausedRequest: (request: Request) => Promise<Response>; /** POST `/api/mcp-sessions/:id/executions/:id/resume` — record the decision. */ readonly handleApprovalRequest: (request: Request) => Promise<Response>; readonly close: () => Promise<void>;}; export interface LocalMcpServerConfig { readonly config: ExecutorMcpToolConfig; readonly close?: () => Promise<void>;} export interface LocalMcpRequestHandlerConfig { readonly defaultConfig: ExecutorMcpToolConfig; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ readonly modernEnabled?: boolean; readonly createConfigForResource?: ( resource: McpResource, ) => Promise<LocalMcpServerConfig> | LocalMcpServerConfig;} // Local serves these error bodies in-process; like the self-host store they are// INNER responses (no CORS) — byte-identical to the prior hand-rolled copy// (`content-type: application/json` only) via the canonical renderer.const jsonError = (status: number, code: number, message: string): Response => jsonRpcErrorBody(status, code, message, { cors: false }); const formatBoundaryError = (error: unknown): unknown => { // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: MCP request handler catches unknown SDK/runtime failures for process logging if (error instanceof Error) return error.stack ?? error.message; return error;}; const ignoreClose = (close: (() => Promise<void>) | undefined): Promise<void> => close ? Effect.runPromise( Effect.ignore( Effect.tryPromise({ try: close, catch: () => undefined, }), ), ) : Promise.resolve(); const pausedRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)$/;const approvalRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/resume$/; const json = (value: unknown, status = 200): Response => new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" }, }); const readResumeResponse = (request: Request): Promise<ResumeResponse | null> => Effect.runPromise( Effect.tryPromise({ try: () => request.json(), catch: () => null, }).pipe(Effect.map((raw) => (raw === null ? null : decodeResumeResponse(raw)))), ); const resumeApprovalResult = (executionId: string, response: ResumeResponse) => ({ status: "completed", ...formatResumeAcknowledgement(executionId, response), isError: false,}); const toolkitPathPattern = /^\/mcp\/toolkits\/([^/?#]+)\/?$/; const resourceFromRequest = (request: Request): McpResource | null => { const pathname = new URL(request.url).pathname; if (pathname === "/mcp" || pathname === "/mcp/") return defaultMcpResource; const match = toolkitPathPattern.exec(pathname); if (!match) return null; return { kind: "toolkit", slug: decodeURIComponent(match[1]) };}; const engineFromConfig = (config: ExecutorMcpToolConfig): AnyExecutionEngine | null => "engine" in config ? config.engine : null; const normalizeHandlerConfig = ( input: ExecutorMcpToolConfig | LocalMcpRequestHandlerConfig,): LocalMcpRequestHandlerConfig => ("defaultConfig" in input ? input : { defaultConfig: input }); export const createMcpRequestHandler = ( input: ExecutorMcpToolConfig | LocalMcpRequestHandlerConfig,): McpRequestHandler => { const handlerConfig = normalizeHandlerConfig(input); const transports = new Map<string, WebStandardStreamableHTTPServerTransport>(); const servers = new Map<string, McpServer>(); const resources = new Map<string, McpResource>(); const sessionEngines = new Map<string, AnyExecutionEngine>(); const sessionClosers = new Map<string, () => Promise<void>>(); const modernHandlers = new Map<string, McpHttpHandler>(); const modernRequestBodies = new WeakMap<Request, unknown>(); const approvals = makeInProcessBrowserApprovalStore(); const defaultEngine = engineFromConfig(handlerConfig.defaultConfig); let requestStateSigningKey: Uint8Array | undefined; const signingKey = (): Uint8Array => (requestStateSigningKey ??= crypto.getRandomValues(new Uint8Array(32))); const pausedDetail = ( sessionId: string, executionId: string, ): Promise<ReturnType<typeof formatPausedExecution> | null> => (sessionEngines.get(sessionId) ?? defaultEngine) ? Effect.runPromise( (sessionEngines.get(sessionId) ?? defaultEngine)!.getPausedExecution(executionId).pipe( Effect.map((paused) => (paused ? formatPausedExecution(paused) : null)), Effect.orElseSucceed(() => null), ), ) : Promise.resolve(null); const configForResource = async (resource: McpResource): Promise<LocalMcpServerConfig> => { if (!handlerConfig.createConfigForResource) return { config: handlerConfig.defaultConfig }; return handlerConfig.createConfigForResource(resource); }; const dispose = async (id: string, opts: { transport?: boolean; server?: boolean } = {}) => { const t = transports.get(id); const s = servers.get(id); const close = sessionClosers.get(id); transports.delete(id); servers.delete(id); resources.delete(id); sessionEngines.delete(id); sessionClosers.delete(id); if (opts.transport) await ignoreClose(t ? () => t.close() : undefined); if (opts.server) await ignoreClose(s ? () => s.close() : undefined); await ignoreClose(close); }; const modernHandlerFor = (resource: McpResource): McpHttpHandler => { const key = mcpResourceKey(resource); const cached = modernHandlers.get(key); if (cached) return cached; const handler = createMcpHandler( (context) => { const request = context.requestInfo; if (!request) { // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: the third-party McpServerFactory Promise contract has no typed failure channel; missing documented request context is an SDK defect return Effect.runPromise(Effect.die("Modern MCP request context has no request")); } const parsedBody = modernRequestBodies.get(request); return Effect.runPromise( Effect.gen(function* () { const resourceConfig = yield* Effect.promise(() => configForResource(resource)); const clientCapabilities = yield* clientCapabilitiesFromRequest(request); const requestStateBinding = yield* Effect.promise(() => mcpRequestStateBindingFromBody({ body: parsedBody, principal: "local", resource, }), ); const server = yield* buildMcpServer({ ...resourceConfig.config, artifactsEnabled: readArtifactsEnabled(request), appsEnabled: appsEnabledForClientCapabilities(clientCapabilities), requestStateSigningKey: signingKey(), requestStatePrincipal: "local", ...(requestStateBinding === null ? {} : { requestStateBinding }), }); if (resourceConfig.close) { const closeServer = server.close.bind(server); const closeConfig = resourceConfig.close; let closed = false; server.close = async () => { if (closed) return; closed = true; await ignoreClose(closeServer); await ignoreClose(closeConfig); }; } return server; }), ); }, { legacy: "reject" }, ); modernHandlers.set(key, handler); return handler; }; return { handleRequest: async (request) => { const resource = resourceFromRequest(request); if (!resource) return jsonError(404, -32001, "MCP resource not found"); if (!(await isLegacyRequest(request))) { if (handlerConfig.modernEnabled === false) { return mcpModernDisabledResponse({ cors: false }); } const parsedBody = await Effect.runPromise(requestBodyFromRequest(request)); modernRequestBodies.set(request, parsedBody); return modernHandlerFor(resource).fetch(request, { parsedBody }); } const sessionId = request.headers.get("mcp-session-id"); if (sessionId) { const transport = transports.get(sessionId); if (!transport) return jsonError(404, -32001, "Session not found"); const sessionResource = resources.get(sessionId); if (!sessionResource || mcpResourceKey(sessionResource) !== mcpResourceKey(resource)) { return jsonError(403, -32003, "Session belongs to a different MCP resource"); } return transport.handleRequest(request); } let created: McpServer | undefined; let createdSessionId: string | null = null; let resourceConfig: LocalMcpServerConfig | null = null; const transport = new WebStandardStreamableHTTPServerTransport({ // SSE streaming (the spec default), NOT `enableJsonResponse: true`. // JSON mode holds the POST open as a bare Promise and resolves it with // one buffered body once every response is ready, so the transport has // no open stream to write on: a server-to-client `elicitation/create` // issued DURING a `tools/call` is dropped, and native elicitation dies // on the client's request timeout (#1555). Streaming keeps the tool // call's own stream writable in both directions, which is what native // elicitation rides. Clients must already accept `text/event-stream` // (the transport rejects a POST otherwise), so this costs nothing. sessionIdGenerator: () => crypto.randomUUID(), onsessioninitialized: (sid) => { createdSessionId = sid; transports.set(sid, transport); if (created) servers.set(sid, created); resources.set(sid, resource); const engine = resourceConfig ? engineFromConfig(resourceConfig.config) : null; if (engine) sessionEngines.set(sid, engine); if (resourceConfig?.close) sessionClosers.set(sid, resourceConfig.close); }, onsessionclosed: (sid) => void dispose(sid, { server: true }), }); transport.onclose = () => { const sid = transport.sessionId; if (sid) void dispose(sid, { server: true }); }; // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: MCP SDK handler must return JSON-RPC errors from thrown Promise APIs try { const elicitationMode = readElicitationMode(request); resourceConfig = await configForResource(resource); created = await Effect.runPromise( buildMcpServer({ ...resourceConfig.config, browserApprovalStore: approvals.store, artifactsEnabled: readArtifactsEnabled(request), appsEnabled: resourceConfig.config.restoredAppsEnabled ?? false, requestStateSigningKey: signingKey(), requestStatePrincipal: "local", sessionful: true, elicitationMode: elicitationMode === "browser" ? { mode: "browser" as const, approvalUrl: (executionId) => approvalUrlForRequest(request, executionId, createdSessionId), } : { mode: elicitationMode }, }), ); await created.connect(transport); const response = await transport.handleRequest(request); if (!transport.sessionId) { await ignoreClose(() => transport.close()); const server = created; await ignoreClose(server ? () => server.close() : undefined); await ignoreClose(resourceConfig?.close); } return response; } catch (error) { console.error("[mcp] handleRequest error:", formatBoundaryError(error)); if (!transport.sessionId) { await ignoreClose(() => transport.close()); const server = created; await ignoreClose(server ? () => server.close() : undefined); await ignoreClose(resourceConfig?.close); } return jsonError(500, -32603, "Internal server error"); } }, handlePausedRequest: async (request) => { const match = pausedRequestPattern.exec(new URL(request.url).pathname); if (!match) return json({ error: "Not found" }, 404); if (request.method !== "GET") return json({ error: "Method not allowed" }, 405); const paused = await pausedDetail(decodeURIComponent(match[1]), decodeURIComponent(match[2])); if (!paused) return json({ error: "Paused execution not found" }, 404); return json({ text: paused.text, structured: paused.structured }); }, handleApprovalRequest: async (request) => { const match = approvalRequestPattern.exec(new URL(request.url).pathname); if (!match) return json({ error: "Not found" }, 404); if (request.method !== "POST") return json({ error: "Method not allowed" }, 405); const sessionId = decodeURIComponent(match[1]); const executionId = decodeURIComponent(match[2]); // The shared engine must still hold the paused execution — guards stale ids. if (!(await pausedDetail(sessionId, executionId))) { return json({ error: "MCP session not found" }, 404); } const response = await readResumeResponse(request); if (!response) return json({ error: "Invalid approval response" }, 400); await Effect.runPromise(approvals.recordResponse(executionId, response)); return json(resumeApprovalResult(executionId, response)); }, close: async () => { const ids = new Set([...transports.keys(), ...servers.keys()]); await Promise.all([ ...[...ids].map((id) => dispose(id, { transport: true, server: true })), ...[...modernHandlers.values()].map((handler) => handler.close()), ]); }, };}; // ---------------------------------------------------------------------------// Stdio transport// --------------------------------------------------------------------------- export const runMcpStdioServer = async (config: ExecutorMcpToolConfig): Promise<void> => { startIntegrationsRefresh(); const requestStateSigningKey = crypto.getRandomValues(new Uint8Array(32)); const stdio = serveStdio(() => Effect.runPromise( buildMcpServer({ ...config, appsEnabled: config.restoredAppsEnabled ?? false, requestStateSigningKey, requestStatePrincipal: "local", sessionful: true, }), ), ); const waitForExit = () => new Promise<void>((resolve) => { const finish = () => { process.off("SIGINT", finish); process.off("SIGTERM", finish); process.stdin.off("end", finish); process.stdin.off("close", finish); resolve(); }; process.once("SIGINT", finish); process.once("SIGTERM", finish); process.stdin.once("end", finish); process.stdin.once("close", finish); }); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: stdio server lifetime uses Promise-based SDK/process APIs and always closes resources try { await waitForExit(); } finally { await ignoreClose(() => stdio.close()); }};
import { Effect, type Cause } from "effect";
import {
createMcpHandler,
isLegacyRequest,
McpServer,
WebStandardStreamableHTTPServerTransport,
type McpHttpHandler,
} from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import {
defaultMcpResource,
jsonRpcErrorBody,
mcpModernDisabledResponse,
mcpResourceKey,
type McpResource,
} from "@executor-js/host-mcp";
import {
appsEnabledForClientCapabilities,
buildMcpServer,
clientCapabilitiesFromRequest,
mcpRequestStateBindingFromBody,
requestBodyFromRequest,
type ExecutorMcpToolConfig,
} from "@executor-js/host-mcp/tool-server";
import {
approvalUrlForRequest,
decodeResumeResponse,
formatResumeAcknowledgement,
readArtifactsEnabled,
readElicitationMode,
} from "@executor-js/host-mcp/browser-approval";
import { makeInProcessBrowserApprovalStore } from "@executor-js/host-mcp/browser-approval-store";
import {
formatPausedExecution,
type ExecutionEngine,
type ResumeResponse,
} from "@executor-js/execution";
import { startIntegrationsRefresh } from "./integrations";
type AnyExecutionEngine = ExecutionEngine<Cause.YieldableError>;
// ---------------------------------------------------------------------------
// Streamable HTTP handler
// ---------------------------------------------------------------------------
export type McpRequestHandler = {
readonly handleRequest: (request: Request) => Promise<Response>;
/** GET `/api/mcp-sessions/:id/executions/:id` — paused detail for the console. */
readonly handlePausedRequest: (request: Request) => Promise<Response>;
/** POST `/api/mcp-sessions/:id/executions/:id/resume` — record the decision. */
readonly handleApprovalRequest: (request: Request) => Promise<Response>;
readonly close: () => Promise<void>;
};
export interface LocalMcpServerConfig {
readonly config: ExecutorMcpToolConfig;
readonly close?: () => Promise<void>;
}
export interface LocalMcpRequestHandlerConfig {
readonly defaultConfig: ExecutorMcpToolConfig;
/** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
readonly modernEnabled?: boolean;
readonly createConfigForResource?: (
resource: McpResource,
) => Promise<LocalMcpServerConfig> | LocalMcpServerConfig;
}
// Local serves these error bodies in-process; like the self-host store they are
// INNER responses (no CORS) — byte-identical to the prior hand-rolled copy
// (`content-type: application/json` only) via the canonical renderer.
const jsonError = (status: number, code: number, message: string): Response =>
jsonRpcErrorBody(status, code, message, { cors: false });
const formatBoundaryError = (error: unknown): unknown => {
// oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: MCP request handler catches unknown SDK/runtime failures for process logging
if (error instanceof Error) return error.stack ?? error.message;
return error;
};
const ignoreClose = (close: (() => Promise<void>) | undefined): Promise<void> =>
close
? Effect.runPromise(
Effect.ignore(
Effect.tryPromise({
try: close,
catch: () => undefined,
}),
),
)
: Promise.resolve();
const pausedRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)$/;
const approvalRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/resume$/;
const json = (value: unknown, status = 200): Response =>
new Response(JSON.stringify(value), {
status,
headers: { "content-type": "application/json" },
});
const readResumeResponse = (request: Request): Promise<ResumeResponse | null> =>
Effect.runPromise(
Effect.tryPromise({
try: () => request.json(),
catch: () => null,
}).pipe(Effect.map((raw) => (raw === null ? null : decodeResumeResponse(raw)))),
);
const resumeApprovalResult = (executionId: string, response: ResumeResponse) => ({
status: "completed",
...formatResumeAcknowledgement(executionId, response),
isError: false,
});
const toolkitPathPattern = /^\/mcp\/toolkits\/([^/?#]+)\/?$/;
const resourceFromRequest = (request: Request): McpResource | null => {
const pathname = new URL(request.url).pathname;
if (pathname === "/mcp" || pathname === "/mcp/") return defaultMcpResource;
const match = toolkitPathPattern.exec(pathname);
if (!match) return null;
return { kind: "toolkit", slug: decodeURIComponent(match[1]) };
};
const engineFromConfig = (config: ExecutorMcpToolConfig): AnyExecutionEngine | null =>
"engine" in config ? config.engine : null;
const normalizeHandlerConfig = (
input: ExecutorMcpToolConfig | LocalMcpRequestHandlerConfig,
): LocalMcpRequestHandlerConfig => ("defaultConfig" in input ? input : { defaultConfig: input });
export const createMcpRequestHandler = (
input: ExecutorMcpToolConfig | LocalMcpRequestHandlerConfig,
): McpRequestHandler => {
const handlerConfig = normalizeHandlerConfig(input);
const transports = new Map<string, WebStandardStreamableHTTPServerTransport>();
const servers = new Map<string, McpServer>();
const resources = new Map<string, McpResource>();
const sessionEngines = new Map<string, AnyExecutionEngine>();
const sessionClosers = new Map<string, () => Promise<void>>();
const modernHandlers = new Map<string, McpHttpHandler>();
const modernRequestBodies = new WeakMap<Request, unknown>();
const approvals = makeInProcessBrowserApprovalStore();
const defaultEngine = engineFromConfig(handlerConfig.defaultConfig);
let requestStateSigningKey: Uint8Array | undefined;
const signingKey = (): Uint8Array =>
(requestStateSigningKey ??= crypto.getRandomValues(new Uint8Array(32)));
const pausedDetail = (
sessionId: string,
executionId: string,
): Promise<ReturnType<typeof formatPausedExecution> | null> =>
(sessionEngines.get(sessionId) ?? defaultEngine)
? Effect.runPromise(
(sessionEngines.get(sessionId) ?? defaultEngine)!.getPausedExecution(executionId).pipe(
Effect.map((paused) => (paused ? formatPausedExecution(paused) : null)),
Effect.orElseSucceed(() => null),
),
)
: Promise.resolve(null);
const configForResource = async (resource: McpResource): Promise<LocalMcpServerConfig> => {
if (!handlerConfig.createConfigForResource) return { config: handlerConfig.defaultConfig };
return handlerConfig.createConfigForResource(resource);
};
const dispose = async (id: string, opts: { transport?: boolean; server?: boolean } = {}) => {
const t = transports.get(id);
const s = servers.get(id);
const close = sessionClosers.get(id);
transports.delete(id);
servers.delete(id);
resources.delete(id);
sessionEngines.delete(id);
sessionClosers.delete(id);
if (opts.transport) await ignoreClose(t ? () => t.close() : undefined);
if (opts.server) await ignoreClose(s ? () => s.close() : undefined);
await ignoreClose(close);
};
const modernHandlerFor = (resource: McpResource): McpHttpHandler => {
const key = mcpResourceKey(resource);
const cached = modernHandlers.get(key);
if (cached) return cached;
const handler = createMcpHandler(
(context) => {
const request = context.requestInfo;
if (!request) {
// oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: the third-party McpServerFactory Promise contract has no typed failure channel; missing documented request context is an SDK defect
return Effect.runPromise(Effect.die("Modern MCP request context has no request"));
}
const parsedBody = modernRequestBodies.get(request);
return Effect.runPromise(
Effect.gen(function* () {
const resourceConfig = yield* Effect.promise(() => configForResource(resource));
const clientCapabilities = yield* clientCapabilitiesFromRequest(request);
const requestStateBinding = yield* Effect.promise(() =>
mcpRequestStateBindingFromBody({
body: parsedBody,
principal: "local",
resource,
}),
);
const server = yield* buildMcpServer({
...resourceConfig.config,
artifactsEnabled: readArtifactsEnabled(request),
appsEnabled: appsEnabledForClientCapabilities(clientCapabilities),
requestStateSigningKey: signingKey(),
requestStatePrincipal: "local",
...(requestStateBinding === null ? {} : { requestStateBinding }),
});
if (resourceConfig.close) {
const closeServer = server.close.bind(server);
const closeConfig = resourceConfig.close;
let closed = false;
server.close = async () => {
if (closed) return;
closed = true;
await ignoreClose(closeServer);
await ignoreClose(closeConfig);
};
}
return server;
}),
);
},
{ legacy: "reject" },
);
modernHandlers.set(key, handler);
return handler;
};
return {
handleRequest: async (request) => {
const resource = resourceFromRequest(request);
if (!resource) return jsonError(404, -32001, "MCP resource not found");
if (!(await isLegacyRequest(request))) {
if (handlerConfig.modernEnabled === false) {
return mcpModernDisabledResponse({ cors: false });
}
const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
modernRequestBodies.set(request, parsedBody);
return modernHandlerFor(resource).fetch(request, { parsedBody });
}
const sessionId = request.headers.get("mcp-session-id");
if (sessionId) {
const transport = transports.get(sessionId);
if (!transport) return jsonError(404, -32001, "Session not found");
const sessionResource = resources.get(sessionId);
if (!sessionResource || mcpResourceKey(sessionResource) !== mcpResourceKey(resource)) {
return jsonError(403, -32003, "Session belongs to a different MCP resource");
}
return transport.handleRequest(request);
}
let created: McpServer | undefined;
let createdSessionId: string | null = null;
let resourceConfig: LocalMcpServerConfig | null = null;
const transport = new WebStandardStreamableHTTPServerTransport({
// SSE streaming (the spec default), NOT `enableJsonResponse: true`.
// JSON mode holds the POST open as a bare Promise and resolves it with
// one buffered body once every response is ready, so the transport has
// no open stream to write on: a server-to-client `elicitation/create`
// issued DURING a `tools/call` is dropped, and native elicitation dies
// on the client's request timeout (#1555). Streaming keeps the tool
// call's own stream writable in both directions, which is what native
// elicitation rides. Clients must already accept `text/event-stream`
// (the transport rejects a POST otherwise), so this costs nothing.
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (sid) => {
createdSessionId = sid;
transports.set(sid, transport);
if (created) servers.set(sid, created);
resources.set(sid, resource);
const engine = resourceConfig ? engineFromConfig(resourceConfig.config) : null;
if (engine) sessionEngines.set(sid, engine);
if (resourceConfig?.close) sessionClosers.set(sid, resourceConfig.close);
},
onsessionclosed: (sid) => void dispose(sid, { server: true }),
});
transport.onclose = () => {
const sid = transport.sessionId;
if (sid) void dispose(sid, { server: true });
};
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: MCP SDK handler must return JSON-RPC errors from thrown Promise APIs
try {
const elicitationMode = readElicitationMode(request);
resourceConfig = await configForResource(resource);
created = await Effect.runPromise(
buildMcpServer({
...resourceConfig.config,
browserApprovalStore: approvals.store,
artifactsEnabled: readArtifactsEnabled(request),
appsEnabled: resourceConfig.config.restoredAppsEnabled ?? false,
requestStateSigningKey: signingKey(),
requestStatePrincipal: "local",
sessionful: true,
elicitationMode:
elicitationMode === "browser"
? {
mode: "browser" as const,
approvalUrl: (executionId) =>
approvalUrlForRequest(request, executionId, createdSessionId),
}
: { mode: elicitationMode },
}),
);
await created.connect(transport);
const response = await transport.handleRequest(request);
if (!transport.sessionId) {
await ignoreClose(() => transport.close());
const server = created;
await ignoreClose(server ? () => server.close() : undefined);
await ignoreClose(resourceConfig?.close);
}
return response;
} catch (error) {
console.error("[mcp] handleRequest error:", formatBoundaryError(error));
if (!transport.sessionId) {
await ignoreClose(() => transport.close());
const server = created;
await ignoreClose(server ? () => server.close() : undefined);
await ignoreClose(resourceConfig?.close);
}
return jsonError(500, -32603, "Internal server error");
}
},
handlePausedRequest: async (request) => {
const match = pausedRequestPattern.exec(new URL(request.url).pathname);
if (!match) return json({ error: "Not found" }, 404);
if (request.method !== "GET") return json({ error: "Method not allowed" }, 405);
const paused = await pausedDetail(decodeURIComponent(match[1]), decodeURIComponent(match[2]));
if (!paused) return json({ error: "Paused execution not found" }, 404);
return json({ text: paused.text, structured: paused.structured });
},
handleApprovalRequest: async (request) => {
const match = approvalRequestPattern.exec(new URL(request.url).pathname);
if (!match) return json({ error: "Not found" }, 404);
if (request.method !== "POST") return json({ error: "Method not allowed" }, 405);
const sessionId = decodeURIComponent(match[1]);
const executionId = decodeURIComponent(match[2]);
// The shared engine must still hold the paused execution — guards stale ids.
if (!(await pausedDetail(sessionId, executionId))) {
return json({ error: "MCP session not found" }, 404);
}
const response = await readResumeResponse(request);
if (!response) return json({ error: "Invalid approval response" }, 400);
await Effect.runPromise(approvals.recordResponse(executionId, response));
return json(resumeApprovalResult(executionId, response));
},
close: async () => {
const ids = new Set([...transports.keys(), ...servers.keys()]);
await Promise.all([
...[...ids].map((id) => dispose(id, { transport: true, server: true })),
...[...modernHandlers.values()].map((handler) => handler.close()),
]);
},
};
};
// ---------------------------------------------------------------------------
// Stdio transport
// ---------------------------------------------------------------------------
export const runMcpStdioServer = async (config: ExecutorMcpToolConfig): Promise<void> => {
startIntegrationsRefresh();
const requestStateSigningKey = crypto.getRandomValues(new Uint8Array(32));
const stdio = serveStdio(() =>
Effect.runPromise(
buildMcpServer({
...config,
appsEnabled: config.restoredAppsEnabled ?? false,
requestStateSigningKey,
requestStatePrincipal: "local",
sessionful: true,
}),
),
);
const waitForExit = () =>
new Promise<void>((resolve) => {
const finish = () => {
process.off("SIGINT", finish);
process.off("SIGTERM", finish);
process.stdin.off("end", finish);
process.stdin.off("close", finish);
resolve();
};
process.once("SIGINT", finish);
process.once("SIGTERM", finish);
process.stdin.once("end", finish);
process.stdin.once("close", finish);
});
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: stdio server lifetime uses Promise-based SDK/process APIs and always closes resources
try {
await waitForExit();
} finally {
await ignoreClose(() => stdio.close());
}
};