@orkestrel/mcp 0.0.8 → 0.0.10

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,10 +6,15 @@ 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';
17
+ import { RouteContext } from '@orkestrel/router';
13
18
  import { RouteInput } from '@orkestrel/router';
14
19
  import { StreamInterface } from '@orkestrel/server';
15
20
  import { UpgradeHandler } from '@orkestrel/server';
@@ -29,6 +34,22 @@ import { UpgradeHandler } from '@orkestrel/server';
29
34
  */
30
35
  export declare function acceptsEventStream(request: Request): boolean;
31
36
 
37
+ /**
38
+ * Whether an HTTP request satisfies the endpoint's origin gate.
39
+ *
40
+ * @remarks
41
+ * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
42
+ * whose host is the `localhost` or `[::1]` literal, or belongs to the `127.0.0.0/8` literal
43
+ * range, is allowed without configuration; every other present origin must occur exactly in
44
+ * the caller-supplied list. Invalid and opaque (`null`) origins are denied. `enabled: false`
45
+ * delegates validation to an upstream layer and allows the request through this gate.
46
+ *
47
+ * @param request - The fetch-standard request to validate
48
+ * @param options - Shared origin validation and delegation options
49
+ * @returns `true` when the request may reach MCP dispatch
50
+ */
51
+ export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
52
+
32
53
  /**
33
54
  * Bridge a message-channel {@link ClientTransportInterface} (the shape the stdio and
34
55
  * WebSocket SERVER transports already implement) into the environment-agnostic
@@ -86,9 +107,9 @@ export declare function bridgeMessageTransport(transport: ClientTransportInterfa
86
107
  * correlation. Add `options.headers` (e.g. an `Authorization` bearer) to reach a guarded
87
108
  * server. `start` / `close` hold no connection; against a STATEFUL server it captures the
88
109
  * `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.
110
+ * the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
111
+ * subsequent legacy request. Modern requests derive protocol and method headers directly
112
+ * from the message, plus a name header only for `tools/call`.
92
113
  *
93
114
  * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
94
115
  * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
@@ -113,12 +134,20 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
113
134
  * Create the Streamable-HTTP POST handler used by `createMCPRoutes`.
114
135
  *
115
136
  * @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.
119
- *
137
+ * Modern requests require matching protocol/method headers and a matching name header only
138
+ * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
139
+ * accepted, while every other headerless request needs a live legacy session to supply its
140
+ * pinned version. A present origin must occur in `origin.origins` unless validation is
141
+ * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
142
+ * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
143
+ * signal with response-body cancellation and supplies the result to every dispatched modern
144
+ * handler through `MCPDispatchOptions.signal`. After every transport validation and immediately
145
+ * before dispatch, the optional synchronous `caller` extractor reads front-middleware state; a
146
+ * defined value is added to `MCPDispatchOptions`, while `undefined` is omitted.
147
+ *
148
+ * @typeParam TState - The consumer's opaque per-request route state type
120
149
  * @param mcp - The transport-agnostic MCP server to dispatch through
121
- * @param streaming - Whether an event-stream response may be negotiated
150
+ * @param options - Optional streaming, origin-validation, SSE keepalive, and caller-extraction options
122
151
  * @returns A request handler for the stateless MCP POST route
123
152
  *
124
153
  * @example
@@ -127,15 +156,15 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
127
156
  * import { createMCPPostHandler } from '@orkestrel/mcp/server'
128
157
  * import { createToolManager } from '@orkestrel/tool'
129
158
  *
130
- * const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
131
- * const handler = createMCPPostHandler(mcp, true)
159
+ * const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
160
+ * const handler = createMCPPostHandler(mcp, { streaming: true })
132
161
  * await handler(new Request('http://localhost/mcp', {
133
162
  * method: 'POST',
134
163
  * body: '{"jsonrpc":"2.0","method":"ping","id":1}',
135
164
  * }))
136
165
  * ```
137
166
  */
138
- export declare function createMCPPostHandler(mcp: MCPServerInterface, streaming: boolean): (request: Request) => Promise<Response>;
167
+ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPServerInterface, options?: HTTPHandlerOptions<TState>): (request: Request, context?: RouteContext<string, TState>) => 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 shared origin, keepalive, and synchronous caller-extraction options; see
211
+ * {@link HTTPTransportOptions}
182
212
  * @returns The {@link RouteInput}s to register with the router
183
213
  *
184
214
  * @example
@@ -186,11 +216,11 @@ 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
  */
193
- export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterface, options?: HTTPTransportOptions): readonly RouteInput<string, TState>[];
223
+ export declare function createMCPRoutes<TState = unknown>(mcp: MCPServerInterface, options?: HTTPTransportOptions<TState>): readonly RouteInput<string, TState>[];
194
224
 
195
225
  /**
196
226
  * Create the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
@@ -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`,
@@ -575,28 +631,79 @@ export declare interface HTTPClientTransportOptions {
575
631
  }
576
632
 
577
633
  /**
578
- * Options for `createMCPRoutes` the path the transport is mounted at and whether an SSE
579
- * response is allowed. `createMCPRoutes` is STATELESS; sessions are a separate middleware
580
- * ({@link import('./middlewares.js').createMCPSession}), composed via `server.use`.
634
+ * Options shared by the MCP Streamable-HTTP POST handler and route factory.
581
635
  *
582
636
  * @remarks
583
- * - `path` — the request path the single `POST` route answers; defaults to
584
- * {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`). `GET` / `DELETE` to this
585
- * path get the spine's automatic `405` unless a {@link
586
- * import('./middlewares.js').createMCPSession} middleware (which owns the same `path`) is
587
- * mounted IN FRONT to serve them.
588
637
  * - `streaming` — when `true` (the DEFAULT) the transport MAY answer with a
589
638
  * Server-Sent-Events response (one `data:` event carrying the JSON-RPC reply, then
590
639
  * the stream ends) whenever the client's `Accept` header includes
591
640
  * `text/event-stream`; when `false` it always answers with a plain JSON body. Either
592
641
  * mode carries the SAME JSON-RPC response envelope — the choice is purely the wire
593
642
  * framing the Streamable-HTTP spec lets the client negotiate.
643
+ * - `origin` — the shared origin-validation options passed to both the route and session
644
+ * enforcement sites. Validation is enabled by default: requests without `Origin` pass,
645
+ * canonical loopback-literal origins pass, and every other present `Origin` must occur
646
+ * exactly in `origin.origins`. Set `origin.enabled` to `false` only when validation is
647
+ * delegated upstream; `origins` is ignored in that mode. Both enforcement sites must receive
648
+ * the same value.
649
+ * - `keepalive` — the SSE liveness options for held-open responses. `interval` defaults to
650
+ * {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}. Keepalives never apply
651
+ * to unary responses.
652
+ * - `caller` — the synchronous extractor for consumer-asserted caller context already resolved
653
+ * by front middleware. It runs only for a validated request that will dispatch. Returning
654
+ * `undefined` omits caller context; a throw propagates.
594
655
  */
595
- export declare interface HTTPTransportOptions {
596
- readonly path?: string;
656
+ export declare interface HTTPHandlerOptions<TState = unknown> {
597
657
  readonly streaming?: boolean;
658
+ /** Must match the session layer's value; `origins` is ignored when `enabled` is `false`. */
659
+ readonly origin?: MCPOriginOptions;
660
+ readonly keepalive?: MCPKeepaliveOptions;
661
+ readonly caller?: MCPCallerHandler<TState>;
662
+ }
663
+
664
+ /**
665
+ * Options for `createMCPRoutes` — the mount path plus the shared POST-handler options.
666
+ * `createMCPRoutes` is STATELESS; sessions are a separate middleware ({@link
667
+ * import('./middlewares.js').createMCPSession}), composed via `server.use`.
668
+ *
669
+ * @remarks
670
+ * `path` is the request path the single `POST` route answers; it defaults to {@link
671
+ * import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`). `GET` / `DELETE` to this path get
672
+ * the spine's automatic `405` unless a {@link import('./middlewares.js').createMCPSession}
673
+ * middleware owning the same path is mounted in front. The remaining options are inherited
674
+ * from {@link HTTPHandlerOptions}.
675
+ */
676
+ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHandlerOptions<TState> {
677
+ readonly path?: string;
598
678
  }
599
679
 
680
+ /**
681
+ * Infer the legacy revision an `initialize` request negotiates.
682
+ *
683
+ * @remarks
684
+ * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
685
+ * request selects the newest supported legacy revision, matching the core initialize result.
686
+ *
687
+ * @param request - The legacy initialize request
688
+ * @returns The negotiated legacy protocol revision
689
+ */
690
+ export declare function inferLegacyVersion(request: JSONRPCRequest): MCPVersion;
691
+
692
+ /**
693
+ * Infer the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
694
+ *
695
+ * @remarks
696
+ * Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`
697
+ * status semantics, including in-band errors. Modern header/capability/version/parameter
698
+ * failures map to `400`, method-not-found maps to `404`, and every other modern result maps
699
+ * to `200`.
700
+ *
701
+ * @param response - The dispatch response, or `undefined` for a notification
702
+ * @param era - The structurally selected request era
703
+ * @returns The HTTP response status
704
+ */
705
+ export declare function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number;
706
+
600
707
  /**
601
708
  * The result of folding one more chunk of raw stdio bytes into a newline-framed
602
709
  * buffer — every COMPLETE line extracted (newline-terminated in the wire bytes) plus
@@ -611,6 +718,27 @@ export declare interface LineExtraction {
611
718
  readonly remainder: string;
612
719
  }
613
720
 
721
+ /**
722
+ * Whether a modern HTTP request's required standard headers match its JSON-RPC body.
723
+ *
724
+ * @remarks
725
+ * Requires `MCP-Protocol-Version` to equal the reserved `_meta` version and `Mcp-Method`
726
+ * to equal `method`. `Mcp-Name` is required only for `tools/call`, where it must equal
727
+ * `params.name`; discovery and listing requests need no name because none is derivable.
728
+ * Legacy requests return `false` because this predicate models the modern contract only.
729
+ *
730
+ * @param request - The HTTP request carrying the headers
731
+ * @param message - The parsed JSON-RPC request body
732
+ * @returns `true` only when every method-applicable modern header matches
733
+ */
734
+ export declare function matchesModernHeaders(request: Request, message: JSONRPCRequest): boolean;
735
+
736
+ /** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
737
+ export declare const MCP_METHOD_HEADER = "mcp-method";
738
+
739
+ /** The modern Streamable-HTTP request header carrying a named method's target. */
740
+ export declare const MCP_NAME_HEADER = "mcp-name";
741
+
614
742
  /**
615
743
  * The Streamable-HTTP transport header carrying the negotiated MCP protocol version
616
744
  * on every post-initialize client request.
@@ -643,6 +771,52 @@ export declare const MCP_SESSION_HEADER = "mcp-session-id";
643
771
  */
644
772
  export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
645
773
 
774
+ /**
775
+ * Synchronously extract consumer-asserted caller context from an HTTP request after the
776
+ * transport has validated it for dispatch.
777
+ *
778
+ * @remarks
779
+ * Authentication belongs to middleware composed in front of the MCP route. This handler only
780
+ * reads what that middleware already resolved. Returning `undefined` supplies no caller; a
781
+ * throw propagates as a route-handler throw. The result remains `unknown` because this package
782
+ * cannot verify caller identity.
783
+ *
784
+ * @typeParam TState - The consumer's opaque per-request route state type
785
+ * @param request - The validated Fetch request that will dispatch
786
+ * @param context - The router context, or `undefined` for direct handler invocation
787
+ * @returns Consumer-asserted caller context, or `undefined` for no caller
788
+ */
789
+ export declare type MCPCallerHandler<TState = unknown> = (request: Request, context: RouteContext<string, TState> | undefined) => unknown;
790
+
791
+ /**
792
+ * Shared SSE keepalive options for held-open HTTP responses.
793
+ *
794
+ * @remarks
795
+ * - `interval` — milliseconds between SSE comment frames. Defaults to {@link
796
+ * import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}. Each comment keeps an idle
797
+ * connection live through intermediaries and bounds how long a dead client can remain
798
+ * unobserved by the HTTP writer.
799
+ */
800
+ export declare interface MCPKeepaliveOptions {
801
+ readonly interval?: number;
802
+ }
803
+
804
+ /**
805
+ * Shared options for the protocol-required HTTP `Origin` validation at the route and session
806
+ * enforcement sites.
807
+ *
808
+ * @remarks
809
+ * - `enabled` — whether this package validates `Origin`; defaults to `true`. Set `false` only
810
+ * when an upstream layer performs the validation for the deployment.
811
+ * - `origins` — the exact serialized non-loopback origins accepted when an `Origin` header is
812
+ * present. Omission still accepts requests without `Origin` and canonical loopback-literal
813
+ * origins (`localhost`, `[::1]`, and `127.0.0.0/8`).
814
+ */
815
+ export declare interface MCPOriginOptions {
816
+ readonly enabled?: boolean;
817
+ readonly origins?: readonly string[];
818
+ }
819
+
646
820
  /**
647
821
  * One MCP transport session — the per-session entity a {@link
648
822
  * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the
@@ -718,10 +892,14 @@ export declare class MCPSession implements MCPSessionInterface {
718
892
  * - `session` — the live {@link MCPSession} entity the store keys by session id.
719
893
  * - `touched` — the epoch-ms instant of the last access; the entry is replaced on every
720
894
  * resolved request so the middleware's lazy sweep can evict an idle entry past `ttl`.
895
+ * - `version` — the legacy revision negotiated by the session's initialize request, used
896
+ * when a later live-session request legitimately omits its protocol header.
721
897
  */
722
898
  export declare interface MCPSessionEntry {
723
899
  readonly session: MCPSession;
724
900
  readonly touched: number;
901
+ /** The legacy revision negotiated when this session was minted. */
902
+ readonly version: MCPVersion;
725
903
  }
726
904
 
727
905
  /**
@@ -775,12 +953,22 @@ export declare interface MCPSessionInterface {
775
953
  * uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
776
954
  * deterministic clock a TTL test advances explicitly instead of racing a real idle window
777
955
  * against wall-clock (AGENTS §16). Production never sets it.
956
+ * - `origin` — the same shared origin-validation options supplied to `createMCPRoutes`.
957
+ * Validation is enabled by default, accepts canonical loopback-literal origins, and rejects
958
+ * every other present origin outside its exact list before any session is minted. Set
959
+ * `origin.enabled` to `false` only when validation is delegated upstream; `origins` is ignored
960
+ * in that mode. Both enforcement sites must receive the same value.
961
+ * - `keepalive` — the SSE liveness options for the held-open resumable response. `interval`
962
+ * defaults to {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}.
778
963
  */
779
964
  export declare interface MCPSessionOptions {
780
965
  readonly path?: string;
781
966
  readonly ttl?: number;
782
967
  readonly capacity?: number;
783
968
  readonly clock?: () => number;
969
+ /** Must match the route layer's value; `origins` is ignored when `enabled` is `false`. */
970
+ readonly origin?: MCPOriginOptions;
971
+ readonly keepalive?: MCPKeepaliveOptions;
784
972
  }
785
973
 
786
974
  /**
@@ -857,7 +1045,7 @@ export declare function readSessionHeader(request: Request): string | undefined;
857
1045
  * JSON-RPC error body.
858
1046
  *
859
1047
  * @remarks
860
- * Returns `Response.json(jsonRPCError(null, JSONRPC_INVALID_REQUEST, 'Session not found'),
1048
+ * Returns `Response.json(buildJSONRPCError(null, JSONRPC_INVALID_REQUEST, 'Session not found'),
861
1049
  * { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a
862
1050
  * JSON-RPC error BODY with a `null` id) but at the session-not-found status. Shared by
863
1051
  * every {@link import('./middlewares.js').createMCPSession} validation site — the
@@ -869,6 +1057,15 @@ export declare function readSessionHeader(request: Request): string | undefined;
869
1057
  */
870
1058
  export declare function rejectUnknownSession(): Response;
871
1059
 
1060
+ /** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1061
+ export declare const SSE_BUFFERING_DISABLED = "no";
1062
+
1063
+ /** The reverse-proxy response header controlling buffering of an SSE response. */
1064
+ export declare const SSE_BUFFERING_HEADER = "x-accel-buffering";
1065
+
1066
+ /** The comment text written by the held-open MCP response keepalive. */
1067
+ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1068
+
872
1069
  /**
873
1070
  * The stdio CLIENT transport for the Model Context Protocol — a
874
1071
  * {@link ClientTransportInterface} that drives a CHILD PROCESS MCP server over