| 548 | // --------------------------------------------------------------------------- |
| 549 | |
| 550 | export const createMcpConnector = (input: ConnectorInput): McpConnector => { |
| 551 | if (input.transport === "stdio") { |
| 552 | const command = input.command.trim(); |
| 553 | if (!command) { |
| 554 | return Effect.fail( |
| 555 | new McpConnectionError({ |
| 556 | transport: "stdio", |
| 557 | message: "MCP stdio transport requires a command", |
| 558 | }), |
| 559 | ); |
| 560 | } |
| 561 | |
| 562 | // The Codex app-server bridge: same spawn mechanics, but the child is |
| 563 | // `codex app-server` and an in-process adapter translates MCP to the |
| 564 | // app-server protocol (see appserver-connector.ts for why direct spawns |
| 565 | // of the curated Codex plugins cannot serve tool calls any more). The |
| 566 | // bridge answers the MCP handshake itself, so `versionNegotiation` does |
| 567 | // not apply on this path. |
| 568 | if (input.appServer !== undefined) { |
| 569 | const { server, surface, modulePath } = input.appServer; |
| 570 | return Effect.gen(function* () { |
| 571 | const { createAppServerTransport } = yield* Effect.tryPromise({ |
| 572 | try: () => import("./appserver-connector"), |
| 573 | catch: () => |
| 574 | new McpConnectionError({ |
| 575 | transport: "appserver", |
| 576 | message: "Failed to load the Codex app-server bridge module", |
| 577 | }), |
| 578 | }); |
| 579 | |
| 580 | return yield* connectClient({ |
| 581 | transport: "appserver", |
| 582 | createTransport: () => |
| 583 | createAppServerTransport({ |
| 584 | command, |
| 585 | args: input.args, |
| 586 | env: input.env, |
| 587 | cwd: input.cwd?.trim().length ? input.cwd.trim() : undefined, |
| 588 | server, |
| 589 | ...(surface === undefined ? {} : { surface }), |
| 590 | ...(modulePath === undefined ? {} : { modulePath }), |
| 591 | }), |
| 592 | }); |
| 593 | }); |
| 594 | } |
| 595 | |
| 596 | return Effect.gen(function* () { |
| 597 | // Dynamic import so the underlying module (which evaluates |
| 598 | // `node:child_process`) is only loaded when stdio is actually used. |
| 599 | const { createStdioTransport } = yield* Effect.tryPromise({ |
| 600 | try: () => import("./stdio-connector"), |
| 601 | catch: () => |
| 602 | new McpConnectionError({ |
| 603 | transport: "stdio", |
| 604 | message: "Failed to load stdio transport module", |
| 605 | }), |
| 606 | }); |
| 607 | |