@orkestrel/mcp 0.0.26 → 0.0.28

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,17 +1,19 @@
1
1
  import { EmitterInterface } from '@orkestrel/emitter';
2
+ import { HTTPClientTransportOptions } from '@orkestrel/mcp';
2
3
  import { IncomingMessage } from 'node:http';
3
4
  import { JSONRPCInvocation } from '@orkestrel/mcp';
4
5
  import { JSONRPCMessage } from '@orkestrel/mcp';
5
6
  import { JSONRPCMessage as JSONRPCMessage_2 } from '@orkestrel/mcp';
6
7
  import { JSONRPCResponse } from '@orkestrel/mcp';
7
- import { MCPClientTransportEventMap } from '@orkestrel/mcp';
8
- import { MCPClientTransportEventMap as MCPClientTransportEventMap_2 } from '@orkestrel/mcp';
9
- import { MCPClientTransportInterface } from '@orkestrel/mcp';
10
- import { MCPClientTransportInterface as MCPClientTransportInterface_2 } from '@orkestrel/mcp';
11
8
  import { MCPContinuationInterface } from '@orkestrel/mcp';
12
9
  import { MCPDispatcherInterface } from '@orkestrel/mcp';
13
10
  import { MCPEra } from '@orkestrel/mcp';
11
+ import { MCPHeaderParameter } from '@orkestrel/mcp';
14
12
  import { MCPLegacyVersion } from '@orkestrel/mcp';
13
+ import { MCPMessageTransportEventMap } from '@orkestrel/mcp';
14
+ import { MCPMessageTransportEventMap as MCPMessageTransportEventMap_2 } from '@orkestrel/mcp';
15
+ import { MCPMessageTransportInterface } from '@orkestrel/mcp';
16
+ import { MCPMessageTransportInterface as MCPMessageTransportInterface_2 } from '@orkestrel/mcp';
15
17
  import { MCPStreamControllerInterface } from '@orkestrel/mcp';
16
18
  import { MCPTransportInterface } from '@orkestrel/mcp';
17
19
  import { MCPVersion } from '@orkestrel/mcp';
@@ -25,7 +27,7 @@ import { TokenSecret } from '@orkestrel/server';
25
27
  import { UpgradeHandler } from '@orkestrel/server';
26
28
 
27
29
  /**
28
- * Whether the request's `Accept` header opts into a Server-Sent-Events response.
30
+ * Checks whether the request's `Accept` header opts into a Server-Sent-Events response.
29
31
  *
30
32
  * @remarks
31
33
  * Reads the fetch-standard `Request.headers.get('accept')` and returns `true` when it
@@ -35,12 +37,12 @@ import { UpgradeHandler } from '@orkestrel/server';
35
37
  * — an absent / unmatched header returns `false`.
36
38
  *
37
39
  * @param request - The fetch-standard `Request`
38
- * @returns `true` when the client `Accept`s `text/event-stream`, else `false`
40
+ * @returns True if the client `Accept`s `text/event-stream`; false otherwise
39
41
  */
40
42
  export declare function acceptsEventStream(request: Request): boolean;
41
43
 
42
44
  /**
43
- * Whether an HTTP request satisfies the endpoint's origin gate.
45
+ * Checks whether an HTTP request satisfies the endpoint's origin gate.
44
46
  *
45
47
  * @remarks
46
48
  * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
@@ -51,18 +53,20 @@ export declare function acceptsEventStream(request: Request): boolean;
51
53
  *
52
54
  * @param request - The fetch-standard request to validate
53
55
  * @param options - Shared origin validation and delegation options
54
- * @returns `true` when the request may reach MCP dispatch
56
+ * @returns True if the request may reach MCP dispatch; false otherwise
55
57
  */
56
58
  export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
57
59
 
58
60
  /**
59
- * Bridges a message-channel {@link MCPClientTransportInterface} (the shape the stdio and
60
- * WebSocket SERVER transports already implement) into the environment-agnostic
61
- * {@link import('@orkestrel/mcp').MCPTransportInterface} port — the adapter
62
- * {@link import('./factories.js').createStdioServer} and {@link
63
- * import('./factories.js').createWebSocketServer} pipe through `bindServer`, so the
64
- * request/reply/error pump those factories used to hand-roll identically now
65
- * lives ONCE in the core binder.
61
+ * Creates the server-side mirror of
62
+ * {@link import('@orkestrel/mcp').createDuplexClientTransport}: the adapter that bridges a
63
+ * message-channel {@link MCPMessageTransportInterface}
64
+ * (the shape the stdio and WebSocket SERVER transports already implement) onto the
65
+ * environment-agnostic {@link import('@orkestrel/mcp').MCPTransportInterface} port what
66
+ * {@link createStdioServer} and {@link createWebSocketServer} pipe through `bindServer`, so
67
+ * the request/reply/error pump those factories used to hand-roll identically now lives ONCE
68
+ * in the core binder. {@link import('@orkestrel/mcp').createDuplexClientTransport} adapts the
69
+ * same two contracts the other way.
66
70
  *
67
71
  * @remarks
68
72
  * `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}
@@ -104,31 +108,22 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
104
108
  * import { bindServer } from '@orkestrel/mcp'
105
109
  *
106
110
  * const transport = new StdioServerTransport(process.stdin, process.stdout)
107
- * bindServer(mcp, bridgeMessageTransport(transport))
111
+ * bindServer(mcp, createDuplexServerTransport(transport))
108
112
  * ```
109
113
  */
110
- export declare function bridgeMessageTransport(transport: MCPClientTransportInterface): MCPTransportInterface;
111
-
112
- /**
113
- * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
114
- *
115
- * @param response - The response whose status is reported
116
- * @param type - The response's content type, or an empty string when absent
117
- * @returns An error naming the HTTP status and unsupported response shape
118
- *
119
- * @example
120
- * ```ts
121
- * const error = buildResponseError(new Response('', { status: 500 }), '')
122
- * ```
123
- */
124
- export declare function buildResponseError(response: Response, type: string): Error;
114
+ export declare function createDuplexServerTransport(transport: MCPMessageTransportInterface): MCPTransportInterface;
125
115
 
126
116
  /**
127
117
  * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
128
- * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
118
+ * — a {@link MCPMessageTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
129
119
  * over `fetch`. The egress mirror of {@link createMCPRoutes}.
130
120
  *
131
121
  * @remarks
122
+ * It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
123
+ * browser face's `createHTTPClientTransport` returns, because the class touches `fetch`,
124
+ * `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone.
125
+ *
126
+ * @remarks
132
127
  * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is
133
128
  * `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of
134
129
  * both `application/json` and `text/event-stream` (the server answers with EITHER — a
@@ -144,7 +139,7 @@ export declare function buildResponseError(response: Response, type: string): Er
144
139
  * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
145
140
  * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
146
141
  * (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}
147
- * @returns A working {@link MCPClientTransportInterface} over `fetch`
142
+ * @returns A working {@link MCPMessageTransportInterface} over `fetch`
148
143
  *
149
144
  * @example
150
145
  * ```ts
@@ -158,7 +153,7 @@ export declare function buildResponseError(response: Response, type: string): Er
158
153
  * const tools = await client.tools()
159
154
  * ```
160
155
  */
161
- export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface;
156
+ export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface;
162
157
 
163
158
  /**
164
159
  * Adapts the installed server token primitives to the host-neutral MCP continuation port.
@@ -172,12 +167,17 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
172
167
  * Creates the Streamable-HTTP POST handler used by `createMCPRoutes`.
173
168
  *
174
169
  * @remarks
175
- * Modern requests require matching protocol/method headers and a matching name header only
176
- * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
177
- * accepted, while every other headerless request needs a live legacy session to supply its
178
- * pinned version. A legacy-shaped request carrying a protocol header is admitted only for a
179
- * legacy revision; any other value, the modern revision included, returns HTTP `400` + `-32022`
180
- * whose `supported` names the legacy revisions this door accepts. A present origin must occur in `origin.origins` unless validation is
170
+ * Modern requests require matching protocol/method headers and a matching name header on each
171
+ * method carrying a named target — `tools/call` and `prompts/get` against `params.name`,
172
+ * `resources/read` against `params.uri` with a Base64-sentinel value decoded before the
173
+ * comparison; a missing, mismatched, or invalidly encoded value returns HTTP `400` + `-32020`.
174
+ * A protocol header naming a MODERN revision holds the request to that revision whatever shape
175
+ * its body arrived in, so a body with no parsable modern `_meta` returns HTTP `400` + `-32602`.
176
+ * Headerless `initialize` is accepted, while every other headerless request needs a live legacy
177
+ * session to supply its pinned version. A legacy-shaped request carrying a protocol header is
178
+ * otherwise admitted only for a legacy revision; a revision this server does not implement
179
+ * returns HTTP `400` + `-32022` whose `supported` names the legacy revisions this door accepts.
180
+ * A present origin must occur in `origin.origins` unless validation is
181
181
  * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
182
182
  * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
183
183
  * signal with response-body cancellation and supplies the result to every dispatched modern
@@ -232,7 +232,7 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
232
232
  * When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,
233
233
  * the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying
234
234
  * the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic
235
- * {@link import('@orkestrel/server').openStream} seam; otherwise it is a plain JSON body.
235
+ * {@link import('@orkestrel/server').createStream} seam; otherwise it is a plain JSON body.
236
236
  *
237
237
  * **Sessions are a SEPARATE, plug-and-play middleware.** `createMCPRoutes` mints / reads no
238
238
  * session id. To make the transport STATEFUL, mount {@link
@@ -282,8 +282,9 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
282
282
  * can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link
283
283
  * readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an
284
284
  * ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link
285
- * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)
286
- * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The
285
+ * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, the `session`
286
+ * options group) and sets `context.state.session`; neither → {@link rejectUnknownSession}
287
+ * (`404`). The
287
288
  * minted entry pins the negotiated legacy revision, which is supplied to a later headerless
288
289
  * live-session request. It then
289
290
  * FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
@@ -294,7 +295,7 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
294
295
  * a `DELETE` arriving while the request was suspended is not undone.
295
296
  * - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);
296
297
  * an invalid / unknown id is the same `404`. A valid session opens the resumable
297
- * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:
298
+ * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').createStream}:
298
299
  * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE
299
300
  * attaching the stream for live pushes, then attaches; cancellation of the streamed response
300
301
  * body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
@@ -309,10 +310,11 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
309
310
  * @typeParam TState - The consumer's `TState`, which MUST extend {@link MCPSessionState} so
310
311
  * the resolved session can be threaded through `context.state.session`
311
312
  * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session
312
- * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`
313
- * (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;
314
- * defaults to `Date.now`), plus the shared `origin` validation options; see
315
- * {@link MCPSessionOptions}
313
+ * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `session`
314
+ * (the knobs each minted {@link MCPSession} takes — `capacity`, the log's own `ttl`, and its
315
+ * `clock`), and `clock` (the deterministic epoch-ms clock this middleware keeps its own
316
+ * bookkeeping on and hands down to a session that names none; defaults to `Date.now`), plus
317
+ * the shared `origin` validation options; see {@link MCPSessionMiddlewareOptions}
316
318
  * @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable
317
319
  * `GET` / `DELETE`
318
320
  *
@@ -328,16 +330,7 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
328
330
  * router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only
329
331
  * ```
330
332
  */
331
- export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionOptions): MiddlewareHandler<TState>;
332
-
333
- /**
334
- * Creates a readable stream from its pull and cancellation behaviours.
335
- *
336
- * @param pull - The behaviour that supplies the stream's next chunk
337
- * @param cancel - The behaviour that releases the stream after consumer cancellation
338
- * @returns A readable stream backed by the supplied behaviours
339
- */
340
- export declare function createReadableStream<T>(pull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>, cancel: (reason?: unknown) => void | PromiseLike<void>): ReadableStream<T>;
333
+ export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionMiddlewareOptions): MiddlewareHandler<TState>;
341
334
 
342
335
  /**
343
336
  * Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
@@ -387,7 +380,7 @@ export declare function createStdioClientTransport(options: StdioClientTransport
387
380
  * Wraps `options.input` (default `process.stdin`) / `options.output` (default
388
381
  * `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
389
382
  * and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port
390
- * through {@link import('./helpers.js').bridgeMessageTransport} + {@link
383
+ * through {@link createDuplexServerTransport} + {@link
391
384
  * import('@orkestrel/mcp').bindServer}: each inbound REQUEST runs through `mcp.dispatch`, and
392
385
  * a defined response is written back as a newline-terminated line — a NOTIFICATION
393
386
  * writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault
@@ -397,7 +390,8 @@ export declare function createStdioClientTransport(options: StdioClientTransport
397
390
  * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over stdio
398
391
  * @param options - Optional injectable `input` / `output` streams; see
399
392
  * {@link StdioServerOptions}
400
- * @returns A `{ start(): void; stop(): void }` handle to arm / tear down the pump
393
+ * @returns A {@link StdioServerInterface} handle to arm / tear down the pump; `stop()` ends
394
+ * that handle's lifetime permanently
401
395
  *
402
396
  * @example
403
397
  * ```ts
@@ -410,14 +404,11 @@ export declare function createStdioClientTransport(options: StdioClientTransport
410
404
  * createStdioServer(createMCPLegacy(mcp)).start() // answers `initialize` too; pass `mcp` alone for modern-only
411
405
  * ```
412
406
  */
413
- export declare function createStdioServer(mcp: MCPDispatcherInterface, options?: StdioServerOptions): {
414
- start(): void;
415
- stop(): void;
416
- };
407
+ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?: StdioServerOptions): StdioServerInterface;
417
408
 
418
409
  /**
419
410
  * Creates the WebSocket CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
420
- * — a {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket. The
411
+ * — a {@link MCPMessageTransportInterface} that drives a REMOTE MCP server over a WebSocket. The
421
412
  * egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link
422
413
  * createHTTPClientTransport}.
423
414
  *
@@ -433,7 +424,7 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
433
424
  *
434
425
  * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional `headers`
435
426
  * merged onto the upgrade request; see {@link WebSocketClientTransportOptions}
436
- * @returns A working {@link MCPClientTransportInterface} over a WebSocket
427
+ * @returns A working {@link MCPMessageTransportInterface} over a WebSocket
437
428
  *
438
429
  * @example
439
430
  * ```ts
@@ -447,7 +438,7 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
447
438
  * const tools = await client.tools()
448
439
  * ```
449
440
  */
450
- export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface;
441
+ export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface;
451
442
 
452
443
  /**
453
444
  * Creates the MCP WebSocket transport INGRESS — an {@link UpgradeHandler} that exposes a
@@ -469,7 +460,7 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
469
460
  * only when the client's offer contains it, and sends UNMASKED frames), wraps it in a
470
461
  * {@link WebSocketServerTransport}, and pipes it through the core {@link
471
462
  * import('@orkestrel/mcp').MCPTransportInterface} port through {@link
472
- * import('./helpers.js').bridgeMessageTransport} + {@link import('@orkestrel/mcp').bindServer}:
463
+ * createDuplexServerTransport} + {@link import('@orkestrel/mcp').bindServer}:
473
464
  * each inbound REQUEST runs through `mcp.dispatch`, and a defined response is written back
474
465
  * as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
475
466
  * ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
@@ -506,21 +497,7 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
506
497
  export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
507
498
 
508
499
  /**
509
- * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
510
- * when it is not one — the per-event step {@link readEventStream} folds over.
511
- *
512
- * @remarks
513
- * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's
514
- * `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.
515
- * Total: malformed JSON or a non-message value yields `undefined`, never throws.
516
- *
517
- * @param data - One SSE event's `data` payload
518
- * @returns The decoded {@link JSONRPCMessage}, or `undefined`
519
- */
520
- export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
521
-
522
- /**
523
- * The default bound in milliseconds on one unconfirmed write to a stdio client transport's
500
+ * Sets the default bound in milliseconds on one unconfirmed write to a stdio client transport's
524
501
  * child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
525
502
  *
526
503
  * @remarks
@@ -533,7 +510,7 @@ export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
533
510
  export declare const DEFAULT_MCP_DELIVERY = 10000;
534
511
 
535
512
  /**
536
- * The default interval in milliseconds between SSE keepalive comments on held-open MCP
513
+ * Sets the default interval in milliseconds between SSE keepalive comments on held-open MCP
537
514
  * responses.
538
515
  *
539
516
  * @remarks
@@ -542,24 +519,24 @@ export declare const DEFAULT_MCP_DELIVERY = 10000;
542
519
  */
543
520
  export declare const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15000;
544
521
 
545
- /** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */
522
+ /** Names the default request path `createMCPRoutes` mounts the transport's `POST` route at. */
546
523
  export declare const DEFAULT_MCP_PATH = "/mcp";
547
524
 
548
525
  /**
549
- * The default capacity of a session's FOLDED resumable event log (the per-{@link
526
+ * Sets the default capacity of a session's FOLDED resumable event log (the per-{@link
550
527
  * import('./MCPSession.js').MCPSession} replay log) — the maximum number of pushed
551
528
  * server→client messages retained for replay before the OLDEST is evicted.
552
529
  *
553
530
  * @remarks
554
531
  * Bounds the replay log's memory: only the most-recent {@link DEFAULT_MCP_SESSION_CAPACITY}
555
532
  * pushes are retained, so a client reconnecting with a `Last-Event-ID` older than that window
556
- * replays nothing (its cursor fell off the back). Override per `createMCPSession`'s `capacity`
557
- * for a deeper / shallower window.
533
+ * replays nothing (its cursor fell off the back). Override through the `session` group of
534
+ * `createMCPSession`'s options (`session.capacity`) for a deeper / shallower window.
558
535
  */
559
536
  export declare const DEFAULT_MCP_SESSION_CAPACITY = 1024;
560
537
 
561
538
  /**
562
- * The default per-event idle lifetime (ms) of a session's folded resumable event log — an
539
+ * Sets the default per-event idle lifetime (ms) of a session's folded resumable event log — an
563
540
  * entry older than this is lazily evicted on the next access (no background timer), bounding
564
541
  * how far back a reconnecting client may replay.
565
542
  *
@@ -572,41 +549,22 @@ export declare const DEFAULT_MCP_SESSION_TTL = 300000;
572
549
 
573
550
  /**
574
551
  * Decodes and delivers each complete newline-framed line onto a {@link
575
- * MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
552
+ * MCPMessageTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
576
553
  * transports run their framed lines through: the server transport frames with {@link
577
554
  * extractLines}, the client transport takes its lines from the process supervisor.
578
555
  *
579
556
  * @remarks
580
- * A blank line is skipped (a stray trailing newline). Every other line is decoded
581
- * with {@link decodeEvent} (`JSON.parse` + `parseJSONRPCMessage`, guarded); a
582
- * well-formed {@link JSONRPCMessage} emits `message`, a malformed / non-message line
583
- * emits `error` (total, never throws). Pure w.r.t. its own state the emit is
584
- * the caller-owned side effect.
557
+ * A blank line is skipped (a stray trailing newline). Every other line runs through the
558
+ * shared {@link import('@orkestrel/mcp').deliverMessage} fold, the one inbound decode every
559
+ * transport in this package shares: a well-formed {@link JSONRPCMessage} emits `message`,
560
+ * unparsable text emits the caught parse error, and a well-formed non-message line emits
561
+ * `error` naming a non-JSON-RPC stdio line (total, never throws). Pure w.r.t. its own state
562
+ * — the emit is the caller-owned side effect.
585
563
  *
586
564
  * @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto
587
565
  * @param lines - The complete lines to decode and deliver
588
566
  */
589
- export declare function dispatchLines(emitter: EmitterInterface<MCPClientTransportEventMap>, lines: readonly string[]): void;
590
-
591
- /**
592
- * One entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed {@link
593
- * JSONRPCMessage} tagged with the monotone event `id` the session assigned and the `timestamp`
594
- * it was appended at (for the lazy-TTL replay window).
595
- *
596
- * @remarks
597
- * - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
598
- * value a resumable client echoes back as its `Last-Event-ID` to replay from here.
599
- * - `message` — the server→client {@link JSONRPCMessage} that was pushed.
600
- * - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
601
- *
602
- * A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
603
- * returns.
604
- */
605
- export declare interface EventStoreEntry {
606
- readonly id: string;
607
- readonly message: JSONRPCMessage;
608
- readonly timestamp: number;
609
- }
567
+ export declare function dispatchLines(emitter: EmitterInterface<MCPMessageTransportEventMap>, lines: readonly string[]): void;
610
568
 
611
569
  /**
612
570
  * Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared
@@ -627,97 +585,6 @@ export declare interface EventStoreEntry {
627
585
  */
628
586
  export declare function extractLines(buffer: string, chunk: string): LineExtraction;
629
587
 
630
- /**
631
- * The HTTP CLIENT transport for the Model Context Protocol — a
632
- * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server over
633
- * `fetch`, the egress mirror of the server's `createMCPRoutes`.
634
- *
635
- * @remarks
636
- * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
637
- * message to `options.url` with `content-type: application/json` and an
638
- * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
639
- * answer with either framing) — plus any `options.headers` (for example, an `Authorization`
640
- * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
641
- * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
642
- * to.
643
- * - **Both reply framings.** A `200` with an `application/json` body is parsed with
644
- * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
645
- * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
646
- * readEventStream}) — the inverse of the server's `openStream` seam, so the wire
647
- * round-trips. A `202`
648
- * Accepted (a notification) carries no body and emits nothing.
649
- * - **Session and protocol headers.** `start()` is a no-op (a
650
- * request/response transport opens no long-lived connection). The
651
- * `mcp-session-id` response header, when a STATEFUL server sends one (on
652
- * `initialize`), is captured into `session` and then ECHOED as the
653
- * `mcp-session-id` request header on every SUBSEQUENT request — so an
654
- * `MCPClient` passes a stateful server's session validation. The
655
- * initialize result's `protocolVersion` is likewise captured, but only
656
- * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
657
- * subsequent legacy requests. Modern requests instead derive protocol and method
658
- * headers from the message, plus the name header only for `tools/call`.
659
- * Before initialize returns, neither captured legacy header is sent.
660
- * `close()` clears the captured protocol so a reconnect's `initialize`
661
- * POST is headerless; the captured `session` persists across `close()`.
662
- * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
663
- * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
664
- * never ends would otherwise outlive the transport, with nothing left able to reach it. The
665
- * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
666
- * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
667
- * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
668
- * the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
669
- * reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
670
- * shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
671
- * on a success response surfaces on the `error` event rather than escaping `send`.
672
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
673
- * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
674
- *
675
- * @example
676
- * ```ts
677
- * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
678
- * const client = new MCPClient({ transport })
679
- * await client.connect()
680
- * ```
681
- */
682
- export declare class HTTPClientTransport implements MCPClientTransportInterface_2 {
683
- #private;
684
- constructor(options: HTTPClientTransportOptions);
685
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
686
- get session(): string | undefined;
687
- get duplex(): boolean;
688
- start(): Promise<void>;
689
- send(message: JSONRPCMessage_2): Promise<void>;
690
- close(): Promise<void>;
691
- }
692
-
693
- /**
694
- * Options for `createHTTPClientTransport` — the remote MCP server's URL and any extra
695
- * request headers.
696
- *
697
- * @remarks
698
- * - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
699
- * `POST` target every JSON-RPC message is written to, for example,
700
- * `http://localhost:3000/mcp`). REQUIRED.
701
- * - `headers` — extra request headers merged onto every `POST` (for example, an
702
- * `Authorization` bearer for a guarded server). The transport always sets
703
- * `content-type: application/json` and an `Accept` of both `application/json` and
704
- * `text/event-stream` (so the server may answer with either framing); a key supplied
705
- * here is merged on top.
706
- * - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
707
- * `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
708
- * - `timeout` — an optional per-request timeout in milliseconds; when set, each
709
- * `fetch` call composes that deadline with the transport's own close through
710
- * `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
711
- * ends the request. Omit for no transport-level deadline; the close signal is passed
712
- * either way.
713
- */
714
- export declare interface HTTPClientTransportOptions {
715
- readonly url: string;
716
- readonly headers?: Readonly<Record<string, string>>;
717
- readonly fetch?: typeof fetch;
718
- readonly timeout?: number;
719
- }
720
-
721
588
  /**
722
589
  * Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.
723
590
  *
@@ -746,10 +613,10 @@ export declare interface HTTPClientTransportOptions {
746
613
  * @example
747
614
  * ```ts
748
615
  * import { HTTPDisconnect } from '@orkestrel/mcp/server'
749
- * import { openStream } from '@orkestrel/server'
616
+ * import { createStream } from '@orkestrel/server'
750
617
  *
751
618
  * const disconnect = new HTTPDisconnect(request.signal, { interval: 15_000 })
752
- * const stream = openStream()
619
+ * const stream = createStream()
753
620
  * const response = disconnect.bridge(stream)
754
621
  * ```
755
622
  */
@@ -764,8 +631,8 @@ export declare class HTTPDisconnect {
764
631
  */
765
632
  constructor(signal: AbortSignal, options?: MCPKeepaliveOptions);
766
633
  /**
767
- * The signal aborted by the incoming request, or by any end of this response that is not
768
- * its graceful completion.
634
+ * Returns the signal aborted by the incoming request, or by any end of this response that
635
+ * is not its graceful completion.
769
636
  *
770
637
  * @returns The composed lifecycle signal
771
638
  */
@@ -811,7 +678,7 @@ export declare class HTTPDisconnect {
811
678
  */
812
679
  export declare interface HTTPHandlerOptions<TState = unknown> {
813
680
  readonly streaming?: boolean;
814
- /** Must match the session layer's value; `origins` is ignored when `enabled` is `false`. */
681
+ /** Requires the session layer's value; `origins` is ignored when `enabled` is `false`. */
815
682
  readonly origin?: MCPOriginOptions;
816
683
  readonly keepalive?: MCPKeepaliveOptions;
817
684
  readonly caller?: MCPCallerHandler<TState>;
@@ -834,16 +701,23 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
834
701
  }
835
702
 
836
703
  /**
837
- * Infers the first required MCP HTTP header that is missing or mismatched.
704
+ * Infers the first required MCP HTTP header a request's own body contradicts.
838
705
  *
839
706
  * @remarks
840
- * A modern request derives its protocol, method, and tools/call-only name expectations from
841
- * the JSON-RPC body. A legacy request body requires a protocol header after initialization,
842
- * while a supplied legacy session version additionally diagnoses a header that disagrees with
843
- * the active session. Messages name the expected value but never echo the client-supplied one.
707
+ * A modern request derives its protocol, method, and name expectations from the JSON-RPC body,
708
+ * the name expectation scoped to the methods {@link inferHeaderTarget} reads a target for. A
709
+ * name header carrying the Base64 sentinel is decoded through
710
+ * {@link import('@orkestrel/mcp').decodeSentinel} before the comparison, so a peer that had
711
+ * to encode its value still matches; a sentinel whose payload is invalid decodes to nothing
712
+ * and therefore mismatches, which is how an invalid header value is refused. A legacy request
713
+ * body requires a protocol header after initialization. Messages name the expected value but
714
+ * never echo the client-supplied one.
715
+ *
716
+ * The expectation a LIVE SESSION supplies is a different rule over a different input, so it
717
+ * is {@link inferSessionHeaderIssue} rather than a second arm of this one.
844
718
  *
845
719
  * @param request - The HTTP request carrying the headers
846
- * @param reference - The parsed invocation body, or the active legacy session version
720
+ * @param invocation - The parsed invocation body the expectations are derived from
847
721
  * @returns The first header issue, or `undefined` when the applicable headers agree
848
722
  *
849
723
  * @example
@@ -852,7 +726,32 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
852
726
  * issue?.header // 'Mcp-Method' when that field is absent or mismatched
853
727
  * ```
854
728
  */
855
- export declare function inferHeaderIssue(request: Request, reference: JSONRPCInvocation | MCPVersion): MCPHeaderIssue | undefined;
729
+ export declare function inferHeaderIssue(request: Request, invocation: JSONRPCInvocation): MCPHeaderIssue | undefined;
730
+
731
+ /**
732
+ * Infers the target one modern request's `Mcp-Name` header must carry.
733
+ *
734
+ * @remarks
735
+ * The protocol scopes the header to the methods whose body carries a name-shaped field, and
736
+ * names the field per method: `tools/call` and `prompts/get` carry `params.name`, and
737
+ * `resources/read` carries `params.uri`. Every other method — `server/discover`, `tools/list`,
738
+ * `resources/list`, `prompts/list` — has nothing to derive a target from, so the header is not
739
+ * required there and a peer that sent one anyway is not held to it.
740
+ *
741
+ * A method within the scope whose named member is absent or is not a string reads as no
742
+ * target. There is nothing for a header to match, and refusing the request over a body member
743
+ * the header rule does not own would report a parameter fault as a header fault. Total.
744
+ *
745
+ * @param request - The parsed modern invocation to read the target from
746
+ * @returns The target the header must carry, or `undefined` when the method carries none
747
+ *
748
+ * @example
749
+ * ```ts
750
+ * inferHeaderTarget({ jsonrpc: '2.0', id: 1, method: 'resources/read', params: { uri: 'file:///a' } })
751
+ * // → 'file:///a'
752
+ * ```
753
+ */
754
+ export declare function inferHeaderTarget(request: JSONRPCInvocation): string | undefined;
856
755
 
857
756
  /**
858
757
  * Infers the legacy revision an `initialize` request negotiates.
@@ -872,6 +771,64 @@ export declare function inferHeaderIssue(request: Request, reference: JSONRPCInv
872
771
  */
873
772
  export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegacyVersion;
874
773
 
774
+ /**
775
+ * Infers the refusal one `tools/call` earns for a `Mcp-Param-*` header the body contradicts.
776
+ *
777
+ * @remarks
778
+ * The custom-header half of the standard-header seam {@link inferHeaderIssue} owns, and it
779
+ * takes the SERVED definition's projections rather than a header issue: SEP-2243 scopes the
780
+ * rule to the `Mcp-Param-*` names the server's OWN tool definitions annotate, so a name no
781
+ * parameter claims is another party's header and travels through untouched.
782
+ *
783
+ * For each recognized parameter the body's value at the parameter's own property path fixes
784
+ * the expectation. A value the call omits or supplies as `null` requires no header, and a
785
+ * header sent anyway is refused because it asserts something the body never said. A value the
786
+ * call does supply requires its header: an absent one, a Base64 sentinel whose payload is
787
+ * invalid, and a decoded value that disagrees are each refused. An `integer` parameter
788
+ * compares numerically, so a peer that padded its decimal still matches. A supplied value
789
+ * whose runtime shape contradicts the declared type is left alone — the tool's own argument
790
+ * validation owns that disagreement, and refusing it here would report an argument fault as a
791
+ * header fault.
792
+ *
793
+ * Messages name the field and the body path the expectation came from, and never echo the
794
+ * value the peer supplied.
795
+ *
796
+ * @param request - The HTTP request carrying the headers
797
+ * @param parameters - The projections the served tool definition declares
798
+ * @param values - The call's `arguments` record
799
+ * @returns The refusal message for the first disagreeing parameter, or `undefined`
800
+ *
801
+ * @example
802
+ * ```ts
803
+ * inferParameterRefusal(request, [{ name: 'Region', path: ['region'], primitive: 'string' }], {})
804
+ * // → undefined when the request carries no `Mcp-Param-Region` either
805
+ * ```
806
+ */
807
+ export declare function inferParameterRefusal(request: Request, parameters: readonly MCPHeaderParameter[], values: unknown): string | undefined;
808
+
809
+ /**
810
+ * Infers the protocol header issue an active legacy session's pinned revision diagnoses.
811
+ *
812
+ * @remarks
813
+ * The session layer's rule, distinct from the body-derived one {@link inferHeaderIssue} owns:
814
+ * a live legacy session pinned its revision at `initialize`, so every later request on that
815
+ * session must name the same one. An absent header reads as `missing`, which the session
816
+ * middleware answers by SUPPLYING the pinned revision rather than refusing; a present header
817
+ * naming another revision reads as `mismatched` and is refused. The message names the session's
818
+ * revision and never echoes the client-supplied value.
819
+ *
820
+ * @param request - The HTTP request carrying the headers
821
+ * @param version - The legacy revision the active session pinned at `initialize`
822
+ * @returns The protocol header issue, or `undefined` when the header agrees
823
+ *
824
+ * @example
825
+ * ```ts
826
+ * const issue = inferSessionHeaderIssue(request, '2025-06-18')
827
+ * issue?.reason // 'missing' when the request carries no protocol header
828
+ * ```
829
+ */
830
+ export declare function inferSessionHeaderIssue(request: Request, version: MCPVersion): MCPHeaderIssue | undefined;
831
+
875
832
  /**
876
833
  * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
877
834
  *
@@ -888,7 +845,7 @@ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegac
888
845
  export declare function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number;
889
846
 
890
847
  /**
891
- * The result of folding one more chunk of raw stdio bytes into a newline-framed
848
+ * Represents the result of folding one more chunk of raw stdio bytes into a newline-framed
892
849
  * buffer — every COMPLETE line extracted (newline-terminated in the wire bytes) plus
893
850
  * the trailing partial line carried forward as the new `remainder`.
894
851
  *
@@ -901,44 +858,6 @@ export declare interface LineExtraction {
901
858
  readonly remainder: string;
902
859
  }
903
860
 
904
- /** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
905
- export declare const MCP_METHOD_HEADER = "mcp-method";
906
-
907
- /** The modern Streamable-HTTP request header carrying a named method's target. */
908
- export declare const MCP_NAME_HEADER = "mcp-name";
909
-
910
- /**
911
- * The Streamable-HTTP transport header carrying the negotiated MCP protocol version
912
- * on every post-initialize client request.
913
- *
914
- * @remarks
915
- * Required by MCP 2025-06-18 after initialization. Both HTTP client transports
916
- * capture the initialize result's `protocolVersion` and send it on subsequent
917
- * requests; `createMCPRoutes` rejects a present unsupported value before dispatch.
918
- */
919
- export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
920
-
921
- /**
922
- * The Streamable-HTTP transport header that carries the MCP session id. When a {@link
923
- * import('./middlewares.js').createMCPSession} middleware is mounted, it SETS this header on
924
- * the `initialize` response (the minted id) and READS it on every subsequent request
925
- * (validating the session); the stateless `createMCPRoutes` default neither sets nor reads it.
926
- */
927
- export declare const MCP_SESSION_HEADER = "mcp-session-id";
928
-
929
- /**
930
- * The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the
931
- * client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.
932
- *
933
- * @remarks
934
- * `createWebSocketServer` echoes it in the upgrade response and `createWebSocketClientTransport`
935
- * requests it, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
936
- * same path. The default WebSocket upgrade path is {@link DEFAULT_MCP_PATH} (the same `'/mcp'`
937
- * the HTTP transport mounts at) — the upgrade is selected by the `Upgrade: websocket` header,
938
- * not a separate path.
939
- */
940
- export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
941
-
942
861
  /**
943
862
  * Extracts consumer-asserted caller context synchronously from an HTTP request after the
944
863
  * transport has validated it for dispatch.
@@ -957,7 +876,8 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
957
876
  export declare type MCPCallerHandler<TState = unknown> = (request: Request, context: RouteContext<string, TState> | undefined) => unknown;
958
877
 
959
878
  /**
960
- * One required MCP HTTP header that is absent or disagrees with its server-derived value.
879
+ * Reports one required MCP HTTP header that is absent or disagrees with its server-derived
880
+ * value.
961
881
  *
962
882
  * @remarks
963
883
  * - `header` — the canonical HTTP field name safe to show to an integrator.
@@ -972,7 +892,7 @@ export declare interface MCPHeaderIssue {
972
892
  }
973
893
 
974
894
  /**
975
- * Shared SSE keepalive options for held-open HTTP responses.
895
+ * Configures the shared SSE keepalive for held-open HTTP responses.
976
896
  *
977
897
  * @remarks
978
898
  * - `interval` — milliseconds between SSE comment frames. Defaults to {@link
@@ -985,7 +905,7 @@ export declare interface MCPKeepaliveOptions {
985
905
  }
986
906
 
987
907
  /**
988
- * Shared options for the protocol-required HTTP `Origin` validation at the route and session
908
+ * Configures the protocol-required HTTP `Origin` validation shared by the route and session
989
909
  * enforcement sites.
990
910
  *
991
911
  * @remarks
@@ -1001,12 +921,12 @@ export declare interface MCPOriginOptions {
1001
921
  }
1002
922
 
1003
923
  /**
1004
- * One MCP transport session — the per-session entity a {@link
924
+ * Represents one MCP transport session — the per-session entity a {@link
1005
925
  * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the
1006
926
  * resumable server→client push channel with its bounded replay log FOLDED IN.
1007
927
  *
1008
928
  * @remarks
1009
- * The single session entity (the old `SessionState` + `EventStore` merged): it holds the
929
+ * One entity carries the whole session: it holds the
1010
930
  * session `id`, its OWN bounded, replayable log of pushed server→client messages (the
1011
931
  * resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with
1012
932
  * `capacity` / `ttl` eviction, not a separate store), and the set of open
@@ -1018,7 +938,7 @@ export declare interface MCPOriginOptions {
1018
938
  * SSE event (`stream.write({ id, data })`). A push with NO attached stream is still logged,
1019
939
  * so a client that connects (or reconnects with a `Last-Event-ID`) LATER replays it from the
1020
940
  * log. A `write` to a closed stream is a safe no-op (the {@link
1021
- * `@orkestrel/server`'s `openStream` contract), so a just-disconnected stream that
941
+ * `@orkestrel/server`'s `createStream` contract), so a just-disconnected stream that
1022
942
  * has not yet been `detach`ed never throws. A replayed event and the live one carry the
1023
943
  * IDENTICAL id (the log assigns it once).
1024
944
  *
@@ -1043,9 +963,10 @@ export declare interface MCPOriginOptions {
1043
963
  * The middleware opens the stream (the spine seam) and registers it here; this class only
1044
964
  * serializes a message onto the already-open streams.
1045
965
  *
1046
- * - **Injected clock.** `push` / `replay` accept an optional `now` (epoch ms), defaulting to
1047
- * `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real
1048
- * timer.
966
+ * - **Injected clock.** {@link import('./types.js').MCPSessionOptions.clock} supplies the
967
+ * epoch-ms clock the lazy TTL sweep reads, defaulting to `Date.now` — so a test drives TTL
968
+ * eviction with an elapsed clock rather than a real timer, and the middleware that mints a
969
+ * session hands its own clock down instead of leaving the log on wall-clock time.
1049
970
  *
1050
971
  * @example
1051
972
  * ```ts
@@ -1061,15 +982,15 @@ export declare class MCPSession implements MCPSessionInterface {
1061
982
  get id(): string;
1062
983
  attach(stream: StreamInterface): void;
1063
984
  detach(stream: StreamInterface): void;
1064
- push(message: JSONRPCMessage, now?: number): string;
1065
- replay(afterId: string, now?: number): readonly EventStoreEntry[];
985
+ push(message: JSONRPCMessage): string;
986
+ replay(afterId: string): readonly MCPSessionEvent[];
1066
987
  }
1067
988
 
1068
989
  /**
1069
- * The closure store entry a {@link import('./middlewares.js').createMCPSession} middleware
1070
- * keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms instant it
1071
- * was last touched (the lazy-TTL sweep's idle clock, independent of the session's own
1072
- * replay-log TTL).
990
+ * Represents the closure store entry a {@link import('./middlewares.js').createMCPSession}
991
+ * middleware keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms
992
+ * instant it was last touched (the lazy-TTL sweep's idle clock, independent of the session's
993
+ * own replay-log TTL).
1073
994
  *
1074
995
  * @remarks
1075
996
  * - `session` — the live {@link MCPSession} entity the store keys by session id.
@@ -1081,12 +1002,32 @@ export declare class MCPSession implements MCPSessionInterface {
1081
1002
  export declare interface MCPSessionEntry {
1082
1003
  readonly session: MCPSession;
1083
1004
  readonly touched: number;
1084
- /** The legacy revision negotiated when this session was minted. */
1005
+ /** Holds the legacy revision negotiated when this session was minted. */
1085
1006
  readonly version: MCPVersion;
1086
1007
  }
1087
1008
 
1088
1009
  /**
1089
- * One MCP transport session the per-session entity a {@link
1010
+ * Represents one entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed
1011
+ * {@link JSONRPCMessage} tagged with the monotone event `id` the session assigned and the
1012
+ * `timestamp` it was appended at (for the lazy-TTL replay window).
1013
+ *
1014
+ * @remarks
1015
+ * - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
1016
+ * value a resumable client echoes back as its `Last-Event-ID` to replay from here.
1017
+ * - `message` — the server→client {@link JSONRPCMessage} that was pushed.
1018
+ * - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
1019
+ *
1020
+ * A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
1021
+ * returns.
1022
+ */
1023
+ export declare interface MCPSessionEvent {
1024
+ readonly id: string;
1025
+ readonly message: JSONRPCMessage;
1026
+ readonly timestamp: number;
1027
+ }
1028
+
1029
+ /**
1030
+ * Represents one MCP transport session — the per-session entity a {@link
1090
1031
  * import('./middlewares.js').createMCPSession} middleware owns (the {@link
1091
1032
  * import('./MCPSession.js').MCPSession} entity), carrying the resumable server→client push
1092
1033
  * channel with its bounded replay log FOLDED IN.
@@ -1111,7 +1052,7 @@ export declare interface MCPSessionInterface {
1111
1052
  attach(stream: StreamInterface): void;
1112
1053
  detach(stream: StreamInterface): void;
1113
1054
  push(message: JSONRPCMessage): string;
1114
- replay(afterId: string): readonly EventStoreEntry[];
1055
+ replay(afterId: string): readonly MCPSessionEvent[];
1115
1056
  }
1116
1057
 
1117
1058
  /**
@@ -1126,12 +1067,11 @@ export declare interface MCPSessionInterface {
1126
1067
  * is treated as ABSENT and lazily evicted on the next access (no background timer — the
1127
1068
  * `createRateLimiter` lazy-window idiom). Omit it for sessions that live until an explicit
1128
1069
  * `DELETE`.
1129
- * - `capacity` — the FOLDED event-log bound per session: the maximum number of pushed
1130
- * server→client messages retained for replay before the OLDEST is evicted, paired with a
1131
- * per-event idle lifetime ({@link import('./constants.js').DEFAULT_MCP_SESSION_TTL}) that
1132
- * bounds how far a reconnecting client may replay. Omit it for the {@link
1133
- * import('./constants.js').DEFAULT_MCP_SESSION_CAPACITY} default. (The session `ttl` bounds
1134
- * the session; this `capacity` bounds its replay log — independent knobs.)
1070
+ * - `session` — the knobs forwarded to each minted {@link MCPSession}: `capacity` bounds its
1071
+ * replay log and `ttl` is that log's per-event lifetime. This type's own `ttl` bounds the
1072
+ * SESSION instead. An omitted leaf takes its {@link MCPSessionOptions} default, and an
1073
+ * omitted `session.clock` inherits this type's own `clock`, so one injected clock governs
1074
+ * both the store sweep and the log sweep unless a caller names a different one.
1135
1075
  * - `clock` — the `() => number` epoch-ms clock {@link import('./middlewares.js').createMCPSession}
1136
1076
  * uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
1137
1077
  * deterministic clock a TTL test advances explicitly instead of racing a real idle window
@@ -1144,18 +1084,44 @@ export declare interface MCPSessionInterface {
1144
1084
  * - `keepalive` — the SSE liveness options for the held-open resumable response. `interval`
1145
1085
  * defaults to {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}.
1146
1086
  */
1147
- export declare interface MCPSessionOptions {
1087
+ export declare interface MCPSessionMiddlewareOptions {
1148
1088
  readonly path?: string;
1149
1089
  readonly ttl?: number;
1150
- readonly capacity?: number;
1090
+ readonly session?: MCPSessionOptions;
1151
1091
  readonly clock?: () => number;
1152
- /** Must match the route layer's value; `origins` is ignored when `enabled` is `false`. */
1092
+ /** Requires the route layer's value; `origins` is ignored when `enabled` is `false`. */
1153
1093
  readonly origin?: MCPOriginOptions;
1154
1094
  readonly keepalive?: MCPKeepaliveOptions;
1155
1095
  }
1156
1096
 
1157
1097
  /**
1158
- * The `context.state` slice a {@link import('./middlewares.js').createMCPSession}
1098
+ * Options for the {@link MCPSession} entity — its folded replay log's capacity and per-event
1099
+ * lifetime.
1100
+ *
1101
+ * @remarks
1102
+ * - `capacity` — the maximum number of pushed server→client messages retained for replay
1103
+ * before the OLDEST is evicted. Omit it for the {@link
1104
+ * import('./constants.js').DEFAULT_MCP_SESSION_CAPACITY} default.
1105
+ * - `ttl` — the PER-EVENT idle lifetime in milliseconds: a log entry older than `ttl` is
1106
+ * dropped by the lazy sweep `push` and `replay` run, which bounds how far a reconnecting
1107
+ * client may replay. Omit it for the {@link
1108
+ * import('./constants.js').DEFAULT_MCP_SESSION_TTL} default; a non-positive value means no
1109
+ * entry ever ages out by time.
1110
+ * - `clock` — the `() => number` epoch-ms clock the log's lazy TTL sweep reads; defaults to
1111
+ * `Date.now`.
1112
+ *
1113
+ * The middleware's own knobs — the owned path, the idle-SESSION sweep window, origin
1114
+ * validation, and keepalive — live on {@link MCPSessionMiddlewareOptions}. The two `ttl`
1115
+ * values measure different things, which is why they sit on different types.
1116
+ */
1117
+ export declare interface MCPSessionOptions {
1118
+ readonly capacity?: number;
1119
+ readonly ttl?: number;
1120
+ readonly clock?: () => number;
1121
+ }
1122
+
1123
+ /**
1124
+ * Declares the `context.state` slice a {@link import('./middlewares.js').createMCPSession}
1159
1125
  * middleware sets on a validated / minted request — a consumer's `TState` extends
1160
1126
  * this so the downstream route handler can read `context.state.session` to `push`
1161
1127
  * a server-initiated message onto the session's resumable stream.
@@ -1170,27 +1136,6 @@ export declare interface MCPSessionState {
1170
1136
  readonly session?: MCPSessionInterface;
1171
1137
  }
1172
1138
 
1173
- /**
1174
- * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
1175
- * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
1176
- *
1177
- * @remarks
1178
- * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({
1179
- * stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s
1180
- * {@link SSEParserInterface} (handling a partial line / in-progress event split across
1181
- * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with
1182
- * `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never
1183
- * thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam
1184
- * serializes against, so the wire round-trips. A `null` body (no stream) yields no
1185
- * messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
1186
- * reads a request/response SSE reply (the server sends one `data:` event then ends), so
1187
- * this drains to completion.
1188
- *
1189
- * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
1190
- * @returns Every {@link JSONRPCMessage} the stream carried, in order
1191
- */
1192
- export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
1193
-
1194
1139
  /**
1195
1140
  * Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it
1196
1141
  * reconnects to the resumable `GET {path}` stream, or `undefined` when absent.
@@ -1268,27 +1213,26 @@ export declare function rejectUnknownSession(): Response;
1268
1213
  * ```ts
1269
1214
  * const answer = await mcp.dispatch(invocation, { signal: disconnect.signal })
1270
1215
  * if (answer !== undefined && Symbol.asyncIterator in answer) {
1271
- * const sse = openStream()
1216
+ * const sse = createStream()
1272
1217
  * queueMicrotask(() => void sendEventStream(answer, sse))
1273
1218
  * }
1274
1219
  * ```
1275
1220
  */
1276
1221
  export declare function sendEventStream(stream: MCPStreamControllerInterface, sse: StreamInterface): Promise<void>;
1277
1222
 
1278
- /** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1223
+ /** Names the `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1279
1224
  export declare const SSE_BUFFERING_DISABLED = "no";
1280
1225
 
1281
- /** The reverse-proxy response header controlling buffering of an SSE response. */
1226
+ /** Names the reverse-proxy response header controlling buffering of an SSE response. */
1282
1227
  export declare const SSE_BUFFERING_HEADER = "x-accel-buffering";
1283
1228
 
1284
- /** The comment text written by the held-open MCP response keepalive. */
1229
+ /** Names the comment text written by the held-open MCP response keepalive. */
1285
1230
  export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1286
1231
 
1287
1232
  /**
1288
- * The stdio CLIENT transport for the Model Context Protocol a
1289
- * {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
1290
- * newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
1291
- * import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
1233
+ * Drives a CHILD PROCESS MCP server over newline-delimited JSON-RPC on `stdin`/`stdout`
1234
+ * a {@link StdioClientTransportInterface}, the stdio sibling of {@link
1235
+ * import('@orkestrel/mcp').HTTPClientTransport} and {@link
1292
1236
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1293
1237
  *
1294
1238
  * @remarks
@@ -1329,7 +1273,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1329
1273
  * never moves again, so a detached descendant writing to the inherited stderr after the cutoff
1330
1274
  * cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
1331
1275
  * byte bound.
1332
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1276
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
1333
1277
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1334
1278
  * fault, including the child spawn cause the supervisor surfaces and the notice that this
1335
1279
  * lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
@@ -1345,7 +1289,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1345
1289
  export declare class StdioClientTransport implements StdioClientTransportInterface {
1346
1290
  #private;
1347
1291
  constructor(options: StdioClientTransportOptions);
1348
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1292
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap_2>;
1349
1293
  get session(): string | undefined;
1350
1294
  get duplex(): boolean;
1351
1295
  get evidence(): string | undefined;
@@ -1365,23 +1309,23 @@ export declare class StdioClientTransport implements StdioClientTransportInterfa
1365
1309
  }
1366
1310
 
1367
1311
  /**
1368
- * The contract `createStdioClientTransport` returns — a {@link MCPClientTransportInterface}
1369
- * that also reports the supervised child's stderr tail, the diagnostic a child that dies at
1370
- * startup leaves behind.
1312
+ * Declares the contract `createStdioClientTransport` returns — a
1313
+ * {@link MCPMessageTransportInterface} that also reports the supervised child's stderr tail, the
1314
+ * diagnostic a child that dies at startup leaves behind.
1371
1315
  *
1372
1316
  * @remarks
1373
1317
  * This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
1374
1318
  * `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
1375
- * rather than on {@link MCPClientTransportInterface} because a transport that supervises no child —
1319
+ * rather than on {@link MCPMessageTransportInterface} because a transport that supervises no child —
1376
1320
  * Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
1377
1321
  * of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
1378
- * consumer that widens this value back to {@link MCPClientTransportInterface}, including by
1322
+ * consumer that widens this value back to {@link MCPMessageTransportInterface}, including by
1379
1323
  * reading `client.transport`, loses the reader and must keep the original reference.
1380
1324
  */
1381
- export declare interface StdioClientTransportInterface extends MCPClientTransportInterface {
1325
+ export declare interface StdioClientTransportInterface extends MCPMessageTransportInterface {
1382
1326
  /**
1383
- * The supervised child's decoded stderr tail — live while a child is held, and the value
1384
- * captured at that child's end afterwards.
1327
+ * Reports the supervised child's decoded stderr tail — live while a child is held, and the
1328
+ * value captured at that child's end afterwards.
1385
1329
  *
1386
1330
  * @remarks
1387
1331
  * - **Readings.** `undefined` while no child has run and none has been captured — before the
@@ -1474,6 +1418,30 @@ export declare interface StdioClientTransportOptions {
1474
1418
  readonly delivery?: number;
1475
1419
  }
1476
1420
 
1421
+ /**
1422
+ * Arms and tears down the newline-delimited JSON-RPC pump over the {@link StdioServerOptions}
1423
+ * stream pair — the stdio INGRESS handle {@link import('./factories.js').createStdioServer}
1424
+ * returns.
1425
+ *
1426
+ * @remarks
1427
+ * - `start()` — arm the pump: subscribe to `input`, and dispatch every complete line through
1428
+ * the bound {@link import('@src/core').MCPDispatcherInterface}, writing each defined
1429
+ * response back to `output`. The subscriptions are attached by the time the call returns.
1430
+ * The pump arms ONCE, so a repeated `start()` attaches nothing further and an inbound
1431
+ * request still draws exactly one reply.
1432
+ * - `stop()` — unbind the pump and close the transport: the listeners `start()` put on
1433
+ * `input` / `output` are removed, every pending `send` rejects, and `input` is released so
1434
+ * the process can exit. The release is complete by the time the call returns, and a
1435
+ * repeated `stop()` does nothing.
1436
+ * - **One lifetime per handle.** `stop()` ends it permanently: a `start()` issued afterwards
1437
+ * arms nothing, and serving again takes a fresh
1438
+ * {@link import('./factories.js').createStdioServer} over a live stream pair.
1439
+ */
1440
+ export declare interface StdioServerInterface {
1441
+ start(): void;
1442
+ stop(): void;
1443
+ }
1444
+
1477
1445
  /**
1478
1446
  * Options for `createStdioServer` — the injectable stdin/stdout streams the server
1479
1447
  * transport reads newline-delimited JSON-RPC requests from and writes responses to.
@@ -1490,15 +1458,14 @@ export declare interface StdioServerOptions {
1490
1458
  }
1491
1459
 
1492
1460
  /**
1493
- * The stdio SERVER transport for the Model Context Protocol — wraps an injectable
1494
- * readable/writable stream pair (`process.stdin`/`process.stdout` in production, a
1495
- * test double in tests) as a {@link MCPClientTransportInterface}, the newline-delimited
1496
- * JSON-RPC channel {@link import('../factories.js').createStdioServer} pumps
1497
- * `mcp.dispatch` over, the stdio mirror of {@link
1461
+ * Wraps an injectable readable/writable stream pair (`process.stdin`/`process.stdout` in
1462
+ * production, a test double in tests) as a {@link MCPMessageTransportInterface} — the
1463
+ * newline-delimited JSON-RPC channel {@link import('../factories.js').createStdioServer}
1464
+ * pumps `mcp.dispatch` over, the stdio mirror of {@link
1498
1465
  * import('./WebSocketServerTransport.js').WebSocketServerTransport}.
1499
1466
  *
1500
1467
  * @remarks
1501
- * - **Reuses `MCPClientTransportInterface`.** The same generic carrier the HTTP
1468
+ * - **Reuses `MCPMessageTransportInterface`.** The same generic carrier the HTTP
1502
1469
  * and WebSocket server transports implement — `emitter` (`message` / `close` /
1503
1470
  * `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).
1504
1471
  * - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each
@@ -1524,14 +1491,14 @@ export declare interface StdioServerOptions {
1524
1491
  * listener receives data. The injected streams are owned by the caller (typically
1525
1492
  * `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears
1526
1493
  * them.
1527
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1494
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
1528
1495
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1529
1496
  * fault), distinct from the emitter's own listener-error channel.
1530
1497
  */
1531
- export declare class StdioServerTransport implements MCPClientTransportInterface_2 {
1498
+ export declare class StdioServerTransport implements MCPMessageTransportInterface_2 {
1532
1499
  #private;
1533
1500
  constructor(input: NodeJS.ReadableStream, output: NodeJS.WritableStream);
1534
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1501
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap_2>;
1535
1502
  get session(): string | undefined;
1536
1503
  get duplex(): boolean;
1537
1504
  start(): Promise<void>;
@@ -1570,10 +1537,10 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1570
1537
  export declare function upgradeRequestPath(request: IncomingMessage): string;
1571
1538
 
1572
1539
  /**
1573
- * The WebSocket CLIENT transport for the Model Context Protocol — a
1574
- * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket, the
1540
+ * Drives a REMOTE MCP server over a WebSocket — a CLIENT
1541
+ * {@link MCPMessageTransportInterface} for the Model Context Protocol, the
1575
1542
  * egress mirror of {@link import('./factories.js').createWebSocketServer} and the WebSocket
1576
- * sibling of {@link import('./HTTPClientTransport.js').HTTPClientTransport}.
1543
+ * sibling of {@link import('@orkestrel/mcp').HTTPClientTransport}.
1577
1544
  *
1578
1545
  * @remarks
1579
1546
  * - **Persistent bidirectional channel (unlike the HTTP transport).** `start()` performs the
@@ -1591,16 +1558,18 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
1591
1558
  * transport while the handshake was on the wire, both WIN — the socket that arrives late is
1592
1559
  * DESTROYED and never bound, so no orphan is left re-emitting frames at nobody. Both
1593
1560
  * `start()` calls still resolve; exactly one socket is ever bound.
1594
- * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and narrowed
1595
- * with `parseJSONRPCMessage` — a {@link JSONRPCMessage} re-emits on this transport's `message`
1561
+ * - **Inbound (`message`).** Each decoded text frame runs through the shared `deliverMessage`
1562
+ * fold (parse, then narrow) — a {@link JSONRPCMessage} re-emits on this transport's `message`
1596
1563
  * event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
1597
1564
  * non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
1598
1565
  * / `error` bridge to this transport's events.
1599
1566
  * - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
1600
- * confirmed, so this transport answers a closed channel from its OWN state: a `send` with no
1601
- * bound socket — before `start()`, after `close()`, or after the peer ended the socket —
1602
- * REJECTS with `WebSocket transport is not connected`. It neither drops the message (the
1603
- * browser face's posture) nor queues it for a connection this transport is not holding.
1567
+ * confirmed, so this transport answers a closed channel from its own state AND the socket's
1568
+ * `readyState`: a `send` with no bound socket — before `start()`, after `close()`, or after the
1569
+ * peer ended the socket — and a `send` on a bound socket that is not `OPEN` both REJECT with
1570
+ * `WebSocket transport is not connected`. It neither drops the message nor queues it for a
1571
+ * connection this transport is not holding — the browser face queues a pre-open send, and this
1572
+ * one, holding no connection to flush it onto, rejects that too.
1604
1573
  * - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
1605
1574
  * upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
1606
1575
  * transport at once instead of waiting for a peer that may never answer — the suspended
@@ -1608,7 +1577,7 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
1608
1577
  * - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`
1609
1578
  * one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`
1610
1579
  * → TLS through `node:https`). Either reaches the same endpoint.
1611
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every emit
1580
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every emit
1612
1581
  * the emitter isolates a listener throw (a buggy observer never corrupts the transport);
1613
1582
  * `error` is a DOMAIN event (a transport-level fault).
1614
1583
  *
@@ -1619,10 +1588,10 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
1619
1588
  * await client.connect() // start() handshakes, then the MCP initialize runs over WS frames
1620
1589
  * ```
1621
1590
  */
1622
- export declare class WebSocketClientTransport implements MCPClientTransportInterface_2 {
1591
+ export declare class WebSocketClientTransport implements MCPMessageTransportInterface_2 {
1623
1592
  #private;
1624
1593
  constructor(options: WebSocketClientTransportOptions);
1625
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1594
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap_2>;
1626
1595
  get session(): string | undefined;
1627
1596
  get duplex(): boolean;
1628
1597
  start(): Promise<void>;
@@ -1672,7 +1641,7 @@ export declare interface WebSocketClientTransportOptions {
1672
1641
  * transport mounts at). A protocol-upgrade request to any OTHER path is DECLINED
1673
1642
  * (the handler returns `false`, so the spine fans it to the next handler or destroys it).
1674
1643
  * - `subprotocol` — the WebSocket subprotocol selected in the `101` handshake's
1675
- * `Sec-WebSocket-Protocol`; defaults to {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL}
1644
+ * `Sec-WebSocket-Protocol`; defaults to {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL}
1676
1645
  * (`'mcp'`). It is sent only when the client's offer contains that token.
1677
1646
  *
1678
1647
  * Auth / origin policy is deliberately ABSENT: like the HTTP transport, the WebSocket
@@ -1686,41 +1655,46 @@ export declare interface WebSocketServerOptions {
1686
1655
  }
1687
1656
 
1688
1657
  /**
1689
- * The per-connection JSON-RPC-over-WebSocket SERVER bridge wraps a
1690
- * {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a
1691
- * {@link MCPClientTransportInterface}, the bidirectional JSON-RPC message channel
1658
+ * Wraps a {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a
1659
+ * {@link MCPMessageTransportInterface} the per-connection JSON-RPC-over-WebSocket SERVER
1660
+ * bridge, the bidirectional JSON-RPC message channel
1692
1661
  * `createWebSocketServer` pumps `mcp.dispatch` over and the egress mirror's
1693
1662
  * {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.
1694
1663
  *
1695
1664
  * @remarks
1696
- * - **Reuses `MCPClientTransportInterface`.** It IS the same generic carrier the HTTP
1665
+ * - **Reuses `MCPMessageTransportInterface`.** It IS the same generic carrier the HTTP
1697
1666
  * client transport implements — `emitter` (`message` / `close` / `error`), `start`,
1698
1667
  * `send`, `close` — so the WebSocket server and client both speak ONE transport contract,
1699
1668
  * no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a
1700
1669
  * session id is the deferred sessions tier). The name keeps the role explicit even though
1701
1670
  * the shape is shared.
1702
1671
  * - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text
1703
- * frame is `JSON.parse`d inside a try/catch and narrowed with `parseJSONRPCMessage` — a
1672
+ * frame runs through the shared `deliverMessage` fold (parse, then narrow) — a
1704
1673
  * well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the
1705
1674
  * parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while
1706
1675
  * a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It
1707
1676
  * also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
1708
1677
  * - **Outbound (`send`).** `send(message)` writes one text frame
1709
- * (`nodeWs.send(JSON.stringify(message))`); the underlying wrapper no-ops a write on a
1710
- * non-open socket, so a closed connection drops silently rather than throwing.
1678
+ * (`nodeWs.send(JSON.stringify(message))`). The underlying wrapper no-ops a write on a
1679
+ * non-open socket and confirms nothing, so this bridge answers a closed channel from its own
1680
+ * state and the socket's `readyState`: a `send` after `close()`, after the peer's close, or on
1681
+ * a socket that is not `OPEN` REJECTS with `WebSocket transport is not connected` rather than
1682
+ * resolving on a frame nobody wrote. `bindServer` catches that rejection and routes it to the
1683
+ * dispatcher's `error` event, and it aborts every in-flight request the moment this transport's
1684
+ * `close` fires — so a peer that disconnects mid-request is answered by no write at all.
1711
1685
  * - **`close()`** removes the subscriptions `start()` installed on the socket, closes the
1712
1686
  * underlying socket (the RFC 6455 close handshake), and fires the transport's `close` event
1713
1687
  * (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives
1714
1688
  * between that release and the peer's close echo reaches nothing: the socket-driven close path
1715
1689
  * releases the same way, so a closed transport is never subscribed to a live socket.
1716
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the emitter
1690
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the emitter
1717
1691
  * isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a
1718
1692
  * DOMAIN event (a transport-level fault), distinct from the emitter's listener-error channel.
1719
1693
  */
1720
- export declare class WebSocketServerTransport implements MCPClientTransportInterface_2 {
1694
+ export declare class WebSocketServerTransport implements MCPMessageTransportInterface_2 {
1721
1695
  #private;
1722
1696
  constructor(socket: NodeWebSocketInterface);
1723
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1697
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap_2>;
1724
1698
  get session(): string | undefined;
1725
1699
  get duplex(): boolean;
1726
1700
  start(): Promise<void>;