(
principal: Principal,
resource: McpResource,
request: Request,
)
| 389 | |
| 390 | /** Open a new session: build the server, connect a transport, drive the request. */ |
| 391 | const openSession = ( |
| 392 | principal: Principal, |
| 393 | resource: McpResource, |
| 394 | request: Request, |
| 395 | ): Effect.Effect<McpDispatchResult> => { |
| 396 | let createdSessionId: string | null = null; |
| 397 | return buildServer(principal, { |
| 398 | ...buildOptionsFor(request, () => createdSessionId), |
| 399 | resource, |
| 400 | }).pipe( |
| 401 | Effect.flatMap(({ mcpServer, engine }) => |
| 402 | Effect.gen(function* () { |
| 403 | const transport = new WebStandardStreamableHTTPServerTransport({ |
| 404 | sessionIdGenerator: () => crypto.randomUUID(), |
| 405 | enableJsonResponse: true, |
| 406 | onsessioninitialized: (sid) => { |
| 407 | createdSessionId = sid; |
| 408 | transports.set(sid, transport); |
| 409 | servers.set(sid, mcpServer); |
| 410 | owners.set(sid, { principal, resource }); |
| 411 | engines.set(sid, engine); |
| 412 | lastSeen.set(sid, Date.now()); |
| 413 | }, |
| 414 | onsessionclosed: (sid) => void dispose(sid, { server: true }), |
| 415 | }); |
| 416 | transport.onclose = () => { |
| 417 | const sid = transport.sessionId; |
| 418 | if (sid) void dispose(sid, { server: true }); |
| 419 | }; |
| 420 | yield* Effect.promise(() => mcpServer.connect(transport)); |
| 421 | // The session id is minted on the first (initialize) request, so we |
| 422 | // drive `handleRequest` here; if no id results we close eagerly. |
| 423 | return yield* runHandleRequest(transport, request, () => { |
| 424 | // Nothing was ever registered under a session id, so `dispose` has |
| 425 | // no entry to work from — release the three handles by hand, engine |
| 426 | // included. |
| 427 | void ignoreClose(null, "transport", () => transport.close()); |
| 428 | void ignoreClose(null, "server", () => mcpServer.close()); |
| 429 | void shutdownEngine(null, engine); |
| 430 | }); |
| 431 | }), |
| 432 | ), |
| 433 | // A build failure has nowhere typed to go in the envelope; render a 500. |
| 434 | Effect.catchTag("McpEngineBuildError", () => |
| 435 | Effect.succeed(jsonRpcError(500, -32603, "Internal server error")), |
| 436 | ), |
| 437 | ); |
| 438 | }; |
| 439 | |
| 440 | /** |
| 441 | * The session-less POST path: answer a probe for anything but `initialize` |
no test coverage detected