@orkestrel/mcp 0.0.28 → 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.
- package/README.md +11 -14
- package/dist/src/browser/index.d.ts +63 -65
- package/dist/src/browser/index.js +44 -44
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +233 -176
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +442 -366
- package/dist/src/core/index.d.ts +442 -366
- package/dist/src/core/index.js +233 -176
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +109 -109
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +209 -203
- package/dist/src/server/index.d.ts +209 -203
- package/dist/src/server/index.js +109 -109
- package/dist/src/server/index.js.map +1 -1
- package/package.json +24 -25
|
@@ -17,33 +17,33 @@ var DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
|
17
17
|
* - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}
|
|
18
18
|
* / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only
|
|
19
19
|
* carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a
|
|
20
|
-
* plain duplex channel — the
|
|
21
|
-
* `MCPTransportInterface` and is handed to
|
|
20
|
+
* plain duplex channel — the same class implements `@orkestrel/mcp`'s
|
|
21
|
+
* `MCPTransportInterface` and is handed to either `bindServer` or
|
|
22
22
|
* `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely
|
|
23
23
|
* from the binder it is given to, not from anything this class decides.
|
|
24
24
|
* - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only
|
|
25
|
-
*
|
|
25
|
+
* required when listening with `addEventListener` (as opposed to the `onmessage`
|
|
26
26
|
* setter, which implies it) — this transport uses `addEventListener`, and
|
|
27
27
|
* `MCPTransportInterface` has no separate open/connect step for the caller to hook
|
|
28
28
|
* a start into, so the constructor calls `port.start()` immediately: the port
|
|
29
|
-
* begins dispatching
|
|
29
|
+
* begins dispatching queued messages the moment the transport exists. This is safe
|
|
30
30
|
* inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`
|
|
31
31
|
* before control returns to the event loop), but is a **footgun for direct use**:
|
|
32
32
|
* if you construct `new MessagePortTransport({ port })` and then `await` anything
|
|
33
|
-
* before calling `listen`, messages that arrived in the gap are
|
|
33
|
+
* before calling `listen`, messages that arrived in the gap are dropped. **Bind
|
|
34
34
|
* synchronously after construction** — do not interleave an `await` between
|
|
35
35
|
* `new MessagePortTransport(…)` and `bindServer` / `listen`.
|
|
36
36
|
* - **String payloads only.** `send` posts the message string as-is (`postMessage`
|
|
37
37
|
* structured-clones it — a string clones to an identical string, so the wire stays
|
|
38
38
|
* plain JSON-RPC text like every other transport in this package). Inbound: a
|
|
39
39
|
* non-string `event.data` (a host or a misbehaving peer posting a structured
|
|
40
|
-
* object) is
|
|
40
|
+
* object) is ignored — dropped silently, never forwarded, never thrown —
|
|
41
41
|
* because `MCPTransportInterface` carries no `error` channel for this port to
|
|
42
42
|
* surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);
|
|
43
43
|
* silently ignoring is the total, contract-shaped choice.
|
|
44
|
-
* - **`messageerror` is
|
|
44
|
+
* - **`messageerror` is ignored, not routed to `closed`.** A `messageerror` event
|
|
45
45
|
* (the structured-clone deserialization of an inbound message threw) reports one
|
|
46
|
-
*
|
|
46
|
+
* bad frame, not a dead channel — the port itself keeps working and later, well-
|
|
47
47
|
* formed messages still arrive. This transport registers no listener for it: an
|
|
48
48
|
* unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor
|
|
49
49
|
* reaches this transport, so one bad frame costs exactly that frame and nothing
|
|
@@ -51,14 +51,14 @@ var DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
|
51
51
|
* `bindServer`/`bindClient` wiring (and, transitively, every session it carries)
|
|
52
52
|
* over a single malformed frame.
|
|
53
53
|
* - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`
|
|
54
|
-
* disconnects it — further `postMessage` calls on
|
|
54
|
+
* disconnects it — further `postMessage` calls on either end are silently
|
|
55
55
|
* undelivered, per the platform contract) and fires the registered `closed`
|
|
56
56
|
* handler exactly once, whether the caller closes it once or twice. There is no
|
|
57
57
|
* native "peer closed" signal for a `MessagePort` (unlike a WebSocket's `close`
|
|
58
|
-
* event) — `closed` fires
|
|
58
|
+
* event) — `closed` fires only from this transport's own `close()`.
|
|
59
59
|
* - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`
|
|
60
60
|
* doc).** `listen`/`closed` each hold the one active handler; a
|
|
61
|
-
* second call
|
|
61
|
+
* second call replaces the first rather than adding a second subscriber.
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* ```ts
|
|
@@ -110,42 +110,42 @@ var MessagePortTransport = class {
|
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/browser/transports/WebSocketClientTransport.ts
|
|
112
112
|
/**
|
|
113
|
-
* Drives a
|
|
114
|
-
*
|
|
115
|
-
* {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
113
|
+
* Drives a remote MCP server over the native `WebSocket` global from the browser face, as a
|
|
114
|
+
* client {@link MCPMessageTransportInterface}. This class is the browser sibling of the Node
|
|
115
|
+
* face's {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.
|
|
116
116
|
*
|
|
117
117
|
* @remarks
|
|
118
118
|
* - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and
|
|
119
119
|
* waits for the native `'open'` event — the RFC 6455 handshake itself is entirely
|
|
120
120
|
* the host's concern, so this transport carries none of the Node client's
|
|
121
121
|
* `node:crypto` / `node:http(s)` machinery. A connection failure (the native
|
|
122
|
-
* `'error'` event while not yet `OPEN`)
|
|
122
|
+
* `'error'` event while not yet `OPEN`) rejects `start()`.
|
|
123
123
|
* - **Queued sends.** `send` writes each message as one text frame immediately once
|
|
124
124
|
* the socket is `OPEN`; a `send` issued before `'open'` fires (or before `start()`
|
|
125
|
-
* is even called) is
|
|
126
|
-
* so a caller need not await `start()` before calling `send`. A queue rides
|
|
127
|
-
* connection: a close
|
|
128
|
-
* - **A closed channel
|
|
125
|
+
* is even called) is queued and flushed, in order, the moment the socket opens —
|
|
126
|
+
* so a caller need not await `start()` before calling `send`. A queue rides one
|
|
127
|
+
* connection: a close discards whatever is still in it.
|
|
128
|
+
* - **A closed channel rejects.** The native socket confirms nothing about a write, so this
|
|
129
129
|
* transport answers from its own state: a `send` after `close()`, or on a socket already
|
|
130
|
-
* reporting `CLOSING` / `CLOSED`,
|
|
130
|
+
* reporting `CLOSING` / `CLOSED`, rejects with `WebSocket transport is not connected` rather
|
|
131
131
|
* than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`
|
|
132
132
|
* still queues.
|
|
133
133
|
* - **Inbound (`message`).** Each decoded text frame runs through the shared
|
|
134
134
|
* `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}
|
|
135
135
|
* re-emits on this transport's `message` event; a non-text (binary) frame or a
|
|
136
|
-
* non-JSON / non-message text frame surfaces on `error` and is
|
|
136
|
+
* non-JSON / non-message text frame surfaces on `error` and is dropped (never
|
|
137
137
|
* throws on adversarial wire input).
|
|
138
138
|
* - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`
|
|
139
139
|
* (idempotent); the socket's native `close` event (a server-initiated close) fires the
|
|
140
|
-
*
|
|
140
|
+
* same `close` exactly once total — `close()` first flips the guard, so the native event
|
|
141
141
|
* never double-emits, and the released socket reports its own close to nobody. Closing before
|
|
142
142
|
* the socket opens resolves the pending `start()` rather than leaving it pending, matching the
|
|
143
|
-
* Node face. A `send` issued after `close()`
|
|
144
|
-
* pre-open queue is
|
|
143
|
+
* Node face. A `send` issued after `close()` rejects (it is never queued), and the
|
|
144
|
+
* pre-open queue is discarded — by `close()` and by the native `close` event alike — so a
|
|
145
145
|
* closed transport delivers nothing until a `start()` opens a new connection, and nothing
|
|
146
146
|
* the caller handed the abandoned connection rides that one.
|
|
147
147
|
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every
|
|
148
|
-
* emit the emitter isolates a listener throw; `error` is a
|
|
148
|
+
* emit the emitter isolates a listener throw; `error` is a domain event (a
|
|
149
149
|
* transport-level fault).
|
|
150
150
|
*
|
|
151
151
|
* @example
|
|
@@ -277,20 +277,20 @@ var WebSocketClientTransport = class {
|
|
|
277
277
|
//#endregion
|
|
278
278
|
//#region src/browser/factories.ts
|
|
279
279
|
/**
|
|
280
|
-
* Creates the browser-face WebSocket
|
|
280
|
+
* Creates the browser-face WebSocket client transport for an
|
|
281
281
|
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
282
|
-
* that drives a
|
|
283
|
-
* sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
282
|
+
* that drives a remote MCP server over the native `WebSocket` global. This factory is the
|
|
283
|
+
* browser sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).
|
|
284
284
|
*
|
|
285
285
|
* @remarks
|
|
286
286
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
287
287
|
* opens `new WebSocket(options.url, options.protocols)` and awaits the native
|
|
288
288
|
* `'open'` event — the RFC 6455 handshake itself is the browser's concern. Each
|
|
289
|
-
* JSON-RPC message the client `send`s before the socket opens is
|
|
289
|
+
* JSON-RPC message the client `send`s before the socket opens is queued and flushed,
|
|
290
290
|
* in order, once it does; each decoded reply is surfaced on the transport's
|
|
291
291
|
* `message` event for the client's id correlation.
|
|
292
292
|
*
|
|
293
|
-
* @param options - `url` (the remote WebSocket endpoint;
|
|
293
|
+
* @param options - `url` (the remote WebSocket endpoint; required) and optional
|
|
294
294
|
* `protocols` (the WebSocket subprotocol(s) to request); see
|
|
295
295
|
* {@link WebSocketClientTransportOptions}
|
|
296
296
|
* @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`
|
|
@@ -311,9 +311,9 @@ function createWebSocketClientTransport(options) {
|
|
|
311
311
|
return new WebSocketClientTransport(options);
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
|
-
* Creates the HTTP
|
|
314
|
+
* Creates the HTTP client transport for an
|
|
315
315
|
* {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}
|
|
316
|
-
* that drives a
|
|
316
|
+
* that drives a remote Streamable-HTTP MCP server over the native `fetch`.
|
|
317
317
|
*
|
|
318
318
|
* @remarks
|
|
319
319
|
* It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
|
|
@@ -325,18 +325,18 @@ function createWebSocketClientTransport(options) {
|
|
|
325
325
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client
|
|
326
326
|
* sends is `POST`ed to `options.url` with `content-type: application/json` and an
|
|
327
327
|
* `Accept` of both `application/json` and `text/event-stream` (the server answers
|
|
328
|
-
* with
|
|
328
|
+
* with either — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,
|
|
329
329
|
* decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's
|
|
330
330
|
* `message` event for the client's id correlation. Add `options.headers` (for example, an
|
|
331
331
|
* `Authorization` bearer) to reach a guarded server. `start` / `close` hold no
|
|
332
|
-
* connection; against a
|
|
332
|
+
* connection; against a stateful server it captures the `mcp-session-id` from
|
|
333
333
|
* `initialize` and echoes it on later requests. It also captures the initialize
|
|
334
334
|
* result's `protocolVersion` and sends `mcp-protocol-version` alone on subsequent
|
|
335
335
|
* legacy requests. Modern requests instead derive `mcp-protocol-version` and
|
|
336
336
|
* `mcp-method` from the message, plus `mcp-name` only for `tools/call`, so the
|
|
337
337
|
* same `MCPClient` passes either era's protocol gates without caller wiring.
|
|
338
338
|
*
|
|
339
|
-
* @param options - `url` (the remote endpoint;
|
|
339
|
+
* @param options - `url` (the remote endpoint; required), optional `headers` merged
|
|
340
340
|
* onto every request, optional `fetch` (default `globalThis.fetch`), and optional
|
|
341
341
|
* `timeout` (ms, applied with `AbortSignal.timeout`); see
|
|
342
342
|
* {@link HTTPClientTransportOptions}
|
|
@@ -360,7 +360,7 @@ function createHTTPClientTransport(options) {
|
|
|
360
360
|
/**
|
|
361
361
|
* Creates the browser-face `MessagePort` transport — a
|
|
362
362
|
* {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the
|
|
363
|
-
*
|
|
363
|
+
* symmetric carrier that works as either a server or a client transport depending on
|
|
364
364
|
* which binder ({@link import('@orkestrel/mcp').bindServer} or
|
|
365
365
|
* {@link import('@orkestrel/mcp').bindClient}) it is handed to.
|
|
366
366
|
*
|
|
@@ -370,7 +370,7 @@ function createHTTPClientTransport(options) {
|
|
|
370
370
|
* dropped, never thrown); `messageerror` is ignored (one bad frame does not close the
|
|
371
371
|
* channel); `close()` closes the port and fires `closed` exactly once.
|
|
372
372
|
*
|
|
373
|
-
* @param options - `port` (the `MessagePort` half to drive;
|
|
373
|
+
* @param options - `port` (the `MessagePort` half to drive; required); see
|
|
374
374
|
* {@link MessagePortTransportOptions}
|
|
375
375
|
* @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port
|
|
376
376
|
*
|
|
@@ -445,28 +445,28 @@ function createScopeServer(options, scope = globalThis) {
|
|
|
445
445
|
}
|
|
446
446
|
/**
|
|
447
447
|
* Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that
|
|
448
|
-
* routes
|
|
448
|
+
* routes every inbound event on a hostable scope, portless or port-bearing, to the right
|
|
449
449
|
* binding.
|
|
450
450
|
*
|
|
451
451
|
* @remarks
|
|
452
|
-
* Port-bearing events (`event.ports.length > 0`) are gated by `options.accept`
|
|
452
|
+
* Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` first
|
|
453
453
|
* — when the gate returns `false` the event is dropped entirely (no binding, no reply).
|
|
454
454
|
* Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,
|
|
455
455
|
* `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)
|
|
456
|
-
* into `teardowns`
|
|
456
|
+
* into `teardowns` keyed by that port. A port already present is ignored — repeated delivery
|
|
457
457
|
* of the same `MessagePort` would create duplicate bindings over one port (→ duplicated
|
|
458
458
|
* replies), so a repeat is silently dropped.
|
|
459
459
|
*
|
|
460
|
-
* The key is what makes `teardowns` the
|
|
460
|
+
* The key is what makes `teardowns` the only place an accepted port is remembered. A separate
|
|
461
461
|
* seen-port set would be a second collection over the same lifetime, and the scope server's
|
|
462
462
|
* `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker
|
|
463
463
|
* would retain every port it ever accepted, closed and unbound ones included. Membership
|
|
464
464
|
* answers "already bound?" and `clear()` drops the binding and the dedup together.
|
|
465
465
|
*
|
|
466
|
-
* This branch fires on
|
|
466
|
+
* This branch fires on either a Service-Worker-shaped scope (its normal per-client
|
|
467
467
|
* channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event
|
|
468
468
|
* (the unified design's deliberate cross-case, needing no upfront shape flag). An event
|
|
469
|
-
* with
|
|
469
|
+
* with no ports and a string `data` is pushed onto `scopeTransport.deliver` (the
|
|
470
470
|
* implicit, already-bound scope channel); any other event (no ports, non-string data)
|
|
471
471
|
* is silently dropped — total, never throws.
|
|
472
472
|
*
|
|
@@ -511,7 +511,7 @@ function createScopeMessageListener(server, scopeTransport, teardowns, options)
|
|
|
511
511
|
*
|
|
512
512
|
* @remarks
|
|
513
513
|
* `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`
|
|
514
|
-
* register the
|
|
514
|
+
* register the single handler `deliver` / the underlying close path route through —
|
|
515
515
|
* the scope server's own `scope` `message`-event listener calls `deliver(event.data)`
|
|
516
516
|
* for every portless, string-payload event (there is no native registration point on
|
|
517
517
|
* the scope itself for the scope server to hand a `listen` handler to, so `deliver` is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/transports/MessagePortTransport.ts","../../../src/browser/transports/WebSocketClientTransport.ts","../../../src/browser/factories.ts"],"sourcesContent":["// The MCP browser-transport constants — the server-identity defaults the browser-face\n// bootstrap falls back to. The Streamable-HTTP wire headers and the WebSocket subprotocol\n// live in `@src/core` beside the transports that write them.\n\n// Scope-server identity defaults — `src/core`'s `createMCPServer` REQUIRES\n// `name`/`version`, but `ScopeServerOptions` (this face's bootstrap) makes them optional\n// (mirroring the CLIENT identity defaults, `DEFAULT_MCP_CLIENT_NAME` /\n// `DEFAULT_MCP_CLIENT_VERSION`, `src/core/constants.ts`), so `createScopeServer` falls\n// back to these when a caller omits them.\n\n/** Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */\nexport const DEFAULT_MCP_SERVER_NAME = '@orkestrel/mcp'\n\n/** Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */\nexport const DEFAULT_MCP_SERVER_VERSION = '1.0.0'\n","import type { MCPTransportInterface } from '@src/core'\nimport type { MessagePortTransportOptions } from '../types.js'\nimport { isString } from '@orkestrel/contract'\n\n/**\n * Carries the Model Context Protocol over a native `MessagePort` from the browser face — a\n * {@link MCPTransportInterface}, the genuinely new capability this face adds: MCP over\n * `postMessage`.\n *\n * @remarks\n * - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}\n * / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only\n * carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a\n * plain duplex channel — the SAME class implements `@orkestrel/mcp`'s\n * `MCPTransportInterface` and is handed to EITHER `bindServer` or\n * `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely\n * from the binder it is given to, not from anything this class decides.\n * - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only\n * REQUIRED when listening with `addEventListener` (as opposed to the `onmessage`\n * setter, which implies it) — this transport uses `addEventListener`, and\n * `MCPTransportInterface` has no separate open/connect step for the caller to hook\n * a start into, so the constructor calls `port.start()` immediately: the port\n * begins dispatching QUEUED messages the moment the transport exists. This is safe\n * inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`\n * before control returns to the event loop), but is a **footgun for direct use**:\n * if you construct `new MessagePortTransport({ port })` and then `await` anything\n * before calling `listen`, messages that arrived in the gap are DROPPED. **Bind\n * synchronously after construction** — do not interleave an `await` between\n * `new MessagePortTransport(…)` and `bindServer` / `listen`.\n * - **String payloads only.** `send` posts the message string as-is (`postMessage`\n * structured-clones it — a string clones to an identical string, so the wire stays\n * plain JSON-RPC text like every other transport in this package). Inbound: a\n * non-string `event.data` (a host or a misbehaving peer posting a structured\n * object) is IGNORED — dropped silently, never forwarded, never thrown —\n * because `MCPTransportInterface` carries no `error` channel for this port to\n * surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);\n * silently ignoring is the total, contract-shaped choice.\n * - **`messageerror` is IGNORED, not routed to `closed`.** A `messageerror` event\n * (the structured-clone deserialization of an inbound message threw) reports one\n * BAD FRAME, not a dead channel — the port itself keeps working and later, well-\n * formed messages still arrive. This transport registers no listener for it: an\n * unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor\n * reaches this transport, so one bad frame costs exactly that frame and nothing\n * tears the binding down. Routing it to `closed` would tear down the\n * `bindServer`/`bindClient` wiring (and, transitively, every session it carries)\n * over a single malformed frame.\n * - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`\n * disconnects it — further `postMessage` calls on EITHER end are silently\n * undelivered, per the platform contract) and fires the registered `closed`\n * handler exactly once, whether the caller closes it once or twice. There is no\n * native \"peer closed\" signal for a `MessagePort` (unlike a WebSocket's `close`\n * event) — `closed` fires ONLY from this transport's own `close()`.\n * - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`\n * doc).** `listen`/`closed` each hold the one active handler; a\n * second call REPLACES the first rather than adding a second subscriber.\n *\n * @example\n * ```ts\n * const { port1, port2 } = new MessageChannel()\n * const serverTransport = new MessagePortTransport({ port: port1 })\n * bindServer(server, serverTransport) // port1 side dispatches inbound requests\n *\n * const clientTransport = new MessagePortTransport({ port: port2 })\n * const client = createMCPClient({ transport: createDuplexClientTransport(clientTransport) })\n * bindClient(client, clientTransport) // port2 side is the client's carrier\n * ```\n */\nexport class MessagePortTransport implements MCPTransportInterface {\n\treadonly #port: MessagePort\n\treadonly #message = (event: MessageEvent): void => this.#receive(event.data)\n\t#onMessage: ((message: string) => void) | undefined = undefined\n\t#onClosed: (() => void) | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: MessagePortTransportOptions) {\n\t\tthis.#port = options.port\n\t\tthis.#port.addEventListener('message', this.#message)\n\t\tthis.#port.start()\n\t}\n\n\tsend(message: string): void {\n\t\tif (this.#closed) return\n\t\tthis.#port.postMessage(message)\n\t}\n\n\tlisten(handler: (message: string) => void): void {\n\t\tthis.#onMessage = handler\n\t}\n\n\tclosed(handler: () => void): void {\n\t\tthis.#onClosed = handler\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tconst onClosed = this.#onClosed\n\t\tthis.#onMessage = undefined\n\t\tthis.#onClosed = undefined\n\t\tthis.#port.removeEventListener('message', this.#message)\n\t\tthis.#port.close()\n\t\tonClosed?.()\n\t}\n\n\t// Decode one inbound `postMessage` payload: a non-string `data` is dropped, never\n\t// forwarded (this port carries only plain JSON-RPC text). A string reaches the\n\t// registered `listen` handler unchanged (the string IS the JSON-RPC message; parsing is\n\t// entirely the core's concern, per the port contract).\n\t#receive(data: unknown): void {\n\t\tif (!isString(data)) return\n\t\tthis.#onMessage?.(data)\n\t}\n}\n","import type {\n\tMCPMessageTransportEventMap,\n\tMCPMessageTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { WebSocketClientTransportOptions } from '../types.js'\nimport { deliverMessage, MCP_WEBSOCKET_SUBPROTOCOL } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\n\n/**\n * Drives a REMOTE MCP server over the native `WebSocket` global from the browser face — a\n * CLIENT {@link MCPMessageTransportInterface}, the browser sibling of the Node face's\n * {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.\n *\n * @remarks\n * - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and\n * waits for the native `'open'` event — the RFC 6455 handshake itself is entirely\n * the host's concern, so this transport carries none of the Node client's\n * `node:crypto` / `node:http(s)` machinery. A connection failure (the native\n * `'error'` event while not yet `OPEN`) REJECTS `start()`.\n * - **Queued sends.** `send` writes each message as one text frame immediately once\n * the socket is `OPEN`; a `send` issued before `'open'` fires (or before `start()`\n * is even called) is QUEUED and flushed, IN ORDER, the moment the socket opens —\n * so a caller need not await `start()` before calling `send`. A queue rides ONE\n * connection: a close DISCARDS whatever is still in it.\n * - **A closed channel REJECTS.** The native socket confirms nothing about a write, so this\n * transport answers from its own state: a `send` after `close()`, or on a socket already\n * reporting `CLOSING` / `CLOSED`, REJECTS with `WebSocket transport is not connected` rather\n * than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`\n * still queues.\n * - **Inbound (`message`).** Each decoded text frame runs through the shared\n * `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}\n * re-emits on this transport's `message` event; a non-text (binary) frame or a\n * non-JSON / non-message text frame surfaces on `error` and is DROPPED (never\n * throws on adversarial wire input).\n * - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`\n * (idempotent); the socket's native `close` event (a server-initiated close) fires the\n * SAME `close` exactly once total — `close()` first flips the guard, so the native event\n * never double-emits, and the released socket reports its own close to nobody. Closing before\n * the socket opens resolves the pending `start()` rather than leaving it pending, matching the\n * Node face. A `send` issued after `close()` REJECTS (it is never queued), and the\n * pre-open queue is DISCARDED — by `close()` and by the native `close` event alike — so a\n * closed transport delivers nothing until a `start()` opens a new connection, and nothing\n * the caller handed the abandoned connection rides that one.\n * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every\n * emit the emitter isolates a listener throw; `error` is a DOMAIN event (a\n * transport-level fault).\n *\n * @example\n * ```ts\n * const transport = new WebSocketClientTransport({ url: 'ws://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect() // the browser handshakes, then the MCP initialize runs over WS frames\n * ```\n */\nexport class WebSocketClientTransport implements MCPMessageTransportInterface {\n\treadonly #emitter: Emitter<MCPMessageTransportEventMap>\n\treadonly #url: string\n\treadonly #protocols: string | string[] | undefined\n\t// Bound once, as fields, so `close` can remove exactly the listeners `#bind` installed: an\n\t// inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (event: MessageEvent<unknown>): void => this.#receive(event.data)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (event: Event): void => this.#emitter.emit('error', event)\n\treadonly #opening = (): void => this.#onOpen()\n\treadonly #rejection = (): void => this.#onHandshakeError()\n\t#socket: WebSocket | undefined = undefined\n\t#handshake: WebSocket | undefined = undefined\n\t#resolve: (() => void) | undefined = undefined\n\t#reject: ((error: Error) => void) | undefined = undefined\n\t#queue: string[] = []\n\t#closed = false\n\n\tconstructor(options: WebSocketClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPMessageTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tconst protocols = options.protocols\n\t\t// Default to MCP_WEBSOCKET_SUBPROTOCOL when `protocols` is omitted; the server selects it\n\t\t// from this offer. An empty array means \"no subprotocol\",\n\t\t// overriding the default explicitly for foreign servers.\n\t\tthis.#protocols =\n\t\t\ttypeof protocols === 'string'\n\t\t\t\t? protocols\n\t\t\t\t: protocols === undefined\n\t\t\t\t\t? MCP_WEBSOCKET_SUBPROTOCOL\n\t\t\t\t\t: protocols.length === 0\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: [...protocols]\n\t}\n\n\tget emitter(): EmitterInterface<MCPMessageTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Already connected — a second `connect()` short-circuits in the client, but guard here\n\t\t// too (idempotent open).\n\t\tif (this.#socket !== undefined) return\n\t\tthis.#closed = false\n\t\tconst socket = new WebSocket(this.#url, this.#protocols)\n\t\tthis.#socket = socket\n\t\tthis.#bind(socket)\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tthis.#handshake = socket\n\t\t\tthis.#resolve = resolve\n\t\t\tthis.#reject = reject\n\t\t\tsocket.addEventListener('open', this.#opening)\n\t\t\tsocket.addEventListener('error', this.#rejection)\n\t\t})\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst socket = this.#socket\n\t\t// A closed transport, and a socket the host has already moved past OPEN, each name a\n\t\t// channel that will never carry this frame. Resolving would tell the client the message\n\t\t// was written and leave its correlated request pending to its own deadline. The socket's\n\t\t// own state is a SECOND source rather than a copy of the first: the native `close` event\n\t\t// lags the readyState transition, so a server-initiated close leaves this transport's flag\n\t\t// clear while the socket already reports `CLOSING`.\n\t\tif (\n\t\t\tthis.#closed ||\n\t\t\tsocket?.readyState === WebSocket.CLOSING ||\n\t\t\tsocket?.readyState === WebSocket.CLOSED\n\t\t) {\n\t\t\tthrow new Error('WebSocket transport is not connected')\n\t\t}\n\t\tconst text = JSON.stringify(message)\n\t\t// No socket yet (`start()` has not run) or still `CONNECTING`: queue it, and `#flush`\n\t\t// writes the whole queue in order the moment the socket opens.\n\t\tif (socket !== undefined && socket.readyState === WebSocket.OPEN) socket.send(text)\n\t\telse this.#queue.push(text)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The queue belongs to the connection the caller handed those frames to. Keeping it\n\t\t// would write them onto whatever socket a later `start()` opens, delivering a message\n\t\t// against a connection the caller had already abandoned.\n\t\tthis.#queue = []\n\t\tconst socket = this.#socket\n\t\tconst resolve = this.#resolve\n\t\tthis.#releaseHandshake()\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tif (socket !== undefined) socket.close()\n\t\tthis.#emitter.emit('close')\n\t\tresolve?.()\n\t}\n\n\t// Bridge the native socket's events onto the transport: a text frame → `message`\n\t// (decoded + narrowed), the socket close → `close`, a socket fault → `error`.\n\t#bind(socket: WebSocket): void {\n\t\tsocket.addEventListener('message', this.#frame)\n\t\tsocket.addEventListener('close', this.#ending)\n\t\tsocket.addEventListener('error', this.#failure)\n\t}\n\n\t// Unsubscribe from the socket this transport holds. A closing socket goes on\n\t// firing its own events, so a bridge left installed on one this transport has released\n\t// would report a connection it no longer owns.\n\t#release(): void {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) return\n\t\tsocket.removeEventListener('message', this.#frame)\n\t\tsocket.removeEventListener('close', this.#ending)\n\t\tsocket.removeEventListener('error', this.#failure)\n\t}\n\n\t#releaseHandshake(): void {\n\t\tconst socket = this.#handshake\n\t\tif (socket === undefined) return\n\t\tsocket.removeEventListener('open', this.#opening)\n\t\tsocket.removeEventListener('error', this.#rejection)\n\t\tthis.#handshake = undefined\n\t\tthis.#resolve = undefined\n\t\tthis.#reject = undefined\n\t}\n\n\t// Write every queued (pre-open) message, in order, as the socket opens.\n\t#flush(socket: WebSocket): void {\n\t\tfor (const text of this.#queue.splice(0)) socket.send(text)\n\t}\n\n\t#onOpen(): void {\n\t\tconst socket = this.#handshake\n\t\tconst resolve = this.#resolve\n\t\tif (socket === undefined || resolve === undefined) return\n\t\tthis.#releaseHandshake()\n\t\tthis.#flush(socket)\n\t\tresolve()\n\t}\n\n\t#onHandshakeError(): void {\n\t\tconst socket = this.#handshake\n\t\tconst reject = this.#reject\n\t\tif (socket === undefined || reject === undefined || socket.readyState === WebSocket.OPEN) return\n\t\tthis.#releaseHandshake()\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\treject(new Error('WebSocket connection failed'))\n\t}\n\n\t// Decode one inbound frame: a non-text (binary) frame is rejected without a throw; a text\n\t// frame runs through the shared `deliverMessage` fold. A well-formed message re-emits on\n\t// `message`; an unparsable or non-message frame surfaces on `error` and is dropped\n\t// (never throws on adversarial wire input).\n\t#receive(data: unknown): void {\n\t\tif (!isString(data)) {\n\t\t\tthis.#emitter.emit('error', new Error('non-text WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tdeliverMessage(this.#emitter, data, 'non-JSON-RPC WebSocket frame')\n\t}\n\n\t// The socket closed underneath us — fire `close` once. Only the socket this transport still\n\t// holds can reach here: a superseded one was unsubscribed when it was released, so its own\n\t// later close cannot end the connection that replaced it.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// Same rule as `close()`: the ended connection takes its queue with it.\n\t\tthis.#queue = []\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n}\n","import type {\n\tHTTPClientTransportOptions,\n\tMCPMessageTransportInterface,\n\tMCPServerInterface,\n\tMCPTransportInterface,\n} from '@src/core'\nimport type {\n\tMessagePortTransportOptions,\n\tScopeInterface,\n\tScopeServerInterface,\n\tScopeServerOptions,\n\tScopeTransportInterface,\n\tWebSocketClientTransportOptions,\n} from './types.js'\nimport { bindServer, createMCPServer, HTTPClientTransport } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION } from './constants.js'\nimport { MessagePortTransport } from './transports/MessagePortTransport.js'\nimport { WebSocketClientTransport } from './transports/WebSocketClientTransport.js'\n\n/**\n * Creates the browser-face WebSocket CLIENT transport for an\n * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}\n * that drives a REMOTE MCP server over the native `WebSocket` global, the browser\n * sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)\n * opens `new WebSocket(options.url, options.protocols)` and awaits the native\n * `'open'` event — the RFC 6455 handshake itself is the browser's concern. Each\n * JSON-RPC message the client `send`s before the socket opens is QUEUED and flushed,\n * in order, once it does; each decoded reply is surfaced on the transport's\n * `message` event for the client's id correlation.\n *\n * @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional\n * `protocols` (the WebSocket subprotocol(s) to request); see\n * {@link WebSocketClientTransportOptions}\n * @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createWebSocketClientTransport } from '@orkestrel/mcp/browser'\n *\n * const client = createMCPClient({\n * \ttransport: createWebSocketClientTransport({ url: 'ws://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createWebSocketClientTransport(\n\toptions: WebSocketClientTransportOptions,\n): MCPMessageTransportInterface {\n\treturn new WebSocketClientTransport(options)\n}\n\n/**\n * Creates the HTTP CLIENT transport for an\n * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}\n * that drives a REMOTE Streamable-HTTP MCP server over the native `fetch`.\n *\n * @remarks\n * It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the\n * Node face's `createHTTPClientTransport` returns, because the class touches `fetch`,\n * `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone. This factory exists so a\n * page imports its transport from the face it already imports everything else from.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client\n * sends is `POST`ed to `options.url` with `content-type: application/json` and an\n * `Accept` of both `application/json` and `text/event-stream` (the server answers\n * with EITHER — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,\n * decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's\n * `message` event for the client's id correlation. Add `options.headers` (for example, an\n * `Authorization` bearer) to reach a guarded server. `start` / `close` hold no\n * connection; against a STATEFUL server it captures the `mcp-session-id` from\n * `initialize` and echoes it on later requests. It also captures the initialize\n * result's `protocolVersion` and sends `mcp-protocol-version` alone on subsequent\n * legacy requests. Modern requests instead derive `mcp-protocol-version` and\n * `mcp-method` from the message, plus `mcp-name` only for `tools/call`, so the\n * same `MCPClient` passes either era's protocol gates without caller wiring.\n *\n * @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged\n * onto every request, optional `fetch` (default `globalThis.fetch`), and optional\n * `timeout` (ms, applied with `AbortSignal.timeout`); see\n * {@link HTTPClientTransportOptions}\n * @returns A working {@link MCPMessageTransportInterface} over the native `fetch`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createHTTPClientTransport } from '@orkestrel/mcp/browser'\n *\n * const client = createMCPClient({\n * \ttransport: createHTTPClientTransport({ url: 'http://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createHTTPClientTransport(\n\toptions: HTTPClientTransportOptions,\n): MCPMessageTransportInterface {\n\treturn new HTTPClientTransport(options)\n}\n\n/**\n * Creates the browser-face `MessagePort` transport — a\n * {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the\n * SYMMETRIC carrier that works as either a server or a client transport depending on\n * which binder ({@link import('@orkestrel/mcp').bindServer} or\n * {@link import('@orkestrel/mcp').bindClient}) it is handed to.\n *\n * @remarks\n * `port.start()` runs at construction (see {@link MessagePortTransport}'s doc for\n * why); inbound payloads are string-only (a non-string `postMessage` payload is\n * dropped, never thrown); `messageerror` is ignored (one bad frame does not close the\n * channel); `close()` closes the port and fires `closed` exactly once.\n *\n * @param options - `port` (the `MessagePort` half to drive; REQUIRED); see\n * {@link MessagePortTransportOptions}\n * @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port\n *\n * @example\n * ```ts\n * import { bindServer, createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMessagePortTransport } from '@orkestrel/mcp/browser'\n *\n * const { port1, port2 } = new MessageChannel()\n * const mcp = createMCPServer({ identity: { name: 's', version: '1.0.0' }, tools })\n * bindServer(createMCPLegacy(mcp), createMessagePortTransport({ port: port1 })) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMessagePortTransport(\n\toptions: MessagePortTransportOptions,\n): MCPTransportInterface {\n\treturn new MessagePortTransport(options)\n}\n\n/**\n * Creates an `MCPServer` hosted inside a worker scope and wires that scope's message events\n * to it — the browser face's bootstrap, and the twin of the Node face's `createStdioServer`.\n *\n * @remarks\n * `scope` defaults to `globalThis`, which is `self` inside a dedicated Web Worker or a\n * Service Worker, so a worker boots with `createScopeServer({ tools })` alone; pass a scope\n * explicitly to host a server on a double or on another message-event-bearing object.\n *\n * Port-bearing events are gated by `options.accept`, deduplicated by port, and receive\n * their own `MessagePortTransport` binding. Portless string events use the scope's\n * implicit channel. The returned handle's `stop` removes the listener, unbinds the implicit\n * channel, closes every accepted port binding, and drops the ports themselves — the\n * bindings are held in one map keyed by port, so nothing survives the clear. The served\n * endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era\n * worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this factory.\n *\n * @param options - The tools, optional identity, and optional port-event gate; see\n * {@link ScopeServerOptions}\n * @param scope - The hostable scope to wire; defaults to `globalThis`\n * @returns A {@link ScopeServerInterface} whose `stop` ends every binding this call owns\n *\n * @example\n * ```ts\n * import { createScopeServer } from '@orkestrel/mcp/browser'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * // Inside a Web Worker: the scope defaults to `globalThis`.\n * const worker = createScopeServer({ tools: createToolManager() })\n * // ... later, release every binding this call owns:\n * worker.stop()\n * ```\n */\nexport function createScopeServer(\n\toptions: ScopeServerOptions,\n\tscope: ScopeInterface = globalThis,\n): ScopeServerInterface {\n\tconst server = createMCPServer({\n\t\ttools: options.tools,\n\t\tidentity: {\n\t\t\tname: options.name ?? DEFAULT_MCP_SERVER_NAME,\n\t\t\tversion: options.version ?? DEFAULT_MCP_SERVER_VERSION,\n\t\t},\n\t})\n\tconst scopeTransport = createScopeTransport(scope)\n\tconst unbindScope = bindServer(server, scopeTransport)\n\tconst teardowns = new Map<MessagePort, () => void>()\n\tconst onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)\n\tscope.addEventListener('message', onMessage)\n\tlet stopped = false\n\treturn {\n\t\tstop(): void {\n\t\t\tif (stopped) return\n\t\t\tstopped = true\n\t\t\tscope.removeEventListener('message', onMessage)\n\t\t\tunbindScope()\n\t\t\tfor (const teardown of teardowns.values()) teardown()\n\t\t\t// One clear releases the bindings AND the ports they were keyed by, so a scope that\n\t\t\t// outlives its handle — a Service Worker — retains neither.\n\t\t\tteardowns.clear()\n\t\t},\n\t}\n}\n\n/**\n * Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that\n * routes EVERY inbound event on a hostable scope, portless or port-bearing, to the right\n * binding.\n *\n * @remarks\n * Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` FIRST\n * — when the gate returns `false` the event is dropped entirely (no binding, no reply).\n * Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,\n * `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)\n * into `teardowns` KEYED BY THAT PORT. A port already present is IGNORED — repeated delivery\n * of the same `MessagePort` would create duplicate bindings over one port (→ duplicated\n * replies), so a repeat is silently dropped.\n *\n * The key is what makes `teardowns` the ONLY place an accepted port is remembered. A separate\n * seen-port set would be a second collection over the same lifetime, and the scope server's\n * `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker\n * would retain every port it ever accepted, closed and unbound ones included. Membership\n * answers \"already bound?\" and `clear()` drops the binding and the dedup together.\n *\n * This branch fires on EITHER a Service-Worker-shaped scope (its normal per-client\n * channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event\n * (the unified design's deliberate cross-case, needing no upfront shape flag). An event\n * with NO ports and a STRING `data` is pushed onto `scopeTransport.deliver` (the\n * implicit, already-bound scope channel); any other event (no ports, non-string data)\n * is silently dropped — total, never throws.\n *\n * @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over\n * @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto\n * @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\n * @param options - The `ScopeServerOptions` (for `options.accept`)\n * @returns The `message`-event listener to register (and later remove) on the scope\n *\n * @example\n * ```ts\n * const teardowns = new Map<MessagePort, () => void>()\n * const scopeTransport = createScopeTransport(scope)\n * bindServer(server, scopeTransport)\n * const onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)\n * scope.addEventListener('message', onMessage)\n * ```\n */\nexport function createScopeMessageListener(\n\tserver: MCPServerInterface,\n\tscopeTransport: ScopeTransportInterface,\n\tteardowns: Map<MessagePort, () => void>,\n\toptions: ScopeServerOptions,\n): (event: MessageEvent) => void {\n\treturn (event: MessageEvent): void => {\n\t\tconst ports = event.ports\n\t\tif (ports.length > 0) {\n\t\t\t// Gate: consult accept (origin/identity check) before binding.\n\t\t\tif (options.accept !== undefined && !options.accept(event)) return\n\t\t\tconst port = ports[0]\n\t\t\tif (port === undefined) return\n\t\t\t// Deduplicate off the teardown map itself: repeated delivery of the same port would\n\t\t\t// create duplicate bindings, and a second collection recording the same fact is one\n\t\t\t// the handle's `stop` can forget to empty.\n\t\t\tif (teardowns.has(port)) return\n\t\t\tconst transport = new MessagePortTransport({ port })\n\t\t\tconst unbind = bindServer(server, transport)\n\t\t\tteardowns.set(port, () => {\n\t\t\t\tunbind()\n\t\t\t\ttransport.close()\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tif (isString(event.data)) scopeTransport.deliver(event.data)\n\t}\n}\n\n/**\n * Adapts a hostable {@link ScopeInterface} (`self` in a dedicated Web Worker, or any\n * structurally matching double) into a {@link ScopeTransportInterface} — the implicit,\n * portless message channel {@link createScopeServer} binds for the dedicated-worker shape.\n *\n * @remarks\n * `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`\n * register the SINGLE handler `deliver` / the underlying close path route through —\n * the scope server's own `scope` `message`-event listener calls `deliver(event.data)`\n * for every portless, string-payload event (there is no native registration point on\n * the scope itself for the scope server to hand a `listen` handler to, so `deliver` is\n * the bridge). `close()` fires the registered `closed` handler — a scope has nothing\n * physically closable, so this is the only teardown signal available.\n *\n * @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`\n * inside a dedicated Web Worker)\n * @returns A {@link ScopeTransportInterface} the scope server binds and drives through `deliver`\n *\n * @example\n * ```ts\n * const scopeTransport = createScopeTransport(self)\n * const unbind = bindServer(server, scopeTransport)\n * ```\n */\nexport function createScopeTransport(scope: ScopeInterface): ScopeTransportInterface {\n\tlet onMessage: ((message: string) => void) | undefined\n\tlet onClosed: (() => void) | undefined\n\treturn {\n\t\tsend(message: string): void {\n\t\t\tscope.postMessage(message)\n\t\t},\n\t\tlisten(handler: (message: string) => void): void {\n\t\t\tonMessage = handler\n\t\t},\n\t\tclosed(handler: () => void): void {\n\t\t\tonClosed = handler\n\t\t},\n\t\tclose(): void {\n\t\t\tonClosed?.()\n\t\t},\n\t\tdeliver(message: string): void {\n\t\t\tonMessage?.(message)\n\t\t},\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/transports/MessagePortTransport.ts","../../../src/browser/transports/WebSocketClientTransport.ts","../../../src/browser/factories.ts"],"sourcesContent":["// The MCP browser-transport constants — the server-identity defaults the browser-face\n// bootstrap falls back to. The Streamable-HTTP wire headers and the WebSocket subprotocol\n// live in `@src/core` beside the transports that write them.\n\n// Scope-server identity defaults — `src/core`'s `createMCPServer` REQUIRES\n// `name`/`version`, but `ScopeServerOptions` (this face's bootstrap) makes them optional\n// (mirroring the CLIENT identity defaults, `DEFAULT_MCP_CLIENT_NAME` /\n// `DEFAULT_MCP_CLIENT_VERSION`, `src/core/constants.ts`), so `createScopeServer` falls\n// back to these when a caller omits them.\n\n/** Supplies the default server name `createScopeServer` reports (`initialize`'s `serverInfo.name`) when `options.name` is omitted. */\nexport const DEFAULT_MCP_SERVER_NAME = '@orkestrel/mcp'\n\n/** Supplies the default server version `createScopeServer` reports (`initialize`'s `serverInfo.version`) when `options.version` is omitted. */\nexport const DEFAULT_MCP_SERVER_VERSION = '1.0.0'\n","import type { MCPTransportInterface } from '@src/core'\nimport type { MessagePortTransportOptions } from '../types.js'\nimport { isString } from '@orkestrel/contract'\n\n/**\n * Carries the Model Context Protocol over a native `MessagePort` from the browser face — a\n * {@link MCPTransportInterface}, the genuinely new capability this face adds: MCP over\n * `postMessage`.\n *\n * @remarks\n * - **Symmetric.** Unlike {@link import('./WebSocketClientTransport.js').WebSocketClientTransport}\n * / {@link import('@orkestrel/mcp').HTTPClientTransport} (CLIENT-only\n * carriers of `@orkestrel/mcp`'s `MCPMessageTransportInterface`), a `MessagePort` is a\n * plain duplex channel — the same class implements `@orkestrel/mcp`'s\n * `MCPTransportInterface` and is handed to either `bindServer` or\n * `bindClient`/`createDuplexClientTransport`; which role it plays comes entirely\n * from the binder it is given to, not from anything this class decides.\n * - **`start()` at construction — bind synchronously.** `MessagePort.start()` is only\n * required when listening with `addEventListener` (as opposed to the `onmessage`\n * setter, which implies it) — this transport uses `addEventListener`, and\n * `MCPTransportInterface` has no separate open/connect step for the caller to hook\n * a start into, so the constructor calls `port.start()` immediately: the port\n * begins dispatching queued messages the moment the transport exists. This is safe\n * inside `createScopeServer`'s flow (the transport is synchronously handed to `bindServer`\n * before control returns to the event loop), but is a **footgun for direct use**:\n * if you construct `new MessagePortTransport({ port })` and then `await` anything\n * before calling `listen`, messages that arrived in the gap are dropped. **Bind\n * synchronously after construction** — do not interleave an `await` between\n * `new MessagePortTransport(…)` and `bindServer` / `listen`.\n * - **String payloads only.** `send` posts the message string as-is (`postMessage`\n * structured-clones it — a string clones to an identical string, so the wire stays\n * plain JSON-RPC text like every other transport in this package). Inbound: a\n * non-string `event.data` (a host or a misbehaving peer posting a structured\n * object) is ignored — dropped silently, never forwarded, never thrown —\n * because `MCPTransportInterface` carries no `error` channel for this port to\n * surface a non-string frame on (unlike `MCPMessageTransportInterface`'s `emitter`);\n * silently ignoring is the total, contract-shaped choice.\n * - **`messageerror` is ignored, not routed to `closed`.** A `messageerror` event\n * (the structured-clone deserialization of an inbound message threw) reports one\n * bad frame, not a dead channel — the port itself keeps working and later, well-\n * formed messages still arrive. This transport registers no listener for it: an\n * unhandled `messageerror` on a `MessagePort` neither throws, closes the port, nor\n * reaches this transport, so one bad frame costs exactly that frame and nothing\n * tears the binding down. Routing it to `closed` would tear down the\n * `bindServer`/`bindClient` wiring (and, transitively, every session it carries)\n * over a single malformed frame.\n * - **`close()`** is idempotent: it closes the underlying `port` (`MessagePort.close()`\n * disconnects it — further `postMessage` calls on either end are silently\n * undelivered, per the platform contract) and fires the registered `closed`\n * handler exactly once, whether the caller closes it once or twice. There is no\n * native \"peer closed\" signal for a `MessagePort` (unlike a WebSocket's `close`\n * event) — `closed` fires only from this transport's own `close()`.\n * - **Single-handler-replace (the port contract, `@orkestrel/mcp`'s `MCPTransportInterface`\n * doc).** `listen`/`closed` each hold the one active handler; a\n * second call replaces the first rather than adding a second subscriber.\n *\n * @example\n * ```ts\n * const { port1, port2 } = new MessageChannel()\n * const serverTransport = new MessagePortTransport({ port: port1 })\n * bindServer(server, serverTransport) // port1 side dispatches inbound requests\n *\n * const clientTransport = new MessagePortTransport({ port: port2 })\n * const client = createMCPClient({ transport: createDuplexClientTransport(clientTransport) })\n * bindClient(client, clientTransport) // port2 side is the client's carrier\n * ```\n */\nexport class MessagePortTransport implements MCPTransportInterface {\n\treadonly #port: MessagePort\n\treadonly #message = (event: MessageEvent): void => this.#receive(event.data)\n\t#onMessage: ((message: string) => void) | undefined = undefined\n\t#onClosed: (() => void) | undefined = undefined\n\t#closed = false\n\n\tconstructor(options: MessagePortTransportOptions) {\n\t\tthis.#port = options.port\n\t\tthis.#port.addEventListener('message', this.#message)\n\t\tthis.#port.start()\n\t}\n\n\tsend(message: string): void {\n\t\tif (this.#closed) return\n\t\tthis.#port.postMessage(message)\n\t}\n\n\tlisten(handler: (message: string) => void): void {\n\t\tthis.#onMessage = handler\n\t}\n\n\tclosed(handler: () => void): void {\n\t\tthis.#onClosed = handler\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\tconst onClosed = this.#onClosed\n\t\tthis.#onMessage = undefined\n\t\tthis.#onClosed = undefined\n\t\tthis.#port.removeEventListener('message', this.#message)\n\t\tthis.#port.close()\n\t\tonClosed?.()\n\t}\n\n\t// Decode one inbound `postMessage` payload: a non-string `data` is dropped, never\n\t// forwarded (this port carries only plain JSON-RPC text). A string reaches the\n\t// registered `listen` handler unchanged (the string IS the JSON-RPC message; parsing is\n\t// entirely the core's concern, per the port contract).\n\t#receive(data: unknown): void {\n\t\tif (!isString(data)) return\n\t\tthis.#onMessage?.(data)\n\t}\n}\n","import type {\n\tMCPMessageTransportEventMap,\n\tMCPMessageTransportInterface,\n\tJSONRPCMessage,\n} from '@src/core'\nimport type { EmitterInterface } from '@orkestrel/emitter'\nimport type { WebSocketClientTransportOptions } from '../types.js'\nimport { deliverMessage, MCP_WEBSOCKET_SUBPROTOCOL } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\n\n/**\n * Drives a remote MCP server over the native `WebSocket` global from the browser face, as a\n * client {@link MCPMessageTransportInterface}. This class is the browser sibling of the Node\n * face's {@link import('@orkestrel/mcp/server').WebSocketClientTransport}.\n *\n * @remarks\n * - **Host-performed handshake.** `start()` opens `new WebSocket(url, protocols)` and\n * waits for the native `'open'` event — the RFC 6455 handshake itself is entirely\n * the host's concern, so this transport carries none of the Node client's\n * `node:crypto` / `node:http(s)` machinery. A connection failure (the native\n * `'error'` event while not yet `OPEN`) rejects `start()`.\n * - **Queued sends.** `send` writes each message as one text frame immediately once\n * the socket is `OPEN`; a `send` issued before `'open'` fires (or before `start()`\n * is even called) is queued and flushed, in order, the moment the socket opens —\n * so a caller need not await `start()` before calling `send`. A queue rides one\n * connection: a close discards whatever is still in it.\n * - **A closed channel rejects.** The native socket confirms nothing about a write, so this\n * transport answers from its own state: a `send` after `close()`, or on a socket already\n * reporting `CLOSING` / `CLOSED`, rejects with `WebSocket transport is not connected` rather\n * than resolving on a frame nobody wrote. Only the closed state rejects — a pre-open `send`\n * still queues.\n * - **Inbound (`message`).** Each decoded text frame runs through the shared\n * `deliverMessage` fold (parse, then narrow) — a well-formed {@link JSONRPCMessage}\n * re-emits on this transport's `message` event; a non-text (binary) frame or a\n * non-JSON / non-message text frame surfaces on `error` and is dropped (never\n * throws on adversarial wire input).\n * - **`close()`** unsubscribes from the underlying socket, closes it, and fires `close`\n * (idempotent); the socket's native `close` event (a server-initiated close) fires the\n * same `close` exactly once total — `close()` first flips the guard, so the native event\n * never double-emits, and the released socket reports its own close to nobody. Closing before\n * the socket opens resolves the pending `start()` rather than leaving it pending, matching the\n * Node face. A `send` issued after `close()` rejects (it is never queued), and the\n * pre-open queue is discarded — by `close()` and by the native `close` event alike — so a\n * closed transport delivers nothing until a `start()` opens a new connection, and nothing\n * the caller handed the abandoned connection rides that one.\n * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every\n * emit the emitter isolates a listener throw; `error` is a domain event (a\n * transport-level fault).\n *\n * @example\n * ```ts\n * const transport = new WebSocketClientTransport({ url: 'ws://localhost:3000/mcp' })\n * const client = new MCPClient({ transport })\n * await client.connect() // the browser handshakes, then the MCP initialize runs over WS frames\n * ```\n */\nexport class WebSocketClientTransport implements MCPMessageTransportInterface {\n\treadonly #emitter: Emitter<MCPMessageTransportEventMap>\n\treadonly #url: string\n\treadonly #protocols: string | string[] | undefined\n\t// Bound once, as fields, so `close` can remove exactly the listeners `#bind` installed: an\n\t// inline arrow is a new function on every call and can never be removed by reference.\n\treadonly #frame = (event: MessageEvent<unknown>): void => this.#receive(event.data)\n\treadonly #ending = (): void => this.#onClose()\n\treadonly #failure = (event: Event): void => this.#emitter.emit('error', event)\n\treadonly #opening = (): void => this.#onOpen()\n\treadonly #rejection = (): void => this.#onHandshakeError()\n\t#socket: WebSocket | undefined = undefined\n\t#handshake: WebSocket | undefined = undefined\n\t#resolve: (() => void) | undefined = undefined\n\t#reject: ((error: Error) => void) | undefined = undefined\n\t#queue: string[] = []\n\t#closed = false\n\n\tconstructor(options: WebSocketClientTransportOptions) {\n\t\tthis.#emitter = new Emitter<MCPMessageTransportEventMap>()\n\t\tthis.#url = options.url\n\t\tconst protocols = options.protocols\n\t\t// Default to MCP_WEBSOCKET_SUBPROTOCOL when `protocols` is omitted; the server selects it\n\t\t// from this offer. An empty array means \"no subprotocol\",\n\t\t// overriding the default explicitly for foreign servers.\n\t\tthis.#protocols =\n\t\t\ttypeof protocols === 'string'\n\t\t\t\t? protocols\n\t\t\t\t: protocols === undefined\n\t\t\t\t\t? MCP_WEBSOCKET_SUBPROTOCOL\n\t\t\t\t\t: protocols.length === 0\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: [...protocols]\n\t}\n\n\tget emitter(): EmitterInterface<MCPMessageTransportEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tget session(): string | undefined {\n\t\treturn undefined\n\t}\n\n\tget duplex(): boolean {\n\t\t// A socket is bidirectional for its whole life: either side writes a frame whenever it\n\t\t// has one, with no request to attach it to.\n\t\treturn true\n\t}\n\n\tasync start(): Promise<void> {\n\t\t// Already connected — a second `connect()` short-circuits in the client, but guard here\n\t\t// too (idempotent open).\n\t\tif (this.#socket !== undefined) return\n\t\tthis.#closed = false\n\t\tconst socket = new WebSocket(this.#url, this.#protocols)\n\t\tthis.#socket = socket\n\t\tthis.#bind(socket)\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tthis.#handshake = socket\n\t\t\tthis.#resolve = resolve\n\t\t\tthis.#reject = reject\n\t\t\tsocket.addEventListener('open', this.#opening)\n\t\t\tsocket.addEventListener('error', this.#rejection)\n\t\t})\n\t}\n\n\tasync send(message: JSONRPCMessage): Promise<void> {\n\t\tconst socket = this.#socket\n\t\t// A closed transport, and a socket the host has already moved past OPEN, each name a\n\t\t// channel that will never carry this frame. Resolving would tell the client the message\n\t\t// was written and leave its correlated request pending to its own deadline. The socket's\n\t\t// own state is a SECOND source rather than a copy of the first: the native `close` event\n\t\t// lags the readyState transition, so a server-initiated close leaves this transport's flag\n\t\t// clear while the socket already reports `CLOSING`.\n\t\tif (\n\t\t\tthis.#closed ||\n\t\t\tsocket?.readyState === WebSocket.CLOSING ||\n\t\t\tsocket?.readyState === WebSocket.CLOSED\n\t\t) {\n\t\t\tthrow new Error('WebSocket transport is not connected')\n\t\t}\n\t\tconst text = JSON.stringify(message)\n\t\t// No socket yet (`start()` has not run) or still `CONNECTING`: queue it, and `#flush`\n\t\t// writes the whole queue in order the moment the socket opens.\n\t\tif (socket !== undefined && socket.readyState === WebSocket.OPEN) socket.send(text)\n\t\telse this.#queue.push(text)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// The queue belongs to the connection the caller handed those frames to. Keeping it\n\t\t// would write them onto whatever socket a later `start()` opens, delivering a message\n\t\t// against a connection the caller had already abandoned.\n\t\tthis.#queue = []\n\t\tconst socket = this.#socket\n\t\tconst resolve = this.#resolve\n\t\tthis.#releaseHandshake()\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tif (socket !== undefined) socket.close()\n\t\tthis.#emitter.emit('close')\n\t\tresolve?.()\n\t}\n\n\t// Bridge the native socket's events onto the transport: a text frame → `message`\n\t// (decoded + narrowed), the socket close → `close`, a socket fault → `error`.\n\t#bind(socket: WebSocket): void {\n\t\tsocket.addEventListener('message', this.#frame)\n\t\tsocket.addEventListener('close', this.#ending)\n\t\tsocket.addEventListener('error', this.#failure)\n\t}\n\n\t// Unsubscribe from the socket this transport holds. A closing socket goes on\n\t// firing its own events, so a bridge left installed on one this transport has released\n\t// would report a connection it no longer owns.\n\t#release(): void {\n\t\tconst socket = this.#socket\n\t\tif (socket === undefined) return\n\t\tsocket.removeEventListener('message', this.#frame)\n\t\tsocket.removeEventListener('close', this.#ending)\n\t\tsocket.removeEventListener('error', this.#failure)\n\t}\n\n\t#releaseHandshake(): void {\n\t\tconst socket = this.#handshake\n\t\tif (socket === undefined) return\n\t\tsocket.removeEventListener('open', this.#opening)\n\t\tsocket.removeEventListener('error', this.#rejection)\n\t\tthis.#handshake = undefined\n\t\tthis.#resolve = undefined\n\t\tthis.#reject = undefined\n\t}\n\n\t// Write every queued (pre-open) message, in order, as the socket opens.\n\t#flush(socket: WebSocket): void {\n\t\tfor (const text of this.#queue.splice(0)) socket.send(text)\n\t}\n\n\t#onOpen(): void {\n\t\tconst socket = this.#handshake\n\t\tconst resolve = this.#resolve\n\t\tif (socket === undefined || resolve === undefined) return\n\t\tthis.#releaseHandshake()\n\t\tthis.#flush(socket)\n\t\tresolve()\n\t}\n\n\t#onHandshakeError(): void {\n\t\tconst socket = this.#handshake\n\t\tconst reject = this.#reject\n\t\tif (socket === undefined || reject === undefined || socket.readyState === WebSocket.OPEN) return\n\t\tthis.#releaseHandshake()\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\treject(new Error('WebSocket connection failed'))\n\t}\n\n\t// Decode one inbound frame: a non-text (binary) frame is rejected without a throw; a text\n\t// frame runs through the shared `deliverMessage` fold. A well-formed message re-emits on\n\t// `message`; an unparsable or non-message frame surfaces on `error` and is dropped\n\t// (never throws on adversarial wire input).\n\t#receive(data: unknown): void {\n\t\tif (!isString(data)) {\n\t\t\tthis.#emitter.emit('error', new Error('non-text WebSocket frame'))\n\t\t\treturn\n\t\t}\n\t\tdeliverMessage(this.#emitter, data, 'non-JSON-RPC WebSocket frame')\n\t}\n\n\t// The socket closed underneath us — fire `close` once. Only the socket this transport still\n\t// holds can reach here: a superseded one was unsubscribed when it was released, so its own\n\t// later close cannot end the connection that replaced it.\n\t#onClose(): void {\n\t\tif (this.#closed) return\n\t\tthis.#closed = true\n\t\t// Same rule as `close()`: the ended connection takes its queue with it.\n\t\tthis.#queue = []\n\t\tthis.#release()\n\t\tthis.#socket = undefined\n\t\tthis.#emitter.emit('close')\n\t}\n}\n","import type {\n\tHTTPClientTransportOptions,\n\tMCPMessageTransportInterface,\n\tMCPServerInterface,\n\tMCPTransportInterface,\n} from '@src/core'\nimport type {\n\tMessagePortTransportOptions,\n\tScopeInterface,\n\tScopeServerInterface,\n\tScopeServerOptions,\n\tScopeTransportInterface,\n\tWebSocketClientTransportOptions,\n} from './types.js'\nimport { bindServer, createMCPServer, HTTPClientTransport } from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION } from './constants.js'\nimport { MessagePortTransport } from './transports/MessagePortTransport.js'\nimport { WebSocketClientTransport } from './transports/WebSocketClientTransport.js'\n\n/**\n * Creates the browser-face WebSocket client transport for an\n * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}\n * that drives a remote MCP server over the native `WebSocket` global. This factory is the\n * browser sibling of the Node face's `createWebSocketClientTransport` (`@orkestrel/mcp/server`).\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)\n * opens `new WebSocket(options.url, options.protocols)` and awaits the native\n * `'open'` event — the RFC 6455 handshake itself is the browser's concern. Each\n * JSON-RPC message the client `send`s before the socket opens is queued and flushed,\n * in order, once it does; each decoded reply is surfaced on the transport's\n * `message` event for the client's id correlation.\n *\n * @param options - `url` (the remote WebSocket endpoint; required) and optional\n * `protocols` (the WebSocket subprotocol(s) to request); see\n * {@link WebSocketClientTransportOptions}\n * @returns A working {@link MCPMessageTransportInterface} over the native `WebSocket`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createWebSocketClientTransport } from '@orkestrel/mcp/browser'\n *\n * const client = createMCPClient({\n * \ttransport: createWebSocketClientTransport({ url: 'ws://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createWebSocketClientTransport(\n\toptions: WebSocketClientTransportOptions,\n): MCPMessageTransportInterface {\n\treturn new WebSocketClientTransport(options)\n}\n\n/**\n * Creates the HTTP client transport for an\n * {@link import('@orkestrel/mcp').MCPClientInterface} — a {@link MCPMessageTransportInterface}\n * that drives a remote Streamable-HTTP MCP server over the native `fetch`.\n *\n * @remarks\n * It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the\n * Node face's `createHTTPClientTransport` returns, because the class touches `fetch`,\n * `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone. This factory exists so a\n * page imports its transport from the face it already imports everything else from.\n *\n * @remarks\n * Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client\n * sends is `POST`ed to `options.url` with `content-type: application/json` and an\n * `Accept` of both `application/json` and `text/event-stream` (the server answers\n * with either — a plain JSON envelope or a Streamable-HTTP SSE `data:` event,\n * decoded with `@orkestrel/sse`), and the reply is surfaced on the transport's\n * `message` event for the client's id correlation. Add `options.headers` (for example, an\n * `Authorization` bearer) to reach a guarded server. `start` / `close` hold no\n * connection; against a stateful server it captures the `mcp-session-id` from\n * `initialize` and echoes it on later requests. It also captures the initialize\n * result's `protocolVersion` and sends `mcp-protocol-version` alone on subsequent\n * legacy requests. Modern requests instead derive `mcp-protocol-version` and\n * `mcp-method` from the message, plus `mcp-name` only for `tools/call`, so the\n * same `MCPClient` passes either era's protocol gates without caller wiring.\n *\n * @param options - `url` (the remote endpoint; required), optional `headers` merged\n * onto every request, optional `fetch` (default `globalThis.fetch`), and optional\n * `timeout` (ms, applied with `AbortSignal.timeout`); see\n * {@link HTTPClientTransportOptions}\n * @returns A working {@link MCPMessageTransportInterface} over the native `fetch`\n *\n * @example\n * ```ts\n * import { createMCPClient } from '@orkestrel/mcp'\n * import { createHTTPClientTransport } from '@orkestrel/mcp/browser'\n *\n * const client = createMCPClient({\n * \ttransport: createHTTPClientTransport({ url: 'http://localhost:3000/mcp' }),\n * })\n * await client.connect()\n * const tools = await client.tools()\n * ```\n */\nexport function createHTTPClientTransport(\n\toptions: HTTPClientTransportOptions,\n): MCPMessageTransportInterface {\n\treturn new HTTPClientTransport(options)\n}\n\n/**\n * Creates the browser-face `MessagePort` transport — a\n * {@link import('@orkestrel/mcp').MCPTransportInterface} over a native `MessagePort`, the\n * symmetric carrier that works as either a server or a client transport depending on\n * which binder ({@link import('@orkestrel/mcp').bindServer} or\n * {@link import('@orkestrel/mcp').bindClient}) it is handed to.\n *\n * @remarks\n * `port.start()` runs at construction (see {@link MessagePortTransport}'s doc for\n * why); inbound payloads are string-only (a non-string `postMessage` payload is\n * dropped, never thrown); `messageerror` is ignored (one bad frame does not close the\n * channel); `close()` closes the port and fires `closed` exactly once.\n *\n * @param options - `port` (the `MessagePort` half to drive; required); see\n * {@link MessagePortTransportOptions}\n * @returns A working {@link import('@orkestrel/mcp').MCPTransportInterface} over the port\n *\n * @example\n * ```ts\n * import { bindServer, createMCPLegacy, createMCPServer } from '@orkestrel/mcp'\n * import { createMessagePortTransport } from '@orkestrel/mcp/browser'\n *\n * const { port1, port2 } = new MessageChannel()\n * const mcp = createMCPServer({ identity: { name: 's', version: '1.0.0' }, tools })\n * bindServer(createMCPLegacy(mcp), createMessagePortTransport({ port: port1 })) // answers `initialize` too; pass `mcp` alone for modern-only\n * ```\n */\nexport function createMessagePortTransport(\n\toptions: MessagePortTransportOptions,\n): MCPTransportInterface {\n\treturn new MessagePortTransport(options)\n}\n\n/**\n * Creates an `MCPServer` hosted inside a worker scope and wires that scope's message events\n * to it — the browser face's bootstrap, and the twin of the Node face's `createStdioServer`.\n *\n * @remarks\n * `scope` defaults to `globalThis`, which is `self` inside a dedicated Web Worker or a\n * Service Worker, so a worker boots with `createScopeServer({ tools })` alone; pass a scope\n * explicitly to host a server on a double or on another message-event-bearing object.\n *\n * Port-bearing events are gated by `options.accept`, deduplicated by port, and receive\n * their own `MessagePortTransport` binding. Portless string events use the scope's\n * implicit channel. The returned handle's `stop` removes the listener, unbinds the implicit\n * channel, closes every accepted port binding, and drops the ports themselves — the\n * bindings are held in one map keyed by port, so nothing survives the clear. The served\n * endpoint is modern-only: it answers a legacy `initialize` with `-32601`. A dual-era\n * worker composes `bindServer(createMCPLegacy(mcp), …)` instead of this factory.\n *\n * @param options - The tools, optional identity, and optional port-event gate; see\n * {@link ScopeServerOptions}\n * @param scope - The hostable scope to wire; defaults to `globalThis`\n * @returns A {@link ScopeServerInterface} whose `stop` ends every binding this call owns\n *\n * @example\n * ```ts\n * import { createScopeServer } from '@orkestrel/mcp/browser'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * // Inside a Web Worker: the scope defaults to `globalThis`.\n * const worker = createScopeServer({ tools: createToolManager() })\n * // ... later, release every binding this call owns:\n * worker.stop()\n * ```\n */\nexport function createScopeServer(\n\toptions: ScopeServerOptions,\n\tscope: ScopeInterface = globalThis,\n): ScopeServerInterface {\n\tconst server = createMCPServer({\n\t\ttools: options.tools,\n\t\tidentity: {\n\t\t\tname: options.name ?? DEFAULT_MCP_SERVER_NAME,\n\t\t\tversion: options.version ?? DEFAULT_MCP_SERVER_VERSION,\n\t\t},\n\t})\n\tconst scopeTransport = createScopeTransport(scope)\n\tconst unbindScope = bindServer(server, scopeTransport)\n\tconst teardowns = new Map<MessagePort, () => void>()\n\tconst onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)\n\tscope.addEventListener('message', onMessage)\n\tlet stopped = false\n\treturn {\n\t\tstop(): void {\n\t\t\tif (stopped) return\n\t\t\tstopped = true\n\t\t\tscope.removeEventListener('message', onMessage)\n\t\t\tunbindScope()\n\t\t\tfor (const teardown of teardowns.values()) teardown()\n\t\t\t// One clear releases the bindings AND the ports they were keyed by, so a scope that\n\t\t\t// outlives its handle — a Service Worker — retains neither.\n\t\t\tteardowns.clear()\n\t\t},\n\t}\n}\n\n/**\n * Builds {@link createScopeServer}'s `message`-event listener — the unified dispatcher that\n * routes every inbound event on a hostable scope, portless or port-bearing, to the right\n * binding.\n *\n * @remarks\n * Port-bearing events (`event.ports.length > 0`) are gated by `options.accept` first\n * — when the gate returns `false` the event is dropped entirely (no binding, no reply).\n * Accepted events spawn a fresh `MessagePortTransport` over `event.ports[0]`,\n * `bindServer` `server` onto it, and record a teardown (`unbind` then `transport.close()`)\n * into `teardowns` keyed by that port. A port already present is ignored — repeated delivery\n * of the same `MessagePort` would create duplicate bindings over one port (→ duplicated\n * replies), so a repeat is silently dropped.\n *\n * The key is what makes `teardowns` the only place an accepted port is remembered. A separate\n * seen-port set would be a second collection over the same lifetime, and the scope server's\n * `stop` would have to remember to empty both — so a long-lived scope such as a Service Worker\n * would retain every port it ever accepted, closed and unbound ones included. Membership\n * answers \"already bound?\" and `clear()` drops the binding and the dedup together.\n *\n * This branch fires on either a Service-Worker-shaped scope (its normal per-client\n * channel) or a dedicated-worker-shaped one that happens to receive a port-bearing event\n * (the unified design's deliberate cross-case, needing no upfront shape flag). An event\n * with no ports and a string `data` is pushed onto `scopeTransport.deliver` (the\n * implicit, already-bound scope channel); any other event (no ports, non-string data)\n * is silently dropped — total, never throws.\n *\n * @param server - The `MCPServerInterface` every spawned/implicit binding dispatches over\n * @param scopeTransport - The implicit scope channel (already `bindServer`-bound) portless events deliver onto\n * @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\n * @param options - The `ScopeServerOptions` (for `options.accept`)\n * @returns The `message`-event listener to register (and later remove) on the scope\n *\n * @example\n * ```ts\n * const teardowns = new Map<MessagePort, () => void>()\n * const scopeTransport = createScopeTransport(scope)\n * bindServer(server, scopeTransport)\n * const onMessage = createScopeMessageListener(server, scopeTransport, teardowns, options)\n * scope.addEventListener('message', onMessage)\n * ```\n */\nexport function createScopeMessageListener(\n\tserver: MCPServerInterface,\n\tscopeTransport: ScopeTransportInterface,\n\tteardowns: Map<MessagePort, () => void>,\n\toptions: ScopeServerOptions,\n): (event: MessageEvent) => void {\n\treturn (event: MessageEvent): void => {\n\t\tconst ports = event.ports\n\t\tif (ports.length > 0) {\n\t\t\t// Gate: consult accept (origin/identity check) before binding.\n\t\t\tif (options.accept !== undefined && !options.accept(event)) return\n\t\t\tconst port = ports[0]\n\t\t\tif (port === undefined) return\n\t\t\t// Deduplicate off the teardown map itself: repeated delivery of the same port would\n\t\t\t// create duplicate bindings, and a second collection recording the same fact is one\n\t\t\t// the handle's `stop` can forget to empty.\n\t\t\tif (teardowns.has(port)) return\n\t\t\tconst transport = new MessagePortTransport({ port })\n\t\t\tconst unbind = bindServer(server, transport)\n\t\t\tteardowns.set(port, () => {\n\t\t\t\tunbind()\n\t\t\t\ttransport.close()\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tif (isString(event.data)) scopeTransport.deliver(event.data)\n\t}\n}\n\n/**\n * Adapts a hostable {@link ScopeInterface} (`self` in a dedicated Web Worker, or any\n * structurally matching double) into a {@link ScopeTransportInterface} — the implicit,\n * portless message channel {@link createScopeServer} binds for the dedicated-worker shape.\n *\n * @remarks\n * `send` writes each outbound string through `scope.postMessage`. `listen`/`closed`\n * register the single handler `deliver` / the underlying close path route through —\n * the scope server's own `scope` `message`-event listener calls `deliver(event.data)`\n * for every portless, string-payload event (there is no native registration point on\n * the scope itself for the scope server to hand a `listen` handler to, so `deliver` is\n * the bridge). `close()` fires the registered `closed` handler — a scope has nothing\n * physically closable, so this is the only teardown signal available.\n *\n * @param scope - The hostable scope to adapt (structurally, `self` / `globalThis`\n * inside a dedicated Web Worker)\n * @returns A {@link ScopeTransportInterface} the scope server binds and drives through `deliver`\n *\n * @example\n * ```ts\n * const scopeTransport = createScopeTransport(self)\n * const unbind = bindServer(server, scopeTransport)\n * ```\n */\nexport function createScopeTransport(scope: ScopeInterface): ScopeTransportInterface {\n\tlet onMessage: ((message: string) => void) | undefined\n\tlet onClosed: (() => void) | undefined\n\treturn {\n\t\tsend(message: string): void {\n\t\t\tscope.postMessage(message)\n\t\t},\n\t\tlisten(handler: (message: string) => void): void {\n\t\t\tonMessage = handler\n\t\t},\n\t\tclosed(handler: () => void): void {\n\t\t\tonClosed = handler\n\t\t},\n\t\tclose(): void {\n\t\t\tonClosed?.()\n\t\t},\n\t\tdeliver(message: string): void {\n\t\t\tonMessage?.(message)\n\t\t},\n\t}\n}\n"],"mappings":";;;;;AAWA,IAAa,0BAA0B;;AAGvC,IAAa,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACqD1C,IAAa,uBAAb,MAAmE;CAClE;CACA,YAAqB,UAA8B,KAAK,SAAS,MAAM,IAAI;CAC3E,aAAsD,KAAA;CACtD,YAAsC,KAAA;CACtC,UAAU;CAEV,YAAY,SAAsC;EACjD,KAAK,QAAQ,QAAQ;EACrB,KAAK,MAAM,iBAAiB,WAAW,KAAK,QAAQ;EACpD,KAAK,MAAM,MAAM;CAClB;CAEA,KAAK,SAAuB;EAC3B,IAAI,KAAK,SAAS;EAClB,KAAK,MAAM,YAAY,OAAO;CAC/B;CAEA,OAAO,SAA0C;EAChD,KAAK,aAAa;CACnB;CAEA,OAAO,SAA2B;EACjC,KAAK,YAAY;CAClB;CAEA,QAAc;EACb,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EACf,MAAM,WAAW,KAAK;EACtB,KAAK,aAAa,KAAA;EAClB,KAAK,YAAY,KAAA;EACjB,KAAK,MAAM,oBAAoB,WAAW,KAAK,QAAQ;EACvD,KAAK,MAAM,MAAM;EACjB,WAAW;CACZ;CAMA,SAAS,MAAqB;EAC7B,IAAI,CAAC,SAAS,IAAI,GAAG;EACrB,KAAK,aAAa,IAAI;CACvB;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACvDA,IAAa,2BAAb,MAA8E;CAC7E;CACA;CACA;CAGA,UAAmB,UAAuC,KAAK,SAAS,MAAM,IAAI;CAClF,gBAA+B,KAAK,SAAS;CAC7C,YAAqB,UAAuB,KAAK,SAAS,KAAK,SAAS,KAAK;CAC7E,iBAAgC,KAAK,QAAQ;CAC7C,mBAAkC,KAAK,kBAAkB;CACzD,UAAiC,KAAA;CACjC,aAAoC,KAAA;CACpC,WAAqC,KAAA;CACrC,UAAgD,KAAA;CAChD,SAAmB,CAAC;CACpB,UAAU;CAEV,YAAY,SAA0C;EACrD,KAAK,WAAW,IAAI,QAAqC;EACzD,KAAK,OAAO,QAAQ;EACpB,MAAM,YAAY,QAAQ;EAI1B,KAAK,aACJ,OAAO,cAAc,WAClB,YACA,cAAc,KAAA,IACb,4BACA,UAAU,WAAW,IACpB,KAAA,IACA,CAAC,GAAG,SAAS;CACpB;CAEA,IAAI,UAAyD;EAC5D,OAAO,KAAK;CACb;CAEA,IAAI,UAA8B,CAElC;CAEA,IAAI,SAAkB;EAGrB,OAAO;CACR;CAEA,MAAM,QAAuB;EAG5B,IAAI,KAAK,YAAY,KAAA,GAAW;EAChC,KAAK,UAAU;EACf,MAAM,SAAS,IAAI,UAAU,KAAK,MAAM,KAAK,UAAU;EACvD,KAAK,UAAU;EACf,KAAK,MAAM,MAAM;EACjB,MAAM,IAAI,SAAe,SAAS,WAAW;GAC5C,KAAK,aAAa;GAClB,KAAK,WAAW;GAChB,KAAK,UAAU;GACf,OAAO,iBAAiB,QAAQ,KAAK,QAAQ;GAC7C,OAAO,iBAAiB,SAAS,KAAK,UAAU;EACjD,CAAC;CACF;CAEA,MAAM,KAAK,SAAwC;EAClD,MAAM,SAAS,KAAK;EAOpB,IACC,KAAK,WACL,QAAQ,eAAe,UAAU,WACjC,QAAQ,eAAe,UAAU,QAEjC,MAAM,IAAI,MAAM,sCAAsC;EAEvD,MAAM,OAAO,KAAK,UAAU,OAAO;EAGnC,IAAI,WAAW,KAAA,KAAa,OAAO,eAAe,UAAU,MAAM,OAAO,KAAK,IAAI;OAC7E,KAAK,OAAO,KAAK,IAAI;CAC3B;CAEA,MAAM,QAAuB;EAC5B,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EAIf,KAAK,SAAS,CAAC;EACf,MAAM,SAAS,KAAK;EACpB,MAAM,UAAU,KAAK;EACrB,KAAK,kBAAkB;EACvB,KAAK,SAAS;EACd,KAAK,UAAU,KAAA;EACf,IAAI,WAAW,KAAA,GAAW,OAAO,MAAM;EACvC,KAAK,SAAS,KAAK,OAAO;EAC1B,UAAU;CACX;CAIA,MAAM,QAAyB;EAC9B,OAAO,iBAAiB,WAAW,KAAK,MAAM;EAC9C,OAAO,iBAAiB,SAAS,KAAK,OAAO;EAC7C,OAAO,iBAAiB,SAAS,KAAK,QAAQ;CAC/C;CAKA,WAAiB;EAChB,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,KAAA,GAAW;EAC1B,OAAO,oBAAoB,WAAW,KAAK,MAAM;EACjD,OAAO,oBAAoB,SAAS,KAAK,OAAO;EAChD,OAAO,oBAAoB,SAAS,KAAK,QAAQ;CAClD;CAEA,oBAA0B;EACzB,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,KAAA,GAAW;EAC1B,OAAO,oBAAoB,QAAQ,KAAK,QAAQ;EAChD,OAAO,oBAAoB,SAAS,KAAK,UAAU;EACnD,KAAK,aAAa,KAAA;EAClB,KAAK,WAAW,KAAA;EAChB,KAAK,UAAU,KAAA;CAChB;CAGA,OAAO,QAAyB;EAC/B,KAAK,MAAM,QAAQ,KAAK,OAAO,OAAO,CAAC,GAAG,OAAO,KAAK,IAAI;CAC3D;CAEA,UAAgB;EACf,MAAM,SAAS,KAAK;EACpB,MAAM,UAAU,KAAK;EACrB,IAAI,WAAW,KAAA,KAAa,YAAY,KAAA,GAAW;EACnD,KAAK,kBAAkB;EACvB,KAAK,OAAO,MAAM;EAClB,QAAQ;CACT;CAEA,oBAA0B;EACzB,MAAM,SAAS,KAAK;EACpB,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,KAAA,KAAa,WAAW,KAAA,KAAa,OAAO,eAAe,UAAU,MAAM;EAC1F,KAAK,kBAAkB;EACvB,KAAK,SAAS;EACd,KAAK,UAAU,KAAA;EACf,uBAAO,IAAI,MAAM,6BAA6B,CAAC;CAChD;CAMA,SAAS,MAAqB;EAC7B,IAAI,CAAC,SAAS,IAAI,GAAG;GACpB,KAAK,SAAS,KAAK,yBAAS,IAAI,MAAM,0BAA0B,CAAC;GACjE;EACD;EACA,eAAe,KAAK,UAAU,MAAM,8BAA8B;CACnE;CAKA,WAAiB;EAChB,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EAEf,KAAK,SAAS,CAAC;EACf,KAAK,SAAS;EACd,KAAK,UAAU,KAAA;EACf,KAAK,SAAS,KAAK,OAAO;CAC3B;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5LA,SAAgB,+BACf,SAC+B;CAC/B,OAAO,IAAI,yBAAyB,OAAO;AAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,0BACf,SAC+B;CAC/B,OAAO,IAAI,oBAAoB,OAAO;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,2BACf,SACwB;CACxB,OAAO,IAAI,qBAAqB,OAAO;AACxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,kBACf,SACA,QAAwB,YACD;CACvB,MAAM,SAAS,gBAAgB;EAC9B,OAAO,QAAQ;EACf,UAAU;GACT,MAAM,QAAQ,QAAA;GACd,SAAS,QAAQ,WAAA;EAClB;CACD,CAAC;CACD,MAAM,iBAAiB,qBAAqB,KAAK;CACjD,MAAM,cAAc,WAAW,QAAQ,cAAc;CACrD,MAAM,4BAAY,IAAI,IAA6B;CACnD,MAAM,YAAY,2BAA2B,QAAQ,gBAAgB,WAAW,OAAO;CACvF,MAAM,iBAAiB,WAAW,SAAS;CAC3C,IAAI,UAAU;CACd,OAAO,EACN,OAAa;EACZ,IAAI,SAAS;EACb,UAAU;EACV,MAAM,oBAAoB,WAAW,SAAS;EAC9C,YAAY;EACZ,KAAK,MAAM,YAAY,UAAU,OAAO,GAAG,SAAS;EAGpD,UAAU,MAAM;CACjB,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,2BACf,QACA,gBACA,WACA,SACgC;CAChC,QAAQ,UAA8B;EACrC,MAAM,QAAQ,MAAM;EACpB,IAAI,MAAM,SAAS,GAAG;GAErB,IAAI,QAAQ,WAAW,KAAA,KAAa,CAAC,QAAQ,OAAO,KAAK,GAAG;GAC5D,MAAM,OAAO,MAAM;GACnB,IAAI,SAAS,KAAA,GAAW;GAIxB,IAAI,UAAU,IAAI,IAAI,GAAG;GACzB,MAAM,YAAY,IAAI,qBAAqB,EAAE,KAAK,CAAC;GACnD,MAAM,SAAS,WAAW,QAAQ,SAAS;GAC3C,UAAU,IAAI,YAAY;IACzB,OAAO;IACP,UAAU,MAAM;GACjB,CAAC;GACD;EACD;EACA,IAAI,SAAS,MAAM,IAAI,GAAG,eAAe,QAAQ,MAAM,IAAI;CAC5D;AACD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,qBAAqB,OAAgD;CACpF,IAAI;CACJ,IAAI;CACJ,OAAO;EACN,KAAK,SAAuB;GAC3B,MAAM,YAAY,OAAO;EAC1B;EACA,OAAO,SAA0C;GAChD,YAAY;EACb;EACA,OAAO,SAA2B;GACjC,WAAW;EACZ;EACA,QAAc;GACb,WAAW;EACZ;EACA,QAAQ,SAAuB;GAC9B,YAAY,OAAO;EACpB;CACD;AACD"}
|