@orkestrel/mcp 0.0.27 → 0.0.29

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,32 +1,30 @@
1
- import { EmitterInterface } from '@orkestrel/emitter';
2
- import { IncomingMessage } from 'node:http';
3
- import { JSONRPCInvocation } from '@orkestrel/mcp';
4
- import { JSONRPCMessage } from '@orkestrel/mcp';
5
- import { JSONRPCMessage as JSONRPCMessage_2 } from '@orkestrel/mcp';
6
- 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
- import { MCPContinuationInterface } from '@orkestrel/mcp';
12
- import { MCPDispatcherInterface } from '@orkestrel/mcp';
13
- import { MCPEra } from '@orkestrel/mcp';
14
- import { MCPHeaderParameter } from '@orkestrel/mcp';
15
- import { MCPLegacyVersion } from '@orkestrel/mcp';
16
- import { MCPStreamControllerInterface } from '@orkestrel/mcp';
17
- import { MCPTransportInterface } from '@orkestrel/mcp';
18
- import { MCPVersion } from '@orkestrel/mcp';
19
- import { MiddlewareHandler } from '@orkestrel/server';
20
- import { NodeWebSocketInterface } from '@orkestrel/websocket';
21
- import { RouteContext } from '@orkestrel/router';
22
- import { RouteInput } from '@orkestrel/router';
23
- import { ServerEventMap } from '@orkestrel/server';
24
- import { StreamInterface } from '@orkestrel/server';
25
- import { TokenSecret } from '@orkestrel/server';
26
- import { UpgradeHandler } from '@orkestrel/server';
1
+ import type { EmitterInterface } from '@orkestrel/emitter';
2
+ import type { HTTPClientTransportOptions } from '@orkestrel/mcp';
3
+ import type { IncomingMessage } from 'node:http';
4
+ import type { JSONRPCInvocation } from '@orkestrel/mcp';
5
+ import type { JSONRPCMessage } from '@orkestrel/mcp';
6
+ import type { JSONRPCResponse } from '@orkestrel/mcp';
7
+ import type { MCPContinuationInterface } from '@orkestrel/mcp';
8
+ import type { MCPDispatcherInterface } from '@orkestrel/mcp';
9
+ import type { MCPEra } from '@orkestrel/mcp';
10
+ import type { MCPHeaderParameter } from '@orkestrel/mcp';
11
+ import type { MCPLegacyVersion } from '@orkestrel/mcp';
12
+ import type { MCPMessageTransportEventMap } from '@orkestrel/mcp';
13
+ import type { MCPMessageTransportInterface } from '@orkestrel/mcp';
14
+ import type { MCPStreamControllerInterface } from '@orkestrel/mcp';
15
+ import type { MCPTransportInterface } from '@orkestrel/mcp';
16
+ import type { MCPVersion } from '@orkestrel/mcp';
17
+ import type { MiddlewareHandler } from '@orkestrel/server';
18
+ import type { NodeWebSocketInterface } from '@orkestrel/websocket';
19
+ import type { RouteContext } from '@orkestrel/router';
20
+ import type { RouteInput } from '@orkestrel/router';
21
+ import type { ServerEventMap } from '@orkestrel/server';
22
+ import type { StreamInterface } from '@orkestrel/server';
23
+ import type { TokenSecret } from '@orkestrel/server';
24
+ import type { UpgradeHandler } from '@orkestrel/server';
27
25
 
28
26
  /**
29
- * Whether the request's `Accept` header opts into a Server-Sent-Events response.
27
+ * Checks whether the request's `Accept` header opts into a Server-Sent-Events response.
30
28
  *
31
29
  * @remarks
32
30
  * Reads the fetch-standard `Request.headers.get('accept')` and returns `true` when it
@@ -36,12 +34,12 @@ import { UpgradeHandler } from '@orkestrel/server';
36
34
  * — an absent / unmatched header returns `false`.
37
35
  *
38
36
  * @param request - The fetch-standard `Request`
39
- * @returns `true` when the client `Accept`s `text/event-stream`, else `false`
37
+ * @returns True if the client `Accept`s `text/event-stream`; false otherwise
40
38
  */
41
39
  export declare function acceptsEventStream(request: Request): boolean;
42
40
 
43
41
  /**
44
- * Whether an HTTP request satisfies the endpoint's origin gate.
42
+ * Checks whether an HTTP request satisfies the endpoint's origin gate.
45
43
  *
46
44
  * @remarks
47
45
  * Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
@@ -52,43 +50,45 @@ export declare function acceptsEventStream(request: Request): boolean;
52
50
  *
53
51
  * @param request - The fetch-standard request to validate
54
52
  * @param options - Shared origin validation and delegation options
55
- * @returns `true` when the request may reach MCP dispatch
53
+ * @returns True if the request may reach MCP dispatch; false otherwise
56
54
  */
57
55
  export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
58
56
 
59
57
  /**
60
- * Bridges a message-channel {@link MCPClientTransportInterface} (the shape the stdio and
61
- * WebSocket SERVER transports already implement) into the environment-agnostic
62
- * {@link import('@orkestrel/mcp').MCPTransportInterface} port — the adapter
63
- * {@link import('./factories.js').createStdioServer} and {@link
64
- * import('./factories.js').createWebSocketServer} pipe through `bindServer`, so the
65
- * request/reply/error pump those factories used to hand-roll identically now
66
- * lives ONCE in the core binder.
58
+ * Creates the server-side mirror of
59
+ * {@link import('@orkestrel/mcp').createDuplexClientTransport}: the adapter that bridges a
60
+ * message-channel {@link MCPMessageTransportInterface}
61
+ * (the shape the stdio and WebSocket server transports already implement) onto the
62
+ * environment-agnostic {@link import('@orkestrel/mcp').MCPTransportInterface} port what
63
+ * {@link createStdioServer} and {@link createWebSocketServer} pipe through `bindServer`, so
64
+ * the request/reply/error pump those factories used to hand-roll identically now lives once
65
+ * in the core binder. {@link import('@orkestrel/mcp').createDuplexClientTransport} adapts the
66
+ * same contracts the other way.
67
67
  *
68
68
  * @remarks
69
69
  * `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}
70
70
  * and writes it through `transport.send` (the same `JSON.stringify` the underlying
71
71
  * transport already performs, so the wire bytes are unchanged). `listen` filters
72
- * `transport`'s `message` event to INVOCATIONS ONLY — requests and notifications, never a
72
+ * `transport`'s `message` event to invocations only — requests and notifications, never a
73
73
  * stray response, exactly as the prior hand-rolled pumps did — and re-serializes each one
74
74
  * back to a string for `bindServer`. `closed` bridges `transport`'s `close` event. `close`
75
75
  * closes the underlying `transport`.
76
76
  *
77
- * @remarks A message crossing this bridge is decoded and re-encoded TWICE, and that is
78
- * ACCEPTED rather than accidental. Inbound: the carrier already parsed the frame into a
77
+ * @remarks A message crossing this bridge is decoded and re-encoded twice, and that is
78
+ * accepted rather than accidental. Inbound: the carrier already parsed the frame into a
79
79
  * {@link JSONRPCMessage}, and `listen` re-serializes it so `bindServer` can decode it again
80
80
  * under the server's own `limit`. Outbound: `bindServer` serialized the reply, `send` parses
81
81
  * it back, and the carrier stringifies it once more. The cost is two extra `JSON.parse` /
82
- * `JSON.stringify` round trips per message, paid to keep ONE pump in the core binder instead
83
- * of a hand-rolled one per carrier. It is BOUNDED rather than unbounded because the binder
82
+ * `JSON.stringify` round trips per message, paid to keep one pump in the core binder instead
83
+ * of a hand-rolled one per carrier. It is bounded rather than unbounded because the binder
84
84
  * decodes within `server.limit.message`, so an oversized frame is refused before the second
85
85
  * decode rather than after it. Removing the cost means giving `MCPTransportInterface` a
86
86
  * message-shaped face beside its string one, which every transport would then carry.
87
87
  *
88
88
  * @remarks Per {@link import('@orkestrel/mcp').MCPTransportInterface}, `listen`/`closed`
89
- * each hold THE SINGLE current handler (a second call REPLACES the first, never adds).
89
+ * each hold the single current handler (a second call replaces the first, never adds).
90
90
  * Because the underlying `transport.emitter` is ADD-based (`on` subscribes, never
91
- * replaces), this bridge installs ONE stable emitter listener per event on first use
91
+ * replaces), this bridge installs one stable emitter listener per event on first use
92
92
  * and re-routes it to whichever handler is active (`undefined` while
93
93
  * none is), so rebinding never double-dispatches.
94
94
  *
@@ -105,47 +105,38 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
105
105
  * import { bindServer } from '@orkestrel/mcp'
106
106
  *
107
107
  * const transport = new StdioServerTransport(process.stdin, process.stdout)
108
- * bindServer(mcp, bridgeMessageTransport(transport))
108
+ * bindServer(mcp, createDuplexServerTransport(transport))
109
109
  * ```
110
110
  */
111
- export declare function bridgeMessageTransport(transport: MCPClientTransportInterface): MCPTransportInterface;
111
+ export declare function createDuplexServerTransport(transport: MCPMessageTransportInterface): MCPTransportInterface;
112
112
 
113
113
  /**
114
- * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
115
- *
116
- * @param response - The response whose status is reported
117
- * @param type - The response's content type, or an empty string when absent
118
- * @returns An error naming the HTTP status and unsupported response shape
119
- *
120
- * @example
121
- * ```ts
122
- * const error = buildResponseError(new Response('', { status: 500 }), '')
123
- * ```
124
- */
125
- export declare function buildResponseError(response: Response, type: string): Error;
126
-
127
- /**
128
- * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
129
- * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
114
+ * Creates the HTTP client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
115
+ * — a {@link MCPMessageTransportInterface} that drives a remote Streamable-HTTP MCP server
130
116
  * over `fetch`. The egress mirror of {@link createMCPRoutes}.
131
117
  *
132
118
  * @remarks
119
+ * It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
120
+ * browser face's `createHTTPClientTransport` returns, because the class touches `fetch`,
121
+ * `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone.
122
+ *
123
+ * @remarks
133
124
  * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is
134
125
  * `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of
135
- * both `application/json` and `text/event-stream` (the server answers with EITHER — a
126
+ * both `application/json` and `text/event-stream` (the server answers with either — a
136
127
  * plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded with `@orkestrel/sse`),
137
128
  * and the reply is surfaced on the transport's `message` event for the client's id
138
129
  * correlation. Add `options.headers` (for example, an `Authorization` bearer) to reach a guarded
139
- * server. `start` / `close` hold no connection; against a STATEFUL server it captures the
130
+ * server. `start` / `close` hold no connection; against a stateful server it captures the
140
131
  * `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
141
132
  * the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
142
133
  * subsequent legacy request. Modern requests derive protocol and method headers directly
143
134
  * from the message, plus a name header only for `tools/call`.
144
135
  *
145
- * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
136
+ * @param options - `url` (the remote endpoint; required), optional `headers` merged onto
146
137
  * every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
147
138
  * (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}
148
- * @returns A working {@link MCPClientTransportInterface} over `fetch`
139
+ * @returns A working {@link MCPMessageTransportInterface} over `fetch`
149
140
  *
150
141
  * @example
151
142
  * ```ts
@@ -159,7 +150,7 @@ export declare function buildResponseError(response: Response, type: string): Er
159
150
  * const tools = await client.tools()
160
151
  * ```
161
152
  */
162
- export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface;
153
+ export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface;
163
154
 
164
155
  /**
165
156
  * Adapts the installed server token primitives to the host-neutral MCP continuation port.
@@ -177,7 +168,7 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
177
168
  * method carrying a named target — `tools/call` and `prompts/get` against `params.name`,
178
169
  * `resources/read` against `params.uri` — with a Base64-sentinel value decoded before the
179
170
  * comparison; a missing, mismatched, or invalidly encoded value returns HTTP `400` + `-32020`.
180
- * A protocol header naming a MODERN revision holds the request to that revision whatever shape
171
+ * A protocol header naming a modern revision holds the request to that revision whatever shape
181
172
  * its body arrived in, so a body with no parsable modern `_meta` returns HTTP `400` + `-32602`.
182
173
  * Headerless `initialize` is accepted, while every other headerless request needs a live legacy
183
174
  * session to supply its pinned version. A legacy-shaped request carrying a protocol header is
@@ -219,14 +210,14 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
219
210
  * hand to `router.add(...)`.
220
211
  *
221
212
  * @remarks
222
- * A SINGLE `POST {path}` route — `createMCPRoutes` is STATELESS. The handler reads its own
213
+ * A single `POST {path}` route — `createMCPRoutes` is stateless. The handler reads its own
223
214
  * request body (its own JSON parse try/catch), so it works with or without a session
224
215
  * middleware mounted in front. It draws a sharp line between TRANSPORT-level and
225
216
  * DISPATCH-level outcomes:
226
217
  *
227
218
  * - A **transport** failure — a malformed JSON body, or a parsed value that is not a
228
- * JSON-RPC INVOCATION — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse
229
- * error / `-32600` Invalid Request), with the `id` it could not read OMITTED.
219
+ * JSON-RPC invocation — is an HTTP `400` carrying a JSON-RPC error body (`-32700` Parse
220
+ * error / `-32600` Invalid Request), with the `id` it could not read omitted.
230
221
  * - Modern protocol/method/name headers are validated against the body; a mismatch is
231
222
  * HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies
232
223
  * its pinned revision, and every other headerless request is rejected.
@@ -238,16 +229,16 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
238
229
  * When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,
239
230
  * the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying
240
231
  * the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic
241
- * {@link import('@orkestrel/server').openStream} seam; otherwise it is a plain JSON body.
232
+ * {@link import('@orkestrel/server').createStream} seam; otherwise it is a plain JSON body.
242
233
  *
243
- * **Sessions are a SEPARATE, plug-and-play middleware.** `createMCPRoutes` mints / reads no
244
- * session id. To make the transport STATEFUL, mount {@link
245
- * import('./middlewares.js').createMCPSession} IN FRONT — it owns the same `path`, mints +
234
+ * **Sessions are a separate, plug-and-play middleware.** `createMCPRoutes` mints / reads no
235
+ * session id. To make the transport stateful, mount {@link
236
+ * import('./middlewares.js').createMCPSession} in front — it owns the same `path`, mints +
246
237
  * validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,
247
238
  * leaving this route to dispatch the validated `POST`.
248
239
  *
249
- * This is MECHANISM, not policy: compose auth / rate-limiting (and the session middleware)
250
- * IN FRONT as ordinary middleware; the optional `origin` group carries the deployment's shared
240
+ * This is mechanism, not policy: compose auth / rate-limiting (and the session middleware)
241
+ * in front as ordinary middleware; the optional `origin` group carries the deployment's shared
251
242
  * allowlist or explicitly delegates validation to an upstream layer.
252
243
  *
253
244
  * @typeParam TState - The consumer's opaque per-request state type
@@ -273,7 +264,7 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
273
264
  * Creates the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
274
265
  * that fronts a session-agnostic {@link import('./factories.js').createMCPRoutes}. Compose it
275
266
  * with `router.use(createMCPSession())` (or the equivalent middleware seam), mirroring any
276
- * other closure-scoped stateful middleware. Has NO dependency on `@orkestrel/middleware` — the
267
+ * other closure-scoped stateful middleware. Has no dependency on `@orkestrel/middleware` — the
277
268
  * session store, mint-on-`initialize`, and resumable stream are all native to this package.
278
269
  *
279
270
  * @remarks
@@ -286,39 +277,41 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
286
277
  *
287
278
  * - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route
288
279
  * can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link
289
- * readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an
290
- * ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link
291
- * isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)
292
- * and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The
280
+ * readSessionHeader}: a valid id touches the entry and sets `context.state.session`; an
281
+ * absent / unknown id whose (guarded) body parses to an `initialize` request ({@link
282
+ * isInitializeRequest}) mints a fresh {@link MCPSession} (`crypto.randomUUID()`, the `session`
283
+ * options group) and sets `context.state.session`; neither → {@link rejectUnknownSession}
284
+ * (`404`). The
293
285
  * minted entry pins the negotiated legacy revision, which is supplied to a later headerless
294
286
  * live-session request. It then
295
- * FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
287
+ * forwards a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
296
288
  * already-consumed original — so the route re-reads the same body, and stamps the response
297
- * with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read AFTER that
298
- * downstream response, because it means the LAST ACCESS: a request slower than `ttl` would
289
+ * with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read after that
290
+ * downstream response, because it means the last access: a request slower than `ttl` would
299
291
  * otherwise store a session that is already expired, and the write-back RE-ASKS the store, so
300
292
  * a `DELETE` arriving while the request was suspended is not undone.
301
293
  * - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);
302
294
  * an invalid / unknown id is the same `404`. A valid session opens the resumable
303
- * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:
304
- * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE
295
+ * server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').createStream}:
296
+ * replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) before
305
297
  * attaching the stream for live pushes, then attaches; cancellation of the streamed response
306
298
  * body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
307
299
  * - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers
308
300
  * `204`; an invalid / unknown id is the same `404`.
309
301
  *
310
- * It is MECHANISM, not policy, and ADDITIVE: omit it entirely for the stateless default
302
+ * It is mechanism, not policy, and additive: omit it entirely for the stateless default
311
303
  * ({@link import('./factories.js').createMCPRoutes}'s only behavior). The `path` MUST match the
312
304
  * `createMCPRoutes` `path` it fronts. The WebSocket transport is inherently one session per
313
- * connection (the socket IS the session), so this middleware does not apply to it.
305
+ * connection (the socket is the session), so this middleware does not apply to it.
314
306
  *
315
307
  * @typeParam TState - The consumer's `TState`, which MUST extend {@link MCPSessionState} so
316
308
  * the resolved session can be threaded through `context.state.session`
317
309
  * @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session
318
- * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`
319
- * (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;
320
- * defaults to `Date.now`), plus the shared `origin` validation options; see
321
- * {@link MCPSessionOptions}
310
+ * sweep window, ms — omit for sessions that live until an explicit `DELETE`), `session`
311
+ * (the knobs each minted {@link MCPSession} takes — `capacity`, the log's own `ttl`, and its
312
+ * `clock`), and `clock` (the deterministic epoch-ms clock this middleware keeps its own
313
+ * bookkeeping on and hands down to a session that names none; defaults to `Date.now`), plus
314
+ * the shared `origin` validation options; see {@link MCPSessionMiddlewareOptions}
322
315
  * @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable
323
316
  * `GET` / `DELETE`
324
317
  *
@@ -334,20 +327,11 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
334
327
  * router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only
335
328
  * ```
336
329
  */
337
- export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionOptions): MiddlewareHandler<TState>;
338
-
339
- /**
340
- * Creates a readable stream from its pull and cancellation behaviours.
341
- *
342
- * @param pull - The behaviour that supplies the stream's next chunk
343
- * @param cancel - The behaviour that releases the stream after consumer cancellation
344
- * @returns A readable stream backed by the supplied behaviours
345
- */
346
- export declare function createReadableStream<T>(pull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>, cancel: (reason?: unknown) => void | PromiseLike<void>): ReadableStream<T>;
330
+ export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionMiddlewareOptions): MiddlewareHandler<TState>;
347
331
 
348
332
  /**
349
- * Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
350
- * — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
333
+ * Creates the stdio client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
334
+ * — a {@link StdioClientTransportInterface} that spawns and drives a child process MCP server
351
335
  * over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
352
336
  * createHTTPClientTransport} and {@link createWebSocketClientTransport}.
353
337
  *
@@ -364,7 +348,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
364
348
  * waits before the `send` rejects. An omitted `delivery` selects {@link
365
349
  * import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.
366
350
  *
367
- * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
351
+ * @param options - `command` (the executable to spawn; required), optional `args`,
368
352
  * optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
369
353
  * `stdin` write; see {@link StdioClientTransportOptions}
370
354
  * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
@@ -385,7 +369,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
385
369
  export declare function createStdioClientTransport(options: StdioClientTransportOptions): StdioClientTransportInterface;
386
370
 
387
371
  /**
388
- * Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link
372
+ * Creates the MCP stdio transport ingress — pumps a transport-agnostic {@link
389
373
  * MCPDispatcherInterface} over newline-delimited JSON-RPC on `stdin`/`stdout` (or an
390
374
  * injected stream pair), the stdio mirror of {@link createWebSocketServer}.
391
375
  *
@@ -393,9 +377,9 @@ export declare function createStdioClientTransport(options: StdioClientTransport
393
377
  * Wraps `options.input` (default `process.stdin`) / `options.output` (default
394
378
  * `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
395
379
  * and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port
396
- * through {@link import('./helpers.js').bridgeMessageTransport} + {@link
397
- * import('@orkestrel/mcp').bindServer}: each inbound REQUEST runs through `mcp.dispatch`, and
398
- * a defined response is written back as a newline-terminated line — a NOTIFICATION
380
+ * through {@link createDuplexServerTransport} + {@link
381
+ * import('@orkestrel/mcp').bindServer}: each inbound request runs through `mcp.dispatch`, and
382
+ * a defined response is written back as a newline-terminated line — a notification
399
383
  * writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault
400
384
  * surfaces on `mcp.emitter`'s `error` event rather than escaping the (async) message
401
385
  * pump.
@@ -420,8 +404,8 @@ export declare function createStdioClientTransport(options: StdioClientTransport
420
404
  export declare function createStdioServer(mcp: MCPDispatcherInterface, options?: StdioServerOptions): StdioServerInterface;
421
405
 
422
406
  /**
423
- * Creates the WebSocket CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
424
- * — a {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket. The
407
+ * Creates the WebSocket client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
408
+ * — a {@link MCPMessageTransportInterface} that drives a remote MCP server over a WebSocket. The
425
409
  * egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link
426
410
  * createHTTPClientTransport}.
427
411
  *
@@ -435,9 +419,9 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
435
419
  * surfaced on the transport's `message` event for the client's id correlation. Add
436
420
  * `options.headers` (for example, an `Authorization` bearer) to reach a guarded server.
437
421
  *
438
- * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional `headers`
422
+ * @param options - `url` (the remote WebSocket endpoint; required) and optional `headers`
439
423
  * merged onto the upgrade request; see {@link WebSocketClientTransportOptions}
440
- * @returns A working {@link MCPClientTransportInterface} over a WebSocket
424
+ * @returns A working {@link MCPMessageTransportInterface} over a WebSocket
441
425
  *
442
426
  * @example
443
427
  * ```ts
@@ -451,10 +435,10 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
451
435
  * const tools = await client.tools()
452
436
  * ```
453
437
  */
454
- export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface;
438
+ export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface;
455
439
 
456
440
  /**
457
- * Creates the MCP WebSocket transport INGRESS — an {@link UpgradeHandler} that exposes a
441
+ * Creates the MCP WebSocket transport ingress — an {@link UpgradeHandler} that exposes a
458
442
  * transport-agnostic {@link MCPDispatcherInterface} over a WebSocket, the WebSocket mirror of
459
443
  * {@link createMCPRoutes}. Register it on the spine's upgrade seam.
460
444
  *
@@ -466,16 +450,16 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
466
450
  * socket to the next handler (or destroys an unclaimed one): the `Upgrade` header is not
467
451
  * `websocket`, the request path is not `options.path` (default {@link DEFAULT_MCP_PATH},
468
452
  * `'/mcp'`), the `Sec-WebSocket-Key` is absent, or the `Sec-WebSocket-Version` is not `13`.
469
- * A decline NEVER writes to the socket (it is not yet ours) — the spine owns the unclaimed
453
+ * A decline never writes to the socket (it is not yet ours) — the spine owns the unclaimed
470
454
  * outcome.
471
455
  * - **Claims (returns `true`)** otherwise: it builds `createNodeWebSocket({ socket, key, head,
472
456
  * protocol })` (SERVER mode → writes the `101` handshake, selects the configured subprotocol
473
- * only when the client's offer contains it, and sends UNMASKED frames), wraps it in a
457
+ * only when the client's offer contains it, and sends unmasked frames), wraps it in a
474
458
  * {@link WebSocketServerTransport}, and pipes it through the core {@link
475
459
  * import('@orkestrel/mcp').MCPTransportInterface} port through {@link
476
- * import('./helpers.js').bridgeMessageTransport} + {@link import('@orkestrel/mcp').bindServer}:
477
- * each inbound REQUEST runs through `mcp.dispatch`, and a defined response is written back
478
- * as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
460
+ * createDuplexServerTransport} + {@link import('@orkestrel/mcp').bindServer}:
461
+ * each inbound request runs through `mcp.dispatch`, and a defined response is written back
462
+ * as a frame — a notification sends nothing, and a non-request message (a stray response) is
479
463
  * ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
480
464
  * escaping the (async) message pump.
481
465
  * - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
@@ -486,12 +470,12 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
486
470
  * then have the connection cut mid-protocol. A socket the peer already dropped is gone from
487
471
  * the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
488
472
  *
489
- * It is MECHANISM, not policy: compose an auth guard IN FRONT by registering an upgrade
490
- * handler BEFORE this one — that handler can claim (decline + destroy) an unauthenticated
473
+ * It is mechanism, not policy: compose an auth guard in front by registering an upgrade
474
+ * handler before this one — that handler can claim (decline + destroy) an unauthenticated
491
475
  * upgrade so it never reaches this pump.
492
476
  *
493
477
  * @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
494
- * @param options - The spine's `emitter` (REQUIRED — the `stop` event this ingress closes its
478
+ * @param options - The spine's `emitter` (required — the `stop` event this ingress closes its
495
479
  * sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
496
480
  * (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
497
481
  * @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
@@ -510,26 +494,12 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
510
494
  export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
511
495
 
512
496
  /**
513
- * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
514
- * when it is not one — the per-event step {@link readEventStream} folds over.
515
- *
516
- * @remarks
517
- * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's
518
- * `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.
519
- * Total: malformed JSON or a non-message value yields `undefined`, never throws.
520
- *
521
- * @param data - One SSE event's `data` payload
522
- * @returns The decoded {@link JSONRPCMessage}, or `undefined`
523
- */
524
- export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
525
-
526
- /**
527
- * The default bound in milliseconds on one unconfirmed write to a stdio client transport's
497
+ * Sets the default bound in milliseconds on one unconfirmed write to a stdio client transport's
528
498
  * child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
529
499
  *
530
500
  * @remarks
531
501
  * Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays
532
- * BELOW {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
502
+ * below {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
533
503
  * reads fails as an undeliverable message while the request that carried it is still open,
534
504
  * rather than being masked by that request's own deadline expiring first. Override per
535
505
  * transport with `delivery`; an explicit `0` there removes the bound.
@@ -537,7 +507,7 @@ export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
537
507
  export declare const DEFAULT_MCP_DELIVERY = 10000;
538
508
 
539
509
  /**
540
- * The default interval in milliseconds between SSE keepalive comments on held-open MCP
510
+ * Sets the default interval in milliseconds between SSE keepalive comments on held-open MCP
541
511
  * responses.
542
512
  *
543
513
  * @remarks
@@ -546,24 +516,24 @@ export declare const DEFAULT_MCP_DELIVERY = 10000;
546
516
  */
547
517
  export declare const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15000;
548
518
 
549
- /** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */
519
+ /** Names the default request path `createMCPRoutes` mounts the transport's `POST` route at. */
550
520
  export declare const DEFAULT_MCP_PATH = "/mcp";
551
521
 
552
522
  /**
553
- * The default capacity of a session's FOLDED resumable event log (the per-{@link
523
+ * Sets the default capacity of a session's folded resumable event log (the per-{@link
554
524
  * import('./MCPSession.js').MCPSession} replay log) — the maximum number of pushed
555
- * server→client messages retained for replay before the OLDEST is evicted.
525
+ * server→client messages retained for replay before the oldest is evicted.
556
526
  *
557
527
  * @remarks
558
528
  * Bounds the replay log's memory: only the most-recent {@link DEFAULT_MCP_SESSION_CAPACITY}
559
529
  * pushes are retained, so a client reconnecting with a `Last-Event-ID` older than that window
560
- * replays nothing (its cursor fell off the back). Override per `createMCPSession`'s `capacity`
561
- * for a deeper / shallower window.
530
+ * replays nothing (its cursor fell off the back). Override through the `session` group of
531
+ * `createMCPSession`'s options (`session.capacity`) for a deeper / shallower window.
562
532
  */
563
533
  export declare const DEFAULT_MCP_SESSION_CAPACITY = 1024;
564
534
 
565
535
  /**
566
- * The default per-event idle lifetime (ms) of a session's folded resumable event log — an
536
+ * Sets the default per-event idle lifetime (ms) of a session's folded resumable event log — an
567
537
  * entry older than this is lazily evicted on the next access (no background timer), bounding
568
538
  * how far back a reconnecting client may replay.
569
539
  *
@@ -576,41 +546,22 @@ export declare const DEFAULT_MCP_SESSION_TTL = 300000;
576
546
 
577
547
  /**
578
548
  * Decodes and delivers each complete newline-framed line onto a {@link
579
- * MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
549
+ * MCPMessageTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
580
550
  * transports run their framed lines through: the server transport frames with {@link
581
551
  * extractLines}, the client transport takes its lines from the process supervisor.
582
552
  *
583
553
  * @remarks
584
- * A blank line is skipped (a stray trailing newline). Every other line is decoded
585
- * with {@link decodeEvent} (`JSON.parse` + `parseJSONRPCMessage`, guarded); a
586
- * well-formed {@link JSONRPCMessage} emits `message`, a malformed / non-message line
587
- * emits `error` (total, never throws). Pure w.r.t. its own state the emit is
588
- * the caller-owned side effect.
554
+ * A blank line is skipped (a stray trailing newline). Every other line runs through the
555
+ * shared {@link import('@orkestrel/mcp').deliverMessage} fold, the one inbound decode every
556
+ * transport in this package shares: a well-formed {@link JSONRPCMessage} emits `message`,
557
+ * unparsable text emits the caught parse error, and a well-formed non-message line emits
558
+ * `error` naming a non-JSON-RPC stdio line (total, never throws). Pure w.r.t. its own state
559
+ * — the emit is the caller-owned side effect.
589
560
  *
590
561
  * @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto
591
562
  * @param lines - The complete lines to decode and deliver
592
563
  */
593
- export declare function dispatchLines(emitter: EmitterInterface<MCPClientTransportEventMap>, lines: readonly string[]): void;
594
-
595
- /**
596
- * One entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed {@link
597
- * JSONRPCMessage} tagged with the monotone event `id` the session assigned and the `timestamp`
598
- * it was appended at (for the lazy-TTL replay window).
599
- *
600
- * @remarks
601
- * - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
602
- * value a resumable client echoes back as its `Last-Event-ID` to replay from here.
603
- * - `message` — the server→client {@link JSONRPCMessage} that was pushed.
604
- * - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
605
- *
606
- * A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
607
- * returns.
608
- */
609
- export declare interface EventStoreEntry {
610
- readonly id: string;
611
- readonly message: JSONRPCMessage;
612
- readonly timestamp: number;
613
- }
564
+ export declare function dispatchLines(emitter: EmitterInterface<MCPMessageTransportEventMap>, lines: readonly string[]): void;
614
565
 
615
566
  /**
616
567
  * Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared
@@ -619,7 +570,7 @@ export declare interface EventStoreEntry {
619
570
  *
620
571
  * @remarks
621
572
  * Concatenates `buffer` (the carried-forward partial line from the previous call)
622
- * with `chunk`, splits on `'\n'`, and returns every COMPLETE line (a `'\r'` trailing
573
+ * with `chunk`, splits on `'\n'`, and returns every complete line (a `'\r'` trailing
623
574
  * a line, from a CRLF-framed peer, is trimmed) plus the final, possibly-empty
624
575
  * fragment as the new `remainder` — the caller threads it back in as the next call's
625
576
  * `buffer`. A chunk containing no `'\n'` yields no lines and the whole (buffer +
@@ -631,118 +582,26 @@ export declare interface EventStoreEntry {
631
582
  */
632
583
  export declare function extractLines(buffer: string, chunk: string): LineExtraction;
633
584
 
634
- /**
635
- * The HTTP CLIENT transport for the Model Context Protocol — a
636
- * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server over
637
- * `fetch`, the egress mirror of the server's `createMCPRoutes`.
638
- *
639
- * @remarks
640
- * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
641
- * message to `options.url` with `content-type: application/json` and an
642
- * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
643
- * answer with either framing) — plus any `options.headers` (for example, an `Authorization`
644
- * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
645
- * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
646
- * to.
647
- * - **Both reply framings.** A `200` with an `application/json` body is parsed with
648
- * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
649
- * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
650
- * readEventStream}) — the inverse of the server's `openStream` seam, so the wire
651
- * round-trips. A `202`
652
- * Accepted (a notification) carries no body and emits nothing.
653
- * - **Session and protocol headers.** `start()` is a no-op (a
654
- * request/response transport opens no long-lived connection). The
655
- * `mcp-session-id` response header, when a STATEFUL server sends one (on
656
- * `initialize`), is captured into `session` and then ECHOED as the
657
- * `mcp-session-id` request header on every SUBSEQUENT request — so an
658
- * `MCPClient` passes a stateful server's session validation. The
659
- * initialize result's `protocolVersion` is likewise captured, but only
660
- * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
661
- * subsequent legacy requests. Modern requests instead derive protocol and method
662
- * headers from the message, plus the name header only for `tools/call` — carried in the
663
- * protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
664
- * Before initialize returns, neither captured legacy header is sent.
665
- * `close()` clears the captured protocol so a reconnect's `initialize`
666
- * POST is headerless; the captured `session` persists across `close()`.
667
- * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
668
- * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
669
- * never ends would otherwise outlive the transport, with nothing left able to reach it. The
670
- * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
671
- * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
672
- * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
673
- * the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
674
- * reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
675
- * shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
676
- * on a success response surfaces on the `error` event rather than escaping `send`.
677
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
678
- * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
679
- *
680
- * @example
681
- * ```ts
682
- * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
683
- * const client = new MCPClient({ transport })
684
- * await client.connect()
685
- * ```
686
- */
687
- export declare class HTTPClientTransport implements MCPClientTransportInterface_2 {
688
- #private;
689
- constructor(options: HTTPClientTransportOptions);
690
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
691
- get session(): string | undefined;
692
- get duplex(): boolean;
693
- start(): Promise<void>;
694
- send(message: JSONRPCMessage_2): Promise<void>;
695
- close(): Promise<void>;
696
- }
697
-
698
- /**
699
- * Options for `createHTTPClientTransport` — the remote MCP server's URL and any extra
700
- * request headers.
701
- *
702
- * @remarks
703
- * - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
704
- * `POST` target every JSON-RPC message is written to, for example,
705
- * `http://localhost:3000/mcp`). REQUIRED.
706
- * - `headers` — extra request headers merged onto every `POST` (for example, an
707
- * `Authorization` bearer for a guarded server). The transport always sets
708
- * `content-type: application/json` and an `Accept` of both `application/json` and
709
- * `text/event-stream` (so the server may answer with either framing); a key supplied
710
- * here is merged on top.
711
- * - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
712
- * `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
713
- * - `timeout` — an optional per-request timeout in milliseconds; when set, each
714
- * `fetch` call composes that deadline with the transport's own close through
715
- * `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
716
- * ends the request. Omit for no transport-level deadline; the close signal is passed
717
- * either way.
718
- */
719
- export declare interface HTTPClientTransportOptions {
720
- readonly url: string;
721
- readonly headers?: Readonly<Record<string, string>>;
722
- readonly fetch?: typeof fetch;
723
- readonly timeout?: number;
724
- }
725
-
726
585
  /**
727
586
  * Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.
728
587
  *
729
588
  * @remarks
730
- * The composed {@link signal} observes request abort and EVERY way this response can end
589
+ * The composed {@link signal} observes request abort and every way this response can end
731
590
  * without one: consumer cancellation of the bridged body, a forwarding failure mid-pump, and a
732
591
  * keepalive tick that finds the SSE stream already closed. That last pair is the whole point of
733
592
  * the composition — a client that vanishes mid-stream aborts nothing by itself, so unless this
734
593
  * object raises the signal on its own failure paths, the handler, the controlled stream, and
735
594
  * the producer behind them all keep running for a response that can no longer be written.
736
- * Graceful upstream completion is the one terminal that does NOT abort: the body simply closes,
595
+ * Graceful upstream completion is the one terminal that does not abort: the body closes,
737
596
  * because the exchange finished rather than ended.
738
597
  *
739
598
  * {@link bridge} preserves the source response status and headers, forwards its body bytes, and
740
599
  * owns keepalive comments plus listener/timer cleanup until upstream completion, request abort,
741
600
  * or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge:
742
- * a second {@link bridge} call THROWS rather than arming a second keepalive over one lifecycle.
601
+ * a second {@link bridge} call throws rather than arming a second keepalive over one lifecycle.
743
602
  * It supplies no handler or session policy.
744
603
  *
745
- * The keepalive interval is a BUDGET, sanitized like every other numeric knob in this package:
604
+ * The keepalive interval is a budget, sanitized like every other numeric knob in this package:
746
605
  * anything that is not a positive integer — `0`, a negative, a fractional value, `NaN`,
747
606
  * `Infinity` — falls back to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and a larger value clamps
748
607
  * to Node's `2_147_483_647` ms timer maximum. None may reach the platform's timer floor, where
@@ -751,10 +610,10 @@ export declare interface HTTPClientTransportOptions {
751
610
  * @example
752
611
  * ```ts
753
612
  * import { HTTPDisconnect } from '@orkestrel/mcp/server'
754
- * import { openStream } from '@orkestrel/server'
613
+ * import { createStream } from '@orkestrel/server'
755
614
  *
756
615
  * const disconnect = new HTTPDisconnect(request.signal, { interval: 15_000 })
757
- * const stream = openStream()
616
+ * const stream = createStream()
758
617
  * const response = disconnect.bridge(stream)
759
618
  * ```
760
619
  */
@@ -769,8 +628,8 @@ export declare class HTTPDisconnect {
769
628
  */
770
629
  constructor(signal: AbortSignal, options?: MCPKeepaliveOptions);
771
630
  /**
772
- * The signal aborted by the incoming request, or by any end of this response that is not
773
- * its graceful completion.
631
+ * Returns the signal aborted by the incoming request, or by any end of this response that
632
+ * is not its graceful completion.
774
633
  *
775
634
  * @returns The composed lifecycle signal
776
635
  */
@@ -795,11 +654,11 @@ export declare class HTTPDisconnect {
795
654
  * Options shared by the MCP Streamable-HTTP POST handler and route factory.
796
655
  *
797
656
  * @remarks
798
- * - `streaming` — when `true` (the DEFAULT) the transport MAY answer with a
657
+ * - `streaming` — when `true` (the default) the transport MAY answer with a
799
658
  * Server-Sent-Events response (one `data:` event carrying the JSON-RPC reply, then
800
659
  * the stream ends) whenever the client's `Accept` header includes
801
660
  * `text/event-stream`; when `false` it always answers with a plain JSON body. Either
802
- * mode carries the SAME JSON-RPC response envelope — the choice is purely the wire
661
+ * mode carries the same JSON-RPC response envelope — the choice is purely the wire
803
662
  * framing the Streamable-HTTP spec lets the client negotiate.
804
663
  * - `origin` — the shared origin-validation options passed to both the route and session
805
664
  * enforcement sites. Validation is enabled by default: requests without `Origin` pass,
@@ -816,7 +675,7 @@ export declare class HTTPDisconnect {
816
675
  */
817
676
  export declare interface HTTPHandlerOptions<TState = unknown> {
818
677
  readonly streaming?: boolean;
819
- /** Must match the session layer's value; `origins` is ignored when `enabled` is `false`. */
678
+ /** Requires the session layer's value; `origins` is ignored when `enabled` is `false`. */
820
679
  readonly origin?: MCPOriginOptions;
821
680
  readonly keepalive?: MCPKeepaliveOptions;
822
681
  readonly caller?: MCPCallerHandler<TState>;
@@ -824,7 +683,7 @@ export declare interface HTTPHandlerOptions<TState = unknown> {
824
683
 
825
684
  /**
826
685
  * Options for `createMCPRoutes` — the mount path plus the shared POST-handler options.
827
- * `createMCPRoutes` is STATELESS; sessions are a separate middleware ({@link
686
+ * `createMCPRoutes` is stateless; sessions are a separate middleware ({@link
828
687
  * import('./middlewares.js').createMCPSession}), composed with `server.use`.
829
688
  *
830
689
  * @remarks
@@ -839,7 +698,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
839
698
  }
840
699
 
841
700
  /**
842
- * Infers the first required MCP HTTP header that is missing or mismatched.
701
+ * Infers the first required MCP HTTP header a request's own body contradicts.
843
702
  *
844
703
  * @remarks
845
704
  * A modern request derives its protocol, method, and name expectations from the JSON-RPC body,
@@ -848,12 +707,14 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
848
707
  * {@link import('@orkestrel/mcp').decodeSentinel} before the comparison, so a peer that had
849
708
  * to encode its value still matches; a sentinel whose payload is invalid decodes to nothing
850
709
  * and therefore mismatches, which is how an invalid header value is refused. A legacy request
851
- * body requires a protocol header after initialization, while a supplied legacy session
852
- * version additionally diagnoses a header that disagrees with the active session. Messages
853
- * name the expected value but never echo the client-supplied one.
710
+ * body requires a protocol header after initialization. Messages name the expected value but
711
+ * never echo the client-supplied one.
712
+ *
713
+ * The expectation a live session supplies is a different rule over a different input, so it
714
+ * is {@link inferSessionHeaderIssue} rather than a second arm of this one.
854
715
  *
855
716
  * @param request - The HTTP request carrying the headers
856
- * @param reference - The parsed invocation body, or the active legacy session version
717
+ * @param invocation - The parsed invocation body the expectations are derived from
857
718
  * @returns The first header issue, or `undefined` when the applicable headers agree
858
719
  *
859
720
  * @example
@@ -862,7 +723,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
862
723
  * issue?.header // 'Mcp-Method' when that field is absent or mismatched
863
724
  * ```
864
725
  */
865
- export declare function inferHeaderIssue(request: Request, reference: JSONRPCInvocation | MCPVersion): MCPHeaderIssue | undefined;
726
+ export declare function inferHeaderIssue(request: Request, invocation: JSONRPCInvocation): MCPHeaderIssue | undefined;
866
727
 
867
728
  /**
868
729
  * Infers the target one modern request's `Mcp-Name` header must carry.
@@ -894,7 +755,7 @@ export declare function inferHeaderTarget(request: JSONRPCInvocation): string |
894
755
  *
895
756
  * @remarks
896
757
  * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
897
- * request selects the newest supported legacy revision. The read is deliberately the SAME one
758
+ * request selects the newest supported legacy revision. The read is deliberately the same one
898
759
  * {@link import('@orkestrel/mcp').buildInitializeResult} performs — `isMCPLegacyVersion` over
899
760
  * the requested revision — because the session version this pins and the version that result
900
761
  * echoes must be the one value. Routing through `inferVersion` cannot do it: that inferer is
@@ -912,8 +773,8 @@ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegac
912
773
  *
913
774
  * @remarks
914
775
  * The custom-header half of the standard-header seam {@link inferHeaderIssue} owns, and it
915
- * takes the SERVED definition's projections rather than a header issue: SEP-2243 scopes the
916
- * rule to the `Mcp-Param-*` names the server's OWN tool definitions annotate, so a name no
776
+ * takes the served definition's projections rather than a header issue: SEP-2243 scopes the
777
+ * rule to the `Mcp-Param-*` names the server's own tool definitions annotate, so a name no
917
778
  * parameter claims is another party's header and travels through untouched.
918
779
  *
919
780
  * For each recognized parameter the body's value at the parameter's own property path fixes
@@ -942,6 +803,29 @@ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegac
942
803
  */
943
804
  export declare function inferParameterRefusal(request: Request, parameters: readonly MCPHeaderParameter[], values: unknown): string | undefined;
944
805
 
806
+ /**
807
+ * Infers the protocol header issue an active legacy session's pinned revision diagnoses.
808
+ *
809
+ * @remarks
810
+ * The session layer's rule, distinct from the body-derived one {@link inferHeaderIssue} owns:
811
+ * a live legacy session pinned its revision at `initialize`, so every later request on that
812
+ * session must name the same one. An absent header reads as `missing`, which the session
813
+ * middleware answers by supplying the pinned revision rather than refusing; a present header
814
+ * naming another revision reads as `mismatched` and is refused. The message names the session's
815
+ * revision and never echoes the client-supplied value.
816
+ *
817
+ * @param request - The HTTP request carrying the headers
818
+ * @param version - The legacy revision the active session pinned at `initialize`
819
+ * @returns The protocol header issue, or `undefined` when the header agrees
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * const issue = inferSessionHeaderIssue(request, '2025-06-18')
824
+ * issue?.reason // 'missing' when the request carries no protocol header
825
+ * ```
826
+ */
827
+ export declare function inferSessionHeaderIssue(request: Request, version: MCPVersion): MCPHeaderIssue | undefined;
828
+
945
829
  /**
946
830
  * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
947
831
  *
@@ -958,8 +842,8 @@ export declare function inferParameterRefusal(request: Request, parameters: read
958
842
  export declare function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number;
959
843
 
960
844
  /**
961
- * The result of folding one more chunk of raw stdio bytes into a newline-framed
962
- * buffer — every COMPLETE line extracted (newline-terminated in the wire bytes) plus
845
+ * Represents the result of folding one more chunk of raw stdio bytes into a newline-framed
846
+ * buffer — every complete line extracted (newline-terminated in the wire bytes) plus
963
847
  * the trailing partial line carried forward as the new `remainder`.
964
848
  *
965
849
  * @remarks
@@ -971,44 +855,6 @@ export declare interface LineExtraction {
971
855
  readonly remainder: string;
972
856
  }
973
857
 
974
- /** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
975
- export declare const MCP_METHOD_HEADER = "mcp-method";
976
-
977
- /** The modern Streamable-HTTP request header carrying a named method's target. */
978
- export declare const MCP_NAME_HEADER = "mcp-name";
979
-
980
- /**
981
- * The Streamable-HTTP transport header carrying the negotiated MCP protocol version
982
- * on every post-initialize client request.
983
- *
984
- * @remarks
985
- * Required by MCP 2025-06-18 after initialization. Both HTTP client transports
986
- * capture the initialize result's `protocolVersion` and send it on subsequent
987
- * requests; `createMCPRoutes` rejects a present unsupported value before dispatch.
988
- */
989
- export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
990
-
991
- /**
992
- * The Streamable-HTTP transport header that carries the MCP session id. When a {@link
993
- * import('./middlewares.js').createMCPSession} middleware is mounted, it SETS this header on
994
- * the `initialize` response (the minted id) and READS it on every subsequent request
995
- * (validating the session); the stateless `createMCPRoutes` default neither sets nor reads it.
996
- */
997
- export declare const MCP_SESSION_HEADER = "mcp-session-id";
998
-
999
- /**
1000
- * The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the
1001
- * client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.
1002
- *
1003
- * @remarks
1004
- * `createWebSocketServer` echoes it in the upgrade response and `createWebSocketClientTransport`
1005
- * requests it, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
1006
- * same path. The default WebSocket upgrade path is {@link DEFAULT_MCP_PATH} (the same `'/mcp'`
1007
- * the HTTP transport mounts at) — the upgrade is selected by the `Upgrade: websocket` header,
1008
- * not a separate path.
1009
- */
1010
- export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
1011
-
1012
858
  /**
1013
859
  * Extracts consumer-asserted caller context synchronously from an HTTP request after the
1014
860
  * transport has validated it for dispatch.
@@ -1027,7 +873,8 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
1027
873
  export declare type MCPCallerHandler<TState = unknown> = (request: Request, context: RouteContext<string, TState> | undefined) => unknown;
1028
874
 
1029
875
  /**
1030
- * One required MCP HTTP header that is absent or disagrees with its server-derived value.
876
+ * Reports one required MCP HTTP header that is absent or disagrees with its server-derived
877
+ * value.
1031
878
  *
1032
879
  * @remarks
1033
880
  * - `header` — the canonical HTTP field name safe to show to an integrator.
@@ -1042,7 +889,7 @@ export declare interface MCPHeaderIssue {
1042
889
  }
1043
890
 
1044
891
  /**
1045
- * Shared SSE keepalive options for held-open HTTP responses.
892
+ * Configures the shared SSE keepalive for held-open HTTP responses.
1046
893
  *
1047
894
  * @remarks
1048
895
  * - `interval` — milliseconds between SSE comment frames. Defaults to {@link
@@ -1055,7 +902,7 @@ export declare interface MCPKeepaliveOptions {
1055
902
  }
1056
903
 
1057
904
  /**
1058
- * Shared options for the protocol-required HTTP `Origin` validation at the route and session
905
+ * Configures the protocol-required HTTP `Origin` validation shared by the route and session
1059
906
  * enforcement sites.
1060
907
  *
1061
908
  * @remarks
@@ -1071,41 +918,41 @@ export declare interface MCPOriginOptions {
1071
918
  }
1072
919
 
1073
920
  /**
1074
- * One MCP transport session — the per-session entity a {@link
921
+ * Represents one MCP transport session — the per-session entity a {@link
1075
922
  * import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the
1076
- * resumable server→client push channel with its bounded replay log FOLDED IN.
923
+ * resumable server→client push channel with its bounded replay log folded in.
1077
924
  *
1078
925
  * @remarks
1079
- * The single session entity (the old `SessionState` + `EventStore` merged): it holds the
1080
- * session `id`, its OWN bounded, replayable log of pushed server→client messages (the
926
+ * One entity carries the whole session: it holds the
927
+ * session `id`, its own bounded, replayable log of pushed server→client messages (the
1081
928
  * resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with
1082
929
  * `capacity` / `ttl` eviction, not a separate store), and the set of open
1083
930
  * server→client SSE streams (a resumable `GET {path}` registers through `attach`, unregisters through
1084
931
  * `detach` on disconnect). Still a small entity (not a record), built minimal + extensible.
1085
932
  *
1086
- * - **`push` is the server-initiated primitive.** It APPENDS the message to the log (assigning
1087
- * a monotone base36 event id) and FANS it out to every attached stream as one `id:`-tagged
1088
- * SSE event (`stream.write({ id, data })`). A push with NO attached stream is still logged,
1089
- * so a client that connects (or reconnects with a `Last-Event-ID`) LATER replays it from the
933
+ * - **`push` is the server-initiated primitive.** It appends the message to the log (assigning
934
+ * a monotone base36 event id) and fans it out to every attached stream as one `id:`-tagged
935
+ * SSE event (`stream.write({ id, data })`). A push with no attached stream is still logged,
936
+ * so a client that connects (or reconnects with a `Last-Event-ID`) later replays it from the
1090
937
  * log. A `write` to a closed stream is a safe no-op (the {@link
1091
- * `@orkestrel/server`'s `openStream` contract), so a just-disconnected stream that
938
+ * `@orkestrel/server`'s `createStream` contract), so a just-disconnected stream that
1092
939
  * has not yet been `detach`ed never throws. A replayed event and the live one carry the
1093
- * IDENTICAL id (the log assigns it once).
940
+ * identical id (the log assigns it once).
1094
941
  *
1095
942
  * - **`replay(afterId)` is strictly-after.** It returns every retained log entry whose id sorts
1096
- * AFTER `afterId` in append order — the missed-events list the `GET {path}` handler writes
1097
- * before attaching the stream for live pushes. The decision for an UNKNOWN / already-evicted
943
+ * after `afterId` in append order — the missed-events list the `GET {path}` handler writes
944
+ * before attaching the stream for live pushes. The decision for an unknown / already-evicted
1098
945
  * `afterId` (the client's cursor fell off the back of the capacity window, or never existed):
1099
- * replay NOTHING. Replaying the whole retained log would re-deliver events the client never
1100
- * lost (its cursor is OLDER than everything retained); returning `[]` lets the handler then
946
+ * replay nothing. Replaying the whole retained log would re-deliver events the client never
947
+ * lost (its cursor is older than everything retained); returning `[]` lets the handler then
1101
948
  * stream only the fresh pushes that follow `attach` — the spec-sane resume.
1102
949
  *
1103
- * - **Bounded, append-ordered, plain `Map`.** The log lives in ONE insertion-ordered
1104
- * `Map<id, entry>` — insertion order IS append order IS id order, so `replay` and capacity
1105
- * eviction both walk the map directly. NO database mirror — the log is process-local
950
+ * - **Bounded, append-ordered, plain `Map`.** The log lives in one insertion-ordered
951
+ * `Map<id, entry>` — insertion order is append order is id order, so `replay` and capacity
952
+ * eviction both walk the map directly. No database mirror — the log is process-local
1106
953
  * transport mechanics, not durable state. `push` first drops every entry older than `ttl`
1107
954
  * (lazy TTL — no background timer, the middleware's lazy-window idiom), appends, then evicts
1108
- * the OLDEST entries until at most `capacity` remain; `replay` also runs the lazy TTL sweep
955
+ * the oldest entries until at most `capacity` remain; `replay` also runs the lazy TTL sweep
1109
956
  * first, so a stale entry is never replayed.
1110
957
  *
1111
958
  * - **No transport coupling beyond the SSE seam.** It holds session state + the generic {@link
@@ -1113,9 +960,10 @@ export declare interface MCPOriginOptions {
1113
960
  * The middleware opens the stream (the spine seam) and registers it here; this class only
1114
961
  * serializes a message onto the already-open streams.
1115
962
  *
1116
- * - **Injected clock.** `push` / `replay` accept an optional `now` (epoch ms), defaulting to
1117
- * `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real
1118
- * timer.
963
+ * - **Injected clock.** {@link import('./types.js').MCPSessionOptions.clock} supplies the
964
+ * epoch-ms clock the lazy TTL sweep reads, defaulting to `Date.now` — so a test drives TTL
965
+ * eviction with an elapsed clock rather than a real timer, and the middleware that mints a
966
+ * session hands its own clock down instead of leaving the log on wall-clock time.
1119
967
  *
1120
968
  * @example
1121
969
  * ```ts
@@ -1131,15 +979,15 @@ export declare class MCPSession implements MCPSessionInterface {
1131
979
  get id(): string;
1132
980
  attach(stream: StreamInterface): void;
1133
981
  detach(stream: StreamInterface): void;
1134
- push(message: JSONRPCMessage, now?: number): string;
1135
- replay(afterId: string, now?: number): readonly EventStoreEntry[];
982
+ push(message: JSONRPCMessage): string;
983
+ replay(afterId: string): readonly MCPSessionEvent[];
1136
984
  }
1137
985
 
1138
986
  /**
1139
- * The closure store entry a {@link import('./middlewares.js').createMCPSession} middleware
1140
- * keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms instant it
1141
- * was last touched (the lazy-TTL sweep's idle clock, independent of the session's own
1142
- * replay-log TTL).
987
+ * Represents the closure store entry a {@link import('./middlewares.js').createMCPSession}
988
+ * middleware keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms
989
+ * instant it was last touched (the lazy-TTL sweep's idle clock, independent of the session's
990
+ * own replay-log TTL).
1143
991
  *
1144
992
  * @remarks
1145
993
  * - `session` — the live {@link MCPSession} entity the store keys by session id.
@@ -1151,37 +999,63 @@ export declare class MCPSession implements MCPSessionInterface {
1151
999
  export declare interface MCPSessionEntry {
1152
1000
  readonly session: MCPSession;
1153
1001
  readonly touched: number;
1154
- /** The legacy revision negotiated when this session was minted. */
1002
+ /** Holds the legacy revision negotiated when this session was minted. */
1155
1003
  readonly version: MCPVersion;
1156
1004
  }
1157
1005
 
1158
1006
  /**
1159
- * One MCP transport session the per-session entity a {@link
1007
+ * Represents one entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed
1008
+ * {@link JSONRPCMessage} tagged with the monotone event `id` the session assigned and the
1009
+ * `timestamp` it was appended at (for the lazy-TTL replay window).
1010
+ *
1011
+ * @remarks
1012
+ * - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
1013
+ * value a resumable client echoes back as its `Last-Event-ID` to replay from here.
1014
+ * - `message` — the server→client {@link JSONRPCMessage} that was pushed.
1015
+ * - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
1016
+ *
1017
+ * A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
1018
+ * returns.
1019
+ */
1020
+ export declare interface MCPSessionEvent {
1021
+ readonly id: string;
1022
+ readonly message: JSONRPCMessage;
1023
+ readonly timestamp: number;
1024
+ }
1025
+
1026
+ /**
1027
+ * Represents one MCP transport session — the per-session entity a {@link
1160
1028
  * import('./middlewares.js').createMCPSession} middleware owns (the {@link
1161
1029
  * import('./MCPSession.js').MCPSession} entity), carrying the resumable server→client push
1162
- * channel with its bounded replay log FOLDED IN.
1030
+ * channel with its bounded replay log folded in.
1163
1031
  *
1164
1032
  * @remarks
1165
- * - `id` the opaque session id (a `crypto.randomUUID()`), echoed in the `mcp-session-id`
1166
- * header. The app reads it off `context.state.session` (the {@link MCPSessionState} slice
1167
- * {@link import('./middlewares.js').createMCPSession} sets) to address a push.
1168
- * - `attach(stream)` register an OPEN server→client SSE stream (a resumable `GET {path}`)
1169
- * so future {@link push}es reach it; `detach(stream)` unregisters it (the middleware calls
1170
- * it when the client disconnects).
1171
- * - `push(message)` — APPEND `message` to the session's folded replay log (assigning a
1172
- * monotone event id, RETURNED) and FAN it out to every attached stream as one `id:`-tagged
1173
- * SSE event — the server-initiated push primitive an in-request handler calls. A push with
1174
- * no attached stream is still logged, so a later-connecting / reconnecting client replays it.
1175
- * - `replay(afterId)` — the missed-events list (every retained log entry STRICTLY AFTER
1176
- * `afterId`, in append order) the resumable `GET {path}` handler writes ahead of live pushes;
1177
- * an unknown / evicted cursor replays NOTHING (the spec-sane resume).
1033
+ * The application addresses a session through `context.state.session`, the {@link
1034
+ * MCPSessionState} slice {@link import('./middlewares.js').createMCPSession} sets. A pushed
1035
+ * message with no attached stream is still logged, so a client that connects or reconnects
1036
+ * later replays it, and the resumable `GET {path}` handler writes a replay ahead of live
1037
+ * pushes.
1178
1038
  */
1179
1039
  export declare interface MCPSessionInterface {
1040
+ /** Holds the opaque session id, a `crypto.randomUUID()` value echoed in the `mcp-session-id` header. */
1180
1041
  readonly id: string;
1042
+ /**
1043
+ * Registers an open server→client SSE stream, a resumable `GET {path}`, so a later pushed
1044
+ * message reaches it.
1045
+ */
1181
1046
  attach(stream: StreamInterface): void;
1047
+ /** Unregisters a stream — the middleware calls it when the client disconnects. */
1182
1048
  detach(stream: StreamInterface): void;
1049
+ /**
1050
+ * Appends a message to the folded replay log under a fresh monotone event id, returns that id,
1051
+ * and fans the message out to every attached stream as one `id:`-tagged SSE event.
1052
+ */
1183
1053
  push(message: JSONRPCMessage): string;
1184
- replay(afterId: string): readonly EventStoreEntry[];
1054
+ /**
1055
+ * Returns every retained log entry strictly after a cursor, in append order; an unknown or
1056
+ * evicted cursor replays nothing.
1057
+ */
1058
+ replay(afterId: string): readonly MCPSessionEvent[];
1185
1059
  }
1186
1060
 
1187
1061
  /**
@@ -1189,19 +1063,18 @@ export declare interface MCPSessionInterface {
1189
1063
  * time-to-live, and the per-session resumable event-log bound.
1190
1064
  *
1191
1065
  * @remarks
1192
- * - `path` — the request path the session middleware OWNS (must match the `createMCPRoutes`
1066
+ * - `path` — the request path the session middleware owns (must match the `createMCPRoutes`
1193
1067
  * `path` it fronts); a request to any other path passes straight through. Defaults to
1194
1068
  * {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`).
1195
1069
  * - `ttl` — the session idle lifetime in milliseconds: a session not accessed within `ttl`
1196
- * is treated as ABSENT and lazily evicted on the next access (no background timer — the
1070
+ * is treated as absent and lazily evicted on the next access (no background timer — the
1197
1071
  * `createRateLimiter` lazy-window idiom). Omit it for sessions that live until an explicit
1198
1072
  * `DELETE`.
1199
- * - `capacity` — the FOLDED event-log bound per session: the maximum number of pushed
1200
- * server→client messages retained for replay before the OLDEST is evicted, paired with a
1201
- * per-event idle lifetime ({@link import('./constants.js').DEFAULT_MCP_SESSION_TTL}) that
1202
- * bounds how far a reconnecting client may replay. Omit it for the {@link
1203
- * import('./constants.js').DEFAULT_MCP_SESSION_CAPACITY} default. (The session `ttl` bounds
1204
- * the session; this `capacity` bounds its replay log — independent knobs.)
1073
+ * - `session` — the knobs forwarded to each minted {@link MCPSession}: `capacity` bounds its
1074
+ * replay log and `ttl` is that log's per-event lifetime. This type's own `ttl` bounds the
1075
+ * session instead. An omitted leaf takes its {@link MCPSessionOptions} default, and an
1076
+ * omitted `session.clock` inherits this type's own `clock`, so one injected clock governs
1077
+ * both the store sweep and the log sweep unless a caller names a different one.
1205
1078
  * - `clock` — the `() => number` epoch-ms clock {@link import('./middlewares.js').createMCPSession}
1206
1079
  * uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
1207
1080
  * deterministic clock a TTL test advances explicitly instead of racing a real idle window
@@ -1214,18 +1087,44 @@ export declare interface MCPSessionInterface {
1214
1087
  * - `keepalive` — the SSE liveness options for the held-open resumable response. `interval`
1215
1088
  * defaults to {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}.
1216
1089
  */
1217
- export declare interface MCPSessionOptions {
1090
+ export declare interface MCPSessionMiddlewareOptions {
1218
1091
  readonly path?: string;
1219
1092
  readonly ttl?: number;
1220
- readonly capacity?: number;
1093
+ readonly session?: MCPSessionOptions;
1221
1094
  readonly clock?: () => number;
1222
- /** Must match the route layer's value; `origins` is ignored when `enabled` is `false`. */
1095
+ /** Requires the route layer's value; `origins` is ignored when `enabled` is `false`. */
1223
1096
  readonly origin?: MCPOriginOptions;
1224
1097
  readonly keepalive?: MCPKeepaliveOptions;
1225
1098
  }
1226
1099
 
1227
1100
  /**
1228
- * The `context.state` slice a {@link import('./middlewares.js').createMCPSession}
1101
+ * Options for the {@link MCPSession} entity — its folded replay log's capacity and per-event
1102
+ * lifetime.
1103
+ *
1104
+ * @remarks
1105
+ * - `capacity` — the maximum number of pushed server→client messages retained for replay
1106
+ * before the oldest is evicted. Omit it for the {@link
1107
+ * import('./constants.js').DEFAULT_MCP_SESSION_CAPACITY} default.
1108
+ * - `ttl` — the PER-EVENT idle lifetime in milliseconds: a log entry older than `ttl` is
1109
+ * dropped by the lazy sweep `push` and `replay` run, which bounds how far a reconnecting
1110
+ * client may replay. Omit it for the {@link
1111
+ * import('./constants.js').DEFAULT_MCP_SESSION_TTL} default; a non-positive value means no
1112
+ * entry ever ages out by time.
1113
+ * - `clock` — the `() => number` epoch-ms clock the log's lazy TTL sweep reads; defaults to
1114
+ * `Date.now`.
1115
+ *
1116
+ * The middleware's own knobs — the owned path, the idle-SESSION sweep window, origin
1117
+ * validation, and keepalive — live on {@link MCPSessionMiddlewareOptions}. This type's `ttl`
1118
+ * and that one's measure different things, which is why they sit on different types.
1119
+ */
1120
+ export declare interface MCPSessionOptions {
1121
+ readonly capacity?: number;
1122
+ readonly ttl?: number;
1123
+ readonly clock?: () => number;
1124
+ }
1125
+
1126
+ /**
1127
+ * Declares the `context.state` slice a {@link import('./middlewares.js').createMCPSession}
1229
1128
  * middleware sets on a validated / minted request — a consumer's `TState` extends
1230
1129
  * this so the downstream route handler can read `context.state.session` to `push`
1231
1130
  * a server-initiated message onto the session's resumable stream.
@@ -1240,27 +1139,6 @@ export declare interface MCPSessionState {
1240
1139
  readonly session?: MCPSessionInterface;
1241
1140
  }
1242
1141
 
1243
- /**
1244
- * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
1245
- * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
1246
- *
1247
- * @remarks
1248
- * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({
1249
- * stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s
1250
- * {@link SSEParserInterface} (handling a partial line / in-progress event split across
1251
- * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with
1252
- * `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never
1253
- * thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam
1254
- * serializes against, so the wire round-trips. A `null` body (no stream) yields no
1255
- * messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
1256
- * reads a request/response SSE reply (the server sends one `data:` event then ends), so
1257
- * this drains to completion.
1258
- *
1259
- * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
1260
- * @returns Every {@link JSONRPCMessage} the stream carried, in order
1261
- */
1262
- export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
1263
-
1264
1142
  /**
1265
1143
  * Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it
1266
1144
  * reconnects to the resumable `GET {path}` stream, or `undefined` when absent.
@@ -1300,7 +1178,7 @@ export declare function readSessionHeader(request: Request): string | undefined;
1300
1178
  * @remarks
1301
1179
  * Returns `Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not
1302
1180
  * found'), { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a
1303
- * JSON-RPC error BODY with NO id) but at the session-not-found status. Shared by
1181
+ * JSON-RPC error body with no id) but at the session-not-found status. Shared by
1304
1182
  * every {@link import('./middlewares.js').createMCPSession} validation site — the
1305
1183
  * non-`initialize` `POST` path, the resumable `GET {path}` open, and the `DELETE {path}`
1306
1184
  * session-end (each a missing / unknown / TTL-evicted id) — so the single `404` envelope
@@ -1312,16 +1190,16 @@ export declare function rejectUnknownSession(): Response;
1312
1190
 
1313
1191
  /**
1314
1192
  * Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per
1315
- * notification in order, then the terminating response — and END the exchange however the
1193
+ * notification in order, then the terminating response — and end the exchange however the
1316
1194
  * pump leaves.
1317
1195
  *
1318
1196
  * @remarks
1319
1197
  * The Streamable-HTTP twin of {@link import('@orkestrel/mcp').sendStream}, and it owns exactly what
1320
- * that owns. The `finally` releases the exchange on EVERY exit — the normal terminal, a
1198
+ * that owns. The `finally` releases the exchange on every exit — the normal terminal, a
1321
1199
  * producer that threw, a `write` that threw, and an abort alike — because nothing else will:
1322
1200
  * a request whose client vanished cancels nothing by itself, so an exchange this pump walks
1323
1201
  * away from keeps its producer, its request lifetime, and its live subscription slot forever.
1324
- * The exchange is released BEFORE the body ends, so the slot is already back when the response
1202
+ * The exchange is released before the body ends, so the slot is already back when the response
1325
1203
  * completes.
1326
1204
  *
1327
1205
  * Total — never throws and never rejects. A held-open SSE response has already sent its
@@ -1338,27 +1216,26 @@ export declare function rejectUnknownSession(): Response;
1338
1216
  * ```ts
1339
1217
  * const answer = await mcp.dispatch(invocation, { signal: disconnect.signal })
1340
1218
  * if (answer !== undefined && Symbol.asyncIterator in answer) {
1341
- * const sse = openStream()
1219
+ * const sse = createStream()
1342
1220
  * queueMicrotask(() => void sendEventStream(answer, sse))
1343
1221
  * }
1344
1222
  * ```
1345
1223
  */
1346
1224
  export declare function sendEventStream(stream: MCPStreamControllerInterface, sse: StreamInterface): Promise<void>;
1347
1225
 
1348
- /** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1226
+ /** Names the `X-Accel-Buffering` value that disables reverse-proxy buffering. */
1349
1227
  export declare const SSE_BUFFERING_DISABLED = "no";
1350
1228
 
1351
- /** The reverse-proxy response header controlling buffering of an SSE response. */
1229
+ /** Names the reverse-proxy response header controlling buffering of an SSE response. */
1352
1230
  export declare const SSE_BUFFERING_HEADER = "x-accel-buffering";
1353
1231
 
1354
- /** The comment text written by the held-open MCP response keepalive. */
1232
+ /** Names the comment text written by the held-open MCP response keepalive. */
1355
1233
  export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1356
1234
 
1357
1235
  /**
1358
- * The stdio CLIENT transport for the Model Context Protocol a
1359
- * {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
1360
- * newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
1361
- * import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
1236
+ * Drives a child process MCP server over newline-delimited JSON-RPC on `stdin`/`stdout`
1237
+ * a {@link StdioClientTransportInterface}, the stdio sibling of {@link
1238
+ * import('@orkestrel/mcp').HTTPClientTransport} and {@link
1362
1239
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1363
1240
  *
1364
1241
  * @remarks
@@ -1372,14 +1249,14 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1372
1249
  * line is decoded and delivered through the shared {@link dispatchLines} helper — a well-formed
1373
1250
  * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (never throws).
1374
1251
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1375
- * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1252
+ * through the supervisor's `send` and awaits its answer, so this promise settles only after the
1376
1253
  * host reports the line handled rather than the moment the write is queued. The supervisor never
1377
1254
  * rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write
1378
1255
  * that failed, or for one that remained unconfirmed through `delivery`. A call made without a
1379
1256
  * live child rejects as not connected; a `false` answer from a live child rejects as unable to
1380
1257
  * deliver. The supervisor does not disclose which cause produced that answer.
1381
1258
  * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
1382
- * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
1259
+ * (idempotent). That teardown reaches the child's terminal moment, where the supervisor freezes
1383
1260
  * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
1384
1261
  * its own to get its line pump back: the stream ends under the pump rather than throwing at it.
1385
1262
  * A line the supervisor had already framed behind the one being delivered is dropped rather than
@@ -1392,15 +1269,15 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1392
1269
  * child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
1393
1270
  * route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
1394
1271
  * tree with `taskkill /F /T`, which nothing in the child can intercept.
1395
- * - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail
1272
+ * - **Evidence.** `evidence` reports that retained stderr tail off the held child — its live tail
1396
1273
  * while the child runs, and the value the supervisor froze at that child's terminal moment
1397
1274
  * afterwards. The reference is held past that moment and replaced only by the next `start()`,
1398
1275
  * which is what keeps a post-`close()` read stable without a private copy: the frozen value
1399
1276
  * never moves again, so a detached descendant writing to the inherited stderr after the cutoff
1400
1277
  * cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
1401
1278
  * byte bound.
1402
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1403
- * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1279
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
1280
+ * emitter isolates a listener throw; `error` is a domain event (a transport-level
1404
1281
  * fault, including the child spawn cause the supervisor surfaces and the notice that this
1405
1282
  * lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
1406
1283
  * listener-error channel.
@@ -1415,7 +1292,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1415
1292
  export declare class StdioClientTransport implements StdioClientTransportInterface {
1416
1293
  #private;
1417
1294
  constructor(options: StdioClientTransportOptions);
1418
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1295
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
1419
1296
  get session(): string | undefined;
1420
1297
  get duplex(): boolean;
1421
1298
  get evidence(): string | undefined;
@@ -1430,28 +1307,28 @@ export declare class StdioClientTransport implements StdioClientTransportInterfa
1430
1307
  * @throws Thrown with `stdio transport could not deliver the message` when a live child's write
1431
1308
  * resolves `false`
1432
1309
  */
1433
- send(message: JSONRPCMessage_2): Promise<void>;
1310
+ send(message: JSONRPCMessage): Promise<void>;
1434
1311
  close(): Promise<void>;
1435
1312
  }
1436
1313
 
1437
1314
  /**
1438
- * The contract `createStdioClientTransport` returns — a {@link MCPClientTransportInterface}
1439
- * that also reports the supervised child's stderr tail, the diagnostic a child that dies at
1440
- * startup leaves behind.
1315
+ * Declares the contract `createStdioClientTransport` returns — a
1316
+ * {@link MCPMessageTransportInterface} that also reports the supervised child's stderr tail, the
1317
+ * diagnostic a child that dies at startup leaves behind.
1441
1318
  *
1442
1319
  * @remarks
1443
1320
  * This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
1444
1321
  * `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
1445
- * rather than on {@link MCPClientTransportInterface} because a transport that supervises no child —
1322
+ * rather than on {@link MCPMessageTransportInterface} because a transport that supervises no child —
1446
1323
  * Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
1447
1324
  * of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
1448
- * consumer that widens this value back to {@link MCPClientTransportInterface}, including by
1325
+ * consumer that widens this value back to {@link MCPMessageTransportInterface}, including by
1449
1326
  * reading `client.transport`, loses the reader and must keep the original reference.
1450
1327
  */
1451
- export declare interface StdioClientTransportInterface extends MCPClientTransportInterface {
1328
+ export declare interface StdioClientTransportInterface extends MCPMessageTransportInterface {
1452
1329
  /**
1453
- * The supervised child's decoded stderr tail — live while a child is held, and the value
1454
- * captured at that child's end afterwards.
1330
+ * Reports the supervised child's decoded stderr tail — live while a child is held, and the
1331
+ * value captured at that child's end afterwards.
1455
1332
  *
1456
1333
  * @remarks
1457
1334
  * - **Readings.** `undefined` while no child has run and none has been captured — before the
@@ -1460,7 +1337,7 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
1460
1337
  * it exited on its own or `close()` terminated it, and `''` there for a child that ran and
1461
1338
  * wrote nothing — an empty tail is a real reading of a silent child, distinct from the
1462
1339
  * absent one.
1463
- * - **Lifetime.** The tail follows the child that produced it. The supervisor FREEZES it at
1340
+ * - **Lifetime.** The tail follows the child that produced it. The supervisor freezes it at
1464
1341
  * that child's terminal moment — the moment `close()`'s teardown resolves past, and the
1465
1342
  * moment the exit that fires this transport's `close` settles at — and this transport keeps
1466
1343
  * reading that same child afterwards. The frozen value never moves again, which is what
@@ -1475,18 +1352,18 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
1475
1352
  * its teardown barrier while those listeners run, so a `start()` one of them calls parks
1476
1353
  * behind it and every later listener reads the ended child's frozen tail. A natural exit
1477
1354
  * holds that barrier only across the `error` it reports at that end, so a restart begun
1478
- * THERE parks until `close` has been delivered, while a `close` listener that calls
1355
+ * there parks until `close` has been delivered, while a `close` listener that calls
1479
1356
  * `start()` opens the next lifetime itself and replaces the value every listener after it
1480
1357
  * would have read.
1481
1358
  * - **What the close path carries.** The frozen value is what the supervisor had received by
1482
1359
  * that terminal moment, not the child's complete output.
1483
1360
  * Windows ends the tree with `taskkill /F /T`, which nothing in the child can intercept: a
1484
1361
  * `SIGTERM` handler never runs there, so the bytes it would have written never exist. A
1485
- * child that ends on its own closes its stderr first, and THAT tail is complete.
1362
+ * child that ends on its own closes its stderr first, and that tail is complete.
1486
1363
  * Where that moment arrived at the supervisor's `drain` bound rather than at the child's
1487
1364
  * own stream close, the tail stops at the cutoff and later diagnostics may have existed;
1488
1365
  * the transport emits an `error` naming that lifetime, so a partial tail reads as partial.
1489
- * - **Bound.** The supervisor keeps the END of the child's raw stderr bytes, at most
1366
+ * - **Bound.** The supervisor keeps the end of the child's raw stderr bytes, at most
1490
1367
  * `@orkestrel/process`'s {@link import('@orkestrel/process').PROCESS_EVIDENCE} (2048
1491
1368
  * bytes under 0.0.6). A child that writes more than the bound loses its earliest output
1492
1369
  * and keeps its last, which is the half that names why it died. The bound counts raw
@@ -1507,13 +1384,13 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
1507
1384
  * stdio-framed MCP server (newline-delimited JSON-RPC over `stdin`/`stdout`).
1508
1385
  *
1509
1386
  * @remarks
1510
- * - `command` — the executable to spawn (for example, `'node'`, `'./my-mcp-server'`). REQUIRED.
1387
+ * - `command` — the executable to spawn (for example, `'node'`, `'./my-mcp-server'`). Required.
1511
1388
  * - `args` — the command-line arguments passed to `command`; defaults to none.
1512
- * - `env` — environment variable overrides MERGED over the parent `process.env` for the
1389
+ * - `env` — environment variable overrides merged over the parent `process.env` for the
1513
1390
  * spawned child (the composed `@orkestrel/process` supervisor's merge semantics): when
1514
- * OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
1391
+ * omitted the child inherits the full `process.env`, when provided each named key overrides
1515
1392
  * the inherited value while every unlisted key is still inherited. This transport cannot
1516
- * REPLACE the inherited environment entirely — the supervisor always merges over the parent.
1393
+ * replace the inherited environment entirely — the supervisor always merges over the parent.
1517
1394
  * - `delivery` — the bound in milliseconds on one unconfirmed write to the child's `stdin`;
1518
1395
  * an explicit `0` opts out. Defaults to {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
1519
1396
  */
@@ -1532,10 +1409,10 @@ export declare interface StdioClientTransportOptions {
1532
1409
  * that will never read it. Default: {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
1533
1410
  *
1534
1411
  * An explicit `0` opts out: the bound is off, and an unconfirmed write stays pending until
1535
- * the channel faults or teardown settles it. Omission does NOT opt out here, which is where
1412
+ * the channel faults or teardown settles it. Omission does not opt out here, which is where
1536
1413
  * this option diverges from the supervisor's own `delivery` on {@link
1537
- * import('@orkestrel/process').ProcessOptions} — omitted THERE disables the bound, omitted
1538
- * HERE selects the default.
1414
+ * import('@orkestrel/process').ProcessOptions} — omitted there disables the bound, omitted
1415
+ * here selects the default.
1539
1416
  *
1540
1417
  * An out-of-range value surfaces at `start()` rather than at construction. This transport
1541
1418
  * forwards the value verbatim and adds no validator of its own, so the supervisor's own timer
@@ -1545,26 +1422,29 @@ export declare interface StdioClientTransportOptions {
1545
1422
  }
1546
1423
 
1547
1424
  /**
1548
- * The stdio INGRESS handle {@link import('./factories.js').createStdioServer} returns — arms
1549
- * and tears down the newline-delimited JSON-RPC pump over the {@link StdioServerOptions}
1550
- * stream pair.
1425
+ * Arms and tears down the newline-delimited JSON-RPC pump over the {@link StdioServerOptions}
1426
+ * stream pair the stdio ingress handle {@link import('./factories.js').createStdioServer}
1427
+ * returns.
1551
1428
  *
1552
1429
  * @remarks
1553
- * - `start()` arm the pump: subscribe to `input`, and dispatch every complete line through
1554
- * the bound {@link import('@src/core').MCPDispatcherInterface}, writing each defined
1555
- * response back to `output`. The subscriptions are attached by the time the call returns.
1556
- * The pump arms ONCE, so a repeated `start()` attaches nothing further and an inbound
1557
- * request still draws exactly one reply.
1558
- * - `stop()` — unbind the pump and close the transport: the listeners `start()` put on
1559
- * `input` / `output` are removed, every pending `send` rejects, and `input` is released so
1560
- * the process can exit. The release is complete by the time the call returns, and a
1561
- * repeated `stop()` does nothing.
1562
- * - **One lifetime per handle.** `stop()` ends it permanently: a `start()` issued afterwards
1563
- * arms nothing, and serving again takes a fresh
1564
- * {@link import('./factories.js').createStdioServer} over a live stream pair.
1430
+ * The subscriptions are attached, and the release complete, by the time each call returns. One
1431
+ * lifetime per handle: the `stop` method ends it permanently, a `start` call issued afterwards
1432
+ * arms nothing, and serving again takes a fresh
1433
+ * {@link import('./factories.js').createStdioServer} over a live stream pair.
1565
1434
  */
1566
1435
  export declare interface StdioServerInterface {
1436
+ /**
1437
+ * Arms the pump: subscribes to `input` and dispatches every complete line through the bound
1438
+ * {@link import('@orkestrel/mcp').MCPDispatcherInterface}, writing each defined response back to
1439
+ * `output`. The pump arms once, so a repeated call attaches nothing further and an inbound
1440
+ * request still draws exactly one reply.
1441
+ */
1567
1442
  start(): void;
1443
+ /**
1444
+ * Unbinds the pump and closes the transport: removes the listeners the `start` method put on
1445
+ * `input` and `output`, rejects every pending write, and releases `input` so the process can
1446
+ * exit. A repeated call does nothing.
1447
+ */
1568
1448
  stop(): void;
1569
1449
  }
1570
1450
 
@@ -1584,15 +1464,14 @@ export declare interface StdioServerOptions {
1584
1464
  }
1585
1465
 
1586
1466
  /**
1587
- * The stdio SERVER transport for the Model Context Protocol — wraps an injectable
1588
- * readable/writable stream pair (`process.stdin`/`process.stdout` in production, a
1589
- * test double in tests) as a {@link MCPClientTransportInterface}, the newline-delimited
1590
- * JSON-RPC channel {@link import('../factories.js').createStdioServer} pumps
1591
- * `mcp.dispatch` over, the stdio mirror of {@link
1467
+ * Wraps an injectable readable/writable stream pair (`process.stdin`/`process.stdout` in
1468
+ * production, a test double in tests) as a {@link MCPMessageTransportInterface} — the
1469
+ * newline-delimited JSON-RPC channel {@link import('../factories.js').createStdioServer}
1470
+ * pumps `mcp.dispatch` over, the stdio mirror of {@link
1592
1471
  * import('./WebSocketServerTransport.js').WebSocketServerTransport}.
1593
1472
  *
1594
1473
  * @remarks
1595
- * - **Reuses `MCPClientTransportInterface`.** The same generic carrier the HTTP
1474
+ * - **Reuses `MCPMessageTransportInterface`.** The same generic carrier the HTTP
1596
1475
  * and WebSocket server transports implement — `emitter` (`message` / `close` /
1597
1476
  * `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).
1598
1477
  * - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each
@@ -1608,7 +1487,7 @@ export declare interface StdioServerOptions {
1608
1487
  * - **`close()`** removes this transport's input and output subscriptions, rejects every
1609
1488
  * pending send, and fires its `close`
1610
1489
  * event (idempotent). It pauses the input only when the caller was not already reading
1611
- * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this
1490
+ * it at `start` (`readableFlowing !== true`) and no `data` listener remains once this
1612
1491
  * transport's own is removed — so a process holding `process.stdin` can exit, and a
1613
1492
  * caller's own flow is never stopped underneath it. The transport preserves flowing versus
1614
1493
  * non-flowing state and restores every caller-owned listener. A Node stream that had never been
@@ -1618,14 +1497,14 @@ export declare interface StdioServerOptions {
1618
1497
  * listener receives data. The injected streams are owned by the caller (typically
1619
1498
  * `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears
1620
1499
  * them.
1621
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1622
- * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1500
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
1501
+ * emitter isolates a listener throw; `error` is a domain event (a transport-level
1623
1502
  * fault), distinct from the emitter's own listener-error channel.
1624
1503
  */
1625
- export declare class StdioServerTransport implements MCPClientTransportInterface_2 {
1504
+ export declare class StdioServerTransport implements MCPMessageTransportInterface {
1626
1505
  #private;
1627
1506
  constructor(input: NodeJS.ReadableStream, output: NodeJS.WritableStream);
1628
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1507
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
1629
1508
  get session(): string | undefined;
1630
1509
  get duplex(): boolean;
1631
1510
  start(): Promise<void>;
@@ -1642,7 +1521,7 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1642
1521
  * @throws Thrown with `stdio transport is not connected` after the transport closes
1643
1522
  * @throws Thrown with the output callback error or synchronous write failure
1644
1523
  */
1645
- send(message: JSONRPCMessage_2): Promise<void>;
1524
+ send(message: JSONRPCMessage): Promise<void>;
1646
1525
  close(): Promise<void>;
1647
1526
  }
1648
1527
 
@@ -1651,7 +1530,7 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1651
1530
  * the `createWebSocketServer` upgrade-path match.
1652
1531
  *
1653
1532
  * @remarks
1654
- * A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request TARGET
1533
+ * A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request target
1655
1534
  * (`'/mcp?x=1'`), narrowed with `isString` (never `as`) and defaulting to `'/'` for an
1656
1535
  * absent target; it is parsed against a placeholder base (only the pathname matters for the upgrade
1657
1536
  * decision) and the `pathname` returned. The upgrade handler compares this against its
@@ -1664,49 +1543,49 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1664
1543
  export declare function upgradeRequestPath(request: IncomingMessage): string;
1665
1544
 
1666
1545
  /**
1667
- * The WebSocket CLIENT transport for the Model Context Protocol — a
1668
- * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket, the
1546
+ * Drives a remote MCP server over a WebSocket — a client
1547
+ * {@link MCPMessageTransportInterface} for the Model Context Protocol, the
1669
1548
  * egress mirror of {@link import('./factories.js').createWebSocketServer} and the WebSocket
1670
- * sibling of {@link import('./HTTPClientTransport.js').HTTPClientTransport}.
1549
+ * sibling of {@link import('@orkestrel/mcp').HTTPClientTransport}.
1671
1550
  *
1672
1551
  * @remarks
1673
1552
  * - **Persistent bidirectional channel (unlike the HTTP transport).** `start()` performs the
1674
1553
  * RFC 6455 client handshake: it opens a `node:http`(`s`) `GET` carrying `Connection: Upgrade`
1675
1554
  * / `Upgrade: websocket` / a random `Sec-WebSocket-Key` / `Sec-WebSocket-Version: 13` /
1676
1555
  * `Sec-WebSocket-Protocol: mcp` (plus any `options.headers`), awaits the client `'upgrade'`
1677
- * event, and VALIDATES `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)
1678
- * — a mismatch (or a non-`101` response, or a request error) REJECTS `start()` and the socket
1556
+ * event, and validates `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)
1557
+ * — a mismatch (or a non-`101` response, or a request error) rejects `start()` and the socket
1679
1558
  * is destroyed. On success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,
1680
- * head })` (CLIENT mode — no key → frames are MASKED per RFC 6455 §5.3) and bridges its
1559
+ * head })` (client mode — no key → frames are masked per RFC 6455 §5.3) and bridges its
1681
1560
  * `message`.
1682
1561
  * - **The arriving socket is RE-ASKED for, never assumed.** `start()` suspends across that
1683
1562
  * connect and upgrade, so it re-checks the transport's state before installing anything: a
1684
1563
  * concurrent `start()` that already installed a socket, or a {@link close} that ended the
1685
- * transport while the handshake was on the wire, both WIN — the socket that arrives late is
1686
- * DESTROYED and never bound, so no orphan is left re-emitting frames at nobody. Both
1564
+ * transport while the handshake was on the wire, both win — the socket that arrives late is
1565
+ * destroyed and never bound, so no orphan is left re-emitting frames at nobody. Both
1687
1566
  * `start()` calls still resolve; exactly one socket is ever bound.
1688
- * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and narrowed
1689
- * with `parseJSONRPCMessage` — a {@link JSONRPCMessage} re-emits on this transport's `message`
1567
+ * - **Inbound (`message`).** Each decoded text frame runs through the shared `deliverMessage`
1568
+ * fold (parse, then narrow) — a {@link JSONRPCMessage} re-emits on this transport's `message`
1690
1569
  * event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
1691
1570
  * non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
1692
1571
  * / `error` bridge to this transport's events.
1693
1572
  * - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
1694
- * confirmed, so this transport answers a closed channel from its own state AND the socket's
1573
+ * confirmed, so this transport answers a closed channel from its own state and the socket's
1695
1574
  * `readyState`: a `send` with no bound socket — before `start()`, after `close()`, or after the
1696
- * peer ended the socket — and a `send` on a bound socket that is not `OPEN` both REJECT with
1575
+ * peer ended the socket — and a `send` on a bound socket that is not `OPEN` both reject with
1697
1576
  * `WebSocket transport is not connected`. It neither drops the message nor queues it for a
1698
1577
  * connection this transport is not holding — the browser face queues a pre-open send, and this
1699
1578
  * one, holding no connection to flush it onto, rejects that too.
1700
1579
  * - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
1701
- * upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
1580
+ * upgrade still on the wire is destroyed, so a `close()` during the handshake ends the
1702
1581
  * transport at once instead of waiting for a peer that may never answer — the suspended
1703
1582
  * `start()` resolves, because the close is the outcome its caller asked for.
1704
1583
  * - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`
1705
1584
  * one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`
1706
1585
  * → TLS through `node:https`). Either reaches the same endpoint.
1707
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every emit
1586
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every emit
1708
1587
  * the emitter isolates a listener throw (a buggy observer never corrupts the transport);
1709
- * `error` is a DOMAIN event (a transport-level fault).
1588
+ * `error` is a domain event (a transport-level fault).
1710
1589
  *
1711
1590
  * @example
1712
1591
  * ```ts
@@ -1715,14 +1594,14 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
1715
1594
  * await client.connect() // start() handshakes, then the MCP initialize runs over WS frames
1716
1595
  * ```
1717
1596
  */
1718
- export declare class WebSocketClientTransport implements MCPClientTransportInterface_2 {
1597
+ export declare class WebSocketClientTransport implements MCPMessageTransportInterface {
1719
1598
  #private;
1720
1599
  constructor(options: WebSocketClientTransportOptions);
1721
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1600
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
1722
1601
  get session(): string | undefined;
1723
1602
  get duplex(): boolean;
1724
1603
  start(): Promise<void>;
1725
- send(message: JSONRPCMessage_2): Promise<void>;
1604
+ send(message: JSONRPCMessage): Promise<void>;
1726
1605
  close(): Promise<void>;
1727
1606
  }
1728
1607
 
@@ -1732,15 +1611,15 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
1732
1611
  *
1733
1612
  * @remarks
1734
1613
  * - `url` — the absolute URL of the remote server's WebSocket endpoint. Accepts a `ws://` /
1735
- * `wss://` URL OR an `http://` / `https://` one (a `ws(s)` scheme is converted to `http(s)`
1614
+ * `wss://` URL or an `http://` / `https://` one (a `ws(s)` scheme is converted to `http(s)`
1736
1615
  * for the underlying `node:http(s)` upgrade request; either reaches the same endpoint).
1737
- * REQUIRED.
1616
+ * Required.
1738
1617
  * - `headers` — extra request headers merged onto the upgrade `GET` (for example, an `Authorization`
1739
1618
  * bearer for a guarded server). The transport always sets `Connection: Upgrade`,
1740
1619
  * `Upgrade: websocket`, a random `Sec-WebSocket-Key`, `Sec-WebSocket-Version: 13`, and
1741
1620
  * `Sec-WebSocket-Protocol: mcp`; a header supplied here is merged on top.
1742
1621
  *
1743
- * **`headers` exists HERE and not on the browser face's `{ url, protocols }`, and that
1622
+ * **`headers` exists here and not on the browser face's `{ url, protocols }`, and that
1744
1623
  * divergence is deliberate rather than a lag: the host performs the WebSocket handshake.**
1745
1624
  * This face owns its own `node:http(s)` upgrade request, so it can set any header on it. A
1746
1625
  * page cannot — the native `WebSocket` constructor takes a URL and subprotocols and nothing
@@ -1758,21 +1637,21 @@ export declare interface WebSocketClientTransportOptions {
1758
1637
  *
1759
1638
  * @remarks
1760
1639
  * - `emitter` — the emitter of the `@orkestrel/server` spine this handler is registered on
1761
- * (`server.emitter`). REQUIRED: on its `stop` event the handler closes every socket it
1640
+ * (`server.emitter`). Required: on its `stop` event the handler closes every socket it
1762
1641
  * still owns with the RFC 6455 close handshake, so the spine's drain settles at once. An
1763
1642
  * upgraded socket is detached from the connection set the spine's own close walks, so
1764
1643
  * nothing but the claimant can end it — leave it open and `stop()` spends its whole
1765
1644
  * `drain` budget and then cuts the connection mid-protocol.
1766
- * - `path` — the request path the upgrade handler CLAIMS; defaults to
1645
+ * - `path` — the request path the upgrade handler claims; defaults to
1767
1646
  * {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`, the same path the HTTP
1768
- * transport mounts at). A protocol-upgrade request to any OTHER path is DECLINED
1647
+ * transport mounts at). A protocol-upgrade request to any other path is declined
1769
1648
  * (the handler returns `false`, so the spine fans it to the next handler or destroys it).
1770
1649
  * - `subprotocol` — the WebSocket subprotocol selected in the `101` handshake's
1771
- * `Sec-WebSocket-Protocol`; defaults to {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL}
1650
+ * `Sec-WebSocket-Protocol`; defaults to {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL}
1772
1651
  * (`'mcp'`). It is sent only when the client's offer contains that token.
1773
1652
  *
1774
- * Auth / origin policy is deliberately ABSENT: like the HTTP transport, the WebSocket
1775
- * transport is MECHANISM — compose a guard IN FRONT (a `Server.upgrade` handler registered
1653
+ * Auth / origin policy is deliberately absent: like the HTTP transport, the WebSocket
1654
+ * transport is mechanism — compose a guard in front (a `Server.upgrade` handler registered
1776
1655
  * before this one can decline an unauthenticated upgrade).
1777
1656
  */
1778
1657
  export declare interface WebSocketServerOptions {
@@ -1782,30 +1661,30 @@ export declare interface WebSocketServerOptions {
1782
1661
  }
1783
1662
 
1784
1663
  /**
1785
- * The per-connection JSON-RPC-over-WebSocket SERVER bridge wraps a
1786
- * {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a
1787
- * {@link MCPClientTransportInterface}, the bidirectional JSON-RPC message channel
1664
+ * Wraps a {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a
1665
+ * {@link MCPMessageTransportInterface} the per-connection JSON-RPC-over-WebSocket server
1666
+ * bridge, the bidirectional JSON-RPC message channel
1788
1667
  * `createWebSocketServer` pumps `mcp.dispatch` over and the egress mirror's
1789
1668
  * {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.
1790
1669
  *
1791
1670
  * @remarks
1792
- * - **Reuses `MCPClientTransportInterface`.** It IS the same generic carrier the HTTP
1671
+ * - **Reuses `MCPMessageTransportInterface`.** It is the same generic carrier the HTTP
1793
1672
  * client transport implements — `emitter` (`message` / `close` / `error`), `start`,
1794
- * `send`, `close` — so the WebSocket server and client both speak ONE transport contract,
1673
+ * `send`, `close` — so the WebSocket server and client both speak one transport contract,
1795
1674
  * no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a
1796
1675
  * session id is the deferred sessions tier). The name keeps the role explicit even though
1797
1676
  * the shape is shared.
1798
1677
  * - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text
1799
- * frame is `JSON.parse`d inside a try/catch and narrowed with `parseJSONRPCMessage` — a
1678
+ * frame runs through the shared `deliverMessage` fold (parse, then narrow) — a
1800
1679
  * well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the
1801
1680
  * parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while
1802
- * a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It
1681
+ * a non-JSON or non-message frame is surfaced on `error` and dropped, never thrown. It
1803
1682
  * also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
1804
1683
  * - **Outbound (`send`).** `send(message)` writes one text frame
1805
1684
  * (`nodeWs.send(JSON.stringify(message))`). The underlying wrapper no-ops a write on a
1806
1685
  * non-open socket and confirms nothing, so this bridge answers a closed channel from its own
1807
1686
  * state and the socket's `readyState`: a `send` after `close()`, after the peer's close, or on
1808
- * a socket that is not `OPEN` REJECTS with `WebSocket transport is not connected` rather than
1687
+ * a socket that is not `OPEN` rejects with `WebSocket transport is not connected` rather than
1809
1688
  * resolving on a frame nobody wrote. `bindServer` catches that rejection and routes it to the
1810
1689
  * dispatcher's `error` event, and it aborts every in-flight request the moment this transport's
1811
1690
  * `close` fires — so a peer that disconnects mid-request is answered by no write at all.
@@ -1814,18 +1693,18 @@ export declare interface WebSocketServerOptions {
1814
1693
  * (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives
1815
1694
  * between that release and the peer's close echo reaches nothing: the socket-driven close path
1816
1695
  * releases the same way, so a closed transport is never subscribed to a live socket.
1817
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the emitter
1696
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the emitter
1818
1697
  * isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a
1819
- * DOMAIN event (a transport-level fault), distinct from the emitter's listener-error channel.
1698
+ * domain event (a transport-level fault), distinct from the emitter's listener-error channel.
1820
1699
  */
1821
- export declare class WebSocketServerTransport implements MCPClientTransportInterface_2 {
1700
+ export declare class WebSocketServerTransport implements MCPMessageTransportInterface {
1822
1701
  #private;
1823
1702
  constructor(socket: NodeWebSocketInterface);
1824
- get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
1703
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
1825
1704
  get session(): string | undefined;
1826
1705
  get duplex(): boolean;
1827
1706
  start(): Promise<void>;
1828
- send(message: JSONRPCMessage_2): Promise<void>;
1707
+ send(message: JSONRPCMessage): Promise<void>;
1829
1708
  close(): Promise<void>;
1830
1709
  }
1831
1710
 
@@ -1836,12 +1715,12 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
1836
1715
  * The completion callback is the writable channel's backpressure boundary. A callback error and
1837
1716
  * a synchronous `write` throw reject the returned promise with the original value.
1838
1717
  *
1839
- * That callback is the ONLY thing that settles the promise: this helper holds no timer and no
1718
+ * That callback is the only thing that settles the promise: this helper holds no timer and no
1840
1719
  * abort, so an output that neither confirms nor fails the write parks the promise for as long as
1841
1720
  * the caller-owned stream holds the callback. A caller wanting a bound races this promise against
1842
1721
  * one it owns — {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
1843
1722
  * registers such a bound per send and rejects it on `close()`, so closing the transport settles
1844
- * the CALLER's `send` while the abandoned write stays with the stream that still holds its
1723
+ * the caller's `send` while the abandoned write stays with the stream that still holds its
1845
1724
  * callback, reachable from nothing the transport retains.
1846
1725
  *
1847
1726
  * @param output - The writable stream that receives the line