| 1071 | // --------------------------------------------------------------------------- |
| 1072 | |
| 1073 | export const createExecutorMcpServer = <E extends Cause.YieldableError>( |
| 1074 | config: ExecutorMcpServerConfig<E>, |
| 1075 | ): Effect.Effect<McpServer> => |
| 1076 | Effect.gen(function* () { |
| 1077 | const engine = "engine" in config ? config.engine : createExecutionEngine(config); |
| 1078 | const description = |
| 1079 | config.description ?? |
| 1080 | (yield* engine.getDescription.pipe(Effect.withSpan("mcp.host.get_description"))); |
| 1081 | // The same live integration inventory the description carries, re-used by |
| 1082 | // the `skills` tool so the `execute` guide lists what is connected too. |
| 1083 | const executeInventory = extractInventory(description); |
| 1084 | // Artifacts are on unless this connection opted out (`?artifacts=false`). |
| 1085 | // One flag decides the whole surface: the tools, the shell resource, and |
| 1086 | // the skills catalog below. |
| 1087 | const artifactsEnabled = config.artifactsEnabled ?? true; |
| 1088 | const skillCatalog: readonly Skill[] = skillCatalogFor({ artifacts: artifactsEnabled }); |
| 1089 | |
| 1090 | // Captured at construction time. SDK callbacks fire later (often |
| 1091 | // deferred past the outer Effect's await), so we use the runtime to |
| 1092 | // re-enter Effect-land at each callback edge. |
| 1093 | const context = yield* Effect.context<never>(); |
| 1094 | const debugEnabled = config.debug ?? readDebugDefault(); |
| 1095 | const debugLog = (event: string, data: Record<string, unknown>) => { |
| 1096 | if (!debugEnabled) return; |
| 1097 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: debug logging must tolerate non-serializable SDK capability snapshots |
| 1098 | try { |
| 1099 | console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); |
| 1100 | } catch { |
| 1101 | console.error(`[executor:mcp] ${event}`, data); |
| 1102 | } |
| 1103 | }; |
| 1104 | const elicitationMode = |
| 1105 | config.elicitationMode ?? |
| 1106 | ({ |
| 1107 | mode: "model", |
| 1108 | } as const); |
| 1109 | const pauseDeadline = (): PausedExecutionDeadline | undefined => { |
| 1110 | const ttlMs = config.pausedExecutionLeaseMs; |
| 1111 | return ttlMs === undefined || ttlMs <= 0 |
| 1112 | ? undefined |
| 1113 | : { ttlMs, expiresAt: new Date(Date.now() + ttlMs).toISOString() }; |
| 1114 | }; |
| 1115 | const onExecutionPaused = ( |
| 1116 | executionId: string, |
| 1117 | deadline: PausedExecutionDeadline | undefined, |
| 1118 | ): Effect.Effect<void> => |
| 1119 | config.pausedExecutionHooks?.onExecutionPaused?.(executionId, deadline) ?? Effect.void; |
| 1120 | const onResumeStarted = (executionId: string): Effect.Effect<void> => |
| 1121 | config.pausedExecutionHooks?.onResumeStarted?.(executionId) ?? Effect.void; |
| 1122 | const onResumeSettled = (executionId: string): Effect.Effect<void> => |
| 1123 | config.pausedExecutionHooks?.onResumeSettled?.(executionId) ?? Effect.void; |
| 1124 | const resumeWithLifecycle = (executionId: string, response: ResumeResponse) => |
| 1125 | Effect.gen(function* () { |
| 1126 | yield* onResumeStarted(executionId); |
| 1127 | return yield* engine.resume(executionId, response); |
| 1128 | }).pipe(Effect.ensuring(onResumeSettled(executionId))); |
| 1129 | |
| 1130 | const localExecutionAlreadySettled = (executionId: string): Effect.Effect<boolean> => |