import { Effect, Match, Runtime } from "effect"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { jsonSchemaValidator, JsonSchemaType, JsonSchemaValidator, } from "@modelcontextprotocol/sdk/validation/types.js"; import { Validator } from "@cfworker/json-schema"; import { z } from "zod/v4"; import type { ElicitationResponse, ElicitationHandler, ElicitationContext, ElicitationRequest, } from "@executor/sdk"; import type * as Cause from "effect/Cause"; import type * as Tracer from "effect/Tracer"; import { createExecutionEngine, formatExecuteResult, formatPausedExecution, type ExecutionEngine, type ExecutionEngineConfig, } from "@executor/execution"; // --------------------------------------------------------------------------- // Workers-compatible JSON Schema validator (replaces Ajv which uses new Function()) // --------------------------------------------------------------------------- class CfWorkerJsonSchemaValidator implements jsonSchemaValidator { getValidator(schema: JsonSchemaType): JsonSchemaValidator { const validator = new Validator(schema as Record, "2020-12", false); return (input: unknown) => { const result = validator.validate(input); if (result.valid) { return { valid: true, data: input as T, errorMessage: undefined }; } const errorMessage = result.errors.map((e) => `${e.instanceLocation}: ${e.error}`).join("; "); return { valid: false, data: undefined, errorMessage }; }; } } // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- type SharedMcpServerConfig = { /** * Pre-built `execute` tool description. When provided, the factory skips * its internal `engine.getDescription` yield. Useful when the caller * wants to compute the description inside its own Effect tracer context * so sub-spans (`executor.sources.list`, `executor.tools.list`) nest as * children of the caller's root span. */ readonly description?: string; /** * Parent span override for engine calls. The factory captures the * caller's `Runtime` at construction time, but `Runtime.runPromise` * starts a fresh fiber per SDK callback — so the `currentSpan` * FiberRef resets to root unless explicitly anchored. * * Accepts either a fixed span (per-request McpServer instances) or a * getter (session-scoped instances that need to anchor each callback * under whichever request triggered it; see the Cloud DO). */ readonly parentSpan?: Tracer.AnySpan | (() => Tracer.AnySpan | undefined); /** * Enable verbose MCP capability / elicitation debug logging. */ readonly debug?: boolean; }; export type ExecutorMcpServerConfig = | (ExecutionEngineConfig & SharedMcpServerConfig) | ({ readonly engine: ExecutionEngine } & SharedMcpServerConfig) | (ExecutionEngineConfig & SharedMcpServerConfig & { readonly stateless: true }) | ({ readonly engine: ExecutionEngine; readonly stateless: true } & SharedMcpServerConfig); // --------------------------------------------------------------------------- // Elicitation bridge // --------------------------------------------------------------------------- const getElicitationSupport = (server: McpServer): { form: boolean; url: boolean } => { const capabilities = server.server.getClientCapabilities(); if (capabilities === undefined || !capabilities.elicitation) return { form: false, url: false }; const elicitation = capabilities.elicitation as Record; return { form: Boolean(elicitation.form), url: Boolean(elicitation.url) }; }; const readDebugDefault = (): boolean => { if (typeof process === "undefined" || !process.env) return false; const value = process.env.EXECUTOR_MCP_DEBUG; return value === "1" || value === "true"; }; const supportsManagedElicitation = (server: McpServer): boolean => getElicitationSupport(server).form; const capabilitySnapshot = (server: McpServer) => ({ clientCapabilities: server.server.getClientCapabilities() ?? null, elicitationSupport: getElicitationSupport(server), managedElicitation: supportsManagedElicitation(server), }); type ElicitInputParams = | { mode?: "form"; message: string; requestedSchema: { readonly [key: string]: unknown }; } | { mode: "url"; message: string; url: string; elicitationId: string }; const elicitationRequestToParams: (request: ElicitationRequest) => ElicitInputParams = Match.type().pipe( Match.tag("UrlElicitation", (req) => ({ mode: "url" as const, message: req.message, url: req.url, elicitationId: req.elicitationId, })), Match.tag("FormElicitation", (req) => ({ message: req.message, // The MCP SDK validates requestedSchema as a JSON Schema with // `type: "object"` and `properties`. For approval-only elicitations // where no fields are needed, provide a minimal valid schema. requestedSchema: Object.keys(req.requestedSchema).length === 0 ? { type: "object" as const, properties: {} } : req.requestedSchema, })), Match.exhaustive, ); const makeMcpElicitationHandler = ( server: McpServer, debugLog?: (event: string, data: Record) => void, ): ElicitationHandler => (ctx: ElicitationContext): Effect.Effect => { const { url: supportsUrl } = getElicitationSupport(server); // If client doesn't support url mode, fall back to a form asking the user // to visit the URL manually and confirm when done. const params = ctx.request._tag === "UrlElicitation" && !supportsUrl ? { message: `${ctx.request.message}\n\nPlease visit this URL:\n${ctx.request.url}\n\nClick accept once you have completed the flow.`, requestedSchema: { type: "object" as const, properties: {} }, } : elicitationRequestToParams(ctx.request); return Effect.promise(async (): Promise => { debugLog?.("elicitation.request", { requestTag: ctx.request._tag, supportsUrl, message: ctx.request.message, hasRequestedSchema: ctx.request._tag === "FormElicitation" ? Object.keys(ctx.request.requestedSchema).length > 0 : false, url: ctx.request._tag === "UrlElicitation" ? ctx.request.url : undefined, clientCapabilities: server.server.getClientCapabilities() ?? null, }); try { const response = await server.server.elicitInput( params as Parameters[0], ); debugLog?.("elicitation.response", { requestTag: ctx.request._tag, action: response.action, hasContent: typeof response.content === "object" && response.content !== null && Object.keys(response.content).length > 0, }); return { action: response.action, content: response.content, }; } catch (err) { debugLog?.("elicitation.error", { requestTag: ctx.request._tag, error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : { message: String(err) }, clientCapabilities: server.server.getClientCapabilities() ?? null, }); console.error( "[executor] elicitInput failed — falling back to cancel.", JSON.stringify({ error: err instanceof Error ? err.message : String(err), requestTag: ctx.request._tag, ...capabilitySnapshot(server), }), ); return { action: "cancel" }; } }); }; // --------------------------------------------------------------------------- // MCP result formatting // --------------------------------------------------------------------------- type McpToolResult = { content: Array<{ type: "text"; text: string }>; structuredContent?: Record; isError?: boolean; }; const toMcpResult = (formatted: ReturnType): McpToolResult => ({ content: [{ type: "text", text: formatted.text }], structuredContent: formatted.structured, isError: formatted.isError || undefined, }); const toMcpPausedResult = (formatted: ReturnType): McpToolResult => ({ content: [{ type: "text", text: formatted.text }], structuredContent: formatted.structured, }); const parseJsonContent = (raw: string): Record | undefined => { if (raw === "{}") return undefined; let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return undefined; } return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? (parsed as Record) : undefined; }; // --------------------------------------------------------------------------- // Server factory // --------------------------------------------------------------------------- export const createExecutorMcpServer = ( config: ExecutorMcpServerConfig, ): Effect.Effect => Effect.gen(function* () { const engine = "engine" in config ? config.engine : createExecutionEngine(config); const description = config.description ?? (yield* engine.getDescription.pipe( Effect.withSpan("mcp.host.get_description"), )); // Captured at construction time. SDK callbacks fire later (often // deferred past the outer Effect's await), so we use the runtime to // re-enter Effect-land at each callback edge. const runtime = yield* Effect.runtime(); const debugEnabled = config.debug ?? readDebugDefault(); const debugLog = (event: string, data: Record) => { if (!debugEnabled) return; try { console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); } catch { console.error(`[executor:mcp] ${event}`, data); } }; const resolveParentSpan = (): Tracer.AnySpan | undefined => { const ps = config.parentSpan; return typeof ps === "function" ? ps() : ps; }; const anchor = (effect: Effect.Effect): Effect.Effect => { const parent = resolveParentSpan(); return parent ? Effect.withParentSpan(effect, parent) : effect; }; const server = yield* Effect.sync( () => new McpServer( { name: "executor", version: "1.0.0" }, { capabilities: { tools: {} }, jsonSchemaValidator: new CfWorkerJsonSchemaValidator(), }, ), ).pipe(Effect.withSpan("mcp.host.create_server")); const executeCode = (code: string): Effect.Effect => Effect.gen(function* () { debugLog("execute.call", { managedElicitation: supportsManagedElicitation(server), elicitationSupport: getElicitationSupport(server), clientCapabilities: server.server.getClientCapabilities() ?? null, codeLength: code.length, }); if (supportsManagedElicitation(server)) { const result = yield* engine.execute(code, { onElicitation: makeMcpElicitationHandler(server, debugLog), }); return toMcpResult(formatExecuteResult(result)); } const outcome = yield* engine.executeWithPause(code); debugLog("execute.paused_flow_result", { status: outcome.status, executionId: outcome.status === "paused" ? outcome.execution.id : undefined, interactionKind: outcome.status === "paused" ? outcome.execution.elicitationContext.request._tag : undefined, }); return outcome.status === "completed" ? toMcpResult(formatExecuteResult(outcome.result)) : toMcpPausedResult(formatPausedExecution(outcome.execution)); }).pipe( Effect.withSpan("mcp.host.tool.execute", { attributes: { "mcp.tool.name": "execute", "mcp.execute.code_length": code.length, }, }), ); const resumeExecution = ( executionId: string, action: "accept" | "decline" | "cancel", content: Record | undefined, ): Effect.Effect => Effect.gen(function* () { debugLog("resume.call", { executionId, action, hasContent: content !== undefined, clientCapabilities: server.server.getClientCapabilities() ?? null, }); const outcome = yield* engine.resume(executionId, { action, content }); if (!outcome) { debugLog("resume.missing_execution", { executionId }); return { content: [ { type: "text" as const, text: `No paused execution: ${executionId}` }, ], isError: true, } satisfies McpToolResult; } debugLog("resume.result", { executionId, status: outcome.status, nextExecutionId: outcome.status === "paused" ? outcome.execution.id : undefined, interactionKind: outcome.status === "paused" ? outcome.execution.elicitationContext.request._tag : undefined, }); return outcome.status === "completed" ? toMcpResult(formatExecuteResult(outcome.result)) : toMcpPausedResult(formatPausedExecution(outcome.execution)); }).pipe( Effect.withSpan("mcp.host.tool.resume", { attributes: { "mcp.tool.name": "resume", "mcp.execute.resume.action": action, "mcp.execute.execution_id": executionId, }, }), ); // --- tools --- const executeTool = yield* Effect.sync(() => server.registerTool( "execute", { description, inputSchema: { code: z.string().trim().min(1) }, }, ({ code }) => Runtime.runPromise(runtime)(anchor(executeCode(code))), ), ).pipe( Effect.withSpan("mcp.host.register_tool", { attributes: { "mcp.tool.name": "execute" }, }), ); const resumeTool = yield* Effect.sync(() => server.registerTool( "resume", { description: [ "Resume a paused execution using the executionId returned by execute.", "Never call this without user approval unless they explicitly state otherwise.", ].join("\n"), inputSchema: { executionId: z.string().describe("The execution ID from the paused result"), action: z .enum(["accept", "decline", "cancel"]) .describe("How to respond to the interaction"), content: z .string() .describe("Optional JSON-encoded response content for form elicitations") .default("{}"), }, }, ({ executionId, action, content: rawContent }) => Runtime.runPromise(runtime)( anchor(resumeExecution(executionId, action, parseJsonContent(rawContent))), ), ), ).pipe( Effect.withSpan("mcp.host.register_tool", { attributes: { "mcp.tool.name": "resume" }, }), ); // --- capability-based tool visibility --- const syncToolAvailability = () => { executeTool.enable(); if (supportsManagedElicitation(server)) { resumeTool.disable(); } else { resumeTool.enable(); } console.error( "[executor] MCP capability snapshot", JSON.stringify({ ...capabilitySnapshot(server), resumeEnabled: !supportsManagedElicitation(server), }), ); debugLog("tool.visibility", { clientCapabilities: server.server.getClientCapabilities() ?? null, elicitationSupport: getElicitationSupport(server), managedElicitation: supportsManagedElicitation(server), resumeEnabled: !supportsManagedElicitation(server), }); }; yield* Effect.sync(() => { syncToolAvailability(); server.server.oninitialized = syncToolAvailability; }).pipe(Effect.withSpan("mcp.host.sync_tool_availability")); return server; }).pipe(Effect.withSpan("mcp.host.create_executor_server"));