@orkestrel/mcp 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_codec = require("@orkestrel/codec");
4
+ let _orkestrel_sse = require("@orkestrel/sse");
4
5
  let _orkestrel_emitter = require("@orkestrel/emitter");
5
6
  let _orkestrel_tool = require("@orkestrel/tool");
6
7
  //#region src/core/constants.ts
7
8
  /**
8
- * The revision offered and defaulted to in the legacy `initialize` handshake.
9
+ * Names the revision offered and defaulted to in the legacy `initialize` handshake.
9
10
  *
10
11
  * @remarks
11
12
  * This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
@@ -13,12 +14,12 @@ let _orkestrel_tool = require("@orkestrel/tool");
13
14
  * it is asking to negotiate a revision with no negotiation.
14
15
  */
15
16
  var MCP_HANDSHAKE_VERSION = "2025-11-25";
16
- /** The older legacy revision the optional legacy decorator accepts and an adapter can pin. */
17
+ /** Names the older legacy revision the optional legacy decorator accepts and an adapter can pin. */
17
18
  var MCP_FALLBACK_VERSION = "2025-06-18";
18
- /** The modern revision offered by an unpinned client during discovery. */
19
+ /** Names the modern revision offered by an unpinned client during discovery. */
19
20
  var MCP_MODERN_VERSION = "2026-07-28";
20
21
  /**
21
- * The modern MCP protocol revisions a bare server accepts and advertises.
22
+ * Lists the modern MCP protocol revisions a bare server accepts and advertises.
22
23
  *
23
24
  * @remarks
24
25
  * Frozen in discovery-advertisement order. Legacy revisions are absent because
@@ -26,22 +27,25 @@ var MCP_MODERN_VERSION = "2026-07-28";
26
27
  * decorator own them.
27
28
  */
28
29
  var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
29
- /** The protocol revisions accepted by the optional legacy decorator. */
30
+ /** Lists the protocol revisions accepted by the optional legacy decorator. */
30
31
  var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
31
- /** The protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy eras. */
32
+ /**
33
+ * Lists the protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy
34
+ * eras.
35
+ */
32
36
  var SUPPORTED_MCP_VERSIONS = Object.freeze([...SUPPORTED_MODERN_PROTOCOL_VERSIONS, ...SUPPORTED_LEGACY_PROTOCOL_VERSIONS]);
33
- /** Reserved modern `_meta` key carrying the request's protocol revision. */
37
+ /** Names the reserved modern `_meta` key carrying the request's protocol revision. */
34
38
  var MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
35
- /** Reserved modern `_meta` key carrying the client's open capability record. */
39
+ /** Names the reserved modern `_meta` key carrying the client's open capability record. */
36
40
  var MCP_META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
37
- /** Reserved modern `_meta` key carrying the optional client identity. */
41
+ /** Names the reserved modern `_meta` key carrying the optional client identity. */
38
42
  var MCP_META_CLIENT = "io.modelcontextprotocol/clientInfo";
39
- /** Reserved modern `_meta` key carrying the server identity on results. */
43
+ /** Names the reserved modern `_meta` key carrying the server identity on results. */
40
44
  var MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
41
- /** Reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
45
+ /** Names the reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
42
46
  var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
43
47
  /**
44
- * The reserved extension key identifying the stable Tasks extension.
48
+ * Names the reserved extension key identifying the stable Tasks extension.
45
49
  *
46
50
  * @remarks
47
51
  * The ONE spelling of it in this package, and the identity of the immutable snapshot dated
@@ -52,7 +56,7 @@ var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
52
56
  */
53
57
  var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
54
58
  /**
55
- * The opening marker of the Base64 sentinel a standard MCP header value travels in.
59
+ * Names the opening marker of the Base64 sentinel a standard MCP header value travels in.
56
60
  *
57
61
  * @remarks
58
62
  * The markers are LOWERCASE and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
@@ -61,10 +65,10 @@ var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
61
65
  * them, so the two directions cannot drift apart.
62
66
  */
63
67
  var MCP_SENTINEL_PREFIX = "=?base64?";
64
- /** The closing marker of the Base64 sentinel a standard MCP header value travels in. */
68
+ /** Names the closing marker of the Base64 sentinel a standard MCP header value travels in. */
65
69
  var MCP_SENTINEL_SUFFIX = "?=";
66
70
  /**
67
- * The request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
71
+ * Names the request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
68
72
  *
69
73
  * @remarks
70
74
  * The full field name is this prefix followed by the annotation's own value verbatim, so
@@ -74,7 +78,40 @@ var MCP_SENTINEL_SUFFIX = "?=";
74
78
  */
75
79
  var MCP_PARAM_PREFIX = "Mcp-Param-";
76
80
  /**
77
- * The tool-schema annotation key naming the header one parameter projects into.
81
+ * Names the Streamable-HTTP transport header that carries the MCP session id.
82
+ *
83
+ * @remarks
84
+ * A STATEFUL server sends it on the `initialize` reply, and
85
+ * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} echoes it as a
86
+ * request header on every subsequent request, so a client passes that server's session
87
+ * validation unchanged.
88
+ */
89
+ var MCP_SESSION_HEADER = "mcp-session-id";
90
+ /**
91
+ * Names the Streamable-HTTP transport header carrying the MCP protocol version.
92
+ *
93
+ * @remarks
94
+ * A modern request derives it from its own `_meta`; a legacy request echoes the revision the
95
+ * `initialize` result negotiated on each subsequent request.
96
+ */
97
+ var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
98
+ /**
99
+ * Names the modern Streamable-HTTP request header carrying the JSON-RPC method.
100
+ *
101
+ * @remarks
102
+ * It is stamped on every modern request and on no legacy request.
103
+ */
104
+ var MCP_METHOD_HEADER = "mcp-method";
105
+ /**
106
+ * Names the modern Streamable-HTTP request header carrying a named target.
107
+ *
108
+ * @remarks
109
+ * The HTTP client transport stamps it only for `tools/call`, from that request's `params.name`,
110
+ * in the Base64 sentinel form whenever the name cannot ride as plain ASCII.
111
+ */
112
+ var MCP_NAME_HEADER = "mcp-name";
113
+ /**
114
+ * Identifies the tool-schema annotation key naming the header one parameter projects into.
78
115
  *
79
116
  * @remarks
80
117
  * It is valid ONLY on a primitive property schema statically reachable from the `inputSchema`
@@ -84,7 +121,23 @@ var MCP_PARAM_PREFIX = "Mcp-Param-";
84
121
  */
85
122
  var MCP_HEADER_ANNOTATION = "x-mcp-header";
86
123
  /**
87
- * The `tools/list` pages one modern `tools/call` walks to reach its own annotations.
124
+ * Names the WebSocket subprotocol `createWebSocketClientTransport` requests by default
125
+ * `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
126
+ * §4.1 a client MUST fail the connection if the server returns
127
+ * a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
128
+ * default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
129
+ * only when connecting to a foreign server that speaks a different subprotocol (or `[]`
130
+ * for no subprotocol negotiation at all).
131
+ *
132
+ * @remarks
133
+ * The client sends it in `Sec-WebSocket-Protocol` and the server echoes it in its `101`
134
+ * handshake, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
135
+ * same path. The default WebSocket upgrade path is the same `'/mcp'` the HTTP transport mounts
136
+ * at — the upgrade is selected by the `Upgrade: websocket` header, not a separate path.
137
+ */
138
+ var MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
139
+ /**
140
+ * Bounds the `tools/list` pages one modern `tools/call` walks to reach its own annotations.
88
141
  *
89
142
  * @remarks
90
143
  * The HTTP POST handler reads a called tool's {@link MCP_HEADER_ANNOTATION} annotations by
@@ -98,10 +151,11 @@ var MCP_HEADER_ANNOTATION = "x-mcp-header";
98
151
  * answer a name no served definition annotates receives.
99
152
  */
100
153
  var MCP_LOOKUP_PAGES = 8;
101
- /** MCP reserved error: required HTTP metadata does not match the request body. */
154
+ /** Names the MCP reserved error for required HTTP metadata that does not match the request body. */
102
155
  var MCP_HEADER_MISMATCH = -32020;
103
156
  /**
104
- * MCP reserved error: an operation needs a client capability that was not declared.
157
+ * Names the MCP reserved error for an operation needing a client capability that was not
158
+ * declared.
105
159
  *
106
160
  * @remarks
107
161
  * The GENERIC code for the whole condition, not one capability's code. This server answers
@@ -114,10 +168,10 @@ var MCP_HEADER_MISMATCH = -32020;
114
168
  * schema is what a peer implements against.
115
169
  */
116
170
  var MCP_MISSING_CAPABILITY = -32021;
117
- /** MCP reserved error: a request names an unsupported protocol revision. */
171
+ /** Names the MCP reserved error for a request naming an unsupported protocol revision. */
118
172
  var MCP_UNSUPPORTED_VERSION = -32022;
119
173
  /**
120
- * Default modern result freshness lifetime in milliseconds.
174
+ * Sets the default modern result freshness lifetime in milliseconds.
121
175
  *
122
176
  * @remarks
123
177
  * `ttlMs` is required on cacheable results, while zero means immediately stale
@@ -125,7 +179,8 @@ var MCP_UNSUPPORTED_VERSION = -32022;
125
179
  */
126
180
  var DEFAULT_MCP_CACHE_TTL = 6e4;
127
181
  /**
128
- * Secure server bounds used when the matching `limit` option leaf is absent or malformed.
182
+ * Sets the secure server bounds used when the matching `limit` option leaf is absent or
183
+ * malformed.
129
184
  *
130
185
  * @remarks
131
186
  * One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
@@ -147,7 +202,7 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
147
202
  depth: 32
148
203
  });
149
204
  /**
150
- * The one empty argument record every argument-less modern `tools/call` runs with.
205
+ * Holds the one empty argument record every argument-less modern `tools/call` runs with.
151
206
  *
152
207
  * @remarks
153
208
  * Frozen and null-prototype, and SHARED: two calls that name no `arguments` receive the same
@@ -161,16 +216,17 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
161
216
  * `arguments.constructor` is `undefined` here rather than a function.
162
217
  */
163
218
  var EMPTY_MCP_ARGUMENTS = Object.freeze(Object.create(null));
164
- /** JSON-RPC 2.0 reserved error: invalid JSON was received (the message did not parse). */
219
+ /** Names the JSON-RPC 2.0 reserved error for invalid JSON received (the message did not parse). */
165
220
  var JSONRPC_PARSE_ERROR = -32700;
166
- /** JSON-RPC 2.0 reserved error: the payload was not a valid Request object. */
221
+ /** Names the JSON-RPC 2.0 reserved error for a payload that was not a valid Request object. */
167
222
  var JSONRPC_INVALID_REQUEST = -32600;
168
- /** JSON-RPC 2.0 reserved error: the requested method does not exist. */
223
+ /** Names the JSON-RPC 2.0 reserved error for a requested method that does not exist. */
169
224
  var JSONRPC_METHOD_NOT_FOUND = -32601;
170
- /** JSON-RPC 2.0 reserved error: the method's parameters were invalid. */
225
+ /** Names the JSON-RPC 2.0 reserved error for a method's invalid parameters. */
171
226
  var JSONRPC_INVALID_PARAMS = -32602;
172
227
  /**
173
- * JSON-RPC 2.0 reserved error: the server failed while handling an otherwise valid request.
228
+ * Names the JSON-RPC 2.0 reserved error for a server that failed while handling an otherwise
229
+ * valid request.
174
230
  *
175
231
  * @remarks
176
232
  * The code every MODERN internal fault answers with — a provider, handler, continuation,
@@ -180,7 +236,7 @@ var JSONRPC_INVALID_PARAMS = -32602;
180
236
  */
181
237
  var JSONRPC_INTERNAL_ERROR = -32603;
182
238
  /**
183
- * JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
239
+ * Names the JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
184
240
  *
185
241
  * @remarks
186
242
  * Retained for the LEGACY branch alone. A modern fault answers
@@ -188,21 +244,27 @@ var JSONRPC_INTERNAL_ERROR = -32603;
188
244
  * already characterized against it.
189
245
  */
190
246
  var JSONRPC_SERVER_ERROR = -32e3;
191
- /** The default client name reported in the MCP `initialize` handshake (`clientInfo.name`). */
192
- var DEFAULT_MCP_CLIENT_NAME = "taverna";
193
- /** The default client version reported in the MCP `initialize` handshake (`clientInfo.version`). */
247
+ /**
248
+ * Supplies the default client name reported in the MCP `initialize` handshake
249
+ * (`clientInfo.name`).
250
+ */
251
+ var DEFAULT_MCP_CLIENT_NAME = "@orkestrel/mcp";
252
+ /**
253
+ * Supplies the default client version reported in the MCP `initialize` handshake
254
+ * (`clientInfo.version`).
255
+ */
194
256
  var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
195
257
  /**
196
- * The default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
258
+ * Sets the default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
197
259
  * is unset — a request the remote server does not answer within it rejects.
198
260
  */
199
261
  var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
200
- /** The default number of subscription frames retained while no client read is parked. */
262
+ /** Sets the default number of subscription frames retained while no client read is parked. */
201
263
  var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
202
264
  //#endregion
203
265
  //#region src/core/errors.ts
204
266
  /**
205
- * A Model Context Protocol error preserving its machine-readable numeric code and
267
+ * Preserves a Model Context Protocol error's machine-readable numeric code and
206
268
  * optional structured context.
207
269
  *
208
270
  * @remarks
@@ -245,7 +307,7 @@ var MCPError = class extends Error {
245
307
  * Determines whether an unknown value is an {@link MCPError}.
246
308
  *
247
309
  * @param value - The unknown value to inspect
248
- * @returns `true` only when the value is an `MCPError`
310
+ * @returns True if the value is an `MCPError`; false otherwise
249
311
  *
250
312
  * @example
251
313
  * ```ts
@@ -498,7 +560,7 @@ function parseRequestContext(value, limits = {
498
560
  function parseMCPInputState(value) {
499
561
  try {
500
562
  if (!(0, _orkestrel_contract.isString)(value)) return void 0;
501
- const parsed = JSON.parse(value);
563
+ const parsed = (0, _orkestrel_contract.parseJSON)(value);
502
564
  if (!(0, _orkestrel_contract.isRecord)(parsed)) return void 0;
503
565
  const principal = parsed["principal"];
504
566
  const expiry = parsed["expiry"];
@@ -540,15 +602,15 @@ function parseMCPInputState(value) {
540
602
  * does not authorize a form request. Total over hostile input.
541
603
  *
542
604
  * @param value - The client capability record to inspect
543
- * @returns `true` when form-mode elicitation is declared
605
+ * @returns True if form-mode elicitation is declared; false otherwise
544
606
  *
545
607
  * @example
546
608
  * ```ts
547
- * isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
548
- * isFormElicitationSupported({ elicitation: { url: {} } }) // false
609
+ * supportsFormElicitation({ elicitation: {} }) // true — implicit form mode
610
+ * supportsFormElicitation({ elicitation: { url: {} } }) // false
549
611
  * ```
550
612
  */
551
- function isFormElicitationSupported(value) {
613
+ function supportsFormElicitation(value) {
552
614
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
553
615
  if (!owned.success) return false;
554
616
  try {
@@ -571,7 +633,7 @@ function isFormElicitationSupported(value) {
571
633
  * `ClientCapabilities` shape the schema defines rather than as a list of names.
572
634
  *
573
635
  * Each kind maps to one declaration: `sampling/createMessage` to `sampling`, `roots/list` to
574
- * `roots`, a form elicitation to what {@link isFormElicitationSupported} accepts, and a
636
+ * `roots`, a form elicitation to what {@link supportsFormElicitation} accepts, and a
575
637
  * URL-mode elicitation to a record-valued `elicitation.url`. A request this package cannot
576
638
  * recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
577
639
  * has already refused the round it would have travelled in. Total over hostile input.
@@ -612,7 +674,7 @@ function computeMissingCapabilities(requests, capabilities) {
612
674
  if (!(0, _orkestrel_contract.isRecord)(elicitation) || !(0, _orkestrel_contract.isRecord)(elicitation["url"])) urlUndeclared = true;
613
675
  continue;
614
676
  }
615
- if (!isFormElicitationSupported(declared)) formUndeclared = true;
677
+ if (!supportsFormElicitation(declared)) formUndeclared = true;
616
678
  }
617
679
  if (formUndeclared && !urlUndeclared) missing["elicitation"] = {};
618
680
  if (urlUndeclared && !formUndeclared) missing["elicitation"] = { url: {} };
@@ -639,16 +701,16 @@ function computeMissingCapabilities(requests, capabilities) {
639
701
  * the request in hand. Total over hostile input.
640
702
  *
641
703
  * @param value - The client capability record to inspect
642
- * @returns `true` when the tasks extension is declared as the schema's empty object
704
+ * @returns True if the tasks extension is declared as the schema's empty object; false otherwise
643
705
  *
644
706
  * @example
645
707
  * ```ts
646
- * isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
647
- * isTaskSupported({ extensions: {} }) // false — the key is the declaration
648
- * isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
708
+ * supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
709
+ * supportsTask({ extensions: {} }) // false — the key is the declaration
710
+ * supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
649
711
  * ```
650
712
  */
651
- function isTaskSupported(value) {
713
+ function supportsTask(value) {
652
714
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
653
715
  if (!owned.success) return false;
654
716
  try {
@@ -925,7 +987,7 @@ function buildProgressNotification(token, progress) {
925
987
  * rather than as a violation.
926
988
  *
927
989
  * Only write one on a carrier that accepts a client-initiated notification — see
928
- * {@link import('./types.js').MCPClientTransportInterface.duplex}. On Streamable HTTP the
990
+ * {@link import('./types.js').MCPMessageTransportInterface.duplex}. On Streamable HTTP the
929
991
  * dated revision defines no such frame, and closing the response stream is the
930
992
  * cancellation signal instead.
931
993
  *
@@ -964,7 +1026,7 @@ function buildCancelledNotification(id, reason) {
964
1026
  *
965
1027
  * @param method - The method the pending request was issued for
966
1028
  * @param resultType - The unknown `resultType` the peer answered with
967
- * @returns Whether that method may legally answer with that `resultType`
1029
+ * @returns True if that method may legally answer with that `resultType`; false otherwise
968
1030
  *
969
1031
  * @example
970
1032
  * ```ts
@@ -1319,7 +1381,7 @@ function buildSubscriptionFilter(requested, supported, enabled = false) {
1319
1381
  *
1320
1382
  * @param notification - The server notification offered by the configured producer
1321
1383
  * @param filter - The filter acknowledged to the client
1322
- * @returns `true` when the notification belongs on this subscription stream
1384
+ * @returns True if the notification belongs on this subscription stream; false otherwise
1323
1385
  */
1324
1386
  function matchesSubscriptionNotification(notification, filter) {
1325
1387
  if (notification.method === "notifications/tools/list_changed") return filter.toolsListChanged === true;
@@ -1441,7 +1503,7 @@ function buildInitializeResult(name, version, requested) {
1441
1503
  *
1442
1504
  * @remarks
1443
1505
  * The bound is checked FIRST, against the raw string, so an oversized message is never
1444
- * `JSON.parse`d at all: a decoder that parses before it measures has already spent the work
1506
+ * parsed at all: a decoder that parses before it measures has already spent the work
1445
1507
  * the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
1446
1508
  * value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
1447
1509
  * exactly the same thing with each of them: nothing, and let
@@ -1461,8 +1523,129 @@ function buildInitializeResult(name, version, requested) {
1461
1523
  */
1462
1524
  function decodeBoundedMessage(message, limits) {
1463
1525
  if (!isBoundedString(message, limits.bytes)) return void 0;
1464
- const parsed = (0, _orkestrel_contract.attempt)(() => JSON.parse(message));
1465
- return parsed.success ? parseJSONRPCMessage(parsed.value, limits) : void 0;
1526
+ return parseJSONRPCMessage((0, _orkestrel_contract.parseJSON)(message), limits);
1527
+ }
1528
+ /**
1529
+ * Decodes one inbound frame and delivers it onto a transport emitter as `message` or `error`.
1530
+ *
1531
+ * @remarks
1532
+ * The ONE inbound fold every message-carrying transport in this package runs: parse the frame,
1533
+ * narrow it with `parseJSONRPCMessage`, emit `message` for a well-formed
1534
+ * {@link JSONRPCMessage}, and emit `error` for anything else. Total — an adversarial frame
1535
+ * produces an `error` emission and never a throw.
1536
+ *
1537
+ * The two failures report differently on purpose. Unparsable text emits the CAUGHT parse
1538
+ * error, which names the offending position; well-formed JSON that is not a JSON-RPC message
1539
+ * has no caught value to report, so it emits `fault` — the carrier's own wording, passed in
1540
+ * rather than forked into a second copy of this body.
1541
+ *
1542
+ * @param emitter - The transport's emitter to deliver onto
1543
+ * @param text - One inbound frame's raw text
1544
+ * @param fault - The message for the error emitted when the frame parses but is not JSON-RPC
1545
+ *
1546
+ * @example
1547
+ * ```ts
1548
+ * deliverMessage(transport.emitter, frame, 'non-JSON-RPC WebSocket frame')
1549
+ * ```
1550
+ */
1551
+ function deliverMessage(emitter, text, fault) {
1552
+ let parsed;
1553
+ try {
1554
+ parsed = JSON.parse(text);
1555
+ } catch (error) {
1556
+ emitter.emit("error", error);
1557
+ return;
1558
+ }
1559
+ const message = parseJSONRPCMessage(parsed);
1560
+ if (message === void 0) {
1561
+ emitter.emit("error", new Error(fault));
1562
+ return;
1563
+ }
1564
+ emitter.emit("message", message);
1565
+ }
1566
+ /**
1567
+ * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
1568
+ * when it is not one — the per-event step {@link readEventStream} folds over.
1569
+ *
1570
+ * @remarks
1571
+ * Parses the `data` (a peer serializes the JSON-RPC envelope as the event's `data`) with
1572
+ * `@orkestrel/contract`'s `parseJSON` — the declared JSON boundary, which answers `undefined`
1573
+ * instead of throwing — and narrows the parsed value with `parseJSONRPCMessage`. Total:
1574
+ * malformed JSON or a non-message value yields `undefined`, never throws.
1575
+ *
1576
+ * @param data - One SSE event's `data` payload
1577
+ * @returns The decoded {@link JSONRPCMessage}, or `undefined`
1578
+ *
1579
+ * @example
1580
+ * ```ts
1581
+ * decodeEvent('{"jsonrpc":"2.0","id":1,"result":{}}') // the decoded response
1582
+ * ```
1583
+ */
1584
+ function decodeEvent(data) {
1585
+ return parseJSONRPCMessage((0, _orkestrel_contract.parseJSON)(data));
1586
+ }
1587
+ /**
1588
+ * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
1589
+ * carried — the CLIENT-side inverse of a server's Streamable-HTTP SSE response.
1590
+ *
1591
+ * @remarks
1592
+ * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({ stream: true
1593
+ * })` (handling a multi-byte character split across reads) and `@orkestrel/sse`'s
1594
+ * {@link SSEParserInterface} (handling a partial line or in-progress event split across
1595
+ * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} through
1596
+ * {@link decodeEvent} (so a non-message or non-JSON `data:` event is DROPPED, never thrown —
1597
+ * total). It reuses the SAME `SSEParser` a server's `createStream` seam serializes against, so
1598
+ * the wire round-trips. A `null` body (no stream) yields no messages;
1599
+ * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} reads a
1600
+ * request/response SSE reply (the server sends one `data:` event then ends), so this drains to
1601
+ * completion.
1602
+ *
1603
+ * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
1604
+ * @returns Every {@link JSONRPCMessage} the stream carried, in order
1605
+ *
1606
+ * @example
1607
+ * ```ts
1608
+ * const messages = await readEventStream(await fetch(url, { method: 'POST', body }))
1609
+ * ```
1610
+ */
1611
+ async function readEventStream(response) {
1612
+ const body = response.body;
1613
+ if (body === null) return [];
1614
+ const reader = body.getReader();
1615
+ const decoder = new TextDecoder();
1616
+ const parser = (0, _orkestrel_sse.createSSEParser)();
1617
+ const messages = [];
1618
+ try {
1619
+ for (;;) {
1620
+ const { done, value } = await reader.read();
1621
+ if (done) break;
1622
+ for (const event of parser.parse(decoder.decode(value, { stream: true }))) {
1623
+ const message = decodeEvent(event.data);
1624
+ if (message !== void 0) messages.push(message);
1625
+ }
1626
+ }
1627
+ } finally {
1628
+ reader.releaseLock();
1629
+ }
1630
+ return messages;
1631
+ }
1632
+ /**
1633
+ * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
1634
+ *
1635
+ * @param response - The response whose status is reported
1636
+ * @param type - The response's content type, or an empty string when absent
1637
+ * @returns An error naming the HTTP status and unsupported response shape
1638
+ *
1639
+ * @example
1640
+ * ```ts
1641
+ * const error = buildResponseError(new Response('', { status: 500 }), '')
1642
+ * ```
1643
+ */
1644
+ function buildResponseError(response, type) {
1645
+ if (type.includes("application/json")) return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained an application/json body that was not a JSON-RPC message`);
1646
+ if (type.includes("text/event-stream")) return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained a text/event-stream body without a JSON-RPC message`);
1647
+ const shape = type === "" ? "a body without a content type" : `an unsupported '${type}' body`;
1648
+ return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained ${shape}`);
1466
1649
  }
1467
1650
  /**
1468
1651
  * Reads the value one standard MCP request header carries, decoding the Base64 sentinel.
@@ -1722,7 +1905,7 @@ function renderHeaderValue(value, primitive) {
1722
1905
  *
1723
1906
  * @remarks
1724
1907
  * The projection SEP-2243 requires of an HTTP client, and the same derivation a server runs
1725
- * to know what the request should have carried. Each parameter's value is read at its exact
1908
+ * to know what the request must carry. Each parameter's value is read at its exact
1726
1909
  * property path in the call's own `arguments`; an absent or `null` value omits its header
1727
1910
  * entirely, which is the protocol's distinction between "not supplied" and "supplied empty".
1728
1911
  * The rendered text then travels through {@link encodeSentinel}, so a value carrying
@@ -1953,7 +2136,7 @@ function bindServer(server, transport) {
1953
2136
  * @remarks
1954
2137
  * The client's outbound writes flow through `client.transport.send` — its existing,
1955
2138
  * unmodified request/response correlation — so `client` must have been constructed
1956
- * with a {@link import('./types.js').MCPClientTransportInterface} that itself carries
2139
+ * with a {@link import('./types.js').MCPMessageTransportInterface} that itself carries
1957
2140
  * the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
1958
2141
  * the additive factory that adapts an {@link MCPTransportInterface} into that shape);
1959
2142
  * this binder then completes the inbound half by decoding each message and pushing it
@@ -2066,7 +2249,7 @@ function isMCPResultMetaObject(value) {
2066
2249
  * {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
2067
2250
  *
2068
2251
  * @param value - The unknown value to inspect
2069
- * @returns `true` when the value is exact metadata whose subscription stamp, if present, is valid
2252
+ * @returns True if the value is exact metadata whose subscription stamp, if present, is valid; false otherwise
2070
2253
  *
2071
2254
  * @example
2072
2255
  * ```ts
@@ -2089,7 +2272,7 @@ function isMCPLoggingLevel(value) {
2089
2272
  * Determines whether a value is standard padded base64 as required by JSON Schema `byte` format.
2090
2273
  *
2091
2274
  * @param value - The unknown value to inspect
2092
- * @returns Whether the value is an empty or completely padded standard base64 encoding
2275
+ * @returns True if the value is an empty or completely padded standard base64 encoding; false otherwise
2093
2276
  */
2094
2277
  function isStandardBase64(value) {
2095
2278
  return (0, _orkestrel_contract.isString)(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
@@ -2105,7 +2288,7 @@ function isStandardBase64(value) {
2105
2288
  * {@link MCP_PARAM_PREFIX} and must survive as an HTTP field name.
2106
2289
  *
2107
2290
  * @param value - The unknown value to inspect
2108
- * @returns Whether the value is a non-empty RFC 9110 token
2291
+ * @returns True if the value is a non-empty RFC 9110 token; false otherwise
2109
2292
  *
2110
2293
  * @example
2111
2294
  * ```ts
@@ -2125,7 +2308,7 @@ function isFieldToken(value) {
2125
2308
  * exactly, and the server compares it numerically.
2126
2309
  *
2127
2310
  * @param value - The unknown value to inspect
2128
- * @returns Whether the value is one of `'string'`, `'integer'`, or `'boolean'`
2311
+ * @returns True if the value is one of `'string'`, `'integer'`, or `'boolean'`; false otherwise
2129
2312
  *
2130
2313
  * @example
2131
2314
  * ```ts
@@ -2144,7 +2327,7 @@ function isMCPHeaderPrimitive(value) {
2144
2327
  * scheme allowlist. Component scanning is bounded by the input length.
2145
2328
  *
2146
2329
  * @param value - The unknown value to inspect
2147
- * @returns Whether the value is an RFC 3986 URI rather than a relative reference
2330
+ * @returns True if the value is an RFC 3986 URI rather than a relative reference; false otherwise
2148
2331
  */
2149
2332
  function isAbsoluteURI(value) {
2150
2333
  if (!(0, _orkestrel_contract.isString)(value) || value.length === 0) return false;
@@ -2240,7 +2423,7 @@ function isAbsoluteURI(value) {
2240
2423
  * refuse. It is a SYNTAX guard: no time zone, locale, calendar era, or leap second applies.
2241
2424
  *
2242
2425
  * @param value - The unknown value to inspect
2243
- * @returns Whether the value is an RFC 3339 `full-date` for a day that exists
2426
+ * @returns True if the value is an RFC 3339 `full-date` for a day that exists; false otherwise
2244
2427
  *
2245
2428
  * @example
2246
2429
  * ```ts
@@ -2273,7 +2456,7 @@ function isRFC3339Date(value) {
2273
2456
  * second.
2274
2457
  *
2275
2458
  * @param value - The unknown value to inspect
2276
- * @returns Whether the value is an RFC 3339 `date-time` for a day that exists
2459
+ * @returns True if the value is an RFC 3339 `date-time` for a day that exists; false otherwise
2277
2460
  *
2278
2461
  * @example
2279
2462
  * ```ts
@@ -2291,7 +2474,7 @@ function isRFC3339DateTime(value) {
2291
2474
  * Determines whether a value is one exact finite MCP progress payload.
2292
2475
  *
2293
2476
  * @param value - The unknown value to inspect
2294
- * @returns Whether required progress and optional total/message fields match the dated schema
2477
+ * @returns True if required progress and optional total/message fields match the dated schema; false otherwise
2295
2478
  */
2296
2479
  function isMCPProgress(value) {
2297
2480
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2310,7 +2493,7 @@ function isMCPProgress(value) {
2310
2493
  * Determines whether a value carries valid dated-schema MCP content annotations.
2311
2494
  *
2312
2495
  * @param value - The unknown value to inspect
2313
- * @returns Whether the value is valid MCP annotations
2496
+ * @returns True if the value is valid MCP annotations; false otherwise
2314
2497
  */
2315
2498
  function isMCPAnnotations(value) {
2316
2499
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2331,7 +2514,7 @@ function isMCPAnnotations(value) {
2331
2514
  * Determines whether a value is one exact dated-schema MCP icon.
2332
2515
  *
2333
2516
  * @param value - The unknown value to inspect
2334
- * @returns Whether the value is a valid MCP icon
2517
+ * @returns True if the value is a valid MCP icon; false otherwise
2335
2518
  */
2336
2519
  function isMCPIcon(value) {
2337
2520
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2422,7 +2605,7 @@ function isMCPServerCapabilities(value) {
2422
2605
  * Determines whether a value is embedded textual MCP resource contents.
2423
2606
  *
2424
2607
  * @param value - The unknown value to inspect
2425
- * @returns Whether the value is embedded textual resource contents
2608
+ * @returns True if the value is embedded textual resource contents; false otherwise
2426
2609
  */
2427
2610
  function isMCPTextResource(value) {
2428
2611
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2441,7 +2624,7 @@ function isMCPTextResource(value) {
2441
2624
  * Determines whether a value is embedded blob MCP resource contents.
2442
2625
  *
2443
2626
  * @param value - The unknown value to inspect
2444
- * @returns Whether the value is embedded blob resource contents
2627
+ * @returns True if the value is embedded blob resource contents; false otherwise
2445
2628
  */
2446
2629
  function isMCPBlobResource(value) {
2447
2630
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2460,7 +2643,7 @@ function isMCPBlobResource(value) {
2460
2643
  * Determines whether a value is one `resources/list` descriptor.
2461
2644
  *
2462
2645
  * @param value - The unknown value to inspect
2463
- * @returns Whether the value is a valid resource descriptor
2646
+ * @returns True if the value is a valid resource descriptor; false otherwise
2464
2647
  */
2465
2648
  function isMCPResource(value) {
2466
2649
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2482,7 +2665,7 @@ function isMCPResource(value) {
2482
2665
  * level belong to the consumer-supplied resource manager; this package projects the string.
2483
2666
  *
2484
2667
  * @param value - The unknown value to inspect
2485
- * @returns Whether the value is a valid resource-template descriptor
2668
+ * @returns True if the value is a valid resource-template descriptor; false otherwise
2486
2669
  */
2487
2670
  function isMCPResourceTemplate(value) {
2488
2671
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2499,7 +2682,7 @@ function isMCPResourceTemplate(value) {
2499
2682
  * Determines whether a value is structurally discriminated resource contents.
2500
2683
  *
2501
2684
  * @param value - The unknown value to inspect
2502
- * @returns Whether exactly one of `text` and `blob` is present and valid
2685
+ * @returns True if exactly one of `text` and `blob` is present and valid; false otherwise
2503
2686
  */
2504
2687
  function isMCPResourceContents(value) {
2505
2688
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2513,7 +2696,7 @@ function isMCPResourceContents(value) {
2513
2696
  * Determines whether a value carries the shared optional pagination cursor.
2514
2697
  *
2515
2698
  * @param value - The unknown value to inspect
2516
- * @returns Whether a present `cursor` is a string
2699
+ * @returns True if a present `cursor` is a string; false otherwise
2517
2700
  */
2518
2701
  function isMCPPaginationParams(value) {
2519
2702
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2523,7 +2706,7 @@ function isMCPPaginationParams(value) {
2523
2706
  * Determines whether a value is one consumer-owned resource page.
2524
2707
  *
2525
2708
  * @param value - The unknown value to inspect
2526
- * @returns Whether the resources and optional following cursor are valid
2709
+ * @returns True if the resources and optional following cursor are valid; false otherwise
2527
2710
  */
2528
2711
  function isMCPResourcePage(value) {
2529
2712
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2536,7 +2719,7 @@ function isMCPResourcePage(value) {
2536
2719
  * Determines whether a value is one consumer-owned resource-template page.
2537
2720
  *
2538
2721
  * @param value - The unknown value to inspect
2539
- * @returns Whether the templates and optional following cursor are valid
2722
+ * @returns True if the templates and optional following cursor are valid; false otherwise
2540
2723
  */
2541
2724
  function isMCPResourceTemplatePage(value) {
2542
2725
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2549,7 +2732,7 @@ function isMCPResourceTemplatePage(value) {
2549
2732
  * Determines whether a value is a string-valued MCP argument record.
2550
2733
  *
2551
2734
  * @param value - The unknown value to inspect
2552
- * @returns Whether every own argument value is a string
2735
+ * @returns True if every own argument value is a string; false otherwise
2553
2736
  */
2554
2737
  function isMCPStringArguments(value) {
2555
2738
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2559,7 +2742,7 @@ function isMCPStringArguments(value) {
2559
2742
  * Determines whether a value is one prompt argument descriptor.
2560
2743
  *
2561
2744
  * @param value - The unknown value to inspect
2562
- * @returns Whether the prompt argument descriptor is valid
2745
+ * @returns True if the prompt argument descriptor is valid; false otherwise
2563
2746
  */
2564
2747
  function isMCPPromptArgument(value) {
2565
2748
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2571,7 +2754,7 @@ function isMCPPromptArgument(value) {
2571
2754
  * Determines whether a value is one `prompts/list` descriptor.
2572
2755
  *
2573
2756
  * @param value - The unknown value to inspect
2574
- * @returns Whether the prompt descriptor is valid
2757
+ * @returns True if the prompt descriptor is valid; false otherwise
2575
2758
  */
2576
2759
  function isMCPPrompt(value) {
2577
2760
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2585,7 +2768,7 @@ function isMCPPrompt(value) {
2585
2768
  * Determines whether a value is one prompt message with existing rich content.
2586
2769
  *
2587
2770
  * @param value - The unknown value to inspect
2588
- * @returns Whether the role and content are valid
2771
+ * @returns True if the role and content are valid; false otherwise
2589
2772
  */
2590
2773
  function isMCPPromptMessage(value) {
2591
2774
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2595,7 +2778,7 @@ function isMCPPromptMessage(value) {
2595
2778
  * Determines whether a value is one consumer-owned prompt page.
2596
2779
  *
2597
2780
  * @param value - The unknown value to inspect
2598
- * @returns Whether the prompts and optional following cursor are valid
2781
+ * @returns True if the prompts and optional following cursor are valid; false otherwise
2599
2782
  */
2600
2783
  function isMCPPromptPage(value) {
2601
2784
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2608,7 +2791,7 @@ function isMCPPromptPage(value) {
2608
2791
  * Determines whether a value is one complete `prompts/get` result.
2609
2792
  *
2610
2793
  * @param value - The unknown value to inspect
2611
- * @returns Whether the prompt result and all messages are valid
2794
+ * @returns True if the prompt result and all messages are valid; false otherwise
2612
2795
  */
2613
2796
  function isMCPPromptGetResult(value) {
2614
2797
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2621,7 +2804,7 @@ function isMCPPromptGetResult(value) {
2621
2804
  * Determines whether a value is a prompt or resource-template completion reference.
2622
2805
  *
2623
2806
  * @param value - The unknown value to inspect
2624
- * @returns Whether the discriminated reference is valid
2807
+ * @returns True if the discriminated reference is valid; false otherwise
2625
2808
  */
2626
2809
  function isMCPCompletionReference(value) {
2627
2810
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2633,7 +2816,7 @@ function isMCPCompletionReference(value) {
2633
2816
  * Determines whether a value is one `completion/complete` parameter object.
2634
2817
  *
2635
2818
  * @param value - The unknown value to inspect
2636
- * @returns Whether its reference, fragment, and optional string context are valid
2819
+ * @returns True if its reference, fragment, and optional string context are valid; false otherwise
2637
2820
  */
2638
2821
  function isMCPCompletionParams(value) {
2639
2822
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2652,7 +2835,7 @@ function isMCPCompletionParams(value) {
2652
2835
  * Determines whether a value is one host-produced completion candidate set.
2653
2836
  *
2654
2837
  * @param value - The unknown value to inspect
2655
- * @returns Whether its candidates and optional result facts are valid
2838
+ * @returns True if its candidates and optional result facts are valid; false otherwise
2656
2839
  */
2657
2840
  function isMCPCompletion(value) {
2658
2841
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2666,7 +2849,7 @@ function isMCPCompletion(value) {
2666
2849
  * Determines whether a value is one complete, capped `completion/complete` result.
2667
2850
  *
2668
2851
  * @param value - The unknown value to inspect
2669
- * @returns Whether the result is complete and carries at most 100 candidates
2852
+ * @returns True if the result is complete and carries at most 100 candidates; false otherwise
2670
2853
  */
2671
2854
  function isMCPCompletionResult(value) {
2672
2855
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2678,7 +2861,7 @@ function isMCPCompletionResult(value) {
2678
2861
  * Determines whether a value is one exact dated-schema MCP tool content block.
2679
2862
  *
2680
2863
  * @param value - The unknown value to inspect
2681
- * @returns Whether the value is valid MCP content
2864
+ * @returns True if the value is valid MCP content; false otherwise
2682
2865
  */
2683
2866
  function isMCPContent(value) {
2684
2867
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2719,7 +2902,7 @@ function isMCPContent(value) {
2719
2902
  * input.
2720
2903
  *
2721
2904
  * @param value - The unknown value to inspect
2722
- * @returns Whether the value is a modern result
2905
+ * @returns True if the value is a modern result; false otherwise
2723
2906
  *
2724
2907
  * @example
2725
2908
  * ```ts
@@ -2745,7 +2928,7 @@ function isMCPResult(value) {
2745
2928
  * hostile input.
2746
2929
  *
2747
2930
  * @param value - The unknown value to inspect
2748
- * @returns Whether the value is a legacy result
2931
+ * @returns True if the value is a legacy result; false otherwise
2749
2932
  *
2750
2933
  * @example
2751
2934
  * ```ts
@@ -2761,7 +2944,7 @@ function isMCPLegacyResult(value) {
2761
2944
  * Determines whether a value is a complete modern MCP tool result.
2762
2945
  *
2763
2946
  * @param value - The unknown value to inspect
2764
- * @returns Whether the value is a complete MCP call result
2947
+ * @returns True if the value is a complete MCP call result; false otherwise
2765
2948
  */
2766
2949
  function isMCPCallResult(value) {
2767
2950
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2789,7 +2972,7 @@ function isMCPCallResult(value) {
2789
2972
  * INTEGER milliseconds because the schema formats them `int`.
2790
2973
  *
2791
2974
  * @param value - The unknown value to inspect
2792
- * @returns Whether the value is a well-formed `resultType: 'task'` result
2975
+ * @returns True if the value is a well-formed `resultType: 'task'` result; false otherwise
2793
2976
  *
2794
2977
  * @example
2795
2978
  * ```ts
@@ -2816,7 +2999,7 @@ function isMCPTaskResult(value) {
2816
2999
  * Determines whether a value is one of the extension's task lifecycle states.
2817
3000
  *
2818
3001
  * @param value - The unknown value to inspect
2819
- * @returns Whether the value is an {@link MCPTaskStatus}
3002
+ * @returns True if the value is an {@link MCPTaskStatus}; false otherwise
2820
3003
  *
2821
3004
  * @example
2822
3005
  * ```ts
@@ -2847,7 +3030,7 @@ function isMCPTaskStatus(value) {
2847
3030
  * What is checked is what this package publishes as the contract.
2848
3031
  *
2849
3032
  * @param value - The unknown value to inspect
2850
- * @returns Whether the value is a well-formed {@link MCPTaskDetail}
3033
+ * @returns True if the value is a well-formed {@link MCPTaskDetail}; false otherwise
2851
3034
  *
2852
3035
  * @example
2853
3036
  * ```ts
@@ -2890,7 +3073,7 @@ function isMCPTaskDetail(value) {
2890
3073
  * peer stamps there is the peer's to write.
2891
3074
  *
2892
3075
  * @param value - The unknown value to inspect
2893
- * @returns Whether the value is a well-formed {@link MCPTaskDetailResult}
3076
+ * @returns True if the value is a well-formed {@link MCPTaskDetailResult}; false otherwise
2894
3077
  *
2895
3078
  * @example
2896
3079
  * ```ts
@@ -2924,7 +3107,7 @@ function isMCPTaskDetailResult(value) {
2924
3107
  * to it, so a guard that demanded the stamp would refuse every frame a producer emits.
2925
3108
  *
2926
3109
  * @param value - The unknown value to inspect
2927
- * @returns Whether the value is a well-formed `notifications/tasks` notification
3110
+ * @returns True if the value is a well-formed `notifications/tasks` notification; false otherwise
2928
3111
  *
2929
3112
  * @example
2930
3113
  * ```ts
@@ -2952,7 +3135,7 @@ function isMCPTaskNotification(value) {
2952
3135
  *
2953
3136
  * @param value - The unknown value to inspect
2954
3137
  * @param bytes - The maximum accepted encoded bytes
2955
- * @returns `true` only for a string whose UTF-8 representation fits the bound
3138
+ * @returns True if `value` is a string whose UTF-8 representation fits the bound; false otherwise
2956
3139
  *
2957
3140
  * @example
2958
3141
  * ```ts
@@ -2988,7 +3171,7 @@ function isBoundedString(value, bytes) {
2988
3171
  *
2989
3172
  * @param value - The unknown value to inspect
2990
3173
  * @param limits - Serialized byte, optional key, and nesting-depth bounds
2991
- * @returns `true` only for safe JSON satisfying every bound
3174
+ * @returns True if `value` is safe JSON satisfying every bound; false otherwise
2992
3175
  *
2993
3176
  * @example
2994
3177
  * ```ts
@@ -3010,7 +3193,7 @@ function isBoundedJSON(value, limits) {
3010
3193
  * no minimum length. Total: any other input returns `false`.
3011
3194
  *
3012
3195
  * @param value - The already-parsed value to test
3013
- * @returns `true` when `value` is a string or a finite integer
3196
+ * @returns True if `value` is a string or a finite integer; false otherwise
3014
3197
  *
3015
3198
  * @example
3016
3199
  * ```ts
@@ -3028,7 +3211,7 @@ function isJSONRPCId(value) {
3028
3211
  * Determines whether a value is a supported {@link MCPVersion}.
3029
3212
  *
3030
3213
  * @param value - The unknown value to inspect
3031
- * @returns `true` when the value is one of {@link SUPPORTED_MCP_VERSIONS}
3214
+ * @returns True if the value is one of {@link SUPPORTED_MCP_VERSIONS}; false otherwise
3032
3215
  */
3033
3216
  function isMCPVersion(value) {
3034
3217
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MCP_VERSIONS.some((version) => version === value);
@@ -3037,7 +3220,7 @@ function isMCPVersion(value) {
3037
3220
  * Determines whether a value is a modern protocol revision accepted by a bare server.
3038
3221
  *
3039
3222
  * @param value - The unknown value to inspect
3040
- * @returns `true` when the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}
3223
+ * @returns True if the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}; false otherwise
3041
3224
  */
3042
3225
  function isMCPModernVersion(value) {
3043
3226
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MODERN_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -3046,7 +3229,7 @@ function isMCPModernVersion(value) {
3046
3229
  * Determines whether a value is a revision accepted by the optional legacy decorator.
3047
3230
  *
3048
3231
  * @param value - The unknown value to inspect
3049
- * @returns `true` when the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
3232
+ * @returns True if the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}; false otherwise
3050
3233
  */
3051
3234
  function isMCPLegacyVersion(value) {
3052
3235
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_LEGACY_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -3065,7 +3248,7 @@ function isMCPLegacyVersion(value) {
3065
3248
  * the caller asked for.
3066
3249
  *
3067
3250
  * @param value - The unknown value to inspect
3068
- * @returns `true` when every recognized filter field has its protocol shape
3251
+ * @returns True if every recognized filter field has its protocol shape; false otherwise
3069
3252
  */
3070
3253
  function isMCPSubscriptionFilter(value) {
3071
3254
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -3086,7 +3269,7 @@ function isMCPSubscriptionFilter(value) {
3086
3269
  * Determines whether a value is a graceful `subscriptions/listen` result.
3087
3270
  *
3088
3271
  * @param value - The unknown value to inspect
3089
- * @returns `true` when the result is complete and carries a valid subscription id
3272
+ * @returns True if the result is complete and carries a valid subscription id; false otherwise
3090
3273
  */
3091
3274
  function isMCPSubscriptionResult(value) {
3092
3275
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -3098,7 +3281,7 @@ function isMCPSubscriptionResult(value) {
3098
3281
  * Determines whether a value is one restricted primitive form-elicitation schema.
3099
3282
  *
3100
3283
  * @param value - The unknown value to inspect
3101
- * @returns `true` for a supported boolean, numeric, string, or string-array schema
3284
+ * @returns True if `value` is a supported boolean, numeric, string, or string-array schema; false otherwise
3102
3285
  *
3103
3286
  * @example
3104
3287
  * ```ts
@@ -3161,7 +3344,7 @@ function isMCPElicitFieldSchema(value) {
3161
3344
  * an unrecognized top-level annotation is data rather than a rejection.
3162
3345
  *
3163
3346
  * @param value - The unknown value to inspect
3164
- * @returns `true` when `value` is a restricted object schema of supported field schemas
3347
+ * @returns True if `value` is a restricted object schema of supported field schemas; false otherwise
3165
3348
  *
3166
3349
  * @example
3167
3350
  * ```ts
@@ -3188,7 +3371,7 @@ function isMCPElicitSchema(value) {
3188
3371
  * Determines whether a value is a form-mode elicitation parameter object.
3189
3372
  *
3190
3373
  * @param value - The unknown value to inspect
3191
- * @returns `true` when `value` has the restricted form elicitation shape
3374
+ * @returns True if `value` has the restricted form elicitation shape; false otherwise
3192
3375
  *
3193
3376
  * @example
3194
3377
  * ```ts
@@ -3214,7 +3397,7 @@ function isMCPElicitForm(value) {
3214
3397
  * Determines whether a value is a URL-mode elicitation parameter object.
3215
3398
  *
3216
3399
  * @param value - The unknown value to inspect
3217
- * @returns `true` when `value` has the URL elicitation shape
3400
+ * @returns True if `value` has the URL elicitation shape; false otherwise
3218
3401
  *
3219
3402
  * @example
3220
3403
  * ```ts
@@ -3235,7 +3418,7 @@ function isMCPElicitURL(value) {
3235
3418
  * Determines whether a value is an embedded `elicitation/create` request.
3236
3419
  *
3237
3420
  * @param value - The unknown value to inspect
3238
- * @returns `true` when `value` is a form- or URL-mode elicitation request
3421
+ * @returns True if `value` is a form- or URL-mode elicitation request; false otherwise
3239
3422
  *
3240
3423
  * @example
3241
3424
  * ```ts
@@ -3260,7 +3443,7 @@ function isMCPElicitRequest(value) {
3260
3443
  * Determines whether a value is one legal embedded multi-round-trip request.
3261
3444
  *
3262
3445
  * @param value - The unknown value to inspect
3263
- * @returns `true` for an embedded elicitation, sampling, or roots request
3446
+ * @returns True if `value` is an embedded elicitation, sampling, or roots request; false otherwise
3264
3447
  *
3265
3448
  * @example
3266
3449
  * ```ts
@@ -3284,7 +3467,7 @@ function isMCPInputRequest(value) {
3284
3467
  * Determines whether a value is a consumer-keyed map of embedded input requests.
3285
3468
  *
3286
3469
  * @param value - The unknown value to inspect
3287
- * @returns `true` when every own value is a legal {@link MCPInputRequest}
3470
+ * @returns True if every own value is a legal {@link MCPInputRequest}; false otherwise
3288
3471
  *
3289
3472
  * @example
3290
3473
  * ```ts
@@ -3304,7 +3487,7 @@ function isMCPInputRequestMap(value) {
3304
3487
  * Determines whether a value is one elicitation response.
3305
3488
  *
3306
3489
  * @param value - The unknown value to inspect
3307
- * @returns `true` when action/content have the protocol shape
3490
+ * @returns True if action/content have the protocol shape; false otherwise
3308
3491
  *
3309
3492
  * @example
3310
3493
  * ```ts
@@ -3354,7 +3537,7 @@ function isMCPElicitResult(value) {
3354
3537
  *
3355
3538
  * @param value - The accepted response content to check
3356
3539
  * @param schema - The exact {@link MCPElicitSchema} that was issued with the elicitation
3357
- * @returns `true` when every declared and undeclared value is legal under `schema`
3540
+ * @returns True if every declared and undeclared value is legal under `schema`; false otherwise
3358
3541
  *
3359
3542
  * @example
3360
3543
  * ```ts
@@ -3443,7 +3626,7 @@ function isElicitContent(value, schema) {
3443
3626
  * including a URL-mode elicitation's `url`. Total over hostile input.
3444
3627
  *
3445
3628
  * @param value - The unknown value to inspect
3446
- * @returns `true` when `value` carries an absolute `uri` and an optional string `name`
3629
+ * @returns True if `value` carries an absolute `uri` and an optional string `name`; false otherwise
3447
3630
  *
3448
3631
  * @example
3449
3632
  * ```ts
@@ -3473,7 +3656,7 @@ function isMCPRoot(value) {
3473
3656
  * {@link isMCPRoot}. Total over hostile input.
3474
3657
  *
3475
3658
  * @param value - The unknown value to inspect
3476
- * @returns `true` when `value` carries an array of valid roots
3659
+ * @returns True if `value` carries an array of valid roots; false otherwise
3477
3660
  *
3478
3661
  * @example
3479
3662
  * ```ts
@@ -3506,7 +3689,7 @@ function isMCPRootResult(value) {
3506
3689
  * input.
3507
3690
  *
3508
3691
  * @param value - The unknown value to inspect
3509
- * @returns `true` when `value` is one legal sampling content block
3692
+ * @returns True if `value` is one legal sampling content block; false otherwise
3510
3693
  *
3511
3694
  * @example
3512
3695
  * ```ts
@@ -3547,7 +3730,7 @@ function isMCPSampleContent(value) {
3547
3730
  * names four values and permits any other a provider reports. Total over hostile input.
3548
3731
  *
3549
3732
  * @param value - The unknown value to inspect
3550
- * @returns `true` when `value` has the sampling-completion shape
3733
+ * @returns True if `value` has the sampling-completion shape; false otherwise
3551
3734
  *
3552
3735
  * @example
3553
3736
  * ```ts
@@ -3595,7 +3778,7 @@ function isMCPSampleResult(value) {
3595
3778
  *
3596
3779
  * @param value - The client's answer to check
3597
3780
  * @param request - The exact {@link MCPInputRequest} that was issued under the same key
3598
- * @returns `true` when the answer is legal for that request
3781
+ * @returns True if the answer is legal for that request; false otherwise
3599
3782
  *
3600
3783
  * @example
3601
3784
  * ```ts
@@ -3623,7 +3806,7 @@ function isMCPInputResponse(value, request) {
3623
3806
  * both must be present and valid. Total over hostile input.
3624
3807
  *
3625
3808
  * @param value - The unknown value to inspect
3626
- * @returns `true` when `value` is a valid input-required result
3809
+ * @returns True if `value` is a valid input-required result; false otherwise
3627
3810
  *
3628
3811
  * @example
3629
3812
  * ```ts
@@ -3660,7 +3843,7 @@ function isMCPInputResult(value) {
3660
3843
  * be a record. Total: any other input returns `false`.
3661
3844
  *
3662
3845
  * @param value - The already-parsed value to test
3663
- * @returns `true` when `value` is a valid JSON-RPC request
3846
+ * @returns True if `value` is a valid JSON-RPC request; false otherwise
3664
3847
  *
3665
3848
  * @example
3666
3849
  * ```ts
@@ -3687,7 +3870,7 @@ function isJSONRPCRequest(value) {
3687
3870
  * be a record. Total: any other input returns `false`.
3688
3871
  *
3689
3872
  * @param value - The already-parsed value to test
3690
- * @returns `true` when `value` is a valid JSON-RPC notification
3873
+ * @returns True if `value` is a valid JSON-RPC notification; false otherwise
3691
3874
  *
3692
3875
  * @example
3693
3876
  * ```ts
@@ -3713,7 +3896,7 @@ function isJSONRPCNotification(value) {
3713
3896
  * mutually exclusive, so a positive answer names exactly one arm. Total.
3714
3897
  *
3715
3898
  * @param value - The already-parsed value to test
3716
- * @returns `true` when `value` is a valid JSON-RPC request or notification
3899
+ * @returns True if `value` is a valid JSON-RPC request or notification; false otherwise
3717
3900
  */
3718
3901
  function isJSONRPCInvocation(value) {
3719
3902
  return isJSONRPCRequest(value) || isJSONRPCNotification(value);
@@ -3731,7 +3914,7 @@ function isJSONRPCInvocation(value) {
3731
3914
  * Total.
3732
3915
  *
3733
3916
  * @param value - The already-parsed value to test
3734
- * @returns `true` when `value` is a valid JSON-RPC result response
3917
+ * @returns True if `value` is a valid JSON-RPC result response; false otherwise
3735
3918
  *
3736
3919
  * @example
3737
3920
  * ```ts
@@ -3767,7 +3950,7 @@ function isJSONRPCResultResponse(value) {
3767
3950
  * itself the hostile step, and it is bounded here rather than allowed to escape. Total.
3768
3951
  *
3769
3952
  * @param value - The already-parsed value to test
3770
- * @returns `true` when `value` carries an integer `code` and a string `message`
3953
+ * @returns True if `value` carries an integer `code` and a string `message`; false otherwise
3771
3954
  *
3772
3955
  * @example
3773
3956
  * ```ts
@@ -3791,7 +3974,7 @@ function isJSONRPCError(value) {
3791
3974
  * `result`. `error` carries an integer `code` and a string `message`. Total.
3792
3975
  *
3793
3976
  * @param value - The already-parsed value to test
3794
- * @returns `true` when `value` is a valid JSON-RPC error response
3977
+ * @returns True if `value` is a valid JSON-RPC error response; false otherwise
3795
3978
  *
3796
3979
  * @example
3797
3980
  * ```ts
@@ -3815,7 +3998,7 @@ function isJSONRPCErrorResponse(value) {
3815
3998
  * The union of the mutually exclusive arms. Total.
3816
3999
  *
3817
4000
  * @param value - The already-parsed value to test
3818
- * @returns `true` when `value` is a valid JSON-RPC response
4001
+ * @returns True if `value` is a valid JSON-RPC response; false otherwise
3819
4002
  */
3820
4003
  function isJSONRPCResponse(value) {
3821
4004
  return isJSONRPCResultResponse(value) || isJSONRPCErrorResponse(value);
@@ -3828,7 +4011,7 @@ function isJSONRPCResponse(value) {
3828
4011
  * The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total.
3829
4012
  *
3830
4013
  * @param value - The already-parsed value to test
3831
- * @returns `true` when `value` is a valid JSON-RPC message
4014
+ * @returns True if `value` is a valid JSON-RPC message; false otherwise
3832
4015
  */
3833
4016
  function isJSONRPCMessage(value) {
3834
4017
  return isJSONRPCInvocation(value) || isJSONRPCResponse(value);
@@ -3837,7 +4020,7 @@ function isJSONRPCMessage(value) {
3837
4020
  * Determines whether a parsed value is an MCP `initialize` invocation.
3838
4021
  *
3839
4022
  * @param value - The already-parsed value to test
3840
- * @returns `true` when `value` is a valid `initialize` request or notification
4023
+ * @returns True if `value` is a valid `initialize` request or notification; false otherwise
3841
4024
  *
3842
4025
  * @example
3843
4026
  * ```ts
@@ -3860,7 +4043,7 @@ function isInitializeRequest(value) {
3860
4043
  * legacy dispatch. Total over hostile and malformed input.
3861
4044
  *
3862
4045
  * @param value - The already-parsed value to inspect
3863
- * @returns `true` when the value is an invocation carrying the reserved version key
4046
+ * @returns True if the value is an invocation carrying the reserved version key; false otherwise
3864
4047
  */
3865
4048
  function isModernRequest(value) {
3866
4049
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -3890,6 +4073,27 @@ function inferEra(version) {
3890
4073
  if (isMCPLegacyVersion(version)) return "legacy";
3891
4074
  }
3892
4075
  /**
4076
+ * Infers the wire era one invocation's own structure selects.
4077
+ *
4078
+ * @remarks
4079
+ * The STRUCTURAL read, distinct from {@link inferEra}'s read of a revision string: era is fixed
4080
+ * by the reserved modern metadata a request carries, so this answers for a message whose
4081
+ * revision has not been read and cannot answer `undefined` — every invocation took one of the
4082
+ * two published wire shapes. It is what an observation surface reports and what an ingress
4083
+ * routes on, so both derive it here rather than each spelling the ternary out.
4084
+ *
4085
+ * @param invocation - The invocation whose structure selects the era
4086
+ * @returns `'modern'` when the invocation carries the modern request shape, `'legacy'` otherwise
4087
+ *
4088
+ * @example
4089
+ * ```ts
4090
+ * inferRequestEra({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
4091
+ * ```
4092
+ */
4093
+ function inferRequestEra(invocation) {
4094
+ return isModernRequest(invocation) ? "modern" : "legacy";
4095
+ }
4096
+ /**
3893
4097
  * Infers the newest supported modern protocol revision present in a peer's offer.
3894
4098
  *
3895
4099
  * @param offered - The protocol revisions offered by the peer
@@ -3935,7 +4139,7 @@ function inferRequestVersion(message) {
3935
4139
  //#endregion
3936
4140
  //#region src/core/MCPMethodManager.ts
3937
4141
  /**
3938
- * The modern method registry an {@link import('./types.js').MCPServerInterface}
4142
+ * Holds the modern methods an {@link import('./types.js').MCPServerInterface}
3939
4143
  * dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
3940
4144
  * map rather than exposing one.
3941
4145
  *
@@ -3969,7 +4173,7 @@ var MCPMethodManager = class {
3969
4173
  //#endregion
3970
4174
  //#region src/core/MCPProgressReporter.ts
3971
4175
  /**
3972
- * A bounded, request-scoped progress handoff between one producer and one serial consumer.
4176
+ * Hands bounded, request-scoped progress from one producer to one serial consumer.
3973
4177
  *
3974
4178
  * The reporter holds at most one owned progress item. {@link report} applies backpressure until
3975
4179
  * {@link take} consumes that slot. It has no replay, queue, concurrent-consumer coordination,
@@ -4093,7 +4297,8 @@ var MCPProgressReporter = class {
4093
4297
  //#endregion
4094
4298
  //#region src/core/MCPStreamController.ts
4095
4299
  /**
4096
- * The one cancellation engine every modern held-open result leaves `MCPServer` through.
4300
+ * Provides the one cancellation engine every modern held-open result leaves `MCPServer`
4301
+ * through.
4097
4302
  *
4098
4303
  * @remarks
4099
4304
  * A native async generator decides cancellation with a QUEUE: `return()` and `throw()` wait
@@ -4290,7 +4495,7 @@ var MCPStreamController = class {
4290
4495
  //#endregion
4291
4496
  //#region src/core/MCPTextStreamController.ts
4292
4497
  /**
4293
- * The string-boundary mirror of a controlled held-open result — the same exchange, already
4498
+ * Mirrors a controlled held-open result at the string boundary — the same exchange, already
4294
4499
  * serialized.
4295
4500
  *
4296
4501
  * @remarks
@@ -4452,12 +4657,7 @@ var MCPLegacy = class {
4452
4657
  }
4453
4658
  async handle(message, options) {
4454
4659
  if (!isBoundedString(message, this.limit.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
4455
- let parsed;
4456
- try {
4457
- parsed = JSON.parse(message);
4458
- } catch {
4459
- return this.#options.dispatcher.handle(message, options);
4460
- }
4660
+ const parsed = (0, _orkestrel_contract.parseJSON)(message);
4461
4661
  if (isModernRequest(parsed) || !isJSONRPCInvocation(parsed)) return this.#options.dispatcher.handle(message, options);
4462
4662
  const answer = await this.#legacy(parsed, options);
4463
4663
  return answer === void 0 ? void 0 : JSON.stringify(answer);
@@ -4544,7 +4744,7 @@ var MCPLegacyClientTransport = class {
4544
4744
  if (requested !== void 0 && !isMCPLegacyVersion(requested)) throw new MCPError("Unsupported legacy protocol version", MCP_UNSUPPORTED_VERSION, { requested });
4545
4745
  this.#transport = transport;
4546
4746
  this.#client = options?.identity ?? {
4547
- name: "taverna",
4747
+ name: "@orkestrel/mcp",
4548
4748
  version: "1.0.0"
4549
4749
  };
4550
4750
  this.#capabilities = options?.capabilities ?? {};
@@ -4736,8 +4936,8 @@ var MCPLegacyClientTransport = class {
4736
4936
  //#endregion
4737
4937
  //#region src/core/MCPServer.ts
4738
4938
  /**
4739
- * A transport-agnostic Model Context Protocol server dispatches JSON-RPC 2.0
4740
- * requests over a live {@link ToolManagerInterface}, with NO transport coupling.
4939
+ * Dispatches JSON-RPC 2.0 requests over a live {@link ToolManagerInterface}, with NO
4940
+ * transport coupling.
4741
4941
  *
4742
4942
  * @remarks
4743
4943
  * - **`dispatch` and `handle`.** `dispatch(invocation)` runs an already-parsed invocation and
@@ -4814,8 +5014,21 @@ var MCPServer = class {
4814
5014
  if (decoded === void 0 || !("method" in decoded)) return buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request");
4815
5015
  return this.#dispatch(decoded, options);
4816
5016
  }
5017
+ async handle(message, options) {
5018
+ if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
5019
+ const parsed = (0, _orkestrel_contract.parseJSON)(message);
5020
+ if (parsed === void 0) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
5021
+ const decoded = parseJSONRPCMessage(parsed, {
5022
+ bytes: this.#limits.message,
5023
+ depth: this.#limits.depth
5024
+ });
5025
+ if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
5026
+ const answer = await this.#dispatch(decoded, options ?? {});
5027
+ if (answer === void 0) return void 0;
5028
+ return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
5029
+ }
4817
5030
  async #dispatch(invocation, options) {
4818
- this.#emitter.emit("request", invocation.method, invocation.id, "modern");
5031
+ this.#emitter.emit("request", invocation.method, invocation.id, inferRequestEra(invocation));
4819
5032
  if (invocation.id === void 0) return;
4820
5033
  const id = invocation.id;
4821
5034
  const metadata = invocation.params?.["_meta"];
@@ -4833,26 +5046,9 @@ var MCPServer = class {
4833
5046
  return this.#contain(error, id);
4834
5047
  }
4835
5048
  }
4836
- async handle(message, options) {
4837
- if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4838
- let parsed;
4839
- try {
4840
- parsed = JSON.parse(message);
4841
- } catch {
4842
- return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4843
- }
4844
- const decoded = parseJSONRPCMessage(parsed, {
4845
- bytes: this.#limits.message,
4846
- depth: this.#limits.depth
4847
- });
4848
- if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
4849
- const answer = await this.#dispatch(decoded, options ?? {});
4850
- if (answer === void 0) return void 0;
4851
- return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
4852
- }
4853
5049
  #register() {
4854
- this.#methods.add("server/discover", async (request, _options) => this.#discover(request));
4855
- this.#methods.add("tools/list", async (request, _options) => this.#list(request));
5050
+ this.#methods.add("server/discover", async (request) => this.#discover(request));
5051
+ this.#methods.add("tools/list", async (request) => this.#list(request));
4856
5052
  this.#methods.add("tools/call", async (request, options) => this.#call(request, options));
4857
5053
  this.#methods.add("subscriptions/listen", async (request, options) => this.#subscribe(request, options));
4858
5054
  const resources = this.#options.resources;
@@ -5066,20 +5262,20 @@ var MCPServer = class {
5066
5262
  async #defer(request, call, options) {
5067
5263
  const configured = this.#options.task;
5068
5264
  if (configured === void 0) return void 0;
5069
- const deferral = {
5265
+ const deferred = {
5070
5266
  request,
5071
5267
  call,
5072
5268
  tools: this.#options.tools
5073
5269
  };
5074
- const key = await configured.defer(deferral, options);
5270
+ const key = await configured.deferral(deferred, options);
5075
5271
  if ((0, _orkestrel_contract.isUndefined)(key)) return void 0;
5076
5272
  if (!(0, _orkestrel_contract.isString)(key) || key.length === 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server execution returned an invalid task key");
5077
5273
  const context = parseRequestContext(request, {
5078
5274
  bytes: this.#limits.message,
5079
5275
  depth: this.#limits.depth
5080
5276
  });
5081
- if (context === void 0 || !isTaskSupported(context.capabilities)) return buildJSONRPCError(request.id, MCP_MISSING_CAPABILITY, "Client does not support the required Tasks extension", { requiredCapabilities: { extensions: { [MCP_EXTENSION_TASKS]: {} } } });
5082
- const created = await configured.tasks.start(key, deferral, options);
5277
+ if (context === void 0 || !supportsTask(context.capabilities)) return buildJSONRPCError(request.id, MCP_MISSING_CAPABILITY, "Client does not support the required Tasks extension", { requiredCapabilities: { extensions: { [MCP_EXTENSION_TASKS]: {} } } });
5278
+ const created = await configured.tasks.start(key, deferred, options);
5083
5279
  const captured = snapshotJSON({
5084
5280
  resultType: "task",
5085
5281
  taskId: created.taskId,
@@ -5163,7 +5359,7 @@ var MCPServer = class {
5163
5359
  arguments: args
5164
5360
  }, options);
5165
5361
  if (selected === void 0) return void 0;
5166
- const round = this.#round(selected);
5362
+ const round = this.#ownRound(selected);
5167
5363
  const context = parseRequestContext(request, {
5168
5364
  bytes: this.#limits.message,
5169
5365
  depth: this.#limits.depth
@@ -5216,7 +5412,7 @@ var MCPServer = class {
5216
5412
  const state = parseMCPInputState(verified);
5217
5413
  if (state === void 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a malformed protected payload"), id);
5218
5414
  if (state.expiry <= Date.now() || state.id === id || state.version !== context.version || state.method !== request.method || state.name !== name || state.digest !== digest) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
5219
- const responses = this.#answers(state.requests, inputResponses);
5415
+ const responses = this.#checkAnswers(state.requests, inputResponses);
5220
5416
  if (responses === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an input response is missing or malformed");
5221
5417
  const principal = await configured.principal(request, options);
5222
5418
  if (!(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || state.principal !== principal) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
@@ -5229,13 +5425,13 @@ var MCPServer = class {
5229
5425
  }, options);
5230
5426
  if (state.expiry <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
5231
5427
  if (selected === void 0) return void 0;
5232
- const round = this.#round(selected);
5428
+ const round = this.#ownRound(selected);
5233
5429
  if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5234
5430
  const refusal = this.#gate(round, context, id);
5235
5431
  if (refusal !== void 0) return refusal;
5236
5432
  return this.#required(request, name, digest, round, principal, state.id, state.expiry);
5237
5433
  }
5238
- #answers(requests, responses) {
5434
+ #checkAnswers(requests, responses) {
5239
5435
  const answered = {};
5240
5436
  for (const [key, issued] of Object.entries(requests)) {
5241
5437
  const response = responses[key];
@@ -5244,7 +5440,7 @@ var MCPServer = class {
5244
5440
  }
5245
5441
  return Object.freeze(answered);
5246
5442
  }
5247
- #round(round) {
5443
+ #ownRound(round) {
5248
5444
  const owned = snapshotJSON(round, {
5249
5445
  bytes: this.#limits.content,
5250
5446
  keys: this.#limits.keys,
@@ -5332,7 +5528,7 @@ var MCPServer = class {
5332
5528
  }
5333
5529
  yield buildSubscriptionAcknowledgement(notifications, id);
5334
5530
  if (configured !== void 0) {
5335
- const iterator = (await configured.listen(notifications, options))[Symbol.asyncIterator]();
5531
+ const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
5336
5532
  options.signal.addEventListener("abort", () => void iterator.return?.(void 0)?.catch(() => void 0), { once: true });
5337
5533
  for (let next = await iterator.next(); next.done !== true; next = await iterator.next()) {
5338
5534
  const owned = parseJSONRPCMessage(next.value, {
@@ -5351,22 +5547,22 @@ var MCPServer = class {
5351
5547
  slot.abort();
5352
5548
  }
5353
5549
  }
5354
- #named(request) {
5550
+ #readTaskId(request) {
5355
5551
  const id = request.id;
5356
5552
  const context = parseRequestContext(request, {
5357
5553
  bytes: this.#limits.message,
5358
5554
  depth: this.#limits.depth
5359
5555
  });
5360
- if (context === void 0 || !isTaskSupported(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the tasks extension capability for this request", { requiredCapabilities: { extensions: { [MCP_EXTENSION_TASKS]: {} } } });
5556
+ if (context === void 0 || !supportsTask(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the tasks extension capability for this request", { requiredCapabilities: { extensions: { [MCP_EXTENSION_TASKS]: {} } } });
5361
5557
  const taskId = request.params?.["taskId"];
5362
5558
  if (!isBoundedString(taskId, this.#limits.state) || taskId.length === 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: a bounded string `taskId` is required");
5363
5559
  return taskId;
5364
5560
  }
5365
5561
  async #task(request, tasks, options) {
5366
5562
  const id = request.id;
5367
- const named = this.#named(request);
5368
- if (!(0, _orkestrel_contract.isString)(named)) return named;
5369
- const found = await tasks.task(named, options);
5563
+ const taskId = this.#readTaskId(request);
5564
+ if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
5565
+ const found = await tasks.task(taskId, options);
5370
5566
  if (found === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5371
5567
  const owned = snapshotJSON(found, {
5372
5568
  bytes: this.#limits.content,
@@ -5378,20 +5574,20 @@ var MCPServer = class {
5378
5574
  }
5379
5575
  async #update(request, tasks, options) {
5380
5576
  const id = request.id;
5381
- const named = this.#named(request);
5382
- if (!(0, _orkestrel_contract.isString)(named)) return named;
5577
+ const taskId = this.#readTaskId(request);
5578
+ if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
5383
5579
  const responses = request.params?.["inputResponses"];
5384
5580
  if (!(0, _orkestrel_contract.isRecord)(responses)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an `inputResponses` object is required");
5385
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5386
- await tasks.update(named, responses, options);
5581
+ if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5582
+ await tasks.update(taskId, responses, options);
5387
5583
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
5388
5584
  }
5389
5585
  async #abort(request, tasks, options) {
5390
5586
  const id = request.id;
5391
- const named = this.#named(request);
5392
- if (!(0, _orkestrel_contract.isString)(named)) return named;
5393
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5394
- await tasks.abort(named, options);
5587
+ const taskId = this.#readTaskId(request);
5588
+ if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
5589
+ if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5590
+ await tasks.abort(taskId, options);
5395
5591
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
5396
5592
  }
5397
5593
  #contain(error, id) {
@@ -5441,8 +5637,8 @@ var MCPServer = class {
5441
5637
  //#endregion
5442
5638
  //#region src/core/MCPTaskClient.ts
5443
5639
  /**
5444
- * The CLIENT half of the stable Tasks extension — the `tasks/*` methods over one
5445
- * correlated-request door, exposed as an {@link import('./types.js').MCPClientInterface}'s
5640
+ * Issues the `tasks/*` methods over one correlated-request door — the CLIENT half of the
5641
+ * stable Tasks extension, exposed as an {@link import('./types.js').MCPClientInterface}'s
5446
5642
  * `tasks`.
5447
5643
  *
5448
5644
  * @remarks
@@ -5501,9 +5697,9 @@ var MCPTaskClient = class {
5501
5697
  //#endregion
5502
5698
  //#region src/core/MCPClient.ts
5503
5699
  /**
5504
- * A transport-agnostic Model Context Protocol CLIENT connects to a REMOTE MCP server
5505
- * over an injected {@link MCPClientTransportInterface}, negotiates the modern revision, and
5506
- * exposes the server's tools as local {@link ToolInterface}s an agent can run.
5700
+ * Connects to a REMOTE MCP server over any injected {@link MCPMessageTransportInterface},
5701
+ * negotiates the modern revision, and exposes the server's tools as local
5702
+ * {@link ToolInterface}s an agent can run.
5507
5703
  *
5508
5704
  * @remarks
5509
5705
  * - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
@@ -5594,7 +5790,7 @@ var MCPClient = class {
5594
5790
  });
5595
5791
  this.#transport = options.transport;
5596
5792
  this.#identity = options.identity ?? {
5597
- name: "taverna",
5793
+ name: "@orkestrel/mcp",
5598
5794
  version: "1.0.0"
5599
5795
  };
5600
5796
  this.#capabilities = options.capabilities ?? {};
@@ -5735,18 +5931,19 @@ var MCPClient = class {
5735
5931
  } }
5736
5932
  }
5737
5933
  };
5738
- const subscription = {
5739
- queue: [],
5740
- capacity
5741
- };
5742
5934
  const abort = this.#abortSubscription.bind(this, id, signal);
5743
5935
  signal.addEventListener("abort", abort, { once: true });
5744
5936
  this.#pending.set(id, {
5745
5937
  method,
5746
5938
  signal,
5747
5939
  abort,
5748
- subscription
5940
+ subscription: {
5941
+ queue: [],
5942
+ capacity
5943
+ }
5749
5944
  });
5945
+ const subscription = this.#pending.get(id)?.subscription;
5946
+ if (subscription === void 0) throw new Error("MCP subscription state is missing");
5750
5947
  this.#transport.send(request).catch((error) => this.#settle(id, error, true));
5751
5948
  try {
5752
5949
  for (;;) {
@@ -6072,13 +6269,222 @@ var MCPClient = class {
6072
6269
  }
6073
6270
  };
6074
6271
  //#endregion
6272
+ //#region src/core/transports/HTTPClientTransport.ts
6273
+ /**
6274
+ * Drives a REMOTE Streamable-HTTP MCP server over `fetch` — a CLIENT
6275
+ * {@link MCPMessageTransportInterface} for the Model Context Protocol, the egress mirror of
6276
+ * the server's `createMCPRoutes`.
6277
+ *
6278
+ * @remarks
6279
+ * - **One class, both faces.** It touches `fetch`, `Response`, `AbortController`,
6280
+ * `AbortSignal`, and `WeakMap` alone, so it is host-independent and lives in core. Each
6281
+ * environment face publishes its own `createHTTPClientTransport` over it —
6282
+ * `@orkestrel/mcp/browser` and `@orkestrel/mcp/server` — and both factories return this
6283
+ * class, so a reply reaches a page and a Node process through the same decode.
6284
+ * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
6285
+ * message to `options.url` with `content-type: application/json` and an
6286
+ * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
6287
+ * answer with either framing) — plus any `options.headers` (for example, an `Authorization`
6288
+ * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
6289
+ * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
6290
+ * to.
6291
+ * - **Both reply framings.** A `200` with an `application/json` body is parsed with
6292
+ * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
6293
+ * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
6294
+ * readEventStream}) — the inverse of the server's `createStream` seam, so the wire
6295
+ * round-trips. A `202`
6296
+ * Accepted (a notification) carries no body and emits nothing.
6297
+ * - **Session and protocol headers.** `start()` is a no-op (a
6298
+ * request/response transport opens no long-lived connection). The
6299
+ * `mcp-session-id` response header, when a STATEFUL server sends one (on
6300
+ * `initialize`), is captured into `session` and then ECHOED as the
6301
+ * `mcp-session-id` request header on every SUBSEQUENT request — so an
6302
+ * `MCPClient` passes a stateful server's session validation. The
6303
+ * initialize result's `protocolVersion` is likewise captured, but only
6304
+ * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
6305
+ * subsequent legacy requests. Modern requests instead derive protocol and method
6306
+ * headers from the message, plus the name header only for `tools/call` — carried in the
6307
+ * protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
6308
+ * Before initialize returns, neither captured legacy header is sent.
6309
+ * `close()` clears the captured protocol so a reconnect's `initialize`
6310
+ * POST is headerless; the captured `session` persists across `close()`.
6311
+ * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
6312
+ * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
6313
+ * never ends would otherwise outlive the transport, with nothing left able to reach it. The
6314
+ * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
6315
+ * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
6316
+ * - **Total at the boundary, and a non-success reply REJECTS.** Every reply is narrowed
6317
+ * (`parseJSONRPCMessage`, the SSE decoder). A non-message success reply is dropped, never
6318
+ * asserted. A non-success reply that carries no valid JSON-RPC message rejects `send` with
6319
+ * an error naming its HTTP status and body shape — the peer answered, and answering the
6320
+ * caller's request with silence would leave it waiting out its own deadline for a failure
6321
+ * the transport already read. A valid JSON-RPC error body is emitted at any HTTP status,
6322
+ * because the protocol carries that outcome in band. A `fetch` or decode failure on a
6323
+ * success response surfaces on the `error` event rather than escaping `send`.
6324
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); fires
6325
+ * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
6326
+ *
6327
+ * @example
6328
+ * ```ts
6329
+ * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
6330
+ * const client = new MCPClient({ transport })
6331
+ * await client.connect()
6332
+ * ```
6333
+ */
6334
+ var HTTPClientTransport = class {
6335
+ #emitter;
6336
+ #url;
6337
+ #headers;
6338
+ #fetch;
6339
+ #timeout;
6340
+ #pending = /* @__PURE__ */ new Set();
6341
+ #parameters = /* @__PURE__ */ new Map();
6342
+ #stamps = /* @__PURE__ */ new WeakMap();
6343
+ #session = void 0;
6344
+ #protocol = void 0;
6345
+ #generation = 0;
6346
+ #closed = false;
6347
+ constructor(options) {
6348
+ this.#emitter = new _orkestrel_emitter.Emitter();
6349
+ this.#url = options.url;
6350
+ this.#headers = options.headers ?? {};
6351
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
6352
+ this.#timeout = options.timeout;
6353
+ }
6354
+ get emitter() {
6355
+ return this.#emitter;
6356
+ }
6357
+ get session() {
6358
+ return this.#session;
6359
+ }
6360
+ get duplex() {
6361
+ return false;
6362
+ }
6363
+ async start() {
6364
+ this.#closed = false;
6365
+ }
6366
+ async send(message) {
6367
+ this.#stamp(message);
6368
+ const request = new AbortController();
6369
+ this.#pending.add(request);
6370
+ try {
6371
+ await this.#exchange(message, request.signal);
6372
+ } finally {
6373
+ this.#pending.delete(request);
6374
+ }
6375
+ }
6376
+ async close() {
6377
+ if (this.#closed) return;
6378
+ this.#closed = true;
6379
+ for (const request of this.#pending) request.abort();
6380
+ this.#pending.clear();
6381
+ this.#protocol = void 0;
6382
+ this.#emitter.emit("close");
6383
+ }
6384
+ #stamp(message) {
6385
+ if (!isModernRequest(message) || message.method !== "tools/list") return;
6386
+ if (message.params?.["cursor"] === void 0) this.#generation += 1;
6387
+ this.#stamps.set(message, this.#generation);
6388
+ }
6389
+ async #exchange(message, signal) {
6390
+ let response;
6391
+ try {
6392
+ response = await this.#fetch(this.#url, {
6393
+ method: "POST",
6394
+ headers: {
6395
+ "content-type": "application/json",
6396
+ accept: "application/json, text/event-stream",
6397
+ ...this.#session === void 0 ? {} : { [MCP_SESSION_HEADER]: this.#session },
6398
+ ...this.#buildHeaders(message),
6399
+ ...this.#headers
6400
+ },
6401
+ body: JSON.stringify(message),
6402
+ signal: this.#timeout === void 0 ? signal : AbortSignal.any([signal, AbortSignal.timeout(this.#timeout)])
6403
+ });
6404
+ } catch (error) {
6405
+ this.#emitter.emit("error", error);
6406
+ return;
6407
+ }
6408
+ const session = response.headers.get(MCP_SESSION_HEADER);
6409
+ if (session !== null) this.#session = session;
6410
+ await this.#deliver(response, message);
6411
+ }
6412
+ #buildHeaders(message) {
6413
+ if (isModernRequest(message)) {
6414
+ const version = inferRequestVersion(message);
6415
+ const name = message.params?.["name"];
6416
+ return {
6417
+ ...version === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version },
6418
+ [MCP_METHOD_HEADER]: message.method,
6419
+ ...message.method === "tools/call" && (0, _orkestrel_contract.isString)(name) ? {
6420
+ [MCP_NAME_HEADER]: encodeSentinel(name),
6421
+ ...buildHeaderProjection(this.#parameters.get(name) ?? [], message.params?.["arguments"])
6422
+ } : {}
6423
+ };
6424
+ }
6425
+ return this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol };
6426
+ }
6427
+ async #deliver(response, sent) {
6428
+ if (response.status === 202) return;
6429
+ const type = response.headers.get("content-type") ?? "";
6430
+ let messages = [];
6431
+ let failure;
6432
+ try {
6433
+ if (type.includes("text/event-stream")) messages = await readEventStream(response);
6434
+ else if (type.includes("application/json")) {
6435
+ const message = parseJSONRPCMessage(await response.json());
6436
+ if (message !== void 0) messages = [message];
6437
+ }
6438
+ } catch (error) {
6439
+ failure = { error };
6440
+ }
6441
+ for (const message of messages) this.#capture(message, sent);
6442
+ if (!response.ok && messages.length === 0) throw buildResponseError(response, type);
6443
+ if (failure !== void 0) this.#emitter.emit("error", failure.error);
6444
+ }
6445
+ #capture(message, sent) {
6446
+ if (isJSONRPCResponse(message) && (0, _orkestrel_contract.isRecord)(message.result) && isMCPVersion(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
6447
+ this.#emitter.emit("message", this.#select(message, sent));
6448
+ }
6449
+ #select(message, sent) {
6450
+ if (!isModernRequest(sent) || sent.method !== "tools/list") return message;
6451
+ if (!isJSONRPCResponse(message) || message.error !== void 0) return message;
6452
+ const result = message.result;
6453
+ const listed = (0, _orkestrel_contract.isRecord)(result) ? result["tools"] : void 0;
6454
+ if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isArray)(listed)) return message;
6455
+ const current = this.#stamps.get(sent) === this.#generation;
6456
+ if (current && sent.params?.["cursor"] === void 0) this.#parameters.clear();
6457
+ const kept = [];
6458
+ for (const tool of listed) {
6459
+ if (!(0, _orkestrel_contract.isRecord)(tool) || !(0, _orkestrel_contract.isString)(tool["name"])) {
6460
+ kept.push(tool);
6461
+ continue;
6462
+ }
6463
+ const parameters = buildHeaderParameters(tool["inputSchema"]);
6464
+ if (parameters === void 0) {
6465
+ this.#emitter.emit("error", /* @__PURE__ */ new Error(`MCP tool '${tool["name"]}' is excluded from tools/list: its inputSchema carries an invalid x-mcp-header annotation`));
6466
+ continue;
6467
+ }
6468
+ if (current) this.#parameters.set(tool["name"], parameters);
6469
+ kept.push(tool);
6470
+ }
6471
+ return {
6472
+ ...message,
6473
+ result: {
6474
+ ...result,
6475
+ tools: kept
6476
+ }
6477
+ };
6478
+ }
6479
+ };
6480
+ //#endregion
6075
6481
  //#region src/core/factories.ts
6076
6482
  /**
6077
6483
  * Creates a transport-agnostic Model Context Protocol server — exposes a live
6078
6484
  * {@link import('@orkestrel/tool').ToolManagerInterface} and an optional
6079
6485
  * {@link import('./types.js').MCPResourceManagerInterface},
6080
6486
  * {@link import('./types.js').MCPPromptManagerInterface}, and
6081
- * {@link import('./types.js').MCPCompletionManagerInterface} over JSON-RPC 2.0.
6487
+ * {@link import('./types.js').MCPCompletionInterface} over JSON-RPC 2.0.
6082
6488
  *
6083
6489
  * @remarks
6084
6490
  * Pump raw message strings through `handle` (parse → dispatch → serialize) from a
@@ -6130,7 +6536,7 @@ function createMCPLegacy(server) {
6130
6536
  }
6131
6537
  /**
6132
6538
  * Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
6133
- * MCP server over an injected {@link import('./types.js').MCPClientTransportInterface},
6539
+ * MCP server over an injected {@link import('./types.js').MCPMessageTransportInterface},
6134
6540
  * negotiates the modern revision through `server/discover`, and exposes the server's tools as local
6135
6541
  * {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
6136
6542
  *
@@ -6161,7 +6567,7 @@ function createMCPLegacy(server) {
6161
6567
  * })
6162
6568
  * await client.connect()
6163
6569
  * agent.context.tools.add(await client.tools()) // give the agent the remote tools
6164
- * const value = await client.call('search', { query: 'mcp' })
6570
+ * const outcome = await client.call('search', { query: 'mcp' })
6165
6571
  * ```
6166
6572
  */
6167
6573
  function createMCPClient(options) {
@@ -6187,7 +6593,7 @@ function createMCPLegacyClientTransport(transport, options) {
6187
6593
  }
6188
6594
  /**
6189
6595
  * Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
6190
- * channel) into a {@link MCPClientTransportInterface} — the additive bridge that lets
6596
+ * channel) into a {@link MCPMessageTransportInterface} — the additive bridge that lets
6191
6597
  * `createMCPClient` run over the new port without any change to `MCPClient`'s
6192
6598
  * existing shape.
6193
6599
  *
@@ -6210,7 +6616,7 @@ function createMCPLegacyClientTransport(transport, options) {
6210
6616
  * capable emitter for `bindClient` to push onto.
6211
6617
  *
6212
6618
  * @param transport - The duplex channel to adapt
6213
- * @returns A {@link MCPClientTransportInterface} `createMCPClient` can drive
6619
+ * @returns A {@link MCPMessageTransportInterface} `createMCPClient` can drive
6214
6620
  *
6215
6621
  * @example
6216
6622
  * ```ts
@@ -6241,6 +6647,7 @@ exports.DEFAULT_MCP_LIMITS = DEFAULT_MCP_LIMITS;
6241
6647
  exports.DEFAULT_MCP_REQUEST_TIMEOUT = DEFAULT_MCP_REQUEST_TIMEOUT;
6242
6648
  exports.DEFAULT_MCP_SUBSCRIPTION_CAPACITY = DEFAULT_MCP_SUBSCRIPTION_CAPACITY;
6243
6649
  exports.EMPTY_MCP_ARGUMENTS = EMPTY_MCP_ARGUMENTS;
6650
+ exports.HTTPClientTransport = HTTPClientTransport;
6244
6651
  exports.JSONRPC_INTERNAL_ERROR = JSONRPC_INTERNAL_ERROR;
6245
6652
  exports.JSONRPC_INVALID_PARAMS = JSONRPC_INVALID_PARAMS;
6246
6653
  exports.JSONRPC_INVALID_REQUEST = JSONRPC_INVALID_REQUEST;
@@ -6268,12 +6675,17 @@ exports.MCP_META_CLIENT = MCP_META_CLIENT;
6268
6675
  exports.MCP_META_SERVER = MCP_META_SERVER;
6269
6676
  exports.MCP_META_SUBSCRIPTION = MCP_META_SUBSCRIPTION;
6270
6677
  exports.MCP_META_VERSION = MCP_META_VERSION;
6678
+ exports.MCP_METHOD_HEADER = MCP_METHOD_HEADER;
6271
6679
  exports.MCP_MISSING_CAPABILITY = MCP_MISSING_CAPABILITY;
6272
6680
  exports.MCP_MODERN_VERSION = MCP_MODERN_VERSION;
6681
+ exports.MCP_NAME_HEADER = MCP_NAME_HEADER;
6273
6682
  exports.MCP_PARAM_PREFIX = MCP_PARAM_PREFIX;
6683
+ exports.MCP_PROTOCOL_VERSION_HEADER = MCP_PROTOCOL_VERSION_HEADER;
6274
6684
  exports.MCP_SENTINEL_PREFIX = MCP_SENTINEL_PREFIX;
6275
6685
  exports.MCP_SENTINEL_SUFFIX = MCP_SENTINEL_SUFFIX;
6686
+ exports.MCP_SESSION_HEADER = MCP_SESSION_HEADER;
6276
6687
  exports.MCP_UNSUPPORTED_VERSION = MCP_UNSUPPORTED_VERSION;
6688
+ exports.MCP_WEBSOCKET_SUBPROTOCOL = MCP_WEBSOCKET_SUBPROTOCOL;
6277
6689
  exports.SUPPORTED_LEGACY_PROTOCOL_VERSIONS = SUPPORTED_LEGACY_PROTOCOL_VERSIONS;
6278
6690
  exports.SUPPORTED_MCP_VERSIONS = SUPPORTED_MCP_VERSIONS;
6279
6691
  exports.SUPPORTED_MODERN_PROTOCOL_VERSIONS = SUPPORTED_MODERN_PROTOCOL_VERSIONS;
@@ -6290,6 +6702,7 @@ exports.buildJSONRPCResult = buildJSONRPCResult;
6290
6702
  exports.buildMethodOptions = buildMethodOptions;
6291
6703
  exports.buildModernResult = buildModernResult;
6292
6704
  exports.buildProgressNotification = buildProgressNotification;
6705
+ exports.buildResponseError = buildResponseError;
6293
6706
  exports.buildSubscriptionAcknowledgement = buildSubscriptionAcknowledgement;
6294
6707
  exports.buildSubscriptionFilter = buildSubscriptionFilter;
6295
6708
  exports.buildSubscriptionResult = buildSubscriptionResult;
@@ -6303,13 +6716,16 @@ exports.createMCPLegacy = createMCPLegacy;
6303
6716
  exports.createMCPLegacyClientTransport = createMCPLegacyClientTransport;
6304
6717
  exports.createMCPServer = createMCPServer;
6305
6718
  exports.decodeBoundedMessage = decodeBoundedMessage;
6719
+ exports.decodeEvent = decodeEvent;
6306
6720
  exports.decodeSentinel = decodeSentinel;
6721
+ exports.deliverMessage = deliverMessage;
6307
6722
  exports.digestJSON = digestJSON;
6308
6723
  exports.encodeSentinel = encodeSentinel;
6309
6724
  exports.extractContentText = extractContentText;
6310
6725
  exports.extractHeaderAnnotations = extractHeaderAnnotations;
6311
6726
  exports.extractToolSchema = extractToolSchema;
6312
6727
  exports.inferEra = inferEra;
6728
+ exports.inferRequestEra = inferRequestEra;
6313
6729
  exports.inferRequestVersion = inferRequestVersion;
6314
6730
  exports.inferVersion = inferVersion;
6315
6731
  exports.isAbsoluteURI = isAbsoluteURI;
@@ -6317,7 +6733,6 @@ exports.isBoundedJSON = isBoundedJSON;
6317
6733
  exports.isBoundedString = isBoundedString;
6318
6734
  exports.isElicitContent = isElicitContent;
6319
6735
  exports.isFieldToken = isFieldToken;
6320
- exports.isFormElicitationSupported = isFormElicitationSupported;
6321
6736
  exports.isInitializeRequest = isInitializeRequest;
6322
6737
  exports.isJSONObject = isJSONObject;
6323
6738
  exports.isJSONRPCError = isJSONRPCError;
@@ -6392,7 +6807,6 @@ exports.isModernRequest = isModernRequest;
6392
6807
  exports.isRFC3339Date = isRFC3339Date;
6393
6808
  exports.isRFC3339DateTime = isRFC3339DateTime;
6394
6809
  exports.isStandardBase64 = isStandardBase64;
6395
- exports.isTaskSupported = isTaskSupported;
6396
6810
  exports.legacyInvocationToModern = legacyInvocationToModern;
6397
6811
  exports.legacyResultToModern = legacyResultToModern;
6398
6812
  exports.matchesResultType = matchesResultType;
@@ -6403,11 +6817,14 @@ exports.parseJSONRPCMessage = parseJSONRPCMessage;
6403
6817
  exports.parseMCPInputState = parseMCPInputState;
6404
6818
  exports.parseRequestContext = parseRequestContext;
6405
6819
  exports.readCancelledId = readCancelledId;
6820
+ exports.readEventStream = readEventStream;
6406
6821
  exports.renderHeaderValue = renderHeaderValue;
6407
6822
  exports.sendStream = sendStream;
6408
6823
  exports.serializeJSON = serializeJSON;
6409
6824
  exports.snapshotJSON = snapshotJSON;
6410
6825
  exports.snapshotToolResult = snapshotToolResult;
6411
6826
  exports.stampSubscriptionNotification = stampSubscriptionNotification;
6827
+ exports.supportsFormElicitation = supportsFormElicitation;
6828
+ exports.supportsTask = supportsTask;
6412
6829
 
6413
6830
  //# sourceMappingURL=index.cjs.map