@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,40 +1,43 @@
1
- import { EmitterInterface } from '@orkestrel/emitter';
2
- import { JSONRPCMessage } from '@orkestrel/mcp';
3
- import { JSONRPCMessage as JSONRPCMessage_2 } from '@orkestrel/mcp';
4
- import { MCPClientTransportEventMap } from '@orkestrel/mcp';
5
- import { MCPClientTransportInterface } from '@orkestrel/mcp';
6
- import { MCPClientTransportInterface as MCPClientTransportInterface_2 } from '@orkestrel/mcp';
7
- import { MCPServerInterface } from '@orkestrel/mcp';
8
- import { MCPTransportInterface } from '@orkestrel/mcp';
9
- import { MCPTransportInterface as MCPTransportInterface_2 } from '@orkestrel/mcp';
10
- import { ToolManagerInterface } from '@orkestrel/tool';
1
+ import type { EmitterInterface } from '@orkestrel/emitter';
2
+ import type { HTTPClientTransportOptions } from '@orkestrel/mcp';
3
+ import type { JSONRPCMessage } from '@orkestrel/mcp';
4
+ import type { MCPMessageTransportEventMap } from '@orkestrel/mcp';
5
+ import type { MCPMessageTransportInterface } from '@orkestrel/mcp';
6
+ import type { MCPServerInterface } from '@orkestrel/mcp';
7
+ import type { MCPTransportInterface } from '@orkestrel/mcp';
8
+ import type { ToolManagerInterface } from '@orkestrel/tool';
11
9
 
12
10
  /**
13
- * Creates the browser-face HTTP CLIENT transport for an
14
- * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
15
- * that drives a REMOTE Streamable-HTTP MCP server over the native `fetch`, the
16
- * browser sibling of the Node face's `createHTTPClientTransport` (`@orkestrel/mcp/server`).
11
+ * Creates the HTTP client transport for an
12
+ * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
13
+ * that drives a remote Streamable-HTTP MCP server over the native `fetch`.
14
+ *
15
+ * @remarks
16
+ * It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
17
+ * Node face's `createHTTPClientTransport` returns, because the class touches `fetch`,
18
+ * `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone. This factory exists so a
19
+ * page imports its transport from the face it already imports everything else from.
17
20
  *
18
21
  * @remarks
19
22
  * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
20
23
  * sends is `POST`ed to `options.url` with `content-type: application/json` and an
21
24
  * `Accept` of both `application/json` and `text/event-stream` (the server answers
22
- * with EITHER — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
25
+ * with either — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
23
26
  * decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's
24
27
  * `message` event for the client's id correlation. Add `options.headers` (for example, an
25
28
  * `Authorization` bearer) to reach a guarded server. `start` / `close` hold no
26
- * connection; against a STATEFUL server it captures the `mcp-session-id` from
29
+ * connection; against a stateful server it captures the `mcp-session-id` from
27
30
  * `initialize` and echoes it on later requests. It also captures the initialize
28
31
  * result's `protocolVersion` and sends `mcp-protocol-version` alone on subsequent
29
32
  * legacy requests. Modern requests instead derive `mcp-protocol-version` and
30
33
  * `mcp-method` from the message, plus `mcp-name` only for `tools/call`, so the
31
34
  * same `MCPClient` passes either era's protocol gates without caller wiring.
32
35
  *
33
- * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged
36
+ * @param options - `url` (the remote endpoint; required), optional `headers` merged
34
37
  * onto every request, optional `fetch` (default `globalThis.fetch`), and optional
35
38
  * `timeout` (ms, applied with `AbortSignal.timeout`); see
36
39
  * {@link HTTPClientTransportOptions}
37
- * @returns A working {@link MCPClientTransportInterface} over the native `fetch`
40
+ * @returns A working {@link MCPMessageTransportInterface} over the native `fetch`
38
41
  *
39
42
  * @example
40
43
  * ```ts
@@ -48,12 +51,12 @@ import { ToolManagerInterface } from '@orkestrel/tool';
48
51
  * const tools = await client.tools()
49
52
  * ```
50
53
  */
51
- export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface_2;
54
+ export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface;
52
55
 
53
56
  /**
54
57
  * Creates the browser-face `MessagePort` transport — a
55
58
  * {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the
56
- * SYMMETRIC carrier that works as either a server or a client transport depending on
59
+ * symmetric carrier that works as either a server or a client transport depending on
57
60
  * which binder ({@link import('@orkestrel/mcp').bindServer} or
58
61
  * {@link import('@orkestrel/mcp').bindClient}) it is handed to.
59
62
  *
@@ -63,7 +66,7 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
63
66
  * dropped, never thrown); `messageerror` is ignored (one bad frame does not close the
64
67
  * channel); `close()` closes the port and fires `closed` exactly once.
65
68
  *
66
- * @param options - `port` (the `MessagePort` half to drive; REQUIRED); see
69
+ * @param options - `port` (the `MessagePort` half to drive; required); see
67
70
  * {@link MessagePortTransportOptions}
68
71
  * @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port
69
72
  *
@@ -80,36 +83,36 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
80
83
  export declare function createMessagePortTransport(options: MessagePortTransportOptions): MCPTransportInterface;
81
84
 
82
85
  /**
83
- * Builds `serveMCPScope`'s `message`-event listener — the unified
84
- * dispatcher that routes EVERY inbound event on a hostable scope, portless or
85
- * port-bearing, to the right binding.
86
+ * Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that
87
+ * routes every inbound event on a hostable scope, portless or port-bearing, to the right
88
+ * binding.
86
89
  *
87
90
  * @remarks
88
- * Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` FIRST
91
+ * Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` first
89
92
  * — when the gate returns `false` the event is dropped entirely (no binding, no reply).
90
93
  * Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,
91
94
  * `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)
92
- * into `teardowns` KEYED BY THAT PORT. A port already present is IGNORED — repeated delivery
95
+ * into `teardowns` keyed by that port. A port already present is ignored — repeated delivery
93
96
  * of the same `MessagePort` would create duplicate bindings over one port (→ duplicated
94
97
  * replies), so a repeat is silently dropped.
95
98
  *
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.
99
+ * The key is what makes `teardowns` the only place an accepted port is remembered. A separate
100
+ * seen-port set would be a second collection over the same lifetime, and the scope server's
101
+ * `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker
102
+ * would retain every port it ever accepted, closed and unbound ones included. Membership
103
+ * answers "already bound?" and `clear()` drops the binding and the dedup together.
101
104
  *
102
- * This branch fires on EITHER a Service-Worker-shaped scope (its normal per-client
105
+ * This branch fires on either a Service-Worker-shaped scope (its normal per-client
103
106
  * channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
104
107
  * (the unified design's deliberate cross-case, needing no upfront shape flag). An event
105
- * with NO ports and a STRING `data` is pushed onto `scopeTransport.deliver` (the
108
+ * with no ports and a string `data` is pushed onto `scopeTransport.deliver` (the
106
109
  * implicit, already-bound scope channel); any other event (no ports, non-string data)
107
110
  * is silently dropped — total, never throws.
108
111
  *
109
112
  * @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over
110
113
  * @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto
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
112
- * @param options - The `ServeMCPOptions` (for `options.accept`)
114
+ * @param teardowns - The shared teardown map the scope server's `stop` drains and clears, keyed by the accepted port; each port-bearing event adds one entry
115
+ * @param options - The `ScopeServerOptions` (for `options.accept`)
113
116
  * @returns The `message`-event listener to register (and later remove) on the scope
114
117
  *
115
118
  * @example
@@ -121,26 +124,60 @@ export declare function createMessagePortTransport(options: MessagePortTransport
121
124
  * scope.addEventListener('message', onMessage)
122
125
  * ```
123
126
  */
124
- export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ServeMCPOptions): (event: MessageEvent) => void;
127
+ export declare function createScopeMessageListener(server: MCPServerInterface, scopeTransport: ScopeTransportInterface, teardowns: Map<MessagePort, () => void>, options: ScopeServerOptions): (event: MessageEvent) => void;
128
+
129
+ /**
130
+ * Creates an `MCPServer` hosted inside a worker scope and wires that scope's message events
131
+ * to it — the browser face's bootstrap, and the twin of the Node face's `createStdioServer`.
132
+ *
133
+ * @remarks
134
+ * `scope` defaults to `globalThis`, which is `self` inside a dedicated Web Worker or a
135
+ * Service Worker, so a worker boots with `createScopeServer({ tools })` alone; pass a scope
136
+ * explicitly to host a server on a double or on another message-event-bearing object.
137
+ *
138
+ * Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
139
+ * their own `MessagePortTransport` binding. Portless string events use the scope's
140
+ * implicit channel. The returned handle's `stop` removes the listener, unbinds the implicit
141
+ * channel, closes every accepted port binding, and drops the ports themselves — the
142
+ * bindings are held in one map keyed by port, so nothing survives the clear. The served
143
+ * endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
144
+ * worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this factory.
145
+ *
146
+ * @param options - The tools, optional identity, and optional port-event gate; see
147
+ * {@link ScopeServerOptions}
148
+ * @param scope - The hostable scope to wire; defaults to `globalThis`
149
+ * @returns A {@link ScopeServerInterface} whose `stop` ends every binding this call owns
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * import { createScopeServer } from '@orkestrel/mcp/browser'
154
+ * import { createToolManager } from '@orkestrel/tool'
155
+ *
156
+ * // Inside a Web Worker: the scope defaults to `globalThis`.
157
+ * const worker = createScopeServer({ tools: createToolManager() })
158
+ * // ... later, release every binding this call owns:
159
+ * worker.stop()
160
+ * ```
161
+ */
162
+ export declare function createScopeServer(options: ScopeServerOptions, scope?: ScopeInterface): ScopeServerInterface;
125
163
 
126
164
  /**
127
- * Adapts a hostable {@link ServeMCPScopeInterface} (`self` in a dedicated Web Worker,
128
- * or any structurally matching double) into a {@link ScopeTransportInterface} — the
129
- * implicit, portless message channel `serveMCPScope` binds for the
130
- * dedicated-worker shape.
165
+ * Adapts a hostable {@link ScopeInterface} (`self` in a dedicated Web Worker, or any
166
+ * structurally matching double) into a {@link ScopeTransportInterface} — the implicit,
167
+ * portless message channel {@link createScopeServer} binds for the dedicated-worker shape.
131
168
  *
132
169
  * @remarks
133
170
  * `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
134
- * register the SINGLE handler `deliver` / the underlying close path route through —
135
- * `serveMCPScope`'s own `scope` `message`-event listener calls `deliver(event.data)`
171
+ * register the single handler `deliver` / the underlying close path route through —
172
+ * the scope server's own `scope` `message`-event listener calls `deliver(event.data)`
136
173
  * for every portless, string-payload event (there is no native registration point on
137
- * the scope itself for `serveMCPScope` to hand a `listen` handler to, so `deliver` is
174
+ * the scope itself for the scope server to hand a `listen` handler to, so `deliver` is
138
175
  * the bridge). `close()` fires the registered `closed` handler — a scope has nothing
139
176
  * physically closable, so this is the only teardown signal available.
140
177
  *
141
178
  * @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`
142
179
  * inside a dedicated Web Worker)
143
- * @returns A {@link ScopeTransportInterface} `serveMCPScope` binds and drives through `deliver`
180
+ * @returns A {@link ScopeTransportInterface} the scope server binds and drives through `deliver`
144
181
  *
145
182
  * @example
146
183
  * ```ts
@@ -148,26 +185,26 @@ export declare function createScopeMessageListener(server: MCPServerInterface, s
148
185
  * const unbind = bindServer(server, scopeTransport)
149
186
  * ```
150
187
  */
151
- export declare function createScopeTransport(scope: ServeMCPScopeInterface): ScopeTransportInterface;
188
+ export declare function createScopeTransport(scope: ScopeInterface): ScopeTransportInterface;
152
189
 
153
190
  /**
154
- * Creates the browser-face WebSocket CLIENT transport for an
155
- * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPClientTransportInterface}
156
- * that drives a REMOTE MCP server over the native `WebSocket` global, the browser
157
- * sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
191
+ * Creates the browser-face WebSocket client transport for an
192
+ * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
193
+ * that drives a remote MCP server over the native `WebSocket` global. This factory is the
194
+ * browser sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
158
195
  *
159
196
  * @remarks
160
197
  * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
161
198
  * opens `new WebSocket(options.url, options.protocols)` and awaits the native
162
199
  * `'open'` event — the RFC 6455 handshake itself is the browser's concern. Each
163
- * JSON-RPC message the client `send`s before the socket opens is QUEUED and flushed,
200
+ * JSON-RPC message the client `send`s before the socket opens is queued and flushed,
164
201
  * in order, once it does; each decoded reply is surfaced on the transport's
165
202
  * `message` event for the client's id correlation.
166
203
  *
167
- * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional
204
+ * @param options - `url` (the remote WebSocket endpoint; required) and optional
168
205
  * `protocols` (the WebSocket subprotocol(s) to request); see
169
206
  * {@link WebSocketClientTransportOptions}
170
- * @returns A working {@link MCPClientTransportInterface} over the native `WebSocket`
207
+ * @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`
171
208
  *
172
209
  * @example
173
210
  * ```ts
@@ -181,209 +218,65 @@ export declare function createScopeTransport(scope: ServeMCPScopeInterface): Sco
181
218
  * const tools = await client.tools()
182
219
  * ```
183
220
  */
184
- export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface_2;
221
+ export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface;
185
222
 
186
- /**
187
- * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
188
- * when it is not one — the per-event step {@link readEventStream} folds over.
189
- *
190
- * @remarks
191
- * `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the
192
- * event's `data`) inside a try/catch and narrows the parsed value with
193
- * `parseJSONRPCMessage`. Total: malformed JSON or a non-message value yields
194
- * `undefined`, never throws.
195
- *
196
- * @param data - One SSE event's `data` payload
197
- * @returns The decoded {@link JSONRPCMessage}, or `undefined`
198
- */
199
- export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
200
-
201
- /** The default server name `serveMCPScope` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */
202
- export declare const DEFAULT_MCP_SERVER_NAME = "taverna";
223
+ /** Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */
224
+ export declare const DEFAULT_MCP_SERVER_NAME = "@orkestrel/mcp";
203
225
 
204
- /** The default server version `serveMCPScope` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */
226
+ /** Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */
205
227
  export declare const DEFAULT_MCP_SERVER_VERSION = "1.0.0";
206
228
 
207
229
  /**
208
- * The browser-face HTTP CLIENT transport for the Model Context Protocol — a
209
- * {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
210
- * over the native `fetch`, the browser sibling of the Node face's
211
- * {@link import('@orkestrel/mcp/server').HTTPClientTransport}, honoring the SAME
212
- * `mcp-session-id` semantics so it interoperates with an `MCPSession`-based server
213
- * unchanged.
214
- *
215
- * @remarks
216
- * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
217
- * message to `options.url` with `content-type: application/json` and an
218
- * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
219
- * answer with either framing) — plus any `options.headers` (for example, an
220
- * `Authorization` bearer). It then decodes the reply and emits each decoded
221
- * {@link JSONRPCMessage} on the `message` event the
222
- * {@link import('@orkestrel/mcp').MCPClientInterface} subscribes to.
223
- * - **Both reply framings.** A `200` with an `application/json` body is parsed with
224
- * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
225
- * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} (the browser
226
- * face's own `readEventStream`) — the inverse of the server's `openStream` seam, so
227
- * the wire round-trips. A `202` Accepted (a notification) carries no body and emits
228
- * nothing.
229
- * - **Session and protocol headers.** `start()` is a no-op (a
230
- * request/response transport opens no long-lived connection). The
231
- * `mcp-session-id` response header, when a STATEFUL server sends one (on
232
- * `initialize`), is captured into `session` and then ECHOED as the
233
- * `mcp-session-id` request header on every SUBSEQUENT request — so an
234
- * `MCPClient` passes a stateful server's session validation. The
235
- * initialize result's `protocolVersion` is likewise captured, but only
236
- * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
237
- * subsequent legacy requests. Modern requests instead derive protocol and method
238
- * headers from the message, plus the name header only for `tools/call` — carried in the
239
- * protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
240
- * Before initialize returns, neither captured legacy header is sent.
241
- * `close()` clears the captured protocol so a reconnect's `initialize`
242
- * POST is headerless; the captured `session` persists across `close()`.
243
- * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
244
- * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
245
- * never ends would otherwise outlive the transport, with nothing left able to reach it. The
246
- * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
247
- * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
248
- * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
249
- * the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
250
- * decode failure surfaces on the `error` event rather than escaping `send`.
251
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
252
- * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
253
- *
254
- * @example
255
- * ```ts
256
- * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
257
- * const client = new MCPClient({ transport })
258
- * await client.connect()
259
- * ```
260
- */
261
- export declare class HTTPClientTransport implements MCPClientTransportInterface {
262
- #private;
263
- constructor(options: HTTPClientTransportOptions);
264
- get emitter(): EmitterInterface<MCPClientTransportEventMap>;
265
- get session(): string | undefined;
266
- get duplex(): boolean;
267
- start(): Promise<void>;
268
- send(message: JSONRPCMessage_2): Promise<void>;
269
- close(): Promise<void>;
270
- }
271
-
272
- /**
273
- * Options for `createHTTPClientTransport` (browser face) — the remote MCP server's
274
- * URL and any extra request headers.
275
- *
276
- * @remarks
277
- * - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
278
- * `POST` target every JSON-RPC message is written to). REQUIRED.
279
- * - `headers` — extra request headers merged onto every `POST` (for example, an
280
- * `Authorization` bearer for a guarded server). The transport always sets
281
- * `content-type: application/json` and an `Accept` of both `application/json` and
282
- * `text/event-stream`; a key supplied here is merged on top.
283
- * - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
284
- * `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
285
- * - `timeout` — an optional per-request timeout in milliseconds; when set, each
286
- * `fetch` call composes that deadline with the transport's own close through
287
- * `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
288
- * ends the request. Omit for no transport-level deadline; the close signal is passed
289
- * either way.
290
- */
291
- export declare interface HTTPClientTransportOptions {
292
- readonly url: string;
293
- readonly headers?: Readonly<Record<string, string>>;
294
- readonly fetch?: typeof fetch;
295
- readonly timeout?: number;
296
- }
297
-
298
- /**
299
- * The modern Streamable-HTTP request header carrying the JSON-RPC method. It is
300
- * emitted on every modern request and never on a legacy request.
301
- */
302
- export declare const MCP_METHOD_HEADER = "mcp-method";
303
-
304
- /**
305
- * The modern Streamable-HTTP request header carrying a named target. The browser
306
- * HTTP client emits it only for `tools/call`, from that request's `params.name`.
307
- */
308
- export declare const MCP_NAME_HEADER = "mcp-name";
309
-
310
- /**
311
- * The Streamable-HTTP transport header carrying the MCP protocol version. Modern
312
- * requests derive it from their own `_meta`; legacy requests echo the negotiated
313
- * initialize result on each subsequent request.
314
- */
315
- export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
316
-
317
- /**
318
- * The Streamable-HTTP transport header that carries the MCP session id. The browser
319
- * face's {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
320
- * ECHOES this header exactly like the Node face's `HTTPClientTransport`
321
- * (`src/server`), so the same client interoperates with an `MCPSession`-based
322
- * server unchanged.
323
- */
324
- export declare const MCP_SESSION_HEADER = "mcp-session-id";
325
-
326
- /**
327
- * The WebSocket subprotocol `createWebSocketClientTransport` requests by default —
328
- * `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
329
- * §4.1 a client MUST fail the connection if the server returns
330
- * a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
331
- * default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
332
- * only when connecting to a foreign server that speaks a different subprotocol (or `[]`
333
- * for no subprotocol negotiation at all).
334
- */
335
- export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
336
-
337
- /**
338
- * The browser-face `MessagePort` transport for the Model Context Protocol — a
339
- * {@link MCPTransportInterface} over a native `MessagePort`, the genuinely new
340
- * capability this face adds: MCP over `postMessage`.
230
+ * Carries the Model Context Protocol over a native `MessagePort` from the browser face — a
231
+ * {@link MCPTransportInterface}, the genuinely new capability this face adds: MCP over
232
+ * `postMessage`.
341
233
  *
342
234
  * @remarks
343
235
  * - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
344
- * / {@link import('./HTTPClientTransport.js').HTTPClientTransport} (CLIENT-only
345
- * carriers of `@orkestrel/mcp`'s `MCPClientTransportInterface`), a `MessagePort` is a
346
- * plain duplex channel — the SAME class implements `@orkestrel/mcp`'s
347
- * `MCPTransportInterface` and is handed to EITHER `bindServer` or
236
+ * / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only
237
+ * carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a
238
+ * plain duplex channel — the same class implements `@orkestrel/mcp`'s
239
+ * `MCPTransportInterface` and is handed to either `bindServer` or
348
240
  * `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
349
241
  * from the binder it is given to, not from anything this class decides.
350
242
  * - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only
351
- * REQUIRED when listening with `addEventListener` (as opposed to the `onmessage`
243
+ * required when listening with `addEventListener` (as opposed to the `onmessage`
352
244
  * setter, which implies it) — this transport uses `addEventListener`, and
353
245
  * `MCPTransportInterface` has no separate open/connect step for the caller to hook
354
246
  * a start into, so the constructor calls `port.start()` immediately: the port
355
- * begins dispatching QUEUED messages the moment the transport exists. This is safe
356
- * inside `serveMCP`'s flow (the transport is synchronously handed to `bindServer`
247
+ * begins dispatching queued messages the moment the transport exists. This is safe
248
+ * inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`
357
249
  * before control returns to the event loop), but is a **footgun for direct use**:
358
250
  * if you construct `new MessagePortTransport({ port })` and then `await` anything
359
- * before calling `listen`, messages that arrived in the gap are DROPPED. **Bind
251
+ * before calling `listen`, messages that arrived in the gap are dropped. **Bind
360
252
  * synchronously after construction** — do not interleave an `await` between
361
253
  * `new MessagePortTransport(…)` and `bindServer` / `listen`.
362
254
  * - **String payloads only.** `send` posts the message string as-is (`postMessage`
363
255
  * structured-clones it — a string clones to an identical string, so the wire stays
364
256
  * plain JSON-RPC text like every other transport in this package). Inbound: a
365
257
  * non-string `event.data` (a host or a misbehaving peer posting a structured
366
- * object) is IGNORED — dropped silently, never forwarded, never thrown —
258
+ * object) is ignored — dropped silently, never forwarded, never thrown —
367
259
  * because `MCPTransportInterface` carries no `error` channel for this port to
368
- * surface a non-string frame on (unlike `MCPClientTransportInterface`'s `emitter`);
260
+ * surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);
369
261
  * silently ignoring is the total, contract-shaped choice.
370
- * - **`messageerror` is IGNORED, not routed to `closed`.** A `messageerror` event
262
+ * - **`messageerror` is ignored, not routed to `closed`.** A `messageerror` event
371
263
  * (the structured-clone deserialization of an inbound message threw) reports one
372
- * BAD FRAME, not a dead channel — the port itself keeps working and later, well-
373
- * formed messages still arrive. Routing it to `closed` would tear down the
264
+ * bad frame, not a dead channel — the port itself keeps working and later, well-
265
+ * formed messages still arrive. This transport registers no listener for it: an
266
+ * unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor
267
+ * reaches this transport, so one bad frame costs exactly that frame and nothing
268
+ * tears the binding down. Routing it to `closed` would tear down the
374
269
  * `bindServer`/`bindClient` wiring (and, transitively, every session it carries)
375
- * over a single malformed frame, which is far more destructive than dropping that
376
- * one frame — so this transport registers a `messageerror` listener that does
377
- * nothing, deliberately.
270
+ * over a single malformed frame.
378
271
  * - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`
379
- * disconnects it — further `postMessage` calls on EITHER end are silently
272
+ * disconnects it — further `postMessage` calls on either end are silently
380
273
  * undelivered, per the platform contract) and fires the registered `closed`
381
274
  * handler exactly once, whether the caller closes it once or twice. There is no
382
275
  * native "peer closed" signal for a `MessagePort` (unlike a WebSocket's `close`
383
- * event) — `closed` fires ONLY from this transport's own `close()`.
276
+ * event) — `closed` fires only from this transport's own `close()`.
384
277
  * - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`
385
278
  * doc).** `listen`/`closed` each hold the one active handler; a
386
- * second call REPLACES the first rather than adding a second subscriber.
279
+ * second call replaces the first rather than adding a second subscriber.
387
280
  *
388
281
  * @example
389
282
  * ```ts
@@ -396,7 +289,7 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
396
289
  * bindClient(client, clientTransport) // port2 side is the client's carrier
397
290
  * ```
398
291
  */
399
- export declare class MessagePortTransport implements MCPTransportInterface_2 {
292
+ export declare class MessagePortTransport implements MCPTransportInterface {
400
293
  #private;
401
294
  constructor(options: MessagePortTransportOptions);
402
295
  send(message: string): void;
@@ -411,7 +304,7 @@ export declare class MessagePortTransport implements MCPTransportInterface_2 {
411
304
  *
412
305
  * @remarks
413
306
  * `port` — the channel half to drive (for example, one side of a `new MessageChannel()`, or
414
- * the port a `message` event's `ports[0]` carried). REQUIRED. The SAME transport
307
+ * the port a `message` event's `ports[0]` carried). Required. The same transport
415
308
  * works as either a server or a client carrier — the role comes from whether it is
416
309
  * handed to `bindServer` or `bindClient`/`createDuplexClientTransport` (`@orkestrel/mcp`).
417
310
  */
@@ -420,72 +313,63 @@ export declare interface MessagePortTransportOptions {
420
313
  }
421
314
 
422
315
  /**
423
- * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
424
- * carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
316
+ * Describes the structural shape {@link import('./factories.js').createScopeServer} needs
317
+ * from a
318
+ * hostable scope — `self` in a dedicated Web Worker or a Service Worker (or any double
319
+ * matching this shape).
425
320
  *
426
321
  * @remarks
427
- * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({
428
- * stream: true })` (handling a multi-byte char split across reads) and
429
- * `@orkestrel/sse`'s {@link SSEParserInterface} (handling a partial line / in-progress
430
- * event split across reads), then narrows each dispatched event's `data` to a
431
- * {@link JSONRPCMessage} through {@link decodeEvent} (so a non-message / non-JSON `data:`
432
- * event is DROPPED, never thrown total). A `null` body (no stream) yields no
433
- * messages; {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
434
- * reads a request/response SSE reply (the server sends one `data:` event then ends),
435
- * so this drains to completion.
436
- *
437
- * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
438
- * @returns Every {@link JSONRPCMessage} the stream carried, in order
439
- */
440
- export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
441
-
442
- /**
443
- * A duplex {@link MCPTransportInterface} adapting a message-event-bearing SCOPE
444
- * (`self` in a dedicated Web Worker, or any object shaped the same way) — the
445
- * internal carrier `serveMCPScope` binds to route the implicit (portless) message
446
- * channel, plus the `deliver` entry point the scope's own `message` listener pushes
447
- * an inbound string through (the scope itself never registers `listen`'s handler
448
- * for the caller — `serveMCPScope`'s dispatcher does, through this `deliver`).
322
+ * Only the members the scope server actually touches: `postMessage` (the
323
+ * dedicated-worker implicit reply channel), and `addEventListener` /
324
+ * `removeEventListener` for `'message'` (every inbound event, portless or
325
+ * port-bearing, arrives through the same listener see {@link ScopeServerOptions}'s
326
+ * doc and the factory). A real `self` / `globalThis` inside a worker satisfies this
327
+ * structurally (it exposes far more, which this narrower shape ignores).
449
328
  */
450
- export declare interface ScopeTransportInterface extends MCPTransportInterface {
451
- /** Pushes one inbound message string into the active `listen` handler. */
452
- deliver(message: string): void;
329
+ export declare interface ScopeInterface {
330
+ postMessage(message: unknown): void;
331
+ addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
332
+ removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
453
333
  }
454
334
 
455
335
  /**
456
- * Boots an `MCPServer` inside the current hostable worker scope.
336
+ * Represents one MCP server hosted inside a worker scope — what
337
+ * {@link import('./factories.js').createScopeServer} returns.
457
338
  *
458
339
  * @remarks
459
- * The served endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A
460
- * dual-era worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
461
- *
462
- * @param options - The tools, optional identity, and optional port-event gate
463
- * @returns The disposer returned by {@link serveMCPScope}
340
+ * The browser twin of the Node face's `StdioServerInterface`, and it publishes only the
341
+ * terminal: the factory arms the scope's `message` listener before it returns, because an
342
+ * event delivered between construction and an explicit `start` would reach nothing. `stop`
343
+ * removes that listener, unbinds the implicit scope channel, and tears down every accepted
344
+ * port binding; it is idempotent, and it ends this handle's lifetime permanently.
464
345
  */
465
- export declare function serveMCP(options: ServeMCPOptions): () => void;
346
+ export declare interface ScopeServerInterface {
347
+ /** Ends every binding this scope server owns — idempotent, and permanent for this handle. */
348
+ stop(): void;
349
+ }
466
350
 
467
351
  /**
468
- * Options for `serveMCP` / `serveMCPScope` — the live {@link ToolManagerInterface} to
469
- * expose plus the optional server identity, mirroring `createMCPServer`'s
470
- * `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version` OPTIONAL (defaulting to
471
- * {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
352
+ * Options for {@link import('./factories.js').createScopeServer} — the live
353
+ * {@link ToolManagerInterface} to expose plus the optional server identity, mirroring
354
+ * `createMCPServer`'s `MCPServerOptions` (`@orkestrel/mcp`) but with `name`/`version`
355
+ * optional (defaulting to {@link import('./constants.js').DEFAULT_MCP_SERVER_NAME} /
472
356
  * {@link import('./constants.js').DEFAULT_MCP_SERVER_VERSION}).
473
357
  *
474
358
  * @remarks
475
359
  * - `accept` — optional identity gate consulted **before** a port-bearing `message`
476
360
  * event is accepted; return `false` to drop the event (no binding, no reply).
477
- * **`accept` gates ONLY port-bearing events** — portless messages bypass it and
361
+ * **`accept` gates only port-bearing events** — portless messages bypass it and
478
362
  * deliver directly to the implicit scope channel (the tool executes, blind; in a
479
- * Service Worker the reply is silently dropped — see `serveMCPScope`'s portless note).
363
+ * Service Worker the reply is silently dropped — see `createScopeServer`'s portless note).
480
364
  * Prefer a handshake token in `event.data` as the primary pattern
481
365
  * (for example, `(event) => event.data === token`) — for same-origin worker/MessagePort
482
366
  * messages `event.origin` is frequently the empty string, making origin
483
367
  * allow-listing unreliable; origin checks are meaningful for cross-origin
484
- * `postMessage` only. When omitted, ALL port-bearing events are accepted — every
368
+ * `postMessage` only. When omitted, all port-bearing events are accepted — every
485
369
  * same-origin context that can reach the scope gets full tool-call access.
486
- * See `serveMCPScope`'s trust-boundary and portless-events notes.
370
+ * See `createScopeServer`'s trust-boundary and portless-events notes.
487
371
  */
488
- export declare interface ServeMCPOptions {
372
+ export declare interface ScopeServerOptions {
489
373
  readonly tools: ToolManagerInterface;
490
374
  readonly name?: string;
491
375
  readonly version?: string;
@@ -493,79 +377,55 @@ export declare interface ServeMCPOptions {
493
377
  }
494
378
 
495
379
  /**
496
- * Boots an `MCPServer` inside a hostable worker scope and wires its message events to it.
497
- *
498
- * @remarks
499
- * Port-bearing events are gated by `options.accept`, deduplicated by port, and receive
500
- * their own `MessagePortTransport` binding. Portless string events use the scope's
501
- * implicit channel. The returned disposer removes the listener, unbinds the implicit
502
- * channel, closes every accepted port binding, and drops the ports themselves — the
503
- * bindings are held in one map keyed by port, so nothing survives the clear. The served
504
- * endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era
505
- * worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this function.
506
- *
507
- * @param scope - The hostable worker scope to wire
508
- * @param options - The tools, optional identity, and optional port-event gate
509
- * @returns An idempotent disposer for every binding owned by this call
510
- */
511
- export declare function serveMCPScope(scope: ServeMCPScopeInterface, options: ServeMCPOptions): () => void;
512
-
513
- /**
514
- * The structural shape `serveMCPScope` needs from a hostable scope — `self` in a
515
- * dedicated Web Worker or a Service Worker (or any double matching this shape).
516
- *
517
- * @remarks
518
- * Only the members `serveMCPScope` actually touches: `postMessage` (the
519
- * dedicated-worker implicit reply channel), and `addEventListener` /
520
- * `removeEventListener` for `'message'` (every inbound event, portless or
521
- * port-bearing, arrives through the SAME listener — see {@link ServeMCPOptions}'s
522
- * doc and the bootstrap factories). A real `self` / `globalThis` inside a worker satisfies this
523
- * structurally (it exposes far more, which this narrower shape ignores).
380
+ * Adapts a message-event-bearing scope (`self` in a dedicated Web Worker, or any object
381
+ * shaped the same way) as a duplex {@link MCPTransportInterface} — the
382
+ * internal carrier `createScopeServer` binds to route the implicit (portless) message
383
+ * channel, plus the `deliver` entry point the scope's own `message` listener pushes
384
+ * an inbound string through (the scope itself never registers `listen`'s handler
385
+ * for the caller the scope server's dispatcher does, through this `deliver`).
524
386
  */
525
- export declare interface ServeMCPScopeInterface {
526
- postMessage(message: unknown): void;
527
- addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
528
- removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
387
+ export declare interface ScopeTransportInterface extends MCPTransportInterface {
388
+ /** Pushes one inbound message string into the active `listen` handler. */
389
+ deliver(message: string): void;
529
390
  }
530
391
 
531
392
  /**
532
- * The browser-face WebSocket CLIENT transport for the Model Context Protocol a
533
- * {@link MCPClientTransportInterface} that drives a REMOTE MCP server over the native
534
- * `WebSocket` global, the browser sibling of the Node face's
535
- * {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
393
+ * Drives a remote MCP server over the native `WebSocket` global from the browser face, as a
394
+ * client {@link MCPMessageTransportInterface}. This class is the browser sibling of the Node
395
+ * face's {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
536
396
  *
537
397
  * @remarks
538
398
  * - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and
539
399
  * waits for the native `'open'` event — the RFC 6455 handshake itself is entirely
540
400
  * the host's concern, so this transport carries none of the Node client's
541
401
  * `node:crypto` / `node:http(s)` machinery. A connection failure (the native
542
- * `'error'` event while not yet `OPEN`) REJECTS `start()`.
402
+ * `'error'` event while not yet `OPEN`) rejects `start()`.
543
403
  * - **Queued sends.** `send` writes each message as one text frame immediately once
544
404
  * the socket is `OPEN`; a `send` issued before `'open'` fires (or before `start()`
545
- * is even called) is QUEUED and flushed, IN ORDER, the moment the socket opens —
546
- * so a caller need not await `start()` before calling `send`. A queue rides ONE
547
- * connection: a close DISCARDS whatever is still in it.
548
- * - **A closed channel REJECTS.** The native socket confirms nothing about a write, so this
405
+ * is even called) is queued and flushed, in order, the moment the socket opens —
406
+ * so a caller need not await `start()` before calling `send`. A queue rides one
407
+ * connection: a close discards whatever is still in it.
408
+ * - **A closed channel rejects.** The native socket confirms nothing about a write, so this
549
409
  * transport answers from its own state: a `send` after `close()`, or on a socket already
550
- * reporting `CLOSING` / `CLOSED`, REJECTS with `WebSocket transport is not connected` rather
410
+ * reporting `CLOSING` / `CLOSED`, rejects with `WebSocket transport is not connected` rather
551
411
  * than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`
552
412
  * still queues.
553
- * - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and
554
- * narrowed with `parseJSONRPCMessage` — a well-formed {@link JSONRPCMessage}
413
+ * - **Inbound (`message`).** Each decoded text frame runs through the shared
414
+ * `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}
555
415
  * re-emits on this transport's `message` event; a non-text (binary) frame or a
556
- * non-JSON / non-message text frame surfaces on `error` and is DROPPED (never
416
+ * non-JSON / non-message text frame surfaces on `error` and is dropped (never
557
417
  * throws on adversarial wire input).
558
418
  * - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`
559
419
  * (idempotent); the socket's native `close` event (a server-initiated close) fires the
560
- * SAME `close` exactly once total — `close()` first flips the guard, so the native event
420
+ * same `close` exactly once total — `close()` first flips the guard, so the native event
561
421
  * never double-emits, and the released socket reports its own close to nobody. Closing before
562
422
  * the socket opens resolves the pending `start()` rather than leaving it pending, matching the
563
- * Node face. A `send` issued after `close()` REJECTS (it is never queued), and the
564
- * pre-open queue is DISCARDED — by `close()` and by the native `close` event alike — so a
423
+ * Node face. A `send` issued after `close()` rejects (it is never queued), and the
424
+ * pre-open queue is discarded — by `close()` and by the native `close` event alike — so a
565
425
  * closed transport delivers nothing until a `start()` opens a new connection, and nothing
566
426
  * the caller handed the abandoned connection rides that one.
567
- * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every
568
- * emit the emitter isolates a listener throw; `error` is a DOMAIN event (a
427
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every
428
+ * emit the emitter isolates a listener throw; `error` is a domain event (a
569
429
  * transport-level fault).
570
430
  *
571
431
  * @example
@@ -575,14 +435,14 @@ export declare interface ServeMCPScopeInterface {
575
435
  * await client.connect() // the browser handshakes, then the MCP initialize runs over WS frames
576
436
  * ```
577
437
  */
578
- export declare class WebSocketClientTransport implements MCPClientTransportInterface {
438
+ export declare class WebSocketClientTransport implements MCPMessageTransportInterface {
579
439
  #private;
580
440
  constructor(options: WebSocketClientTransportOptions);
581
- get emitter(): EmitterInterface<MCPClientTransportEventMap>;
441
+ get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
582
442
  get session(): string | undefined;
583
443
  get duplex(): boolean;
584
444
  start(): Promise<void>;
585
- send(message: JSONRPCMessage_2): Promise<void>;
445
+ send(message: JSONRPCMessage): Promise<void>;
586
446
  close(): Promise<void>;
587
447
  }
588
448
 
@@ -593,9 +453,9 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
593
453
  * @remarks
594
454
  * - `url` — the absolute `ws://` / `wss://` (or `http://` / `https://`, accepted by
595
455
  * the native `WebSocket` constructor the same way) URL of the remote server's
596
- * WebSocket endpoint. REQUIRED.
456
+ * WebSocket endpoint. Required.
597
457
  * - `protocols` — the WebSocket subprotocol(s) to request. **Defaults to
598
- * {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
458
+ * {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL} (`'mcp'`)**, which
599
459
  * `createWebSocketServer` selects when the offer contains it. Per
600
460
  * RFC 6455 §4.1 a client must fail the connection if the server returns a subprotocol
601
461
  * it did not request; Node ≥ 22 (undici) enforces this strictly, so the default saves
@@ -609,7 +469,7 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
609
469
  * upgrade request header at all — there is no seam for an `Authorization` bearer to reach.
610
470
  * The Node face owns its own `node:http(s)` upgrade request and therefore can, which is why
611
471
  * only that side offers `headers`. Reach a guarded server from a page with a credential the
612
- * platform DOES carry: a cookie the browser attaches to the upgrade, a subprotocol token, or
472
+ * platform does carry: a cookie the browser attaches to the upgrade, a subprotocol token, or
613
473
  * a signed value in the URL. Adding a `headers` key here would be an option that silently
614
474
  * did nothing.
615
475
  */