@orkestrel/mcp 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  import { createSSEParser } from "@orkestrel/sse";
2
- import { JSONRPC_INVALID_REQUEST, JSONRPC_PARSE_ERROR, bindServer, isInitializeRequest, isJSONRPCRequest, jsonRPCError, parseJSONRPCMessage } from "../core/index.js";
3
- import { isString } from "@orkestrel/contract";
2
+ import { JSONRPC_INVALID_REQUEST, JSONRPC_PARSE_ERROR, SUPPORTED_PROTOCOL_VERSIONS, bindServer, isInitializeRequest, isJSONRPCRequest, isJSONRPCResponse, jsonRPCError, parseJSONRPCMessage } from "../core/index.js";
3
+ import { isRecord, isString } from "@orkestrel/contract";
4
+ import { openStream } from "@orkestrel/server";
4
5
  import { Emitter } from "@orkestrel/emitter";
5
6
  import { randomBytes } from "node:crypto";
6
7
  import { request } from "node:http";
7
8
  import { request as request$1 } from "node:https";
8
9
  import { WEBSOCKET_VERSION, computeWebSocketAccept, createNodeWebSocket } from "@orkestrel/websocket";
9
10
  import { spawn } from "node:child_process";
10
- import { openStream } from "@orkestrel/server";
11
11
  //#region src/server/constants.ts
12
12
  /**
13
13
  * The Streamable-HTTP transport header that carries the MCP session id. When a {@link
@@ -17,10 +17,13 @@ import { openStream } from "@orkestrel/server";
17
17
  */
18
18
  var MCP_SESSION_HEADER = "mcp-session-id";
19
19
  /**
20
- * The Streamable-HTTP transport header that carries the negotiated MCP protocol version
21
- * on a subsequent request. The version is negotiated in the `initialize` JSON-RPC result
22
- * body; a stateful transport MAY additionally read this header to pin the per-request
23
- * protocol version (optional — the result body remains the source of truth).
20
+ * The Streamable-HTTP transport header carrying the negotiated MCP protocol version
21
+ * on every post-initialize client request.
22
+ *
23
+ * @remarks
24
+ * Required by MCP 2025-06-18 after initialization. Both HTTP client transports
25
+ * capture the initialize result's `protocolVersion` and send it on subsequent
26
+ * requests; `createMCPRoutes` rejects a present unsupported value before dispatch.
24
27
  */
25
28
  var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
26
29
  /** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */
@@ -307,18 +310,13 @@ function dispatchLines(emitter, lines) {
307
310
  function bridgeMessageTransport(transport) {
308
311
  let onMessage;
309
312
  let onClosed;
310
- let subscribed = false;
311
- function subscribe() {
312
- if (subscribed) return;
313
- subscribed = true;
314
- transport.emitter.on("message", (message) => {
315
- if (!isJSONRPCRequest(message)) return;
316
- onMessage?.(JSON.stringify(message));
317
- });
318
- transport.emitter.on("close", () => {
319
- onClosed?.();
320
- });
321
- }
313
+ transport.emitter.on("message", (message) => {
314
+ if (!isJSONRPCRequest(message)) return;
315
+ onMessage?.(JSON.stringify(message));
316
+ });
317
+ transport.emitter.on("close", () => {
318
+ onClosed?.();
319
+ });
322
320
  return {
323
321
  async send(message) {
324
322
  const decoded = decodeEvent(message);
@@ -326,11 +324,9 @@ function bridgeMessageTransport(transport) {
326
324
  await transport.send(decoded);
327
325
  },
328
326
  listen(handler) {
329
- subscribe();
330
327
  onMessage = handler;
331
328
  },
332
329
  closed(handler) {
333
- subscribe();
334
330
  onClosed = handler;
335
331
  },
336
332
  async close() {
@@ -339,6 +335,63 @@ function bridgeMessageTransport(transport) {
339
335
  };
340
336
  }
341
337
  //#endregion
338
+ //#region src/server/handlers.ts
339
+ /**
340
+ * Create the Streamable-HTTP POST handler used by `createMCPRoutes`.
341
+ *
342
+ * @remarks
343
+ * A present `mcp-protocol-version` header must name a supported revision; an
344
+ * unsupported value returns an HTTP `400` JSON-RPC invalid-request error without
345
+ * dispatching. An absent header is accepted for the initialize/bootstrap request.
346
+ *
347
+ * @param mcp - The transport-agnostic MCP server to dispatch through
348
+ * @param streaming - Whether an event-stream response may be negotiated
349
+ * @returns A request handler for the stateless MCP POST route
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * import { createMCPServer } from '@orkestrel/mcp'
354
+ * import { createMCPPostHandler } from '@orkestrel/mcp/server'
355
+ * import { createToolManager } from '@orkestrel/agent'
356
+ *
357
+ * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
358
+ * const handler = createMCPPostHandler(mcp, true)
359
+ * await handler(new Request('http://localhost/mcp', {
360
+ * method: 'POST',
361
+ * body: '{"jsonrpc":"2.0","method":"ping","id":1}',
362
+ * }))
363
+ * ```
364
+ */
365
+ function createMCPPostHandler(mcp, streaming) {
366
+ return async (request) => {
367
+ const protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER);
368
+ if (protocol !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocol)) return Response.json(jsonRPCError(null, JSONRPC_INVALID_REQUEST, `Unsupported MCP protocol version '${protocol}'`), { status: 400 });
369
+ let text;
370
+ try {
371
+ text = await request.text();
372
+ } catch {
373
+ return Response.json(jsonRPCError(null, JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
374
+ }
375
+ let parsed;
376
+ try {
377
+ parsed = JSON.parse(text);
378
+ } catch {
379
+ return Response.json(jsonRPCError(null, JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
380
+ }
381
+ const rpcRequest = parseJSONRPCMessage(parsed);
382
+ if (rpcRequest === void 0 || !("method" in rpcRequest)) return Response.json(jsonRPCError(null, JSONRPC_INVALID_REQUEST, "Invalid Request"), { status: 400 });
383
+ const response = await mcp.dispatch(rpcRequest);
384
+ if (response === void 0) return new Response(null, { status: 202 });
385
+ if (streaming && acceptsEventStream(request)) {
386
+ const stream = openStream();
387
+ stream.write({ data: JSON.stringify(response) });
388
+ stream.end();
389
+ return stream.response;
390
+ }
391
+ return Response.json(response);
392
+ };
393
+ }
394
+ //#endregion
342
395
  //#region src/server/transports/HTTPClientTransport.ts
343
396
  /**
344
397
  * The HTTP CLIENT transport for the Model Context Protocol — a
@@ -347,7 +400,7 @@ function bridgeMessageTransport(transport) {
347
400
  *
348
401
  * @remarks
349
402
  * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
350
- * message (or batch) to `options.url` with `content-type: application/json` and an
403
+ * message to `options.url` with `content-type: application/json` and an
351
404
  * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
352
405
  * answer with either framing) — plus any `options.headers` (e.g. an `Authorization`
353
406
  * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
@@ -359,13 +412,18 @@ function bridgeMessageTransport(transport) {
359
412
  * readEventStream}) — the inverse of the server's `openStream` seam, so the wire
360
413
  * round-trips. A `202`
361
414
  * Accepted (a notification) carries no body and emits nothing.
362
- * - **Session echo.** `start()` / `close()` are no-ops (a request/response transport
363
- * holds no long-lived connection). The `mcp-session-id` response header, when a
364
- * STATEFUL server sends one (on `initialize`), is captured into `session` and then
365
- * ECHOED as the `mcp-session-id` request header on every SUBSEQUENT request — so an
366
- * `MCPClient` passes a stateful server's session validation. Before initialize returns
367
- * an id, `session` is `undefined` and no header is sent (safe against a stateless
368
- * server, which neither sends nor expects one).
415
+ * - **Session and protocol echo.** `start()` is a no-op (a
416
+ * request/response transport opens no long-lived connection). The
417
+ * `mcp-session-id` response header, when a STATEFUL server sends one (on
418
+ * `initialize`), is captured into `session` and then ECHOED as the
419
+ * `mcp-session-id` request header on every SUBSEQUENT request so an
420
+ * `MCPClient` passes a stateful server's session validation. The
421
+ * initialize result's `protocolVersion` is likewise captured, but only
422
+ * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` on
423
+ * every subsequent request, as required by the 2025-06-18 Streamable-HTTP
424
+ * transport. Before initialize returns, neither captured header is sent.
425
+ * `close()` clears the captured protocol so a reconnect's `initialize`
426
+ * POST is headerless; the captured `session` persists across `close()`.
369
427
  * - **Total at the boundary (§14).** Every reply is narrowed (`parseJSONRPCMessage`,
370
428
  * the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
371
429
  * decode failure surfaces on the `error` event rather than escaping `send`.
@@ -386,6 +444,7 @@ var HTTPClientTransport = class {
386
444
  #fetch;
387
445
  #timeout;
388
446
  #session = void 0;
447
+ #protocol = void 0;
389
448
  constructor(options) {
390
449
  this.#emitter = new Emitter();
391
450
  this.#url = options.url;
@@ -409,6 +468,7 @@ var HTTPClientTransport = class {
409
468
  "content-type": "application/json",
410
469
  accept: "application/json, text/event-stream",
411
470
  ...this.#session === void 0 ? {} : { [MCP_SESSION_HEADER]: this.#session },
471
+ ...this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol },
412
472
  ...this.#headers
413
473
  },
414
474
  body: JSON.stringify(message),
@@ -423,6 +483,7 @@ var HTTPClientTransport = class {
423
483
  await this.#deliver(response);
424
484
  }
425
485
  async close() {
486
+ this.#protocol = void 0;
426
487
  this.#emitter.emit("close");
427
488
  }
428
489
  async #deliver(response) {
@@ -430,17 +491,21 @@ var HTTPClientTransport = class {
430
491
  const type = response.headers.get("content-type") ?? "";
431
492
  try {
432
493
  if (type.includes("text/event-stream")) {
433
- for (const message of await readEventStream(response)) this.#emitter.emit("message", message);
494
+ for (const message of await readEventStream(response)) this.#capture(message);
434
495
  return;
435
496
  }
436
497
  if (type.includes("application/json")) {
437
498
  const message = parseJSONRPCMessage(await response.json());
438
- if (message !== void 0) this.#emitter.emit("message", message);
499
+ if (message !== void 0) this.#capture(message);
439
500
  }
440
501
  } catch (error) {
441
502
  this.#emitter.emit("error", error);
442
503
  }
443
504
  }
505
+ #capture(message) {
506
+ if (isJSONRPCResponse(message) && isRecord(message.result) && isString(message.result["protocolVersion"]) && SUPPORTED_PROTOCOL_VERSIONS.includes(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
507
+ this.#emitter.emit("message", message);
508
+ }
444
509
  };
445
510
  //#endregion
446
511
  //#region src/server/MCPSession.ts
@@ -582,8 +647,8 @@ var MCPSession = class {
582
647
  * parsed envelope the {@link import('@src/core').MCPServerInterface} pump dispatches), while
583
648
  * a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown (§14). It
584
649
  * also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
585
- * - **Outbound (`send`).** `send(message | messages)` writes ONE text frame per message
586
- * (`nodeWs.send(JSON.stringify(...))`); the underlying wrapper no-ops a write on a
650
+ * - **Outbound (`send`).** `send(message)` writes one text frame
651
+ * (`nodeWs.send(JSON.stringify(message))`); the underlying wrapper no-ops a write on a
587
652
  * non-open socket, so a closed connection drops silently rather than throwing.
588
653
  * - **`close()`** closes the underlying socket (the RFC 6455 close handshake) and fires the
589
654
  * transport's `close` event (idempotent — a second `close`, or a socket-driven close, emits
@@ -613,8 +678,7 @@ var WebSocketServerTransport = class {
613
678
  this.#socket.emitter.on("error", (error) => this.#emitter.emit("error", error));
614
679
  }
615
680
  async send(message) {
616
- const messages = Array.isArray(message) ? message : [message];
617
- for (const one of messages) this.#socket.send(JSON.stringify(one));
681
+ this.#socket.send(JSON.stringify(message));
618
682
  }
619
683
  async close() {
620
684
  if (this.#closed) return;
@@ -665,7 +729,7 @@ var WebSocketServerTransport = class {
665
729
  * event (the reply the {@link import('@src/core').MCPClientInterface} correlates by `id`); a
666
730
  * non-JSON / non-message frame surfaces on `error` and is dropped (§14). The socket's `close`
667
731
  * / `error` bridge to this transport's events.
668
- * - **Outbound (`send`).** `send(message | messages)` writes ONE masked text frame per message.
732
+ * - **Outbound (`send`).** `send(message)` writes one masked text frame.
669
733
  * - **`close()`** closes the underlying socket and fires `close` (idempotent).
670
734
  * - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`
671
735
  * one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`
@@ -704,12 +768,6 @@ var WebSocketClientTransport = class {
704
768
  const secure = url.protocol === "https:";
705
769
  const send = secure ? request$1 : request;
706
770
  await new Promise((resolve, reject) => {
707
- let settled = false;
708
- const fail = (error) => {
709
- if (settled) return;
710
- settled = true;
711
- reject(error);
712
- };
713
771
  const request = send({
714
772
  hostname: url.hostname,
715
773
  port: url.port.length > 0 ? Number(url.port) : secure ? 443 : 80,
@@ -727,7 +785,7 @@ var WebSocketClientTransport = class {
727
785
  const accept = response.headers["sec-websocket-accept"];
728
786
  if (!isString(accept) || accept !== computeWebSocketAccept(key)) {
729
787
  socket.destroy();
730
- fail(/* @__PURE__ */ new Error("WebSocket handshake failed: Sec-WebSocket-Accept mismatch"));
788
+ reject(/* @__PURE__ */ new Error("WebSocket handshake failed: Sec-WebSocket-Accept mismatch"));
731
789
  return;
732
790
  }
733
791
  const ws = createNodeWebSocket({
@@ -736,24 +794,20 @@ var WebSocketClientTransport = class {
736
794
  });
737
795
  this.#socket = ws;
738
796
  this.#bind(ws);
739
- if (!settled) {
740
- settled = true;
741
- resolve();
742
- }
797
+ resolve();
743
798
  });
744
799
  request.on("response", (response) => {
745
800
  response.resume();
746
- fail(/* @__PURE__ */ new Error(`WebSocket upgrade declined with status ${response.statusCode ?? 0}`));
801
+ reject(/* @__PURE__ */ new Error(`WebSocket upgrade declined with status ${response.statusCode ?? 0}`));
747
802
  });
748
- request.on("error", (error) => fail(error instanceof Error ? error : new Error(String(error))));
803
+ request.on("error", (error) => reject(error instanceof Error ? error : new Error(String(error))));
749
804
  request.end();
750
805
  });
751
806
  }
752
807
  async send(message) {
753
808
  const socket = this.#socket;
754
809
  if (socket === void 0) throw new Error("WebSocket transport is not connected");
755
- const messages = Array.isArray(message) ? message : [message];
756
- for (const one of messages) socket.send(JSON.stringify(one));
810
+ socket.send(JSON.stringify(message));
757
811
  }
758
812
  async close() {
759
813
  if (this.#closed) return;
@@ -817,8 +871,8 @@ var WebSocketClientTransport = class {
817
871
  * {@link dispatchLines} helper — a well-formed {@link JSONRPCMessage} emits
818
872
  * `message`, a malformed line emits `error` (§14, never throws). The child's
819
873
  * `close` bridges to this transport's `close`.
820
- * - **Outbound (`send`).** `send(message | messages)` writes ONE newline-terminated
821
- * `JSON.stringify`d line per message to the child's `stdin`.
874
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated
875
+ * `JSON.stringify`d line to the child's `stdin`.
822
876
  * - **`close()`** kills the child process and fires `close` (idempotent).
823
877
  * - **Observable (§13).** Owns the `emitter` ({@link ClientTransportEventMap}); the
824
878
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
@@ -869,8 +923,7 @@ var StdioClientTransport = class {
869
923
  async send(message) {
870
924
  const child = this.#child;
871
925
  if (child === void 0) throw new Error("stdio transport is not connected");
872
- const messages = Array.isArray(message) ? message : [message];
873
- for (const one of messages) child.stdin.write(`${JSON.stringify(one)}\n`);
926
+ child.stdin.write(`${JSON.stringify(message)}\n`);
874
927
  }
875
928
  async close() {
876
929
  if (this.#closed) return;
@@ -913,8 +966,8 @@ var StdioClientTransport = class {
913
966
  * well-formed {@link JSONRPCMessage} re-emits on `message`, a malformed line
914
967
  * emits `error` (§14, never throws). `input`'s `close` bridges to this
915
968
  * transport's `close`.
916
- * - **Outbound (`send`).** `send(message | messages)` writes ONE newline-terminated
917
- * `JSON.stringify`d line per message to `output`.
969
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated
970
+ * `JSON.stringify`d line to `output`.
918
971
  * - **`close()`** fires this transport's `close` (idempotent) — the injected streams
919
972
  * are owned by the caller (typically `process.stdin`/`process.stdout`, which must
920
973
  * never be closed out from under the process) and are not torn down here.
@@ -946,8 +999,7 @@ var StdioServerTransport = class {
946
999
  this.#input.on("error", (error) => this.#emitter.emit("error", error));
947
1000
  }
948
1001
  async send(message) {
949
- const messages = Array.isArray(message) ? message : [message];
950
- for (const one of messages) this.#output.write(`${JSON.stringify(one)}\n`);
1002
+ this.#output.write(`${JSON.stringify(message)}\n`);
951
1003
  }
952
1004
  async close() {
953
1005
  if (this.#closed) return;
@@ -982,6 +1034,9 @@ var StdioServerTransport = class {
982
1034
  * - A **transport** failure — a malformed JSON body, or a parsed value that is not a
983
1035
  * JSON-RPC REQUEST — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse
984
1036
  * error / `-32600` Invalid Request, id `null`).
1037
+ * - A present `mcp-protocol-version` header is validated before dispatch: a supported
1038
+ * value proceeds, while an unsupported value returns HTTP `400` with a JSON-RPC
1039
+ * `-32600` body. An absent value proceeds for initialize/bootstrap compatibility.
985
1040
  * - A **dispatch** result — a success OR an IN-BAND JSON-RPC error from `mcp.dispatch`
986
1041
  * (e.g. `-32601` method-not-found) — is an HTTP `200` carrying the JSON-RPC response
987
1042
  * envelope (the error is in-band per JSON-RPC, NOT an HTTP error).
@@ -1018,37 +1073,11 @@ var StdioServerTransport = class {
1018
1073
  * ```
1019
1074
  */
1020
1075
  function createMCPRoutes(mcp, options) {
1021
- const path = options?.path ?? "/mcp";
1022
- const streaming = options?.streaming ?? true;
1023
1076
  return [{
1024
1077
  method: "POST",
1025
- path,
1078
+ path: options?.path ?? "/mcp",
1026
1079
  name: "mcp",
1027
- handler: async (request) => {
1028
- let text;
1029
- try {
1030
- text = await request.text();
1031
- } catch {
1032
- return Response.json(jsonRPCError(null, JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
1033
- }
1034
- let parsed;
1035
- try {
1036
- parsed = JSON.parse(text);
1037
- } catch {
1038
- return Response.json(jsonRPCError(null, JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
1039
- }
1040
- const rpcRequest = parseJSONRPCMessage(parsed);
1041
- if (rpcRequest === void 0 || !("method" in rpcRequest)) return Response.json(jsonRPCError(null, JSONRPC_INVALID_REQUEST, "Invalid Request"), { status: 400 });
1042
- const response = await mcp.dispatch(rpcRequest);
1043
- if (response === void 0) return new Response(null, { status: 202 });
1044
- if (streaming && acceptsEventStream(request)) {
1045
- const s = openStream();
1046
- s.write({ data: JSON.stringify(response) });
1047
- s.end();
1048
- return s.response;
1049
- }
1050
- return Response.json(response);
1051
- }
1080
+ handler: createMCPPostHandler(mcp, options?.streaming ?? true)
1052
1081
  }];
1053
1082
  }
1054
1083
  /**
@@ -1064,8 +1093,10 @@ function createMCPRoutes(mcp, options) {
1064
1093
  * and the reply is surfaced on the transport's `message` event for the client's id
1065
1094
  * correlation. Add `options.headers` (e.g. an `Authorization` bearer) to reach a guarded
1066
1095
  * server. `start` / `close` hold no connection; against a STATEFUL server it captures the
1067
- * `mcp-session-id` from `initialize` and echoes it on later requests, so the same
1068
- * `MCPClient` passes session validation (a stateless server sends none).
1096
+ * `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
1097
+ * the initialize result's `protocolVersion` and sends `mcp-protocol-version` on every
1098
+ * subsequent request, so the same `MCPClient` passes the session and 2025-06-18
1099
+ * protocol gates without caller wiring.
1069
1100
  *
1070
1101
  * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
1071
1102
  * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
@@ -1328,30 +1359,43 @@ function createMCPSession(options) {
1328
1359
  const store = /* @__PURE__ */ new Map();
1329
1360
  return async (request, context, next) => {
1330
1361
  if (context.url.pathname !== path) return next();
1331
- sweep();
1362
+ if (ttl !== void 0) {
1363
+ const cutoff = clock() - ttl;
1364
+ for (const [id, entry] of store) if (entry.touched <= cutoff) store.delete(id);
1365
+ }
1366
+ if (context.method === "DELETE") {
1367
+ const id = readSessionHeader(request);
1368
+ if (id === void 0 || !store.delete(id)) return rejectUnknownSession();
1369
+ return new Response(null, { status: 204 });
1370
+ }
1371
+ let entry;
1372
+ const id = readSessionHeader(request);
1373
+ if (id !== void 0) {
1374
+ const current = store.get(id);
1375
+ if (current !== void 0) {
1376
+ entry = {
1377
+ session: current.session,
1378
+ touched: clock()
1379
+ };
1380
+ store.set(id, entry);
1381
+ }
1382
+ }
1332
1383
  if (context.method === "GET") {
1333
- const entry = resolve(request);
1334
1384
  if (entry === void 0) return rejectUnknownSession();
1385
+ const session = entry.session;
1335
1386
  const stream = openStream();
1336
1387
  stream.comment("open");
1337
1388
  const lastEventId = readLastEventId(request);
1338
- if (lastEventId !== void 0) for (const e of entry.session.replay(lastEventId)) stream.write({
1389
+ if (lastEventId !== void 0) for (const e of session.replay(lastEventId)) stream.write({
1339
1390
  id: e.id,
1340
1391
  data: JSON.stringify(e.message)
1341
1392
  });
1342
- entry.session.attach(stream);
1343
- if (request.signal.aborted) entry.session.detach(stream);
1344
- else request.signal.addEventListener("abort", () => entry.session.detach(stream), { once: true });
1393
+ session.attach(stream);
1394
+ if (request.signal.aborted) session.detach(stream);
1395
+ else request.signal.addEventListener("abort", () => session.detach(stream), { once: true });
1345
1396
  return stream.response;
1346
1397
  }
1347
- if (context.method === "DELETE") {
1348
- const id = readSessionHeader(request);
1349
- if (id === void 0 || !store.has(id)) return rejectUnknownSession();
1350
- store.delete(id);
1351
- return new Response(null, { status: 204 });
1352
- }
1353
1398
  const text = await request.text();
1354
- let entry = resolve(request);
1355
1399
  if (entry === void 0) {
1356
1400
  let parsed;
1357
1401
  try {
@@ -1360,7 +1404,7 @@ function createMCPSession(options) {
1360
1404
  parsed = void 0;
1361
1405
  }
1362
1406
  if (parsed !== void 0 && isInitializeRequest(parsed)) {
1363
- const session = new MCPSession(crypto.randomUUID(), { capacity });
1407
+ const session = new MCPSession(crypto.randomUUID(), capacity !== void 0 ? { capacity } : {});
1364
1408
  entry = {
1365
1409
  session,
1366
1410
  touched: clock()
@@ -1368,7 +1412,7 @@ function createMCPSession(options) {
1368
1412
  store.set(session.id, entry);
1369
1413
  } else return rejectUnknownSession();
1370
1414
  }
1371
- context.state.session = entry.session;
1415
+ if (!Reflect.set(context.state, "session", entry.session)) throw new Error("MCP session state is not writable");
1372
1416
  const response = await next(new Request(context.url, {
1373
1417
  method: "POST",
1374
1418
  headers: request.headers,
@@ -1377,21 +1421,8 @@ function createMCPSession(options) {
1377
1421
  response.headers.set(MCP_SESSION_HEADER, entry.session.id);
1378
1422
  return response;
1379
1423
  };
1380
- function resolve(request) {
1381
- const id = readSessionHeader(request);
1382
- if (id === void 0) return void 0;
1383
- const entry = store.get(id);
1384
- if (entry === void 0) return void 0;
1385
- entry.touched = clock();
1386
- return entry;
1387
- }
1388
- function sweep() {
1389
- if (ttl === void 0) return;
1390
- const cutoff = clock() - ttl;
1391
- for (const [id, entry] of store) if (entry.touched <= cutoff) store.delete(id);
1392
- }
1393
1424
  }
1394
1425
  //#endregion
1395
- export { DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, MCPSession, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, bridgeMessageTransport, createHTTPClientTransport, createMCPRoutes, createMCPSession, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, upgradeRequestPath };
1426
+ export { DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, MCPSession, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, bridgeMessageTransport, createHTTPClientTransport, createMCPPostHandler, createMCPRoutes, createMCPSession, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, upgradeRequestPath };
1396
1427
 
1397
1428
  //# sourceMappingURL=index.js.map