| 1027 | // --------------------------------------------------------------------------- |
| 1028 | |
| 1029 | export const createExecutorMcpServer = <E extends Cause.YieldableError>( |
| 1030 | config: ExecutorMcpServerConfig<E>, |
| 1031 | ): Effect.Effect<McpServer> => |
| 1032 | Effect.gen(function* () { |
| 1033 | const engine = "engine" in config ? config.engine : createExecutionEngine(config); |
| 1034 | const description = |
| 1035 | config.description ?? |
| 1036 | (yield* engine.getDescription.pipe(Effect.withSpan("mcp.host.get_description"))); |
| 1037 | // The same live integration inventory the description carries, re-used by |
| 1038 | // the `skills` tool so the `execute` guide lists what is connected too. |
| 1039 | const executeInventory = extractInventory(description); |
| 1040 | // Artifacts are on unless this connection opted out (`?artifacts=false`). |
| 1041 | // One flag decides the whole surface: the tools, the shell resource, and |
| 1042 | // the skills catalog below. |
| 1043 | const artifactsEnabled = config.artifactsEnabled ?? true; |
| 1044 | const skillCatalog: readonly Skill[] = skillCatalogFor({ artifacts: artifactsEnabled }); |
| 1045 | |
| 1046 | // Captured at construction time. SDK callbacks fire later (often |
| 1047 | // deferred past the outer Effect's await), so we use the runtime to |
| 1048 | // re-enter Effect-land at each callback edge. |
| 1049 | const context = yield* Effect.context<never>(); |
| 1050 | const debugEnabled = config.debug ?? readDebugDefault(); |
| 1051 | const debugLog = (event: string, data: Record<string, unknown>) => { |
| 1052 | if (!debugEnabled) return; |
| 1053 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: debug logging must tolerate non-serializable SDK capability snapshots |
| 1054 | try { |
| 1055 | console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); |
| 1056 | } catch { |
| 1057 | console.error(`[executor:mcp] ${event}`, data); |
| 1058 | } |
| 1059 | }; |
| 1060 | const elicitationMode = |
| 1061 | config.elicitationMode ?? |
| 1062 | ({ |
| 1063 | mode: "model", |
| 1064 | } as const); |
| 1065 | const pauseDeadline = (): PausedExecutionDeadline | undefined => { |
| 1066 | const ttlMs = config.pausedExecutionLeaseMs; |
| 1067 | return ttlMs === undefined || ttlMs <= 0 |
| 1068 | ? undefined |
| 1069 | : { ttlMs, expiresAt: new Date(Date.now() + ttlMs).toISOString() }; |
| 1070 | }; |
| 1071 | const onExecutionPaused = ( |
| 1072 | executionId: string, |
| 1073 | deadline: PausedExecutionDeadline | undefined, |
| 1074 | ): Effect.Effect<void> => |
| 1075 | config.pausedExecutionHooks?.onExecutionPaused?.(executionId, deadline) ?? Effect.void; |
| 1076 | const onResumeStarted = (executionId: string): Effect.Effect<void> => |
| 1077 | config.pausedExecutionHooks?.onResumeStarted?.(executionId) ?? Effect.void; |
| 1078 | const onResumeSettled = (executionId: string): Effect.Effect<void> => |
| 1079 | config.pausedExecutionHooks?.onResumeSettled?.(executionId) ?? Effect.void; |
| 1080 | const resumeWithLifecycle = (executionId: string, response: ResumeResponse) => |
| 1081 | Effect.gen(function* () { |
| 1082 | yield* onResumeStarted(executionId); |
| 1083 | return yield* engine.resume(executionId, response); |
| 1084 | }).pipe(Effect.ensuring(onResumeSettled(executionId))); |
| 1085 | |
| 1086 | const localExecutionAlreadySettled = (executionId: string): Effect.Effect<boolean> => |