@orkestrel/mcp 0.0.5 → 0.0.7

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