918 lines · 34.8 KB · TypeScript
RawDownload
import { Duration, Effect, Match, Option, Schema } from "effect";import * as Cause from "effect/Cause";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { ContentBlockSchema, type ContentBlock } from "@modelcontextprotocol/sdk/types.js";import type {  jsonSchemaValidator,  JsonSchemaType,  JsonSchemaValidator,} from "@modelcontextprotocol/sdk/validation/types.js";import { Validator } from "@cfworker/json-schema";import * as z from "zod/v4"; import { isToolFile } from "@executor-js/sdk";import type {  ElicitationResponse,  ElicitationHandler,  ElicitationContext,  ElicitationRequest,  ToolFileValue,} from "@executor-js/sdk";import type * as Tracer from "effect/Tracer";import {  createExecutionEngine,  formatExecuteResult,  formatPausedExecution,  findSkill,  renderSkillsIndex,  EXECUTE_SKILL,  INTEGRATION_INVENTORY_HEADER,  type ExecutionEngine,  type ExecutionEngineConfig,  type ResumeResponse,} from "@executor-js/execution"; // ---------------------------------------------------------------------------// Workers-compatible JSON Schema validator (replaces Ajv which uses new Function())// --------------------------------------------------------------------------- class CfWorkerJsonSchemaValidator implements jsonSchemaValidator {  getValidator<T>(schema: JsonSchemaType): JsonSchemaValidator<T> {    const validator = new Validator(schema as Record<string, unknown>, "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.integrations.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 context at construction time, but `Effect.runPromiseWith`   * 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;  /**   * Controls how elicitation is handled for this MCP connection. The default   * is model-managed resume, where paused executions expose interaction   * metadata and the model can call `resume` with the user's response.   */  readonly elicitationMode?:    | {        readonly mode: "browser";        readonly approvalUrl: (executionId: string) => string;      }    | {        readonly mode: "model";      }    | {        readonly mode: "native";      };  readonly browserApprovalStore?: BrowserApprovalStore;  /**   * Host-owned lifecycle for paused executions. The MCP server reports pause   * boundaries; the host decides whether that means a keepAlive lease, browser   * wait, durable record, or no-op.   */  readonly pausedExecutionHooks?: PausedExecutionHooks;}; export type ExecutorMcpServerConfig<E extends Cause.YieldableError = Cause.YieldableError> =  | (ExecutionEngineConfig<E> & SharedMcpServerConfig)  | ({ readonly engine: ExecutionEngine<E> } & SharedMcpServerConfig)  | (ExecutionEngineConfig<E> & SharedMcpServerConfig & { readonly stateless: true })  | ({ readonly engine: ExecutionEngine<E>; readonly stateless: true } & SharedMcpServerConfig); export type BrowserApprovalStore = {  readonly takeResponse: (executionId: string) => Effect.Effect<ResumeResponse | null>;  readonly waitForResponse?: (executionId: string) => Effect.Effect<ResumeResponse | null>;}; export const PAUSED_APPROVAL_TIMEOUT_MS = 4 * 60 * 1000;const BROWSER_APPROVAL_WAIT_TIMEOUT_MS = PAUSED_APPROVAL_TIMEOUT_MS + 1000; export type PausedExecutionHooks = {  readonly onExecutionPaused?: (executionId: string) => Effect.Effect<void>;  readonly onResumeStarted?: (executionId: string) => Effect.Effect<void>;  readonly onResumeSettled?: (executionId: string) => Effect.Effect<void>;}; // ---------------------------------------------------------------------------// 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<string, unknown>;  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 capabilitySnapshot = (server: McpServer) => ({  clientCapabilities: server.server.getClientCapabilities() ?? null,  elicitationSupport: getElicitationSupport(server),}); type ElicitInputParams =  | {      mode?: "form";      message: string;      requestedSchema: { readonly [key: string]: unknown };    }  | { mode: "url"; message: string; url: string; elicitationId: string }; const elicitationRequestTag = (request: ElicitationRequest): ElicitationRequest["_tag"] =>  Match.value(request).pipe(    Match.tag("UrlElicitation", () => "UrlElicitation" as const),    Match.tag("FormElicitation", () => "FormElicitation" as const),    Match.exhaustive,  ); const requestedSchemaIsNonEmpty = (request: ElicitationRequest): boolean =>  Match.value(request).pipe(    Match.tag("FormElicitation", (req) => Object.keys(req.requestedSchema).length > 0),    Match.tag("UrlElicitation", () => false),    Match.exhaustive,  ); const elicitationRequestUrl = (request: ElicitationRequest): string | undefined =>  Match.value(request).pipe(    Match.tag("UrlElicitation", (req): string | undefined => req.url),    Match.tag("FormElicitation", (): string | undefined => undefined),    Match.exhaustive,  ); const pausedInteractionKind = (request: ElicitationRequest): ElicitationRequest["_tag"] =>  elicitationRequestTag(request); const elicitationRequestToParams: (request: ElicitationRequest) => ElicitInputParams =  Match.type<ElicitationRequest>().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<string, unknown>) => void,  ): ElicitationHandler =>  (ctx: ElicitationContext): Effect.Effect<typeof ElicitationResponse.Type> => {    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 = Match.value(ctx.request).pipe(      Match.tag(        "UrlElicitation",        (req): ElicitInputParams =>          !supportsUrl            ? {                message: `${req.message}\n\nPlease visit this URL:\n${req.url}\n\nClick accept once you have completed the flow.`,                requestedSchema: { type: "object" as const, properties: {} },              }            : elicitationRequestToParams(req),      ),      Match.tag("FormElicitation", (req): ElicitInputParams => elicitationRequestToParams(req)),      Match.exhaustive,    );     return Effect.promise(async (): Promise<typeof ElicitationResponse.Type> => {      const requestTag = elicitationRequestTag(ctx.request);      debugLog?.("elicitation.request", {        requestTag,        supportsUrl,        message: ctx.request.message,        hasRequestedSchema: requestedSchemaIsNonEmpty(ctx.request),        url: elicitationRequestUrl(ctx.request),        clientCapabilities: server.server.getClientCapabilities() ?? null,      });       // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: MCP SDK elicitInput is a Promise API; failures become a cancel response      try {        const response = await server.server.elicitInput(          params as Parameters<typeof server.server.elicitInput>[0],        );         debugLog?.("elicitation.response", {          requestTag,          action: response.action,          hasContent:            typeof response.content === "object" &&            response.content !== null &&            Object.keys(response.content).length > 0,        });         return {          action: response.action as typeof ElicitationResponse.Type.action,          content: response.content,        };      } catch (err) {        const error = formatBoundaryError(err);        debugLog?.("elicitation.error", {          requestTag,          error,          clientCapabilities: server.server.getClientCapabilities() ?? null,        });        console.error(          "[executor] elicitInput failed - falling back to cancel.",          JSON.stringify({            error,            requestTag,            ...capabilitySnapshot(server),          }),        );        return { action: "cancel" as const } as ElicitationResponse;      }    });  }; const formatBoundaryError = (err: unknown): { name?: string; message: string; stack?: string } => {  // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: SDK Promise rejection supplies unknown JS errors for logging only  if (err instanceof Error) return { name: err.name, message: err.message, stack: err.stack };  // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: fallback log formatting for unknown SDK Promise rejection values  return { message: String(err) };}; // ---------------------------------------------------------------------------// MCP result formatting// --------------------------------------------------------------------------- type McpToolResult = {  content: ContentBlock[];  structuredContent?: Record<string, unknown>;  isError?: boolean;}; type FormattedExecuteInput = Parameters<typeof formatExecuteResult>[0];type ExecuteOutputItem = NonNullable<FormattedExecuteInput["output"]>[number]; const TEXT_FILE_CONTENT_MAX_CHARS = 64_000; const isRecord = (value: unknown): value is Record<string, unknown> =>  typeof value === "object" && value !== null && !Array.isArray(value); const toolFileName = (file: ToolFileValue): string => file.name ?? "tool-output"; const fileResourceUri = (file: ToolFileValue): string =>  `executor-file:///${encodeURIComponent(toolFileName(file))}`; const normalizedMimeType = (file: ToolFileValue): string =>  file.mimeType.split(";")[0]?.trim().toLowerCase() ?? ""; const toolFileKind = (file: ToolFileValue): "image" | "audio" | "text" | "resource" => {  const mimeType = normalizedMimeType(file);  if (mimeType.startsWith("image/")) return "image";  if (mimeType.startsWith("audio/")) return "audio";  if (    mimeType.startsWith("text/") ||    mimeType === "application/json" ||    mimeType.endsWith("+json") ||    mimeType === "application/xml" ||    mimeType.endsWith("+xml") ||    mimeType === "application/javascript" ||    mimeType === "application/x-javascript" ||    mimeType === "application/yaml" ||    mimeType === "application/x-yaml"  ) {    return "text";  }  return "resource";}; const bytesFromBase64 = (base64: string): Uint8Array => {  const binary = atob(base64);  const bytes = new Uint8Array(binary.length);  for (let index = 0; index < binary.length; index += 1) {    bytes[index] = binary.charCodeAt(index);  }  return bytes;}; const decodeTextFile = (file: ToolFileValue): string => {  const text = new TextDecoder("utf-8", { fatal: false }).decode(bytesFromBase64(file.data));  if (text.length <= TEXT_FILE_CONTENT_MAX_CHARS) return text;  return `${text.slice(0, TEXT_FILE_CONTENT_MAX_CHARS)}\n\n[truncated ${    text.length - TEXT_FILE_CONTENT_MAX_CHARS  } characters]`;}; const toolFileContent = (file: ToolFileValue): ContentBlock[] => {  const kind = toolFileKind(file);  if (kind === "image") {    return [{ type: "image", data: file.data, mimeType: file.mimeType }];  }  if (kind === "audio") {    return [{ type: "audio", data: file.data, mimeType: file.mimeType }];  }  if (kind === "text") {    return [{ type: "text", text: decodeTextFile(file) }];  }  return [    {      type: "resource",      resource: {        uri: fileResourceUri(file),        mimeType: file.mimeType,        blob: file.data,      },    },  ];}; const toolFileSummaryLine = (file: ToolFileValue, index?: number): string => {  const prefix = index === undefined ? "" : `${index + 1}. `;  return `${prefix}${toolFileName(file)} (${file.mimeType}, ${file.byteLength} bytes)`;}; const outputFileContent = (file: ToolFileValue): ContentBlock[] => [  {    type: "text",    text: `File output: ${toolFileSummaryLine(file)}`,  },  ...toolFileContent(file),]; const isFileOutputItem = (  item: ExecuteOutputItem,): item is { readonly type: "file"; readonly file: ToolFileValue } =>  isRecord(item) && item.type === "file" && isToolFile(item.file); const isMcpContentBlock = (value: unknown): value is ContentBlock =>  ContentBlockSchema.safeParse(value).success; const isContentOutputItem = (  item: ExecuteOutputItem,): item is { readonly type: "content"; readonly content: ContentBlock } =>  isRecord(item) && item.type === "content" && isMcpContentBlock(item.content); const outputItemContent = (item: ExecuteOutputItem): ContentBlock[] => {  if (isFileOutputItem(item)) {    return outputFileContent(item.file);  }  if (isContentOutputItem(item)) {    return [item.content];  }  return [{ type: "text", text: "Invalid execution output item omitted." }];}; const toMcpOutputResult = (  result: FormattedExecuteInput,  output: readonly ExecuteOutputItem[],): McpToolResult => {  const formatted = formatExecuteResult(result);  const content = output.flatMap(outputItemContent);  const extraText: string[] = [];  if (result.error) {    extraText.push(formatted.text);  } else if (result.logs && result.logs.length > 0) {    extraText.push(`Logs:\n${result.logs.join("\n")}`);  }  content.push(...extraText.map((text): ContentBlock => ({ type: "text", text })));   return {    content,    structuredContent: formatted.structured,    isError: formatted.isError || undefined,  };}; const toMcpResult = (result: FormattedExecuteInput): McpToolResult => {  if (result.output && result.output.length > 0) return toMcpOutputResult(result, result.output);  const formatted = formatExecuteResult(result);  return {    content: [{ type: "text", text: formatted.text }],    structuredContent: formatted.structured,    isError: formatted.isError || undefined,  };}; const toMcpPausedResult = (formatted: ReturnType<typeof formatPausedExecution>): McpToolResult => ({  content: [{ type: "text", text: formatted.text }],  structuredContent: formatted.structured,}); // `execute` failures reaching the MCP host are infra defects — domain// failures from tools are now expressed as `ToolResult` values (success// channel) and flow through `formatExecuteResult`. Emit an opaque// generic plus a fresh correlation id and log the cause out-of-band so// the model can't read internal context off `.message`.const newCorrelationId = (): string =>  Math.floor(Math.random() * 0x1_0000_0000)    .toString(16)    .padStart(8, "0"); const defaultResumeApprovalUrl = (executionId: string): string =>  `/resume/${encodeURIComponent(executionId)}`; const browserApprovalReturnPrompt =  "Return text to the user telling them to approve the action at this approvalUrl. Only after you have prompted the user, call the `resume` tool with this executionId; `resume` will wait for the user's browser decision."; const formatResumeApprovalRequired = (input: {  readonly executionId: string;  readonly approvalUrl: string;}): McpToolResult => ({  content: [    {      type: "text",      text: [        "User approval required.",        "",        "Tell the user to open this URL while signed in and approve or decline the paused interaction:",        input.approvalUrl,        "",        "Required next steps for this agent:",        browserApprovalReturnPrompt,      ].join("\n"),    },  ],  structuredContent: {    status: "user_approval_required",    executionId: input.executionId,    approvalUrl: input.approvalUrl,    resumePrompt: browserApprovalReturnPrompt,  },}); const toMcpFailureResult = (cause: Cause.Cause<unknown>): McpToolResult => {  const correlationId = newCorrelationId();  // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: best-effort defect logging must tolerate non-serializable causes  try {    console.error(      `[executor:mcp] execute defect correlation_id=${correlationId}`,      Cause.pretty(cause),    );  } catch {    /* ignore logger failures */  }  const text = `Internal tool error [${correlationId}]`;  return {    content: [{ type: "text", text: `Error: ${text}` }],    structuredContent: { status: "error", error: text },    isError: true,  };};

Lines 1–500 of 918.