@orkestrel/mcp 0.0.26 → 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,10 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
+ let _orkestrel_codec = require("@orkestrel/codec");
4
+ let _orkestrel_sse = require("@orkestrel/sse");
3
5
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
6
  let _orkestrel_tool = require("@orkestrel/tool");
5
7
  //#region src/core/constants.ts
6
8
  /**
7
- * The revision offered and defaulted to in the legacy `initialize` handshake.
9
+ * Names the revision offered and defaulted to in the legacy `initialize` handshake.
8
10
  *
9
11
  * @remarks
10
12
  * This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
@@ -12,12 +14,12 @@ let _orkestrel_tool = require("@orkestrel/tool");
12
14
  * it is asking to negotiate a revision with no negotiation.
13
15
  */
14
16
  var MCP_HANDSHAKE_VERSION = "2025-11-25";
15
- /** 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. */
16
18
  var MCP_FALLBACK_VERSION = "2025-06-18";
17
- /** The modern revision offered by an unpinned client during discovery. */
19
+ /** Names the modern revision offered by an unpinned client during discovery. */
18
20
  var MCP_MODERN_VERSION = "2026-07-28";
19
21
  /**
20
- * The modern MCP protocol revisions a bare server accepts and advertises.
22
+ * Lists the modern MCP protocol revisions a bare server accepts and advertises.
21
23
  *
22
24
  * @remarks
23
25
  * Frozen in discovery-advertisement order. Legacy revisions are absent because
@@ -25,22 +27,25 @@ var MCP_MODERN_VERSION = "2026-07-28";
25
27
  * decorator own them.
26
28
  */
27
29
  var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
28
- /** The protocol revisions accepted by the optional legacy decorator. */
30
+ /** Lists the protocol revisions accepted by the optional legacy decorator. */
29
31
  var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
30
- /** 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
+ */
31
36
  var SUPPORTED_MCP_VERSIONS = Object.freeze([...SUPPORTED_MODERN_PROTOCOL_VERSIONS, ...SUPPORTED_LEGACY_PROTOCOL_VERSIONS]);
32
- /** Reserved modern `_meta` key carrying the request's protocol revision. */
37
+ /** Names the reserved modern `_meta` key carrying the request's protocol revision. */
33
38
  var MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
34
- /** 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. */
35
40
  var MCP_META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
36
- /** Reserved modern `_meta` key carrying the optional client identity. */
41
+ /** Names the reserved modern `_meta` key carrying the optional client identity. */
37
42
  var MCP_META_CLIENT = "io.modelcontextprotocol/clientInfo";
38
- /** Reserved modern `_meta` key carrying the server identity on results. */
43
+ /** Names the reserved modern `_meta` key carrying the server identity on results. */
39
44
  var MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
40
- /** Reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
45
+ /** Names the reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
41
46
  var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
42
47
  /**
43
- * The reserved extension key identifying the stable Tasks extension.
48
+ * Names the reserved extension key identifying the stable Tasks extension.
44
49
  *
45
50
  * @remarks
46
51
  * The ONE spelling of it in this package, and the identity of the immutable snapshot dated
@@ -50,10 +55,107 @@ var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
50
55
  * the extension defines no options, so presence is the entire declaration.
51
56
  */
52
57
  var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
53
- /** MCP reserved error: required HTTP metadata does not match the request body. */
58
+ /**
59
+ * Names the opening marker of the Base64 sentinel a standard MCP header value travels in.
60
+ *
61
+ * @remarks
62
+ * The markers are LOWERCASE and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
63
+ * their ONE spelling in this package: {@link import('@orkestrel/mcp').encodeSentinel} builds a
64
+ * sentinel from them and {@link import('@orkestrel/mcp').decodeSentinel} recognizes one by
65
+ * them, so the two directions cannot drift apart.
66
+ */
67
+ var MCP_SENTINEL_PREFIX = "=?base64?";
68
+ /** Names the closing marker of the Base64 sentinel a standard MCP header value travels in. */
69
+ var MCP_SENTINEL_SUFFIX = "?=";
70
+ /**
71
+ * Names the request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
72
+ *
73
+ * @remarks
74
+ * The full field name is this prefix followed by the annotation's own value verbatim, so
75
+ * `x-mcp-header: 'Region'` becomes `Mcp-Param-Region`. HTTP field names are case-insensitive,
76
+ * which is why {@link MCP_HEADER_ANNOTATION} values are unique case-insensitively within one
77
+ * `inputSchema`.
78
+ */
79
+ var MCP_PARAM_PREFIX = "Mcp-Param-";
80
+ /**
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.
115
+ *
116
+ * @remarks
117
+ * It is valid ONLY on a primitive property schema statically reachable from the `inputSchema`
118
+ * root through `properties` keys alone. An occurrence anywhere else — under `items`, a
119
+ * composition or conditional keyword, or a `$ref` target — makes the whole tool definition
120
+ * invalid, which is what {@link import('@orkestrel/mcp').buildHeaderParameters} decides.
121
+ */
122
+ var MCP_HEADER_ANNOTATION = "x-mcp-header";
123
+ /**
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.
141
+ *
142
+ * @remarks
143
+ * The HTTP POST handler reads a called tool's {@link MCP_HEADER_ANNOTATION} annotations by
144
+ * dispatching `tools/list` fresh on every `tools/call`, following `nextCursor` until the
145
+ * named tool is found or the answer carries no cursor. The walk is bounded because its cost
146
+ * is paid per call: at a page size of 100 this bound reaches 800 definitions, and a consumer
147
+ * whose replacement `tools/list` pages more finely than that pays the extra dispatches on
148
+ * every call it serves. The built-in listing answers the whole registry on one page and
149
+ * never reaches the second. A definition further in than the walk reaches reads as no
150
+ * definition, so its {@link MCP_PARAM_PREFIX} headers are forwarded untouched — the same
151
+ * answer a name no served definition annotates receives.
152
+ */
153
+ var MCP_LOOKUP_PAGES = 8;
154
+ /** Names the MCP reserved error for required HTTP metadata that does not match the request body. */
54
155
  var MCP_HEADER_MISMATCH = -32020;
55
156
  /**
56
- * 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.
57
159
  *
58
160
  * @remarks
59
161
  * The GENERIC code for the whole condition, not one capability's code. This server answers
@@ -66,10 +168,10 @@ var MCP_HEADER_MISMATCH = -32020;
66
168
  * schema is what a peer implements against.
67
169
  */
68
170
  var MCP_MISSING_CAPABILITY = -32021;
69
- /** MCP reserved error: a request names an unsupported protocol revision. */
171
+ /** Names the MCP reserved error for a request naming an unsupported protocol revision. */
70
172
  var MCP_UNSUPPORTED_VERSION = -32022;
71
173
  /**
72
- * Default modern result freshness lifetime in milliseconds.
174
+ * Sets the default modern result freshness lifetime in milliseconds.
73
175
  *
74
176
  * @remarks
75
177
  * `ttlMs` is required on cacheable results, while zero means immediately stale
@@ -77,16 +179,18 @@ var MCP_UNSUPPORTED_VERSION = -32022;
77
179
  */
78
180
  var DEFAULT_MCP_CACHE_TTL = 6e4;
79
181
  /**
80
- * 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.
81
184
  *
82
185
  * @remarks
83
186
  * One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
84
187
  * extension-rich modern metadata and signed multi-round state; four MiB admits substantial
85
188
  * JSON tool output without allowing an unconfigured service to serialize arbitrary process
86
- * memory; 64 metadata keys admits the reserved keys plus many extensions; 128 concurrent
87
- * streams admits a busy service while bounding retained producers; depth 32 admits ordinary
88
- * JSON documents while rejecting stack-hostile nesting. Frozen so callers cannot alter the
89
- * defaults observed by later servers.
189
+ * memory; 64 keys admits `_meta`'s reserved keys plus many extensions, and bounds a produced
190
+ * result's breadth by the same leaf; 128 concurrent streams admits a busy service while
191
+ * bounding retained producers; depth 32 admits ordinary JSON documents while rejecting
192
+ * stack-hostile nesting. Frozen so callers cannot alter the defaults observed by later
193
+ * servers.
90
194
  */
91
195
  var DEFAULT_MCP_LIMITS = Object.freeze({
92
196
  message: 1048576,
@@ -98,7 +202,7 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
98
202
  depth: 32
99
203
  });
100
204
  /**
101
- * 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.
102
206
  *
103
207
  * @remarks
104
208
  * Frozen and null-prototype, and SHARED: two calls that name no `arguments` receive the same
@@ -112,16 +216,17 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
112
216
  * `arguments.constructor` is `undefined` here rather than a function.
113
217
  */
114
218
  var EMPTY_MCP_ARGUMENTS = Object.freeze(Object.create(null));
115
- /** 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). */
116
220
  var JSONRPC_PARSE_ERROR = -32700;
117
- /** 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. */
118
222
  var JSONRPC_INVALID_REQUEST = -32600;
119
- /** 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. */
120
224
  var JSONRPC_METHOD_NOT_FOUND = -32601;
121
- /** 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. */
122
226
  var JSONRPC_INVALID_PARAMS = -32602;
123
227
  /**
124
- * 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.
125
230
  *
126
231
  * @remarks
127
232
  * The code every MODERN internal fault answers with — a provider, handler, continuation,
@@ -131,7 +236,7 @@ var JSONRPC_INVALID_PARAMS = -32602;
131
236
  */
132
237
  var JSONRPC_INTERNAL_ERROR = -32603;
133
238
  /**
134
- * 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).
135
240
  *
136
241
  * @remarks
137
242
  * Retained for the LEGACY branch alone. A modern fault answers
@@ -139,21 +244,27 @@ var JSONRPC_INTERNAL_ERROR = -32603;
139
244
  * already characterized against it.
140
245
  */
141
246
  var JSONRPC_SERVER_ERROR = -32e3;
142
- /** The default client name reported in the MCP `initialize` handshake (`clientInfo.name`). */
143
- var DEFAULT_MCP_CLIENT_NAME = "taverna";
144
- /** 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
+ */
145
256
  var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
146
257
  /**
147
- * 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`
148
259
  * is unset — a request the remote server does not answer within it rejects.
149
260
  */
150
261
  var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
151
- /** 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. */
152
263
  var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
153
264
  //#endregion
154
265
  //#region src/core/errors.ts
155
266
  /**
156
- * 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
157
268
  * optional structured context.
158
269
  *
159
270
  * @remarks
@@ -196,7 +307,7 @@ var MCPError = class extends Error {
196
307
  * Determines whether an unknown value is an {@link MCPError}.
197
308
  *
198
309
  * @param value - The unknown value to inspect
199
- * @returns `true` only when the value is an `MCPError`
310
+ * @returns True if the value is an `MCPError`; false otherwise
200
311
  *
201
312
  * @example
202
313
  * ```ts
@@ -432,49 +543,48 @@ function parseRequestContext(value, limits = {
432
543
  * This parser does not open the opaque continuation carrier; the configured
433
544
  * continuation port performs that boundary first. The protected
434
545
  * payload binds the authenticated principal, absolute expiry, ORIGINAL request id, version,
435
- * method, server-assigned key, tool name, argument digest, the exact issued elicitation
436
- * schema, and optional application state. Every member is required except application state:
437
- * a payload missing its schema cannot have its accepted response enforced, so it is refused
438
- * rather than admitted unenforced. Total over malformed or hostile input.
546
+ * method, the exact round that was issued, tool name, argument digest, and optional
547
+ * application state. Every member is required except application state: a payload missing its
548
+ * round cannot have the client's answers enforced, so it is refused rather than admitted
549
+ * unenforced. An EMPTY round is refused for the same reason — a retry against it would answer
550
+ * no question at all. Total over malformed or hostile input.
439
551
  *
440
552
  * @param value - The opened canonical continuation value to parse
441
553
  * @returns The protected input state, or `undefined` when malformed
442
554
  *
443
555
  * @example
444
556
  * ```ts
445
- * parseMCPInputState('{"principal":"user-1","expiry":2000,"id":1,"version":"2026-07-28","method":"tools/call","key":"k","name":"reply","digest":"abc","schema":{"type":"object","properties":{}}}')
557
+ * parseMCPInputState('{"principal":"user-1","expiry":2000,"id":1,"version":"2026-07-28","method":"tools/call","requests":{"k":{"method":"roots/list"}},"name":"reply","digest":"abc"}')
446
558
  * ```
447
559
  */
448
560
  function parseMCPInputState(value) {
449
561
  try {
450
562
  if (!(0, _orkestrel_contract.isString)(value)) return void 0;
451
- const parsed = JSON.parse(value);
563
+ const parsed = (0, _orkestrel_contract.parseJSON)(value);
452
564
  if (!(0, _orkestrel_contract.isRecord)(parsed)) return void 0;
453
565
  const principal = parsed["principal"];
454
566
  const expiry = parsed["expiry"];
455
567
  const id = parsed["id"];
456
568
  const version = parsed["version"];
457
569
  const method = parsed["method"];
458
- const key = parsed["key"];
570
+ const requests = parsed["requests"];
459
571
  const name = parsed["name"];
460
572
  const digest = parsed["digest"];
461
- const schema = parsed["schema"];
462
573
  const state = parsed["state"];
463
574
  if (!(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || !(0, _orkestrel_contract.isNumber)(expiry) || !Number.isFinite(expiry)) return;
464
575
  if (!isJSONRPCId(id)) return void 0;
465
- if (!(0, _orkestrel_contract.isString)(version) || !(0, _orkestrel_contract.isString)(method) || !(0, _orkestrel_contract.isString)(key) || !(0, _orkestrel_contract.isString)(name)) return;
576
+ if (!(0, _orkestrel_contract.isString)(version) || !(0, _orkestrel_contract.isString)(method) || !(0, _orkestrel_contract.isString)(name)) return void 0;
466
577
  if (!(0, _orkestrel_contract.isString)(digest) || !(0, _orkestrel_contract.isUndefined)(state) && !(0, _orkestrel_contract.isJSONValue)(state)) return void 0;
467
- if (!isMCPElicitSchema(schema)) return void 0;
578
+ if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
468
579
  return {
469
580
  principal,
470
581
  expiry,
471
582
  id,
472
583
  version,
473
584
  method,
474
- key,
585
+ requests,
475
586
  name,
476
587
  digest,
477
- schema,
478
588
  ...(0, _orkestrel_contract.isUndefined)(state) ? {} : { state }
479
589
  };
480
590
  } catch {
@@ -492,15 +602,15 @@ function parseMCPInputState(value) {
492
602
  * does not authorize a form request. Total over hostile input.
493
603
  *
494
604
  * @param value - The client capability record to inspect
495
- * @returns `true` when form-mode elicitation is declared
605
+ * @returns True if form-mode elicitation is declared; false otherwise
496
606
  *
497
607
  * @example
498
608
  * ```ts
499
- * isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
500
- * isFormElicitationSupported({ elicitation: { url: {} } }) // false
609
+ * supportsFormElicitation({ elicitation: {} }) // true — implicit form mode
610
+ * supportsFormElicitation({ elicitation: { url: {} } }) // false
501
611
  * ```
502
612
  */
503
- function isFormElicitationSupported(value) {
613
+ function supportsFormElicitation(value) {
504
614
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
505
615
  if (!owned.success) return false;
506
616
  try {
@@ -513,6 +623,68 @@ function isFormElicitationSupported(value) {
513
623
  }
514
624
  }
515
625
  /**
626
+ * Computes the capabilities one round of input requests needs and the client did not declare.
627
+ *
628
+ * @remarks
629
+ * The protocol's rule is about SENDING: a server never issues a request kind the client's
630
+ * declared capabilities exclude. So this reads the round rather than the method, and it
631
+ * answers with the refusal's own payload — the `requiredCapabilities` record a
632
+ * `MissingRequiredClientCapability` error carries, keyed by each missing capability, in the
633
+ * `ClientCapabilities` shape the schema defines rather than as a list of names.
634
+ *
635
+ * Each kind maps to one declaration: `sampling/createMessage` to `sampling`, `roots/list` to
636
+ * `roots`, a form elicitation to what {@link supportsFormElicitation} accepts, and a
637
+ * URL-mode elicitation to a record-valued `elicitation.url`. A request this package cannot
638
+ * recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
639
+ * has already refused the round it would have travelled in. Total over hostile input.
640
+ *
641
+ * The `elicitation` value names the ARM the round needs, so a client can act on the refusal
642
+ * by declaring exactly what the payload asks for. A missing URL arm answers `{ url: {} }`, a
643
+ * missing form arm answers the empty record this package reads as form-only, and a round
644
+ * needing both answers `{ form: {}, url: {} }`. An empty record for a URL round would name
645
+ * the declaration a URL-capable client already sent, and refuse the identical round again.
646
+ *
647
+ * @param requests - The round the server is about to issue
648
+ * @param capabilities - The client capability record the request declared
649
+ * @returns The missing capabilities, or `undefined` when the client declared every one
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * computeMissingCapabilities({ answer: { method: 'roots/list' } }, {}) // { roots: {} }
654
+ * computeMissingCapabilities({ answer: { method: 'roots/list' } }, { roots: {} }) // undefined
655
+ * ```
656
+ */
657
+ function computeMissingCapabilities(requests, capabilities) {
658
+ const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(capabilities));
659
+ const declared = owned.success ? owned.value : {};
660
+ const missing = {};
661
+ let formUndeclared = false;
662
+ let urlUndeclared = false;
663
+ for (const request of Object.values(requests)) {
664
+ if (request.method === "sampling/createMessage") {
665
+ if (!(0, _orkestrel_contract.isRecord)(declared["sampling"])) missing["sampling"] = {};
666
+ continue;
667
+ }
668
+ if (request.method === "roots/list") {
669
+ if (!(0, _orkestrel_contract.isRecord)(declared["roots"])) missing["roots"] = {};
670
+ continue;
671
+ }
672
+ const elicitation = declared["elicitation"];
673
+ if (request.params.mode === "url") {
674
+ if (!(0, _orkestrel_contract.isRecord)(elicitation) || !(0, _orkestrel_contract.isRecord)(elicitation["url"])) urlUndeclared = true;
675
+ continue;
676
+ }
677
+ if (!supportsFormElicitation(declared)) formUndeclared = true;
678
+ }
679
+ if (formUndeclared && !urlUndeclared) missing["elicitation"] = {};
680
+ if (urlUndeclared && !formUndeclared) missing["elicitation"] = { url: {} };
681
+ if (formUndeclared && urlUndeclared) missing["elicitation"] = {
682
+ form: {},
683
+ url: {}
684
+ };
685
+ return Object.keys(missing).length === 0 ? void 0 : Object.freeze(missing);
686
+ }
687
+ /**
516
688
  * Determines whether a client capability record declares the stable Tasks extension.
517
689
  *
518
690
  * @remarks
@@ -529,16 +701,16 @@ function isFormElicitationSupported(value) {
529
701
  * the request in hand. Total over hostile input.
530
702
  *
531
703
  * @param value - The client capability record to inspect
532
- * @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
533
705
  *
534
706
  * @example
535
707
  * ```ts
536
- * isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
537
- * isTaskSupported({ extensions: {} }) // false — the key is the declaration
538
- * 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
539
711
  * ```
540
712
  */
541
- function isTaskSupported(value) {
713
+ function supportsTask(value) {
542
714
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
543
715
  if (!owned.success) return false;
544
716
  try {
@@ -780,7 +952,7 @@ async function digestJSON(value, limits) {
780
952
  const serialized = serializeJSON(value, limits);
781
953
  if (serialized === void 0) return void 0;
782
954
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized));
783
- return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
955
+ return (0, _orkestrel_codec.encodeHex)(new Uint8Array(digest));
784
956
  }
785
957
  /**
786
958
  * Builds one official progress notification for the original request stream.
@@ -815,7 +987,7 @@ function buildProgressNotification(token, progress) {
815
987
  * rather than as a violation.
816
988
  *
817
989
  * Only write one on a carrier that accepts a client-initiated notification — see
818
- * {@link import('./types.js').MCPClientTransportInterface.duplex}. On Streamable HTTP the
990
+ * {@link import('./types.js').MCPMessageTransportInterface.duplex}. On Streamable HTTP the
819
991
  * dated revision defines no such frame, and closing the response stream is the
820
992
  * cancellation signal instead.
821
993
  *
@@ -854,7 +1026,7 @@ function buildCancelledNotification(id, reason) {
854
1026
  *
855
1027
  * @param method - The method the pending request was issued for
856
1028
  * @param resultType - The unknown `resultType` the peer answered with
857
- * @returns Whether that method may legally answer with that `resultType`
1029
+ * @returns True if that method may legally answer with that `resultType`; false otherwise
858
1030
  *
859
1031
  * @example
860
1032
  * ```ts
@@ -1209,7 +1381,7 @@ function buildSubscriptionFilter(requested, supported, enabled = false) {
1209
1381
  *
1210
1382
  * @param notification - The server notification offered by the configured producer
1211
1383
  * @param filter - The filter acknowledged to the client
1212
- * @returns `true` when the notification belongs on this subscription stream
1384
+ * @returns True if the notification belongs on this subscription stream; false otherwise
1213
1385
  */
1214
1386
  function matchesSubscriptionNotification(notification, filter) {
1215
1387
  if (notification.method === "notifications/tools/list_changed") return filter.toolsListChanged === true;
@@ -1331,7 +1503,7 @@ function buildInitializeResult(name, version, requested) {
1331
1503
  *
1332
1504
  * @remarks
1333
1505
  * The bound is checked FIRST, against the raw string, so an oversized message is never
1334
- * `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
1335
1507
  * the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
1336
1508
  * value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
1337
1509
  * exactly the same thing with each of them: nothing, and let
@@ -1351,8 +1523,444 @@ function buildInitializeResult(name, version, requested) {
1351
1523
  */
1352
1524
  function decodeBoundedMessage(message, limits) {
1353
1525
  if (!isBoundedString(message, limits.bytes)) return void 0;
1354
- const parsed = (0, _orkestrel_contract.attempt)(() => JSON.parse(message));
1355
- 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}`);
1649
+ }
1650
+ /**
1651
+ * Reads the value one standard MCP request header carries, decoding the Base64 sentinel.
1652
+ *
1653
+ * @remarks
1654
+ * The sentinel format is `=?base64?{Base64OfUTF8}?=`, spelled once as
1655
+ * {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX} and read from there by both
1656
+ * directions of the codec.
1657
+ * The markers alone decide whether a value is a sentinel: a value carrying the prefix and the
1658
+ * suffix is one, and its payload is then held to `decodeBase64` from `@orkestrel/codec` — the
1659
+ * canonical RFC 4648 § 4 grammar, which admits exactly one spelling per byte sequence — and to
1660
+ * well-formed UTF-8. A payload leaving a non-zero bit in the sextet its padding discards is a
1661
+ * second spelling of a byte, so it is refused: `=?base64?QR==?=` reaches for the byte
1662
+ * `=?base64?QQ==?=` spells canonically, and only the canonical spelling decodes. A malformed
1663
+ * payload answers `undefined` rather than falling back to the literal, because the protocol
1664
+ * requires a server to REJECT invalid characters, and a fallback would admit the very value
1665
+ * the rule exists to refuse. A value missing either marker is a literal and comes back
1666
+ * unchanged.
1667
+ *
1668
+ * `decodeUTF8` from `@orkestrel/codec` reads the bytes back as text: strict RFC 3629, where an
1669
+ * overlong, an encoded surrogate, a code point past U+10FFFF, and a truncated sequence each
1670
+ * answer `undefined` rather than a replacement character, and total, so the refusal arrives as
1671
+ * that value instead of as a throw. It also keeps a leading U+FEFF as a character of the
1672
+ * value, where the platform decoder consumes it as a byte order mark — which is what lets a
1673
+ * value leading with U+FEFF survive {@link encodeSentinel} and come back whole.
1674
+ *
1675
+ * {@link import('./validators.js').isStandardBase64} is a wider and separate rule: it names
1676
+ * JSON Schema `byte` membership for the blob, image, and audio content a peer sends, where
1677
+ * this package receives liberally. It does not govern this payload.
1678
+ *
1679
+ * Optional whitespace is excluded first, per RFC 9110 § 5.5: a recipient parses a field value
1680
+ * with its surrounding spaces and horizontal tabs removed, so a peer that padded a plain value
1681
+ * still matches the body. A value whose own leading or trailing whitespace is significant
1682
+ * cannot survive that, which is what {@link encodeSentinel} encodes it for.
1683
+ *
1684
+ * Total — never throws, whatever the input.
1685
+ *
1686
+ * @param value - The raw header field value the peer sent
1687
+ * @returns The carried value, or `undefined` when the sentinel's payload is invalid
1688
+ *
1689
+ * @example
1690
+ * ```ts
1691
+ * decodeSentinel('=?base64?Y2Fmw6k=?=') // 'café'
1692
+ * decodeSentinel(' search ') // 'search' — optional whitespace excluded
1693
+ * decodeSentinel('=?base64?SGVsbG8?=') // undefined — invalid padding
1694
+ * decodeSentinel('=?base64?QR==?=') // undefined — a non-canonical spelling
1695
+ * ```
1696
+ */
1697
+ function decodeSentinel(value) {
1698
+ const field = value.replace(/^[ \t]+|[ \t]+$/g, "");
1699
+ if (!(field.length >= 11 && field.startsWith("=?base64?") && field.endsWith("?="))) return field;
1700
+ const payload = field.slice(MCP_SENTINEL_PREFIX.length, field.length - 2);
1701
+ const bytes = (0, _orkestrel_codec.decodeBase64)(payload);
1702
+ if (bytes === void 0) return void 0;
1703
+ return (0, _orkestrel_codec.decodeUTF8)(bytes);
1704
+ }
1705
+ /**
1706
+ * Builds the wire form one standard MCP request header value must travel as.
1707
+ *
1708
+ * @remarks
1709
+ * The exact inverse of {@link decodeSentinel}, and its membership rule is stated as that
1710
+ * inverse rather than as a second list that could drift: a value travels LITERALLY when it is
1711
+ * plain printable ASCII — every code point in `U+0020`–`U+007E`, the RFC 9110 field-value
1712
+ * range this package admits — and {@link decodeSentinel} gives it back unchanged. Every other
1713
+ * value travels wrapped in {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX}, the
1714
+ * same markers the decode recognizes a sentinel by. `encodeBase64` from `@orkestrel/codec`
1715
+ * spells the payload, so the wire form carries the canonical spelling {@link decodeSentinel}
1716
+ * accepts.
1717
+ *
1718
+ * That one rule covers each row of the protocol's encoding table. A non-ASCII value and a
1719
+ * value carrying a control character fail the ASCII test. A value with leading or trailing
1720
+ * whitespace comes back trimmed, so it fails the round trip. A value already wearing the
1721
+ * sentinel markers decodes to something else, or to nothing, so it fails the round trip too
1722
+ * and is encoded rather than read back as a sentinel it never was.
1723
+ *
1724
+ * The bytes come from the platform `TextEncoder`, not from codec's `encodeUTF8`, and that is a
1725
+ * ruling rather than an oversight. `TextEncoder` is total: it spells ill-formed text — a lone
1726
+ * surrogate, which has no UTF-8 spelling — with the replacement character, so this function
1727
+ * answers a `string` for every input. `encodeUTF8` refuses that text with `undefined`, which
1728
+ * would widen this return to `string | undefined` and oblige every header projection to handle
1729
+ * a value it cannot send. The decode side carries no such tension, so it reads back through
1730
+ * codec's strict `decodeUTF8`.
1731
+ *
1732
+ * @param value - The value the header must carry
1733
+ * @returns The literal value, or its Base64 sentinel form
1734
+ *
1735
+ * @example
1736
+ * ```ts
1737
+ * encodeSentinel('search') // 'search'
1738
+ * encodeSentinel('café') // '=?base64?Y2Fmw6k=?='
1739
+ * ```
1740
+ */
1741
+ function encodeSentinel(value) {
1742
+ if (/^[ -~]*$/.test(value) && decodeSentinel(value) === value) return value;
1743
+ return `${MCP_SENTINEL_PREFIX}${(0, _orkestrel_codec.encodeBase64)(new TextEncoder().encode(value))}?=`;
1744
+ }
1745
+ /**
1746
+ * Counts every {@link MCP_HEADER_ANNOTATION} key one JSON value carries, at any position.
1747
+ *
1748
+ * @remarks
1749
+ * The companion of {@link extractHeaderAnnotations}, which reads only the annotations a
1750
+ * `properties` chain reaches. Comparing the two answers is how
1751
+ * {@link buildHeaderParameters} decides reachability without a second walk that would have
1752
+ * to re-state which JSON Schema keywords are traversable: an annotation the reachable walk
1753
+ * did not read is one sitting under `items`, a composition or conditional keyword, a `$ref`
1754
+ * target, or any other position, and the protocol makes the whole tool definition invalid for
1755
+ * it.
1756
+ *
1757
+ * Iterative and ancestor-tracked, so a deeply nested or self-referential value terminates
1758
+ * rather than exhausting the stack. Total — never throws, whatever the input.
1759
+ *
1760
+ * @param value - The value to scan, normally a tool's `inputSchema`
1761
+ * @returns How many annotation keys the value carries
1762
+ *
1763
+ * @example
1764
+ * ```ts
1765
+ * countHeaderAnnotations({ properties: { region: { 'x-mcp-header': 'Region' } } }) // 1
1766
+ * ```
1767
+ */
1768
+ function countHeaderAnnotations(value) {
1769
+ let total = 0;
1770
+ const seen = /* @__PURE__ */ new Set();
1771
+ const pending = [value];
1772
+ while (pending.length > 0) {
1773
+ const node = pending.pop();
1774
+ if ((0, _orkestrel_contract.isArray)(node)) {
1775
+ if (seen.has(node)) continue;
1776
+ seen.add(node);
1777
+ for (const item of node) pending.push(item);
1778
+ continue;
1779
+ }
1780
+ if (!(0, _orkestrel_contract.isRecord)(node) || seen.has(node)) continue;
1781
+ seen.add(node);
1782
+ for (const [key, member] of Object.entries(node)) if (key === "x-mcp-header") total += 1;
1783
+ else pending.push(member);
1784
+ }
1785
+ return total;
1786
+ }
1787
+ /**
1788
+ * Reads every `x-mcp-header` annotation reachable from a schema node through `properties`.
1789
+ *
1790
+ * @remarks
1791
+ * Reachability is the protocol's own rule: an annotation counts only where a chain of
1792
+ * `properties` keys leads to it from the `inputSchema` root, so `path` is both the schema
1793
+ * position and the position the call's `arguments` carry the value at. A property named
1794
+ * `items` is reachable like any other, because the chain is read by key POSITION rather than
1795
+ * by key name.
1796
+ *
1797
+ * `undefined` means the definition is invalid rather than empty: a reachable annotation whose
1798
+ * value is not an {@link import('./validators.js').isFieldToken} token, one sitting on the
1799
+ * schema ROOT (which is no property), one on a leaf whose declared type is not an
1800
+ * {@link import('./validators.js').isMCPHeaderPrimitive} primitive, or a chain deeper than
1801
+ * `DEFAULT_MCP_LIMITS.depth` — which is also what makes a self-referential schema terminate.
1802
+ * A node that is not a record carries nothing and answers an empty list, because a leaf the
1803
+ * walk cannot read is not a violation.
1804
+ *
1805
+ * @param schema - The schema node to read
1806
+ * @param path - The `properties` keys already traversed; the root is called with `[]`
1807
+ * @returns The annotations reachable from this node, or `undefined` when one is invalid
1808
+ *
1809
+ * @example
1810
+ * ```ts
1811
+ * extractHeaderAnnotations({ properties: { region: { type: 'string', 'x-mcp-header': 'Region' } } }, [])
1812
+ * // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
1813
+ * ```
1814
+ */
1815
+ function extractHeaderAnnotations(schema, path) {
1816
+ if (path.length > DEFAULT_MCP_LIMITS.depth) return void 0;
1817
+ if (!(0, _orkestrel_contract.isRecord)(schema)) return [];
1818
+ const found = [];
1819
+ const annotation = schema[MCP_HEADER_ANNOTATION];
1820
+ if (annotation !== void 0) {
1821
+ if (path.length === 0 || !isFieldToken(annotation)) return void 0;
1822
+ const primitive = schema["type"];
1823
+ if (!isMCPHeaderPrimitive(primitive)) return void 0;
1824
+ found.push({
1825
+ name: annotation,
1826
+ path,
1827
+ primitive
1828
+ });
1829
+ }
1830
+ const properties = schema["properties"];
1831
+ if ((0, _orkestrel_contract.isRecord)(properties)) for (const [key, leaf] of Object.entries(properties)) {
1832
+ const nested = extractHeaderAnnotations(leaf, [...path, key]);
1833
+ if (nested === void 0) return void 0;
1834
+ found.push(...nested);
1835
+ }
1836
+ return found;
1837
+ }
1838
+ /**
1839
+ * Builds the `x-mcp-header` projections one tool's `inputSchema` declares.
1840
+ *
1841
+ * @remarks
1842
+ * The single decision both sides of the protocol make about an annotated tool: an HTTP
1843
+ * CLIENT excludes a definition this refuses from the `tools/list` result it delivers, and a
1844
+ * SERVER recognizes exactly the `Mcp-Param-*` names this returns for its own definitions.
1845
+ *
1846
+ * `undefined` means the definition is invalid, and every rule the protocol states produces
1847
+ * it: a value that is not an RFC 9110 token, a non-primitive or untyped annotated leaf, a
1848
+ * name repeated case-insensitively within the schema, an annotation the `properties` chain
1849
+ * does not reach, and a schema that is not a record at all. An empty list is the valid answer
1850
+ * for a schema carrying no annotation.
1851
+ *
1852
+ * Total — never throws, and a cyclic or stack-hostile schema is refused rather than followed.
1853
+ *
1854
+ * @param schema - The tool's advertised `inputSchema`
1855
+ * @returns The declared projections, or `undefined` when the definition is invalid
1856
+ *
1857
+ * @example
1858
+ * ```ts
1859
+ * buildHeaderParameters({
1860
+ * type: 'object',
1861
+ * properties: { region: { type: 'string', 'x-mcp-header': 'Region' } },
1862
+ * }) // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
1863
+ * ```
1864
+ */
1865
+ function buildHeaderParameters(schema) {
1866
+ if (!(0, _orkestrel_contract.isRecord)(schema)) return void 0;
1867
+ const found = extractHeaderAnnotations(schema, []);
1868
+ if (found === void 0 || found.length !== countHeaderAnnotations(schema)) return void 0;
1869
+ const taken = /* @__PURE__ */ new Set();
1870
+ for (const parameter of found) {
1871
+ const key = parameter.name.toLowerCase();
1872
+ if (taken.has(key)) return void 0;
1873
+ taken.add(key);
1874
+ }
1875
+ return found;
1876
+ }
1877
+ /**
1878
+ * Renders one projected argument as the text its `Mcp-Param-*` header carries.
1879
+ *
1880
+ * @remarks
1881
+ * The protocol's conversion table, and the ONE place it is stated: a string travels as
1882
+ * itself, an integer in decimal, and a boolean as lowercase `true` or `false`. The value's
1883
+ * runtime shape must match the leaf's declared type, so a schema that declares `integer` and
1884
+ * an argument that supplies a string, a fraction, or a magnitude outside the IEEE 754 safe
1885
+ * range carries NOTHING — a header that cannot round-trip the body value is worse than an
1886
+ * absent one, and the tool's own argument validation owns the disagreement.
1887
+ *
1888
+ * @param value - The argument value read at the parameter's path
1889
+ * @param primitive - The leaf's declared type
1890
+ * @returns The header text, or `undefined` when the value cannot travel as that type
1891
+ *
1892
+ * @example
1893
+ * ```ts
1894
+ * renderHeaderValue(42, 'integer') // '42'
1895
+ * renderHeaderValue(false, 'boolean') // 'false'
1896
+ * ```
1897
+ */
1898
+ function renderHeaderValue(value, primitive) {
1899
+ if (primitive === "string") return (0, _orkestrel_contract.isString)(value) ? value : void 0;
1900
+ if (primitive === "boolean") return (0, _orkestrel_contract.isBoolean)(value) ? value ? "true" : "false" : void 0;
1901
+ return (0, _orkestrel_contract.isNumber)(value) && Number.isSafeInteger(value) ? String(value) : void 0;
1902
+ }
1903
+ /**
1904
+ * Builds the `Mcp-Param-*` request headers one `tools/call` carries.
1905
+ *
1906
+ * @remarks
1907
+ * The projection SEP-2243 requires of an HTTP client, and the same derivation a server runs
1908
+ * to know what the request must carry. Each parameter's value is read at its exact
1909
+ * property path in the call's own `arguments`; an absent or `null` value omits its header
1910
+ * entirely, which is the protocol's distinction between "not supplied" and "supplied empty".
1911
+ * The rendered text then travels through {@link encodeSentinel}, so a value carrying
1912
+ * non-ASCII, control, or edge whitespace characters reaches the peer intact.
1913
+ *
1914
+ * @param parameters - The projections the tool's `inputSchema` declares
1915
+ * @param values - The call's `arguments` record
1916
+ * @returns The header field names and values, empty when nothing projects
1917
+ *
1918
+ * @example
1919
+ * ```ts
1920
+ * buildHeaderProjection(
1921
+ * [{ name: 'Region', path: ['region'], primitive: 'string' }],
1922
+ * { region: 'us-west1' },
1923
+ * ) // → { 'Mcp-Param-Region': 'us-west1' }
1924
+ * ```
1925
+ */
1926
+ function buildHeaderProjection(parameters, values) {
1927
+ const headers = {};
1928
+ for (const parameter of parameters) {
1929
+ let carried = values;
1930
+ for (const key of parameter.path) carried = (0, _orkestrel_contract.isRecord)(carried) ? carried[key] : void 0;
1931
+ if (carried === void 0 || carried === null) continue;
1932
+ const text = renderHeaderValue(carried, parameter.primitive);
1933
+ if (text !== void 0) headers[`${MCP_PARAM_PREFIX}${parameter.name}`] = encodeSentinel(text);
1934
+ }
1935
+ return headers;
1936
+ }
1937
+ /**
1938
+ * Reads one named tool's advertised `inputSchema` out of a `tools/list` answer.
1939
+ *
1940
+ * @remarks
1941
+ * The answer is read as foreign data end to end — a dispatched response, an error envelope,
1942
+ * and a result whose `tools` member is absent or is not an array all read as "no schema"
1943
+ * rather than as a fault. That is what lets the HTTP POST handler ask its own dispatcher
1944
+ * which `Mcp-Param-*` names a `tools/call` may carry without narrowing anything first.
1945
+ *
1946
+ * @param response - The `tools/list` answer, normally a {@link JSONRPCResponse}
1947
+ * @param name - The tool whose schema to read
1948
+ * @returns The advertised `inputSchema`, or `undefined` when the answer carries none
1949
+ *
1950
+ * @example
1951
+ * ```ts
1952
+ * extractToolSchema(answer, 'search')?.['properties']
1953
+ * ```
1954
+ */
1955
+ function extractToolSchema(response, name) {
1956
+ const result = (0, _orkestrel_contract.isRecord)(response) ? response["result"] : void 0;
1957
+ const tools = (0, _orkestrel_contract.isRecord)(result) ? result["tools"] : void 0;
1958
+ if (!(0, _orkestrel_contract.isArray)(tools)) return void 0;
1959
+ for (const tool of tools) {
1960
+ if (!(0, _orkestrel_contract.isRecord)(tool) || tool["name"] !== name) continue;
1961
+ const schema = tool["inputSchema"];
1962
+ return (0, _orkestrel_contract.isRecord)(schema) ? schema : void 0;
1963
+ }
1356
1964
  }
1357
1965
  /**
1358
1966
  * Reads the request id an inbound `notifications/cancelled` names — the inverse of
@@ -1528,7 +2136,7 @@ function bindServer(server, transport) {
1528
2136
  * @remarks
1529
2137
  * The client's outbound writes flow through `client.transport.send` — its existing,
1530
2138
  * unmodified request/response correlation — so `client` must have been constructed
1531
- * with a {@link import('./types.js').MCPClientTransportInterface} that itself carries
2139
+ * with a {@link import('./types.js').MCPMessageTransportInterface} that itself carries
1532
2140
  * the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
1533
2141
  * the additive factory that adapts an {@link MCPTransportInterface} into that shape);
1534
2142
  * this binder then completes the inbound half by decoding each message and pushing it
@@ -1641,7 +2249,7 @@ function isMCPResultMetaObject(value) {
1641
2249
  * {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
1642
2250
  *
1643
2251
  * @param value - The unknown value to inspect
1644
- * @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
1645
2253
  *
1646
2254
  * @example
1647
2255
  * ```ts
@@ -1664,12 +2272,54 @@ function isMCPLoggingLevel(value) {
1664
2272
  * Determines whether a value is standard padded base64 as required by JSON Schema `byte` format.
1665
2273
  *
1666
2274
  * @param value - The unknown value to inspect
1667
- * @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
1668
2276
  */
1669
2277
  function isStandardBase64(value) {
1670
2278
  return (0, _orkestrel_contract.isString)(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
1671
2279
  }
1672
2280
  /**
2281
+ * Determines whether a value is one RFC 9110 field token.
2282
+ *
2283
+ * @remarks
2284
+ * A token is one or more `tchar`: the ASCII letters, the digits, and
2285
+ * ``!#$%&'*+-.^_`|~``. That set already excludes the empty string, whitespace, a colon, a
2286
+ * control character, and every non-ASCII code point, so it is the whole constraint an
2287
+ * `x-mcp-header` annotation's value must satisfy — the value is appended verbatim to
2288
+ * {@link MCP_PARAM_PREFIX} and must survive as an HTTP field name.
2289
+ *
2290
+ * @param value - The unknown value to inspect
2291
+ * @returns True if the value is a non-empty RFC 9110 token; false otherwise
2292
+ *
2293
+ * @example
2294
+ * ```ts
2295
+ * isFieldToken('Region') // true
2296
+ * isFieldToken('My Region') // false
2297
+ * ```
2298
+ */
2299
+ function isFieldToken(value) {
2300
+ return (0, _orkestrel_contract.isString)(value) && /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value);
2301
+ }
2302
+ /**
2303
+ * Determines whether a value is a JSON Schema type an `x-mcp-header` annotation may sit on.
2304
+ *
2305
+ * @remarks
2306
+ * `number` is refused deliberately: a JSON number has no interoperable decimal text form, so
2307
+ * a header carrying one could not be compared with the body byte for byte. `integer` renders
2308
+ * exactly, and the server compares it numerically.
2309
+ *
2310
+ * @param value - The unknown value to inspect
2311
+ * @returns True if the value is one of `'string'`, `'integer'`, or `'boolean'`; false otherwise
2312
+ *
2313
+ * @example
2314
+ * ```ts
2315
+ * isMCPHeaderPrimitive('integer') // true
2316
+ * isMCPHeaderPrimitive('number') // false
2317
+ * ```
2318
+ */
2319
+ function isMCPHeaderPrimitive(value) {
2320
+ return value === "string" || value === "integer" || value === "boolean";
2321
+ }
2322
+ /**
1673
2323
  * Determines whether a value is one absolute URI under RFC 3986 syntax.
1674
2324
  *
1675
2325
  * @remarks
@@ -1677,7 +2327,7 @@ function isStandardBase64(value) {
1677
2327
  * scheme allowlist. Component scanning is bounded by the input length.
1678
2328
  *
1679
2329
  * @param value - The unknown value to inspect
1680
- * @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
1681
2331
  */
1682
2332
  function isAbsoluteURI(value) {
1683
2333
  if (!(0, _orkestrel_contract.isString)(value) || value.length === 0) return false;
@@ -1773,7 +2423,7 @@ function isAbsoluteURI(value) {
1773
2423
  * refuse. It is a SYNTAX guard: no time zone, locale, calendar era, or leap second applies.
1774
2424
  *
1775
2425
  * @param value - The unknown value to inspect
1776
- * @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
1777
2427
  *
1778
2428
  * @example
1779
2429
  * ```ts
@@ -1806,7 +2456,7 @@ function isRFC3339Date(value) {
1806
2456
  * second.
1807
2457
  *
1808
2458
  * @param value - The unknown value to inspect
1809
- * @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
1810
2460
  *
1811
2461
  * @example
1812
2462
  * ```ts
@@ -1824,7 +2474,7 @@ function isRFC3339DateTime(value) {
1824
2474
  * Determines whether a value is one exact finite MCP progress payload.
1825
2475
  *
1826
2476
  * @param value - The unknown value to inspect
1827
- * @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
1828
2478
  */
1829
2479
  function isMCPProgress(value) {
1830
2480
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -1843,7 +2493,7 @@ function isMCPProgress(value) {
1843
2493
  * Determines whether a value carries valid dated-schema MCP content annotations.
1844
2494
  *
1845
2495
  * @param value - The unknown value to inspect
1846
- * @returns Whether the value is valid MCP annotations
2496
+ * @returns True if the value is valid MCP annotations; false otherwise
1847
2497
  */
1848
2498
  function isMCPAnnotations(value) {
1849
2499
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -1864,7 +2514,7 @@ function isMCPAnnotations(value) {
1864
2514
  * Determines whether a value is one exact dated-schema MCP icon.
1865
2515
  *
1866
2516
  * @param value - The unknown value to inspect
1867
- * @returns Whether the value is a valid MCP icon
2517
+ * @returns True if the value is a valid MCP icon; false otherwise
1868
2518
  */
1869
2519
  function isMCPIcon(value) {
1870
2520
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -1955,7 +2605,7 @@ function isMCPServerCapabilities(value) {
1955
2605
  * Determines whether a value is embedded textual MCP resource contents.
1956
2606
  *
1957
2607
  * @param value - The unknown value to inspect
1958
- * @returns Whether the value is embedded textual resource contents
2608
+ * @returns True if the value is embedded textual resource contents; false otherwise
1959
2609
  */
1960
2610
  function isMCPTextResource(value) {
1961
2611
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -1974,7 +2624,7 @@ function isMCPTextResource(value) {
1974
2624
  * Determines whether a value is embedded blob MCP resource contents.
1975
2625
  *
1976
2626
  * @param value - The unknown value to inspect
1977
- * @returns Whether the value is embedded blob resource contents
2627
+ * @returns True if the value is embedded blob resource contents; false otherwise
1978
2628
  */
1979
2629
  function isMCPBlobResource(value) {
1980
2630
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -1993,7 +2643,7 @@ function isMCPBlobResource(value) {
1993
2643
  * Determines whether a value is one `resources/list` descriptor.
1994
2644
  *
1995
2645
  * @param value - The unknown value to inspect
1996
- * @returns Whether the value is a valid resource descriptor
2646
+ * @returns True if the value is a valid resource descriptor; false otherwise
1997
2647
  */
1998
2648
  function isMCPResource(value) {
1999
2649
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2015,7 +2665,7 @@ function isMCPResource(value) {
2015
2665
  * level belong to the consumer-supplied resource manager; this package projects the string.
2016
2666
  *
2017
2667
  * @param value - The unknown value to inspect
2018
- * @returns Whether the value is a valid resource-template descriptor
2668
+ * @returns True if the value is a valid resource-template descriptor; false otherwise
2019
2669
  */
2020
2670
  function isMCPResourceTemplate(value) {
2021
2671
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2032,7 +2682,7 @@ function isMCPResourceTemplate(value) {
2032
2682
  * Determines whether a value is structurally discriminated resource contents.
2033
2683
  *
2034
2684
  * @param value - The unknown value to inspect
2035
- * @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
2036
2686
  */
2037
2687
  function isMCPResourceContents(value) {
2038
2688
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2046,7 +2696,7 @@ function isMCPResourceContents(value) {
2046
2696
  * Determines whether a value carries the shared optional pagination cursor.
2047
2697
  *
2048
2698
  * @param value - The unknown value to inspect
2049
- * @returns Whether a present `cursor` is a string
2699
+ * @returns True if a present `cursor` is a string; false otherwise
2050
2700
  */
2051
2701
  function isMCPPaginationParams(value) {
2052
2702
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2056,7 +2706,7 @@ function isMCPPaginationParams(value) {
2056
2706
  * Determines whether a value is one consumer-owned resource page.
2057
2707
  *
2058
2708
  * @param value - The unknown value to inspect
2059
- * @returns Whether the resources and optional following cursor are valid
2709
+ * @returns True if the resources and optional following cursor are valid; false otherwise
2060
2710
  */
2061
2711
  function isMCPResourcePage(value) {
2062
2712
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2069,7 +2719,7 @@ function isMCPResourcePage(value) {
2069
2719
  * Determines whether a value is one consumer-owned resource-template page.
2070
2720
  *
2071
2721
  * @param value - The unknown value to inspect
2072
- * @returns Whether the templates and optional following cursor are valid
2722
+ * @returns True if the templates and optional following cursor are valid; false otherwise
2073
2723
  */
2074
2724
  function isMCPResourceTemplatePage(value) {
2075
2725
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2082,7 +2732,7 @@ function isMCPResourceTemplatePage(value) {
2082
2732
  * Determines whether a value is a string-valued MCP argument record.
2083
2733
  *
2084
2734
  * @param value - The unknown value to inspect
2085
- * @returns Whether every own argument value is a string
2735
+ * @returns True if every own argument value is a string; false otherwise
2086
2736
  */
2087
2737
  function isMCPStringArguments(value) {
2088
2738
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2092,7 +2742,7 @@ function isMCPStringArguments(value) {
2092
2742
  * Determines whether a value is one prompt argument descriptor.
2093
2743
  *
2094
2744
  * @param value - The unknown value to inspect
2095
- * @returns Whether the prompt argument descriptor is valid
2745
+ * @returns True if the prompt argument descriptor is valid; false otherwise
2096
2746
  */
2097
2747
  function isMCPPromptArgument(value) {
2098
2748
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2104,7 +2754,7 @@ function isMCPPromptArgument(value) {
2104
2754
  * Determines whether a value is one `prompts/list` descriptor.
2105
2755
  *
2106
2756
  * @param value - The unknown value to inspect
2107
- * @returns Whether the prompt descriptor is valid
2757
+ * @returns True if the prompt descriptor is valid; false otherwise
2108
2758
  */
2109
2759
  function isMCPPrompt(value) {
2110
2760
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2118,7 +2768,7 @@ function isMCPPrompt(value) {
2118
2768
  * Determines whether a value is one prompt message with existing rich content.
2119
2769
  *
2120
2770
  * @param value - The unknown value to inspect
2121
- * @returns Whether the role and content are valid
2771
+ * @returns True if the role and content are valid; false otherwise
2122
2772
  */
2123
2773
  function isMCPPromptMessage(value) {
2124
2774
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2128,7 +2778,7 @@ function isMCPPromptMessage(value) {
2128
2778
  * Determines whether a value is one consumer-owned prompt page.
2129
2779
  *
2130
2780
  * @param value - The unknown value to inspect
2131
- * @returns Whether the prompts and optional following cursor are valid
2781
+ * @returns True if the prompts and optional following cursor are valid; false otherwise
2132
2782
  */
2133
2783
  function isMCPPromptPage(value) {
2134
2784
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2141,7 +2791,7 @@ function isMCPPromptPage(value) {
2141
2791
  * Determines whether a value is one complete `prompts/get` result.
2142
2792
  *
2143
2793
  * @param value - The unknown value to inspect
2144
- * @returns Whether the prompt result and all messages are valid
2794
+ * @returns True if the prompt result and all messages are valid; false otherwise
2145
2795
  */
2146
2796
  function isMCPPromptGetResult(value) {
2147
2797
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2154,7 +2804,7 @@ function isMCPPromptGetResult(value) {
2154
2804
  * Determines whether a value is a prompt or resource-template completion reference.
2155
2805
  *
2156
2806
  * @param value - The unknown value to inspect
2157
- * @returns Whether the discriminated reference is valid
2807
+ * @returns True if the discriminated reference is valid; false otherwise
2158
2808
  */
2159
2809
  function isMCPCompletionReference(value) {
2160
2810
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2166,7 +2816,7 @@ function isMCPCompletionReference(value) {
2166
2816
  * Determines whether a value is one `completion/complete` parameter object.
2167
2817
  *
2168
2818
  * @param value - The unknown value to inspect
2169
- * @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
2170
2820
  */
2171
2821
  function isMCPCompletionParams(value) {
2172
2822
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2185,7 +2835,7 @@ function isMCPCompletionParams(value) {
2185
2835
  * Determines whether a value is one host-produced completion candidate set.
2186
2836
  *
2187
2837
  * @param value - The unknown value to inspect
2188
- * @returns Whether its candidates and optional result facts are valid
2838
+ * @returns True if its candidates and optional result facts are valid; false otherwise
2189
2839
  */
2190
2840
  function isMCPCompletion(value) {
2191
2841
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2199,7 +2849,7 @@ function isMCPCompletion(value) {
2199
2849
  * Determines whether a value is one complete, capped `completion/complete` result.
2200
2850
  *
2201
2851
  * @param value - The unknown value to inspect
2202
- * @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
2203
2853
  */
2204
2854
  function isMCPCompletionResult(value) {
2205
2855
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2211,7 +2861,7 @@ function isMCPCompletionResult(value) {
2211
2861
  * Determines whether a value is one exact dated-schema MCP tool content block.
2212
2862
  *
2213
2863
  * @param value - The unknown value to inspect
2214
- * @returns Whether the value is valid MCP content
2864
+ * @returns True if the value is valid MCP content; false otherwise
2215
2865
  */
2216
2866
  function isMCPContent(value) {
2217
2867
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2252,7 +2902,7 @@ function isMCPContent(value) {
2252
2902
  * input.
2253
2903
  *
2254
2904
  * @param value - The unknown value to inspect
2255
- * @returns Whether the value is a modern result
2905
+ * @returns True if the value is a modern result; false otherwise
2256
2906
  *
2257
2907
  * @example
2258
2908
  * ```ts
@@ -2278,7 +2928,7 @@ function isMCPResult(value) {
2278
2928
  * hostile input.
2279
2929
  *
2280
2930
  * @param value - The unknown value to inspect
2281
- * @returns Whether the value is a legacy result
2931
+ * @returns True if the value is a legacy result; false otherwise
2282
2932
  *
2283
2933
  * @example
2284
2934
  * ```ts
@@ -2294,7 +2944,7 @@ function isMCPLegacyResult(value) {
2294
2944
  * Determines whether a value is a complete modern MCP tool result.
2295
2945
  *
2296
2946
  * @param value - The unknown value to inspect
2297
- * @returns Whether the value is a complete MCP call result
2947
+ * @returns True if the value is a complete MCP call result; false otherwise
2298
2948
  */
2299
2949
  function isMCPCallResult(value) {
2300
2950
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2322,7 +2972,7 @@ function isMCPCallResult(value) {
2322
2972
  * INTEGER milliseconds because the schema formats them `int`.
2323
2973
  *
2324
2974
  * @param value - The unknown value to inspect
2325
- * @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
2326
2976
  *
2327
2977
  * @example
2328
2978
  * ```ts
@@ -2349,7 +2999,7 @@ function isMCPTaskResult(value) {
2349
2999
  * Determines whether a value is one of the extension's task lifecycle states.
2350
3000
  *
2351
3001
  * @param value - The unknown value to inspect
2352
- * @returns Whether the value is an {@link MCPTaskStatus}
3002
+ * @returns True if the value is an {@link MCPTaskStatus}; false otherwise
2353
3003
  *
2354
3004
  * @example
2355
3005
  * ```ts
@@ -2380,7 +3030,7 @@ function isMCPTaskStatus(value) {
2380
3030
  * What is checked is what this package publishes as the contract.
2381
3031
  *
2382
3032
  * @param value - The unknown value to inspect
2383
- * @returns Whether the value is a well-formed {@link MCPTaskDetail}
3033
+ * @returns True if the value is a well-formed {@link MCPTaskDetail}; false otherwise
2384
3034
  *
2385
3035
  * @example
2386
3036
  * ```ts
@@ -2423,7 +3073,7 @@ function isMCPTaskDetail(value) {
2423
3073
  * peer stamps there is the peer's to write.
2424
3074
  *
2425
3075
  * @param value - The unknown value to inspect
2426
- * @returns Whether the value is a well-formed {@link MCPTaskDetailResult}
3076
+ * @returns True if the value is a well-formed {@link MCPTaskDetailResult}; false otherwise
2427
3077
  *
2428
3078
  * @example
2429
3079
  * ```ts
@@ -2457,7 +3107,7 @@ function isMCPTaskDetailResult(value) {
2457
3107
  * to it, so a guard that demanded the stamp would refuse every frame a producer emits.
2458
3108
  *
2459
3109
  * @param value - The unknown value to inspect
2460
- * @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
2461
3111
  *
2462
3112
  * @example
2463
3113
  * ```ts
@@ -2485,7 +3135,7 @@ function isMCPTaskNotification(value) {
2485
3135
  *
2486
3136
  * @param value - The unknown value to inspect
2487
3137
  * @param bytes - The maximum accepted encoded bytes
2488
- * @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
2489
3139
  *
2490
3140
  * @example
2491
3141
  * ```ts
@@ -2521,7 +3171,7 @@ function isBoundedString(value, bytes) {
2521
3171
  *
2522
3172
  * @param value - The unknown value to inspect
2523
3173
  * @param limits - Serialized byte, optional key, and nesting-depth bounds
2524
- * @returns `true` only for safe JSON satisfying every bound
3174
+ * @returns True if `value` is safe JSON satisfying every bound; false otherwise
2525
3175
  *
2526
3176
  * @example
2527
3177
  * ```ts
@@ -2543,7 +3193,7 @@ function isBoundedJSON(value, limits) {
2543
3193
  * no minimum length. Total: any other input returns `false`.
2544
3194
  *
2545
3195
  * @param value - The already-parsed value to test
2546
- * @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
2547
3197
  *
2548
3198
  * @example
2549
3199
  * ```ts
@@ -2561,7 +3211,7 @@ function isJSONRPCId(value) {
2561
3211
  * Determines whether a value is a supported {@link MCPVersion}.
2562
3212
  *
2563
3213
  * @param value - The unknown value to inspect
2564
- * @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
2565
3215
  */
2566
3216
  function isMCPVersion(value) {
2567
3217
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MCP_VERSIONS.some((version) => version === value);
@@ -2570,7 +3220,7 @@ function isMCPVersion(value) {
2570
3220
  * Determines whether a value is a modern protocol revision accepted by a bare server.
2571
3221
  *
2572
3222
  * @param value - The unknown value to inspect
2573
- * @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
2574
3224
  */
2575
3225
  function isMCPModernVersion(value) {
2576
3226
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MODERN_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -2579,7 +3229,7 @@ function isMCPModernVersion(value) {
2579
3229
  * Determines whether a value is a revision accepted by the optional legacy decorator.
2580
3230
  *
2581
3231
  * @param value - The unknown value to inspect
2582
- * @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
2583
3233
  */
2584
3234
  function isMCPLegacyVersion(value) {
2585
3235
  return (0, _orkestrel_contract.isString)(value) && SUPPORTED_LEGACY_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -2598,7 +3248,7 @@ function isMCPLegacyVersion(value) {
2598
3248
  * the caller asked for.
2599
3249
  *
2600
3250
  * @param value - The unknown value to inspect
2601
- * @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
2602
3252
  */
2603
3253
  function isMCPSubscriptionFilter(value) {
2604
3254
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2619,7 +3269,7 @@ function isMCPSubscriptionFilter(value) {
2619
3269
  * Determines whether a value is a graceful `subscriptions/listen` result.
2620
3270
  *
2621
3271
  * @param value - The unknown value to inspect
2622
- * @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
2623
3273
  */
2624
3274
  function isMCPSubscriptionResult(value) {
2625
3275
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -2631,7 +3281,7 @@ function isMCPSubscriptionResult(value) {
2631
3281
  * Determines whether a value is one restricted primitive form-elicitation schema.
2632
3282
  *
2633
3283
  * @param value - The unknown value to inspect
2634
- * @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
2635
3285
  *
2636
3286
  * @example
2637
3287
  * ```ts
@@ -2694,7 +3344,7 @@ function isMCPElicitFieldSchema(value) {
2694
3344
  * an unrecognized top-level annotation is data rather than a rejection.
2695
3345
  *
2696
3346
  * @param value - The unknown value to inspect
2697
- * @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
2698
3348
  *
2699
3349
  * @example
2700
3350
  * ```ts
@@ -2721,7 +3371,7 @@ function isMCPElicitSchema(value) {
2721
3371
  * Determines whether a value is a form-mode elicitation parameter object.
2722
3372
  *
2723
3373
  * @param value - The unknown value to inspect
2724
- * @returns `true` when `value` has the restricted form elicitation shape
3374
+ * @returns True if `value` has the restricted form elicitation shape; false otherwise
2725
3375
  *
2726
3376
  * @example
2727
3377
  * ```ts
@@ -2747,7 +3397,7 @@ function isMCPElicitForm(value) {
2747
3397
  * Determines whether a value is a URL-mode elicitation parameter object.
2748
3398
  *
2749
3399
  * @param value - The unknown value to inspect
2750
- * @returns `true` when `value` has the URL elicitation shape
3400
+ * @returns True if `value` has the URL elicitation shape; false otherwise
2751
3401
  *
2752
3402
  * @example
2753
3403
  * ```ts
@@ -2768,7 +3418,7 @@ function isMCPElicitURL(value) {
2768
3418
  * Determines whether a value is an embedded `elicitation/create` request.
2769
3419
  *
2770
3420
  * @param value - The unknown value to inspect
2771
- * @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
2772
3422
  *
2773
3423
  * @example
2774
3424
  * ```ts
@@ -2793,7 +3443,7 @@ function isMCPElicitRequest(value) {
2793
3443
  * Determines whether a value is one legal embedded multi-round-trip request.
2794
3444
  *
2795
3445
  * @param value - The unknown value to inspect
2796
- * @returns `true` for elicitation, deprecated sampling, or deprecated roots requests
3446
+ * @returns True if `value` is an embedded elicitation, sampling, or roots request; false otherwise
2797
3447
  *
2798
3448
  * @example
2799
3449
  * ```ts
@@ -2814,10 +3464,10 @@ function isMCPInputRequest(value) {
2814
3464
  }
2815
3465
  }
2816
3466
  /**
2817
- * Determines whether a value is a server-keyed map of embedded input requests.
3467
+ * Determines whether a value is a consumer-keyed map of embedded input requests.
2818
3468
  *
2819
3469
  * @param value - The unknown value to inspect
2820
- * @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
2821
3471
  *
2822
3472
  * @example
2823
3473
  * ```ts
@@ -2837,7 +3487,7 @@ function isMCPInputRequestMap(value) {
2837
3487
  * Determines whether a value is one elicitation response.
2838
3488
  *
2839
3489
  * @param value - The unknown value to inspect
2840
- * @returns `true` when action/content have the protocol shape
3490
+ * @returns True if action/content have the protocol shape; false otherwise
2841
3491
  *
2842
3492
  * @example
2843
3493
  * ```ts
@@ -2887,7 +3537,7 @@ function isMCPElicitResult(value) {
2887
3537
  *
2888
3538
  * @param value - The accepted response content to check
2889
3539
  * @param schema - The exact {@link MCPElicitSchema} that was issued with the elicitation
2890
- * @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
2891
3541
  *
2892
3542
  * @example
2893
3543
  * ```ts
@@ -2968,6 +3618,187 @@ function isElicitContent(value, schema) {
2968
3618
  }
2969
3619
  }
2970
3620
  /**
3621
+ * Determines whether a value is one filesystem root a client exposes.
3622
+ *
3623
+ * @remarks
3624
+ * The dated schema declares `uri` with `format: uri`, so this applies the same RFC 3986
3625
+ * check {@link isAbsoluteURI} gives every other `format: uri` field the package validates,
3626
+ * including a URL-mode elicitation's `url`. Total over hostile input.
3627
+ *
3628
+ * @param value - The unknown value to inspect
3629
+ * @returns True if `value` carries an absolute `uri` and an optional string `name`; false otherwise
3630
+ *
3631
+ * @example
3632
+ * ```ts
3633
+ * isMCPRoot({ uri: 'file:///workspace', name: 'workspace' }) // true
3634
+ * isMCPRoot({ uri: 'workspace' }) // false — the schema declares `format: uri`
3635
+ * ```
3636
+ */
3637
+ function isMCPRoot(value) {
3638
+ const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
3639
+ if (!owned.success) return false;
3640
+ try {
3641
+ const root = owned.value;
3642
+ const name = root["name"];
3643
+ const metadata = root["_meta"];
3644
+ if (!isAbsoluteURI(root["uri"])) return false;
3645
+ if (!(0, _orkestrel_contract.isUndefined)(name) && !(0, _orkestrel_contract.isString)(name)) return false;
3646
+ return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
3647
+ } catch {
3648
+ return false;
3649
+ }
3650
+ }
3651
+ /**
3652
+ * Determines whether a value is one client answer to an embedded `roots/list` request.
3653
+ *
3654
+ * @remarks
3655
+ * The dated schema requires the `roots` array, and each root is checked by
3656
+ * {@link isMCPRoot}. Total over hostile input.
3657
+ *
3658
+ * @param value - The unknown value to inspect
3659
+ * @returns True if `value` carries an array of valid roots; false otherwise
3660
+ *
3661
+ * @example
3662
+ * ```ts
3663
+ * isMCPRootResult({ roots: [{ uri: 'file:///workspace' }] }) // true
3664
+ * isMCPRootResult({ roots: {} }) // false — the schema requires an array
3665
+ * ```
3666
+ */
3667
+ function isMCPRootResult(value) {
3668
+ const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
3669
+ if (!owned.success) return false;
3670
+ try {
3671
+ const result = owned.value;
3672
+ const roots = result["roots"];
3673
+ const metadata = result["_meta"];
3674
+ if (!Array.isArray(roots) || !roots.every((root) => isMCPRoot(root))) return false;
3675
+ return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
3676
+ } catch {
3677
+ return false;
3678
+ }
3679
+ }
3680
+ /**
3681
+ * Determines whether a value is one block a sampling completion may carry.
3682
+ *
3683
+ * @remarks
3684
+ * The schema's `SamplingMessageContentBlock`: the text, image, and audio blocks
3685
+ * {@link isMCPContent} also admits, plus `tool_use` and `tool_result`. The resource arms of
3686
+ * {@link isMCPContent} are refused, because the schema leaves them out of a sampling
3687
+ * completion. A `tool_result` carries ordinary {@link isMCPContent} blocks and an open
3688
+ * `structuredContent`, which the schema constrains to no shape at all. Total over hostile
3689
+ * input.
3690
+ *
3691
+ * @param value - The unknown value to inspect
3692
+ * @returns True if `value` is one legal sampling content block; false otherwise
3693
+ *
3694
+ * @example
3695
+ * ```ts
3696
+ * isMCPSampleContent({ type: 'text', text: 'Paris' }) // true
3697
+ * isMCPSampleContent({ type: 'tool_use', id: 'c1', name: 'lookup', input: {} }) // true
3698
+ * isMCPSampleContent({ type: 'resource_link', name: 'doc', uri: 'file:///doc' }) // false
3699
+ * ```
3700
+ */
3701
+ function isMCPSampleContent(value) {
3702
+ const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
3703
+ if (!owned.success) return false;
3704
+ try {
3705
+ const block = owned.value;
3706
+ const metadata = block["_meta"];
3707
+ if (!(0, _orkestrel_contract.isUndefined)(metadata) && !isMCPMetaObject(metadata)) return false;
3708
+ if (block["type"] === "tool_use") return (0, _orkestrel_contract.isString)(block["id"]) && (0, _orkestrel_contract.isString)(block["name"]) && (0, _orkestrel_contract.isRecord)(block["input"]);
3709
+ if (block["type"] === "tool_result") {
3710
+ const carried = block["content"];
3711
+ const failed = block["isError"];
3712
+ if (!Array.isArray(carried) || !carried.every((entry) => isMCPContent(entry))) return false;
3713
+ if (!(0, _orkestrel_contract.isUndefined)(failed) && !(0, _orkestrel_contract.isBoolean)(failed)) return false;
3714
+ return (0, _orkestrel_contract.isString)(block["toolUseId"]);
3715
+ }
3716
+ if (!isMCPContent(block)) return false;
3717
+ return block.type === "text" || block.type === "image" || block.type === "audio";
3718
+ } catch {
3719
+ return false;
3720
+ }
3721
+ }
3722
+ /**
3723
+ * Determines whether a value is one client answer to an embedded sampling request.
3724
+ *
3725
+ * @remarks
3726
+ * The schema's `CreateMessageResult` types `content` as an `anyOf` over one
3727
+ * {@link isMCPSampleContent} block or an ARRAY of them, so both are admitted here: a
3728
+ * tool-using model answers with `tool_use` and `tool_result` blocks, and a model answering in
3729
+ * several parts answers with the array. `stopReason` stays an open string because the schema
3730
+ * names four values and permits any other a provider reports. Total over hostile input.
3731
+ *
3732
+ * @param value - The unknown value to inspect
3733
+ * @returns True if `value` has the sampling-completion shape; false otherwise
3734
+ *
3735
+ * @example
3736
+ * ```ts
3737
+ * isMCPSampleResult({
3738
+ * role: 'assistant',
3739
+ * content: { type: 'text', text: 'Paris' },
3740
+ * model: 'test-model',
3741
+ * }) // true
3742
+ * isMCPSampleResult({
3743
+ * role: 'assistant',
3744
+ * content: [{ type: 'text', text: 'Paris' }],
3745
+ * model: 'test-model',
3746
+ * }) // true
3747
+ * ```
3748
+ */
3749
+ function isMCPSampleResult(value) {
3750
+ const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
3751
+ if (!owned.success) return false;
3752
+ try {
3753
+ const result = owned.value;
3754
+ const role = result["role"];
3755
+ const content = result["content"];
3756
+ const reason = result["stopReason"];
3757
+ const metadata = result["_meta"];
3758
+ if (role !== "user" && role !== "assistant") return false;
3759
+ if (!(0, _orkestrel_contract.isString)(result["model"])) return false;
3760
+ if (!(Array.isArray(content) ? content : [content]).every((block) => isMCPSampleContent(block))) return false;
3761
+ if (!(0, _orkestrel_contract.isUndefined)(reason) && !(0, _orkestrel_contract.isString)(reason)) return false;
3762
+ return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
3763
+ } catch {
3764
+ return false;
3765
+ }
3766
+ }
3767
+ /**
3768
+ * Determines whether a response answers the exact embedded request that was issued.
3769
+ *
3770
+ * @remarks
3771
+ * A response carries no `method` of its own, so the ISSUED request selects which arm applies
3772
+ * — the same way {@link isElicitContent} takes the issued schema rather than trusting the
3773
+ * content to describe itself. A form elicitation is checked twice: once for the response
3774
+ * shape and once, on `accept`, for the content against the schema that round issued. A
3775
+ * URL-mode elicitation issues no schema, so only the shape is checked. A request this
3776
+ * package cannot recognize admits NOTHING, because an unrecognized question has no correct
3777
+ * answer. Total over hostile responses and hostile requests alike.
3778
+ *
3779
+ * @param value - The client's answer to check
3780
+ * @param request - The exact {@link MCPInputRequest} that was issued under the same key
3781
+ * @returns True if the answer is legal for that request; false otherwise
3782
+ *
3783
+ * @example
3784
+ * ```ts
3785
+ * isMCPInputResponse({ roots: [] }, { method: 'roots/list' }) // true
3786
+ * isMCPInputResponse({ roots: [] }, { method: 'sampling/createMessage', params: {} }) // false
3787
+ * ```
3788
+ */
3789
+ function isMCPInputResponse(value, request) {
3790
+ if (!isMCPInputRequest(request)) return false;
3791
+ try {
3792
+ if (request.method === "roots/list") return isMCPRootResult(value);
3793
+ if (request.method === "sampling/createMessage") return isMCPSampleResult(value);
3794
+ if (!isMCPElicitResult(value)) return false;
3795
+ if (value.action !== "accept" || !isMCPElicitForm(request.params)) return true;
3796
+ return isElicitContent(value.content ?? {}, request.params.requestedSchema);
3797
+ } catch {
3798
+ return false;
3799
+ }
3800
+ }
3801
+ /**
2971
3802
  * Determines whether a value is an MCP input-required result.
2972
3803
  *
2973
3804
  * @remarks
@@ -2975,7 +3806,7 @@ function isElicitContent(value, schema) {
2975
3806
  * both must be present and valid. Total over hostile input.
2976
3807
  *
2977
3808
  * @param value - The unknown value to inspect
2978
- * @returns `true` when `value` is a valid input-required result
3809
+ * @returns True if `value` is a valid input-required result; false otherwise
2979
3810
  *
2980
3811
  * @example
2981
3812
  * ```ts
@@ -3012,7 +3843,7 @@ function isMCPInputResult(value) {
3012
3843
  * be a record. Total: any other input returns `false`.
3013
3844
  *
3014
3845
  * @param value - The already-parsed value to test
3015
- * @returns `true` when `value` is a valid JSON-RPC request
3846
+ * @returns True if `value` is a valid JSON-RPC request; false otherwise
3016
3847
  *
3017
3848
  * @example
3018
3849
  * ```ts
@@ -3039,7 +3870,7 @@ function isJSONRPCRequest(value) {
3039
3870
  * be a record. Total: any other input returns `false`.
3040
3871
  *
3041
3872
  * @param value - The already-parsed value to test
3042
- * @returns `true` when `value` is a valid JSON-RPC notification
3873
+ * @returns True if `value` is a valid JSON-RPC notification; false otherwise
3043
3874
  *
3044
3875
  * @example
3045
3876
  * ```ts
@@ -3065,7 +3896,7 @@ function isJSONRPCNotification(value) {
3065
3896
  * mutually exclusive, so a positive answer names exactly one arm. Total.
3066
3897
  *
3067
3898
  * @param value - The already-parsed value to test
3068
- * @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
3069
3900
  */
3070
3901
  function isJSONRPCInvocation(value) {
3071
3902
  return isJSONRPCRequest(value) || isJSONRPCNotification(value);
@@ -3083,7 +3914,7 @@ function isJSONRPCInvocation(value) {
3083
3914
  * Total.
3084
3915
  *
3085
3916
  * @param value - The already-parsed value to test
3086
- * @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
3087
3918
  *
3088
3919
  * @example
3089
3920
  * ```ts
@@ -3119,7 +3950,7 @@ function isJSONRPCResultResponse(value) {
3119
3950
  * itself the hostile step, and it is bounded here rather than allowed to escape. Total.
3120
3951
  *
3121
3952
  * @param value - The already-parsed value to test
3122
- * @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
3123
3954
  *
3124
3955
  * @example
3125
3956
  * ```ts
@@ -3143,7 +3974,7 @@ function isJSONRPCError(value) {
3143
3974
  * `result`. `error` carries an integer `code` and a string `message`. Total.
3144
3975
  *
3145
3976
  * @param value - The already-parsed value to test
3146
- * @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
3147
3978
  *
3148
3979
  * @example
3149
3980
  * ```ts
@@ -3167,7 +3998,7 @@ function isJSONRPCErrorResponse(value) {
3167
3998
  * The union of the mutually exclusive arms. Total.
3168
3999
  *
3169
4000
  * @param value - The already-parsed value to test
3170
- * @returns `true` when `value` is a valid JSON-RPC response
4001
+ * @returns True if `value` is a valid JSON-RPC response; false otherwise
3171
4002
  */
3172
4003
  function isJSONRPCResponse(value) {
3173
4004
  return isJSONRPCResultResponse(value) || isJSONRPCErrorResponse(value);
@@ -3180,7 +4011,7 @@ function isJSONRPCResponse(value) {
3180
4011
  * The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total.
3181
4012
  *
3182
4013
  * @param value - The already-parsed value to test
3183
- * @returns `true` when `value` is a valid JSON-RPC message
4014
+ * @returns True if `value` is a valid JSON-RPC message; false otherwise
3184
4015
  */
3185
4016
  function isJSONRPCMessage(value) {
3186
4017
  return isJSONRPCInvocation(value) || isJSONRPCResponse(value);
@@ -3189,7 +4020,7 @@ function isJSONRPCMessage(value) {
3189
4020
  * Determines whether a parsed value is an MCP `initialize` invocation.
3190
4021
  *
3191
4022
  * @param value - The already-parsed value to test
3192
- * @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
3193
4024
  *
3194
4025
  * @example
3195
4026
  * ```ts
@@ -3212,7 +4043,7 @@ function isInitializeRequest(value) {
3212
4043
  * legacy dispatch. Total over hostile and malformed input.
3213
4044
  *
3214
4045
  * @param value - The already-parsed value to inspect
3215
- * @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
3216
4047
  */
3217
4048
  function isModernRequest(value) {
3218
4049
  const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
@@ -3242,6 +4073,27 @@ function inferEra(version) {
3242
4073
  if (isMCPLegacyVersion(version)) return "legacy";
3243
4074
  }
3244
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
+ /**
3245
4097
  * Infers the newest supported modern protocol revision present in a peer's offer.
3246
4098
  *
3247
4099
  * @param offered - The protocol revisions offered by the peer
@@ -3287,7 +4139,7 @@ function inferRequestVersion(message) {
3287
4139
  //#endregion
3288
4140
  //#region src/core/MCPMethodManager.ts
3289
4141
  /**
3290
- * The modern method registry an {@link import('./types.js').MCPServerInterface}
4142
+ * Holds the modern methods an {@link import('./types.js').MCPServerInterface}
3291
4143
  * dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
3292
4144
  * map rather than exposing one.
3293
4145
  *
@@ -3321,7 +4173,7 @@ var MCPMethodManager = class {
3321
4173
  //#endregion
3322
4174
  //#region src/core/MCPProgressReporter.ts
3323
4175
  /**
3324
- * 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.
3325
4177
  *
3326
4178
  * The reporter holds at most one owned progress item. {@link report} applies backpressure until
3327
4179
  * {@link take} consumes that slot. It has no replay, queue, concurrent-consumer coordination,
@@ -3445,7 +4297,8 @@ var MCPProgressReporter = class {
3445
4297
  //#endregion
3446
4298
  //#region src/core/MCPStreamController.ts
3447
4299
  /**
3448
- * 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.
3449
4302
  *
3450
4303
  * @remarks
3451
4304
  * A native async generator decides cancellation with a QUEUE: `return()` and `throw()` wait
@@ -3642,7 +4495,7 @@ var MCPStreamController = class {
3642
4495
  //#endregion
3643
4496
  //#region src/core/MCPTextStreamController.ts
3644
4497
  /**
3645
- * 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
3646
4499
  * serialized.
3647
4500
  *
3648
4501
  * @remarks
@@ -3804,12 +4657,7 @@ var MCPLegacy = class {
3804
4657
  }
3805
4658
  async handle(message, options) {
3806
4659
  if (!isBoundedString(message, this.limit.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
3807
- let parsed;
3808
- try {
3809
- parsed = JSON.parse(message);
3810
- } catch {
3811
- return this.#options.dispatcher.handle(message, options);
3812
- }
4660
+ const parsed = (0, _orkestrel_contract.parseJSON)(message);
3813
4661
  if (isModernRequest(parsed) || !isJSONRPCInvocation(parsed)) return this.#options.dispatcher.handle(message, options);
3814
4662
  const answer = await this.#legacy(parsed, options);
3815
4663
  return answer === void 0 ? void 0 : JSON.stringify(answer);
@@ -3896,7 +4744,7 @@ var MCPLegacyClientTransport = class {
3896
4744
  if (requested !== void 0 && !isMCPLegacyVersion(requested)) throw new MCPError("Unsupported legacy protocol version", MCP_UNSUPPORTED_VERSION, { requested });
3897
4745
  this.#transport = transport;
3898
4746
  this.#client = options?.identity ?? {
3899
- name: "taverna",
4747
+ name: "@orkestrel/mcp",
3900
4748
  version: "1.0.0"
3901
4749
  };
3902
4750
  this.#capabilities = options?.capabilities ?? {};
@@ -4088,8 +4936,8 @@ var MCPLegacyClientTransport = class {
4088
4936
  //#endregion
4089
4937
  //#region src/core/MCPServer.ts
4090
4938
  /**
4091
- * A transport-agnostic Model Context Protocol server dispatches JSON-RPC 2.0
4092
- * 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.
4093
4941
  *
4094
4942
  * @remarks
4095
4943
  * - **`dispatch` and `handle`.** `dispatch(invocation)` runs an already-parsed invocation and
@@ -4166,8 +5014,21 @@ var MCPServer = class {
4166
5014
  if (decoded === void 0 || !("method" in decoded)) return buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request");
4167
5015
  return this.#dispatch(decoded, options);
4168
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
+ }
4169
5030
  async #dispatch(invocation, options) {
4170
- this.#emitter.emit("request", invocation.method, invocation.id, "modern");
5031
+ this.#emitter.emit("request", invocation.method, invocation.id, inferRequestEra(invocation));
4171
5032
  if (invocation.id === void 0) return;
4172
5033
  const id = invocation.id;
4173
5034
  const metadata = invocation.params?.["_meta"];
@@ -4185,26 +5046,9 @@ var MCPServer = class {
4185
5046
  return this.#contain(error, id);
4186
5047
  }
4187
5048
  }
4188
- async handle(message, options) {
4189
- if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4190
- let parsed;
4191
- try {
4192
- parsed = JSON.parse(message);
4193
- } catch {
4194
- return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4195
- }
4196
- const decoded = parseJSONRPCMessage(parsed, {
4197
- bytes: this.#limits.message,
4198
- depth: this.#limits.depth
4199
- });
4200
- if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
4201
- const answer = await this.#dispatch(decoded, options ?? {});
4202
- if (answer === void 0) return void 0;
4203
- return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
4204
- }
4205
5049
  #register() {
4206
- this.#methods.add("server/discover", async (request, _options) => this.#discover(request));
4207
- 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));
4208
5052
  this.#methods.add("tools/call", async (request, options) => this.#call(request, options));
4209
5053
  this.#methods.add("subscriptions/listen", async (request, options) => this.#subscribe(request, options));
4210
5054
  const resources = this.#options.resources;
@@ -4293,13 +5137,7 @@ var MCPServer = class {
4293
5137
  if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
4294
5138
  if (isMCPInputResult(captured[0])) {
4295
5139
  const input = captured[0];
4296
- return buildJSONRPCResult(request.id, {
4297
- ...input,
4298
- _meta: {
4299
- ...input["_meta"] ?? {},
4300
- [MCP_META_SERVER]: this.#options.identity
4301
- }
4302
- });
5140
+ return this.#forward(input, request);
4303
5141
  }
4304
5142
  if (!Array.isArray(captured[0]) || !captured[0].every((entry) => isMCPResourceContents(entry))) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
4305
5143
  const result = buildModernResult({ contents: captured[0] }, this.#options.identity, this.#options.cache?.ttl ?? 6e4, this.#options.cache?.scope);
@@ -4361,13 +5199,7 @@ var MCPServer = class {
4361
5199
  if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
4362
5200
  if (isMCPInputResult(captured[0])) {
4363
5201
  const input = captured[0];
4364
- return buildJSONRPCResult(request.id, {
4365
- ...input,
4366
- _meta: {
4367
- ...input["_meta"] ?? {},
4368
- [MCP_META_SERVER]: this.#options.identity
4369
- }
4370
- });
5202
+ return this.#forward(input, request);
4371
5203
  }
4372
5204
  if (!isMCPPromptGetResult(captured[0])) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
4373
5205
  const result = captured[0];
@@ -4430,20 +5262,20 @@ var MCPServer = class {
4430
5262
  async #defer(request, call, options) {
4431
5263
  const configured = this.#options.task;
4432
5264
  if (configured === void 0) return void 0;
4433
- const deferral = {
5265
+ const deferred = {
4434
5266
  request,
4435
5267
  call,
4436
5268
  tools: this.#options.tools
4437
5269
  };
4438
- const key = await configured.defer(deferral, options);
5270
+ const key = await configured.deferral(deferred, options);
4439
5271
  if ((0, _orkestrel_contract.isUndefined)(key)) return void 0;
4440
5272
  if (!(0, _orkestrel_contract.isString)(key) || key.length === 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server execution returned an invalid task key");
4441
5273
  const context = parseRequestContext(request, {
4442
5274
  bytes: this.#limits.message,
4443
5275
  depth: this.#limits.depth
4444
5276
  });
4445
- 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]: {} } } });
4446
- 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);
4447
5279
  const captured = snapshotJSON({
4448
5280
  resultType: "task",
4449
5281
  taskId: created.taskId,
@@ -4521,21 +5353,46 @@ var MCPServer = class {
4521
5353
  });
4522
5354
  if (digest === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: tool arguments are too large or unsafe");
4523
5355
  if (params?.["requestState"] !== void 0 || params?.["inputResponses"] !== void 0) return this.#retry(request, name, digest, args, options);
4524
- const elicitation = await configured.elicit({
5356
+ const selected = await configured.selector({
4525
5357
  request,
4526
5358
  name,
4527
5359
  arguments: args
4528
5360
  }, options);
4529
- if (elicitation === void 0) return void 0;
4530
- const form = this.#form(elicitation);
5361
+ if (selected === void 0) return void 0;
5362
+ const round = this.#ownRound(selected);
4531
5363
  const context = parseRequestContext(request, {
4532
5364
  bytes: this.#limits.message,
4533
5365
  depth: this.#limits.depth
4534
5366
  });
4535
- if (form === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: elicitation policy returned an invalid form or continuation context");
4536
- if (context === void 0 || !isFormElicitationSupported(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the elicitation capability for this request", { requiredCapabilities: { elicitation: {} } });
5367
+ if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5368
+ const refusal = this.#gate(round, context, id);
5369
+ if (refusal !== void 0) return refusal;
4537
5370
  const principal = await configured.principal(request, options);
4538
- return this.#required(request, name, digest, form, principal, id, void 0);
5371
+ return this.#required(request, name, digest, round, principal, id, void 0);
5372
+ }
5373
+ #gate(round, context, id) {
5374
+ if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
5375
+ const missing = computeMissingCapabilities(round.requests, context.capabilities);
5376
+ if (missing === void 0) return void 0;
5377
+ return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires a client capability this request did not declare", { requiredCapabilities: missing });
5378
+ }
5379
+ #forward(input, request) {
5380
+ const requests = input.inputRequests;
5381
+ if (requests !== void 0) {
5382
+ const context = parseRequestContext(request, {
5383
+ bytes: this.#limits.message,
5384
+ depth: this.#limits.depth
5385
+ });
5386
+ const refusal = this.#gate({ requests }, context, request.id);
5387
+ if (refusal !== void 0) return refusal;
5388
+ }
5389
+ return buildJSONRPCResult(request.id, {
5390
+ ...input,
5391
+ _meta: {
5392
+ ...input["_meta"] ?? {},
5393
+ [MCP_META_SERVER]: this.#options.identity
5394
+ }
5395
+ });
4539
5396
  }
4540
5397
  async #retry(request, name, digest, args, options) {
4541
5398
  const configured = this.#options.input;
@@ -4548,55 +5405,66 @@ var MCPServer = class {
4548
5405
  bytes: this.#limits.message,
4549
5406
  depth: this.#limits.depth
4550
5407
  });
4551
- if (context === void 0 || !isFormElicitationSupported(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the elicitation capability for this request", { requiredCapabilities: { elicitation: {} } });
5408
+ if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
4552
5409
  const verified = await configured.continuation.open(requestState);
4553
5410
  if (verified === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be recovered");
4554
5411
  if (!isBoundedString(verified, this.#limits.state) || verified.length === 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a value outside the configured state bound"), id);
4555
5412
  const state = parseMCPInputState(verified);
4556
5413
  if (state === void 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a malformed protected payload"), id);
4557
- const response = inputResponses[state.key];
4558
- if (state.expiry <= Date.now() || state.id === id || state.version !== context.version || state.method !== request.method || state.name !== name || state.digest !== digest || !Object.hasOwn(inputResponses, state.key)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
4559
- if (!isMCPElicitResult(response) || response.action === "accept" && !isElicitContent(response.content ?? {}, state.schema)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: the elicitation response is missing or malformed");
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");
5415
+ const responses = this.#checkAnswers(state.requests, inputResponses);
5416
+ if (responses === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an input response is missing or malformed");
4560
5417
  const principal = await configured.principal(request, options);
4561
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");
4562
- const elicitation = await configured.elicit({
5419
+ const selected = await configured.selector({
4563
5420
  request,
4564
5421
  name,
4565
5422
  arguments: args,
4566
- response,
5423
+ responses,
4567
5424
  ...state.state !== void 0 ? { state: state.state } : {}
4568
5425
  }, options);
4569
5426
  if (state.expiry <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
4570
- if (elicitation === void 0) return void 0;
4571
- const form = this.#form(elicitation);
4572
- if (form === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: elicitation policy returned an invalid form or continuation context");
4573
- return this.#required(request, name, digest, form, principal, state.id, state.expiry);
5427
+ if (selected === void 0) return void 0;
5428
+ const round = this.#ownRound(selected);
5429
+ if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5430
+ const refusal = this.#gate(round, context, id);
5431
+ if (refusal !== void 0) return refusal;
5432
+ return this.#required(request, name, digest, round, principal, state.id, state.expiry);
5433
+ }
5434
+ #checkAnswers(requests, responses) {
5435
+ const answered = {};
5436
+ for (const [key, issued] of Object.entries(requests)) {
5437
+ const response = responses[key];
5438
+ if (!Object.hasOwn(responses, key) || !isMCPInputResponse(response, issued)) return void 0;
5439
+ answered[key] = response;
5440
+ }
5441
+ return Object.freeze(answered);
4574
5442
  }
4575
- #form(elicitation) {
4576
- const owned = snapshotJSON(elicitation, {
5443
+ #ownRound(round) {
5444
+ const owned = snapshotJSON(round, {
4577
5445
  bytes: this.#limits.content,
4578
5446
  keys: this.#limits.keys,
4579
5447
  depth: this.#limits.depth
4580
5448
  });
4581
5449
  if (owned === void 0 || !(0, _orkestrel_contract.isRecord)(owned[0])) return void 0;
4582
- const form = owned[0]["request"];
5450
+ const requests = owned[0]["requests"];
4583
5451
  const state = owned[0]["state"];
4584
- if (!isMCPElicitForm(form) || !(0, _orkestrel_contract.isUndefined)(state) && !(0, _orkestrel_contract.isJSONValue)(state)) return void 0;
4585
- return (0, _orkestrel_contract.isUndefined)(state) ? { request: form } : {
4586
- request: form,
5452
+ if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
5453
+ if (!(0, _orkestrel_contract.isUndefined)(state) && !(0, _orkestrel_contract.isJSONValue)(state)) return void 0;
5454
+ return (0, _orkestrel_contract.isUndefined)(state) ? { requests } : {
5455
+ requests,
4587
5456
  state
4588
5457
  };
4589
5458
  }
4590
- async #required(request, name, digest, form, principal, origin, previous) {
5459
+ async #required(request, name, digest, round, principal, origin, previous) {
4591
5460
  const id = request.id;
4592
5461
  const configured = this.#options.input;
4593
5462
  const context = parseRequestContext(request, {
4594
5463
  bytes: this.#limits.message,
4595
5464
  depth: this.#limits.depth
4596
5465
  });
4597
- if (configured === void 0 || context === void 0 || !(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || !Number.isFinite(configured.ttl) || configured.ttl <= 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: elicitation policy returned an invalid form or continuation context");
5466
+ if (configured === void 0 || context === void 0 || !(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || !Number.isFinite(configured.ttl) || configured.ttl <= 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
4598
5467
  if (previous !== void 0 && previous <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
4599
- const key = crypto.randomUUID();
4600
5468
  const expiry = Date.now() + configured.ttl;
4601
5469
  const protectedState = {
4602
5470
  principal,
@@ -4604,11 +5472,10 @@ var MCPServer = class {
4604
5472
  id: origin,
4605
5473
  version: context.version,
4606
5474
  method: request.method,
4607
- key,
5475
+ requests: round.requests,
4608
5476
  name,
4609
5477
  digest,
4610
- schema: form.request.requestedSchema,
4611
- ...form.state !== void 0 ? { state: form.state } : {}
5478
+ ...round.state !== void 0 ? { state: round.state } : {}
4612
5479
  };
4613
5480
  if (!isBoundedJSON(protectedState, {
4614
5481
  bytes: this.#limits.state,
@@ -4624,13 +5491,7 @@ var MCPServer = class {
4624
5491
  if (expiry <= Date.now() || previous !== void 0 && previous <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, previous === void 0 ? "Invalid params: request state expired before it could be issued" : "Invalid params: request state could not be verified for this retry");
4625
5492
  return buildJSONRPCResult(id, {
4626
5493
  resultType: "input_required",
4627
- inputRequests: { [key]: {
4628
- method: "elicitation/create",
4629
- params: {
4630
- ...form.request,
4631
- mode: "form"
4632
- }
4633
- } },
5494
+ inputRequests: round.requests,
4634
5495
  requestState,
4635
5496
  _meta: { [MCP_META_SERVER]: this.#options.identity }
4636
5497
  });
@@ -4667,7 +5528,7 @@ var MCPServer = class {
4667
5528
  }
4668
5529
  yield buildSubscriptionAcknowledgement(notifications, id);
4669
5530
  if (configured !== void 0) {
4670
- const iterator = (await configured.listen(notifications, options))[Symbol.asyncIterator]();
5531
+ const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
4671
5532
  options.signal.addEventListener("abort", () => void iterator.return?.(void 0)?.catch(() => void 0), { once: true });
4672
5533
  for (let next = await iterator.next(); next.done !== true; next = await iterator.next()) {
4673
5534
  const owned = parseJSONRPCMessage(next.value, {
@@ -4686,22 +5547,22 @@ var MCPServer = class {
4686
5547
  slot.abort();
4687
5548
  }
4688
5549
  }
4689
- #named(request) {
5550
+ #readTaskId(request) {
4690
5551
  const id = request.id;
4691
5552
  const context = parseRequestContext(request, {
4692
5553
  bytes: this.#limits.message,
4693
5554
  depth: this.#limits.depth
4694
5555
  });
4695
- 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]: {} } } });
4696
5557
  const taskId = request.params?.["taskId"];
4697
5558
  if (!isBoundedString(taskId, this.#limits.state) || taskId.length === 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: a bounded string `taskId` is required");
4698
5559
  return taskId;
4699
5560
  }
4700
5561
  async #task(request, tasks, options) {
4701
5562
  const id = request.id;
4702
- const named = this.#named(request);
4703
- if (!(0, _orkestrel_contract.isString)(named)) return named;
4704
- 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);
4705
5566
  if (found === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4706
5567
  const owned = snapshotJSON(found, {
4707
5568
  bytes: this.#limits.content,
@@ -4713,20 +5574,20 @@ var MCPServer = class {
4713
5574
  }
4714
5575
  async #update(request, tasks, options) {
4715
5576
  const id = request.id;
4716
- const named = this.#named(request);
4717
- if (!(0, _orkestrel_contract.isString)(named)) return named;
5577
+ const taskId = this.#readTaskId(request);
5578
+ if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
4718
5579
  const responses = request.params?.["inputResponses"];
4719
5580
  if (!(0, _orkestrel_contract.isRecord)(responses)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an `inputResponses` object is required");
4720
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4721
- 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);
4722
5583
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
4723
5584
  }
4724
5585
  async #abort(request, tasks, options) {
4725
5586
  const id = request.id;
4726
- const named = this.#named(request);
4727
- if (!(0, _orkestrel_contract.isString)(named)) return named;
4728
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4729
- 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);
4730
5591
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
4731
5592
  }
4732
5593
  #contain(error, id) {
@@ -4776,8 +5637,8 @@ var MCPServer = class {
4776
5637
  //#endregion
4777
5638
  //#region src/core/MCPTaskClient.ts
4778
5639
  /**
4779
- * The CLIENT half of the stable Tasks extension — the `tasks/*` methods over one
4780
- * 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
4781
5642
  * `tasks`.
4782
5643
  *
4783
5644
  * @remarks
@@ -4836,9 +5697,9 @@ var MCPTaskClient = class {
4836
5697
  //#endregion
4837
5698
  //#region src/core/MCPClient.ts
4838
5699
  /**
4839
- * A transport-agnostic Model Context Protocol CLIENT connects to a REMOTE MCP server
4840
- * over an injected {@link MCPClientTransportInterface}, negotiates the modern revision, and
4841
- * 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.
4842
5703
  *
4843
5704
  * @remarks
4844
5705
  * - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
@@ -4929,7 +5790,7 @@ var MCPClient = class {
4929
5790
  });
4930
5791
  this.#transport = options.transport;
4931
5792
  this.#identity = options.identity ?? {
4932
- name: "taverna",
5793
+ name: "@orkestrel/mcp",
4933
5794
  version: "1.0.0"
4934
5795
  };
4935
5796
  this.#capabilities = options.capabilities ?? {};
@@ -5045,7 +5906,7 @@ var MCPClient = class {
5045
5906
  name,
5046
5907
  arguments: args,
5047
5908
  ...input === void 0 ? {} : {
5048
- requestState: input.state,
5909
+ ...input.state === void 0 ? {} : { requestState: input.state },
5049
5910
  inputResponses: input.responses
5050
5911
  }
5051
5912
  }, this.#timeout, void 0, options));
@@ -5070,18 +5931,19 @@ var MCPClient = class {
5070
5931
  } }
5071
5932
  }
5072
5933
  };
5073
- const subscription = {
5074
- queue: [],
5075
- capacity
5076
- };
5077
5934
  const abort = this.#abortSubscription.bind(this, id, signal);
5078
5935
  signal.addEventListener("abort", abort, { once: true });
5079
5936
  this.#pending.set(id, {
5080
5937
  method,
5081
5938
  signal,
5082
5939
  abort,
5083
- subscription
5940
+ subscription: {
5941
+ queue: [],
5942
+ capacity
5943
+ }
5084
5944
  });
5945
+ const subscription = this.#pending.get(id)?.subscription;
5946
+ if (subscription === void 0) throw new Error("MCP subscription state is missing");
5085
5947
  this.#transport.send(request).catch((error) => this.#settle(id, error, true));
5086
5948
  try {
5087
5949
  for (;;) {
@@ -5407,13 +6269,222 @@ var MCPClient = class {
5407
6269
  }
5408
6270
  };
5409
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
5410
6481
  //#region src/core/factories.ts
5411
6482
  /**
5412
6483
  * Creates a transport-agnostic Model Context Protocol server — exposes a live
5413
6484
  * {@link import('@orkestrel/tool').ToolManagerInterface} and an optional
5414
6485
  * {@link import('./types.js').MCPResourceManagerInterface},
5415
6486
  * {@link import('./types.js').MCPPromptManagerInterface}, and
5416
- * {@link import('./types.js').MCPCompletionManagerInterface} over JSON-RPC 2.0.
6487
+ * {@link import('./types.js').MCPCompletionInterface} over JSON-RPC 2.0.
5417
6488
  *
5418
6489
  * @remarks
5419
6490
  * Pump raw message strings through `handle` (parse → dispatch → serialize) from a
@@ -5465,7 +6536,7 @@ function createMCPLegacy(server) {
5465
6536
  }
5466
6537
  /**
5467
6538
  * Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
5468
- * MCP server over an injected {@link import('./types.js').MCPClientTransportInterface},
6539
+ * MCP server over an injected {@link import('./types.js').MCPMessageTransportInterface},
5469
6540
  * negotiates the modern revision through `server/discover`, and exposes the server's tools as local
5470
6541
  * {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
5471
6542
  *
@@ -5496,7 +6567,7 @@ function createMCPLegacy(server) {
5496
6567
  * })
5497
6568
  * await client.connect()
5498
6569
  * agent.context.tools.add(await client.tools()) // give the agent the remote tools
5499
- * const value = await client.call('search', { query: 'mcp' })
6570
+ * const outcome = await client.call('search', { query: 'mcp' })
5500
6571
  * ```
5501
6572
  */
5502
6573
  function createMCPClient(options) {
@@ -5522,7 +6593,7 @@ function createMCPLegacyClientTransport(transport, options) {
5522
6593
  }
5523
6594
  /**
5524
6595
  * Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
5525
- * channel) into a {@link MCPClientTransportInterface} — the additive bridge that lets
6596
+ * channel) into a {@link MCPMessageTransportInterface} — the additive bridge that lets
5526
6597
  * `createMCPClient` run over the new port without any change to `MCPClient`'s
5527
6598
  * existing shape.
5528
6599
  *
@@ -5545,7 +6616,7 @@ function createMCPLegacyClientTransport(transport, options) {
5545
6616
  * capable emitter for `bindClient` to push onto.
5546
6617
  *
5547
6618
  * @param transport - The duplex channel to adapt
5548
- * @returns A {@link MCPClientTransportInterface} `createMCPClient` can drive
6619
+ * @returns A {@link MCPMessageTransportInterface} `createMCPClient` can drive
5549
6620
  *
5550
6621
  * @example
5551
6622
  * ```ts
@@ -5576,6 +6647,7 @@ exports.DEFAULT_MCP_LIMITS = DEFAULT_MCP_LIMITS;
5576
6647
  exports.DEFAULT_MCP_REQUEST_TIMEOUT = DEFAULT_MCP_REQUEST_TIMEOUT;
5577
6648
  exports.DEFAULT_MCP_SUBSCRIPTION_CAPACITY = DEFAULT_MCP_SUBSCRIPTION_CAPACITY;
5578
6649
  exports.EMPTY_MCP_ARGUMENTS = EMPTY_MCP_ARGUMENTS;
6650
+ exports.HTTPClientTransport = HTTPClientTransport;
5579
6651
  exports.JSONRPC_INTERNAL_ERROR = JSONRPC_INTERNAL_ERROR;
5580
6652
  exports.JSONRPC_INVALID_PARAMS = JSONRPC_INVALID_PARAMS;
5581
6653
  exports.JSONRPC_INVALID_REQUEST = JSONRPC_INVALID_REQUEST;
@@ -5595,15 +6667,25 @@ exports.MCPTextStreamController = MCPTextStreamController;
5595
6667
  exports.MCP_EXTENSION_TASKS = MCP_EXTENSION_TASKS;
5596
6668
  exports.MCP_FALLBACK_VERSION = MCP_FALLBACK_VERSION;
5597
6669
  exports.MCP_HANDSHAKE_VERSION = MCP_HANDSHAKE_VERSION;
6670
+ exports.MCP_HEADER_ANNOTATION = MCP_HEADER_ANNOTATION;
5598
6671
  exports.MCP_HEADER_MISMATCH = MCP_HEADER_MISMATCH;
6672
+ exports.MCP_LOOKUP_PAGES = MCP_LOOKUP_PAGES;
5599
6673
  exports.MCP_META_CAPABILITIES = MCP_META_CAPABILITIES;
5600
6674
  exports.MCP_META_CLIENT = MCP_META_CLIENT;
5601
6675
  exports.MCP_META_SERVER = MCP_META_SERVER;
5602
6676
  exports.MCP_META_SUBSCRIPTION = MCP_META_SUBSCRIPTION;
5603
6677
  exports.MCP_META_VERSION = MCP_META_VERSION;
6678
+ exports.MCP_METHOD_HEADER = MCP_METHOD_HEADER;
5604
6679
  exports.MCP_MISSING_CAPABILITY = MCP_MISSING_CAPABILITY;
5605
6680
  exports.MCP_MODERN_VERSION = MCP_MODERN_VERSION;
6681
+ exports.MCP_NAME_HEADER = MCP_NAME_HEADER;
6682
+ exports.MCP_PARAM_PREFIX = MCP_PARAM_PREFIX;
6683
+ exports.MCP_PROTOCOL_VERSION_HEADER = MCP_PROTOCOL_VERSION_HEADER;
6684
+ exports.MCP_SENTINEL_PREFIX = MCP_SENTINEL_PREFIX;
6685
+ exports.MCP_SENTINEL_SUFFIX = MCP_SENTINEL_SUFFIX;
6686
+ exports.MCP_SESSION_HEADER = MCP_SESSION_HEADER;
5606
6687
  exports.MCP_UNSUPPORTED_VERSION = MCP_UNSUPPORTED_VERSION;
6688
+ exports.MCP_WEBSOCKET_SUBPROTOCOL = MCP_WEBSOCKET_SUBPROTOCOL;
5607
6689
  exports.SUPPORTED_LEGACY_PROTOCOL_VERSIONS = SUPPORTED_LEGACY_PROTOCOL_VERSIONS;
5608
6690
  exports.SUPPORTED_MCP_VERSIONS = SUPPORTED_MCP_VERSIONS;
5609
6691
  exports.SUPPORTED_MODERN_PROTOCOL_VERSIONS = SUPPORTED_MODERN_PROTOCOL_VERSIONS;
@@ -5612,33 +6694,45 @@ exports.bindServer = bindServer;
5612
6694
  exports.buildCallOutcome = buildCallOutcome;
5613
6695
  exports.buildCancelledNotification = buildCancelledNotification;
5614
6696
  exports.buildDiscoverResult = buildDiscoverResult;
6697
+ exports.buildHeaderParameters = buildHeaderParameters;
6698
+ exports.buildHeaderProjection = buildHeaderProjection;
5615
6699
  exports.buildInitializeResult = buildInitializeResult;
5616
6700
  exports.buildJSONRPCError = buildJSONRPCError;
5617
6701
  exports.buildJSONRPCResult = buildJSONRPCResult;
5618
6702
  exports.buildMethodOptions = buildMethodOptions;
5619
6703
  exports.buildModernResult = buildModernResult;
5620
6704
  exports.buildProgressNotification = buildProgressNotification;
6705
+ exports.buildResponseError = buildResponseError;
5621
6706
  exports.buildSubscriptionAcknowledgement = buildSubscriptionAcknowledgement;
5622
6707
  exports.buildSubscriptionFilter = buildSubscriptionFilter;
5623
6708
  exports.buildSubscriptionResult = buildSubscriptionResult;
5624
6709
  exports.buildToolCall = buildToolCall;
5625
6710
  exports.buildToolDescriptors = buildToolDescriptors;
6711
+ exports.computeMissingCapabilities = computeMissingCapabilities;
6712
+ exports.countHeaderAnnotations = countHeaderAnnotations;
5626
6713
  exports.createDuplexClientTransport = createDuplexClientTransport;
5627
6714
  exports.createMCPClient = createMCPClient;
5628
6715
  exports.createMCPLegacy = createMCPLegacy;
5629
6716
  exports.createMCPLegacyClientTransport = createMCPLegacyClientTransport;
5630
6717
  exports.createMCPServer = createMCPServer;
5631
6718
  exports.decodeBoundedMessage = decodeBoundedMessage;
6719
+ exports.decodeEvent = decodeEvent;
6720
+ exports.decodeSentinel = decodeSentinel;
6721
+ exports.deliverMessage = deliverMessage;
5632
6722
  exports.digestJSON = digestJSON;
6723
+ exports.encodeSentinel = encodeSentinel;
5633
6724
  exports.extractContentText = extractContentText;
6725
+ exports.extractHeaderAnnotations = extractHeaderAnnotations;
6726
+ exports.extractToolSchema = extractToolSchema;
5634
6727
  exports.inferEra = inferEra;
6728
+ exports.inferRequestEra = inferRequestEra;
5635
6729
  exports.inferRequestVersion = inferRequestVersion;
5636
6730
  exports.inferVersion = inferVersion;
5637
6731
  exports.isAbsoluteURI = isAbsoluteURI;
5638
6732
  exports.isBoundedJSON = isBoundedJSON;
5639
6733
  exports.isBoundedString = isBoundedString;
5640
6734
  exports.isElicitContent = isElicitContent;
5641
- exports.isFormElicitationSupported = isFormElicitationSupported;
6735
+ exports.isFieldToken = isFieldToken;
5642
6736
  exports.isInitializeRequest = isInitializeRequest;
5643
6737
  exports.isJSONObject = isJSONObject;
5644
6738
  exports.isJSONRPCError = isJSONRPCError;
@@ -5666,10 +6760,12 @@ exports.isMCPElicitResult = isMCPElicitResult;
5666
6760
  exports.isMCPElicitSchema = isMCPElicitSchema;
5667
6761
  exports.isMCPElicitURL = isMCPElicitURL;
5668
6762
  exports.isMCPError = isMCPError;
6763
+ exports.isMCPHeaderPrimitive = isMCPHeaderPrimitive;
5669
6764
  exports.isMCPIcon = isMCPIcon;
5670
6765
  exports.isMCPIdentity = isMCPIdentity;
5671
6766
  exports.isMCPInputRequest = isMCPInputRequest;
5672
6767
  exports.isMCPInputRequestMap = isMCPInputRequestMap;
6768
+ exports.isMCPInputResponse = isMCPInputResponse;
5673
6769
  exports.isMCPInputResult = isMCPInputResult;
5674
6770
  exports.isMCPLegacyResult = isMCPLegacyResult;
5675
6771
  exports.isMCPLegacyVersion = isMCPLegacyVersion;
@@ -5692,6 +6788,10 @@ exports.isMCPResourceTemplate = isMCPResourceTemplate;
5692
6788
  exports.isMCPResourceTemplatePage = isMCPResourceTemplatePage;
5693
6789
  exports.isMCPResult = isMCPResult;
5694
6790
  exports.isMCPResultMetaObject = isMCPResultMetaObject;
6791
+ exports.isMCPRoot = isMCPRoot;
6792
+ exports.isMCPRootResult = isMCPRootResult;
6793
+ exports.isMCPSampleContent = isMCPSampleContent;
6794
+ exports.isMCPSampleResult = isMCPSampleResult;
5695
6795
  exports.isMCPServerCapabilities = isMCPServerCapabilities;
5696
6796
  exports.isMCPStringArguments = isMCPStringArguments;
5697
6797
  exports.isMCPSubscriptionFilter = isMCPSubscriptionFilter;
@@ -5707,7 +6807,6 @@ exports.isModernRequest = isModernRequest;
5707
6807
  exports.isRFC3339Date = isRFC3339Date;
5708
6808
  exports.isRFC3339DateTime = isRFC3339DateTime;
5709
6809
  exports.isStandardBase64 = isStandardBase64;
5710
- exports.isTaskSupported = isTaskSupported;
5711
6810
  exports.legacyInvocationToModern = legacyInvocationToModern;
5712
6811
  exports.legacyResultToModern = legacyResultToModern;
5713
6812
  exports.matchesResultType = matchesResultType;
@@ -5718,10 +6817,14 @@ exports.parseJSONRPCMessage = parseJSONRPCMessage;
5718
6817
  exports.parseMCPInputState = parseMCPInputState;
5719
6818
  exports.parseRequestContext = parseRequestContext;
5720
6819
  exports.readCancelledId = readCancelledId;
6820
+ exports.readEventStream = readEventStream;
6821
+ exports.renderHeaderValue = renderHeaderValue;
5721
6822
  exports.sendStream = sendStream;
5722
6823
  exports.serializeJSON = serializeJSON;
5723
6824
  exports.snapshotJSON = snapshotJSON;
5724
6825
  exports.snapshotToolResult = snapshotToolResult;
5725
6826
  exports.stampSubscriptionNotification = stampSubscriptionNotification;
6827
+ exports.supportsFormElicitation = supportsFormElicitation;
6828
+ exports.supportsTask = supportsTask;
5726
6829
 
5727
6830
  //# sourceMappingURL=index.cjs.map