@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,9 +1,11 @@
1
- import { arrayOf, attempt, cloneJSONRecord, cloneJSONValue, isArray, isBoolean, isFiniteNumber, isInteger, isJSONValue, isNumber, isRecord, isString, isUndefined, sanitizeBudget } from "@orkestrel/contract";
1
+ import { arrayOf, attempt, cloneJSONRecord, cloneJSONValue, isArray, isBoolean, isFiniteNumber, isInteger, isJSONValue, isNumber, isRecord, isString, isUndefined, parseJSON, sanitizeBudget } from "@orkestrel/contract";
2
+ import { decodeBase64, decodeUTF8, encodeBase64, encodeHex } from "@orkestrel/codec";
3
+ import { createSSEParser } from "@orkestrel/sse";
2
4
  import { Emitter } from "@orkestrel/emitter";
3
5
  import { Tool } from "@orkestrel/tool";
4
6
  //#region src/core/constants.ts
5
7
  /**
6
- * The revision offered and defaulted to in the legacy `initialize` handshake.
8
+ * Names the revision offered and defaulted to in the legacy `initialize` handshake.
7
9
  *
8
10
  * @remarks
9
11
  * This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
@@ -11,12 +13,12 @@ import { Tool } from "@orkestrel/tool";
11
13
  * it is asking to negotiate a revision with no negotiation.
12
14
  */
13
15
  var MCP_HANDSHAKE_VERSION = "2025-11-25";
14
- /** The older legacy revision the optional legacy decorator accepts and an adapter can pin. */
16
+ /** Names the older legacy revision the optional legacy decorator accepts and an adapter can pin. */
15
17
  var MCP_FALLBACK_VERSION = "2025-06-18";
16
- /** The modern revision offered by an unpinned client during discovery. */
18
+ /** Names the modern revision offered by an unpinned client during discovery. */
17
19
  var MCP_MODERN_VERSION = "2026-07-28";
18
20
  /**
19
- * The modern MCP protocol revisions a bare server accepts and advertises.
21
+ * Lists the modern MCP protocol revisions a bare server accepts and advertises.
20
22
  *
21
23
  * @remarks
22
24
  * Frozen in discovery-advertisement order. Legacy revisions are absent because
@@ -24,22 +26,25 @@ var MCP_MODERN_VERSION = "2026-07-28";
24
26
  * decorator own them.
25
27
  */
26
28
  var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
27
- /** The protocol revisions accepted by the optional legacy decorator. */
29
+ /** Lists the protocol revisions accepted by the optional legacy decorator. */
28
30
  var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
29
- /** The protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy eras. */
31
+ /**
32
+ * Lists the protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy
33
+ * eras.
34
+ */
30
35
  var SUPPORTED_MCP_VERSIONS = Object.freeze([...SUPPORTED_MODERN_PROTOCOL_VERSIONS, ...SUPPORTED_LEGACY_PROTOCOL_VERSIONS]);
31
- /** Reserved modern `_meta` key carrying the request's protocol revision. */
36
+ /** Names the reserved modern `_meta` key carrying the request's protocol revision. */
32
37
  var MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
33
- /** Reserved modern `_meta` key carrying the client's open capability record. */
38
+ /** Names the reserved modern `_meta` key carrying the client's open capability record. */
34
39
  var MCP_META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
35
- /** Reserved modern `_meta` key carrying the optional client identity. */
40
+ /** Names the reserved modern `_meta` key carrying the optional client identity. */
36
41
  var MCP_META_CLIENT = "io.modelcontextprotocol/clientInfo";
37
- /** Reserved modern `_meta` key carrying the server identity on results. */
42
+ /** Names the reserved modern `_meta` key carrying the server identity on results. */
38
43
  var MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
39
- /** Reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
44
+ /** Names the reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
40
45
  var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
41
46
  /**
42
- * The reserved extension key identifying the stable Tasks extension.
47
+ * Names the reserved extension key identifying the stable Tasks extension.
43
48
  *
44
49
  * @remarks
45
50
  * The ONE spelling of it in this package, and the identity of the immutable snapshot dated
@@ -49,10 +54,107 @@ var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
49
54
  * the extension defines no options, so presence is the entire declaration.
50
55
  */
51
56
  var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
52
- /** MCP reserved error: required HTTP metadata does not match the request body. */
57
+ /**
58
+ * Names the opening marker of the Base64 sentinel a standard MCP header value travels in.
59
+ *
60
+ * @remarks
61
+ * The markers are LOWERCASE and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
62
+ * their ONE spelling in this package: {@link import('@orkestrel/mcp').encodeSentinel} builds a
63
+ * sentinel from them and {@link import('@orkestrel/mcp').decodeSentinel} recognizes one by
64
+ * them, so the two directions cannot drift apart.
65
+ */
66
+ var MCP_SENTINEL_PREFIX = "=?base64?";
67
+ /** Names the closing marker of the Base64 sentinel a standard MCP header value travels in. */
68
+ var MCP_SENTINEL_SUFFIX = "?=";
69
+ /**
70
+ * Names the request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
71
+ *
72
+ * @remarks
73
+ * The full field name is this prefix followed by the annotation's own value verbatim, so
74
+ * `x-mcp-header: 'Region'` becomes `Mcp-Param-Region`. HTTP field names are case-insensitive,
75
+ * which is why {@link MCP_HEADER_ANNOTATION} values are unique case-insensitively within one
76
+ * `inputSchema`.
77
+ */
78
+ var MCP_PARAM_PREFIX = "Mcp-Param-";
79
+ /**
80
+ * Names the Streamable-HTTP transport header that carries the MCP session id.
81
+ *
82
+ * @remarks
83
+ * A STATEFUL server sends it on the `initialize` reply, and
84
+ * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} echoes it as a
85
+ * request header on every subsequent request, so a client passes that server's session
86
+ * validation unchanged.
87
+ */
88
+ var MCP_SESSION_HEADER = "mcp-session-id";
89
+ /**
90
+ * Names the Streamable-HTTP transport header carrying the MCP protocol version.
91
+ *
92
+ * @remarks
93
+ * A modern request derives it from its own `_meta`; a legacy request echoes the revision the
94
+ * `initialize` result negotiated on each subsequent request.
95
+ */
96
+ var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
97
+ /**
98
+ * Names the modern Streamable-HTTP request header carrying the JSON-RPC method.
99
+ *
100
+ * @remarks
101
+ * It is stamped on every modern request and on no legacy request.
102
+ */
103
+ var MCP_METHOD_HEADER = "mcp-method";
104
+ /**
105
+ * Names the modern Streamable-HTTP request header carrying a named target.
106
+ *
107
+ * @remarks
108
+ * The HTTP client transport stamps it only for `tools/call`, from that request's `params.name`,
109
+ * in the Base64 sentinel form whenever the name cannot ride as plain ASCII.
110
+ */
111
+ var MCP_NAME_HEADER = "mcp-name";
112
+ /**
113
+ * Identifies the tool-schema annotation key naming the header one parameter projects into.
114
+ *
115
+ * @remarks
116
+ * It is valid ONLY on a primitive property schema statically reachable from the `inputSchema`
117
+ * root through `properties` keys alone. An occurrence anywhere else — under `items`, a
118
+ * composition or conditional keyword, or a `$ref` target — makes the whole tool definition
119
+ * invalid, which is what {@link import('@orkestrel/mcp').buildHeaderParameters} decides.
120
+ */
121
+ var MCP_HEADER_ANNOTATION = "x-mcp-header";
122
+ /**
123
+ * Names the WebSocket subprotocol `createWebSocketClientTransport` requests by default —
124
+ * `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
125
+ * §4.1 a client MUST fail the connection if the server returns
126
+ * a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
127
+ * default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
128
+ * only when connecting to a foreign server that speaks a different subprotocol (or `[]`
129
+ * for no subprotocol negotiation at all).
130
+ *
131
+ * @remarks
132
+ * The client sends it in `Sec-WebSocket-Protocol` and the server echoes it in its `101`
133
+ * handshake, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
134
+ * same path. The default WebSocket upgrade path is the same `'/mcp'` the HTTP transport mounts
135
+ * at — the upgrade is selected by the `Upgrade: websocket` header, not a separate path.
136
+ */
137
+ var MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
138
+ /**
139
+ * Bounds the `tools/list` pages one modern `tools/call` walks to reach its own annotations.
140
+ *
141
+ * @remarks
142
+ * The HTTP POST handler reads a called tool's {@link MCP_HEADER_ANNOTATION} annotations by
143
+ * dispatching `tools/list` fresh on every `tools/call`, following `nextCursor` until the
144
+ * named tool is found or the answer carries no cursor. The walk is bounded because its cost
145
+ * is paid per call: at a page size of 100 this bound reaches 800 definitions, and a consumer
146
+ * whose replacement `tools/list` pages more finely than that pays the extra dispatches on
147
+ * every call it serves. The built-in listing answers the whole registry on one page and
148
+ * never reaches the second. A definition further in than the walk reaches reads as no
149
+ * definition, so its {@link MCP_PARAM_PREFIX} headers are forwarded untouched — the same
150
+ * answer a name no served definition annotates receives.
151
+ */
152
+ var MCP_LOOKUP_PAGES = 8;
153
+ /** Names the MCP reserved error for required HTTP metadata that does not match the request body. */
53
154
  var MCP_HEADER_MISMATCH = -32020;
54
155
  /**
55
- * MCP reserved error: an operation needs a client capability that was not declared.
156
+ * Names the MCP reserved error for an operation needing a client capability that was not
157
+ * declared.
56
158
  *
57
159
  * @remarks
58
160
  * The GENERIC code for the whole condition, not one capability's code. This server answers
@@ -65,10 +167,10 @@ var MCP_HEADER_MISMATCH = -32020;
65
167
  * schema is what a peer implements against.
66
168
  */
67
169
  var MCP_MISSING_CAPABILITY = -32021;
68
- /** MCP reserved error: a request names an unsupported protocol revision. */
170
+ /** Names the MCP reserved error for a request naming an unsupported protocol revision. */
69
171
  var MCP_UNSUPPORTED_VERSION = -32022;
70
172
  /**
71
- * Default modern result freshness lifetime in milliseconds.
173
+ * Sets the default modern result freshness lifetime in milliseconds.
72
174
  *
73
175
  * @remarks
74
176
  * `ttlMs` is required on cacheable results, while zero means immediately stale
@@ -76,16 +178,18 @@ var MCP_UNSUPPORTED_VERSION = -32022;
76
178
  */
77
179
  var DEFAULT_MCP_CACHE_TTL = 6e4;
78
180
  /**
79
- * Secure server bounds used when the matching `limit` option leaf is absent or malformed.
181
+ * Sets the secure server bounds used when the matching `limit` option leaf is absent or
182
+ * malformed.
80
183
  *
81
184
  * @remarks
82
185
  * One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
83
186
  * extension-rich modern metadata and signed multi-round state; four MiB admits substantial
84
187
  * JSON tool output without allowing an unconfigured service to serialize arbitrary process
85
- * memory; 64 metadata keys admits the reserved keys plus many extensions; 128 concurrent
86
- * streams admits a busy service while bounding retained producers; depth 32 admits ordinary
87
- * JSON documents while rejecting stack-hostile nesting. Frozen so callers cannot alter the
88
- * defaults observed by later servers.
188
+ * memory; 64 keys admits `_meta`'s reserved keys plus many extensions, and bounds a produced
189
+ * result's breadth by the same leaf; 128 concurrent streams admits a busy service while
190
+ * bounding retained producers; depth 32 admits ordinary JSON documents while rejecting
191
+ * stack-hostile nesting. Frozen so callers cannot alter the defaults observed by later
192
+ * servers.
89
193
  */
90
194
  var DEFAULT_MCP_LIMITS = Object.freeze({
91
195
  message: 1048576,
@@ -97,7 +201,7 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
97
201
  depth: 32
98
202
  });
99
203
  /**
100
- * The one empty argument record every argument-less modern `tools/call` runs with.
204
+ * Holds the one empty argument record every argument-less modern `tools/call` runs with.
101
205
  *
102
206
  * @remarks
103
207
  * Frozen and null-prototype, and SHARED: two calls that name no `arguments` receive the same
@@ -111,16 +215,17 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
111
215
  * `arguments.constructor` is `undefined` here rather than a function.
112
216
  */
113
217
  var EMPTY_MCP_ARGUMENTS = Object.freeze(Object.create(null));
114
- /** JSON-RPC 2.0 reserved error: invalid JSON was received (the message did not parse). */
218
+ /** Names the JSON-RPC 2.0 reserved error for invalid JSON received (the message did not parse). */
115
219
  var JSONRPC_PARSE_ERROR = -32700;
116
- /** JSON-RPC 2.0 reserved error: the payload was not a valid Request object. */
220
+ /** Names the JSON-RPC 2.0 reserved error for a payload that was not a valid Request object. */
117
221
  var JSONRPC_INVALID_REQUEST = -32600;
118
- /** JSON-RPC 2.0 reserved error: the requested method does not exist. */
222
+ /** Names the JSON-RPC 2.0 reserved error for a requested method that does not exist. */
119
223
  var JSONRPC_METHOD_NOT_FOUND = -32601;
120
- /** JSON-RPC 2.0 reserved error: the method's parameters were invalid. */
224
+ /** Names the JSON-RPC 2.0 reserved error for a method's invalid parameters. */
121
225
  var JSONRPC_INVALID_PARAMS = -32602;
122
226
  /**
123
- * JSON-RPC 2.0 reserved error: the server failed while handling an otherwise valid request.
227
+ * Names the JSON-RPC 2.0 reserved error for a server that failed while handling an otherwise
228
+ * valid request.
124
229
  *
125
230
  * @remarks
126
231
  * The code every MODERN internal fault answers with — a provider, handler, continuation,
@@ -130,7 +235,7 @@ var JSONRPC_INVALID_PARAMS = -32602;
130
235
  */
131
236
  var JSONRPC_INTERNAL_ERROR = -32603;
132
237
  /**
133
- * JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
238
+ * Names the JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
134
239
  *
135
240
  * @remarks
136
241
  * Retained for the LEGACY branch alone. A modern fault answers
@@ -138,21 +243,27 @@ var JSONRPC_INTERNAL_ERROR = -32603;
138
243
  * already characterized against it.
139
244
  */
140
245
  var JSONRPC_SERVER_ERROR = -32e3;
141
- /** The default client name reported in the MCP `initialize` handshake (`clientInfo.name`). */
142
- var DEFAULT_MCP_CLIENT_NAME = "taverna";
143
- /** The default client version reported in the MCP `initialize` handshake (`clientInfo.version`). */
246
+ /**
247
+ * Supplies the default client name reported in the MCP `initialize` handshake
248
+ * (`clientInfo.name`).
249
+ */
250
+ var DEFAULT_MCP_CLIENT_NAME = "@orkestrel/mcp";
251
+ /**
252
+ * Supplies the default client version reported in the MCP `initialize` handshake
253
+ * (`clientInfo.version`).
254
+ */
144
255
  var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
145
256
  /**
146
- * The default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
257
+ * Sets the default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
147
258
  * is unset — a request the remote server does not answer within it rejects.
148
259
  */
149
260
  var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
150
- /** The default number of subscription frames retained while no client read is parked. */
261
+ /** Sets the default number of subscription frames retained while no client read is parked. */
151
262
  var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
152
263
  //#endregion
153
264
  //#region src/core/errors.ts
154
265
  /**
155
- * A Model Context Protocol error preserving its machine-readable numeric code and
266
+ * Preserves a Model Context Protocol error's machine-readable numeric code and
156
267
  * optional structured context.
157
268
  *
158
269
  * @remarks
@@ -195,7 +306,7 @@ var MCPError = class extends Error {
195
306
  * Determines whether an unknown value is an {@link MCPError}.
196
307
  *
197
308
  * @param value - The unknown value to inspect
198
- * @returns `true` only when the value is an `MCPError`
309
+ * @returns True if the value is an `MCPError`; false otherwise
199
310
  *
200
311
  * @example
201
312
  * ```ts
@@ -431,49 +542,48 @@ function parseRequestContext(value, limits = {
431
542
  * This parser does not open the opaque continuation carrier; the configured
432
543
  * continuation port performs that boundary first. The protected
433
544
  * payload binds the authenticated principal, absolute expiry, ORIGINAL request id, version,
434
- * method, server-assigned key, tool name, argument digest, the exact issued elicitation
435
- * schema, and optional application state. Every member is required except application state:
436
- * a payload missing its schema cannot have its accepted response enforced, so it is refused
437
- * rather than admitted unenforced. Total over malformed or hostile input.
545
+ * method, the exact round that was issued, tool name, argument digest, and optional
546
+ * application state. Every member is required except application state: a payload missing its
547
+ * round cannot have the client's answers enforced, so it is refused rather than admitted
548
+ * unenforced. An EMPTY round is refused for the same reason — a retry against it would answer
549
+ * no question at all. Total over malformed or hostile input.
438
550
  *
439
551
  * @param value - The opened canonical continuation value to parse
440
552
  * @returns The protected input state, or `undefined` when malformed
441
553
  *
442
554
  * @example
443
555
  * ```ts
444
- * 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":{}}}')
556
+ * parseMCPInputState('{"principal":"user-1","expiry":2000,"id":1,"version":"2026-07-28","method":"tools/call","requests":{"k":{"method":"roots/list"}},"name":"reply","digest":"abc"}')
445
557
  * ```
446
558
  */
447
559
  function parseMCPInputState(value) {
448
560
  try {
449
561
  if (!isString(value)) return void 0;
450
- const parsed = JSON.parse(value);
562
+ const parsed = parseJSON(value);
451
563
  if (!isRecord(parsed)) return void 0;
452
564
  const principal = parsed["principal"];
453
565
  const expiry = parsed["expiry"];
454
566
  const id = parsed["id"];
455
567
  const version = parsed["version"];
456
568
  const method = parsed["method"];
457
- const key = parsed["key"];
569
+ const requests = parsed["requests"];
458
570
  const name = parsed["name"];
459
571
  const digest = parsed["digest"];
460
- const schema = parsed["schema"];
461
572
  const state = parsed["state"];
462
573
  if (!isString(principal) || principal.length === 0 || !isNumber(expiry) || !Number.isFinite(expiry)) return;
463
574
  if (!isJSONRPCId(id)) return void 0;
464
- if (!isString(version) || !isString(method) || !isString(key) || !isString(name)) return;
575
+ if (!isString(version) || !isString(method) || !isString(name)) return void 0;
465
576
  if (!isString(digest) || !isUndefined(state) && !isJSONValue(state)) return void 0;
466
- if (!isMCPElicitSchema(schema)) return void 0;
577
+ if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
467
578
  return {
468
579
  principal,
469
580
  expiry,
470
581
  id,
471
582
  version,
472
583
  method,
473
- key,
584
+ requests,
474
585
  name,
475
586
  digest,
476
- schema,
477
587
  ...isUndefined(state) ? {} : { state }
478
588
  };
479
589
  } catch {
@@ -491,15 +601,15 @@ function parseMCPInputState(value) {
491
601
  * does not authorize a form request. Total over hostile input.
492
602
  *
493
603
  * @param value - The client capability record to inspect
494
- * @returns `true` when form-mode elicitation is declared
604
+ * @returns True if form-mode elicitation is declared; false otherwise
495
605
  *
496
606
  * @example
497
607
  * ```ts
498
- * isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
499
- * isFormElicitationSupported({ elicitation: { url: {} } }) // false
608
+ * supportsFormElicitation({ elicitation: {} }) // true — implicit form mode
609
+ * supportsFormElicitation({ elicitation: { url: {} } }) // false
500
610
  * ```
501
611
  */
502
- function isFormElicitationSupported(value) {
612
+ function supportsFormElicitation(value) {
503
613
  const owned = attempt(() => cloneJSONRecord(value));
504
614
  if (!owned.success) return false;
505
615
  try {
@@ -512,6 +622,68 @@ function isFormElicitationSupported(value) {
512
622
  }
513
623
  }
514
624
  /**
625
+ * Computes the capabilities one round of input requests needs and the client did not declare.
626
+ *
627
+ * @remarks
628
+ * The protocol's rule is about SENDING: a server never issues a request kind the client's
629
+ * declared capabilities exclude. So this reads the round rather than the method, and it
630
+ * answers with the refusal's own payload — the `requiredCapabilities` record a
631
+ * `MissingRequiredClientCapability` error carries, keyed by each missing capability, in the
632
+ * `ClientCapabilities` shape the schema defines rather than as a list of names.
633
+ *
634
+ * Each kind maps to one declaration: `sampling/createMessage` to `sampling`, `roots/list` to
635
+ * `roots`, a form elicitation to what {@link supportsFormElicitation} accepts, and a
636
+ * URL-mode elicitation to a record-valued `elicitation.url`. A request this package cannot
637
+ * recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
638
+ * has already refused the round it would have travelled in. Total over hostile input.
639
+ *
640
+ * The `elicitation` value names the ARM the round needs, so a client can act on the refusal
641
+ * by declaring exactly what the payload asks for. A missing URL arm answers `{ url: {} }`, a
642
+ * missing form arm answers the empty record this package reads as form-only, and a round
643
+ * needing both answers `{ form: {}, url: {} }`. An empty record for a URL round would name
644
+ * the declaration a URL-capable client already sent, and refuse the identical round again.
645
+ *
646
+ * @param requests - The round the server is about to issue
647
+ * @param capabilities - The client capability record the request declared
648
+ * @returns The missing capabilities, or `undefined` when the client declared every one
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * computeMissingCapabilities({ answer: { method: 'roots/list' } }, {}) // { roots: {} }
653
+ * computeMissingCapabilities({ answer: { method: 'roots/list' } }, { roots: {} }) // undefined
654
+ * ```
655
+ */
656
+ function computeMissingCapabilities(requests, capabilities) {
657
+ const owned = attempt(() => cloneJSONRecord(capabilities));
658
+ const declared = owned.success ? owned.value : {};
659
+ const missing = {};
660
+ let formUndeclared = false;
661
+ let urlUndeclared = false;
662
+ for (const request of Object.values(requests)) {
663
+ if (request.method === "sampling/createMessage") {
664
+ if (!isRecord(declared["sampling"])) missing["sampling"] = {};
665
+ continue;
666
+ }
667
+ if (request.method === "roots/list") {
668
+ if (!isRecord(declared["roots"])) missing["roots"] = {};
669
+ continue;
670
+ }
671
+ const elicitation = declared["elicitation"];
672
+ if (request.params.mode === "url") {
673
+ if (!isRecord(elicitation) || !isRecord(elicitation["url"])) urlUndeclared = true;
674
+ continue;
675
+ }
676
+ if (!supportsFormElicitation(declared)) formUndeclared = true;
677
+ }
678
+ if (formUndeclared && !urlUndeclared) missing["elicitation"] = {};
679
+ if (urlUndeclared && !formUndeclared) missing["elicitation"] = { url: {} };
680
+ if (formUndeclared && urlUndeclared) missing["elicitation"] = {
681
+ form: {},
682
+ url: {}
683
+ };
684
+ return Object.keys(missing).length === 0 ? void 0 : Object.freeze(missing);
685
+ }
686
+ /**
515
687
  * Determines whether a client capability record declares the stable Tasks extension.
516
688
  *
517
689
  * @remarks
@@ -528,16 +700,16 @@ function isFormElicitationSupported(value) {
528
700
  * the request in hand. Total over hostile input.
529
701
  *
530
702
  * @param value - The client capability record to inspect
531
- * @returns `true` when the tasks extension is declared as the schema's empty object
703
+ * @returns True if the tasks extension is declared as the schema's empty object; false otherwise
532
704
  *
533
705
  * @example
534
706
  * ```ts
535
- * isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
536
- * isTaskSupported({ extensions: {} }) // false — the key is the declaration
537
- * isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
707
+ * supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
708
+ * supportsTask({ extensions: {} }) // false — the key is the declaration
709
+ * supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
538
710
  * ```
539
711
  */
540
- function isTaskSupported(value) {
712
+ function supportsTask(value) {
541
713
  const owned = attempt(() => cloneJSONRecord(value));
542
714
  if (!owned.success) return false;
543
715
  try {
@@ -779,7 +951,7 @@ async function digestJSON(value, limits) {
779
951
  const serialized = serializeJSON(value, limits);
780
952
  if (serialized === void 0) return void 0;
781
953
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized));
782
- return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
954
+ return encodeHex(new Uint8Array(digest));
783
955
  }
784
956
  /**
785
957
  * Builds one official progress notification for the original request stream.
@@ -814,7 +986,7 @@ function buildProgressNotification(token, progress) {
814
986
  * rather than as a violation.
815
987
  *
816
988
  * Only write one on a carrier that accepts a client-initiated notification — see
817
- * {@link import('./types.js').MCPClientTransportInterface.duplex}. On Streamable HTTP the
989
+ * {@link import('./types.js').MCPMessageTransportInterface.duplex}. On Streamable HTTP the
818
990
  * dated revision defines no such frame, and closing the response stream is the
819
991
  * cancellation signal instead.
820
992
  *
@@ -853,7 +1025,7 @@ function buildCancelledNotification(id, reason) {
853
1025
  *
854
1026
  * @param method - The method the pending request was issued for
855
1027
  * @param resultType - The unknown `resultType` the peer answered with
856
- * @returns Whether that method may legally answer with that `resultType`
1028
+ * @returns True if that method may legally answer with that `resultType`; false otherwise
857
1029
  *
858
1030
  * @example
859
1031
  * ```ts
@@ -1208,7 +1380,7 @@ function buildSubscriptionFilter(requested, supported, enabled = false) {
1208
1380
  *
1209
1381
  * @param notification - The server notification offered by the configured producer
1210
1382
  * @param filter - The filter acknowledged to the client
1211
- * @returns `true` when the notification belongs on this subscription stream
1383
+ * @returns True if the notification belongs on this subscription stream; false otherwise
1212
1384
  */
1213
1385
  function matchesSubscriptionNotification(notification, filter) {
1214
1386
  if (notification.method === "notifications/tools/list_changed") return filter.toolsListChanged === true;
@@ -1330,7 +1502,7 @@ function buildInitializeResult(name, version, requested) {
1330
1502
  *
1331
1503
  * @remarks
1332
1504
  * The bound is checked FIRST, against the raw string, so an oversized message is never
1333
- * `JSON.parse`d at all: a decoder that parses before it measures has already spent the work
1505
+ * parsed at all: a decoder that parses before it measures has already spent the work
1334
1506
  * the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
1335
1507
  * value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
1336
1508
  * exactly the same thing with each of them: nothing, and let
@@ -1350,8 +1522,444 @@ function buildInitializeResult(name, version, requested) {
1350
1522
  */
1351
1523
  function decodeBoundedMessage(message, limits) {
1352
1524
  if (!isBoundedString(message, limits.bytes)) return void 0;
1353
- const parsed = attempt(() => JSON.parse(message));
1354
- return parsed.success ? parseJSONRPCMessage(parsed.value, limits) : void 0;
1525
+ return parseJSONRPCMessage(parseJSON(message), limits);
1526
+ }
1527
+ /**
1528
+ * Decodes one inbound frame and delivers it onto a transport emitter as `message` or `error`.
1529
+ *
1530
+ * @remarks
1531
+ * The ONE inbound fold every message-carrying transport in this package runs: parse the frame,
1532
+ * narrow it with `parseJSONRPCMessage`, emit `message` for a well-formed
1533
+ * {@link JSONRPCMessage}, and emit `error` for anything else. Total — an adversarial frame
1534
+ * produces an `error` emission and never a throw.
1535
+ *
1536
+ * The two failures report differently on purpose. Unparsable text emits the CAUGHT parse
1537
+ * error, which names the offending position; well-formed JSON that is not a JSON-RPC message
1538
+ * has no caught value to report, so it emits `fault` — the carrier's own wording, passed in
1539
+ * rather than forked into a second copy of this body.
1540
+ *
1541
+ * @param emitter - The transport's emitter to deliver onto
1542
+ * @param text - One inbound frame's raw text
1543
+ * @param fault - The message for the error emitted when the frame parses but is not JSON-RPC
1544
+ *
1545
+ * @example
1546
+ * ```ts
1547
+ * deliverMessage(transport.emitter, frame, 'non-JSON-RPC WebSocket frame')
1548
+ * ```
1549
+ */
1550
+ function deliverMessage(emitter, text, fault) {
1551
+ let parsed;
1552
+ try {
1553
+ parsed = JSON.parse(text);
1554
+ } catch (error) {
1555
+ emitter.emit("error", error);
1556
+ return;
1557
+ }
1558
+ const message = parseJSONRPCMessage(parsed);
1559
+ if (message === void 0) {
1560
+ emitter.emit("error", new Error(fault));
1561
+ return;
1562
+ }
1563
+ emitter.emit("message", message);
1564
+ }
1565
+ /**
1566
+ * Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
1567
+ * when it is not one — the per-event step {@link readEventStream} folds over.
1568
+ *
1569
+ * @remarks
1570
+ * Parses the `data` (a peer serializes the JSON-RPC envelope as the event's `data`) with
1571
+ * `@orkestrel/contract`'s `parseJSON` — the declared JSON boundary, which answers `undefined`
1572
+ * instead of throwing — and narrows the parsed value with `parseJSONRPCMessage`. Total:
1573
+ * malformed JSON or a non-message value yields `undefined`, never throws.
1574
+ *
1575
+ * @param data - One SSE event's `data` payload
1576
+ * @returns The decoded {@link JSONRPCMessage}, or `undefined`
1577
+ *
1578
+ * @example
1579
+ * ```ts
1580
+ * decodeEvent('{"jsonrpc":"2.0","id":1,"result":{}}') // the decoded response
1581
+ * ```
1582
+ */
1583
+ function decodeEvent(data) {
1584
+ return parseJSONRPCMessage(parseJSON(data));
1585
+ }
1586
+ /**
1587
+ * Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
1588
+ * carried — the CLIENT-side inverse of a server's Streamable-HTTP SSE response.
1589
+ *
1590
+ * @remarks
1591
+ * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({ stream: true
1592
+ * })` (handling a multi-byte character split across reads) and `@orkestrel/sse`'s
1593
+ * {@link SSEParserInterface} (handling a partial line or in-progress event split across
1594
+ * reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} through
1595
+ * {@link decodeEvent} (so a non-message or non-JSON `data:` event is DROPPED, never thrown —
1596
+ * total). It reuses the SAME `SSEParser` a server's `createStream` seam serializes against, so
1597
+ * the wire round-trips. A `null` body (no stream) yields no messages;
1598
+ * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} reads a
1599
+ * request/response SSE reply (the server sends one `data:` event then ends), so this drains to
1600
+ * completion.
1601
+ *
1602
+ * @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
1603
+ * @returns Every {@link JSONRPCMessage} the stream carried, in order
1604
+ *
1605
+ * @example
1606
+ * ```ts
1607
+ * const messages = await readEventStream(await fetch(url, { method: 'POST', body }))
1608
+ * ```
1609
+ */
1610
+ async function readEventStream(response) {
1611
+ const body = response.body;
1612
+ if (body === null) return [];
1613
+ const reader = body.getReader();
1614
+ const decoder = new TextDecoder();
1615
+ const parser = createSSEParser();
1616
+ const messages = [];
1617
+ try {
1618
+ for (;;) {
1619
+ const { done, value } = await reader.read();
1620
+ if (done) break;
1621
+ for (const event of parser.parse(decoder.decode(value, { stream: true }))) {
1622
+ const message = decodeEvent(event.data);
1623
+ if (message !== void 0) messages.push(message);
1624
+ }
1625
+ }
1626
+ } finally {
1627
+ reader.releaseLock();
1628
+ }
1629
+ return messages;
1630
+ }
1631
+ /**
1632
+ * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
1633
+ *
1634
+ * @param response - The response whose status is reported
1635
+ * @param type - The response's content type, or an empty string when absent
1636
+ * @returns An error naming the HTTP status and unsupported response shape
1637
+ *
1638
+ * @example
1639
+ * ```ts
1640
+ * const error = buildResponseError(new Response('', { status: 500 }), '')
1641
+ * ```
1642
+ */
1643
+ function buildResponseError(response, type) {
1644
+ 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`);
1645
+ 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`);
1646
+ const shape = type === "" ? "a body without a content type" : `an unsupported '${type}' body`;
1647
+ return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained ${shape}`);
1648
+ }
1649
+ /**
1650
+ * Reads the value one standard MCP request header carries, decoding the Base64 sentinel.
1651
+ *
1652
+ * @remarks
1653
+ * The sentinel format is `=?base64?{Base64OfUTF8}?=`, spelled once as
1654
+ * {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX} and read from there by both
1655
+ * directions of the codec.
1656
+ * The markers alone decide whether a value is a sentinel: a value carrying the prefix and the
1657
+ * suffix is one, and its payload is then held to `decodeBase64` from `@orkestrel/codec` — the
1658
+ * canonical RFC 4648 § 4 grammar, which admits exactly one spelling per byte sequence — and to
1659
+ * well-formed UTF-8. A payload leaving a non-zero bit in the sextet its padding discards is a
1660
+ * second spelling of a byte, so it is refused: `=?base64?QR==?=` reaches for the byte
1661
+ * `=?base64?QQ==?=` spells canonically, and only the canonical spelling decodes. A malformed
1662
+ * payload answers `undefined` rather than falling back to the literal, because the protocol
1663
+ * requires a server to REJECT invalid characters, and a fallback would admit the very value
1664
+ * the rule exists to refuse. A value missing either marker is a literal and comes back
1665
+ * unchanged.
1666
+ *
1667
+ * `decodeUTF8` from `@orkestrel/codec` reads the bytes back as text: strict RFC 3629, where an
1668
+ * overlong, an encoded surrogate, a code point past U+10FFFF, and a truncated sequence each
1669
+ * answer `undefined` rather than a replacement character, and total, so the refusal arrives as
1670
+ * that value instead of as a throw. It also keeps a leading U+FEFF as a character of the
1671
+ * value, where the platform decoder consumes it as a byte order mark — which is what lets a
1672
+ * value leading with U+FEFF survive {@link encodeSentinel} and come back whole.
1673
+ *
1674
+ * {@link import('./validators.js').isStandardBase64} is a wider and separate rule: it names
1675
+ * JSON Schema `byte` membership for the blob, image, and audio content a peer sends, where
1676
+ * this package receives liberally. It does not govern this payload.
1677
+ *
1678
+ * Optional whitespace is excluded first, per RFC 9110 § 5.5: a recipient parses a field value
1679
+ * with its surrounding spaces and horizontal tabs removed, so a peer that padded a plain value
1680
+ * still matches the body. A value whose own leading or trailing whitespace is significant
1681
+ * cannot survive that, which is what {@link encodeSentinel} encodes it for.
1682
+ *
1683
+ * Total — never throws, whatever the input.
1684
+ *
1685
+ * @param value - The raw header field value the peer sent
1686
+ * @returns The carried value, or `undefined` when the sentinel's payload is invalid
1687
+ *
1688
+ * @example
1689
+ * ```ts
1690
+ * decodeSentinel('=?base64?Y2Fmw6k=?=') // 'café'
1691
+ * decodeSentinel(' search ') // 'search' — optional whitespace excluded
1692
+ * decodeSentinel('=?base64?SGVsbG8?=') // undefined — invalid padding
1693
+ * decodeSentinel('=?base64?QR==?=') // undefined — a non-canonical spelling
1694
+ * ```
1695
+ */
1696
+ function decodeSentinel(value) {
1697
+ const field = value.replace(/^[ \t]+|[ \t]+$/g, "");
1698
+ if (!(field.length >= 11 && field.startsWith("=?base64?") && field.endsWith("?="))) return field;
1699
+ const payload = field.slice(MCP_SENTINEL_PREFIX.length, field.length - 2);
1700
+ const bytes = decodeBase64(payload);
1701
+ if (bytes === void 0) return void 0;
1702
+ return decodeUTF8(bytes);
1703
+ }
1704
+ /**
1705
+ * Builds the wire form one standard MCP request header value must travel as.
1706
+ *
1707
+ * @remarks
1708
+ * The exact inverse of {@link decodeSentinel}, and its membership rule is stated as that
1709
+ * inverse rather than as a second list that could drift: a value travels LITERALLY when it is
1710
+ * plain printable ASCII — every code point in `U+0020`–`U+007E`, the RFC 9110 field-value
1711
+ * range this package admits — and {@link decodeSentinel} gives it back unchanged. Every other
1712
+ * value travels wrapped in {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX}, the
1713
+ * same markers the decode recognizes a sentinel by. `encodeBase64` from `@orkestrel/codec`
1714
+ * spells the payload, so the wire form carries the canonical spelling {@link decodeSentinel}
1715
+ * accepts.
1716
+ *
1717
+ * That one rule covers each row of the protocol's encoding table. A non-ASCII value and a
1718
+ * value carrying a control character fail the ASCII test. A value with leading or trailing
1719
+ * whitespace comes back trimmed, so it fails the round trip. A value already wearing the
1720
+ * sentinel markers decodes to something else, or to nothing, so it fails the round trip too
1721
+ * and is encoded rather than read back as a sentinel it never was.
1722
+ *
1723
+ * The bytes come from the platform `TextEncoder`, not from codec's `encodeUTF8`, and that is a
1724
+ * ruling rather than an oversight. `TextEncoder` is total: it spells ill-formed text — a lone
1725
+ * surrogate, which has no UTF-8 spelling — with the replacement character, so this function
1726
+ * answers a `string` for every input. `encodeUTF8` refuses that text with `undefined`, which
1727
+ * would widen this return to `string | undefined` and oblige every header projection to handle
1728
+ * a value it cannot send. The decode side carries no such tension, so it reads back through
1729
+ * codec's strict `decodeUTF8`.
1730
+ *
1731
+ * @param value - The value the header must carry
1732
+ * @returns The literal value, or its Base64 sentinel form
1733
+ *
1734
+ * @example
1735
+ * ```ts
1736
+ * encodeSentinel('search') // 'search'
1737
+ * encodeSentinel('café') // '=?base64?Y2Fmw6k=?='
1738
+ * ```
1739
+ */
1740
+ function encodeSentinel(value) {
1741
+ if (/^[ -~]*$/.test(value) && decodeSentinel(value) === value) return value;
1742
+ return `${MCP_SENTINEL_PREFIX}${encodeBase64(new TextEncoder().encode(value))}?=`;
1743
+ }
1744
+ /**
1745
+ * Counts every {@link MCP_HEADER_ANNOTATION} key one JSON value carries, at any position.
1746
+ *
1747
+ * @remarks
1748
+ * The companion of {@link extractHeaderAnnotations}, which reads only the annotations a
1749
+ * `properties` chain reaches. Comparing the two answers is how
1750
+ * {@link buildHeaderParameters} decides reachability without a second walk that would have
1751
+ * to re-state which JSON Schema keywords are traversable: an annotation the reachable walk
1752
+ * did not read is one sitting under `items`, a composition or conditional keyword, a `$ref`
1753
+ * target, or any other position, and the protocol makes the whole tool definition invalid for
1754
+ * it.
1755
+ *
1756
+ * Iterative and ancestor-tracked, so a deeply nested or self-referential value terminates
1757
+ * rather than exhausting the stack. Total — never throws, whatever the input.
1758
+ *
1759
+ * @param value - The value to scan, normally a tool's `inputSchema`
1760
+ * @returns How many annotation keys the value carries
1761
+ *
1762
+ * @example
1763
+ * ```ts
1764
+ * countHeaderAnnotations({ properties: { region: { 'x-mcp-header': 'Region' } } }) // 1
1765
+ * ```
1766
+ */
1767
+ function countHeaderAnnotations(value) {
1768
+ let total = 0;
1769
+ const seen = /* @__PURE__ */ new Set();
1770
+ const pending = [value];
1771
+ while (pending.length > 0) {
1772
+ const node = pending.pop();
1773
+ if (isArray(node)) {
1774
+ if (seen.has(node)) continue;
1775
+ seen.add(node);
1776
+ for (const item of node) pending.push(item);
1777
+ continue;
1778
+ }
1779
+ if (!isRecord(node) || seen.has(node)) continue;
1780
+ seen.add(node);
1781
+ for (const [key, member] of Object.entries(node)) if (key === "x-mcp-header") total += 1;
1782
+ else pending.push(member);
1783
+ }
1784
+ return total;
1785
+ }
1786
+ /**
1787
+ * Reads every `x-mcp-header` annotation reachable from a schema node through `properties`.
1788
+ *
1789
+ * @remarks
1790
+ * Reachability is the protocol's own rule: an annotation counts only where a chain of
1791
+ * `properties` keys leads to it from the `inputSchema` root, so `path` is both the schema
1792
+ * position and the position the call's `arguments` carry the value at. A property named
1793
+ * `items` is reachable like any other, because the chain is read by key POSITION rather than
1794
+ * by key name.
1795
+ *
1796
+ * `undefined` means the definition is invalid rather than empty: a reachable annotation whose
1797
+ * value is not an {@link import('./validators.js').isFieldToken} token, one sitting on the
1798
+ * schema ROOT (which is no property), one on a leaf whose declared type is not an
1799
+ * {@link import('./validators.js').isMCPHeaderPrimitive} primitive, or a chain deeper than
1800
+ * `DEFAULT_MCP_LIMITS.depth` — which is also what makes a self-referential schema terminate.
1801
+ * A node that is not a record carries nothing and answers an empty list, because a leaf the
1802
+ * walk cannot read is not a violation.
1803
+ *
1804
+ * @param schema - The schema node to read
1805
+ * @param path - The `properties` keys already traversed; the root is called with `[]`
1806
+ * @returns The annotations reachable from this node, or `undefined` when one is invalid
1807
+ *
1808
+ * @example
1809
+ * ```ts
1810
+ * extractHeaderAnnotations({ properties: { region: { type: 'string', 'x-mcp-header': 'Region' } } }, [])
1811
+ * // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
1812
+ * ```
1813
+ */
1814
+ function extractHeaderAnnotations(schema, path) {
1815
+ if (path.length > DEFAULT_MCP_LIMITS.depth) return void 0;
1816
+ if (!isRecord(schema)) return [];
1817
+ const found = [];
1818
+ const annotation = schema[MCP_HEADER_ANNOTATION];
1819
+ if (annotation !== void 0) {
1820
+ if (path.length === 0 || !isFieldToken(annotation)) return void 0;
1821
+ const primitive = schema["type"];
1822
+ if (!isMCPHeaderPrimitive(primitive)) return void 0;
1823
+ found.push({
1824
+ name: annotation,
1825
+ path,
1826
+ primitive
1827
+ });
1828
+ }
1829
+ const properties = schema["properties"];
1830
+ if (isRecord(properties)) for (const [key, leaf] of Object.entries(properties)) {
1831
+ const nested = extractHeaderAnnotations(leaf, [...path, key]);
1832
+ if (nested === void 0) return void 0;
1833
+ found.push(...nested);
1834
+ }
1835
+ return found;
1836
+ }
1837
+ /**
1838
+ * Builds the `x-mcp-header` projections one tool's `inputSchema` declares.
1839
+ *
1840
+ * @remarks
1841
+ * The single decision both sides of the protocol make about an annotated tool: an HTTP
1842
+ * CLIENT excludes a definition this refuses from the `tools/list` result it delivers, and a
1843
+ * SERVER recognizes exactly the `Mcp-Param-*` names this returns for its own definitions.
1844
+ *
1845
+ * `undefined` means the definition is invalid, and every rule the protocol states produces
1846
+ * it: a value that is not an RFC 9110 token, a non-primitive or untyped annotated leaf, a
1847
+ * name repeated case-insensitively within the schema, an annotation the `properties` chain
1848
+ * does not reach, and a schema that is not a record at all. An empty list is the valid answer
1849
+ * for a schema carrying no annotation.
1850
+ *
1851
+ * Total — never throws, and a cyclic or stack-hostile schema is refused rather than followed.
1852
+ *
1853
+ * @param schema - The tool's advertised `inputSchema`
1854
+ * @returns The declared projections, or `undefined` when the definition is invalid
1855
+ *
1856
+ * @example
1857
+ * ```ts
1858
+ * buildHeaderParameters({
1859
+ * type: 'object',
1860
+ * properties: { region: { type: 'string', 'x-mcp-header': 'Region' } },
1861
+ * }) // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
1862
+ * ```
1863
+ */
1864
+ function buildHeaderParameters(schema) {
1865
+ if (!isRecord(schema)) return void 0;
1866
+ const found = extractHeaderAnnotations(schema, []);
1867
+ if (found === void 0 || found.length !== countHeaderAnnotations(schema)) return void 0;
1868
+ const taken = /* @__PURE__ */ new Set();
1869
+ for (const parameter of found) {
1870
+ const key = parameter.name.toLowerCase();
1871
+ if (taken.has(key)) return void 0;
1872
+ taken.add(key);
1873
+ }
1874
+ return found;
1875
+ }
1876
+ /**
1877
+ * Renders one projected argument as the text its `Mcp-Param-*` header carries.
1878
+ *
1879
+ * @remarks
1880
+ * The protocol's conversion table, and the ONE place it is stated: a string travels as
1881
+ * itself, an integer in decimal, and a boolean as lowercase `true` or `false`. The value's
1882
+ * runtime shape must match the leaf's declared type, so a schema that declares `integer` and
1883
+ * an argument that supplies a string, a fraction, or a magnitude outside the IEEE 754 safe
1884
+ * range carries NOTHING — a header that cannot round-trip the body value is worse than an
1885
+ * absent one, and the tool's own argument validation owns the disagreement.
1886
+ *
1887
+ * @param value - The argument value read at the parameter's path
1888
+ * @param primitive - The leaf's declared type
1889
+ * @returns The header text, or `undefined` when the value cannot travel as that type
1890
+ *
1891
+ * @example
1892
+ * ```ts
1893
+ * renderHeaderValue(42, 'integer') // '42'
1894
+ * renderHeaderValue(false, 'boolean') // 'false'
1895
+ * ```
1896
+ */
1897
+ function renderHeaderValue(value, primitive) {
1898
+ if (primitive === "string") return isString(value) ? value : void 0;
1899
+ if (primitive === "boolean") return isBoolean(value) ? value ? "true" : "false" : void 0;
1900
+ return isNumber(value) && Number.isSafeInteger(value) ? String(value) : void 0;
1901
+ }
1902
+ /**
1903
+ * Builds the `Mcp-Param-*` request headers one `tools/call` carries.
1904
+ *
1905
+ * @remarks
1906
+ * The projection SEP-2243 requires of an HTTP client, and the same derivation a server runs
1907
+ * to know what the request must carry. Each parameter's value is read at its exact
1908
+ * property path in the call's own `arguments`; an absent or `null` value omits its header
1909
+ * entirely, which is the protocol's distinction between "not supplied" and "supplied empty".
1910
+ * The rendered text then travels through {@link encodeSentinel}, so a value carrying
1911
+ * non-ASCII, control, or edge whitespace characters reaches the peer intact.
1912
+ *
1913
+ * @param parameters - The projections the tool's `inputSchema` declares
1914
+ * @param values - The call's `arguments` record
1915
+ * @returns The header field names and values, empty when nothing projects
1916
+ *
1917
+ * @example
1918
+ * ```ts
1919
+ * buildHeaderProjection(
1920
+ * [{ name: 'Region', path: ['region'], primitive: 'string' }],
1921
+ * { region: 'us-west1' },
1922
+ * ) // → { 'Mcp-Param-Region': 'us-west1' }
1923
+ * ```
1924
+ */
1925
+ function buildHeaderProjection(parameters, values) {
1926
+ const headers = {};
1927
+ for (const parameter of parameters) {
1928
+ let carried = values;
1929
+ for (const key of parameter.path) carried = isRecord(carried) ? carried[key] : void 0;
1930
+ if (carried === void 0 || carried === null) continue;
1931
+ const text = renderHeaderValue(carried, parameter.primitive);
1932
+ if (text !== void 0) headers[`${MCP_PARAM_PREFIX}${parameter.name}`] = encodeSentinel(text);
1933
+ }
1934
+ return headers;
1935
+ }
1936
+ /**
1937
+ * Reads one named tool's advertised `inputSchema` out of a `tools/list` answer.
1938
+ *
1939
+ * @remarks
1940
+ * The answer is read as foreign data end to end — a dispatched response, an error envelope,
1941
+ * and a result whose `tools` member is absent or is not an array all read as "no schema"
1942
+ * rather than as a fault. That is what lets the HTTP POST handler ask its own dispatcher
1943
+ * which `Mcp-Param-*` names a `tools/call` may carry without narrowing anything first.
1944
+ *
1945
+ * @param response - The `tools/list` answer, normally a {@link JSONRPCResponse}
1946
+ * @param name - The tool whose schema to read
1947
+ * @returns The advertised `inputSchema`, or `undefined` when the answer carries none
1948
+ *
1949
+ * @example
1950
+ * ```ts
1951
+ * extractToolSchema(answer, 'search')?.['properties']
1952
+ * ```
1953
+ */
1954
+ function extractToolSchema(response, name) {
1955
+ const result = isRecord(response) ? response["result"] : void 0;
1956
+ const tools = isRecord(result) ? result["tools"] : void 0;
1957
+ if (!isArray(tools)) return void 0;
1958
+ for (const tool of tools) {
1959
+ if (!isRecord(tool) || tool["name"] !== name) continue;
1960
+ const schema = tool["inputSchema"];
1961
+ return isRecord(schema) ? schema : void 0;
1962
+ }
1355
1963
  }
1356
1964
  /**
1357
1965
  * Reads the request id an inbound `notifications/cancelled` names — the inverse of
@@ -1527,7 +2135,7 @@ function bindServer(server, transport) {
1527
2135
  * @remarks
1528
2136
  * The client's outbound writes flow through `client.transport.send` — its existing,
1529
2137
  * unmodified request/response correlation — so `client` must have been constructed
1530
- * with a {@link import('./types.js').MCPClientTransportInterface} that itself carries
2138
+ * with a {@link import('./types.js').MCPMessageTransportInterface} that itself carries
1531
2139
  * the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
1532
2140
  * the additive factory that adapts an {@link MCPTransportInterface} into that shape);
1533
2141
  * this binder then completes the inbound half by decoding each message and pushing it
@@ -1640,7 +2248,7 @@ function isMCPResultMetaObject(value) {
1640
2248
  * {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
1641
2249
  *
1642
2250
  * @param value - The unknown value to inspect
1643
- * @returns `true` when the value is exact metadata whose subscription stamp, if present, is valid
2251
+ * @returns True if the value is exact metadata whose subscription stamp, if present, is valid; false otherwise
1644
2252
  *
1645
2253
  * @example
1646
2254
  * ```ts
@@ -1663,12 +2271,54 @@ function isMCPLoggingLevel(value) {
1663
2271
  * Determines whether a value is standard padded base64 as required by JSON Schema `byte` format.
1664
2272
  *
1665
2273
  * @param value - The unknown value to inspect
1666
- * @returns Whether the value is an empty or completely padded standard base64 encoding
2274
+ * @returns True if the value is an empty or completely padded standard base64 encoding; false otherwise
1667
2275
  */
1668
2276
  function isStandardBase64(value) {
1669
2277
  return isString(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
1670
2278
  }
1671
2279
  /**
2280
+ * Determines whether a value is one RFC 9110 field token.
2281
+ *
2282
+ * @remarks
2283
+ * A token is one or more `tchar`: the ASCII letters, the digits, and
2284
+ * ``!#$%&'*+-.^_`|~``. That set already excludes the empty string, whitespace, a colon, a
2285
+ * control character, and every non-ASCII code point, so it is the whole constraint an
2286
+ * `x-mcp-header` annotation's value must satisfy — the value is appended verbatim to
2287
+ * {@link MCP_PARAM_PREFIX} and must survive as an HTTP field name.
2288
+ *
2289
+ * @param value - The unknown value to inspect
2290
+ * @returns True if the value is a non-empty RFC 9110 token; false otherwise
2291
+ *
2292
+ * @example
2293
+ * ```ts
2294
+ * isFieldToken('Region') // true
2295
+ * isFieldToken('My Region') // false
2296
+ * ```
2297
+ */
2298
+ function isFieldToken(value) {
2299
+ return isString(value) && /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value);
2300
+ }
2301
+ /**
2302
+ * Determines whether a value is a JSON Schema type an `x-mcp-header` annotation may sit on.
2303
+ *
2304
+ * @remarks
2305
+ * `number` is refused deliberately: a JSON number has no interoperable decimal text form, so
2306
+ * a header carrying one could not be compared with the body byte for byte. `integer` renders
2307
+ * exactly, and the server compares it numerically.
2308
+ *
2309
+ * @param value - The unknown value to inspect
2310
+ * @returns True if the value is one of `'string'`, `'integer'`, or `'boolean'`; false otherwise
2311
+ *
2312
+ * @example
2313
+ * ```ts
2314
+ * isMCPHeaderPrimitive('integer') // true
2315
+ * isMCPHeaderPrimitive('number') // false
2316
+ * ```
2317
+ */
2318
+ function isMCPHeaderPrimitive(value) {
2319
+ return value === "string" || value === "integer" || value === "boolean";
2320
+ }
2321
+ /**
1672
2322
  * Determines whether a value is one absolute URI under RFC 3986 syntax.
1673
2323
  *
1674
2324
  * @remarks
@@ -1676,7 +2326,7 @@ function isStandardBase64(value) {
1676
2326
  * scheme allowlist. Component scanning is bounded by the input length.
1677
2327
  *
1678
2328
  * @param value - The unknown value to inspect
1679
- * @returns Whether the value is an RFC 3986 URI rather than a relative reference
2329
+ * @returns True if the value is an RFC 3986 URI rather than a relative reference; false otherwise
1680
2330
  */
1681
2331
  function isAbsoluteURI(value) {
1682
2332
  if (!isString(value) || value.length === 0) return false;
@@ -1772,7 +2422,7 @@ function isAbsoluteURI(value) {
1772
2422
  * refuse. It is a SYNTAX guard: no time zone, locale, calendar era, or leap second applies.
1773
2423
  *
1774
2424
  * @param value - The unknown value to inspect
1775
- * @returns Whether the value is an RFC 3339 `full-date` for a day that exists
2425
+ * @returns True if the value is an RFC 3339 `full-date` for a day that exists; false otherwise
1776
2426
  *
1777
2427
  * @example
1778
2428
  * ```ts
@@ -1805,7 +2455,7 @@ function isRFC3339Date(value) {
1805
2455
  * second.
1806
2456
  *
1807
2457
  * @param value - The unknown value to inspect
1808
- * @returns Whether the value is an RFC 3339 `date-time` for a day that exists
2458
+ * @returns True if the value is an RFC 3339 `date-time` for a day that exists; false otherwise
1809
2459
  *
1810
2460
  * @example
1811
2461
  * ```ts
@@ -1823,7 +2473,7 @@ function isRFC3339DateTime(value) {
1823
2473
  * Determines whether a value is one exact finite MCP progress payload.
1824
2474
  *
1825
2475
  * @param value - The unknown value to inspect
1826
- * @returns Whether required progress and optional total/message fields match the dated schema
2476
+ * @returns True if required progress and optional total/message fields match the dated schema; false otherwise
1827
2477
  */
1828
2478
  function isMCPProgress(value) {
1829
2479
  const owned = attempt(() => cloneJSONRecord(value));
@@ -1842,7 +2492,7 @@ function isMCPProgress(value) {
1842
2492
  * Determines whether a value carries valid dated-schema MCP content annotations.
1843
2493
  *
1844
2494
  * @param value - The unknown value to inspect
1845
- * @returns Whether the value is valid MCP annotations
2495
+ * @returns True if the value is valid MCP annotations; false otherwise
1846
2496
  */
1847
2497
  function isMCPAnnotations(value) {
1848
2498
  const owned = attempt(() => cloneJSONRecord(value));
@@ -1863,7 +2513,7 @@ function isMCPAnnotations(value) {
1863
2513
  * Determines whether a value is one exact dated-schema MCP icon.
1864
2514
  *
1865
2515
  * @param value - The unknown value to inspect
1866
- * @returns Whether the value is a valid MCP icon
2516
+ * @returns True if the value is a valid MCP icon; false otherwise
1867
2517
  */
1868
2518
  function isMCPIcon(value) {
1869
2519
  const owned = attempt(() => cloneJSONRecord(value));
@@ -1954,7 +2604,7 @@ function isMCPServerCapabilities(value) {
1954
2604
  * Determines whether a value is embedded textual MCP resource contents.
1955
2605
  *
1956
2606
  * @param value - The unknown value to inspect
1957
- * @returns Whether the value is embedded textual resource contents
2607
+ * @returns True if the value is embedded textual resource contents; false otherwise
1958
2608
  */
1959
2609
  function isMCPTextResource(value) {
1960
2610
  const owned = attempt(() => cloneJSONRecord(value));
@@ -1973,7 +2623,7 @@ function isMCPTextResource(value) {
1973
2623
  * Determines whether a value is embedded blob MCP resource contents.
1974
2624
  *
1975
2625
  * @param value - The unknown value to inspect
1976
- * @returns Whether the value is embedded blob resource contents
2626
+ * @returns True if the value is embedded blob resource contents; false otherwise
1977
2627
  */
1978
2628
  function isMCPBlobResource(value) {
1979
2629
  const owned = attempt(() => cloneJSONRecord(value));
@@ -1992,7 +2642,7 @@ function isMCPBlobResource(value) {
1992
2642
  * Determines whether a value is one `resources/list` descriptor.
1993
2643
  *
1994
2644
  * @param value - The unknown value to inspect
1995
- * @returns Whether the value is a valid resource descriptor
2645
+ * @returns True if the value is a valid resource descriptor; false otherwise
1996
2646
  */
1997
2647
  function isMCPResource(value) {
1998
2648
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2014,7 +2664,7 @@ function isMCPResource(value) {
2014
2664
  * level belong to the consumer-supplied resource manager; this package projects the string.
2015
2665
  *
2016
2666
  * @param value - The unknown value to inspect
2017
- * @returns Whether the value is a valid resource-template descriptor
2667
+ * @returns True if the value is a valid resource-template descriptor; false otherwise
2018
2668
  */
2019
2669
  function isMCPResourceTemplate(value) {
2020
2670
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2031,7 +2681,7 @@ function isMCPResourceTemplate(value) {
2031
2681
  * Determines whether a value is structurally discriminated resource contents.
2032
2682
  *
2033
2683
  * @param value - The unknown value to inspect
2034
- * @returns Whether exactly one of `text` and `blob` is present and valid
2684
+ * @returns True if exactly one of `text` and `blob` is present and valid; false otherwise
2035
2685
  */
2036
2686
  function isMCPResourceContents(value) {
2037
2687
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2045,7 +2695,7 @@ function isMCPResourceContents(value) {
2045
2695
  * Determines whether a value carries the shared optional pagination cursor.
2046
2696
  *
2047
2697
  * @param value - The unknown value to inspect
2048
- * @returns Whether a present `cursor` is a string
2698
+ * @returns True if a present `cursor` is a string; false otherwise
2049
2699
  */
2050
2700
  function isMCPPaginationParams(value) {
2051
2701
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2055,7 +2705,7 @@ function isMCPPaginationParams(value) {
2055
2705
  * Determines whether a value is one consumer-owned resource page.
2056
2706
  *
2057
2707
  * @param value - The unknown value to inspect
2058
- * @returns Whether the resources and optional following cursor are valid
2708
+ * @returns True if the resources and optional following cursor are valid; false otherwise
2059
2709
  */
2060
2710
  function isMCPResourcePage(value) {
2061
2711
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2068,7 +2718,7 @@ function isMCPResourcePage(value) {
2068
2718
  * Determines whether a value is one consumer-owned resource-template page.
2069
2719
  *
2070
2720
  * @param value - The unknown value to inspect
2071
- * @returns Whether the templates and optional following cursor are valid
2721
+ * @returns True if the templates and optional following cursor are valid; false otherwise
2072
2722
  */
2073
2723
  function isMCPResourceTemplatePage(value) {
2074
2724
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2081,7 +2731,7 @@ function isMCPResourceTemplatePage(value) {
2081
2731
  * Determines whether a value is a string-valued MCP argument record.
2082
2732
  *
2083
2733
  * @param value - The unknown value to inspect
2084
- * @returns Whether every own argument value is a string
2734
+ * @returns True if every own argument value is a string; false otherwise
2085
2735
  */
2086
2736
  function isMCPStringArguments(value) {
2087
2737
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2091,7 +2741,7 @@ function isMCPStringArguments(value) {
2091
2741
  * Determines whether a value is one prompt argument descriptor.
2092
2742
  *
2093
2743
  * @param value - The unknown value to inspect
2094
- * @returns Whether the prompt argument descriptor is valid
2744
+ * @returns True if the prompt argument descriptor is valid; false otherwise
2095
2745
  */
2096
2746
  function isMCPPromptArgument(value) {
2097
2747
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2103,7 +2753,7 @@ function isMCPPromptArgument(value) {
2103
2753
  * Determines whether a value is one `prompts/list` descriptor.
2104
2754
  *
2105
2755
  * @param value - The unknown value to inspect
2106
- * @returns Whether the prompt descriptor is valid
2756
+ * @returns True if the prompt descriptor is valid; false otherwise
2107
2757
  */
2108
2758
  function isMCPPrompt(value) {
2109
2759
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2117,7 +2767,7 @@ function isMCPPrompt(value) {
2117
2767
  * Determines whether a value is one prompt message with existing rich content.
2118
2768
  *
2119
2769
  * @param value - The unknown value to inspect
2120
- * @returns Whether the role and content are valid
2770
+ * @returns True if the role and content are valid; false otherwise
2121
2771
  */
2122
2772
  function isMCPPromptMessage(value) {
2123
2773
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2127,7 +2777,7 @@ function isMCPPromptMessage(value) {
2127
2777
  * Determines whether a value is one consumer-owned prompt page.
2128
2778
  *
2129
2779
  * @param value - The unknown value to inspect
2130
- * @returns Whether the prompts and optional following cursor are valid
2780
+ * @returns True if the prompts and optional following cursor are valid; false otherwise
2131
2781
  */
2132
2782
  function isMCPPromptPage(value) {
2133
2783
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2140,7 +2790,7 @@ function isMCPPromptPage(value) {
2140
2790
  * Determines whether a value is one complete `prompts/get` result.
2141
2791
  *
2142
2792
  * @param value - The unknown value to inspect
2143
- * @returns Whether the prompt result and all messages are valid
2793
+ * @returns True if the prompt result and all messages are valid; false otherwise
2144
2794
  */
2145
2795
  function isMCPPromptGetResult(value) {
2146
2796
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2153,7 +2803,7 @@ function isMCPPromptGetResult(value) {
2153
2803
  * Determines whether a value is a prompt or resource-template completion reference.
2154
2804
  *
2155
2805
  * @param value - The unknown value to inspect
2156
- * @returns Whether the discriminated reference is valid
2806
+ * @returns True if the discriminated reference is valid; false otherwise
2157
2807
  */
2158
2808
  function isMCPCompletionReference(value) {
2159
2809
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2165,7 +2815,7 @@ function isMCPCompletionReference(value) {
2165
2815
  * Determines whether a value is one `completion/complete` parameter object.
2166
2816
  *
2167
2817
  * @param value - The unknown value to inspect
2168
- * @returns Whether its reference, fragment, and optional string context are valid
2818
+ * @returns True if its reference, fragment, and optional string context are valid; false otherwise
2169
2819
  */
2170
2820
  function isMCPCompletionParams(value) {
2171
2821
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2184,7 +2834,7 @@ function isMCPCompletionParams(value) {
2184
2834
  * Determines whether a value is one host-produced completion candidate set.
2185
2835
  *
2186
2836
  * @param value - The unknown value to inspect
2187
- * @returns Whether its candidates and optional result facts are valid
2837
+ * @returns True if its candidates and optional result facts are valid; false otherwise
2188
2838
  */
2189
2839
  function isMCPCompletion(value) {
2190
2840
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2198,7 +2848,7 @@ function isMCPCompletion(value) {
2198
2848
  * Determines whether a value is one complete, capped `completion/complete` result.
2199
2849
  *
2200
2850
  * @param value - The unknown value to inspect
2201
- * @returns Whether the result is complete and carries at most 100 candidates
2851
+ * @returns True if the result is complete and carries at most 100 candidates; false otherwise
2202
2852
  */
2203
2853
  function isMCPCompletionResult(value) {
2204
2854
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2210,7 +2860,7 @@ function isMCPCompletionResult(value) {
2210
2860
  * Determines whether a value is one exact dated-schema MCP tool content block.
2211
2861
  *
2212
2862
  * @param value - The unknown value to inspect
2213
- * @returns Whether the value is valid MCP content
2863
+ * @returns True if the value is valid MCP content; false otherwise
2214
2864
  */
2215
2865
  function isMCPContent(value) {
2216
2866
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2251,7 +2901,7 @@ function isMCPContent(value) {
2251
2901
  * input.
2252
2902
  *
2253
2903
  * @param value - The unknown value to inspect
2254
- * @returns Whether the value is a modern result
2904
+ * @returns True if the value is a modern result; false otherwise
2255
2905
  *
2256
2906
  * @example
2257
2907
  * ```ts
@@ -2277,7 +2927,7 @@ function isMCPResult(value) {
2277
2927
  * hostile input.
2278
2928
  *
2279
2929
  * @param value - The unknown value to inspect
2280
- * @returns Whether the value is a legacy result
2930
+ * @returns True if the value is a legacy result; false otherwise
2281
2931
  *
2282
2932
  * @example
2283
2933
  * ```ts
@@ -2293,7 +2943,7 @@ function isMCPLegacyResult(value) {
2293
2943
  * Determines whether a value is a complete modern MCP tool result.
2294
2944
  *
2295
2945
  * @param value - The unknown value to inspect
2296
- * @returns Whether the value is a complete MCP call result
2946
+ * @returns True if the value is a complete MCP call result; false otherwise
2297
2947
  */
2298
2948
  function isMCPCallResult(value) {
2299
2949
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2321,7 +2971,7 @@ function isMCPCallResult(value) {
2321
2971
  * INTEGER milliseconds because the schema formats them `int`.
2322
2972
  *
2323
2973
  * @param value - The unknown value to inspect
2324
- * @returns Whether the value is a well-formed `resultType: 'task'` result
2974
+ * @returns True if the value is a well-formed `resultType: 'task'` result; false otherwise
2325
2975
  *
2326
2976
  * @example
2327
2977
  * ```ts
@@ -2348,7 +2998,7 @@ function isMCPTaskResult(value) {
2348
2998
  * Determines whether a value is one of the extension's task lifecycle states.
2349
2999
  *
2350
3000
  * @param value - The unknown value to inspect
2351
- * @returns Whether the value is an {@link MCPTaskStatus}
3001
+ * @returns True if the value is an {@link MCPTaskStatus}; false otherwise
2352
3002
  *
2353
3003
  * @example
2354
3004
  * ```ts
@@ -2379,7 +3029,7 @@ function isMCPTaskStatus(value) {
2379
3029
  * What is checked is what this package publishes as the contract.
2380
3030
  *
2381
3031
  * @param value - The unknown value to inspect
2382
- * @returns Whether the value is a well-formed {@link MCPTaskDetail}
3032
+ * @returns True if the value is a well-formed {@link MCPTaskDetail}; false otherwise
2383
3033
  *
2384
3034
  * @example
2385
3035
  * ```ts
@@ -2422,7 +3072,7 @@ function isMCPTaskDetail(value) {
2422
3072
  * peer stamps there is the peer's to write.
2423
3073
  *
2424
3074
  * @param value - The unknown value to inspect
2425
- * @returns Whether the value is a well-formed {@link MCPTaskDetailResult}
3075
+ * @returns True if the value is a well-formed {@link MCPTaskDetailResult}; false otherwise
2426
3076
  *
2427
3077
  * @example
2428
3078
  * ```ts
@@ -2456,7 +3106,7 @@ function isMCPTaskDetailResult(value) {
2456
3106
  * to it, so a guard that demanded the stamp would refuse every frame a producer emits.
2457
3107
  *
2458
3108
  * @param value - The unknown value to inspect
2459
- * @returns Whether the value is a well-formed `notifications/tasks` notification
3109
+ * @returns True if the value is a well-formed `notifications/tasks` notification; false otherwise
2460
3110
  *
2461
3111
  * @example
2462
3112
  * ```ts
@@ -2484,7 +3134,7 @@ function isMCPTaskNotification(value) {
2484
3134
  *
2485
3135
  * @param value - The unknown value to inspect
2486
3136
  * @param bytes - The maximum accepted encoded bytes
2487
- * @returns `true` only for a string whose UTF-8 representation fits the bound
3137
+ * @returns True if `value` is a string whose UTF-8 representation fits the bound; false otherwise
2488
3138
  *
2489
3139
  * @example
2490
3140
  * ```ts
@@ -2520,7 +3170,7 @@ function isBoundedString(value, bytes) {
2520
3170
  *
2521
3171
  * @param value - The unknown value to inspect
2522
3172
  * @param limits - Serialized byte, optional key, and nesting-depth bounds
2523
- * @returns `true` only for safe JSON satisfying every bound
3173
+ * @returns True if `value` is safe JSON satisfying every bound; false otherwise
2524
3174
  *
2525
3175
  * @example
2526
3176
  * ```ts
@@ -2542,7 +3192,7 @@ function isBoundedJSON(value, limits) {
2542
3192
  * no minimum length. Total: any other input returns `false`.
2543
3193
  *
2544
3194
  * @param value - The already-parsed value to test
2545
- * @returns `true` when `value` is a string or a finite integer
3195
+ * @returns True if `value` is a string or a finite integer; false otherwise
2546
3196
  *
2547
3197
  * @example
2548
3198
  * ```ts
@@ -2560,7 +3210,7 @@ function isJSONRPCId(value) {
2560
3210
  * Determines whether a value is a supported {@link MCPVersion}.
2561
3211
  *
2562
3212
  * @param value - The unknown value to inspect
2563
- * @returns `true` when the value is one of {@link SUPPORTED_MCP_VERSIONS}
3213
+ * @returns True if the value is one of {@link SUPPORTED_MCP_VERSIONS}; false otherwise
2564
3214
  */
2565
3215
  function isMCPVersion(value) {
2566
3216
  return isString(value) && SUPPORTED_MCP_VERSIONS.some((version) => version === value);
@@ -2569,7 +3219,7 @@ function isMCPVersion(value) {
2569
3219
  * Determines whether a value is a modern protocol revision accepted by a bare server.
2570
3220
  *
2571
3221
  * @param value - The unknown value to inspect
2572
- * @returns `true` when the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}
3222
+ * @returns True if the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}; false otherwise
2573
3223
  */
2574
3224
  function isMCPModernVersion(value) {
2575
3225
  return isString(value) && SUPPORTED_MODERN_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -2578,7 +3228,7 @@ function isMCPModernVersion(value) {
2578
3228
  * Determines whether a value is a revision accepted by the optional legacy decorator.
2579
3229
  *
2580
3230
  * @param value - The unknown value to inspect
2581
- * @returns `true` when the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
3231
+ * @returns True if the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}; false otherwise
2582
3232
  */
2583
3233
  function isMCPLegacyVersion(value) {
2584
3234
  return isString(value) && SUPPORTED_LEGACY_PROTOCOL_VERSIONS.some((version) => version === value);
@@ -2597,7 +3247,7 @@ function isMCPLegacyVersion(value) {
2597
3247
  * the caller asked for.
2598
3248
  *
2599
3249
  * @param value - The unknown value to inspect
2600
- * @returns `true` when every recognized filter field has its protocol shape
3250
+ * @returns True if every recognized filter field has its protocol shape; false otherwise
2601
3251
  */
2602
3252
  function isMCPSubscriptionFilter(value) {
2603
3253
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2618,7 +3268,7 @@ function isMCPSubscriptionFilter(value) {
2618
3268
  * Determines whether a value is a graceful `subscriptions/listen` result.
2619
3269
  *
2620
3270
  * @param value - The unknown value to inspect
2621
- * @returns `true` when the result is complete and carries a valid subscription id
3271
+ * @returns True if the result is complete and carries a valid subscription id; false otherwise
2622
3272
  */
2623
3273
  function isMCPSubscriptionResult(value) {
2624
3274
  const owned = attempt(() => cloneJSONRecord(value));
@@ -2630,7 +3280,7 @@ function isMCPSubscriptionResult(value) {
2630
3280
  * Determines whether a value is one restricted primitive form-elicitation schema.
2631
3281
  *
2632
3282
  * @param value - The unknown value to inspect
2633
- * @returns `true` for a supported boolean, numeric, string, or string-array schema
3283
+ * @returns True if `value` is a supported boolean, numeric, string, or string-array schema; false otherwise
2634
3284
  *
2635
3285
  * @example
2636
3286
  * ```ts
@@ -2693,7 +3343,7 @@ function isMCPElicitFieldSchema(value) {
2693
3343
  * an unrecognized top-level annotation is data rather than a rejection.
2694
3344
  *
2695
3345
  * @param value - The unknown value to inspect
2696
- * @returns `true` when `value` is a restricted object schema of supported field schemas
3346
+ * @returns True if `value` is a restricted object schema of supported field schemas; false otherwise
2697
3347
  *
2698
3348
  * @example
2699
3349
  * ```ts
@@ -2720,7 +3370,7 @@ function isMCPElicitSchema(value) {
2720
3370
  * Determines whether a value is a form-mode elicitation parameter object.
2721
3371
  *
2722
3372
  * @param value - The unknown value to inspect
2723
- * @returns `true` when `value` has the restricted form elicitation shape
3373
+ * @returns True if `value` has the restricted form elicitation shape; false otherwise
2724
3374
  *
2725
3375
  * @example
2726
3376
  * ```ts
@@ -2746,7 +3396,7 @@ function isMCPElicitForm(value) {
2746
3396
  * Determines whether a value is a URL-mode elicitation parameter object.
2747
3397
  *
2748
3398
  * @param value - The unknown value to inspect
2749
- * @returns `true` when `value` has the URL elicitation shape
3399
+ * @returns True if `value` has the URL elicitation shape; false otherwise
2750
3400
  *
2751
3401
  * @example
2752
3402
  * ```ts
@@ -2767,7 +3417,7 @@ function isMCPElicitURL(value) {
2767
3417
  * Determines whether a value is an embedded `elicitation/create` request.
2768
3418
  *
2769
3419
  * @param value - The unknown value to inspect
2770
- * @returns `true` when `value` is a form- or URL-mode elicitation request
3420
+ * @returns True if `value` is a form- or URL-mode elicitation request; false otherwise
2771
3421
  *
2772
3422
  * @example
2773
3423
  * ```ts
@@ -2792,7 +3442,7 @@ function isMCPElicitRequest(value) {
2792
3442
  * Determines whether a value is one legal embedded multi-round-trip request.
2793
3443
  *
2794
3444
  * @param value - The unknown value to inspect
2795
- * @returns `true` for elicitation, deprecated sampling, or deprecated roots requests
3445
+ * @returns True if `value` is an embedded elicitation, sampling, or roots request; false otherwise
2796
3446
  *
2797
3447
  * @example
2798
3448
  * ```ts
@@ -2813,10 +3463,10 @@ function isMCPInputRequest(value) {
2813
3463
  }
2814
3464
  }
2815
3465
  /**
2816
- * Determines whether a value is a server-keyed map of embedded input requests.
3466
+ * Determines whether a value is a consumer-keyed map of embedded input requests.
2817
3467
  *
2818
3468
  * @param value - The unknown value to inspect
2819
- * @returns `true` when every own value is a legal {@link MCPInputRequest}
3469
+ * @returns True if every own value is a legal {@link MCPInputRequest}; false otherwise
2820
3470
  *
2821
3471
  * @example
2822
3472
  * ```ts
@@ -2836,7 +3486,7 @@ function isMCPInputRequestMap(value) {
2836
3486
  * Determines whether a value is one elicitation response.
2837
3487
  *
2838
3488
  * @param value - The unknown value to inspect
2839
- * @returns `true` when action/content have the protocol shape
3489
+ * @returns True if action/content have the protocol shape; false otherwise
2840
3490
  *
2841
3491
  * @example
2842
3492
  * ```ts
@@ -2886,7 +3536,7 @@ function isMCPElicitResult(value) {
2886
3536
  *
2887
3537
  * @param value - The accepted response content to check
2888
3538
  * @param schema - The exact {@link MCPElicitSchema} that was issued with the elicitation
2889
- * @returns `true` when every declared and undeclared value is legal under `schema`
3539
+ * @returns True if every declared and undeclared value is legal under `schema`; false otherwise
2890
3540
  *
2891
3541
  * @example
2892
3542
  * ```ts
@@ -2967,6 +3617,187 @@ function isElicitContent(value, schema) {
2967
3617
  }
2968
3618
  }
2969
3619
  /**
3620
+ * Determines whether a value is one filesystem root a client exposes.
3621
+ *
3622
+ * @remarks
3623
+ * The dated schema declares `uri` with `format: uri`, so this applies the same RFC 3986
3624
+ * check {@link isAbsoluteURI} gives every other `format: uri` field the package validates,
3625
+ * including a URL-mode elicitation's `url`. Total over hostile input.
3626
+ *
3627
+ * @param value - The unknown value to inspect
3628
+ * @returns True if `value` carries an absolute `uri` and an optional string `name`; false otherwise
3629
+ *
3630
+ * @example
3631
+ * ```ts
3632
+ * isMCPRoot({ uri: 'file:///workspace', name: 'workspace' }) // true
3633
+ * isMCPRoot({ uri: 'workspace' }) // false — the schema declares `format: uri`
3634
+ * ```
3635
+ */
3636
+ function isMCPRoot(value) {
3637
+ const owned = attempt(() => cloneJSONRecord(value));
3638
+ if (!owned.success) return false;
3639
+ try {
3640
+ const root = owned.value;
3641
+ const name = root["name"];
3642
+ const metadata = root["_meta"];
3643
+ if (!isAbsoluteURI(root["uri"])) return false;
3644
+ if (!isUndefined(name) && !isString(name)) return false;
3645
+ return isUndefined(metadata) || isMCPMetaObject(metadata);
3646
+ } catch {
3647
+ return false;
3648
+ }
3649
+ }
3650
+ /**
3651
+ * Determines whether a value is one client answer to an embedded `roots/list` request.
3652
+ *
3653
+ * @remarks
3654
+ * The dated schema requires the `roots` array, and each root is checked by
3655
+ * {@link isMCPRoot}. Total over hostile input.
3656
+ *
3657
+ * @param value - The unknown value to inspect
3658
+ * @returns True if `value` carries an array of valid roots; false otherwise
3659
+ *
3660
+ * @example
3661
+ * ```ts
3662
+ * isMCPRootResult({ roots: [{ uri: 'file:///workspace' }] }) // true
3663
+ * isMCPRootResult({ roots: {} }) // false — the schema requires an array
3664
+ * ```
3665
+ */
3666
+ function isMCPRootResult(value) {
3667
+ const owned = attempt(() => cloneJSONRecord(value));
3668
+ if (!owned.success) return false;
3669
+ try {
3670
+ const result = owned.value;
3671
+ const roots = result["roots"];
3672
+ const metadata = result["_meta"];
3673
+ if (!Array.isArray(roots) || !roots.every((root) => isMCPRoot(root))) return false;
3674
+ return isUndefined(metadata) || isMCPMetaObject(metadata);
3675
+ } catch {
3676
+ return false;
3677
+ }
3678
+ }
3679
+ /**
3680
+ * Determines whether a value is one block a sampling completion may carry.
3681
+ *
3682
+ * @remarks
3683
+ * The schema's `SamplingMessageContentBlock`: the text, image, and audio blocks
3684
+ * {@link isMCPContent} also admits, plus `tool_use` and `tool_result`. The resource arms of
3685
+ * {@link isMCPContent} are refused, because the schema leaves them out of a sampling
3686
+ * completion. A `tool_result` carries ordinary {@link isMCPContent} blocks and an open
3687
+ * `structuredContent`, which the schema constrains to no shape at all. Total over hostile
3688
+ * input.
3689
+ *
3690
+ * @param value - The unknown value to inspect
3691
+ * @returns True if `value` is one legal sampling content block; false otherwise
3692
+ *
3693
+ * @example
3694
+ * ```ts
3695
+ * isMCPSampleContent({ type: 'text', text: 'Paris' }) // true
3696
+ * isMCPSampleContent({ type: 'tool_use', id: 'c1', name: 'lookup', input: {} }) // true
3697
+ * isMCPSampleContent({ type: 'resource_link', name: 'doc', uri: 'file:///doc' }) // false
3698
+ * ```
3699
+ */
3700
+ function isMCPSampleContent(value) {
3701
+ const owned = attempt(() => cloneJSONRecord(value));
3702
+ if (!owned.success) return false;
3703
+ try {
3704
+ const block = owned.value;
3705
+ const metadata = block["_meta"];
3706
+ if (!isUndefined(metadata) && !isMCPMetaObject(metadata)) return false;
3707
+ if (block["type"] === "tool_use") return isString(block["id"]) && isString(block["name"]) && isRecord(block["input"]);
3708
+ if (block["type"] === "tool_result") {
3709
+ const carried = block["content"];
3710
+ const failed = block["isError"];
3711
+ if (!Array.isArray(carried) || !carried.every((entry) => isMCPContent(entry))) return false;
3712
+ if (!isUndefined(failed) && !isBoolean(failed)) return false;
3713
+ return isString(block["toolUseId"]);
3714
+ }
3715
+ if (!isMCPContent(block)) return false;
3716
+ return block.type === "text" || block.type === "image" || block.type === "audio";
3717
+ } catch {
3718
+ return false;
3719
+ }
3720
+ }
3721
+ /**
3722
+ * Determines whether a value is one client answer to an embedded sampling request.
3723
+ *
3724
+ * @remarks
3725
+ * The schema's `CreateMessageResult` types `content` as an `anyOf` over one
3726
+ * {@link isMCPSampleContent} block or an ARRAY of them, so both are admitted here: a
3727
+ * tool-using model answers with `tool_use` and `tool_result` blocks, and a model answering in
3728
+ * several parts answers with the array. `stopReason` stays an open string because the schema
3729
+ * names four values and permits any other a provider reports. Total over hostile input.
3730
+ *
3731
+ * @param value - The unknown value to inspect
3732
+ * @returns True if `value` has the sampling-completion shape; false otherwise
3733
+ *
3734
+ * @example
3735
+ * ```ts
3736
+ * isMCPSampleResult({
3737
+ * role: 'assistant',
3738
+ * content: { type: 'text', text: 'Paris' },
3739
+ * model: 'test-model',
3740
+ * }) // true
3741
+ * isMCPSampleResult({
3742
+ * role: 'assistant',
3743
+ * content: [{ type: 'text', text: 'Paris' }],
3744
+ * model: 'test-model',
3745
+ * }) // true
3746
+ * ```
3747
+ */
3748
+ function isMCPSampleResult(value) {
3749
+ const owned = attempt(() => cloneJSONRecord(value));
3750
+ if (!owned.success) return false;
3751
+ try {
3752
+ const result = owned.value;
3753
+ const role = result["role"];
3754
+ const content = result["content"];
3755
+ const reason = result["stopReason"];
3756
+ const metadata = result["_meta"];
3757
+ if (role !== "user" && role !== "assistant") return false;
3758
+ if (!isString(result["model"])) return false;
3759
+ if (!(Array.isArray(content) ? content : [content]).every((block) => isMCPSampleContent(block))) return false;
3760
+ if (!isUndefined(reason) && !isString(reason)) return false;
3761
+ return isUndefined(metadata) || isMCPMetaObject(metadata);
3762
+ } catch {
3763
+ return false;
3764
+ }
3765
+ }
3766
+ /**
3767
+ * Determines whether a response answers the exact embedded request that was issued.
3768
+ *
3769
+ * @remarks
3770
+ * A response carries no `method` of its own, so the ISSUED request selects which arm applies
3771
+ * — the same way {@link isElicitContent} takes the issued schema rather than trusting the
3772
+ * content to describe itself. A form elicitation is checked twice: once for the response
3773
+ * shape and once, on `accept`, for the content against the schema that round issued. A
3774
+ * URL-mode elicitation issues no schema, so only the shape is checked. A request this
3775
+ * package cannot recognize admits NOTHING, because an unrecognized question has no correct
3776
+ * answer. Total over hostile responses and hostile requests alike.
3777
+ *
3778
+ * @param value - The client's answer to check
3779
+ * @param request - The exact {@link MCPInputRequest} that was issued under the same key
3780
+ * @returns True if the answer is legal for that request; false otherwise
3781
+ *
3782
+ * @example
3783
+ * ```ts
3784
+ * isMCPInputResponse({ roots: [] }, { method: 'roots/list' }) // true
3785
+ * isMCPInputResponse({ roots: [] }, { method: 'sampling/createMessage', params: {} }) // false
3786
+ * ```
3787
+ */
3788
+ function isMCPInputResponse(value, request) {
3789
+ if (!isMCPInputRequest(request)) return false;
3790
+ try {
3791
+ if (request.method === "roots/list") return isMCPRootResult(value);
3792
+ if (request.method === "sampling/createMessage") return isMCPSampleResult(value);
3793
+ if (!isMCPElicitResult(value)) return false;
3794
+ if (value.action !== "accept" || !isMCPElicitForm(request.params)) return true;
3795
+ return isElicitContent(value.content ?? {}, request.params.requestedSchema);
3796
+ } catch {
3797
+ return false;
3798
+ }
3799
+ }
3800
+ /**
2970
3801
  * Determines whether a value is an MCP input-required result.
2971
3802
  *
2972
3803
  * @remarks
@@ -2974,7 +3805,7 @@ function isElicitContent(value, schema) {
2974
3805
  * both must be present and valid. Total over hostile input.
2975
3806
  *
2976
3807
  * @param value - The unknown value to inspect
2977
- * @returns `true` when `value` is a valid input-required result
3808
+ * @returns True if `value` is a valid input-required result; false otherwise
2978
3809
  *
2979
3810
  * @example
2980
3811
  * ```ts
@@ -3011,7 +3842,7 @@ function isMCPInputResult(value) {
3011
3842
  * be a record. Total: any other input returns `false`.
3012
3843
  *
3013
3844
  * @param value - The already-parsed value to test
3014
- * @returns `true` when `value` is a valid JSON-RPC request
3845
+ * @returns True if `value` is a valid JSON-RPC request; false otherwise
3015
3846
  *
3016
3847
  * @example
3017
3848
  * ```ts
@@ -3038,7 +3869,7 @@ function isJSONRPCRequest(value) {
3038
3869
  * be a record. Total: any other input returns `false`.
3039
3870
  *
3040
3871
  * @param value - The already-parsed value to test
3041
- * @returns `true` when `value` is a valid JSON-RPC notification
3872
+ * @returns True if `value` is a valid JSON-RPC notification; false otherwise
3042
3873
  *
3043
3874
  * @example
3044
3875
  * ```ts
@@ -3064,7 +3895,7 @@ function isJSONRPCNotification(value) {
3064
3895
  * mutually exclusive, so a positive answer names exactly one arm. Total.
3065
3896
  *
3066
3897
  * @param value - The already-parsed value to test
3067
- * @returns `true` when `value` is a valid JSON-RPC request or notification
3898
+ * @returns True if `value` is a valid JSON-RPC request or notification; false otherwise
3068
3899
  */
3069
3900
  function isJSONRPCInvocation(value) {
3070
3901
  return isJSONRPCRequest(value) || isJSONRPCNotification(value);
@@ -3082,7 +3913,7 @@ function isJSONRPCInvocation(value) {
3082
3913
  * Total.
3083
3914
  *
3084
3915
  * @param value - The already-parsed value to test
3085
- * @returns `true` when `value` is a valid JSON-RPC result response
3916
+ * @returns True if `value` is a valid JSON-RPC result response; false otherwise
3086
3917
  *
3087
3918
  * @example
3088
3919
  * ```ts
@@ -3118,7 +3949,7 @@ function isJSONRPCResultResponse(value) {
3118
3949
  * itself the hostile step, and it is bounded here rather than allowed to escape. Total.
3119
3950
  *
3120
3951
  * @param value - The already-parsed value to test
3121
- * @returns `true` when `value` carries an integer `code` and a string `message`
3952
+ * @returns True if `value` carries an integer `code` and a string `message`; false otherwise
3122
3953
  *
3123
3954
  * @example
3124
3955
  * ```ts
@@ -3142,7 +3973,7 @@ function isJSONRPCError(value) {
3142
3973
  * `result`. `error` carries an integer `code` and a string `message`. Total.
3143
3974
  *
3144
3975
  * @param value - The already-parsed value to test
3145
- * @returns `true` when `value` is a valid JSON-RPC error response
3976
+ * @returns True if `value` is a valid JSON-RPC error response; false otherwise
3146
3977
  *
3147
3978
  * @example
3148
3979
  * ```ts
@@ -3166,7 +3997,7 @@ function isJSONRPCErrorResponse(value) {
3166
3997
  * The union of the mutually exclusive arms. Total.
3167
3998
  *
3168
3999
  * @param value - The already-parsed value to test
3169
- * @returns `true` when `value` is a valid JSON-RPC response
4000
+ * @returns True if `value` is a valid JSON-RPC response; false otherwise
3170
4001
  */
3171
4002
  function isJSONRPCResponse(value) {
3172
4003
  return isJSONRPCResultResponse(value) || isJSONRPCErrorResponse(value);
@@ -3179,7 +4010,7 @@ function isJSONRPCResponse(value) {
3179
4010
  * The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total.
3180
4011
  *
3181
4012
  * @param value - The already-parsed value to test
3182
- * @returns `true` when `value` is a valid JSON-RPC message
4013
+ * @returns True if `value` is a valid JSON-RPC message; false otherwise
3183
4014
  */
3184
4015
  function isJSONRPCMessage(value) {
3185
4016
  return isJSONRPCInvocation(value) || isJSONRPCResponse(value);
@@ -3188,7 +4019,7 @@ function isJSONRPCMessage(value) {
3188
4019
  * Determines whether a parsed value is an MCP `initialize` invocation.
3189
4020
  *
3190
4021
  * @param value - The already-parsed value to test
3191
- * @returns `true` when `value` is a valid `initialize` request or notification
4022
+ * @returns True if `value` is a valid `initialize` request or notification; false otherwise
3192
4023
  *
3193
4024
  * @example
3194
4025
  * ```ts
@@ -3211,7 +4042,7 @@ function isInitializeRequest(value) {
3211
4042
  * legacy dispatch. Total over hostile and malformed input.
3212
4043
  *
3213
4044
  * @param value - The already-parsed value to inspect
3214
- * @returns `true` when the value is an invocation carrying the reserved version key
4045
+ * @returns True if the value is an invocation carrying the reserved version key; false otherwise
3215
4046
  */
3216
4047
  function isModernRequest(value) {
3217
4048
  const owned = attempt(() => cloneJSONRecord(value));
@@ -3241,6 +4072,27 @@ function inferEra(version) {
3241
4072
  if (isMCPLegacyVersion(version)) return "legacy";
3242
4073
  }
3243
4074
  /**
4075
+ * Infers the wire era one invocation's own structure selects.
4076
+ *
4077
+ * @remarks
4078
+ * The STRUCTURAL read, distinct from {@link inferEra}'s read of a revision string: era is fixed
4079
+ * by the reserved modern metadata a request carries, so this answers for a message whose
4080
+ * revision has not been read and cannot answer `undefined` — every invocation took one of the
4081
+ * two published wire shapes. It is what an observation surface reports and what an ingress
4082
+ * routes on, so both derive it here rather than each spelling the ternary out.
4083
+ *
4084
+ * @param invocation - The invocation whose structure selects the era
4085
+ * @returns `'modern'` when the invocation carries the modern request shape, `'legacy'` otherwise
4086
+ *
4087
+ * @example
4088
+ * ```ts
4089
+ * inferRequestEra({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
4090
+ * ```
4091
+ */
4092
+ function inferRequestEra(invocation) {
4093
+ return isModernRequest(invocation) ? "modern" : "legacy";
4094
+ }
4095
+ /**
3244
4096
  * Infers the newest supported modern protocol revision present in a peer's offer.
3245
4097
  *
3246
4098
  * @param offered - The protocol revisions offered by the peer
@@ -3286,7 +4138,7 @@ function inferRequestVersion(message) {
3286
4138
  //#endregion
3287
4139
  //#region src/core/MCPMethodManager.ts
3288
4140
  /**
3289
- * The modern method registry an {@link import('./types.js').MCPServerInterface}
4141
+ * Holds the modern methods an {@link import('./types.js').MCPServerInterface}
3290
4142
  * dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
3291
4143
  * map rather than exposing one.
3292
4144
  *
@@ -3320,7 +4172,7 @@ var MCPMethodManager = class {
3320
4172
  //#endregion
3321
4173
  //#region src/core/MCPProgressReporter.ts
3322
4174
  /**
3323
- * A bounded, request-scoped progress handoff between one producer and one serial consumer.
4175
+ * Hands bounded, request-scoped progress from one producer to one serial consumer.
3324
4176
  *
3325
4177
  * The reporter holds at most one owned progress item. {@link report} applies backpressure until
3326
4178
  * {@link take} consumes that slot. It has no replay, queue, concurrent-consumer coordination,
@@ -3444,7 +4296,8 @@ var MCPProgressReporter = class {
3444
4296
  //#endregion
3445
4297
  //#region src/core/MCPStreamController.ts
3446
4298
  /**
3447
- * The one cancellation engine every modern held-open result leaves `MCPServer` through.
4299
+ * Provides the one cancellation engine every modern held-open result leaves `MCPServer`
4300
+ * through.
3448
4301
  *
3449
4302
  * @remarks
3450
4303
  * A native async generator decides cancellation with a QUEUE: `return()` and `throw()` wait
@@ -3641,7 +4494,7 @@ var MCPStreamController = class {
3641
4494
  //#endregion
3642
4495
  //#region src/core/MCPTextStreamController.ts
3643
4496
  /**
3644
- * The string-boundary mirror of a controlled held-open result — the same exchange, already
4497
+ * Mirrors a controlled held-open result at the string boundary — the same exchange, already
3645
4498
  * serialized.
3646
4499
  *
3647
4500
  * @remarks
@@ -3803,12 +4656,7 @@ var MCPLegacy = class {
3803
4656
  }
3804
4657
  async handle(message, options) {
3805
4658
  if (!isBoundedString(message, this.limit.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
3806
- let parsed;
3807
- try {
3808
- parsed = JSON.parse(message);
3809
- } catch {
3810
- return this.#options.dispatcher.handle(message, options);
3811
- }
4659
+ const parsed = parseJSON(message);
3812
4660
  if (isModernRequest(parsed) || !isJSONRPCInvocation(parsed)) return this.#options.dispatcher.handle(message, options);
3813
4661
  const answer = await this.#legacy(parsed, options);
3814
4662
  return answer === void 0 ? void 0 : JSON.stringify(answer);
@@ -3895,7 +4743,7 @@ var MCPLegacyClientTransport = class {
3895
4743
  if (requested !== void 0 && !isMCPLegacyVersion(requested)) throw new MCPError("Unsupported legacy protocol version", MCP_UNSUPPORTED_VERSION, { requested });
3896
4744
  this.#transport = transport;
3897
4745
  this.#client = options?.identity ?? {
3898
- name: "taverna",
4746
+ name: "@orkestrel/mcp",
3899
4747
  version: "1.0.0"
3900
4748
  };
3901
4749
  this.#capabilities = options?.capabilities ?? {};
@@ -4087,8 +4935,8 @@ var MCPLegacyClientTransport = class {
4087
4935
  //#endregion
4088
4936
  //#region src/core/MCPServer.ts
4089
4937
  /**
4090
- * A transport-agnostic Model Context Protocol server dispatches JSON-RPC 2.0
4091
- * requests over a live {@link ToolManagerInterface}, with NO transport coupling.
4938
+ * Dispatches JSON-RPC 2.0 requests over a live {@link ToolManagerInterface}, with NO
4939
+ * transport coupling.
4092
4940
  *
4093
4941
  * @remarks
4094
4942
  * - **`dispatch` and `handle`.** `dispatch(invocation)` runs an already-parsed invocation and
@@ -4165,8 +5013,21 @@ var MCPServer = class {
4165
5013
  if (decoded === void 0 || !("method" in decoded)) return buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request");
4166
5014
  return this.#dispatch(decoded, options);
4167
5015
  }
5016
+ async handle(message, options) {
5017
+ if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
5018
+ const parsed = parseJSON(message);
5019
+ if (parsed === void 0) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
5020
+ const decoded = parseJSONRPCMessage(parsed, {
5021
+ bytes: this.#limits.message,
5022
+ depth: this.#limits.depth
5023
+ });
5024
+ if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
5025
+ const answer = await this.#dispatch(decoded, options ?? {});
5026
+ if (answer === void 0) return void 0;
5027
+ return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
5028
+ }
4168
5029
  async #dispatch(invocation, options) {
4169
- this.#emitter.emit("request", invocation.method, invocation.id, "modern");
5030
+ this.#emitter.emit("request", invocation.method, invocation.id, inferRequestEra(invocation));
4170
5031
  if (invocation.id === void 0) return;
4171
5032
  const id = invocation.id;
4172
5033
  const metadata = invocation.params?.["_meta"];
@@ -4184,26 +5045,9 @@ var MCPServer = class {
4184
5045
  return this.#contain(error, id);
4185
5046
  }
4186
5047
  }
4187
- async handle(message, options) {
4188
- if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4189
- let parsed;
4190
- try {
4191
- parsed = JSON.parse(message);
4192
- } catch {
4193
- return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
4194
- }
4195
- const decoded = parseJSONRPCMessage(parsed, {
4196
- bytes: this.#limits.message,
4197
- depth: this.#limits.depth
4198
- });
4199
- if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
4200
- const answer = await this.#dispatch(decoded, options ?? {});
4201
- if (answer === void 0) return void 0;
4202
- return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
4203
- }
4204
5048
  #register() {
4205
- this.#methods.add("server/discover", async (request, _options) => this.#discover(request));
4206
- this.#methods.add("tools/list", async (request, _options) => this.#list(request));
5049
+ this.#methods.add("server/discover", async (request) => this.#discover(request));
5050
+ this.#methods.add("tools/list", async (request) => this.#list(request));
4207
5051
  this.#methods.add("tools/call", async (request, options) => this.#call(request, options));
4208
5052
  this.#methods.add("subscriptions/listen", async (request, options) => this.#subscribe(request, options));
4209
5053
  const resources = this.#options.resources;
@@ -4292,13 +5136,7 @@ var MCPServer = class {
4292
5136
  if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
4293
5137
  if (isMCPInputResult(captured[0])) {
4294
5138
  const input = captured[0];
4295
- return buildJSONRPCResult(request.id, {
4296
- ...input,
4297
- _meta: {
4298
- ...input["_meta"] ?? {},
4299
- [MCP_META_SERVER]: this.#options.identity
4300
- }
4301
- });
5139
+ return this.#forward(input, request);
4302
5140
  }
4303
5141
  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");
4304
5142
  const result = buildModernResult({ contents: captured[0] }, this.#options.identity, this.#options.cache?.ttl ?? 6e4, this.#options.cache?.scope);
@@ -4360,13 +5198,7 @@ var MCPServer = class {
4360
5198
  if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
4361
5199
  if (isMCPInputResult(captured[0])) {
4362
5200
  const input = captured[0];
4363
- return buildJSONRPCResult(request.id, {
4364
- ...input,
4365
- _meta: {
4366
- ...input["_meta"] ?? {},
4367
- [MCP_META_SERVER]: this.#options.identity
4368
- }
4369
- });
5201
+ return this.#forward(input, request);
4370
5202
  }
4371
5203
  if (!isMCPPromptGetResult(captured[0])) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
4372
5204
  const result = captured[0];
@@ -4429,20 +5261,20 @@ var MCPServer = class {
4429
5261
  async #defer(request, call, options) {
4430
5262
  const configured = this.#options.task;
4431
5263
  if (configured === void 0) return void 0;
4432
- const deferral = {
5264
+ const deferred = {
4433
5265
  request,
4434
5266
  call,
4435
5267
  tools: this.#options.tools
4436
5268
  };
4437
- const key = await configured.defer(deferral, options);
5269
+ const key = await configured.deferral(deferred, options);
4438
5270
  if (isUndefined(key)) return void 0;
4439
5271
  if (!isString(key) || key.length === 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server execution returned an invalid task key");
4440
5272
  const context = parseRequestContext(request, {
4441
5273
  bytes: this.#limits.message,
4442
5274
  depth: this.#limits.depth
4443
5275
  });
4444
- 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]: {} } } });
4445
- const created = await configured.tasks.start(key, deferral, options);
5276
+ 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]: {} } } });
5277
+ const created = await configured.tasks.start(key, deferred, options);
4446
5278
  const captured = snapshotJSON({
4447
5279
  resultType: "task",
4448
5280
  taskId: created.taskId,
@@ -4520,21 +5352,46 @@ var MCPServer = class {
4520
5352
  });
4521
5353
  if (digest === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: tool arguments are too large or unsafe");
4522
5354
  if (params?.["requestState"] !== void 0 || params?.["inputResponses"] !== void 0) return this.#retry(request, name, digest, args, options);
4523
- const elicitation = await configured.elicit({
5355
+ const selected = await configured.selector({
4524
5356
  request,
4525
5357
  name,
4526
5358
  arguments: args
4527
5359
  }, options);
4528
- if (elicitation === void 0) return void 0;
4529
- const form = this.#form(elicitation);
5360
+ if (selected === void 0) return void 0;
5361
+ const round = this.#ownRound(selected);
4530
5362
  const context = parseRequestContext(request, {
4531
5363
  bytes: this.#limits.message,
4532
5364
  depth: this.#limits.depth
4533
5365
  });
4534
- if (form === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: elicitation policy returned an invalid form or continuation context");
4535
- if (context === void 0 || !isFormElicitationSupported(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the elicitation capability for this request", { requiredCapabilities: { elicitation: {} } });
5366
+ if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5367
+ const refusal = this.#gate(round, context, id);
5368
+ if (refusal !== void 0) return refusal;
4536
5369
  const principal = await configured.principal(request, options);
4537
- return this.#required(request, name, digest, form, principal, id, void 0);
5370
+ return this.#required(request, name, digest, round, principal, id, void 0);
5371
+ }
5372
+ #gate(round, context, id) {
5373
+ if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
5374
+ const missing = computeMissingCapabilities(round.requests, context.capabilities);
5375
+ if (missing === void 0) return void 0;
5376
+ return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires a client capability this request did not declare", { requiredCapabilities: missing });
5377
+ }
5378
+ #forward(input, request) {
5379
+ const requests = input.inputRequests;
5380
+ if (requests !== void 0) {
5381
+ const context = parseRequestContext(request, {
5382
+ bytes: this.#limits.message,
5383
+ depth: this.#limits.depth
5384
+ });
5385
+ const refusal = this.#gate({ requests }, context, request.id);
5386
+ if (refusal !== void 0) return refusal;
5387
+ }
5388
+ return buildJSONRPCResult(request.id, {
5389
+ ...input,
5390
+ _meta: {
5391
+ ...input["_meta"] ?? {},
5392
+ [MCP_META_SERVER]: this.#options.identity
5393
+ }
5394
+ });
4538
5395
  }
4539
5396
  async #retry(request, name, digest, args, options) {
4540
5397
  const configured = this.#options.input;
@@ -4547,55 +5404,66 @@ var MCPServer = class {
4547
5404
  bytes: this.#limits.message,
4548
5405
  depth: this.#limits.depth
4549
5406
  });
4550
- if (context === void 0 || !isFormElicitationSupported(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the elicitation capability for this request", { requiredCapabilities: { elicitation: {} } });
5407
+ if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
4551
5408
  const verified = await configured.continuation.open(requestState);
4552
5409
  if (verified === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be recovered");
4553
5410
  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);
4554
5411
  const state = parseMCPInputState(verified);
4555
5412
  if (state === void 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a malformed protected payload"), id);
4556
- const response = inputResponses[state.key];
4557
- 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");
4558
- 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");
5413
+ 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");
5414
+ const responses = this.#checkAnswers(state.requests, inputResponses);
5415
+ if (responses === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an input response is missing or malformed");
4559
5416
  const principal = await configured.principal(request, options);
4560
5417
  if (!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");
4561
- const elicitation = await configured.elicit({
5418
+ const selected = await configured.selector({
4562
5419
  request,
4563
5420
  name,
4564
5421
  arguments: args,
4565
- response,
5422
+ responses,
4566
5423
  ...state.state !== void 0 ? { state: state.state } : {}
4567
5424
  }, options);
4568
5425
  if (state.expiry <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
4569
- if (elicitation === void 0) return void 0;
4570
- const form = this.#form(elicitation);
4571
- if (form === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: elicitation policy returned an invalid form or continuation context");
4572
- return this.#required(request, name, digest, form, principal, state.id, state.expiry);
5426
+ if (selected === void 0) return void 0;
5427
+ const round = this.#ownRound(selected);
5428
+ if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5429
+ const refusal = this.#gate(round, context, id);
5430
+ if (refusal !== void 0) return refusal;
5431
+ return this.#required(request, name, digest, round, principal, state.id, state.expiry);
5432
+ }
5433
+ #checkAnswers(requests, responses) {
5434
+ const answered = {};
5435
+ for (const [key, issued] of Object.entries(requests)) {
5436
+ const response = responses[key];
5437
+ if (!Object.hasOwn(responses, key) || !isMCPInputResponse(response, issued)) return void 0;
5438
+ answered[key] = response;
5439
+ }
5440
+ return Object.freeze(answered);
4573
5441
  }
4574
- #form(elicitation) {
4575
- const owned = snapshotJSON(elicitation, {
5442
+ #ownRound(round) {
5443
+ const owned = snapshotJSON(round, {
4576
5444
  bytes: this.#limits.content,
4577
5445
  keys: this.#limits.keys,
4578
5446
  depth: this.#limits.depth
4579
5447
  });
4580
5448
  if (owned === void 0 || !isRecord(owned[0])) return void 0;
4581
- const form = owned[0]["request"];
5449
+ const requests = owned[0]["requests"];
4582
5450
  const state = owned[0]["state"];
4583
- if (!isMCPElicitForm(form) || !isUndefined(state) && !isJSONValue(state)) return void 0;
4584
- return isUndefined(state) ? { request: form } : {
4585
- request: form,
5451
+ if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
5452
+ if (!isUndefined(state) && !isJSONValue(state)) return void 0;
5453
+ return isUndefined(state) ? { requests } : {
5454
+ requests,
4586
5455
  state
4587
5456
  };
4588
5457
  }
4589
- async #required(request, name, digest, form, principal, origin, previous) {
5458
+ async #required(request, name, digest, round, principal, origin, previous) {
4590
5459
  const id = request.id;
4591
5460
  const configured = this.#options.input;
4592
5461
  const context = parseRequestContext(request, {
4593
5462
  bytes: this.#limits.message,
4594
5463
  depth: this.#limits.depth
4595
5464
  });
4596
- if (configured === void 0 || context === void 0 || !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");
5465
+ if (configured === void 0 || context === void 0 || !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");
4597
5466
  if (previous !== void 0 && previous <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
4598
- const key = crypto.randomUUID();
4599
5467
  const expiry = Date.now() + configured.ttl;
4600
5468
  const protectedState = {
4601
5469
  principal,
@@ -4603,11 +5471,10 @@ var MCPServer = class {
4603
5471
  id: origin,
4604
5472
  version: context.version,
4605
5473
  method: request.method,
4606
- key,
5474
+ requests: round.requests,
4607
5475
  name,
4608
5476
  digest,
4609
- schema: form.request.requestedSchema,
4610
- ...form.state !== void 0 ? { state: form.state } : {}
5477
+ ...round.state !== void 0 ? { state: round.state } : {}
4611
5478
  };
4612
5479
  if (!isBoundedJSON(protectedState, {
4613
5480
  bytes: this.#limits.state,
@@ -4623,13 +5490,7 @@ var MCPServer = class {
4623
5490
  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");
4624
5491
  return buildJSONRPCResult(id, {
4625
5492
  resultType: "input_required",
4626
- inputRequests: { [key]: {
4627
- method: "elicitation/create",
4628
- params: {
4629
- ...form.request,
4630
- mode: "form"
4631
- }
4632
- } },
5493
+ inputRequests: round.requests,
4633
5494
  requestState,
4634
5495
  _meta: { [MCP_META_SERVER]: this.#options.identity }
4635
5496
  });
@@ -4666,7 +5527,7 @@ var MCPServer = class {
4666
5527
  }
4667
5528
  yield buildSubscriptionAcknowledgement(notifications, id);
4668
5529
  if (configured !== void 0) {
4669
- const iterator = (await configured.listen(notifications, options))[Symbol.asyncIterator]();
5530
+ const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
4670
5531
  options.signal.addEventListener("abort", () => void iterator.return?.(void 0)?.catch(() => void 0), { once: true });
4671
5532
  for (let next = await iterator.next(); next.done !== true; next = await iterator.next()) {
4672
5533
  const owned = parseJSONRPCMessage(next.value, {
@@ -4685,22 +5546,22 @@ var MCPServer = class {
4685
5546
  slot.abort();
4686
5547
  }
4687
5548
  }
4688
- #named(request) {
5549
+ #readTaskId(request) {
4689
5550
  const id = request.id;
4690
5551
  const context = parseRequestContext(request, {
4691
5552
  bytes: this.#limits.message,
4692
5553
  depth: this.#limits.depth
4693
5554
  });
4694
- 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]: {} } } });
5555
+ 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]: {} } } });
4695
5556
  const taskId = request.params?.["taskId"];
4696
5557
  if (!isBoundedString(taskId, this.#limits.state) || taskId.length === 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: a bounded string `taskId` is required");
4697
5558
  return taskId;
4698
5559
  }
4699
5560
  async #task(request, tasks, options) {
4700
5561
  const id = request.id;
4701
- const named = this.#named(request);
4702
- if (!isString(named)) return named;
4703
- const found = await tasks.task(named, options);
5562
+ const taskId = this.#readTaskId(request);
5563
+ if (!isString(taskId)) return taskId;
5564
+ const found = await tasks.task(taskId, options);
4704
5565
  if (found === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4705
5566
  const owned = snapshotJSON(found, {
4706
5567
  bytes: this.#limits.content,
@@ -4712,20 +5573,20 @@ var MCPServer = class {
4712
5573
  }
4713
5574
  async #update(request, tasks, options) {
4714
5575
  const id = request.id;
4715
- const named = this.#named(request);
4716
- if (!isString(named)) return named;
5576
+ const taskId = this.#readTaskId(request);
5577
+ if (!isString(taskId)) return taskId;
4717
5578
  const responses = request.params?.["inputResponses"];
4718
5579
  if (!isRecord(responses)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an `inputResponses` object is required");
4719
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4720
- await tasks.update(named, responses, options);
5580
+ if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5581
+ await tasks.update(taskId, responses, options);
4721
5582
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
4722
5583
  }
4723
5584
  async #abort(request, tasks, options) {
4724
5585
  const id = request.id;
4725
- const named = this.#named(request);
4726
- if (!isString(named)) return named;
4727
- if (!isMCPTaskDetail(await tasks.task(named, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
4728
- await tasks.abort(named, options);
5586
+ const taskId = this.#readTaskId(request);
5587
+ if (!isString(taskId)) return taskId;
5588
+ if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
5589
+ await tasks.abort(taskId, options);
4729
5590
  return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
4730
5591
  }
4731
5592
  #contain(error, id) {
@@ -4775,8 +5636,8 @@ var MCPServer = class {
4775
5636
  //#endregion
4776
5637
  //#region src/core/MCPTaskClient.ts
4777
5638
  /**
4778
- * The CLIENT half of the stable Tasks extension — the `tasks/*` methods over one
4779
- * correlated-request door, exposed as an {@link import('./types.js').MCPClientInterface}'s
5639
+ * Issues the `tasks/*` methods over one correlated-request door — the CLIENT half of the
5640
+ * stable Tasks extension, exposed as an {@link import('./types.js').MCPClientInterface}'s
4780
5641
  * `tasks`.
4781
5642
  *
4782
5643
  * @remarks
@@ -4835,9 +5696,9 @@ var MCPTaskClient = class {
4835
5696
  //#endregion
4836
5697
  //#region src/core/MCPClient.ts
4837
5698
  /**
4838
- * A transport-agnostic Model Context Protocol CLIENT connects to a REMOTE MCP server
4839
- * over an injected {@link MCPClientTransportInterface}, negotiates the modern revision, and
4840
- * exposes the server's tools as local {@link ToolInterface}s an agent can run.
5699
+ * Connects to a REMOTE MCP server over any injected {@link MCPMessageTransportInterface},
5700
+ * negotiates the modern revision, and exposes the server's tools as local
5701
+ * {@link ToolInterface}s an agent can run.
4841
5702
  *
4842
5703
  * @remarks
4843
5704
  * - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
@@ -4928,7 +5789,7 @@ var MCPClient = class {
4928
5789
  });
4929
5790
  this.#transport = options.transport;
4930
5791
  this.#identity = options.identity ?? {
4931
- name: "taverna",
5792
+ name: "@orkestrel/mcp",
4932
5793
  version: "1.0.0"
4933
5794
  };
4934
5795
  this.#capabilities = options.capabilities ?? {};
@@ -5044,7 +5905,7 @@ var MCPClient = class {
5044
5905
  name,
5045
5906
  arguments: args,
5046
5907
  ...input === void 0 ? {} : {
5047
- requestState: input.state,
5908
+ ...input.state === void 0 ? {} : { requestState: input.state },
5048
5909
  inputResponses: input.responses
5049
5910
  }
5050
5911
  }, this.#timeout, void 0, options));
@@ -5069,18 +5930,19 @@ var MCPClient = class {
5069
5930
  } }
5070
5931
  }
5071
5932
  };
5072
- const subscription = {
5073
- queue: [],
5074
- capacity
5075
- };
5076
5933
  const abort = this.#abortSubscription.bind(this, id, signal);
5077
5934
  signal.addEventListener("abort", abort, { once: true });
5078
5935
  this.#pending.set(id, {
5079
5936
  method,
5080
5937
  signal,
5081
5938
  abort,
5082
- subscription
5939
+ subscription: {
5940
+ queue: [],
5941
+ capacity
5942
+ }
5083
5943
  });
5944
+ const subscription = this.#pending.get(id)?.subscription;
5945
+ if (subscription === void 0) throw new Error("MCP subscription state is missing");
5084
5946
  this.#transport.send(request).catch((error) => this.#settle(id, error, true));
5085
5947
  try {
5086
5948
  for (;;) {
@@ -5406,13 +6268,222 @@ var MCPClient = class {
5406
6268
  }
5407
6269
  };
5408
6270
  //#endregion
6271
+ //#region src/core/transports/HTTPClientTransport.ts
6272
+ /**
6273
+ * Drives a REMOTE Streamable-HTTP MCP server over `fetch` — a CLIENT
6274
+ * {@link MCPMessageTransportInterface} for the Model Context Protocol, the egress mirror of
6275
+ * the server's `createMCPRoutes`.
6276
+ *
6277
+ * @remarks
6278
+ * - **One class, both faces.** It touches `fetch`, `Response`, `AbortController`,
6279
+ * `AbortSignal`, and `WeakMap` alone, so it is host-independent and lives in core. Each
6280
+ * environment face publishes its own `createHTTPClientTransport` over it —
6281
+ * `@orkestrel/mcp/browser` and `@orkestrel/mcp/server` — and both factories return this
6282
+ * class, so a reply reaches a page and a Node process through the same decode.
6283
+ * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
6284
+ * message to `options.url` with `content-type: application/json` and an
6285
+ * `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
6286
+ * answer with either framing) — plus any `options.headers` (for example, an `Authorization`
6287
+ * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
6288
+ * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
6289
+ * to.
6290
+ * - **Both reply framings.** A `200` with an `application/json` body is parsed with
6291
+ * `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
6292
+ * `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
6293
+ * readEventStream}) — the inverse of the server's `createStream` seam, so the wire
6294
+ * round-trips. A `202`
6295
+ * Accepted (a notification) carries no body and emits nothing.
6296
+ * - **Session and protocol headers.** `start()` is a no-op (a
6297
+ * request/response transport opens no long-lived connection). The
6298
+ * `mcp-session-id` response header, when a STATEFUL server sends one (on
6299
+ * `initialize`), is captured into `session` and then ECHOED as the
6300
+ * `mcp-session-id` request header on every SUBSEQUENT request — so an
6301
+ * `MCPClient` passes a stateful server's session validation. The
6302
+ * initialize result's `protocolVersion` is likewise captured, but only
6303
+ * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
6304
+ * subsequent legacy requests. Modern requests instead derive protocol and method
6305
+ * headers from the message, plus the name header only for `tools/call` — carried in the
6306
+ * protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
6307
+ * Before initialize returns, neither captured legacy header is sent.
6308
+ * `close()` clears the captured protocol so a reconnect's `initialize`
6309
+ * POST is headerless; the captured `session` persists across `close()`.
6310
+ * - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
6311
+ * ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
6312
+ * never ends would otherwise outlive the transport, with nothing left able to reach it. The
6313
+ * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
6314
+ * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
6315
+ * - **Total at the boundary, and a non-success reply REJECTS.** Every reply is narrowed
6316
+ * (`parseJSONRPCMessage`, the SSE decoder). A non-message success reply is dropped, never
6317
+ * asserted. A non-success reply that carries no valid JSON-RPC message rejects `send` with
6318
+ * an error naming its HTTP status and body shape — the peer answered, and answering the
6319
+ * caller's request with silence would leave it waiting out its own deadline for a failure
6320
+ * the transport already read. A valid JSON-RPC error body is emitted at any HTTP status,
6321
+ * because the protocol carries that outcome in band. A `fetch` or decode failure on a
6322
+ * success response surfaces on the `error` event rather than escaping `send`.
6323
+ * - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); fires
6324
+ * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
6325
+ *
6326
+ * @example
6327
+ * ```ts
6328
+ * const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
6329
+ * const client = new MCPClient({ transport })
6330
+ * await client.connect()
6331
+ * ```
6332
+ */
6333
+ var HTTPClientTransport = class {
6334
+ #emitter;
6335
+ #url;
6336
+ #headers;
6337
+ #fetch;
6338
+ #timeout;
6339
+ #pending = /* @__PURE__ */ new Set();
6340
+ #parameters = /* @__PURE__ */ new Map();
6341
+ #stamps = /* @__PURE__ */ new WeakMap();
6342
+ #session = void 0;
6343
+ #protocol = void 0;
6344
+ #generation = 0;
6345
+ #closed = false;
6346
+ constructor(options) {
6347
+ this.#emitter = new Emitter();
6348
+ this.#url = options.url;
6349
+ this.#headers = options.headers ?? {};
6350
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
6351
+ this.#timeout = options.timeout;
6352
+ }
6353
+ get emitter() {
6354
+ return this.#emitter;
6355
+ }
6356
+ get session() {
6357
+ return this.#session;
6358
+ }
6359
+ get duplex() {
6360
+ return false;
6361
+ }
6362
+ async start() {
6363
+ this.#closed = false;
6364
+ }
6365
+ async send(message) {
6366
+ this.#stamp(message);
6367
+ const request = new AbortController();
6368
+ this.#pending.add(request);
6369
+ try {
6370
+ await this.#exchange(message, request.signal);
6371
+ } finally {
6372
+ this.#pending.delete(request);
6373
+ }
6374
+ }
6375
+ async close() {
6376
+ if (this.#closed) return;
6377
+ this.#closed = true;
6378
+ for (const request of this.#pending) request.abort();
6379
+ this.#pending.clear();
6380
+ this.#protocol = void 0;
6381
+ this.#emitter.emit("close");
6382
+ }
6383
+ #stamp(message) {
6384
+ if (!isModernRequest(message) || message.method !== "tools/list") return;
6385
+ if (message.params?.["cursor"] === void 0) this.#generation += 1;
6386
+ this.#stamps.set(message, this.#generation);
6387
+ }
6388
+ async #exchange(message, signal) {
6389
+ let response;
6390
+ try {
6391
+ response = await this.#fetch(this.#url, {
6392
+ method: "POST",
6393
+ headers: {
6394
+ "content-type": "application/json",
6395
+ accept: "application/json, text/event-stream",
6396
+ ...this.#session === void 0 ? {} : { [MCP_SESSION_HEADER]: this.#session },
6397
+ ...this.#buildHeaders(message),
6398
+ ...this.#headers
6399
+ },
6400
+ body: JSON.stringify(message),
6401
+ signal: this.#timeout === void 0 ? signal : AbortSignal.any([signal, AbortSignal.timeout(this.#timeout)])
6402
+ });
6403
+ } catch (error) {
6404
+ this.#emitter.emit("error", error);
6405
+ return;
6406
+ }
6407
+ const session = response.headers.get(MCP_SESSION_HEADER);
6408
+ if (session !== null) this.#session = session;
6409
+ await this.#deliver(response, message);
6410
+ }
6411
+ #buildHeaders(message) {
6412
+ if (isModernRequest(message)) {
6413
+ const version = inferRequestVersion(message);
6414
+ const name = message.params?.["name"];
6415
+ return {
6416
+ ...version === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version },
6417
+ [MCP_METHOD_HEADER]: message.method,
6418
+ ...message.method === "tools/call" && isString(name) ? {
6419
+ [MCP_NAME_HEADER]: encodeSentinel(name),
6420
+ ...buildHeaderProjection(this.#parameters.get(name) ?? [], message.params?.["arguments"])
6421
+ } : {}
6422
+ };
6423
+ }
6424
+ return this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol };
6425
+ }
6426
+ async #deliver(response, sent) {
6427
+ if (response.status === 202) return;
6428
+ const type = response.headers.get("content-type") ?? "";
6429
+ let messages = [];
6430
+ let failure;
6431
+ try {
6432
+ if (type.includes("text/event-stream")) messages = await readEventStream(response);
6433
+ else if (type.includes("application/json")) {
6434
+ const message = parseJSONRPCMessage(await response.json());
6435
+ if (message !== void 0) messages = [message];
6436
+ }
6437
+ } catch (error) {
6438
+ failure = { error };
6439
+ }
6440
+ for (const message of messages) this.#capture(message, sent);
6441
+ if (!response.ok && messages.length === 0) throw buildResponseError(response, type);
6442
+ if (failure !== void 0) this.#emitter.emit("error", failure.error);
6443
+ }
6444
+ #capture(message, sent) {
6445
+ if (isJSONRPCResponse(message) && isRecord(message.result) && isMCPVersion(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
6446
+ this.#emitter.emit("message", this.#select(message, sent));
6447
+ }
6448
+ #select(message, sent) {
6449
+ if (!isModernRequest(sent) || sent.method !== "tools/list") return message;
6450
+ if (!isJSONRPCResponse(message) || message.error !== void 0) return message;
6451
+ const result = message.result;
6452
+ const listed = isRecord(result) ? result["tools"] : void 0;
6453
+ if (!isRecord(result) || !isArray(listed)) return message;
6454
+ const current = this.#stamps.get(sent) === this.#generation;
6455
+ if (current && sent.params?.["cursor"] === void 0) this.#parameters.clear();
6456
+ const kept = [];
6457
+ for (const tool of listed) {
6458
+ if (!isRecord(tool) || !isString(tool["name"])) {
6459
+ kept.push(tool);
6460
+ continue;
6461
+ }
6462
+ const parameters = buildHeaderParameters(tool["inputSchema"]);
6463
+ if (parameters === void 0) {
6464
+ 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`));
6465
+ continue;
6466
+ }
6467
+ if (current) this.#parameters.set(tool["name"], parameters);
6468
+ kept.push(tool);
6469
+ }
6470
+ return {
6471
+ ...message,
6472
+ result: {
6473
+ ...result,
6474
+ tools: kept
6475
+ }
6476
+ };
6477
+ }
6478
+ };
6479
+ //#endregion
5409
6480
  //#region src/core/factories.ts
5410
6481
  /**
5411
6482
  * Creates a transport-agnostic Model Context Protocol server — exposes a live
5412
6483
  * {@link import('@orkestrel/tool').ToolManagerInterface} and an optional
5413
6484
  * {@link import('./types.js').MCPResourceManagerInterface},
5414
6485
  * {@link import('./types.js').MCPPromptManagerInterface}, and
5415
- * {@link import('./types.js').MCPCompletionManagerInterface} over JSON-RPC 2.0.
6486
+ * {@link import('./types.js').MCPCompletionInterface} over JSON-RPC 2.0.
5416
6487
  *
5417
6488
  * @remarks
5418
6489
  * Pump raw message strings through `handle` (parse → dispatch → serialize) from a
@@ -5464,7 +6535,7 @@ function createMCPLegacy(server) {
5464
6535
  }
5465
6536
  /**
5466
6537
  * Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
5467
- * MCP server over an injected {@link import('./types.js').MCPClientTransportInterface},
6538
+ * MCP server over an injected {@link import('./types.js').MCPMessageTransportInterface},
5468
6539
  * negotiates the modern revision through `server/discover`, and exposes the server's tools as local
5469
6540
  * {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
5470
6541
  *
@@ -5495,7 +6566,7 @@ function createMCPLegacy(server) {
5495
6566
  * })
5496
6567
  * await client.connect()
5497
6568
  * agent.context.tools.add(await client.tools()) // give the agent the remote tools
5498
- * const value = await client.call('search', { query: 'mcp' })
6569
+ * const outcome = await client.call('search', { query: 'mcp' })
5499
6570
  * ```
5500
6571
  */
5501
6572
  function createMCPClient(options) {
@@ -5521,7 +6592,7 @@ function createMCPLegacyClientTransport(transport, options) {
5521
6592
  }
5522
6593
  /**
5523
6594
  * Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
5524
- * channel) into a {@link MCPClientTransportInterface} — the additive bridge that lets
6595
+ * channel) into a {@link MCPMessageTransportInterface} — the additive bridge that lets
5525
6596
  * `createMCPClient` run over the new port without any change to `MCPClient`'s
5526
6597
  * existing shape.
5527
6598
  *
@@ -5544,7 +6615,7 @@ function createMCPLegacyClientTransport(transport, options) {
5544
6615
  * capable emitter for `bindClient` to push onto.
5545
6616
  *
5546
6617
  * @param transport - The duplex channel to adapt
5547
- * @returns A {@link MCPClientTransportInterface} `createMCPClient` can drive
6618
+ * @returns A {@link MCPMessageTransportInterface} `createMCPClient` can drive
5548
6619
  *
5549
6620
  * @example
5550
6621
  * ```ts
@@ -5568,6 +6639,6 @@ function createDuplexClientTransport(transport) {
5568
6639
  };
5569
6640
  }
5570
6641
  //#endregion
5571
- export { DEFAULT_MCP_CACHE_TTL, DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_LIMITS, DEFAULT_MCP_REQUEST_TIMEOUT, DEFAULT_MCP_SUBSCRIPTION_CAPACITY, EMPTY_MCP_ARGUMENTS, JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPError, MCPLegacy, MCPLegacyClientTransport, MCPMethodManager, MCPProgressReporter, MCPServer, MCPStreamController, MCPTaskClient, MCPTextStreamController, MCP_EXTENSION_TASKS, MCP_FALLBACK_VERSION, MCP_HANDSHAKE_VERSION, MCP_HEADER_MISMATCH, MCP_META_CAPABILITIES, MCP_META_CLIENT, MCP_META_SERVER, MCP_META_SUBSCRIPTION, MCP_META_VERSION, MCP_MISSING_CAPABILITY, MCP_MODERN_VERSION, MCP_UNSUPPORTED_VERSION, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, SUPPORTED_MCP_VERSIONS, SUPPORTED_MODERN_PROTOCOL_VERSIONS, bindClient, bindServer, buildCallOutcome, buildCancelledNotification, buildDiscoverResult, buildInitializeResult, buildJSONRPCError, buildJSONRPCResult, buildMethodOptions, buildModernResult, buildProgressNotification, buildSubscriptionAcknowledgement, buildSubscriptionFilter, buildSubscriptionResult, buildToolCall, buildToolDescriptors, createDuplexClientTransport, createMCPClient, createMCPLegacy, createMCPLegacyClientTransport, createMCPServer, decodeBoundedMessage, digestJSON, extractContentText, inferEra, inferRequestVersion, inferVersion, isAbsoluteURI, isBoundedJSON, isBoundedString, isElicitContent, isFormElicitationSupported, isInitializeRequest, isJSONObject, isJSONRPCError, isJSONRPCErrorResponse, isJSONRPCId, isJSONRPCInvocation, isJSONRPCMessage, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResponse, isJSONRPCResultResponse, isMCPAnnotations, isMCPBlobResource, isMCPCallResult, isMCPClientCapabilities, isMCPCompletion, isMCPCompletionParams, isMCPCompletionReference, isMCPCompletionResult, isMCPContent, isMCPElicitFieldSchema, isMCPElicitForm, isMCPElicitRequest, isMCPElicitResult, isMCPElicitSchema, isMCPElicitURL, isMCPError, isMCPIcon, isMCPIdentity, isMCPInputRequest, isMCPInputRequestMap, isMCPInputResult, isMCPLegacyResult, isMCPLegacyVersion, isMCPLoggingLevel, isMCPMetaKey, isMCPMetaObject, isMCPModernVersion, isMCPNotificationMetaObject, isMCPPaginationParams, isMCPProgress, isMCPPrompt, isMCPPromptArgument, isMCPPromptGetResult, isMCPPromptMessage, isMCPPromptPage, isMCPResource, isMCPResourceContents, isMCPResourcePage, isMCPResourceTemplate, isMCPResourceTemplatePage, isMCPResult, isMCPResultMetaObject, isMCPServerCapabilities, isMCPStringArguments, isMCPSubscriptionFilter, isMCPSubscriptionResult, isMCPTaskDetail, isMCPTaskDetailResult, isMCPTaskNotification, isMCPTaskResult, isMCPTaskStatus, isMCPTextResource, isMCPVersion, isModernRequest, isRFC3339Date, isRFC3339DateTime, isStandardBase64, isTaskSupported, legacyInvocationToModern, legacyResultToModern, matchesResultType, matchesSubscriptionNotification, modernInvocationToLegacy, modernResultToLegacy, parseJSONRPCMessage, parseMCPInputState, parseRequestContext, readCancelledId, sendStream, serializeJSON, snapshotJSON, snapshotToolResult, stampSubscriptionNotification };
6642
+ export { DEFAULT_MCP_CACHE_TTL, DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_LIMITS, DEFAULT_MCP_REQUEST_TIMEOUT, DEFAULT_MCP_SUBSCRIPTION_CAPACITY, EMPTY_MCP_ARGUMENTS, HTTPClientTransport, JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPError, MCPLegacy, MCPLegacyClientTransport, MCPMethodManager, MCPProgressReporter, MCPServer, MCPStreamController, MCPTaskClient, MCPTextStreamController, MCP_EXTENSION_TASKS, MCP_FALLBACK_VERSION, MCP_HANDSHAKE_VERSION, MCP_HEADER_ANNOTATION, MCP_HEADER_MISMATCH, MCP_LOOKUP_PAGES, MCP_META_CAPABILITIES, MCP_META_CLIENT, MCP_META_SERVER, MCP_META_SUBSCRIPTION, MCP_META_VERSION, MCP_METHOD_HEADER, MCP_MISSING_CAPABILITY, MCP_MODERN_VERSION, MCP_NAME_HEADER, MCP_PARAM_PREFIX, MCP_PROTOCOL_VERSION_HEADER, MCP_SENTINEL_PREFIX, MCP_SENTINEL_SUFFIX, MCP_SESSION_HEADER, MCP_UNSUPPORTED_VERSION, MCP_WEBSOCKET_SUBPROTOCOL, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, SUPPORTED_MCP_VERSIONS, SUPPORTED_MODERN_PROTOCOL_VERSIONS, bindClient, bindServer, buildCallOutcome, buildCancelledNotification, buildDiscoverResult, buildHeaderParameters, buildHeaderProjection, buildInitializeResult, buildJSONRPCError, buildJSONRPCResult, buildMethodOptions, buildModernResult, buildProgressNotification, buildResponseError, buildSubscriptionAcknowledgement, buildSubscriptionFilter, buildSubscriptionResult, buildToolCall, buildToolDescriptors, computeMissingCapabilities, countHeaderAnnotations, createDuplexClientTransport, createMCPClient, createMCPLegacy, createMCPLegacyClientTransport, createMCPServer, decodeBoundedMessage, decodeEvent, decodeSentinel, deliverMessage, digestJSON, encodeSentinel, extractContentText, extractHeaderAnnotations, extractToolSchema, inferEra, inferRequestEra, inferRequestVersion, inferVersion, isAbsoluteURI, isBoundedJSON, isBoundedString, isElicitContent, isFieldToken, isInitializeRequest, isJSONObject, isJSONRPCError, isJSONRPCErrorResponse, isJSONRPCId, isJSONRPCInvocation, isJSONRPCMessage, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResponse, isJSONRPCResultResponse, isMCPAnnotations, isMCPBlobResource, isMCPCallResult, isMCPClientCapabilities, isMCPCompletion, isMCPCompletionParams, isMCPCompletionReference, isMCPCompletionResult, isMCPContent, isMCPElicitFieldSchema, isMCPElicitForm, isMCPElicitRequest, isMCPElicitResult, isMCPElicitSchema, isMCPElicitURL, isMCPError, isMCPHeaderPrimitive, isMCPIcon, isMCPIdentity, isMCPInputRequest, isMCPInputRequestMap, isMCPInputResponse, isMCPInputResult, isMCPLegacyResult, isMCPLegacyVersion, isMCPLoggingLevel, isMCPMetaKey, isMCPMetaObject, isMCPModernVersion, isMCPNotificationMetaObject, isMCPPaginationParams, isMCPProgress, isMCPPrompt, isMCPPromptArgument, isMCPPromptGetResult, isMCPPromptMessage, isMCPPromptPage, isMCPResource, isMCPResourceContents, isMCPResourcePage, isMCPResourceTemplate, isMCPResourceTemplatePage, isMCPResult, isMCPResultMetaObject, isMCPRoot, isMCPRootResult, isMCPSampleContent, isMCPSampleResult, isMCPServerCapabilities, isMCPStringArguments, isMCPSubscriptionFilter, isMCPSubscriptionResult, isMCPTaskDetail, isMCPTaskDetailResult, isMCPTaskNotification, isMCPTaskResult, isMCPTaskStatus, isMCPTextResource, isMCPVersion, isModernRequest, isRFC3339Date, isRFC3339DateTime, isStandardBase64, legacyInvocationToModern, legacyResultToModern, matchesResultType, matchesSubscriptionNotification, modernInvocationToLegacy, modernResultToLegacy, parseJSONRPCMessage, parseMCPInputState, parseRequestContext, readCancelledId, readEventStream, renderHeaderValue, sendStream, serializeJSON, snapshotJSON, snapshotToolResult, stampSubscriptionNotification, supportsFormElicitation, supportsTask };
5572
6643
 
5573
6644
  //# sourceMappingURL=index.js.map