| 1520 | // --------------------------------------------------------------------------- |
| 1521 | |
| 1522 | export const createExecutorMcpServer = <E extends Cause.YieldableError>( |
| 1523 | config: ExecutorMcpServerConfig<E>, |
| 1524 | ): Effect.Effect<McpServer, McpPassthroughUnavailableError> => |
| 1525 | Effect.gen(function* () { |
| 1526 | const engine = "engine" in config ? config.engine : createExecutionEngine(config); |
| 1527 | const description = |
| 1528 | config.description ?? |
| 1529 | (yield* engine.getDescription.pipe(Effect.withSpan("mcp.host.get_description"))); |
| 1530 | // The same live integration inventory the description carries, re-used by |
| 1531 | // the `skills` tool so the `execute` guide lists what is connected too. |
| 1532 | const executeInventory = extractInventory(description); |
| 1533 | // Artifacts are on unless this connection opted out (`?artifacts=false`). |
| 1534 | // One flag decides the whole surface: the tools, the shell resource, and |
| 1535 | // the skills catalog below. |
| 1536 | const artifactsEnabled = config.artifactsEnabled ?? true; |
| 1537 | const skillCatalog: readonly Skill[] = |
| 1538 | config.mode === "passthrough" |
| 1539 | ? [ |
| 1540 | SEARCH_INVOKE_SKILL, |
| 1541 | ...skillCatalogFor({ |
| 1542 | artifacts: artifactsEnabled && config.loadAppShellHtml !== undefined, |
| 1543 | discovery: "search-invoke", |
| 1544 | }), |
| 1545 | ] |
| 1546 | : skillCatalogFor({ artifacts: artifactsEnabled }); |
| 1547 | // Per-integration search tools are off unless this connection opted in |
| 1548 | // (`?search_tools=true`). |
| 1549 | const searchToolsEnabled = config.searchToolsEnabled ?? false; |
| 1550 | // Passthrough (`?mode=passthrough`) replaces the codemode surface |
| 1551 | // wholesale. The flag is read once here and every codemode-only |
| 1552 | // registration below is gated on it, so the two surfaces cannot leak into |
| 1553 | // each other. |
| 1554 | const mode: McpToolMode = config.mode ?? "codemode"; |
| 1555 | const passthrough = mode === "passthrough"; |
| 1556 | if (passthrough && (!config.tools || !config.connections || !config.integrations)) { |
| 1557 | return yield* new McpPassthroughUnavailableError({ |
| 1558 | reason: |
| 1559 | "passthrough mode requires tool list/schema, connection list, and integration list APIs", |
| 1560 | }); |
| 1561 | } |
| 1562 | |
| 1563 | // Captured at construction time. SDK callbacks fire later (often |
| 1564 | // deferred past the outer Effect's await), so we use the runtime to |
| 1565 | // re-enter Effect-land at each callback edge. |
| 1566 | const context = yield* Effect.context<never>(); |
| 1567 | const debugEnabled = config.debug ?? readDebugDefault(); |
| 1568 | const debugLog = (event: string, data: Record<string, unknown>) => { |
| 1569 | if (!debugEnabled) return; |
| 1570 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: debug logging must tolerate non-serializable SDK capability snapshots |
| 1571 | try { |
| 1572 | console.error(`[executor:mcp] ${event} ${JSON.stringify(data)}`); |
| 1573 | } catch { |
| 1574 | console.error(`[executor:mcp] ${event}`, data); |
| 1575 | } |
| 1576 | }; |
| 1577 | const elicitationMode = |
| 1578 | config.elicitationMode ?? |
| 1579 | ({ |