| 569 | // --------------------------------------------------------------------------- |
| 570 | |
| 571 | export const createExecutorMcpServer = <E extends Cause.YieldableError>( |
| 572 | config: ExecutorMcpServerConfig<E>, |
| 573 | ): Effect.Effect<McpServer> => |
| 574 | Effect.gen(function* () { |
| 575 | const engine = "engine" in config ? config.engine : createExecutionEngine(config); |
| 576 | const description = |
| 577 | config.description ?? |
| 578 | (yield* engine.getDescription.pipe(Effect.withSpan("mcp.host.get_description"))); |
| 579 | // The same live integration inventory the description carries, re-used by |
| 580 | // the `skills` tool so the `execute` guide lists what is connected too. |
| 581 | const executeInventory = extractInventory(description); |
| 582 | |
| 583 | // Captured at construction time. SDK callbacks fire later (often |
| 584 | // deferred past the outer Effect's await), so we use the runtime to |
| 585 | // re-enter Effect-land at each callback edge. |
| 586 | const context = yield* Effect.context<never>(); |
| 587 | const debugEnabled = config.debug ?? readDebugDefault(); |
| 588 | const debugLog = (event: string, data: Record<string, unknown>) => { |
| 589 | if (!debugEnabled) return; |
| 590 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: debug logging must tolerate non-serializable SDK capability snapshots |
| 591 | try { |
| 592 | console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); |
| 593 | } catch { |
| 594 | console.error(`[executor:mcp] ${event}`, data); |
| 595 | } |
| 596 | }; |
| 597 | const elicitationMode = |
| 598 | config.elicitationMode ?? |
| 599 | ({ |
| 600 | mode: "model", |
| 601 | } as const); |
| 602 | const onExecutionPaused = (executionId: string): Effect.Effect<void> => |
| 603 | config.pausedExecutionHooks?.onExecutionPaused?.(executionId) ?? Effect.void; |
| 604 | const onResumeStarted = (executionId: string): Effect.Effect<void> => |
| 605 | config.pausedExecutionHooks?.onResumeStarted?.(executionId) ?? Effect.void; |
| 606 | const onResumeSettled = (executionId: string): Effect.Effect<void> => |
| 607 | config.pausedExecutionHooks?.onResumeSettled?.(executionId) ?? Effect.void; |
| 608 | const resumeWithLifecycle = (executionId: string, response: ResumeResponse) => |
| 609 | Effect.gen(function* () { |
| 610 | yield* onResumeStarted(executionId); |
| 611 | return yield* engine.resume(executionId, response); |
| 612 | }).pipe(Effect.ensuring(onResumeSettled(executionId))); |
| 613 | |
| 614 | const resolveParentSpan = (): Tracer.AnySpan | undefined => { |
| 615 | const ps = config.parentSpan; |
| 616 | return typeof ps === "function" ? ps() : ps; |
| 617 | }; |
| 618 | const anchor = <A, EffE>(effect: Effect.Effect<A, EffE>): Effect.Effect<A, EffE> => { |
| 619 | const parent = resolveParentSpan(); |
| 620 | return parent ? Effect.withParentSpan(effect, parent) : effect; |
| 621 | }; |
| 622 | const runToolEffect = <EffE>(effect: Effect.Effect<McpToolResult, EffE>) => |
| 623 | Effect.runPromiseWith(context)( |
| 624 | anchor(effect).pipe( |
| 625 | Effect.catchCause((cause) => Effect.succeed(toMcpFailureResult(cause))), |
| 626 | ), |
| 627 | ); |
| 628 | |