(
principal: Principal,
resource: McpResource,
request: Request,
)
| 421 | |
| 422 | /** Open a new session: build the server, connect a transport, drive the request. */ |
| 423 | const openSession = ( |
| 424 | principal: Principal, |
| 425 | resource: McpResource, |
| 426 | request: Request, |
| 427 | ): Effect.Effect<McpDispatchResult> => { |
| 428 | let createdSessionId: string | null = null; |
| 429 | return buildServer(principal, { |
| 430 | ...buildOptionsFor(request, () => createdSessionId), |
| 431 | resource, |
| 432 | }).pipe( |
| 433 | Effect.flatMap(({ mcpServer, engine }) => |
| 434 | Effect.gen(function* () { |
| 435 | const transport = new WebStandardStreamableHTTPServerTransport({ |
| 436 | sessionIdGenerator: () => crypto.randomUUID(), |
| 437 | enableJsonResponse: true, |
| 438 | onsessioninitialized: (sid) => { |
| 439 | createdSessionId = sid; |
| 440 | transports.set(sid, transport); |
| 441 | servers.set(sid, mcpServer); |
| 442 | owners.set(sid, { principal, resource }); |
| 443 | engines.set(sid, engine); |
| 444 | lastSeen.set(sid, Date.now()); |
| 445 | }, |
| 446 | onsessionclosed: (sid) => void dispose(sid, { server: true }), |
| 447 | }); |
| 448 | transport.onclose = () => { |
| 449 | const sid = transport.sessionId; |
| 450 | if (sid) void dispose(sid, { server: true }); |
| 451 | }; |
| 452 | yield* Effect.promise(() => mcpServer.connect(transport)); |
| 453 | // The session id is minted on the first (initialize) request, so we |
| 454 | // drive `handleRequest` here; if no id results we close eagerly. |
| 455 | return yield* runHandleRequest( |
| 456 | transport, |
| 457 | request, |
| 458 | orgWriteAccessForPrincipal(principal), |
| 459 | () => { |
| 460 | // Nothing was ever registered under a session id, so `dispose` has |
| 461 | // no entry to work from — release the three handles by hand, engine |
| 462 | // included. |
| 463 | void ignoreClose(null, "transport", () => transport.close()); |
| 464 | void ignoreClose(null, "server", () => mcpServer.close()); |
| 465 | void shutdownEngine(null, engine); |
| 466 | }, |
| 467 | ); |
| 468 | }), |
| 469 | ), |
| 470 | // A build failure has nowhere typed to go in the envelope; render a 500. |
| 471 | Effect.catchTag("McpEngineBuildError", () => |
| 472 | Effect.succeed(jsonRpcError(500, -32603, "Internal server error")), |
| 473 | ), |
| 474 | ); |
| 475 | }; |
| 476 | |
| 477 | /** |
| 478 | * The session-less POST path: answer a probe for anything but `initialize` |
no test coverage detected