@orkestrel/mcp 0.0.19 → 0.0.21

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ surfaces its tools as local `ToolInterface`s. No agent runtime is required.
8
8
  The dispatch core is transport- and provider-agnostic
9
9
  (`src/core` — JSON-RPC 2.0, no HTTP, no `as`); every transport (Streamable
10
10
  HTTP over `@orkestrel/router` / `@orkestrel/server`, WebSocket over
11
- `@orkestrel/websocket`, and stdio over `node:child_process`) lives one layer
11
+ `@orkestrel/websocket`, and stdio over `@orkestrel/process`) lives one layer
12
12
  out (`src/server`), each mechanism, not policy. Part of the `@orkestrel` line.
13
13
 
14
14
  ## Install
@@ -19,8 +19,12 @@ npm install @orkestrel/mcp
19
19
 
20
20
  ## Requirements
21
21
 
22
- - Node.js >= 24
23
- - ESM and CommonJS builds ship for both the core and server entry points
22
+ - Node.js >= 22.12.0
23
+ - ESM and CommonJS builds ship for both the core and server entry points; the
24
+ browser entry point ships ESM only
25
+ - TypeScript `moduleResolution` set to `node16`, `nodenext`, or `bundler`. Under
26
+ legacy `node` resolution the `./browser` and `./server` subpaths resolve no
27
+ declarations
24
28
  - `@orkestrel/server` and `@orkestrel/router` are peer dependencies (the HTTP
25
29
  spine the `./server` transports mount onto)
26
30
 
@@ -29,15 +33,16 @@ npm install @orkestrel/mcp
29
33
  Expose a tool registry over MCP, mounted on the HTTP spine:
30
34
 
31
35
  ```ts
32
- import { createMCPServer } from '@orkestrel/mcp'
36
+ import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
33
37
  import { createMCPRoutes } from '@orkestrel/mcp/server'
34
38
  import { createTool, createToolManager } from '@orkestrel/tool'
35
39
 
36
40
  const tools = createToolManager()
37
41
  tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
38
42
 
39
- const mcp = createMCPServer({ name: 'calculator', version: '1.0.0', tools })
40
- const routes = createMCPRoutes(mcp) // POST /mcp dispatches JSON-RPC (JSON or SSE per Accept)
43
+ const mcp = createMCPServer({ identity: { name: 'calculator', version: '1.0.0' }, tools })
44
+ // POST /mcp dispatches JSON-RPC (JSON or SSE per Accept):
45
+ const routes = createMCPRoutes(createMCPLegacy(mcp)) // answers `initialize` too; pass `mcp` alone for modern-only
41
46
  router.add(routes)
42
47
  ```
43
48
 
@@ -60,19 +65,15 @@ The SAME `MCPClient` drives a `createWebSocketClientTransport` or
60
65
 
61
66
  ## Guide
62
67
 
63
- For the full surface — the JSON-RPC dispatch core, the three server
64
- transports (HTTP, WebSocket, stdio), the native session middleware, and
65
- usage patterns — see [`guides/src/mcp.md`](guides/src/mcp.md).
68
+ For the full surface — the JSON-RPC dispatch core, the server transports
69
+ (HTTP, WebSocket, stdio), the native session middleware, and usage
70
+ patterns — see the [MCP guide](https://github.com/orkestrel/mcp/blob/main/guides/mcp.md).
66
71
 
67
72
  ## Package
68
73
 
69
- Published with two entry points per the `exports` field in `package.json`:
70
- the environment-agnostic core (`.`) and the Node-only server surface
71
- (`./server`). The browser face (`./browser`) is ESM only. The tarball is
72
- 625.0 kB packed across 18 files, 2.5 MB unpacked.
73
-
74
- [`CHANGELOG.md`](CHANGELOG.md) lives in the repository and is not in the
75
- tarball, because `files` is `["dist/src", "README.md"]`.
74
+ Published per the `exports` field in `package.json`: the
75
+ environment-agnostic core (`.`), the Node-only server surface (`./server`),
76
+ and the browser face (`./browser`), which is ESM only.
76
77
 
77
78
  ## Proven
78
79
 
@@ -91,9 +92,9 @@ of the IDE class has.
91
92
 
92
93
  ## Declared limits
93
94
 
94
- Four publication facts, each with its number. Full detail, plus every
95
+ The publication facts, each with its number. Full detail, plus every
95
96
  protocol-level gap and non-goal, is in
96
- [`guides/src/mcp.md`](guides/src/mcp.md#declared-packaging-limits).
97
+ [the MCP guide](https://github.com/orkestrel/mcp/blob/main/guides/mcp.md#declared-packaging-limits).
97
98
 
98
99
  - **No IDE evidence.** See above. The conformance number is about the wire
99
100
  and does not transfer to a host application.
@@ -101,12 +102,12 @@ protocol-level gap and non-goal, is in
101
102
  condition, so `node16`, `nodenext`, and `bundler` resolution find
102
103
  declarations. A consumer on legacy `moduleResolution: node` does not read
103
104
  `exports` and sees an untyped package.
104
- - **A build-time version notice, three times.** API Extractor bundles
105
+ - **A build-time version notice on every built face.** API Extractor bundles
105
106
  TypeScript 5.9.3 through a transitive pin and this project compiles with
106
107
  6.0.3, so `build` prints one notice per built face. It is informational:
107
108
  `build` exits 0 and every declaration is emitted.
108
- - **Source maps ship.** Five `.map` files are 1,130 kB of the 2.5 MB
109
- unpacked, about 45 percent. They are kept so a consumer debugging a
109
+ - **Source maps ship.** Measured on 2026-08-20, the `.map` files are
110
+ 1,168,764 of 2,543,024 unpacked bytes (46.0 percent). They are kept so a consumer debugging a
110
111
  protocol library steps into real source.
111
112
 
112
113
  The notice, verbatim:
@@ -117,4 +118,4 @@ The notice, verbatim:
117
118
 
118
119
  ## License
119
120
 
120
- MIT © [Orkestrel](https://github.com/orkestrel) — see [LICENSE](./LICENSE).
121
+ MIT © [Orkestrel](https://github.com/orkestrel) — see the [license](https://github.com/orkestrel/mcp/blob/main/LICENSE).
@@ -10,18 +10,18 @@ import { MCPTransportInterface as MCPTransportInterface_2 } from '@orkestrel/mcp
10
10
  import { ToolManagerInterface } from '@orkestrel/tool';
11
11
 
12
12
  /**
13
- * Create the browser-face HTTP CLIENT transport for an
14
- * {@link import('@src/core').MCPClientInterface} — a {@link MCPClientTransportInterface}
13
+ * Creates the browser-face HTTP CLIENT transport for an
14
+ * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
15
15
  * that drives a REMOTE Streamable-HTTP MCP server over the native `fetch`, the
16
- * browser sibling of the Node face's `createHTTPClientTransport` (`@src/server`).
16
+ * browser sibling of the Node face's `createHTTPClientTransport` (`@orkestrel/mcp/server`).
17
17
  *
18
18
  * @remarks
19
19
  * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
20
20
  * sends is `POST`ed to `options.url` with `content-type: application/json` and an
21
21
  * `Accept` of both `application/json` and `text/event-stream` (the server answers
22
22
  * with EITHER — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
23
- * decoded via `@orkestrel/sse`), and the reply is surfaced on the transport's
24
- * `message` event for the client's id correlation. Add `options.headers` (e.g. an
23
+ * decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's
24
+ * `message` event for the client's id correlation. Add `options.headers` (for example, an
25
25
  * `Authorization` bearer) to reach a guarded server. `start` / `close` hold no
26
26
  * connection; against a STATEFUL server it captures the `mcp-session-id` from
27
27
  * `initialize` and echoes it on later requests. It also captures the initialize
@@ -32,7 +32,7 @@ import { ToolManagerInterface } from '@orkestrel/tool';
32
32
  *
33
33
  * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged
34
34
  * onto every request, optional `fetch` (default `globalThis.fetch`), and optional
35
- * `timeout` (ms, applied via `AbortSignal.timeout`); see
35
+ * `timeout` (ms, applied with `AbortSignal.timeout`); see
36
36
  * {@link HTTPClientTransportOptions}
37
37
  * @returns A working {@link MCPClientTransportInterface} over the native `fetch`
38
38
  *
@@ -51,11 +51,11 @@ import { ToolManagerInterface } from '@orkestrel/tool';
51
51
  export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface_2;
52
52
 
53
53
  /**
54
- * Create the browser-face `MessagePort` transport — a
55
- * {@link import('@src/core').MCPTransportInterface} over a native `MessagePort`, the
54
+ * Creates the browser-face `MessagePort` transport — a
55
+ * {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the
56
56
  * SYMMETRIC carrier that works as either a server or a client transport depending on
57
- * which binder ({@link import('@src/core').bindServer} or
58
- * {@link import('@src/core').bindClient}) it is handed to.
57
+ * which binder ({@link import('@orkestrel/mcp').bindServer} or
58
+ * {@link import('@orkestrel/mcp').bindClient}) it is handed to.
59
59
  *
60
60
  * @remarks
61
61
  * `port.start()` runs at construction (see {@link MessagePortTransport}'s doc for
@@ -65,21 +65,22 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
65
65
  *
66
66
  * @param options - `port` (the `MessagePort` half to drive; REQUIRED); see
67
67
  * {@link MessagePortTransportOptions}
68
- * @returns A working {@link import('@src/core').MCPTransportInterface} over the port
68
+ * @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port
69
69
  *
70
70
  * @example
71
71
  * ```ts
72
- * import { bindServer, createMCPServer } from '@orkestrel/mcp'
72
+ * import { bindServer, createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
73
73
  * import { createMessagePortTransport } from '@orkestrel/mcp/browser'
74
74
  *
75
75
  * const { port1, port2 } = new MessageChannel()
76
- * bindServer(createMCPServer({ identity: { name: 's', version: '1.0.0' }, tools }), createMessagePortTransport({ port: port1 }))
76
+ * const mcp = createMCPServer({ identity: { name: 's', version: '1.0.0' }, tools })
77
+ * bindServer(createMCPLegacy(mcp), createMessagePortTransport({ port: port1 })) // answers `initialize` too; pass `mcp` alone for modern-only
77
78
  * ```
78
79
  */
79
80
  export declare function createMessagePortTransport(options: MessagePortTransportOptions): MCPTransportInterface;
80
81
 
81
82
  /**
82
- * Build `serveMCPScope`'s `message`-event listener — the unified
83
+ * Builds `serveMCPScope`'s `message`-event listener — the unified
83
84
  * dispatcher that routes EVERY inbound event on a hostable scope, portless or
84
85
  * port-bearing, to the right binding.
85
86
  *
@@ -88,42 +89,48 @@ export declare function createMessagePortTransport(options: MessagePortTransport
88
89
  * — when the gate returns `false` the event is dropped entirely (no binding, no reply).
89
90
  * Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,
90
91
  * `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)
91
- * into `teardowns`. A port that was already seen is IGNORED — repeated delivery of the
92
- * same `MessagePort` would create duplicate bindings over one port (→ duplicated replies),
93
- * so the listener tracks seen ports and silently drops repeats.
92
+ * into `teardowns` KEYED BY THAT PORT. A port already present is IGNORED — repeated delivery
93
+ * of the same `MessagePort` would create duplicate bindings over one port (→ duplicated
94
+ * replies), so a repeat is silently dropped.
95
+ *
96
+ * The key is what makes `teardowns` the ONLY place an accepted port is remembered. A separate
97
+ * seen-port set would be a second collection over the same lifetime, and the caller's disposer
98
+ * would have to remember to empty both — so a long-lived scope such as a Service Worker would
99
+ * retain every port it ever accepted, closed and unbound ones included. Membership answers
100
+ * "already bound?" and `clear()` drops the binding and the dedup together.
94
101
  *
95
102
  * This branch fires on EITHER a Service-Worker-shaped scope (its normal per-client
96
103
  * channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
97
104
  * (the unified design's deliberate cross-case, needing no upfront shape flag). An event
98
105
  * with NO ports and a STRING `data` is pushed onto `scopeTransport.deliver` (the
99
106
  * implicit, already-bound scope channel); any other event (no ports, non-string data)
100
- * is silently dropped — total (§14), never throws.
107
+ * is silently dropped — total, never throws.
101
108
  *
102
109
  * @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over
103
110
  * @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto
104
- * @param teardowns - The shared teardown set `serveMCPScope`'s dispose drains; each port-bearing event adds one entry
111
+ * @param teardowns - The shared teardown map `serveMCPScope`'s dispose drains and clears, keyed by the accepted port; each port-bearing event adds one entry
105
112
  * @param options - The `ServeMCPOptions` (for `options.accept`)
106
113
  * @returns The `message`-event listener to register (and later remove) on the scope
107
114
  *
108
115
  * @example
109
116
  * ```ts
110
- * const teardowns = new Set<() => void>()
117
+ * const teardowns = new Map<MessagePort, () => void>()
111
118
  * const scopeTransport = createScopeTransport(scope)
112
119
  * bindServer(server, scopeTransport)
113
120
  * const onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)
114
121
  * scope.addEventListener('message', onMessage)
115
122
  * ```
116
123
  */
117
- export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Set<() => void>, options: ServeMCPOptions): (event: MessageEvent) => void;
124
+ export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ServeMCPOptions): (event: MessageEvent) => void;
118
125
 
119
126
  /**
120
- * Adapt a hostable {@link ServeMCPScopeInterface} (`self` in a dedicated Web Worker,
127
+ * Adapts a hostable {@link ServeMCPScopeInterface} (`self` in a dedicated Web Worker,
121
128
  * or any structurally matching double) into a {@link ScopeTransportInterface} — the
122
129
  * implicit, portless message channel `serveMCPScope` binds for the
123
130
  * dedicated-worker shape.
124
131
  *
125
132
  * @remarks
126
- * `send` writes each outbound string via `scope.postMessage`. `listen`/`closed`
133
+ * `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
127
134
  * register the SINGLE handler `deliver` / the underlying close path route through —
128
135
  * `serveMCPScope`'s own `scope` `message`-event listener calls `deliver(event.data)`
129
136
  * for every portless, string-payload event (there is no native registration point on
@@ -133,7 +140,7 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
133
140
  *
134
141
  * @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`
135
142
  * inside a dedicated Web Worker)
136
- * @returns A {@link ScopeTransportInterface} `serveMCPScope` binds and drives via `deliver`
143
+ * @returns A {@link ScopeTransportInterface} `serveMCPScope` binds and drives through `deliver`
137
144
  *
138
145
  * @example
139
146
  * ```ts
@@ -144,10 +151,10 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
144
151
  export declare function createScopeTransport(scope: ServeMCPScopeInterface): ScopeTransportInterface;
145
152
 
146
153
  /**
147
- * Create the browser-face WebSocket CLIENT transport for an
148
- * {@link import('@src/core').MCPClientInterface} — a {@link MCPClientTransportInterface}
154
+ * Creates the browser-face WebSocket CLIENT transport for an
155
+ * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
149
156
  * that drives a REMOTE MCP server over the native `WebSocket` global, the browser
150
- * sibling of the Node face's `createWebSocketClientTransport` (`@src/server`).
157
+ * sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
151
158
  *
152
159
  * @remarks
153
160
  * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
@@ -177,13 +184,13 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
177
184
  export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface_2;
178
185
 
179
186
  /**
180
- * Decode one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
187
+ * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
181
188
  * when it is not one — the per-event step {@link readEventStream} folds over.
182
189
  *
183
190
  * @remarks
184
191
  * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the
185
192
  * event's `data`) inside a try/catch and narrows the parsed value with
186
- * `parseJSONRPCMessage`. Total (§14): malformed JSON or a non-message value yields
193
+ * `parseJSONRPCMessage`. Total: malformed JSON or a non-message value yields
187
194
  * `undefined`, never throws.
188
195
  *
189
196
  * @param data - One SSE event's `data` payload
@@ -201,7 +208,7 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
201
208
  * The browser-face HTTP CLIENT transport for the Model Context Protocol — a
202
209
  * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
203
210
  * over the native `fetch`, the browser sibling of the Node face's
204
- * {@link import('@src/server').HTTPClientTransport}, honoring the SAME
211
+ * {@link import('@orkestrel/mcp/server').HTTPClientTransport}, honoring the SAME
205
212
  * `mcp-session-id` semantics so it interoperates with an `MCPSession`-based server
206
213
  * unchanged.
207
214
  *
@@ -209,12 +216,12 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
209
216
  * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
210
217
  * message to `options.url` with `content-type: application/json` and an
211
218
  * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
212
- * answer with either framing) — plus any `options.headers` (e.g. an
219
+ * answer with either framing) — plus any `options.headers` (for example, an
213
220
  * `Authorization` bearer). It then decodes the reply and emits each decoded
214
221
  * {@link JSONRPCMessage} on the `message` event the
215
- * {@link import('@src/core').MCPClientInterface} subscribes to.
222
+ * {@link import('@orkestrel/mcp').MCPClientInterface} subscribes to.
216
223
  * - **Both reply framings.** A `200` with an `application/json` body is parsed with
217
- * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded via the
224
+ * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
218
225
  * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} (the browser
219
226
  * face's own `readEventStream`) — the inverse of the server's `openStream` seam, so
220
227
  * the wire round-trips. A `202` Accepted (a notification) carries no body and emits
@@ -232,10 +239,15 @@ export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
232
239
  * Before initialize returns, neither captured legacy header is sent.
233
240
  * `close()` clears the captured protocol so a reconnect's `initialize`
234
241
  * POST is headerless; the captured `session` persists across `close()`.
235
- * - **Total at the boundary (§14).** Every reply is narrowed (`parseJSONRPCMessage`,
242
+ * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
243
+ * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
244
+ * never ends would otherwise outlive the transport, with nothing left able to reach it. The
245
+ * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
246
+ * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
247
+ * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
236
248
  * the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
237
249
  * decode failure surfaces on the `error` event rather than escaping `send`.
238
- * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
250
+ * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
239
251
  * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
240
252
  *
241
253
  * @example
@@ -263,15 +275,17 @@ export declare class HTTPClientTransport implements MCPClientTransportInterface
263
275
  * @remarks
264
276
  * - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
265
277
  * `POST` target every JSON-RPC message is written to). REQUIRED.
266
- * - `headers` — extra request headers merged onto every `POST` (e.g. an
278
+ * - `headers` — extra request headers merged onto every `POST` (for example, an
267
279
  * `Authorization` bearer for a guarded server). The transport always sets
268
280
  * `content-type: application/json` and an `Accept` of both `application/json` and
269
281
  * `text/event-stream`; a key supplied here is merged on top.
270
282
  * - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
271
283
  * `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
272
284
  * - `timeout` — an optional per-request timeout in milliseconds; when set, each
273
- * `fetch` call is issued with `signal: AbortSignal.timeout(timeout)`. Omit for no
274
- * transport-level deadline.
285
+ * `fetch` call composes that deadline with the transport's own close through
286
+ * `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
287
+ * ends the request. Omit for no transport-level deadline; the close signal is passed
288
+ * either way.
275
289
  */
276
290
  export declare interface HTTPClientTransportOptions {
277
291
  readonly url: string;
@@ -310,8 +324,8 @@ export declare const MCP_SESSION_HEADER = "mcp-session-id";
310
324
 
311
325
  /**
312
326
  * The WebSocket subprotocol `createWebSocketClientTransport` requests by default —
313
- * `'mcp'`, matching `createWebSocketServer`'s unconditional `Sec-WebSocket-Protocol:
314
- * mcp` echo. Per RFC 6455 §4.1 a client MUST fail the connection if the server returns
327
+ * `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
328
+ * §4.1 a client MUST fail the connection if the server returns
315
329
  * a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
316
330
  * default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
317
331
  * only when connecting to a foreign server that speaks a different subprotocol (or `[]`
@@ -327,13 +341,13 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
327
341
  * @remarks
328
342
  * - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
329
343
  * / {@link import('./HTTPClientTransport.js').HTTPClientTransport} (CLIENT-only
330
- * carriers of `@src/core`'s `MCPClientTransportInterface`), a `MessagePort` is a
331
- * plain duplex channel — the SAME class implements `@src/core`'s
344
+ * carriers of `@orkestrel/mcp`'s `MCPClientTransportInterface`), a `MessagePort` is a
345
+ * plain duplex channel — the SAME class implements `@orkestrel/mcp`'s
332
346
  * `MCPTransportInterface` and is handed to EITHER `bindServer` or
333
347
  * `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
334
348
  * from the binder it is given to, not from anything this class decides.
335
349
  * - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only
336
- * REQUIRED when listening via `addEventListener` (as opposed to the `onmessage`
350
+ * REQUIRED when listening with `addEventListener` (as opposed to the `onmessage`
337
351
  * setter, which implies it) — this transport uses `addEventListener`, and
338
352
  * `MCPTransportInterface` has no separate open/connect step for the caller to hook
339
353
  * a start into, so the constructor calls `port.start()` immediately: the port
@@ -348,7 +362,7 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
348
362
  * structured-clones it — a string clones to an identical string, so the wire stays
349
363
  * plain JSON-RPC text like every other transport in this package). Inbound: a
350
364
  * non-string `event.data` (a host or a misbehaving peer posting a structured
351
- * object) is IGNORED — dropped silently, never forwarded, never thrown (§14)
365
+ * object) is IGNORED — dropped silently, never forwarded, never thrown —
352
366
  * because `MCPTransportInterface` carries no `error` channel for this port to
353
367
  * surface a non-string frame on (unlike `MCPClientTransportInterface`'s `emitter`);
354
368
  * silently ignoring is the total, contract-shaped choice.
@@ -366,8 +380,8 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
366
380
  * handler exactly once, whether the caller closes it once or twice. There is no
367
381
  * native "peer closed" signal for a `MessagePort` (unlike a WebSocket's `close`
368
382
  * event) — `closed` fires ONLY from this transport's own `close()`.
369
- * - **Single-handler-replace (the port contract, `@src/core`'s `MCPTransportInterface`
370
- * doc).** `listen`/`closed` each hold the ONE currently registered handler; a
383
+ * - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`
384
+ * doc).** `listen`/`closed` each hold the one active handler; a
371
385
  * second call REPLACES the first rather than adding a second subscriber.
372
386
  *
373
387
  * @example
@@ -395,17 +409,17 @@ export declare class MessagePortTransport implements MCPTransportInterface_2 {
395
409
  * {@link MessagePortTransport} sends and listens on.
396
410
  *
397
411
  * @remarks
398
- * `port` — the channel half to drive (e.g. one side of a `new MessageChannel()`, or
412
+ * `port` — the channel half to drive (for example, one side of a `new MessageChannel()`, or
399
413
  * the port a `message` event's `ports[0]` carried). REQUIRED. The SAME transport
400
414
  * works as either a server or a client carrier — the role comes from whether it is
401
- * handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@src/core`).
415
+ * handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@orkestrel/mcp`).
402
416
  */
403
417
  export declare interface MessagePortTransportOptions {
404
418
  readonly port: MessagePort;
405
419
  }
406
420
 
407
421
  /**
408
- * Decode a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
422
+ * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
409
423
  * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
410
424
  *
411
425
  * @remarks
@@ -413,8 +427,8 @@ export declare interface MessagePortTransportOptions {
413
427
  * stream: true })` (handling a multi-byte char split across reads) and
414
428
  * `@orkestrel/sse`'s {@link SSEParserInterface} (handling a partial line / in-progress
415
429
  * event split across reads), then narrows each dispatched event's `data` to a
416
- * {@link JSONRPCMessage} via {@link decodeEvent} (so a non-message / non-JSON `data:`
417
- * event is DROPPED, never thrown — total, §14). A `null` body (no stream) yields no
430
+ * {@link JSONRPCMessage} through {@link decodeEvent} (so a non-message / non-JSON `data:`
431
+ * event is DROPPED, never thrown — total). A `null` body (no stream) yields no
418
432
  * messages; {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
419
433
  * reads a request/response SSE reply (the server sends one `data:` event then ends),
420
434
  * so this drains to completion.
@@ -430,15 +444,19 @@ export declare function readEventStream(response: Response): Promise<readonly JS
430
444
  * internal carrier `serveMCPScope` binds to route the implicit (portless) message
431
445
  * channel, plus the `deliver` entry point the scope's own `message` listener pushes
432
446
  * an inbound string through (the scope itself never registers `listen`'s handler
433
- * for the caller — `serveMCPScope`'s dispatcher does, via this `deliver`).
447
+ * for the caller — `serveMCPScope`'s dispatcher does, through this `deliver`).
434
448
  */
435
449
  export declare interface ScopeTransportInterface extends MCPTransportInterface {
436
- /** Push one inbound message string into the currently registered `listen` handler. */
450
+ /** Pushes one inbound message string into the active `listen` handler. */
437
451
  deliver(message: string): void;
438
452
  }
439
453
 
440
454
  /**
441
- * Boot an `MCPServer` inside the current hostable worker scope.
455
+ * Boots an `MCPServer` inside the current hostable worker scope.
456
+ *
457
+ * @remarks
458
+ * The served endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A
459
+ * dual-era worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
442
460
  *
443
461
  * @param options - The tools, optional identity, and optional port-event gate
444
462
  * @returns The disposer returned by {@link serveMCPScope}
@@ -448,7 +466,7 @@ export declare function serveMCP(options: ServeMCPOptions): () => void;
448
466
  /**
449
467
  * Options for `serveMCP` / `serveMCPScope` — the live {@link ToolManagerInterface} to
450
468
  * expose plus the optional server identity, mirroring `createMCPServer`'s
451
- * `MCPServerOptions` (`@src/core`) but with `name`/`version` OPTIONAL (defaulting to
469
+ * `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version` OPTIONAL (defaulting to
452
470
  * {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
453
471
  * {@link import('./constants.js').DEFAULT_MCP_SERVER_VERSION}).
454
472
  *
@@ -459,7 +477,7 @@ export declare function serveMCP(options: ServeMCPOptions): () => void;
459
477
  * deliver directly to the implicit scope channel (the tool executes, blind; in a
460
478
  * Service Worker the reply is silently dropped — see `serveMCPScope`'s portless note).
461
479
  * Prefer a handshake token in `event.data` as the primary pattern
462
- * (e.g. `(event) => event.data === token`) — for same-origin worker/MessagePort
480
+ * (for example, `(event) => event.data === token`) — for same-origin worker/MessagePort
463
481
  * messages `event.origin` is frequently the empty string, making origin
464
482
  * allow-listing unreliable; origin checks are meaningful for cross-origin
465
483
  * `postMessage` only. When omitted, ALL port-bearing events are accepted — every
@@ -474,13 +492,16 @@ export declare interface ServeMCPOptions {
474
492
  }
475
493
 
476
494
  /**
477
- * Boot an `MCPServer` inside a hostable worker scope and wire its message events to it.
495
+ * Boots an `MCPServer` inside a hostable worker scope and wires its message events to it.
478
496
  *
479
497
  * @remarks
480
498
  * Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
481
499
  * their own `MessagePortTransport` binding. Portless string events use the scope's
482
500
  * implicit channel. The returned disposer removes the listener, unbinds the implicit
483
- * channel, and closes every accepted port binding.
501
+ * channel, closes every accepted port binding, and drops the ports themselves — the
502
+ * bindings are held in one map keyed by port, so nothing survives the clear. The served
503
+ * endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
504
+ * worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
484
505
  *
485
506
  * @param scope - The hostable worker scope to wire
486
507
  * @param options - The tools, optional identity, and optional port-event gate
@@ -493,7 +514,7 @@ export declare function serveMCPScope(scope: ServeMCPScopeInterface, options: Se
493
514
  * dedicated Web Worker or a Service Worker (or any double matching this shape).
494
515
  *
495
516
  * @remarks
496
- * Only the three members `serveMCPScope` actually touches: `postMessage` (the
517
+ * Only the members `serveMCPScope` actually touches: `postMessage` (the
497
518
  * dedicated-worker implicit reply channel), and `addEventListener` /
498
519
  * `removeEventListener` for `'message'` (every inbound event, portless or
499
520
  * port-bearing, arrives through the SAME listener — see {@link ServeMCPOptions}'s
@@ -510,7 +531,7 @@ export declare interface ServeMCPScopeInterface {
510
531
  * The browser-face WebSocket CLIENT transport for the Model Context Protocol — a
511
532
  * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over the native
512
533
  * `WebSocket` global, the browser sibling of the Node face's
513
- * {@link import('@src/server').WebSocketClientTransport}.
534
+ * {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
514
535
  *
515
536
  * @remarks
516
537
  * - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and
@@ -525,15 +546,16 @@ export declare interface ServeMCPScopeInterface {
525
546
  * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and
526
547
  * narrowed with `parseJSONRPCMessage` — a well-formed {@link JSONRPCMessage}
527
548
  * re-emits on this transport's `message` event; a non-text (binary) frame or a
528
- * non-JSON / non-message text frame surfaces on `error` and is DROPPED (§14 — never
549
+ * non-JSON / non-message text frame surfaces on `error` and is DROPPED (never
529
550
  * throws on adversarial wire input).
530
- * - **`close()`** closes the underlying socket and fires `close` (idempotent); the
531
- * socket's native `close` event (a server-initiated close) fires the SAME `close`
532
- * exactly once total — `close()` first flips the guard, so the native event never
533
- * double-emits. **This transport is not reusable after `close()`** a `send` issued
534
- * after `close()` is silently dropped (not queued, not delivered even on a later
535
- * `start()`).
536
- * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); every
551
+ * - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`
552
+ * (idempotent); the socket's native `close` event (a server-initiated close) fires the
553
+ * SAME `close` exactly once total — `close()` first flips the guard, so the native event
554
+ * never double-emits, and the released socket reports its own close to nobody. Closing before
555
+ * the socket opens resolves the pending `start()` rather than leaving it pending, matching the
556
+ * Node face. A `send` issued after `close()` is silently dropped (not queued), so a closed
557
+ * transport delivers nothing until a `start()` opens a new connection.
558
+ * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every
537
559
  * emit the emitter isolates a listener throw; `error` is a DOMAIN event (a
538
560
  * transport-level fault).
539
561
  *
@@ -564,8 +586,8 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
564
586
  * the native `WebSocket` constructor the same way) URL of the remote server's
565
587
  * WebSocket endpoint. REQUIRED.
566
588
  * - `protocols` — the WebSocket subprotocol(s) to request. **Defaults to
567
- * {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, matching
568
- * `createWebSocketServer`'s unconditional `Sec-WebSocket-Protocol: mcp` echo. Per
589
+ * {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
590
+ * `createWebSocketServer` selects when the offer contains it. Per
569
591
  * RFC 6455 §4.1 a client must fail the connection if the server returns a subprotocol
570
592
  * it did not request; Node ≥ 22 (undici) enforces this strictly, so the default saves
571
593
  * you from that trap when connecting to this repo's own server. Override only when