@orkestrel/mcp 0.0.33 → 0.0.34
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 +3 -2
- package/dist/src/browser/index.d.ts +65 -12
- package/dist/src/browser/index.js +36 -3
- package/dist/src/browser/index.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -76,8 +76,9 @@ and the browser face (`./browser`), which is ESM only.
|
|
|
76
76
|
|
|
77
77
|
`npm run test:conformance` starts the real Streamable HTTP server from this
|
|
78
78
|
package's source and runs
|
|
79
|
-
`@modelcontextprotocol/conformance@0.2.0-alpha.
|
|
80
|
-
`2026-07-28`. The recorded result is **
|
|
79
|
+
`@modelcontextprotocol/conformance@0.2.0-alpha.11` against MCP revision
|
|
80
|
+
`2026-07-28`. The recorded result is **147 passed / 0 failed**, from the run on
|
|
81
|
+
2026-09-29 against the built `dist/`. That is a
|
|
81
82
|
genuine foreign MCP client driving this server end to end, and it is
|
|
82
83
|
evidence about the wire. It resolves the runner from `node_modules` and
|
|
83
84
|
drives a loopback socket, so the run is offline and `npm test` gates it.
|
|
@@ -433,6 +433,23 @@ export declare function isWebMCPDocument(value: unknown): value is WebMCPDocumen
|
|
|
433
433
|
*/
|
|
434
434
|
export declare function isWebMCPRegistry(value: unknown): value is WebMCPRegistryInterface;
|
|
435
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Determines whether an unknown value is a WebMCP execution event.
|
|
438
|
+
*
|
|
439
|
+
* @remarks
|
|
440
|
+
* Reads the IDL's `toolName` attribute and nothing else, so it admits a `ToolActivatedEvent`
|
|
441
|
+
* and a `ToolCancelEvent` and refuses a plain `Event`.
|
|
442
|
+
*
|
|
443
|
+
* @param value - The unknown value to inspect
|
|
444
|
+
* @returns True if the value carries a string `toolName`; false otherwise
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```ts
|
|
448
|
+
* isWebMCPToolEvent(new Event('toolactivated')) // false
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
export declare function isWebMCPToolEvent(value: unknown): value is WebMCPToolEvent;
|
|
452
|
+
|
|
436
453
|
/**
|
|
437
454
|
* Determines whether two WebMCP descriptors advertise the same tool to the registry.
|
|
438
455
|
*
|
|
@@ -618,7 +635,7 @@ export declare interface MessagePortTransportOptions {
|
|
|
618
635
|
export declare class ModelContext implements ModelContextInterface {
|
|
619
636
|
#private;
|
|
620
637
|
/**
|
|
621
|
-
* Binds a narrowed document's registry and arms
|
|
638
|
+
* Binds a narrowed document's registry and arms its change and execution subscriptions.
|
|
622
639
|
*
|
|
623
640
|
* @param document - The document whose `modelContext` this handle bridges
|
|
624
641
|
* @param options - The emitter's initial hooks and listener-error handler; see
|
|
@@ -632,28 +649,37 @@ export declare class ModelContext implements ModelContextInterface {
|
|
|
632
649
|
}
|
|
633
650
|
|
|
634
651
|
/**
|
|
635
|
-
* Options for {@link ModelContextInterface.adopt} — the origins
|
|
652
|
+
* Options for {@link ModelContextInterface.adopt} — the origins and debugging tools to include.
|
|
636
653
|
*
|
|
637
654
|
* @remarks
|
|
638
655
|
* `origins` is this package's one-word name for WebMCP's `fromOrigins`, forwarded unchanged.
|
|
639
656
|
* Omitting it reads this document's own registrations.
|
|
657
|
+
* If `debugging` is `true`, tools carrying `annotations.debugging: true` are included;
|
|
658
|
+
* if `false` or omitted, they are excluded. Default: `false`.
|
|
640
659
|
*/
|
|
641
660
|
export declare interface ModelContextAdoptOptions {
|
|
642
661
|
readonly origins?: readonly string[];
|
|
662
|
+
readonly debugging?: boolean;
|
|
643
663
|
}
|
|
644
664
|
|
|
645
665
|
/**
|
|
646
|
-
* Reports
|
|
666
|
+
* Reports changes and execution events from a WebMCP registry.
|
|
647
667
|
*
|
|
648
668
|
* @remarks
|
|
649
669
|
* Declared as a `type` alias rather than an interface, so the type-literal satisfies
|
|
650
|
-
* `EventMap` structurally.
|
|
670
|
+
* `EventMap` structurally. The 2026-09-29 WebMCP draft's
|
|
651
671
|
* `toolchange` names no tool and carries no payload, so the bridge republishes it as a bare
|
|
652
672
|
* signal and a listener re-reads {@link ModelContextInterface.adopt} to learn what changed.
|
|
673
|
+
* `toolactivated` and `toolcancel` carry the tool name; cancellation uses the bridge's
|
|
674
|
+
* lifecycle verb `abort`.
|
|
653
675
|
*/
|
|
654
676
|
export declare type ModelContextEventMap = {
|
|
655
677
|
/** Reports that the document's registry changed — re-read it to learn how. */
|
|
656
678
|
readonly change: readonly [];
|
|
679
|
+
/** Reports the tool whose execution begins. */
|
|
680
|
+
readonly activate: readonly [name: string];
|
|
681
|
+
/** Reports the tool whose pending execution is aborted. */
|
|
682
|
+
readonly abort: readonly [name: string];
|
|
657
683
|
};
|
|
658
684
|
|
|
659
685
|
/**
|
|
@@ -672,7 +698,7 @@ export declare type ModelContextEventMap = {
|
|
|
672
698
|
* never registered are untouched.
|
|
673
699
|
*/
|
|
674
700
|
export declare interface ModelContextInterface {
|
|
675
|
-
/** Holds the emitter republishing
|
|
701
|
+
/** Holds the emitter republishing registry changes, activation, and aborts. */
|
|
676
702
|
readonly emitter: EmitterInterface<ModelContextEventMap>;
|
|
677
703
|
/**
|
|
678
704
|
* Registers every tool the manager holds at this moment, then follows it.
|
|
@@ -734,9 +760,12 @@ export declare interface ModelContextInterface {
|
|
|
734
760
|
*/
|
|
735
761
|
publish(tools: ToolManagerInterface, options?: ModelContextPublishOptions): Promise<void>;
|
|
736
762
|
/**
|
|
737
|
-
* Reads the document's registered tools as locally executable tools.
|
|
763
|
+
* Reads the document's registered tools as locally executable tools, excluding debugging tools unless requested.
|
|
738
764
|
*
|
|
739
765
|
* @remarks
|
|
766
|
+
* Tools carrying `annotations.debugging: true` are excluded unless `options.debugging`
|
|
767
|
+
* is `true`. Tools with a false or omitted hint are included either way.
|
|
768
|
+
*
|
|
740
769
|
* Each returned tool's `execute` runs the registry's `executeTool` and forwards its
|
|
741
770
|
* `ToolContext.signal` as WebMCP's `signal`, so an agent-side abort reaches the foreign
|
|
742
771
|
* tool. The value resolves unchanged: WebMCP's own sources disagree about whether a tool
|
|
@@ -747,8 +776,8 @@ export declare interface ModelContextInterface {
|
|
|
747
776
|
* unreadable or hostile schema refuse the whole `adopt` call, and the arguments reach a
|
|
748
777
|
* handler in another document that has to validate them anyway.
|
|
749
778
|
*
|
|
750
|
-
* @param options - The
|
|
751
|
-
* @returns The registry
|
|
779
|
+
* @param options - The origin and debugging filters; see {@link ModelContextAdoptOptions}
|
|
780
|
+
* @returns The included registry tools, in registry order
|
|
752
781
|
*/
|
|
753
782
|
adopt(options?: ModelContextAdoptOptions): Promise<readonly ToolInterface[]>;
|
|
754
783
|
/**
|
|
@@ -964,6 +993,12 @@ export declare function toolAnnotationsToWebMCP(annotations: ToolAnnotations): W
|
|
|
964
993
|
*/
|
|
965
994
|
export declare function toolToWebMCP(definition: ToolDefinition): WebMCPDescriptor | undefined;
|
|
966
995
|
|
|
996
|
+
/** Names the WebMCP IDL `toolcancel` event the bridge republishes as `abort`. */
|
|
997
|
+
export declare const WEBMCP_ABORT_EVENT = "toolcancel";
|
|
998
|
+
|
|
999
|
+
/** Names the WebMCP IDL `toolactivated` event the bridge republishes as `activate`. */
|
|
1000
|
+
export declare const WEBMCP_ACTIVATED_EVENT = "toolactivated";
|
|
1001
|
+
|
|
967
1002
|
/** Names the WebMCP registry event the bridge republishes as its own `change`. */
|
|
968
1003
|
export declare const WEBMCP_CHANGE_EVENT = "toolchange";
|
|
969
1004
|
|
|
@@ -971,17 +1006,20 @@ export declare const WEBMCP_CHANGE_EVENT = "toolchange";
|
|
|
971
1006
|
* Describes a tool's observable effects as the WebMCP registry declares them.
|
|
972
1007
|
*
|
|
973
1008
|
* @remarks
|
|
974
|
-
* Transliterates the WebMCP `ToolAnnotations` dictionary. The IDL defaults each member to
|
|
1009
|
+
* Transliterates the 2026-09-29 WebMCP draft's `ToolAnnotations` dictionary. The IDL defaults each member to
|
|
975
1010
|
* `false`; this declaration keeps every member optional instead, because the bridge projects
|
|
976
1011
|
* from `@orkestrel/tool`'s `ToolAnnotations` and never invents a hint the author omitted. The
|
|
977
1012
|
* mapping is `pure` to `readOnlyHint`, `untrusted` to `untrustedContentHint`, and
|
|
978
1013
|
* `consequential` to `consequentialHint` — a fuller correspondence than the MCP wire's, which
|
|
979
1014
|
* has no counterpart for `untrusted` and spells the consequence `destructiveHint`.
|
|
1015
|
+
* The `debugging` hint has no domain counterpart: publication omits it and adoption filters it before
|
|
1016
|
+
* projecting the remaining hints.
|
|
980
1017
|
*/
|
|
981
1018
|
export declare interface WebMCPAnnotations {
|
|
982
1019
|
readonly readOnlyHint?: boolean;
|
|
983
1020
|
readonly untrustedContentHint?: boolean;
|
|
984
1021
|
readonly consequentialHint?: boolean;
|
|
1022
|
+
readonly debugging?: boolean;
|
|
985
1023
|
}
|
|
986
1024
|
|
|
987
1025
|
/**
|
|
@@ -1109,9 +1147,10 @@ export declare interface WebMCPRegisterOptions {
|
|
|
1109
1147
|
* Represents the WebMCP tool registry a document exposes as `document.modelContext`.
|
|
1110
1148
|
*
|
|
1111
1149
|
* @remarks
|
|
1112
|
-
* Transliterates the WebMCP `ModelContext` interface, which extends
|
|
1113
|
-
* operations plus the `toolchange`
|
|
1114
|
-
* {@link ModelContextEventMap}'s `change
|
|
1150
|
+
* Transliterates the 2026-09-29 WebMCP draft's `ModelContext` interface, which extends
|
|
1151
|
+
* `EventTarget`: the operations plus the `toolchange`, `toolactivated`, and `toolcancel`
|
|
1152
|
+
* subscriptions the bridge republishes as {@link ModelContextEventMap}'s `change`,
|
|
1153
|
+
* `activate`, and `abort`. Only the members the bridge touches are declared,
|
|
1115
1154
|
* exactly as {@link ScopeInterface} declares only what `createScopeServer` touches, so a real
|
|
1116
1155
|
* `ModelContext` satisfies this structurally and an IDL-faithful double satisfies it without
|
|
1117
1156
|
* implementing the whole of `EventTarget`.
|
|
@@ -1131,8 +1170,10 @@ export declare interface WebMCPRegistryInterface {
|
|
|
1131
1170
|
executeTool(tool: WebMCPRegisteredTool, input?: Readonly<Record<string, unknown>>, options?: WebMCPExecuteOptions): Promise<unknown>;
|
|
1132
1171
|
/** Subscribes to the registry's `toolchange` event. */
|
|
1133
1172
|
addEventListener(type: 'toolchange', listener: () => void): void;
|
|
1173
|
+
addEventListener(type: 'toolactivated' | 'toolcancel', listener: (event: Event) => void): void;
|
|
1134
1174
|
/** Drops a `toolchange` subscription. */
|
|
1135
1175
|
removeEventListener(type: 'toolchange', listener: () => void): void;
|
|
1176
|
+
removeEventListener(type: 'toolactivated' | 'toolcancel', listener: (event: Event) => void): void;
|
|
1136
1177
|
}
|
|
1137
1178
|
|
|
1138
1179
|
/**
|
|
@@ -1146,6 +1187,18 @@ export declare interface WebMCPTool extends WebMCPDescriptor {
|
|
|
1146
1187
|
readonly execute: WebMCPExecuteHandler;
|
|
1147
1188
|
}
|
|
1148
1189
|
|
|
1190
|
+
/**
|
|
1191
|
+
* Carries the tool name a WebMCP execution event reports.
|
|
1192
|
+
*
|
|
1193
|
+
* @remarks
|
|
1194
|
+
* Transliterates the `toolName` attribute of `ToolActivatedEvent` and `ToolCancelEvent` in
|
|
1195
|
+
* the 2026-09-29 WebMCP draft, the only event member the bridge reads. The `isWebMCPToolEvent`
|
|
1196
|
+
* guard narrows a dispatched `Event` onto this shape.
|
|
1197
|
+
*/
|
|
1198
|
+
export declare interface WebMCPToolEvent {
|
|
1199
|
+
readonly toolName: string;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1149
1202
|
/**
|
|
1150
1203
|
* Options for the WebMCP registry's `getTools` — the origins whose tools are read.
|
|
1151
1204
|
*
|
|
@@ -9,6 +9,10 @@ var DEFAULT_MCP_SERVER_NAME = "@orkestrel/mcp";
|
|
|
9
9
|
var DEFAULT_MCP_SERVER_VERSION = "1.0.0";
|
|
10
10
|
/** Names the WebMCP registry event the bridge republishes as its own `change`. */
|
|
11
11
|
var WEBMCP_CHANGE_EVENT = "toolchange";
|
|
12
|
+
/** Names the WebMCP IDL `toolactivated` event the bridge republishes as `activate`. */
|
|
13
|
+
var WEBMCP_ACTIVATED_EVENT = "toolactivated";
|
|
14
|
+
/** Names the WebMCP IDL `toolcancel` event the bridge republishes as `abort`. */
|
|
15
|
+
var WEBMCP_ABORT_EVENT = "toolcancel";
|
|
12
16
|
//#endregion
|
|
13
17
|
//#region src/browser/validators.ts
|
|
14
18
|
/**
|
|
@@ -57,6 +61,24 @@ function isWebMCPRegistry(value) {
|
|
|
57
61
|
function isWebMCPDocument(value) {
|
|
58
62
|
return objectOf({ modelContext: isWebMCPRegistry })(value);
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Determines whether an unknown value is a WebMCP execution event.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* Reads the IDL's `toolName` attribute and nothing else, so it admits a `ToolActivatedEvent`
|
|
69
|
+
* and a `ToolCancelEvent` and refuses a plain `Event`.
|
|
70
|
+
*
|
|
71
|
+
* @param value - The unknown value to inspect
|
|
72
|
+
* @returns True if the value carries a string `toolName`; false otherwise
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* isWebMCPToolEvent(new Event('toolactivated')) // false
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
function isWebMCPToolEvent(value) {
|
|
80
|
+
return objectOf({ toolName: isString })(value);
|
|
81
|
+
}
|
|
60
82
|
//#endregion
|
|
61
83
|
//#region src/browser/helpers.ts
|
|
62
84
|
/**
|
|
@@ -358,12 +380,14 @@ var ModelContext = class {
|
|
|
358
380
|
#emitter;
|
|
359
381
|
#registrations = /* @__PURE__ */ new Map();
|
|
360
382
|
#listener;
|
|
383
|
+
#activated;
|
|
384
|
+
#aborted;
|
|
361
385
|
#followed = void 0;
|
|
362
386
|
#pendingManager = void 0;
|
|
363
387
|
#queue = Promise.resolve();
|
|
364
388
|
#destroyed = false;
|
|
365
389
|
/**
|
|
366
|
-
* Binds a narrowed document's registry and arms
|
|
390
|
+
* Binds a narrowed document's registry and arms its change and execution subscriptions.
|
|
367
391
|
*
|
|
368
392
|
* @param document - The document whose `modelContext` this handle bridges
|
|
369
393
|
* @param options - The emitter's initial hooks and listener-error handler; see
|
|
@@ -377,6 +401,10 @@ var ModelContext = class {
|
|
|
377
401
|
});
|
|
378
402
|
this.#listener = this.#republish.bind(this);
|
|
379
403
|
this.#registry.addEventListener(WEBMCP_CHANGE_EVENT, this.#listener);
|
|
404
|
+
this.#activated = this.#republishTool.bind(this, "activate");
|
|
405
|
+
this.#aborted = this.#republishTool.bind(this, "abort");
|
|
406
|
+
this.#registry.addEventListener(WEBMCP_ACTIVATED_EVENT, this.#activated);
|
|
407
|
+
this.#registry.addEventListener(WEBMCP_ABORT_EVENT, this.#aborted);
|
|
380
408
|
}
|
|
381
409
|
get emitter() {
|
|
382
410
|
return this.#emitter;
|
|
@@ -391,7 +419,7 @@ var ModelContext = class {
|
|
|
391
419
|
return settled;
|
|
392
420
|
}
|
|
393
421
|
async adopt(options) {
|
|
394
|
-
return (await this.#registry.getTools(options?.origins === void 0 ? {} : { fromOrigins: options.origins })).map((tool) => createTool({
|
|
422
|
+
return (await this.#registry.getTools(options?.origins === void 0 ? {} : { fromOrigins: options.origins })).filter((tool) => options?.debugging === true || tool.annotations?.debugging !== true).map((tool) => createTool({
|
|
395
423
|
...webMCPToTool(tool),
|
|
396
424
|
execute: this.#execute.bind(this, tool)
|
|
397
425
|
}));
|
|
@@ -402,6 +430,8 @@ var ModelContext = class {
|
|
|
402
430
|
this.#unfollow();
|
|
403
431
|
this.#pendingManager = void 0;
|
|
404
432
|
this.#registry.removeEventListener(WEBMCP_CHANGE_EVENT, this.#listener);
|
|
433
|
+
this.#registry.removeEventListener(WEBMCP_ACTIVATED_EVENT, this.#activated);
|
|
434
|
+
this.#registry.removeEventListener(WEBMCP_ABORT_EVENT, this.#aborted);
|
|
405
435
|
for (const held of this.#registrations.values()) held.controller.abort();
|
|
406
436
|
this.#registrations.clear();
|
|
407
437
|
this.#emitter.destroy();
|
|
@@ -409,6 +439,9 @@ var ModelContext = class {
|
|
|
409
439
|
#republish() {
|
|
410
440
|
this.#emitter.emit("change");
|
|
411
441
|
}
|
|
442
|
+
#republishTool(name, event) {
|
|
443
|
+
if (isWebMCPToolEvent(event)) this.#emitter.emit(name, event.toolName);
|
|
444
|
+
}
|
|
412
445
|
#follow(tools, options) {
|
|
413
446
|
this.#unfollow();
|
|
414
447
|
const changed = this.#changed.bind(this, tools, options);
|
|
@@ -1182,6 +1215,6 @@ function createModelContext(options) {
|
|
|
1182
1215
|
return new ModelContext(host, options);
|
|
1183
1216
|
}
|
|
1184
1217
|
//#endregion
|
|
1185
|
-
export { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION, MessagePortTransport, ModelContext, WEBMCP_CHANGE_EVENT, WebSocketClientTransport, buildWebMCPProjections, collectWebMCPProjections, createHTTPClientTransport, createMessagePortTransport, createModelContext, createPageServer, createScopeMessageListener, createScopeServer, createScopeTransport, createWebSocketClientTransport, describeWebMCPTool, isWebMCPDocument, isWebMCPRegistry, matchesDescriptor, toolAnnotationsToWebMCP, toolToWebMCP, webMCPAnnotationsToTool, webMCPToTool };
|
|
1218
|
+
export { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION, MessagePortTransport, ModelContext, WEBMCP_ABORT_EVENT, WEBMCP_ACTIVATED_EVENT, WEBMCP_CHANGE_EVENT, WebSocketClientTransport, buildWebMCPProjections, collectWebMCPProjections, createHTTPClientTransport, createMessagePortTransport, createModelContext, createPageServer, createScopeMessageListener, createScopeServer, createScopeTransport, createWebSocketClientTransport, describeWebMCPTool, isWebMCPDocument, isWebMCPRegistry, isWebMCPToolEvent, matchesDescriptor, toolAnnotationsToWebMCP, toolToWebMCP, webMCPAnnotationsToTool, webMCPToTool };
|
|
1186
1219
|
|
|
1187
1220
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/validators.ts","../../../src/browser/helpers.ts","../../../src/browser/ModelContext.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\n// WebMCP registry subscription — the event name `ModelContext` binds on the document's\n// registry. The members `isWebMCPRegistry` requires are the guard shape in `validators.ts`,\n// where `objectOf` reads them, rather than a second list here that could disagree with it.\n\n/** Names the WebMCP registry event the bridge republishes as its own `change`. */\nexport const WEBMCP_CHANGE_EVENT = 'toolchange'\n","import type { WebMCPDocument, WebMCPRegistryInterface } from './types.js'\nimport { isFunction, objectOf } from '@orkestrel/contract'\n\n// The browser face's guards. Both narrow a FOREIGN surface no TypeScript library declares, so\n// each enforces the published WebMCP contract and no more: the operations the bridge calls and\n// the subscription it registers, each read as the IDL declares it and nothing read beyond\n// that. Both are built from `@orkestrel/contract`'s `objectOf`, which is the combinator this\n// case needs and the reason neither guard hand-rolls a read loop: it admits unknown members,\n// reads each declared member through `Reflect.get` so a prototype-carried operation satisfies\n// the shape, refuses arrays and primitives, and answers `false` on a hostile read instead of\n// throwing. Neither uses an exact-record guard. A `Document` and a `ModelContext` are platform\n// class instances whose members arrive through a prototype chain, and an exact-record guard\n// refuses exactly those — it would fail closed on every valid implementation.\n\n/**\n * Determines whether an unknown value is a WebMCP tool registry.\n *\n * @remarks\n * Reads the members the bridge dereferences — the IDL's `registerTool`, `getTools`, and\n * `executeTool` operations, plus the `EventTarget` pair the `toolchange` subscription needs —\n * and nothing else. A registry carrying extra members is still a registry, and a user agent's\n * own implementation reaches every one of these through its prototype.\n *\n * @param value - The unknown value to inspect\n * @returns True if the value exposes every WebMCP registry operation; false otherwise\n *\n * @example\n * ```ts\n * isWebMCPRegistry({}) // false\n * ```\n */\nexport function isWebMCPRegistry(value: unknown): value is WebMCPRegistryInterface {\n\treturn objectOf({\n\t\tregisterTool: isFunction,\n\t\tgetTools: isFunction,\n\t\texecuteTool: isFunction,\n\t\taddEventListener: isFunction,\n\t\tremoveEventListener: isFunction,\n\t})(value)\n}\n\n/**\n * Determines whether an unknown value is a document exposing the WebMCP tool registry.\n *\n * @remarks\n * This is the feature detection {@link import('./factories.js').createModelContext} performs,\n * published so a consumer can run it before deciding to build a bridge at all. It reads\n * `modelContext` and checks it with {@link isWebMCPRegistry}; it asserts nothing about the rest\n * of a `Document`, because that member is the whole of what the bridge needs.\n *\n * @param value - The unknown value to inspect\n * @returns True if the value carries a WebMCP registry; false otherwise\n *\n * @example\n * ```ts\n * isWebMCPDocument(globalThis.document) // false in a browser that ships no WebMCP\n * ```\n */\nexport function isWebMCPDocument(value: unknown): value is WebMCPDocument {\n\treturn objectOf({ modelContext: isWebMCPRegistry })(value)\n}\n","import type { ToolAnnotations, ToolDefinition, ToolManagerInterface } from '@orkestrel/tool'\nimport type {\n\tWebMCPAnnotations,\n\tWebMCPDescriptor,\n\tWebMCPProjection,\n\tWebMCPRegisteredTool,\n} from './types.js'\nimport { JSONRPC_INVALID_PARAMS, MCPError } from '@src/core'\nimport { attempt, canonicalStringify } from '@orkestrel/contract'\nimport { toolToDefinition } from '@orkestrel/tool'\n\n// The browser face's pure projection leaves — the WebMCP direction of the same translation\n// `@orkestrel/mcp`'s `toolAnnotationsToMCP` / `mcpAnnotationsToTool` perform for the MCP wire.\n// They sit here rather than beside those because the surface they project onto is this face's:\n// WebMCP carries a third hint the MCP wire has no counterpart for (`untrustedContentHint`) and\n// spells the consequence differently (`consequentialHint`, not `destructiveHint`).\n\n/**\n * Projects domain tool annotations onto WebMCP registry hints without inventing defaults.\n *\n * @param annotations - The authored domain annotations\n * @returns The mapped hints; an omitted annotation stays omitted\n *\n * @example\n * ```ts\n * toolAnnotationsToWebMCP({ pure: true, untrusted: true }) // { readOnlyHint: true, untrustedContentHint: true }\n * ```\n */\nexport function toolAnnotationsToWebMCP(annotations: ToolAnnotations): WebMCPAnnotations {\n\treturn {\n\t\t...(annotations.pure === undefined ? {} : { readOnlyHint: annotations.pure }),\n\t\t...(annotations.untrusted === undefined ? {} : { untrustedContentHint: annotations.untrusted }),\n\t\t...(annotations.consequential === undefined\n\t\t\t? {}\n\t\t\t: { consequentialHint: annotations.consequential }),\n\t}\n}\n\n/**\n * Projects WebMCP registry hints onto domain tool annotations without inventing defaults.\n *\n * @param annotations - The registry's hints, as the WebMCP dictionary declares them\n * @returns The mapped annotations; an omitted hint stays omitted\n *\n * @example\n * ```ts\n * webMCPAnnotationsToTool({ readOnlyHint: false, consequentialHint: true }) // { pure: false, consequential: true }\n * ```\n */\nexport function webMCPAnnotationsToTool(annotations: WebMCPAnnotations): ToolAnnotations {\n\treturn {\n\t\t...(annotations.readOnlyHint === undefined ? {} : { pure: annotations.readOnlyHint }),\n\t\t...(annotations.untrustedContentHint === undefined\n\t\t\t? {}\n\t\t\t: { untrusted: annotations.untrustedContentHint }),\n\t\t...(annotations.consequentialHint === undefined\n\t\t\t? {}\n\t\t\t: { consequential: annotations.consequentialHint }),\n\t}\n}\n\n/**\n * Projects one advertised tool definition onto the WebMCP descriptor a registration carries.\n *\n * @remarks\n * Takes the definition a `ToolManagerInterface` advertises rather than the tool itself, so the\n * description here is the one the MCP wire advertises too — the registry substitutes an\n * authored `summary` for the full `description`, and reading the same projection keeps one\n * advertised description across both surfaces.\n *\n * WebMCP requires `description`, so a definition carrying none cannot be registered at all.\n * Returning `undefined` is what lets the caller refuse the whole batch before registering any\n * of it; registering an empty string instead would be an invented value a foreign agent reads\n * as a real one.\n *\n * @param definition - The advertised definition to project\n * @returns The WebMCP descriptor, or `undefined` when the definition advertises no description\n *\n * @example\n * ```ts\n * toolToWebMCP({ name: 'add', description: 'Adds two numbers' })?.description // 'Adds two numbers'\n * ```\n */\nexport function toolToWebMCP(definition: ToolDefinition): WebMCPDescriptor | undefined {\n\tif (definition.description === undefined) return undefined\n\tconst annotations =\n\t\tdefinition.annotations === undefined ? {} : toolAnnotationsToWebMCP(definition.annotations)\n\treturn {\n\t\tname: definition.name,\n\t\tdescription: definition.description,\n\t\t...(definition.title === undefined ? {} : { title: definition.title }),\n\t\t...(definition.parameters === undefined ? {} : { inputSchema: definition.parameters }),\n\t\t...(Object.keys(annotations).length === 0 ? {} : { annotations }),\n\t}\n}\n\n/**\n * Projects one registered WebMCP tool onto the tool definition an adopted tool advertises.\n *\n * @remarks\n * The inverse of {@link toolToWebMCP}, and deliberately lossy in the other direction: the\n * registry's `window` and `origin` describe where the tool lives rather than what it does, and\n * the bridge hands the whole registered record back to `executeTool` instead of rebuilding it.\n *\n * @param registered - The registered tool the registry reported\n * @returns The definition an adopted tool advertises\n *\n * @example\n * ```ts\n * webMCPToTool({ name: 'add', description: 'Adds', window, origin: 'https://a.example' }).name // 'add'\n * ```\n */\nexport function webMCPToTool(registered: WebMCPRegisteredTool): ToolDefinition {\n\tconst annotations =\n\t\tregistered.annotations === undefined ? {} : webMCPAnnotationsToTool(registered.annotations)\n\treturn {\n\t\tname: registered.name,\n\t\tdescription: registered.description,\n\t\t...(registered.title === undefined ? {} : { title: registered.title }),\n\t\t...(registered.inputSchema === undefined ? {} : { parameters: registered.inputSchema }),\n\t\t...(Object.keys(annotations).length === 0 ? {} : { annotations }),\n\t}\n}\n\n/**\n * Determines whether two WebMCP descriptors advertise the same tool to the registry.\n *\n * @remarks\n * The reading `ModelContextInterface.publish` reconciles a name against once the manager's\n * tool has changed under it: equal descriptors leave the live registration standing, because\n * execution routes through the manager by name, and anything else releases it and registers\n * the new one.\n *\n * Equality is structural and key-order-independent, through `@orkestrel/contract`'s\n * `canonicalStringify`: `inputSchema` is the author's own JSON Schema record, and two\n * authorings of the same schema that differ only in key order describe the same tool. A\n * descriptor JSON cannot encode — a cyclic or unreadable `inputSchema` — is reported as\n * unequal, which re-registers rather than serving a descriptor nothing could compare.\n *\n * @param held - The descriptor the live registration carries\n * @param projected - The descriptor this publication projected\n * @returns True when both describe the same tool; false otherwise\n *\n * @example\n * ```ts\n * matchesDescriptor({ name: 'add', description: 'Adds' }, { description: 'Adds', name: 'add' }) // true\n * ```\n */\nexport function matchesDescriptor(held: WebMCPDescriptor, projected: WebMCPDescriptor): boolean {\n\tconst left = attempt(() => canonicalStringify(held))\n\tconst right = attempt(() => canonicalStringify(projected))\n\tif (!left.success || !right.success) return false\n\treturn left.value !== undefined && left.value === right.value\n}\n\n/**\n * Projects the descriptor a registry advertises for one tool name, or reports that it has none.\n *\n * @remarks\n * Reads the manager's own `definitions()` rather than a tool instance, so the description here\n * is the one the registry advertises — an authored `summary` in place of the full\n * `description` — and one tool reaches the WebMCP registry and the MCP wire describing itself\n * the same way.\n *\n * `undefined` covers both answers a caller must not conflate with a descriptor: the manager\n * advertises no tool under that name, and the tool it advertises carries no description, which\n * is the member WebMCP requires.\n *\n * @param manager - The tool registry to read\n * @param name - The tool name to describe\n * @returns The WebMCP descriptor, or `undefined` when the registry advertises none\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', description: 'Adds two numbers', execute: () => 5 }))\n * describeWebMCPTool(tools, 'add')?.description // 'Adds two numbers'\n * ```\n */\nexport function describeWebMCPTool(\n\tmanager: ToolManagerInterface,\n\tname: string,\n): WebMCPDescriptor | undefined {\n\tfor (const definition of manager.definitions()) {\n\t\tif (definition.name === name) return toolToWebMCP(definition)\n\t}\n\treturn undefined\n}\n\n/**\n * Builds the WebMCP projection of every tool a registry advertises, or refuses the batch.\n *\n * @remarks\n * The WebMCP twin of `@orkestrel/mcp`'s `buildToolDescriptors`. Each tool is projected through\n * `@orkestrel/tool`'s own `toolToDefinition` — the projection `definitions()` applies — so a\n * tool advertises one description across the MCP wire and the WebMCP registry alike. The tool\n * travels beside its descriptor because a registration records which tool it was made for, and\n * a descriptor cannot report that.\n *\n * It refuses rather than skips. WebMCP requires `description`, and each alternative to\n * refusing is worse: an empty string is an invented value a foreign agent reads as a real one,\n * and silently dropping the tool publishes a registry missing a tool its author asked for.\n * Refusing before any registration happens is also what keeps `publish` atomic — nothing is\n * registered when one tool cannot be.\n *\n * @param manager - The tool registry to project\n * @returns One projection per advertised tool, in registry order\n * @throws Thrown as an `MCPError` carrying `-32602` when a tool advertises no description,\n * naming the tool\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', description: 'Adds two numbers', execute: () => 5 }))\n * buildWebMCPProjections(tools).map((projection) => projection.descriptor.name) // ['add']\n * ```\n */\nexport function buildWebMCPProjections(manager: ToolManagerInterface): readonly WebMCPProjection[] {\n\tconst projections: WebMCPProjection[] = []\n\tfor (const tool of manager.tools()) {\n\t\tconst descriptor = toolToWebMCP(toolToDefinition(tool))\n\t\tif (descriptor === undefined) {\n\t\t\tthrow new MCPError(\n\t\t\t\t`WebMCP requires a description for tool '${tool.name}'`,\n\t\t\t\tJSONRPC_INVALID_PARAMS,\n\t\t\t)\n\t\t}\n\t\tprojections.push({ tool, descriptor })\n\t}\n\treturn projections\n}\n\n/**\n * Collects the WebMCP projection of every tool a registry advertises that WebMCP can carry.\n *\n * @remarks\n * The skipping sibling of {@link buildWebMCPProjections}, and the reading a followed change\n * reconciles against: a followed change reaches no caller, so a tool advertising neither a\n * `description` nor a `summary` is left out of the collection rather than refusing a batch\n * nobody asked for.\n *\n * @param manager - The tool registry to project\n * @returns One projection per advertised tool WebMCP can carry, in registry order\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'bare', execute: () => 1 }))\n * collectWebMCPProjections(tools) // []\n * ```\n */\nexport function collectWebMCPProjections(\n\tmanager: ToolManagerInterface,\n): readonly WebMCPProjection[] {\n\tconst projections: WebMCPProjection[] = []\n\tfor (const tool of manager.tools()) {\n\t\tconst descriptor = toolToWebMCP(toolToDefinition(tool))\n\t\tif (descriptor !== undefined) projections.push({ tool, descriptor })\n\t}\n\treturn projections\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { ToolContext, ToolInterface, ToolManagerInterface } from '@orkestrel/tool'\nimport type {\n\tModelContextAdoptOptions,\n\tModelContextEventMap,\n\tModelContextInterface,\n\tModelContextOptions,\n\tModelContextPublishOptions,\n\tWebMCPDescriptor,\n\tWebMCPDocument,\n\tWebMCPHandlerOptions,\n\tWebMCPProjection,\n\tWebMCPRegisteredTool,\n\tWebMCPRegistryInterface,\n} from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { createTool } from '@orkestrel/tool'\nimport { attempt } from '@orkestrel/contract'\nimport { WEBMCP_CHANGE_EVENT } from './constants.js'\nimport {\n\tbuildWebMCPProjections,\n\tcollectWebMCPProjections,\n\tmatchesDescriptor,\n\twebMCPToTool,\n} from './helpers.js'\n\n/**\n * Bridges a `ToolManagerInterface` and a document's WebMCP tool registry — the\n * {@link ModelContextInterface} {@link import('./factories.js').createModelContext} returns.\n *\n * @remarks\n * - **It borrows the registry, it does not own it.** The handle registers tools, retains one\n * `AbortController` per registration, and aborts exactly those on `destroy`. WebMCP's own\n * unregistration path is that abort. Registration identity is the tool name, per document,\n * so releasing a name releases whatever now stands under it — including a same-name\n * registration another handle made later.\n * - **`publish` snapshots at the call, then follows the manager.** The manager is projected\n * when `publish` is called, before the work queues behind an earlier publication, so a\n * registry mutated while this call waits its turn does not decide what this call registers.\n * The same call subscribes to the manager's own `emitter`, so a later `add`, `remove`, or\n * `clear` reaches the document registry without a second `publish`.\n * - **One manager is followed at a time.** A `publish` naming another manager releases the\n * subscription and takes up the new one, and `destroy` releases it outright. An event from a\n * manager this handle no longer follows is ignored, which is what a listener republishing\n * another manager from inside a dispatch produces. A followed change has no caller to refuse\n * to, so a tool advertising neither a `description` nor a `summary` is left unregistered\n * rather than refusing anything; `publish` still refuses such a batch whole. Under a name\n * this handle never registered that skip emits no `change`, because nothing reached the\n * document registry. A synchronisation registers only what it can carry, so such a tool\n * standing under a name this handle already registered releases that registration rather\n * than leaving it advertising a descriptor the manager no longer stands behind, and that\n * release emits the registry's `change` like any other.\n * - **A followed change is a trigger, not a fact.** Each `add`, `remove`, or `clear` queues one\n * synchronisation of this handle's registrations for that manager against what the manager\n * holds when that queued work runs. The event cannot decide the outcome: `remove` and\n * `clear` name tools the manager no longer holds, an earlier listener in the same dispatch\n * may already have put another tool under one of those names, and the manager's `destroy`\n * empties its map after the `clear` it publishes. Reading the manager converges on its state\n * however the change was reached, so a synchronisation queued and not yet started already\n * covers every change that arrives before it runs and a second one is not queued. A\n * publication queued behind that synchronisation ends its cover, because the publication\n * prunes what the synchronisation registered: a change arriving after that call queues a\n * synchronisation of its own, which runs after the publication.\n * - **A later `publish` reconciles, and so does every synchronisation.** Each name is compared\n * with what this handle already registered for it: the same manager holding the same tool\n * leaves the registration alone, another tool of that same manager advertising an equal\n * descriptor leaves it registered and records the tool it now stands for, and anything else\n * releases it and registers the new descriptor bound to the new manager. A name the snapshot\n * dropped, or a name the manager no longer holds, is released — after the batch has\n * reconciled, never before, so no name is withdrawn while the tools replacing it are still\n * being registered. A failed batch releases them too, and still withdraws nothing it\n * carries. Nothing has to be removed from the manager to make the registry agree with it.\n * - **Work serializes.** Registration is asynchronous and `destroy` is not, so overlapping\n * calls would interleave registrations with the aborts meant to end them. Each publication\n * and each synchronisation queues behind the previous one, and every step re-reads the\n * destroyed flag, so a `destroy` issued mid-publish stops the registrations that have not\n * happened yet instead of racing them.\n * - **Nothing is polyfilled.** A document exposing no registry never reaches this class:\n * {@link import('./factories.js').createModelContext} returns `undefined` instead, so feature\n * absence stays absence rather than becoming a local implementation a caller mistakes for\n * the platform.\n * - **The result shape is the registry's.** An adopted tool resolves whatever `executeTool`\n * resolved, unchanged. WebMCP's IDL types that `Promise<DOMString>` while the\n * specification's README sample returns `{ content: [...] }`; the primary source disagrees\n * with itself, and normalizing either way would encode a guess as a contract.\n *\n * @example\n * ```ts\n * import { isWebMCPDocument, ModelContext } from '@orkestrel/mcp/browser'\n *\n * if (isWebMCPDocument(document)) {\n * \tconst bridge = new ModelContext(document)\n * \tawait bridge.publish(tools)\n * }\n * ```\n */\nexport class ModelContext implements ModelContextInterface {\n\treadonly #registry: WebMCPRegistryInterface\n\treadonly #emitter: Emitter<ModelContextEventMap>\n\t// What this handle registered, per name: the signal that releases it, the descriptor the\n\t// registry is advertising for it, the manager's own tool the registration was made for\n\t// (`tool`), and the manager its execution is bound to (`tools`). Genuinely private glue —\n\t// the controller alone cannot answer whether the manager still holds the tool this\n\t// registration serves, and answering that is the whole of the reconciliation.\n\treadonly #registrations = new Map<\n\t\tstring,\n\t\t{\n\t\t\treadonly controller: AbortController\n\t\t\treadonly descriptor: WebMCPDescriptor\n\t\t\treadonly tool: ToolInterface\n\t\t\treadonly tools: ToolManagerInterface\n\t\t}\n\t>()\n\treadonly #listener: () => void\n\t// The manager this handle is following, with the exact handler reference `off` needs to\n\t// release it. Genuinely private glue: the subscription is an implementation of `publish`'s\n\t// contract rather than a member a consumer reads, and one handler serves all three events\n\t// because each of them asks for the same thing — read the manager and agree with it.\n\t#followed: { readonly tools: ToolManagerInterface; readonly changed: () => void } | undefined =\n\t\tundefined\n\t// The manager whose synchronisation is queued, has not started, and has nothing queued\n\t// behind it. A second event for that manager before the work runs would read the same\n\t// state twice, so it is coalesced into the first; a `publish` queued behind it clears the\n\t// mark, because the publication that follows the synchronisation prunes what it registers.\n\t#pendingManager: ToolManagerInterface | undefined = undefined\n\t#queue: Promise<void> = Promise.resolve()\n\t#destroyed = false\n\n\t/**\n\t * Binds a narrowed document's registry and arms the `toolchange` subscription.\n\t *\n\t * @param document - The document whose `modelContext` this handle bridges\n\t * @param options - The emitter's initial hooks and listener-error handler; see\n\t * {@link ModelContextOptions}\n\t */\n\tconstructor(document: WebMCPDocument, options?: ModelContextOptions) {\n\t\tthis.#registry = document.modelContext\n\t\tthis.#emitter = new Emitter<ModelContextEventMap>({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\t// The subscription arms at construction rather than on a first `publish`, because a\n\t\t// registry change between the two would reach nothing. The bound reference is retained\n\t\t// so `destroy` removes the same listener it added.\n\t\tthis.#listener = this.#republish.bind(this)\n\t\tthis.#registry.addEventListener(WEBMCP_CHANGE_EVENT, this.#listener)\n\t}\n\n\tget emitter(): EmitterInterface<ModelContextEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tpublish(tools: ToolManagerInterface, options?: ModelContextPublishOptions): Promise<void> {\n\t\t// Projected here, at the call, rather than where the queue reaches this publication: the\n\t\t// contract is the tools the manager holds now, and a manager mutated while this call\n\t\t// waits behind an earlier one must not decide what this call registers. The projection\n\t\t// is also where a tool WebMCP cannot carry refuses the whole batch, so that refusal\n\t\t// reaches the caller as a rejection rather than as a synchronous throw.\n\t\tconst snapshot = attempt(() => buildWebMCPProjections(tools))\n\t\tif (!snapshot.success) return Promise.reject(snapshot.error)\n\t\t// Subscribed at the call, beside the snapshot, so there is no window between the two in\n\t\t// which a change reaches nothing. A refused projection registers nothing and therefore\n\t\t// follows nothing, and a destroyed handle follows nothing either.\n\t\tif (!this.#destroyed) this.#follow(tools, options)\n\t\t// A synchronisation already queued runs before this publication, and this publication\n\t\t// prunes what that synchronisation registered — so the mark it left stops covering the\n\t\t// changes that arrive from here on. Clearing it is what sends the next change to a\n\t\t// synchronisation of its own, queued after this publication rather than before it.\n\t\tthis.#pendingManager = undefined\n\t\tconst settled = this.#queue.then(() => this.#publish(tools, snapshot.value, options))\n\t\t// The queue tracks completion, never outcome: a rejected publish must not poison the\n\t\t// next one, and the caller already receives the rejection through `settled`.\n\t\tthis.#queue = settled.catch(() => undefined)\n\t\treturn settled\n\t}\n\n\tasync adopt(options?: ModelContextAdoptOptions): Promise<readonly ToolInterface[]> {\n\t\tconst registered = await this.#registry.getTools(\n\t\t\toptions?.origins === undefined ? {} : { fromOrigins: options.origins },\n\t\t)\n\t\treturn registered.map((tool) =>\n\t\t\tcreateTool({ ...webMCPToTool(tool), execute: this.#execute.bind(this, tool) }),\n\t\t)\n\t}\n\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#destroyed = true\n\t\tthis.#unfollow()\n\t\tthis.#pendingManager = undefined\n\t\tthis.#registry.removeEventListener(WEBMCP_CHANGE_EVENT, this.#listener)\n\t\tfor (const held of this.#registrations.values()) held.controller.abort()\n\t\tthis.#registrations.clear()\n\t\tthis.#emitter.destroy()\n\t}\n\n\t// Republishes the registry's own `toolchange` as this handle's `change`. It exists as a\n\t// method so `addEventListener` and `removeEventListener` receive one stable reference.\n\t#republish(): void {\n\t\tthis.#emitter.emit('change')\n\t}\n\n\t// Subscribes to a manager's own registry events, releasing whatever was followed before.\n\t// One manager at a time, because a handle's registrations are keyed by name and two\n\t// managers publishing one name would each believe they owned it.\n\t#follow(tools: ToolManagerInterface, options?: ModelContextPublishOptions): void {\n\t\tthis.#unfollow()\n\t\tconst changed = this.#changed.bind(this, tools, options)\n\t\ttools.emitter.on('add', changed)\n\t\ttools.emitter.on('remove', changed)\n\t\ttools.emitter.on('clear', changed)\n\t\tthis.#followed = { tools, changed }\n\t}\n\n\t// Releases the subscription by handing `off` the same reference `on` received. The\n\t// installed emitter's `on` returns nothing, so the handler identity the record retains is\n\t// the cleanup.\n\t#unfollow(): void {\n\t\tconst followed = this.#followed\n\t\tif (followed === undefined) return\n\t\tthis.#followed = undefined\n\t\tfollowed.tools.emitter.off('add', followed.changed)\n\t\tfollowed.tools.emitter.off('remove', followed.changed)\n\t\tfollowed.tools.emitter.off('clear', followed.changed)\n\t}\n\n\t// Reports whether an event is the followed manager's. `#unfollow` hands the emitter its\n\t// handlers back, but it cannot withdraw them from the listener array a dispatch already\n\t// walking that event is holding — so a listener that republishes another manager from\n\t// inside a dispatch leaves this handle's own handler still to run, for a subscription that\n\t// no longer exists. Manager identity is the whole reading, and it answers destruction too:\n\t// `destroy` releases the subscription before it aborts anything, so a destroyed handle\n\t// follows nobody and every followed handler that outlives it stops here.\n\t#follows(tools: ToolManagerInterface): boolean {\n\t\treturn this.#followed?.tools === tools\n\t}\n\n\t// Queues one synchronisation for a change the followed manager reported. Every event takes\n\t// this door, because each of them says only THAT the manager changed: `remove` and `clear`\n\t// carry tools the manager no longer holds, an earlier listener in the same dispatch may\n\t// already have put another tool under one of those names, and the manager's own `destroy`\n\t// empties its map after the `clear` it publishes. What the event carries therefore cannot\n\t// decide what to release; the manager's state when the queued work runs decides it. It\n\t// queues rather than acting here, so a change arriving while an earlier publication is\n\t// still registering runs after it instead of racing it.\n\t#changed(tools: ToolManagerInterface, options: ModelContextPublishOptions | undefined): void {\n\t\tif (!this.#follows(tools)) return\n\t\t// A synchronisation queued and not yet started reads the manager when it runs, so it\n\t\t// already covers every change that arrives before then — while nothing is queued behind\n\t\t// it. `publish` clears the mark for exactly that reason, so a change arriving after a\n\t\t// publication takes a synchronisation of its own rather than one the publication will\n\t\t// prune. The mark holds the manager rather than a boolean, so a change from a manager a\n\t\t// `publish` took up in the meantime still queues its own.\n\t\tif (this.#pendingManager === tools) return\n\t\tthis.#pendingManager = tools\n\t\tthis.#queue = this.#queue.then(this.#sync.bind(this, tools, options)).catch(() => undefined)\n\t}\n\n\t// Brings this handle's registrations for one manager to what that manager holds NOW. The\n\t// whole of what a followed change does: every tool the manager advertises reconciles, and a\n\t// held name it dropped is released. Only registrations bound to this manager are released,\n\t// so a name another manager's publication put there is left standing.\n\tasync #sync(tools: ToolManagerInterface, options?: ModelContextPublishOptions): Promise<void> {\n\t\t// The mark clears as this work STARTS, not when it was queued: the manager is read\n\t\t// below, so a change arriving from here on reaches a reading already taken and needs a\n\t\t// synchronisation of its own. A mark a later change left is cleared with it, which\n\t\t// costs one synchronisation that had already been covered and never one too few.\n\t\tif (this.#pendingManager === tools) this.#pendingManager = undefined\n\t\tif (this.#destroyed) return\n\t\t// A tool WebMCP cannot carry is skipped rather than refusing anything, because a\n\t\t// followed change has no caller a refusal could reach. The prune below then releases\n\t\t// the name that skip left out, so the registry advertises only what this handle can\n\t\t// carry rather than a descriptor whose tool the manager replaced with one WebMCP\n\t\t// cannot carry.\n\t\tconst projections = collectWebMCPProjections(tools)\n\t\ttry {\n\t\t\tfor (const projection of projections) await this.#reconcile(tools, projection, options)\n\t\t} finally {\n\t\t\tthis.#prune(projections, tools)\n\t\t}\n\t}\n\n\tasync #publish(\n\t\ttools: ToolManagerInterface,\n\t\tprojections: readonly WebMCPProjection[],\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\tif (this.#destroyed) return\n\t\ttry {\n\t\t\tfor (const projection of projections) await this.#reconcile(tools, projection, options)\n\t\t} finally {\n\t\t\tthis.#prune(projections)\n\t\t}\n\t}\n\n\t// Brings one name to the descriptor a publication or a followed change is asking for. The\n\t// single door both paths take, so a tool added through the manager's own event lands under\n\t// exactly the rule a `publish` would have applied to it.\n\tasync #reconcile(\n\t\ttools: ToolManagerInterface,\n\t\tprojection: WebMCPProjection,\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\t// The flag is re-read at every step that can register, and this is that step: a\n\t\t// publication's loop resumes here after each suspended registration, and a followed\n\t\t// change reaches it from a queue a `destroy` may have overtaken. Reading it once, here,\n\t\t// is what keeps one rule rather than a copy per caller to drift against.\n\t\tif (this.#destroyed) return\n\t\tconst { descriptor, tool } = projection\n\t\tconst held = this.#registrations.get(descriptor.name)\n\t\tif (held !== undefined && held.tools === tools) {\n\t\t\t// The manager holds the very tool this registration was made for, so nothing it\n\t\t\t// advertises can have changed. Reading tool identity rather than comparing\n\t\t\t// descriptors is also what leaves a descriptor JSON cannot encode — a cyclic\n\t\t\t// `inputSchema` — alone, instead of churning it on every change to another name.\n\t\t\tif (held.tool === tool) return\n\t\t\t// Another tool of the same manager, under the same name, advertising the same\n\t\t\t// descriptor. Execution routes through the manager by name, so a foreign agent\n\t\t\t// already reaches the replacement's handler, and re-registering would abort a\n\t\t\t// live registration to put an identical one back. The tool it now stands for is\n\t\t\t// recorded instead.\n\t\t\tif (matchesDescriptor(held.descriptor, descriptor)) {\n\t\t\t\tthis.#registrations.set(descriptor.name, { ...held, tool })\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif (held !== undefined) {\n\t\t\t// Anything else is a different tool under a name WebMCP keys per document, so the\n\t\t\t// registration this handle holds is released before the new one replaces it.\n\t\t\t// Leaving it would advertise a descriptor whose handler no longer matches it, and\n\t\t\t// a foreign agent would send arguments the registry told it were valid.\n\t\t\tthis.#registrations.delete(descriptor.name)\n\t\t\theld.controller.abort()\n\t\t\t// That abort is the registry's unregistration path, and the registry dispatches\n\t\t\t// `toolchange` inside it — synchronously, into listeners that can destroy this\n\t\t\t// handle. So the flag is re-read here, between releasing the old registration and\n\t\t\t// creating its replacement, or a destroyed handle would leave one live controller\n\t\t\t// behind that nothing will ever abort.\n\t\t\tif (this.#destroyed) return\n\t\t}\n\t\tawait this.#register(tools, projection, options)\n\t}\n\n\tasync #register(\n\t\ttools: ToolManagerInterface,\n\t\tprojection: WebMCPProjection,\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\tconst { descriptor, tool } = projection\n\t\tconst controller = new AbortController()\n\t\tthis.#registrations.set(descriptor.name, { controller, descriptor, tool, tools })\n\t\ttry {\n\t\t\tawait this.#registry.registerTool(\n\t\t\t\t{ ...descriptor, execute: this.#run.bind(this, tools, descriptor.name) },\n\t\t\t\t{\n\t\t\t\t\t...(options?.origins === undefined ? {} : { exposedTo: options.origins }),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t},\n\t\t\t)\n\t\t} catch (error) {\n\t\t\t// A registration the registry refused is not one this handle owns. Dropping the\n\t\t\t// entry lets a later publication or synchronisation register the name again, and\n\t\t\t// the abort releases a tool a registry recorded before it failed.\n\t\t\tthis.#registrations.delete(descriptor.name)\n\t\t\tcontroller.abort()\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Prunes this handle's registrations against the projections a batch reconciled: every\n\t// name they do not carry is released, and WebMCP's unregistration path is the registration\n\t// signal, so aborting is the removal. Both callers prune LAST, in a `finally` after their\n\t// projections have reconciled. One order, so no name is withdrawn while the tools replacing\n\t// it are still being registered; and a `finally`, so a batch that fails partway still\n\t// releases the names it dropped rather than leaving them advertised until some later batch\n\t// withdraws them. A failed batch withdraws nothing it carries: `kept` is read from the\n\t// projections rather than from the registrations, so the names the batch carries are\n\t// protected whether or not their reconcile ran. A publication hands its own snapshot,\n\t// across managers, because a publication decides the whole registry: reading the snapshot\n\t// rather than the live manager is why a manager emptied mid-publication does not unregister\n\t// what that publication captured. A synchronisation hands what the manager advertises now,\n\t// and names the manager, because a followed change decides only what that manager put\n\t// there.\n\t#prune(projections: readonly WebMCPProjection[], tools?: ToolManagerInterface): void {\n\t\t// Read here rather than at each caller, because each abort below dispatches `toolchange`\n\t\t// synchronously into listeners that can destroy this handle: one door, one reading, and\n\t\t// a destroyed handle has already taken back every registration itself.\n\t\tif (this.#destroyed) return\n\t\tconst kept = new Set(projections.map((projection) => projection.descriptor.name))\n\t\tfor (const [name, held] of this.#registrations) {\n\t\t\tif (kept.has(name) || (tools !== undefined && held.tools !== tools)) continue\n\t\t\tthis.#registrations.delete(name)\n\t\t\theld.controller.abort()\n\t\t\t// The abort dispatches `toolchange` synchronously, into listeners that can destroy\n\t\t\t// this handle, and `destroy` takes back every remaining registration itself.\n\t\t\tif (this.#destroyed) return\n\t\t}\n\t}\n\n\t// Runs one published tool on behalf of the registry. The registry's signal becomes the\n\t// call's `ToolContext.signal`, so a foreign agent's abort reaches the local handler, and a\n\t// contained failure becomes the rejection WebMCP's own samples catch. That rejection is a\n\t// FRESH `Error` carrying the failure's text: `ToolFailure.error` is a string, so the value\n\t// the handler threw no longer exists to forward, and the text is the whole of what the\n\t// manager kept.\n\tasync #run(\n\t\ttools: ToolManagerInterface,\n\t\tname: string,\n\t\tinput: Readonly<Record<string, unknown>>,\n\t\toptions: WebMCPHandlerOptions,\n\t): Promise<unknown> {\n\t\tconst result = await tools.execute(\n\t\t\t{ id: crypto.randomUUID(), name, arguments: input },\n\t\t\t{ signal: options.signal },\n\t\t)\n\t\tif (!result.success) throw new Error(result.error)\n\t\treturn result.value\n\t}\n\n\t// Runs one adopted tool through the registry, forwarding the local caller's abort onto\n\t// WebMCP's own execution signal and resolving the registry's answer unchanged.\n\t#execute(\n\t\tregistered: WebMCPRegisteredTool,\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> {\n\t\treturn this.#registry.executeTool(registered, args, { signal: context.signal })\n\t}\n}\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 = isString(protocols)\n\t\t\t? protocols\n\t\t\t: protocols === undefined\n\t\t\t\t? MCP_WEBSOCKET_SUBPROTOCOL\n\t\t\t\t: protocols.length === 0\n\t\t\t\t\t? undefined\n\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\tModelContextInterface,\n\tModelContextOptions,\n\tPageServerInterface,\n\tPageServerOptions,\n\tScopeInterface,\n\tScopeServerInterface,\n\tScopeServerOptions,\n\tScopeTransportInterface,\n\tWebSocketClientTransportOptions,\n} from './types.js'\nimport {\n\tbindClient,\n\tbindServer,\n\tcreateDuplexClientTransport,\n\tcreateMCPClient,\n\tcreateMCPServer,\n\tHTTPClientTransport,\n} from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION } from './constants.js'\nimport { isWebMCPDocument } from './validators.js'\nimport { ModelContext } from './ModelContext.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\n/**\n * Creates an `MCPServer` hosted inside the calling page and hands back the client bound to it\n * — the page twin of {@link createScopeServer}, and the in-page MCP pair as one call.\n *\n * @remarks\n * The pair is a native `MessageChannel`: the server binds `port1`, the client drives `port2`,\n * and no byte leaves the page. That is the point of the factory — a consumer assembling it by\n * hand writes the channel, two transports, `bindServer`, `createDuplexClientTransport`,\n * `createMCPClient`, and `bindClient`, in an order {@link MessagePortTransport}'s own doc warns\n * about: a `MessagePort` starts dispatching at construction, so an `await` interleaved between\n * a transport and its binder drops whatever arrived in the gap. This factory never suspends\n * between the two.\n *\n * The returned client is bound but not connected. Connection is a protocol round trip, so it\n * stays the consumer's `await client.connect()` rather than a promise this call hides — and a\n * factory that returned a promise could not return the terminal beside it.\n *\n * `stop` closes the client's port first, so the client observes the close and reports\n * `connected` as `false` with its pending requests rejected, then unbinds both sides and\n * closes the server's port. It is idempotent, and it takes the twin's verb because it is the\n * twin's action: {@link createScopeServer} publishes `stop` for ending a hosted server's\n * bindings, and a consumer who learned one factory reads the other without checking.\n *\n * The published `client` outlives the pair and is inert after `stop`: every session-bound\n * request issued on it — `call`, `tools`, each `tasks/*` method, and a `listen` stream on its\n * first `next()` — rejects at once with an `MCPError` carrying `-32600`, rather than waiting\n * out its request deadline against a channel nothing is listening on.\n *\n * @param options - The tools, the optional server identity, and the optional client settings;\n * see {@link PageServerOptions}\n * @returns A {@link PageServerInterface} holding the bound client and the pair's `stop`\n *\n * @example\n * ```ts\n * import { createPageServer } from '@orkestrel/mcp/browser'\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', execute: () => 5 }))\n *\n * const page = createPageServer({ tools })\n * await page.client.connect()\n * const value = await page.client.call('add', {}) // { resultType: 'complete', value: 5 }\n * page.stop()\n * ```\n */\nexport function createPageServer(options: PageServerOptions): PageServerInterface {\n\tconst { port1, port2 } = new MessageChannel()\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\t// Construct and bind each half without suspending: `MessagePortTransport` starts its port\n\t// at construction, so an `await` here would drop every frame that arrived in the gap.\n\tconst hosted = new MessagePortTransport({ port: port1 })\n\tconst unbindServer = bindServer(server, hosted)\n\tconst driven = new MessagePortTransport({ port: port2 })\n\tconst client = createMCPClient({\n\t\t...options.client,\n\t\ttransport: createDuplexClientTransport(driven),\n\t})\n\tconst unbindClient = bindClient(client, driven)\n\tlet stopped = false\n\treturn {\n\t\tclient,\n\t\tstop(): void {\n\t\t\tif (stopped) return\n\t\t\tstopped = true\n\t\t\t// Close before unbinding, in this order: the binder's `closed` handler is what tells\n\t\t\t// the client its transport is gone, and unbinding first would replace that handler\n\t\t\t// with a no-op and leave the client reporting a connection nothing carries.\n\t\t\tdriven.close()\n\t\t\tunbindClient()\n\t\t\tunbindServer()\n\t\t\thosted.close()\n\t\t},\n\t}\n}\n\n/**\n * Creates the bridge between a tool registry and a document's WebMCP registry, or reports that\n * the document exposes none.\n *\n * @remarks\n * Feature detection is the return value: `undefined` means this document has no\n * `document.modelContext`, which is the reading every browser gives today — the specification\n * is incubating in a Community Group, and the chromestatus record, read 2026-09-15 and last\n * updated 2026-08-12, reports `Proposed` with `\"flag\": false` and `\"origintrial\": false`. There\n * is no `supported` flag to read and no polyfill behind the factory, because a local\n * implementation of an absent platform feature is one a caller mistakes for the platform.\n *\n * The bridge borrows the registry. It aborts only the registrations it made, so a name it\n * never registered is left exactly as it found it. WebMCP keys a registration by tool name per\n * document, so releasing a name releases whatever now stands under it — a same-name\n * registration the page or another bridge made later goes with it.\n *\n * @param options - The document to bridge and the emitter's initial wiring; see\n * {@link ModelContextOptions}\n * @returns A {@link ModelContextInterface}, or `undefined` when the document exposes no\n * WebMCP registry\n *\n * @example\n * ```ts\n * import { createModelContext } from '@orkestrel/mcp/browser'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const bridge = createModelContext()\n * if (bridge !== undefined) {\n * \tawait bridge.publish(createToolManager())\n * \tconst foreign = await bridge.adopt()\n * \tbridge.destroy()\n * }\n * ```\n */\nexport function createModelContext(\n\toptions?: ModelContextOptions,\n): ModelContextInterface | undefined {\n\t// Typed `unknown` deliberately: the DOM library declares `globalThis.document` as a\n\t// `Document`, and it is `undefined` in a Web Worker and a Service Worker, so the guard\n\t// rather than the declaration decides.\n\tconst host: unknown = options?.document ?? globalThis.document\n\tif (!isWebMCPDocument(host)) return undefined\n\treturn new ModelContext(host, options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/validators.ts","../../../src/browser/helpers.ts","../../../src/browser/ModelContext.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\n// WebMCP registry subscription — the event name `ModelContext` binds on the document's\n// registry. The members `isWebMCPRegistry` requires are the guard shape in `validators.ts`,\n// where `objectOf` reads them, rather than a second list here that could disagree with it.\n\n/** Names the WebMCP registry event the bridge republishes as its own `change`. */\nexport const WEBMCP_CHANGE_EVENT = 'toolchange'\n\n/** Names the WebMCP IDL `toolactivated` event the bridge republishes as `activate`. */\nexport const WEBMCP_ACTIVATED_EVENT = 'toolactivated'\n\n/** Names the WebMCP IDL `toolcancel` event the bridge republishes as `abort`. */\nexport const WEBMCP_ABORT_EVENT = 'toolcancel'\n","import type { WebMCPDocument, WebMCPRegistryInterface, WebMCPToolEvent } from './types.js'\nimport { isFunction, isString, objectOf } from '@orkestrel/contract'\n\n// The browser face's guards. Both narrow a FOREIGN surface no TypeScript library declares, so\n// each enforces the published WebMCP contract and no more: the operations the bridge calls and\n// the subscription it registers, each read as the IDL declares it and nothing read beyond\n// that. Both are built from `@orkestrel/contract`'s `objectOf`, which is the combinator this\n// case needs and the reason neither guard hand-rolls a read loop: it admits unknown members,\n// reads each declared member through `Reflect.get` so a prototype-carried operation satisfies\n// the shape, refuses arrays and primitives, and answers `false` on a hostile read instead of\n// throwing. Neither uses an exact-record guard. A `Document` and a `ModelContext` are platform\n// class instances whose members arrive through a prototype chain, and an exact-record guard\n// refuses exactly those — it would fail closed on every valid implementation.\n\n/**\n * Determines whether an unknown value is a WebMCP tool registry.\n *\n * @remarks\n * Reads the members the bridge dereferences — the IDL's `registerTool`, `getTools`, and\n * `executeTool` operations, plus the `EventTarget` pair the `toolchange` subscription needs —\n * and nothing else. A registry carrying extra members is still a registry, and a user agent's\n * own implementation reaches every one of these through its prototype.\n *\n * @param value - The unknown value to inspect\n * @returns True if the value exposes every WebMCP registry operation; false otherwise\n *\n * @example\n * ```ts\n * isWebMCPRegistry({}) // false\n * ```\n */\nexport function isWebMCPRegistry(value: unknown): value is WebMCPRegistryInterface {\n\treturn objectOf({\n\t\tregisterTool: isFunction,\n\t\tgetTools: isFunction,\n\t\texecuteTool: isFunction,\n\t\taddEventListener: isFunction,\n\t\tremoveEventListener: isFunction,\n\t})(value)\n}\n\n/**\n * Determines whether an unknown value is a document exposing the WebMCP tool registry.\n *\n * @remarks\n * This is the feature detection {@link import('./factories.js').createModelContext} performs,\n * published so a consumer can run it before deciding to build a bridge at all. It reads\n * `modelContext` and checks it with {@link isWebMCPRegistry}; it asserts nothing about the rest\n * of a `Document`, because that member is the whole of what the bridge needs.\n *\n * @param value - The unknown value to inspect\n * @returns True if the value carries a WebMCP registry; false otherwise\n *\n * @example\n * ```ts\n * isWebMCPDocument(globalThis.document) // false in a browser that ships no WebMCP\n * ```\n */\nexport function isWebMCPDocument(value: unknown): value is WebMCPDocument {\n\treturn objectOf({ modelContext: isWebMCPRegistry })(value)\n}\n\n/**\n * Determines whether an unknown value is a WebMCP execution event.\n *\n * @remarks\n * Reads the IDL's `toolName` attribute and nothing else, so it admits a `ToolActivatedEvent`\n * and a `ToolCancelEvent` and refuses a plain `Event`.\n *\n * @param value - The unknown value to inspect\n * @returns True if the value carries a string `toolName`; false otherwise\n *\n * @example\n * ```ts\n * isWebMCPToolEvent(new Event('toolactivated')) // false\n * ```\n */\nexport function isWebMCPToolEvent(value: unknown): value is WebMCPToolEvent {\n\treturn objectOf({ toolName: isString })(value)\n}\n","import type { ToolAnnotations, ToolDefinition, ToolManagerInterface } from '@orkestrel/tool'\nimport type {\n\tWebMCPAnnotations,\n\tWebMCPDescriptor,\n\tWebMCPProjection,\n\tWebMCPRegisteredTool,\n} from './types.js'\nimport { JSONRPC_INVALID_PARAMS, MCPError } from '@src/core'\nimport { attempt, canonicalStringify } from '@orkestrel/contract'\nimport { toolToDefinition } from '@orkestrel/tool'\n\n// The browser face's pure projection leaves — the WebMCP direction of the same translation\n// `@orkestrel/mcp`'s `toolAnnotationsToMCP` / `mcpAnnotationsToTool` perform for the MCP wire.\n// They sit here rather than beside those because the surface they project onto is this face's:\n// WebMCP carries a third hint the MCP wire has no counterpart for (`untrustedContentHint`) and\n// spells the consequence differently (`consequentialHint`, not `destructiveHint`).\n\n/**\n * Projects domain tool annotations onto WebMCP registry hints without inventing defaults.\n *\n * @param annotations - The authored domain annotations\n * @returns The mapped hints; an omitted annotation stays omitted\n *\n * @example\n * ```ts\n * toolAnnotationsToWebMCP({ pure: true, untrusted: true }) // { readOnlyHint: true, untrustedContentHint: true }\n * ```\n */\nexport function toolAnnotationsToWebMCP(annotations: ToolAnnotations): WebMCPAnnotations {\n\treturn {\n\t\t...(annotations.pure === undefined ? {} : { readOnlyHint: annotations.pure }),\n\t\t...(annotations.untrusted === undefined ? {} : { untrustedContentHint: annotations.untrusted }),\n\t\t...(annotations.consequential === undefined\n\t\t\t? {}\n\t\t\t: { consequentialHint: annotations.consequential }),\n\t}\n}\n\n/**\n * Projects WebMCP registry hints onto domain tool annotations without inventing defaults.\n *\n * @param annotations - The registry's hints, as the WebMCP dictionary declares them\n * @returns The mapped annotations; an omitted hint stays omitted\n *\n * @example\n * ```ts\n * webMCPAnnotationsToTool({ readOnlyHint: false, consequentialHint: true }) // { pure: false, consequential: true }\n * ```\n */\nexport function webMCPAnnotationsToTool(annotations: WebMCPAnnotations): ToolAnnotations {\n\treturn {\n\t\t...(annotations.readOnlyHint === undefined ? {} : { pure: annotations.readOnlyHint }),\n\t\t...(annotations.untrustedContentHint === undefined\n\t\t\t? {}\n\t\t\t: { untrusted: annotations.untrustedContentHint }),\n\t\t...(annotations.consequentialHint === undefined\n\t\t\t? {}\n\t\t\t: { consequential: annotations.consequentialHint }),\n\t}\n}\n\n/**\n * Projects one advertised tool definition onto the WebMCP descriptor a registration carries.\n *\n * @remarks\n * Takes the definition a `ToolManagerInterface` advertises rather than the tool itself, so the\n * description here is the one the MCP wire advertises too — the registry substitutes an\n * authored `summary` for the full `description`, and reading the same projection keeps one\n * advertised description across both surfaces.\n *\n * WebMCP requires `description`, so a definition carrying none cannot be registered at all.\n * Returning `undefined` is what lets the caller refuse the whole batch before registering any\n * of it; registering an empty string instead would be an invented value a foreign agent reads\n * as a real one.\n *\n * @param definition - The advertised definition to project\n * @returns The WebMCP descriptor, or `undefined` when the definition advertises no description\n *\n * @example\n * ```ts\n * toolToWebMCP({ name: 'add', description: 'Adds two numbers' })?.description // 'Adds two numbers'\n * ```\n */\nexport function toolToWebMCP(definition: ToolDefinition): WebMCPDescriptor | undefined {\n\tif (definition.description === undefined) return undefined\n\tconst annotations =\n\t\tdefinition.annotations === undefined ? {} : toolAnnotationsToWebMCP(definition.annotations)\n\treturn {\n\t\tname: definition.name,\n\t\tdescription: definition.description,\n\t\t...(definition.title === undefined ? {} : { title: definition.title }),\n\t\t...(definition.parameters === undefined ? {} : { inputSchema: definition.parameters }),\n\t\t...(Object.keys(annotations).length === 0 ? {} : { annotations }),\n\t}\n}\n\n/**\n * Projects one registered WebMCP tool onto the tool definition an adopted tool advertises.\n *\n * @remarks\n * The inverse of {@link toolToWebMCP}, and deliberately lossy in the other direction: the\n * registry's `window` and `origin` describe where the tool lives rather than what it does, and\n * the bridge hands the whole registered record back to `executeTool` instead of rebuilding it.\n *\n * @param registered - The registered tool the registry reported\n * @returns The definition an adopted tool advertises\n *\n * @example\n * ```ts\n * webMCPToTool({ name: 'add', description: 'Adds', window, origin: 'https://a.example' }).name // 'add'\n * ```\n */\nexport function webMCPToTool(registered: WebMCPRegisteredTool): ToolDefinition {\n\tconst annotations =\n\t\tregistered.annotations === undefined ? {} : webMCPAnnotationsToTool(registered.annotations)\n\treturn {\n\t\tname: registered.name,\n\t\tdescription: registered.description,\n\t\t...(registered.title === undefined ? {} : { title: registered.title }),\n\t\t...(registered.inputSchema === undefined ? {} : { parameters: registered.inputSchema }),\n\t\t...(Object.keys(annotations).length === 0 ? {} : { annotations }),\n\t}\n}\n\n/**\n * Determines whether two WebMCP descriptors advertise the same tool to the registry.\n *\n * @remarks\n * The reading `ModelContextInterface.publish` reconciles a name against once the manager's\n * tool has changed under it: equal descriptors leave the live registration standing, because\n * execution routes through the manager by name, and anything else releases it and registers\n * the new one.\n *\n * Equality is structural and key-order-independent, through `@orkestrel/contract`'s\n * `canonicalStringify`: `inputSchema` is the author's own JSON Schema record, and two\n * authorings of the same schema that differ only in key order describe the same tool. A\n * descriptor JSON cannot encode — a cyclic or unreadable `inputSchema` — is reported as\n * unequal, which re-registers rather than serving a descriptor nothing could compare.\n *\n * @param held - The descriptor the live registration carries\n * @param projected - The descriptor this publication projected\n * @returns True when both describe the same tool; false otherwise\n *\n * @example\n * ```ts\n * matchesDescriptor({ name: 'add', description: 'Adds' }, { description: 'Adds', name: 'add' }) // true\n * ```\n */\nexport function matchesDescriptor(held: WebMCPDescriptor, projected: WebMCPDescriptor): boolean {\n\tconst left = attempt(() => canonicalStringify(held))\n\tconst right = attempt(() => canonicalStringify(projected))\n\tif (!left.success || !right.success) return false\n\treturn left.value !== undefined && left.value === right.value\n}\n\n/**\n * Projects the descriptor a registry advertises for one tool name, or reports that it has none.\n *\n * @remarks\n * Reads the manager's own `definitions()` rather than a tool instance, so the description here\n * is the one the registry advertises — an authored `summary` in place of the full\n * `description` — and one tool reaches the WebMCP registry and the MCP wire describing itself\n * the same way.\n *\n * `undefined` covers both answers a caller must not conflate with a descriptor: the manager\n * advertises no tool under that name, and the tool it advertises carries no description, which\n * is the member WebMCP requires.\n *\n * @param manager - The tool registry to read\n * @param name - The tool name to describe\n * @returns The WebMCP descriptor, or `undefined` when the registry advertises none\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', description: 'Adds two numbers', execute: () => 5 }))\n * describeWebMCPTool(tools, 'add')?.description // 'Adds two numbers'\n * ```\n */\nexport function describeWebMCPTool(\n\tmanager: ToolManagerInterface,\n\tname: string,\n): WebMCPDescriptor | undefined {\n\tfor (const definition of manager.definitions()) {\n\t\tif (definition.name === name) return toolToWebMCP(definition)\n\t}\n\treturn undefined\n}\n\n/**\n * Builds the WebMCP projection of every tool a registry advertises, or refuses the batch.\n *\n * @remarks\n * The WebMCP twin of `@orkestrel/mcp`'s `buildToolDescriptors`. Each tool is projected through\n * `@orkestrel/tool`'s own `toolToDefinition` — the projection `definitions()` applies — so a\n * tool advertises one description across the MCP wire and the WebMCP registry alike. The tool\n * travels beside its descriptor because a registration records which tool it was made for, and\n * a descriptor cannot report that.\n *\n * It refuses rather than skips. WebMCP requires `description`, and each alternative to\n * refusing is worse: an empty string is an invented value a foreign agent reads as a real one,\n * and silently dropping the tool publishes a registry missing a tool its author asked for.\n * Refusing before any registration happens is also what keeps `publish` atomic — nothing is\n * registered when one tool cannot be.\n *\n * @param manager - The tool registry to project\n * @returns One projection per advertised tool, in registry order\n * @throws Thrown as an `MCPError` carrying `-32602` when a tool advertises no description,\n * naming the tool\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', description: 'Adds two numbers', execute: () => 5 }))\n * buildWebMCPProjections(tools).map((projection) => projection.descriptor.name) // ['add']\n * ```\n */\nexport function buildWebMCPProjections(manager: ToolManagerInterface): readonly WebMCPProjection[] {\n\tconst projections: WebMCPProjection[] = []\n\tfor (const tool of manager.tools()) {\n\t\tconst descriptor = toolToWebMCP(toolToDefinition(tool))\n\t\tif (descriptor === undefined) {\n\t\t\tthrow new MCPError(\n\t\t\t\t`WebMCP requires a description for tool '${tool.name}'`,\n\t\t\t\tJSONRPC_INVALID_PARAMS,\n\t\t\t)\n\t\t}\n\t\tprojections.push({ tool, descriptor })\n\t}\n\treturn projections\n}\n\n/**\n * Collects the WebMCP projection of every tool a registry advertises that WebMCP can carry.\n *\n * @remarks\n * The skipping sibling of {@link buildWebMCPProjections}, and the reading a followed change\n * reconciles against: a followed change reaches no caller, so a tool advertising neither a\n * `description` nor a `summary` is left out of the collection rather than refusing a batch\n * nobody asked for.\n *\n * @param manager - The tool registry to project\n * @returns One projection per advertised tool WebMCP can carry, in registry order\n *\n * @example\n * ```ts\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'bare', execute: () => 1 }))\n * collectWebMCPProjections(tools) // []\n * ```\n */\nexport function collectWebMCPProjections(\n\tmanager: ToolManagerInterface,\n): readonly WebMCPProjection[] {\n\tconst projections: WebMCPProjection[] = []\n\tfor (const tool of manager.tools()) {\n\t\tconst descriptor = toolToWebMCP(toolToDefinition(tool))\n\t\tif (descriptor !== undefined) projections.push({ tool, descriptor })\n\t}\n\treturn projections\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { ToolContext, ToolInterface, ToolManagerInterface } from '@orkestrel/tool'\nimport type {\n\tModelContextAdoptOptions,\n\tModelContextEventMap,\n\tModelContextInterface,\n\tModelContextOptions,\n\tModelContextPublishOptions,\n\tWebMCPDescriptor,\n\tWebMCPDocument,\n\tWebMCPHandlerOptions,\n\tWebMCPProjection,\n\tWebMCPRegisteredTool,\n\tWebMCPRegistryInterface,\n} from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { createTool } from '@orkestrel/tool'\nimport { attempt } from '@orkestrel/contract'\nimport { WEBMCP_ABORT_EVENT, WEBMCP_ACTIVATED_EVENT, WEBMCP_CHANGE_EVENT } from './constants.js'\nimport {\n\tbuildWebMCPProjections,\n\tcollectWebMCPProjections,\n\tmatchesDescriptor,\n\twebMCPToTool,\n} from './helpers.js'\nimport { isWebMCPToolEvent } from './validators.js'\n\n/**\n * Bridges a `ToolManagerInterface` and a document's WebMCP tool registry — the\n * {@link ModelContextInterface} {@link import('./factories.js').createModelContext} returns.\n *\n * @remarks\n * - **It borrows the registry, it does not own it.** The handle registers tools, retains one\n * `AbortController` per registration, and aborts exactly those on `destroy`. WebMCP's own\n * unregistration path is that abort. Registration identity is the tool name, per document,\n * so releasing a name releases whatever now stands under it — including a same-name\n * registration another handle made later.\n * - **`publish` snapshots at the call, then follows the manager.** The manager is projected\n * when `publish` is called, before the work queues behind an earlier publication, so a\n * registry mutated while this call waits its turn does not decide what this call registers.\n * The same call subscribes to the manager's own `emitter`, so a later `add`, `remove`, or\n * `clear` reaches the document registry without a second `publish`.\n * - **One manager is followed at a time.** A `publish` naming another manager releases the\n * subscription and takes up the new one, and `destroy` releases it outright. An event from a\n * manager this handle no longer follows is ignored, which is what a listener republishing\n * another manager from inside a dispatch produces. A followed change has no caller to refuse\n * to, so a tool advertising neither a `description` nor a `summary` is left unregistered\n * rather than refusing anything; `publish` still refuses such a batch whole. Under a name\n * this handle never registered that skip emits no `change`, because nothing reached the\n * document registry. A synchronisation registers only what it can carry, so such a tool\n * standing under a name this handle already registered releases that registration rather\n * than leaving it advertising a descriptor the manager no longer stands behind, and that\n * release emits the registry's `change` like any other.\n * - **A followed change is a trigger, not a fact.** Each `add`, `remove`, or `clear` queues one\n * synchronisation of this handle's registrations for that manager against what the manager\n * holds when that queued work runs. The event cannot decide the outcome: `remove` and\n * `clear` name tools the manager no longer holds, an earlier listener in the same dispatch\n * may already have put another tool under one of those names, and the manager's `destroy`\n * empties its map after the `clear` it publishes. Reading the manager converges on its state\n * however the change was reached, so a synchronisation queued and not yet started already\n * covers every change that arrives before it runs and a second one is not queued. A\n * publication queued behind that synchronisation ends its cover, because the publication\n * prunes what the synchronisation registered: a change arriving after that call queues a\n * synchronisation of its own, which runs after the publication.\n * - **A later `publish` reconciles, and so does every synchronisation.** Each name is compared\n * with what this handle already registered for it: the same manager holding the same tool\n * leaves the registration alone, another tool of that same manager advertising an equal\n * descriptor leaves it registered and records the tool it now stands for, and anything else\n * releases it and registers the new descriptor bound to the new manager. A name the snapshot\n * dropped, or a name the manager no longer holds, is released — after the batch has\n * reconciled, never before, so no name is withdrawn while the tools replacing it are still\n * being registered. A failed batch releases them too, and still withdraws nothing it\n * carries. Nothing has to be removed from the manager to make the registry agree with it.\n * - **Work serializes.** Registration is asynchronous and `destroy` is not, so overlapping\n * calls would interleave registrations with the aborts meant to end them. Each publication\n * and each synchronisation queues behind the previous one, and every step re-reads the\n * destroyed flag, so a `destroy` issued mid-publish stops the registrations that have not\n * happened yet instead of racing them.\n * - **Nothing is polyfilled.** A document exposing no registry never reaches this class:\n * {@link import('./factories.js').createModelContext} returns `undefined` instead, so feature\n * absence stays absence rather than becoming a local implementation a caller mistakes for\n * the platform.\n * - **The result shape is the registry's.** An adopted tool resolves whatever `executeTool`\n * resolved, unchanged. WebMCP's IDL types that `Promise<DOMString>` while the\n * specification's README sample returns `{ content: [...] }`; the primary source disagrees\n * with itself, and normalizing either way would encode a guess as a contract.\n *\n * @example\n * ```ts\n * import { isWebMCPDocument, ModelContext } from '@orkestrel/mcp/browser'\n *\n * if (isWebMCPDocument(document)) {\n * \tconst bridge = new ModelContext(document)\n * \tawait bridge.publish(tools)\n * }\n * ```\n */\nexport class ModelContext implements ModelContextInterface {\n\treadonly #registry: WebMCPRegistryInterface\n\treadonly #emitter: Emitter<ModelContextEventMap>\n\t// What this handle registered, per name: the signal that releases it, the descriptor the\n\t// registry is advertising for it, the manager's own tool the registration was made for\n\t// (`tool`), and the manager its execution is bound to (`tools`). Genuinely private glue —\n\t// the controller alone cannot answer whether the manager still holds the tool this\n\t// registration serves, and answering that is the whole of the reconciliation.\n\treadonly #registrations = new Map<\n\t\tstring,\n\t\t{\n\t\t\treadonly controller: AbortController\n\t\t\treadonly descriptor: WebMCPDescriptor\n\t\t\treadonly tool: ToolInterface\n\t\t\treadonly tools: ToolManagerInterface\n\t\t}\n\t>()\n\treadonly #listener: () => void\n\treadonly #activated: (event: Event) => void\n\treadonly #aborted: (event: Event) => void\n\t// The manager this handle is following, with the exact handler reference `off` needs to\n\t// release it. Genuinely private glue: the subscription is an implementation of `publish`'s\n\t// contract rather than a member a consumer reads, and one handler serves all three events\n\t// because each of them asks for the same thing — read the manager and agree with it.\n\t#followed: { readonly tools: ToolManagerInterface; readonly changed: () => void } | undefined =\n\t\tundefined\n\t// The manager whose synchronisation is queued, has not started, and has nothing queued\n\t// behind it. A second event for that manager before the work runs would read the same\n\t// state twice, so it is coalesced into the first; a `publish` queued behind it clears the\n\t// mark, because the publication that follows the synchronisation prunes what it registers.\n\t#pendingManager: ToolManagerInterface | undefined = undefined\n\t#queue: Promise<void> = Promise.resolve()\n\t#destroyed = false\n\n\t/**\n\t * Binds a narrowed document's registry and arms its change and execution subscriptions.\n\t *\n\t * @param document - The document whose `modelContext` this handle bridges\n\t * @param options - The emitter's initial hooks and listener-error handler; see\n\t * {@link ModelContextOptions}\n\t */\n\tconstructor(document: WebMCPDocument, options?: ModelContextOptions) {\n\t\tthis.#registry = document.modelContext\n\t\tthis.#emitter = new Emitter<ModelContextEventMap>({\n\t\t\t...(options?.on === undefined ? {} : { on: options.on }),\n\t\t\t...(options?.error === undefined ? {} : { error: options.error }),\n\t\t})\n\t\t// The subscription arms at construction rather than on a first `publish`, because a\n\t\t// registry change between the two would reach nothing. The bound reference is retained\n\t\t// so `destroy` removes the same listener it added.\n\t\tthis.#listener = this.#republish.bind(this)\n\t\tthis.#registry.addEventListener(WEBMCP_CHANGE_EVENT, this.#listener)\n\t\tthis.#activated = this.#republishTool.bind(this, 'activate')\n\t\tthis.#aborted = this.#republishTool.bind(this, 'abort')\n\t\tthis.#registry.addEventListener(WEBMCP_ACTIVATED_EVENT, this.#activated)\n\t\tthis.#registry.addEventListener(WEBMCP_ABORT_EVENT, this.#aborted)\n\t}\n\n\tget emitter(): EmitterInterface<ModelContextEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tpublish(tools: ToolManagerInterface, options?: ModelContextPublishOptions): Promise<void> {\n\t\t// Projected here, at the call, rather than where the queue reaches this publication: the\n\t\t// contract is the tools the manager holds now, and a manager mutated while this call\n\t\t// waits behind an earlier one must not decide what this call registers. The projection\n\t\t// is also where a tool WebMCP cannot carry refuses the whole batch, so that refusal\n\t\t// reaches the caller as a rejection rather than as a synchronous throw.\n\t\tconst snapshot = attempt(() => buildWebMCPProjections(tools))\n\t\tif (!snapshot.success) return Promise.reject(snapshot.error)\n\t\t// Subscribed at the call, beside the snapshot, so there is no window between the two in\n\t\t// which a change reaches nothing. A refused projection registers nothing and therefore\n\t\t// follows nothing, and a destroyed handle follows nothing either.\n\t\tif (!this.#destroyed) this.#follow(tools, options)\n\t\t// A synchronisation already queued runs before this publication, and this publication\n\t\t// prunes what that synchronisation registered — so the mark it left stops covering the\n\t\t// changes that arrive from here on. Clearing it is what sends the next change to a\n\t\t// synchronisation of its own, queued after this publication rather than before it.\n\t\tthis.#pendingManager = undefined\n\t\tconst settled = this.#queue.then(() => this.#publish(tools, snapshot.value, options))\n\t\t// The queue tracks completion, never outcome: a rejected publish must not poison the\n\t\t// next one, and the caller already receives the rejection through `settled`.\n\t\tthis.#queue = settled.catch(() => undefined)\n\t\treturn settled\n\t}\n\n\tasync adopt(options?: ModelContextAdoptOptions): Promise<readonly ToolInterface[]> {\n\t\tconst registered = await this.#registry.getTools(\n\t\t\toptions?.origins === undefined ? {} : { fromOrigins: options.origins },\n\t\t)\n\t\treturn registered\n\t\t\t.filter((tool) => options?.debugging === true || tool.annotations?.debugging !== true)\n\t\t\t.map((tool) => createTool({ ...webMCPToTool(tool), execute: this.#execute.bind(this, tool) }))\n\t}\n\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#destroyed = true\n\t\tthis.#unfollow()\n\t\tthis.#pendingManager = undefined\n\t\tthis.#registry.removeEventListener(WEBMCP_CHANGE_EVENT, this.#listener)\n\t\tthis.#registry.removeEventListener(WEBMCP_ACTIVATED_EVENT, this.#activated)\n\t\tthis.#registry.removeEventListener(WEBMCP_ABORT_EVENT, this.#aborted)\n\t\tfor (const held of this.#registrations.values()) held.controller.abort()\n\t\tthis.#registrations.clear()\n\t\tthis.#emitter.destroy()\n\t}\n\n\t// Republishes the registry's own `toolchange` as this handle's `change`. It exists as a\n\t// method so `addEventListener` and `removeEventListener` receive one stable reference.\n\t#republish(): void {\n\t\tthis.#emitter.emit('change')\n\t}\n\n\t#republishTool(name: 'activate' | 'abort', event: Event): void {\n\t\tif (isWebMCPToolEvent(event)) this.#emitter.emit(name, event.toolName)\n\t}\n\n\t// Subscribes to a manager's own registry events, releasing whatever was followed before.\n\t// One manager at a time, because a handle's registrations are keyed by name and two\n\t// managers publishing one name would each believe they owned it.\n\t#follow(tools: ToolManagerInterface, options?: ModelContextPublishOptions): void {\n\t\tthis.#unfollow()\n\t\tconst changed = this.#changed.bind(this, tools, options)\n\t\ttools.emitter.on('add', changed)\n\t\ttools.emitter.on('remove', changed)\n\t\ttools.emitter.on('clear', changed)\n\t\tthis.#followed = { tools, changed }\n\t}\n\n\t// Releases the subscription by handing `off` the same reference `on` received. The\n\t// installed emitter's `on` returns nothing, so the handler identity the record retains is\n\t// the cleanup.\n\t#unfollow(): void {\n\t\tconst followed = this.#followed\n\t\tif (followed === undefined) return\n\t\tthis.#followed = undefined\n\t\tfollowed.tools.emitter.off('add', followed.changed)\n\t\tfollowed.tools.emitter.off('remove', followed.changed)\n\t\tfollowed.tools.emitter.off('clear', followed.changed)\n\t}\n\n\t// Reports whether an event is the followed manager's. `#unfollow` hands the emitter its\n\t// handlers back, but it cannot withdraw them from the listener array a dispatch already\n\t// walking that event is holding — so a listener that republishes another manager from\n\t// inside a dispatch leaves this handle's own handler still to run, for a subscription that\n\t// no longer exists. Manager identity is the whole reading, and it answers destruction too:\n\t// `destroy` releases the subscription before it aborts anything, so a destroyed handle\n\t// follows nobody and every followed handler that outlives it stops here.\n\t#follows(tools: ToolManagerInterface): boolean {\n\t\treturn this.#followed?.tools === tools\n\t}\n\n\t// Queues one synchronisation for a change the followed manager reported. Every event takes\n\t// this door, because each of them says only THAT the manager changed: `remove` and `clear`\n\t// carry tools the manager no longer holds, an earlier listener in the same dispatch may\n\t// already have put another tool under one of those names, and the manager's own `destroy`\n\t// empties its map after the `clear` it publishes. What the event carries therefore cannot\n\t// decide what to release; the manager's state when the queued work runs decides it. It\n\t// queues rather than acting here, so a change arriving while an earlier publication is\n\t// still registering runs after it instead of racing it.\n\t#changed(tools: ToolManagerInterface, options: ModelContextPublishOptions | undefined): void {\n\t\tif (!this.#follows(tools)) return\n\t\t// A synchronisation queued and not yet started reads the manager when it runs, so it\n\t\t// already covers every change that arrives before then — while nothing is queued behind\n\t\t// it. `publish` clears the mark for exactly that reason, so a change arriving after a\n\t\t// publication takes a synchronisation of its own rather than one the publication will\n\t\t// prune. The mark holds the manager rather than a boolean, so a change from a manager a\n\t\t// `publish` took up in the meantime still queues its own.\n\t\tif (this.#pendingManager === tools) return\n\t\tthis.#pendingManager = tools\n\t\tthis.#queue = this.#queue.then(this.#sync.bind(this, tools, options)).catch(() => undefined)\n\t}\n\n\t// Brings this handle's registrations for one manager to what that manager holds NOW. The\n\t// whole of what a followed change does: every tool the manager advertises reconciles, and a\n\t// held name it dropped is released. Only registrations bound to this manager are released,\n\t// so a name another manager's publication put there is left standing.\n\tasync #sync(tools: ToolManagerInterface, options?: ModelContextPublishOptions): Promise<void> {\n\t\t// The mark clears as this work STARTS, not when it was queued: the manager is read\n\t\t// below, so a change arriving from here on reaches a reading already taken and needs a\n\t\t// synchronisation of its own. A mark a later change left is cleared with it, which\n\t\t// costs one synchronisation that had already been covered and never one too few.\n\t\tif (this.#pendingManager === tools) this.#pendingManager = undefined\n\t\tif (this.#destroyed) return\n\t\t// A tool WebMCP cannot carry is skipped rather than refusing anything, because a\n\t\t// followed change has no caller a refusal could reach. The prune below then releases\n\t\t// the name that skip left out, so the registry advertises only what this handle can\n\t\t// carry rather than a descriptor whose tool the manager replaced with one WebMCP\n\t\t// cannot carry.\n\t\tconst projections = collectWebMCPProjections(tools)\n\t\ttry {\n\t\t\tfor (const projection of projections) await this.#reconcile(tools, projection, options)\n\t\t} finally {\n\t\t\tthis.#prune(projections, tools)\n\t\t}\n\t}\n\n\tasync #publish(\n\t\ttools: ToolManagerInterface,\n\t\tprojections: readonly WebMCPProjection[],\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\tif (this.#destroyed) return\n\t\ttry {\n\t\t\tfor (const projection of projections) await this.#reconcile(tools, projection, options)\n\t\t} finally {\n\t\t\tthis.#prune(projections)\n\t\t}\n\t}\n\n\t// Brings one name to the descriptor a publication or a followed change is asking for. The\n\t// single door both paths take, so a tool added through the manager's own event lands under\n\t// exactly the rule a `publish` would have applied to it.\n\tasync #reconcile(\n\t\ttools: ToolManagerInterface,\n\t\tprojection: WebMCPProjection,\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\t// The flag is re-read at every step that can register, and this is that step: a\n\t\t// publication's loop resumes here after each suspended registration, and a followed\n\t\t// change reaches it from a queue a `destroy` may have overtaken. Reading it once, here,\n\t\t// is what keeps one rule rather than a copy per caller to drift against.\n\t\tif (this.#destroyed) return\n\t\tconst { descriptor, tool } = projection\n\t\tconst held = this.#registrations.get(descriptor.name)\n\t\tif (held !== undefined && held.tools === tools) {\n\t\t\t// The manager holds the very tool this registration was made for, so nothing it\n\t\t\t// advertises can have changed. Reading tool identity rather than comparing\n\t\t\t// descriptors is also what leaves a descriptor JSON cannot encode — a cyclic\n\t\t\t// `inputSchema` — alone, instead of churning it on every change to another name.\n\t\t\tif (held.tool === tool) return\n\t\t\t// Another tool of the same manager, under the same name, advertising the same\n\t\t\t// descriptor. Execution routes through the manager by name, so a foreign agent\n\t\t\t// already reaches the replacement's handler, and re-registering would abort a\n\t\t\t// live registration to put an identical one back. The tool it now stands for is\n\t\t\t// recorded instead.\n\t\t\tif (matchesDescriptor(held.descriptor, descriptor)) {\n\t\t\t\tthis.#registrations.set(descriptor.name, { ...held, tool })\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif (held !== undefined) {\n\t\t\t// Anything else is a different tool under a name WebMCP keys per document, so the\n\t\t\t// registration this handle holds is released before the new one replaces it.\n\t\t\t// Leaving it would advertise a descriptor whose handler no longer matches it, and\n\t\t\t// a foreign agent would send arguments the registry told it were valid.\n\t\t\tthis.#registrations.delete(descriptor.name)\n\t\t\theld.controller.abort()\n\t\t\t// That abort is the registry's unregistration path, and the registry dispatches\n\t\t\t// `toolchange` inside it — synchronously, into listeners that can destroy this\n\t\t\t// handle. So the flag is re-read here, between releasing the old registration and\n\t\t\t// creating its replacement, or a destroyed handle would leave one live controller\n\t\t\t// behind that nothing will ever abort.\n\t\t\tif (this.#destroyed) return\n\t\t}\n\t\tawait this.#register(tools, projection, options)\n\t}\n\n\tasync #register(\n\t\ttools: ToolManagerInterface,\n\t\tprojection: WebMCPProjection,\n\t\toptions?: ModelContextPublishOptions,\n\t): Promise<void> {\n\t\tconst { descriptor, tool } = projection\n\t\tconst controller = new AbortController()\n\t\tthis.#registrations.set(descriptor.name, { controller, descriptor, tool, tools })\n\t\ttry {\n\t\t\tawait this.#registry.registerTool(\n\t\t\t\t{ ...descriptor, execute: this.#run.bind(this, tools, descriptor.name) },\n\t\t\t\t{\n\t\t\t\t\t...(options?.origins === undefined ? {} : { exposedTo: options.origins }),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t},\n\t\t\t)\n\t\t} catch (error) {\n\t\t\t// A registration the registry refused is not one this handle owns. Dropping the\n\t\t\t// entry lets a later publication or synchronisation register the name again, and\n\t\t\t// the abort releases a tool a registry recorded before it failed.\n\t\t\tthis.#registrations.delete(descriptor.name)\n\t\t\tcontroller.abort()\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t// Prunes this handle's registrations against the projections a batch reconciled: every\n\t// name they do not carry is released, and WebMCP's unregistration path is the registration\n\t// signal, so aborting is the removal. Both callers prune LAST, in a `finally` after their\n\t// projections have reconciled. One order, so no name is withdrawn while the tools replacing\n\t// it are still being registered; and a `finally`, so a batch that fails partway still\n\t// releases the names it dropped rather than leaving them advertised until some later batch\n\t// withdraws them. A failed batch withdraws nothing it carries: `kept` is read from the\n\t// projections rather than from the registrations, so the names the batch carries are\n\t// protected whether or not their reconcile ran. A publication hands its own snapshot,\n\t// across managers, because a publication decides the whole registry: reading the snapshot\n\t// rather than the live manager is why a manager emptied mid-publication does not unregister\n\t// what that publication captured. A synchronisation hands what the manager advertises now,\n\t// and names the manager, because a followed change decides only what that manager put\n\t// there.\n\t#prune(projections: readonly WebMCPProjection[], tools?: ToolManagerInterface): void {\n\t\t// Read here rather than at each caller, because each abort below dispatches `toolchange`\n\t\t// synchronously into listeners that can destroy this handle: one door, one reading, and\n\t\t// a destroyed handle has already taken back every registration itself.\n\t\tif (this.#destroyed) return\n\t\tconst kept = new Set(projections.map((projection) => projection.descriptor.name))\n\t\tfor (const [name, held] of this.#registrations) {\n\t\t\tif (kept.has(name) || (tools !== undefined && held.tools !== tools)) continue\n\t\t\tthis.#registrations.delete(name)\n\t\t\theld.controller.abort()\n\t\t\t// The abort dispatches `toolchange` synchronously, into listeners that can destroy\n\t\t\t// this handle, and `destroy` takes back every remaining registration itself.\n\t\t\tif (this.#destroyed) return\n\t\t}\n\t}\n\n\t// Runs one published tool on behalf of the registry. The registry's signal becomes the\n\t// call's `ToolContext.signal`, so a foreign agent's abort reaches the local handler, and a\n\t// contained failure becomes the rejection WebMCP's own samples catch. That rejection is a\n\t// FRESH `Error` carrying the failure's text: `ToolFailure.error` is a string, so the value\n\t// the handler threw no longer exists to forward, and the text is the whole of what the\n\t// manager kept.\n\tasync #run(\n\t\ttools: ToolManagerInterface,\n\t\tname: string,\n\t\tinput: Readonly<Record<string, unknown>>,\n\t\toptions: WebMCPHandlerOptions,\n\t): Promise<unknown> {\n\t\tconst result = await tools.execute(\n\t\t\t{ id: crypto.randomUUID(), name, arguments: input },\n\t\t\t{ signal: options.signal },\n\t\t)\n\t\tif (!result.success) throw new Error(result.error)\n\t\treturn result.value\n\t}\n\n\t// Runs one adopted tool through the registry, forwarding the local caller's abort onto\n\t// WebMCP's own execution signal and resolving the registry's answer unchanged.\n\t#execute(\n\t\tregistered: WebMCPRegisteredTool,\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> {\n\t\treturn this.#registry.executeTool(registered, args, { signal: context.signal })\n\t}\n}\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 = isString(protocols)\n\t\t\t? protocols\n\t\t\t: protocols === undefined\n\t\t\t\t? MCP_WEBSOCKET_SUBPROTOCOL\n\t\t\t\t: protocols.length === 0\n\t\t\t\t\t? undefined\n\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\tModelContextInterface,\n\tModelContextOptions,\n\tPageServerInterface,\n\tPageServerOptions,\n\tScopeInterface,\n\tScopeServerInterface,\n\tScopeServerOptions,\n\tScopeTransportInterface,\n\tWebSocketClientTransportOptions,\n} from './types.js'\nimport {\n\tbindClient,\n\tbindServer,\n\tcreateDuplexClientTransport,\n\tcreateMCPClient,\n\tcreateMCPServer,\n\tHTTPClientTransport,\n} from '@src/core'\nimport { isString } from '@orkestrel/contract'\nimport { DEFAULT_MCP_SERVER_NAME, DEFAULT_MCP_SERVER_VERSION } from './constants.js'\nimport { isWebMCPDocument } from './validators.js'\nimport { ModelContext } from './ModelContext.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\n/**\n * Creates an `MCPServer` hosted inside the calling page and hands back the client bound to it\n * — the page twin of {@link createScopeServer}, and the in-page MCP pair as one call.\n *\n * @remarks\n * The pair is a native `MessageChannel`: the server binds `port1`, the client drives `port2`,\n * and no byte leaves the page. That is the point of the factory — a consumer assembling it by\n * hand writes the channel, two transports, `bindServer`, `createDuplexClientTransport`,\n * `createMCPClient`, and `bindClient`, in an order {@link MessagePortTransport}'s own doc warns\n * about: a `MessagePort` starts dispatching at construction, so an `await` interleaved between\n * a transport and its binder drops whatever arrived in the gap. This factory never suspends\n * between the two.\n *\n * The returned client is bound but not connected. Connection is a protocol round trip, so it\n * stays the consumer's `await client.connect()` rather than a promise this call hides — and a\n * factory that returned a promise could not return the terminal beside it.\n *\n * `stop` closes the client's port first, so the client observes the close and reports\n * `connected` as `false` with its pending requests rejected, then unbinds both sides and\n * closes the server's port. It is idempotent, and it takes the twin's verb because it is the\n * twin's action: {@link createScopeServer} publishes `stop` for ending a hosted server's\n * bindings, and a consumer who learned one factory reads the other without checking.\n *\n * The published `client` outlives the pair and is inert after `stop`: every session-bound\n * request issued on it — `call`, `tools`, each `tasks/*` method, and a `listen` stream on its\n * first `next()` — rejects at once with an `MCPError` carrying `-32600`, rather than waiting\n * out its request deadline against a channel nothing is listening on.\n *\n * @param options - The tools, the optional server identity, and the optional client settings;\n * see {@link PageServerOptions}\n * @returns A {@link PageServerInterface} holding the bound client and the pair's `stop`\n *\n * @example\n * ```ts\n * import { createPageServer } from '@orkestrel/mcp/browser'\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'add', execute: () => 5 }))\n *\n * const page = createPageServer({ tools })\n * await page.client.connect()\n * const value = await page.client.call('add', {}) // { resultType: 'complete', value: 5 }\n * page.stop()\n * ```\n */\nexport function createPageServer(options: PageServerOptions): PageServerInterface {\n\tconst { port1, port2 } = new MessageChannel()\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\t// Construct and bind each half without suspending: `MessagePortTransport` starts its port\n\t// at construction, so an `await` here would drop every frame that arrived in the gap.\n\tconst hosted = new MessagePortTransport({ port: port1 })\n\tconst unbindServer = bindServer(server, hosted)\n\tconst driven = new MessagePortTransport({ port: port2 })\n\tconst client = createMCPClient({\n\t\t...options.client,\n\t\ttransport: createDuplexClientTransport(driven),\n\t})\n\tconst unbindClient = bindClient(client, driven)\n\tlet stopped = false\n\treturn {\n\t\tclient,\n\t\tstop(): void {\n\t\t\tif (stopped) return\n\t\t\tstopped = true\n\t\t\t// Close before unbinding, in this order: the binder's `closed` handler is what tells\n\t\t\t// the client its transport is gone, and unbinding first would replace that handler\n\t\t\t// with a no-op and leave the client reporting a connection nothing carries.\n\t\t\tdriven.close()\n\t\t\tunbindClient()\n\t\t\tunbindServer()\n\t\t\thosted.close()\n\t\t},\n\t}\n}\n\n/**\n * Creates the bridge between a tool registry and a document's WebMCP registry, or reports that\n * the document exposes none.\n *\n * @remarks\n * Feature detection is the return value: `undefined` means this document has no\n * `document.modelContext`, which is the reading every browser gives today — the specification\n * is incubating in a Community Group, and the chromestatus record, read 2026-09-15 and last\n * updated 2026-08-12, reports `Proposed` with `\"flag\": false` and `\"origintrial\": false`. There\n * is no `supported` flag to read and no polyfill behind the factory, because a local\n * implementation of an absent platform feature is one a caller mistakes for the platform.\n *\n * The bridge borrows the registry. It aborts only the registrations it made, so a name it\n * never registered is left exactly as it found it. WebMCP keys a registration by tool name per\n * document, so releasing a name releases whatever now stands under it — a same-name\n * registration the page or another bridge made later goes with it.\n *\n * @param options - The document to bridge and the emitter's initial wiring; see\n * {@link ModelContextOptions}\n * @returns A {@link ModelContextInterface}, or `undefined` when the document exposes no\n * WebMCP registry\n *\n * @example\n * ```ts\n * import { createModelContext } from '@orkestrel/mcp/browser'\n * import { createToolManager } from '@orkestrel/tool'\n *\n * const bridge = createModelContext()\n * if (bridge !== undefined) {\n * \tawait bridge.publish(createToolManager())\n * \tconst foreign = await bridge.adopt()\n * \tbridge.destroy()\n * }\n * ```\n */\nexport function createModelContext(\n\toptions?: ModelContextOptions,\n): ModelContextInterface | undefined {\n\t// Typed `unknown` deliberately: the DOM library declares `globalThis.document` as a\n\t// `Document`, and it is `undefined` in a Web Worker and a Service Worker, so the guard\n\t// rather than the declaration decides.\n\tconst host: unknown = options?.document ?? globalThis.document\n\tif (!isWebMCPDocument(host)) return undefined\n\treturn new ModelContext(host, options)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"description": "A typed Model Context Protocol client/server with pluggable HTTP, WebSocket, and stdio transports. Part of the @orkestrel line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"json-rpc",
|
|
@@ -97,20 +97,20 @@
|
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@orkestrel/codec": "^0.0.5",
|
|
100
|
-
"@orkestrel/contract": "^0.0.
|
|
100
|
+
"@orkestrel/contract": "^0.0.19",
|
|
101
101
|
"@orkestrel/emitter": "^0.0.11",
|
|
102
102
|
"@orkestrel/process": "^0.0.14",
|
|
103
103
|
"@orkestrel/sse": "^0.0.9",
|
|
104
|
-
"@orkestrel/tool": "^0.0.
|
|
104
|
+
"@orkestrel/tool": "^0.0.18",
|
|
105
105
|
"@orkestrel/websocket": "^0.0.14"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@microsoft/api-extractor": "^7.59.3",
|
|
109
109
|
"@modelcontextprotocol/conformance": "0.2.0-alpha.11",
|
|
110
|
-
"@orkestrel/guide": "^0.0.
|
|
111
|
-
"@orkestrel/probe": "^0.0.
|
|
110
|
+
"@orkestrel/guide": "^0.0.22",
|
|
111
|
+
"@orkestrel/probe": "^0.0.19",
|
|
112
112
|
"@orkestrel/router": "^0.0.16",
|
|
113
|
-
"@orkestrel/scaffold": "^0.0.
|
|
113
|
+
"@orkestrel/scaffold": "^0.0.82",
|
|
114
114
|
"@orkestrel/server": "^0.0.21",
|
|
115
115
|
"@orkestrel/test": "^0.0.24",
|
|
116
116
|
"@types/node": "^26.6.3",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"oxlint": "^1.86.0",
|
|
120
120
|
"playwright": "^1.63.0",
|
|
121
121
|
"typescript": "^6.0.3",
|
|
122
|
-
"vite": "^8.3.
|
|
122
|
+
"vite": "^8.3.2",
|
|
123
123
|
"vitest": "^4.1.11"
|
|
124
124
|
},
|
|
125
125
|
"peerDependencies": {
|