@orkestrel/mcp 0.0.8 → 0.0.9

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.
@@ -6,8 +6,12 @@ import { EmitterInterface } from '@orkestrel/emitter';
6
6
  import { IncomingMessage } from 'node:http';
7
7
  import { JSONRPCMessage } from '../core/index.ts';
8
8
  import { JSONRPCMessage as JSONRPCMessage_2 } from '../../core/index.ts';
9
+ import { JSONRPCRequest } from '../core/index.ts';
10
+ import { JSONRPCResponse } from '../core/index.ts';
11
+ import { MCPEra } from '../core/index.ts';
9
12
  import { MCPServerInterface } from '../core/index.ts';
10
13
  import { MCPTransportInterface } from '../core/index.ts';
14
+ import { MCPVersion } from '../core/index.ts';
11
15
  import { MiddlewareHandler } from '@orkestrel/server';
12
16
  import { NodeWebSocketInterface } from '@orkestrel/websocket';
13
17
  import { RouteInput } from '@orkestrel/router';
@@ -29,6 +33,22 @@ import { UpgradeHandler } from '@orkestrel/server';
29
33
  */
30
34
  export declare function acceptsEventStream(request: Request): boolean;
31
35
 
36
+ /**
37
+ * Whether an HTTP request satisfies the endpoint's origin gate.
38
+ *
39
+ * @remarks
40
+ * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
41
+ * whose host is the `localhost` or `[::1]` literal, or belongs to the `127.0.0.0/8` literal
42
+ * range, is allowed without configuration; every other present origin must occur exactly in
43
+ * the caller-supplied list. Invalid and opaque (`null`) origins are denied. `enabled: false`
44
+ * delegates validation to an upstream layer and allows the request through this gate.
45
+ *
46
+ * @param request - The fetch-standard request to validate
47
+ * @param options - Shared origin validation and delegation options
48
+ * @returns `true` when the request may reach MCP dispatch
49
+ */
50
+ export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
51
+
32
52
  /**
33
53
  * Bridge a message-channel {@link ClientTransportInterface} (the shape the stdio and
34
54
  * WebSocket SERVER transports already implement) into the environment-agnostic
@@ -86,9 +106,9 @@ export declare function bridgeMessageTransport(transport: ClientTransportInterfa
86
106
  * correlation. Add `options.headers` (e.g. an `Authorization` bearer) to reach a guarded
87
107
  * server. `start` / `close` hold no connection; against a STATEFUL server it captures the
88
108
  * `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
89
- * the initialize result's `protocolVersion` and sends `mcp-protocol-version` on every
90
- * subsequent request, so the same `MCPClient` passes the session and 2025-06-18
91
- * protocol gates without caller wiring.
109
+ * the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
110
+ * subsequent legacy request. Modern requests derive protocol and method headers directly
111
+ * from the message, plus a name header only for `tools/call`.
92
112
  *
93
113
  * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
94
114
  * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
@@ -113,12 +133,17 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
113
133
  * Create the Streamable-HTTP POST handler used by `createMCPRoutes`.
114
134
  *
115
135
  * @remarks
116
- * A present `mcp-protocol-version` header must name a supported revision; an
117
- * unsupported value returns an HTTP `400` JSON-RPC invalid-request error without
118
- * dispatching. An absent header is accepted for the initialize/bootstrap request.
136
+ * Modern requests require matching protocol/method headers and a matching name header only
137
+ * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
138
+ * accepted, while every other headerless request needs a live legacy session to supply its
139
+ * pinned version. A present origin must occur in `origin.origins` unless validation is
140
+ * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
141
+ * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
142
+ * signal with response-body cancellation and supplies the result to every dispatched modern
143
+ * handler through `MCPDispatchOptions.signal`.
119
144
  *
120
145
  * @param mcp - The transport-agnostic MCP server to dispatch through
121
- * @param streaming - Whether an event-stream response may be negotiated
146
+ * @param options - Optional streaming, origin-validation, and SSE keepalive options
122
147
  * @returns A request handler for the stateless MCP POST route
123
148
  *
124
149
  * @example
@@ -127,15 +152,19 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
127
152
  * import { createMCPPostHandler } from '@orkestrel/mcp/server'
128
153
  * import { createToolManager } from '@orkestrel/tool'
129
154
  *
130
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
131
- * const handler = createMCPPostHandler(mcp, true)
155
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
156
+ * const handler = createMCPPostHandler(mcp, { streaming: true })
132
157
  * await handler(new Request('http://localhost/mcp', {
133
158
  * method: 'POST',
134
159
  * body: '{"jsonrpc":"2.0","method":"ping","id":1}',
135
160
  * }))
136
161
  * ```
137
162
  */
138
- export declare function createMCPPostHandler(mcp: MCPServerInterface, streaming: boolean): (request: Request) => Promise<Response>;
163
+ export declare function createMCPPostHandler(mcp: MCPServerInterface, options?: {
164
+ readonly streaming?: boolean;
165
+ readonly origin?: MCPOriginOptions;
166
+ readonly keepalive?: MCPKeepaliveOptions;
167
+ }): (request: Request) => Promise<Response>;
139
168
 
140
169
  /**
141
170
  * Create the MCP Streamable-HTTP transport routes — mounts a transport-agnostic
@@ -152,12 +181,11 @@ export declare function createMCPPostHandler(mcp: MCPServerInterface, streaming:
152
181
  * - A **transport** failure — a malformed JSON body, or a parsed value that is not a
153
182
  * JSON-RPC REQUEST — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse
154
183
  * error / `-32600` Invalid Request, id `null`).
155
- * - A present `mcp-protocol-version` header is validated before dispatch: a supported
156
- * value proceeds, while an unsupported value returns HTTP `400` with a JSON-RPC
157
- * `-32600` body. An absent value proceeds for initialize/bootstrap compatibility.
158
- * - A **dispatch** result a success OR an IN-BAND JSON-RPC error from `mcp.dispatch`
159
- * (e.g. `-32601` method-not-found) is an HTTP `200` carrying the JSON-RPC response
160
- * envelope (the error is in-band per JSON-RPC, NOT an HTTP error).
184
+ * - Modern protocol/method/name headers are validated against the body; a mismatch is
185
+ * HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies
186
+ * its pinned revision, and every other headerless request is rejected.
187
+ * - Legacy dispatch errors stay IN-BAND at HTTP `200`; modern errors map to `400` for
188
+ * `-32020` / `-32021` / `-32022` / `-32602`, `404` for `-32601`, and `200` otherwise.
161
189
  * - A **notification** (a request with no `id`, which `dispatch` resolves to
162
190
  * `undefined`) is a `202 Accepted` with no body.
163
191
  *
@@ -172,13 +200,15 @@ export declare function createMCPPostHandler(mcp: MCPServerInterface, streaming:
172
200
  * validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,
173
201
  * leaving this route to dispatch the validated `POST`.
174
202
  *
175
- * This is MECHANISM, not policy: compose auth / CORS / rate-limiting (and the session
176
- * middleware) IN FRONT as ordinary middleware the transport route adds none.
203
+ * This is MECHANISM, not policy: compose auth / rate-limiting (and the session middleware)
204
+ * IN FRONT as ordinary middleware; the optional `origin` group carries the deployment's shared
205
+ * allowlist or explicitly delegates validation to an upstream layer.
177
206
  *
178
207
  * @typeParam TState - The consumer's opaque per-request state type
179
208
  * @param mcp - The transport-agnostic {@link MCPServerInterface} to expose over HTTP
180
209
  * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}) and `streaming`
181
- * (default `true`); see {@link HTTPTransportOptions}
210
+ * (default `true`), plus the shared `origin` validation options; see
211
+ * {@link HTTPTransportOptions}
182
212
  * @returns The {@link RouteInput}s to register with the router
183
213
  *
184
214
  * @example
@@ -186,7 +216,7 @@ export declare function createMCPPostHandler(mcp: MCPServerInterface, streaming:
186
216
  * import { createMCPServer, createToolManager } from '@src/core'
187
217
  * import { createMCPRoutes } from '@src/server'
188
218
  *
189
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
219
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
190
220
  * const routes = createMCPRoutes(mcp) // POST /mcp dispatches JSON-RPC (JSON or SSE per Accept)
191
221
  * ```
192
222
  */
@@ -204,12 +234,17 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterfac
204
234
  * `path` (default {@link DEFAULT_MCP_PATH}); a request to any other path passes straight
205
235
  * through (`next()`).
206
236
  *
237
+ * A modern-shaped POST also passes straight through via `next()`, ignoring any session id.
238
+ * The remaining behavior is the legacy session layer:
239
+ *
207
240
  * - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route
208
241
  * can re-read it via a freshly-built forwarded `Request`). Resolves a session via {@link
209
242
  * readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an
210
243
  * ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link
211
244
  * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)
212
- * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). It then
245
+ * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The
246
+ * minted entry pins the negotiated legacy revision, which is supplied to a later headerless
247
+ * live-session request. It then
213
248
  * FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
214
249
  * already-consumed original — so the route re-reads the same body, and stamps the response
215
250
  * with {@link MCP_SESSION_HEADER}.
@@ -217,8 +252,8 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterfac
217
252
  * an invalid / unknown id is the same `404`. A valid session opens the resumable
218
253
  * server→client stream via `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:
219
254
  * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE
220
- * attaching the stream for live pushes, then attaches; a client disconnect (`request.signal`)
221
- * detaches it. Long-lived — never `end()`ed here.
255
+ * attaching the stream for live pushes, then attaches; cancellation of the streamed response
256
+ * body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
222
257
  * - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers
223
258
  * `204`; an invalid / unknown id is the same `404`.
224
259
  *
@@ -232,7 +267,8 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterfac
232
267
  * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session
233
268
  * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`
234
269
  * (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;
235
- * defaults to `Date.now`); see {@link MCPSessionOptions}
270
+ * defaults to `Date.now`), plus the shared `origin` validation options; see
271
+ * {@link MCPSessionOptions}
236
272
  * @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable
237
273
  * `GET` / `DELETE`
238
274
  *
@@ -241,13 +277,22 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterfac
241
277
  * import { createMCPServer, createToolManager } from '@src/core'
242
278
  * import { createMCPRoutes, createMCPSession } from '@src/server'
243
279
  *
244
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
280
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
245
281
  * router.use(createMCPSession({ ttl: 60_000 })) // stateful: mint + validate + resumable GET / DELETE
246
282
  * router.add(createMCPRoutes(mcp)) // the route stays session-agnostic
247
283
  * ```
248
284
  */
249
285
  export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionOptions): MiddlewareHandler<TState>;
250
286
 
287
+ /**
288
+ * Create a readable stream from its pull and cancellation behaviours.
289
+ *
290
+ * @param pull - The behaviour that supplies the stream's next chunk
291
+ * @param cancel - The behaviour that releases the stream after consumer cancellation
292
+ * @returns A readable stream backed by the supplied behaviours
293
+ */
294
+ export declare function createReadableStream<T>(pull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>, cancel: (reason?: unknown) => void | PromiseLike<void>): ReadableStream<T>;
295
+
251
296
  /**
252
297
  * Create the stdio CLIENT transport for an {@link import('@src/core').MCPClientInterface}
253
298
  * — a {@link ClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
@@ -307,7 +352,7 @@ export declare function createStdioClientTransport(options: StdioClientTransport
307
352
  * import { createMCPServer, createToolManager } from '@src/core'
308
353
  * import { createStdioServer } from '@src/server'
309
354
  *
310
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
355
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
311
356
  * createStdioServer(mcp).start() // an MCP client now connects over this process's stdio
312
357
  * ```
313
358
  */
@@ -390,7 +435,7 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
390
435
  * import { createMCPServer, createToolManager } from '@src/core'
391
436
  * import { createWebSocketServer } from '@src/server'
392
437
  *
393
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
438
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
394
439
  * server.upgrade(createWebSocketServer(mcp)) // an MCP client now connects over ws://…/mcp
395
440
  * ```
396
441
  */
@@ -410,6 +455,16 @@ export declare function createWebSocketServer(mcp: MCPServerInterface, options?:
410
455
  */
411
456
  export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
412
457
 
458
+ /**
459
+ * The default interval in milliseconds between SSE keepalive comments on held-open MCP
460
+ * responses.
461
+ *
462
+ * @remarks
463
+ * Fifteen seconds is infrequent enough to avoid chatty idle connections while bounding dead
464
+ * client detection and staying comfortably inside common intermediary idle windows.
465
+ */
466
+ export declare const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15000;
467
+
413
468
  /** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */
414
469
  export declare const DEFAULT_MCP_PATH = "/mcp";
415
470
 
@@ -513,16 +568,17 @@ export declare function extractLines(buffer: string, chunk: string): LineExtract
513
568
  * readEventStream}) — the inverse of the server's `openStream` seam, so the wire
514
569
  * round-trips. A `202`
515
570
  * Accepted (a notification) carries no body and emits nothing.
516
- * - **Session and protocol echo.** `start()` is a no-op (a
571
+ * - **Session and protocol headers.** `start()` is a no-op (a
517
572
  * request/response transport opens no long-lived connection). The
518
573
  * `mcp-session-id` response header, when a STATEFUL server sends one (on
519
574
  * `initialize`), is captured into `session` and then ECHOED as the
520
575
  * `mcp-session-id` request header on every SUBSEQUENT request — so an
521
576
  * `MCPClient` passes a stateful server's session validation. The
522
577
  * initialize result's `protocolVersion` is likewise captured, but only
523
- * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` on
524
- * every subsequent request, as required by the 2025-06-18 Streamable-HTTP
525
- * transport. Before initialize returns, neither captured header is sent.
578
+ * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
579
+ * subsequent legacy requests. Modern requests instead derive protocol and method
580
+ * headers from the message, plus the name header only for `tools/call`.
581
+ * Before initialize returns, neither captured legacy header is sent.
526
582
  * `close()` clears the captured protocol so a reconnect's `initialize`
527
583
  * POST is headerless; the captured `session` persists across `close()`.
528
584
  * - **Total at the boundary (§14).** Every reply is narrowed (`parseJSONRPCMessage`,
@@ -591,12 +647,51 @@ export declare interface HTTPClientTransportOptions {
591
647
  * `text/event-stream`; when `false` it always answers with a plain JSON body. Either
592
648
  * mode carries the SAME JSON-RPC response envelope — the choice is purely the wire
593
649
  * framing the Streamable-HTTP spec lets the client negotiate.
650
+ * - `origin` — the shared origin-validation options passed to both the route and session
651
+ * enforcement sites. Validation is enabled by default: requests without `Origin` pass,
652
+ * canonical loopback-literal origins pass, and every other present `Origin` must occur
653
+ * exactly in `origin.origins`. Set `origin.enabled` to `false` only when validation is
654
+ * delegated upstream; `origins` is ignored in that mode. Both enforcement sites must receive
655
+ * the same value.
656
+ * - `keepalive` — the SSE liveness options for held-open responses. `interval` defaults to
657
+ * {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}. Keepalives never apply
658
+ * to unary responses.
594
659
  */
595
660
  export declare interface HTTPTransportOptions {
596
661
  readonly path?: string;
597
662
  readonly streaming?: boolean;
663
+ /** Must match the session layer's value; `origins` is ignored when `enabled` is `false`. */
664
+ readonly origin?: MCPOriginOptions;
665
+ readonly keepalive?: MCPKeepaliveOptions;
598
666
  }
599
667
 
668
+ /**
669
+ * Infer the legacy revision an `initialize` request negotiates.
670
+ *
671
+ * @remarks
672
+ * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
673
+ * request selects the newest supported legacy revision, matching the core initialize result.
674
+ *
675
+ * @param request - The legacy initialize request
676
+ * @returns The negotiated legacy protocol revision
677
+ */
678
+ export declare function inferLegacyVersion(request: JSONRPCRequest): MCPVersion;
679
+
680
+ /**
681
+ * Infer the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
682
+ *
683
+ * @remarks
684
+ * Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`
685
+ * status semantics, including in-band errors. Modern header/capability/version/parameter
686
+ * failures map to `400`, method-not-found maps to `404`, and every other modern result maps
687
+ * to `200`.
688
+ *
689
+ * @param response - The dispatch response, or `undefined` for a notification
690
+ * @param era - The structurally selected request era
691
+ * @returns The HTTP response status
692
+ */
693
+ export declare function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number;
694
+
600
695
  /**
601
696
  * The result of folding one more chunk of raw stdio bytes into a newline-framed
602
697
  * buffer — every COMPLETE line extracted (newline-terminated in the wire bytes) plus
@@ -611,6 +706,27 @@ export declare interface LineExtraction {
611
706
  readonly remainder: string;
612
707
  }
613
708
 
709
+ /**
710
+ * Whether a modern HTTP request's required standard headers match its JSON-RPC body.
711
+ *
712
+ * @remarks
713
+ * Requires `MCP-Protocol-Version` to equal the reserved `_meta` version and `Mcp-Method`
714
+ * to equal `method`. `Mcp-Name` is required only for `tools/call`, where it must equal
715
+ * `params.name`; discovery and listing requests need no name because none is derivable.
716
+ * Legacy requests return `false` because this predicate models the modern contract only.
717
+ *
718
+ * @param request - The HTTP request carrying the headers
719
+ * @param message - The parsed JSON-RPC request body
720
+ * @returns `true` only when every method-applicable modern header matches
721
+ */
722
+ export declare function matchesModernHeaders(request: Request, message: JSONRPCRequest): boolean;
723
+
724
+ /** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
725
+ export declare const MCP_METHOD_HEADER = "mcp-method";
726
+
727
+ /** The modern Streamable-HTTP request header carrying a named method's target. */
728
+ export declare const MCP_NAME_HEADER = "mcp-name";
729
+
614
730
  /**
615
731
  * The Streamable-HTTP transport header carrying the negotiated MCP protocol version
616
732
  * on every post-initialize client request.
@@ -643,6 +759,35 @@ export declare const MCP_SESSION_HEADER = "mcp-session-id";
643
759
  */
644
760
  export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
645
761
 
762
+ /**
763
+ * Shared SSE keepalive options for held-open HTTP responses.
764
+ *
765
+ * @remarks
766
+ * - `interval` — milliseconds between SSE comment frames. Defaults to {@link
767
+ * import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}. Each comment keeps an idle
768
+ * connection live through intermediaries and bounds how long a dead client can remain
769
+ * unobserved by the HTTP writer.
770
+ */
771
+ export declare interface MCPKeepaliveOptions {
772
+ readonly interval?: number;
773
+ }
774
+
775
+ /**
776
+ * Shared options for the protocol-required HTTP `Origin` validation at the route and session
777
+ * enforcement sites.
778
+ *
779
+ * @remarks
780
+ * - `enabled` — whether this package validates `Origin`; defaults to `true`. Set `false` only
781
+ * when an upstream layer performs the validation for the deployment.
782
+ * - `origins` — the exact serialized non-loopback origins accepted when an `Origin` header is
783
+ * present. Omission still accepts requests without `Origin` and canonical loopback-literal
784
+ * origins (`localhost`, `[::1]`, and `127.0.0.0/8`).
785
+ */
786
+ export declare interface MCPOriginOptions {
787
+ readonly enabled?: boolean;
788
+ readonly origins?: readonly string[];
789
+ }
790
+
646
791
  /**
647
792
  * One MCP transport session — the per-session entity a {@link
648
793
  * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the
@@ -718,10 +863,14 @@ export declare class MCPSession implements MCPSessionInterface {
718
863
  * - `session` — the live {@link MCPSession} entity the store keys by session id.
719
864
  * - `touched` — the epoch-ms instant of the last access; the entry is replaced on every
720
865
  * resolved request so the middleware's lazy sweep can evict an idle entry past `ttl`.
866
+ * - `version` — the legacy revision negotiated by the session's initialize request, used
867
+ * when a later live-session request legitimately omits its protocol header.
721
868
  */
722
869
  export declare interface MCPSessionEntry {
723
870
  readonly session: MCPSession;
724
871
  readonly touched: number;
872
+ /** The legacy revision negotiated when this session was minted. */
873
+ readonly version: MCPVersion;
725
874
  }
726
875
 
727
876
  /**
@@ -775,12 +924,22 @@ export declare interface MCPSessionInterface {
775
924
  * uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
776
925
  * deterministic clock a TTL test advances explicitly instead of racing a real idle window
777
926
  * against wall-clock (AGENTS §16). Production never sets it.
927
+ * - `origin` — the same shared origin-validation options supplied to `createMCPRoutes`.
928
+ * Validation is enabled by default, accepts canonical loopback-literal origins, and rejects
929
+ * every other present origin outside its exact list before any session is minted. Set
930
+ * `origin.enabled` to `false` only when validation is delegated upstream; `origins` is ignored
931
+ * in that mode. Both enforcement sites must receive the same value.
932
+ * - `keepalive` — the SSE liveness options for the held-open resumable response. `interval`
933
+ * defaults to {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}.
778
934
  */
779
935
  export declare interface MCPSessionOptions {
780
936
  readonly path?: string;
781
937
  readonly ttl?: number;
782
938
  readonly capacity?: number;
783
939
  readonly clock?: () => number;
940
+ /** Must match the route layer's value; `origins` is ignored when `enabled` is `false`. */
941
+ readonly origin?: MCPOriginOptions;
942
+ readonly keepalive?: MCPKeepaliveOptions;
784
943
  }
785
944
 
786
945
  /**
@@ -857,7 +1016,7 @@ export declare function readSessionHeader(request: Request): string | undefined;
857
1016
  * JSON-RPC error body.
858
1017
  *
859
1018
  * @remarks
860
- * Returns `Response.json(jsonRPCError(null, JSONRPC_INVALID_REQUEST, 'Session not found'),
1019
+ * Returns `Response.json(buildJSONRPCError(null, JSONRPC_INVALID_REQUEST, 'Session not found'),
861
1020
  * { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a
862
1021
  * JSON-RPC error BODY with a `null` id) but at the session-not-found status. Shared by
863
1022
  * every {@link import('./middlewares.js').createMCPSession} validation site — the
@@ -869,6 +1028,15 @@ export declare function readSessionHeader(request: Request): string | undefined;
869
1028
  */
870
1029
  export declare function rejectUnknownSession(): Response;
871
1030
 
1031
+ /** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1032
+ export declare const SSE_BUFFERING_DISABLED = "no";
1033
+
1034
+ /** The reverse-proxy response header controlling buffering of an SSE response. */
1035
+ export declare const SSE_BUFFERING_HEADER = "x-accel-buffering";
1036
+
1037
+ /** The comment text written by the held-open MCP response keepalive. */
1038
+ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1039
+
872
1040
  /**
873
1041
  * The stdio CLIENT transport for the Model Context Protocol — a
874
1042
  * {@link ClientTransportInterface} that drives a CHILD PROCESS MCP server over