@orkestrel/mcp 0.0.28 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,8 @@ import { Emitter } from "@orkestrel/emitter";
5
5
  import { Tool } from "@orkestrel/tool";
6
6
  //#region src/core/constants.ts
7
7
  /**
8
- * Names the revision offered and defaulted to in the legacy `initialize` handshake.
8
+ * Names the revision offered and defaulted to in the legacy `initialize` handshake,
9
+ * `'2025-11-25'`.
9
10
  *
10
11
  * @remarks
11
12
  * This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
@@ -13,12 +14,15 @@ import { Tool } from "@orkestrel/tool";
13
14
  * it is asking to negotiate a revision with no negotiation.
14
15
  */
15
16
  var MCP_HANDSHAKE_VERSION = "2025-11-25";
16
- /** Names the older legacy revision the optional legacy decorator accepts and an adapter can pin. */
17
+ /**
18
+ * Names the older legacy revision the optional legacy decorator accepts and an adapter can pin,
19
+ * `'2025-06-18'`.
20
+ */
17
21
  var MCP_FALLBACK_VERSION = "2025-06-18";
18
- /** Names the modern revision offered by an unpinned client during discovery. */
22
+ /** Names the modern revision offered by an unpinned client during discovery, `'2026-07-28'`. */
19
23
  var MCP_MODERN_VERSION = "2026-07-28";
20
24
  /**
21
- * Lists the modern MCP protocol revisions a bare server accepts and advertises.
25
+ * Lists the modern MCP protocol revisions a bare server accepts and advertises, `2026-07-28`.
22
26
  *
23
27
  * @remarks
24
28
  * Frozen in discovery-advertisement order. Legacy revisions are absent because
@@ -26,7 +30,10 @@ var MCP_MODERN_VERSION = "2026-07-28";
26
30
  * decorator own them.
27
31
  */
28
32
  var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
29
- /** Lists the protocol revisions accepted by the optional legacy decorator. */
33
+ /**
34
+ * Lists the protocol revisions accepted by the optional legacy decorator, `2025-11-25` and
35
+ * `2025-06-18`.
36
+ */
30
37
  var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
31
38
  /**
32
39
  * Lists the protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy
@@ -47,8 +54,8 @@ var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
47
54
  * Names the reserved extension key identifying the stable Tasks extension.
48
55
  *
49
56
  * @remarks
50
- * The ONE spelling of it in this package, and the identity of the immutable snapshot dated
51
- * 2026-07-28 this package implements. A client declares it per REQUEST, under
57
+ * The one spelling of it in this package, and the identity of the immutable snapshot dated
58
+ * 2026-07-28 this package implements. A client declares it per request, under
52
59
  * `_meta['io.modelcontextprotocol/clientCapabilities'].extensions`; a server advertises it
53
60
  * under `server/discover`'s `capabilities.extensions`. Both sides carry an empty object —
54
61
  * the extension defines no options, so presence is the entire declaration.
@@ -58,10 +65,10 @@ var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
58
65
  * Names the opening marker of the Base64 sentinel a standard MCP header value travels in.
59
66
  *
60
67
  * @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
68
+ * The markers are lowercase and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
69
+ * their one spelling in this package: {@link import('@orkestrel/mcp').encodeSentinel} builds a
63
70
  * sentinel from them and {@link import('@orkestrel/mcp').decodeSentinel} recognizes one by
64
- * them, so the two directions cannot drift apart.
71
+ * them, so the directions cannot drift apart.
65
72
  */
66
73
  var MCP_SENTINEL_PREFIX = "=?base64?";
67
74
  /** Names the closing marker of the Base64 sentinel a standard MCP header value travels in. */
@@ -80,7 +87,7 @@ var MCP_PARAM_PREFIX = "Mcp-Param-";
80
87
  * Names the Streamable-HTTP transport header that carries the MCP session id.
81
88
  *
82
89
  * @remarks
83
- * A STATEFUL server sends it on the `initialize` reply, and
90
+ * A stateful server sends it on the `initialize` reply, and
84
91
  * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} echoes it as a
85
92
  * request header on every subsequent request, so a client passes that server's session
86
93
  * validation unchanged.
@@ -113,7 +120,7 @@ var MCP_NAME_HEADER = "mcp-name";
113
120
  * Identifies the tool-schema annotation key naming the header one parameter projects into.
114
121
  *
115
122
  * @remarks
116
- * It is valid ONLY on a primitive property schema statically reachable from the `inputSchema`
123
+ * It is valid only on a primitive property schema statically reachable from the `inputSchema`
117
124
  * root through `properties` keys alone. An occurrence anywhere else — under `items`, a
118
125
  * composition or conditional keyword, or a `$ref` target — makes the whole tool definition
119
126
  * invalid, which is what {@link import('@orkestrel/mcp').buildHeaderParameters} decides.
@@ -157,7 +164,7 @@ var MCP_HEADER_MISMATCH = -32020;
157
164
  * declared.
158
165
  *
159
166
  * @remarks
160
- * The GENERIC code for the whole condition, not one capability's code. This server answers
167
+ * The generic code for the whole condition, not one capability's code. This server answers
161
168
  * it in more than one place — a `tools/call` that needs `elicitation`, and a `tasks/*` request
162
169
  * whose client never declared `io.modelcontextprotocol/tasks` — and they are told apart by
163
170
  * `error.data.requiredCapabilities` alone (`{ elicitation: {} }` against
@@ -204,7 +211,7 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
204
211
  * Holds the one empty argument record every argument-less modern `tools/call` runs with.
205
212
  *
206
213
  * @remarks
207
- * Frozen and null-prototype, and SHARED: two calls that name no `arguments` receive the same
214
+ * Frozen and null-prototype, and shared: two calls that name no `arguments` receive the same
208
215
  * reference, so nothing a tool writes into its own `arguments` can survive into the next
209
216
  * call — the write fails instead. That failure is a tool-domain failure like any other: the
210
217
  * registry isolates it into a `success: false` result, which reaches the peer as an
@@ -228,7 +235,7 @@ var JSONRPC_INVALID_PARAMS = -32602;
228
235
  * valid request.
229
236
  *
230
237
  * @remarks
231
- * The code every MODERN internal fault answers with — a provider, handler, continuation,
238
+ * The code every modern internal fault answers with — a provider, handler, continuation,
232
239
  * capacity, stream-source, normalization, or serialization failure the server contained.
233
240
  * It is detail-free on the wire: the caught value reaches the application through the
234
241
  * server's `error` event and never through the response.
@@ -238,7 +245,7 @@ var JSONRPC_INTERNAL_ERROR = -32603;
238
245
  * Names the JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
239
246
  *
240
247
  * @remarks
241
- * Retained for the LEGACY branch alone. A modern fault answers
248
+ * Retained for the legacy branch alone. A modern fault answers
242
249
  * {@link JSONRPC_INTERNAL_ERROR}; this code survives only where an old-wire peer was
243
250
  * already characterized against it.
244
251
  */
@@ -437,19 +444,19 @@ function snapshotToolResult(value, limits) {
437
444
  *
438
445
  * @remarks
439
446
  * Total — a non-message returns `undefined`, never throws. The input must
440
- * ALREADY be `JSON.parse`d: the raw-string parse (which can throw on malformed
447
+ * already be `JSON.parse`d: the raw-string parse (which can throw on malformed
441
448
  * JSON) happens in `MCPServer.handle` inside a try/catch that maps a parse failure
442
449
  * to a `-32700` response.
443
450
  *
444
- * A defined result is an OWNED CANONICAL SNAPSHOT, never the input reference: it is
451
+ * A defined result is an owned canonical snapshot, never the input reference: it is
445
452
  * rebuilt from the canonical text and deeply frozen, so `-0` arrives as `0`. Every record
446
- * was SERIALIZED with its keys sorted, but the rebuilt object enumerates its own keys the
453
+ * was serialized with its keys sorted, but the rebuilt object enumerates its own keys the
447
454
  * way JavaScript does, so an integer-like `'9'` still precedes `'10'`: the result's key
448
455
  * order is neither promised nor generally the canonical one. A caller who needs canonical
449
- * BYTES takes them from `serializeJSON`/`snapshotJSON` rather than re-stringifying this
456
+ * bytes takes them from `serializeJSON`/`snapshotJSON` rather than re-stringifying this
450
457
  * result. Identity is not preserved and is not promised.
451
458
  *
452
- * The parser's sound partner is the COMPOSITE `isJSONRPCMessage(value) &&
459
+ * The parser's sound partner is the composite `isJSONRPCMessage(value) &&
453
460
  * isBoundedJSON(value, limits)`, and against it both halves of the soundness law
454
461
  * hold by construction:
455
462
  *
@@ -457,12 +464,12 @@ function snapshotToolResult(value, limits) {
457
464
  * is applied to the exact frozen reference returned.
458
465
  * - Every input satisfying the composite is admitted rather than rejected, because
459
466
  * `isBoundedJSON` is this parser's own admission test — the same canonical
460
- * serializer under the same `limits` — so the two cannot disagree about the bound.
467
+ * serializer under the same `limits` — so they cannot disagree about the bound.
461
468
  *
462
- * {@link isJSONRPCMessage} ALONE is not that partner. It is clone-backed and so already
469
+ * {@link isJSONRPCMessage} Alone is not that partner. It is clone-backed and so already
463
470
  * exact about shape, but it carries no size or depth bound — so guard-valid values
464
471
  * exist that this parser rejects: a message nested deeper than `limits.depth`, and one
465
- * whose canonical text exceeds `limits.bytes`. Those are named causes, NOT a complete
472
+ * whose canonical text exceeds `limits.bytes`. Those are named causes, not a complete
466
473
  * boundary. Among values `isJSONRPCMessage` already admits, the admitted set is exactly
467
474
  * what canonical serialization accepts under `limits`, so a caller who needs that line
468
475
  * tests it with `isBoundedJSON` rather than inferring it from this list.
@@ -492,7 +499,7 @@ function parseJSONRPCMessage(value, limits = {
492
499
  * This is the validity step after {@link isModernRequest}: a defined result can
493
500
  * only come from a guard-positive request, while a guard-positive request returns
494
501
  * `undefined` when its required modern metadata is malformed — and also when the
495
- * request falls outside the bound this parser INHERITS by routing through
502
+ * request falls outside the bound this parser inherits by routing through
496
503
  * {@link parseJSONRPCMessage} under the same `limits`. The version
497
504
  * must be a string but need not be supported; unsupported strings belong to the
498
505
  * dedicated protocol-version error path. Client identity is optional, but when
@@ -541,11 +548,11 @@ function parseRequestContext(value, limits = {
541
548
  * @remarks
542
549
  * This parser does not open the opaque continuation carrier; the configured
543
550
  * continuation port performs that boundary first. The protected
544
- * payload binds the authenticated principal, absolute expiry, ORIGINAL request id, version,
551
+ * payload binds the authenticated principal, absolute expiry, original request id, version,
545
552
  * method, the exact round that was issued, tool name, argument digest, and optional
546
553
  * application state. Every member is required except application state: a payload missing its
547
554
  * 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
555
+ * unenforced. An empty round is refused for the same reason — a retry against it would answer
549
556
  * no question at all. Total over malformed or hostile input.
550
557
  *
551
558
  * @param value - The opened canonical continuation value to parse
@@ -625,7 +632,7 @@ function supportsFormElicitation(value) {
625
632
  * Computes the capabilities one round of input requests needs and the client did not declare.
626
633
  *
627
634
  * @remarks
628
- * The protocol's rule is about SENDING: a server never issues a request kind the client's
635
+ * The protocol's rule is about sending: a server never issues a request kind the client's
629
636
  * declared capabilities exclude. So this reads the round rather than the method, and it
630
637
  * answers with the refusal's own payload — the `requiredCapabilities` record a
631
638
  * `MissingRequiredClientCapability` error carries, keyed by each missing capability, in the
@@ -637,7 +644,7 @@ function supportsFormElicitation(value) {
637
644
  * recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
638
645
  * has already refused the round it would have travelled in. Total over hostile input.
639
646
  *
640
- * The `elicitation` value names the ARM the round needs, so a client can act on the refusal
647
+ * The `elicitation` value names the arm the round needs, so a client can act on the refusal
641
648
  * by declaring exactly what the payload asks for. A missing URL arm answers `{ url: {} }`, a
642
649
  * missing form arm answers the empty record this package reads as form-only, and a round
643
650
  * needing both answers `{ form: {}, url: {} }`. An empty record for a URL round would name
@@ -688,14 +695,14 @@ function computeMissingCapabilities(requests, capabilities) {
688
695
  *
689
696
  * @remarks
690
697
  * The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and the schema
691
- * types its value EXACTLY EMPTY — `Record<string, never>`, an object with no additional
698
+ * types its value exactly empty — `Record<string, never>`, an object with no additional
692
699
  * properties. So the key's presence is the whole declaration, and the value carries the
693
700
  * whole of the check: a `true` or a string there is a client speaking a different protocol
694
701
  * rather than a shorthand, and a member inside the object is a client declaring an option
695
702
  * this extension does not define. Both are refused, because a server that accepted either
696
703
  * would be reading a shape no peer can produce from the snapshot's own schema.
697
704
  *
698
- * A client declares this PER REQUEST. Nothing here consults a session, because the modern
705
+ * A client declares this per request. Nothing here consults a session, because the modern
699
706
  * revision is stateless and a capability declared once at connect time says nothing about
700
707
  * the request in hand. Total over hostile input.
701
708
  *
@@ -976,7 +983,7 @@ function buildProgressNotification(token, progress) {
976
983
  * Builds one official cancellation notification for a request already sent.
977
984
  *
978
985
  * @remarks
979
- * `requestId` and `reason` are WIRE SPELLINGS carried verbatim from the dated schema's
986
+ * `requestId` and `reason` are wire spellings carried verbatim from the dated schema's
980
987
  * `CancelledNotificationParams`, and so is the `cancelled` in the method name — this
981
988
  * package's own vocabulary says `abort`, but the method is the protocol's and does not
982
989
  * change. The notification is FIRE-AND-FORGET in the strongest sense: it carries no id,
@@ -1014,7 +1021,7 @@ function buildCancelledNotification(id, reason) {
1014
1021
  * Determines whether one method may answer with a given modern `resultType`.
1015
1022
  *
1016
1023
  * @remarks
1017
- * The dated protocol lets a `tools/call` answer in more than one way — it COMPLETED, it became a
1024
+ * The dated protocol lets a `tools/call` answer in more than one way — it completed, it became a
1018
1025
  * durable task, or it needs another round trip — while every other method this client
1019
1026
  * issues has exactly one legal answer. So the arm a peer chose is only meaningful beside
1020
1027
  * the method it answers, and this is the one place that pairing is decided.
@@ -1043,9 +1050,9 @@ function matchesResultType(method, resultType) {
1043
1050
  * Concatenates an MCP tool-call result's text content blocks into one string.
1044
1051
  *
1045
1052
  * @remarks
1046
- * The inverse of a server splitting a value into text block(s), and TOTAL: a non-record
1053
+ * The inverse of a server splitting a value into text block(s), and total: a non-record
1047
1054
  * result, a non-array `content`, or a non-string `text` contributes nothing rather than
1048
- * throwing. What it returns is a RENDERING — the prose a model reads — and not the tool's
1055
+ * throwing. What it returns is a rendering — the prose a model reads — and not the tool's
1049
1056
  * value, which travels as `structuredContent` whenever the peer sent one.
1050
1057
  *
1051
1058
  * @param result - The unknown result payload to read content blocks from
@@ -1072,12 +1079,12 @@ function extractContentText(result) {
1072
1079
  * the arms the protocol gives a shape to, and deriving the tool's value from the one it
1073
1080
  * does not:
1074
1081
  *
1075
- * - A peer's `structuredContent` is PREFERRED over the content blocks, because it is the
1082
+ * - A peer's `structuredContent` is preferred over the content blocks, because it is the
1076
1083
  * tool's value in its original structure while the blocks are a rendering beside it. Its
1077
1084
  * mere presence decides — an explicit `null` is a value the tool returned, not an absence.
1078
1085
  * - With no structured value the legacy shape applies: the value was JSON-serialized into
1079
1086
  * the text block(s), so parse them and fall back to the raw string when they are not JSON.
1080
- * - A remote tool FAILURE (`isError: true`) THROWS the error text, so an agent's tool
1087
+ * - A remote tool failure (`isError: true`) throws the error text, so an agent's tool
1081
1088
  * registry isolates it into a failure result exactly as it would a local throw.
1082
1089
  *
1083
1090
  * @param name - The tool's name, used only to describe a failure that carried no text
@@ -1159,14 +1166,14 @@ function buildJSONRPCResult(id, result) {
1159
1166
  * as an `error` object.
1160
1167
  *
1161
1168
  * @remarks
1162
- * An `undefined` `id` is OMITTED from the envelope rather than serialized as `null`:
1169
+ * An `undefined` `id` is omitted from the envelope rather than serialized as `null`:
1163
1170
  * MCP overrides the base specification here, so a peer that could not have its id
1164
1171
  * read receives a response with no `id` member at all.
1165
1172
  *
1166
1173
  * @param id - The failed request's id, or `undefined` when none could be read
1167
1174
  * @param code - One of the reserved JSON-RPC codes (see `./constants.js`)
1168
1175
  * @param message - A short human description of the failure
1169
- * @param data - An OPTIONAL machine-readable payload (omitted from the envelope when absent)
1176
+ * @param data - An optional machine-readable payload (omitted from the envelope when absent)
1170
1177
  * @returns The error response envelope
1171
1178
  */
1172
1179
  function buildJSONRPCError(id, code, message, data) {
@@ -1188,11 +1195,11 @@ function buildJSONRPCError(id, code, message, data) {
1188
1195
  * receives.
1189
1196
  *
1190
1197
  * @remarks
1191
- * The ONE place a cancellation signal is resolved. A caller may have no signal to
1198
+ * The one place a cancellation signal is resolved. A caller may have no signal to
1192
1199
  * offer; a dispatched method always has one to observe, so a missing signal becomes
1193
1200
  * a real signal rather than an absence every downstream handler would have to case on.
1194
1201
  *
1195
- * The resolved signal is the request's LIFETIME, which is strictly wider than the
1202
+ * The resolved signal is the request's lifetime, which is strictly wider than the
1196
1203
  * caller's: it composes the caller's signal, when there is one, with the `lifetime`
1197
1204
  * dispatch owns and aborts once the answer this request produced is finished. That is
1198
1205
  * what wakes a stream producer parked on an event that will never arrive after its
@@ -1445,7 +1452,7 @@ function buildSubscriptionResult(id, identity) {
1445
1452
  * `capabilities.resources` and `capabilities.prompts` appear only for servers with their
1446
1453
  * respective managers and derive notification flags from the configured subscription filter.
1447
1454
  * `capabilities.completions` is independent and appears only with a completion provider.
1448
- * `capabilities.extensions` appears only for a server that CONFIGURED the extension it
1455
+ * `capabilities.extensions` appears only for a server that configured the extension it
1449
1456
  * would name. An advertisement is a promise a client is entitled to act on, so a server
1450
1457
  * with no `task` policy omits the member entirely rather than advertising an empty
1451
1458
  * record — and its discovery answer stays byte-for-byte what it was before the extension
@@ -1501,7 +1508,7 @@ function buildInitializeResult(name, version, requested) {
1501
1508
  * before it hands the string on.
1502
1509
  *
1503
1510
  * @remarks
1504
- * The bound is checked FIRST, against the raw string, so an oversized message is never
1511
+ * The bound is checked first, against the raw string, so an oversized message is never
1505
1512
  * parsed at all: a decoder that parses before it measures has already spent the work
1506
1513
  * the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
1507
1514
  * value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
@@ -1528,12 +1535,12 @@ function decodeBoundedMessage(message, limits) {
1528
1535
  * Decodes one inbound frame and delivers it onto a transport emitter as `message` or `error`.
1529
1536
  *
1530
1537
  * @remarks
1531
- * The ONE inbound fold every message-carrying transport in this package runs: parse the frame,
1538
+ * The one inbound fold every message-carrying transport in this package runs: parse the frame,
1532
1539
  * narrow it with `parseJSONRPCMessage`, emit `message` for a well-formed
1533
1540
  * {@link JSONRPCMessage}, and emit `error` for anything else. Total — an adversarial frame
1534
1541
  * produces an `error` emission and never a throw.
1535
1542
  *
1536
- * The two failures report differently on purpose. Unparsable text emits the CAUGHT parse
1543
+ * The failures report differently on purpose. Unparsable text emits the caught parse
1537
1544
  * error, which names the offending position; well-formed JSON that is not a JSON-RPC message
1538
1545
  * has no caught value to report, so it emits `fault` — the carrier's own wording, passed in
1539
1546
  * rather than forked into a second copy of this body.
@@ -1585,15 +1592,15 @@ function decodeEvent(data) {
1585
1592
  }
1586
1593
  /**
1587
1594
  * 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.
1595
+ * carried — the client-side inverse of a server's Streamable-HTTP SSE response.
1589
1596
  *
1590
1597
  * @remarks
1591
1598
  * Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({ stream: true
1592
1599
  * })` (handling a multi-byte character split across reads) and `@orkestrel/sse`'s
1593
1600
  * {@link SSEParserInterface} (handling a partial line or in-progress event split across
1594
1601
  * 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
1602
+ * {@link decodeEvent} (so a non-message or non-JSON `data:` event is dropped, never thrown —
1603
+ * total). It reuses the same `SSEParser` a server's `createStream` seam serializes against, so
1597
1604
  * the wire round-trips. A `null` body (no stream) yields no messages;
1598
1605
  * {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} reads a
1599
1606
  * request/response SSE reply (the server sends one `data:` event then ends), so this drains to
@@ -1660,7 +1667,7 @@ function buildResponseError(response, type) {
1660
1667
  * second spelling of a byte, so it is refused: `=?base64?QR==?=` reaches for the byte
1661
1668
  * `=?base64?QQ==?=` spells canonically, and only the canonical spelling decodes. A malformed
1662
1669
  * 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
1670
+ * requires a server to reject invalid characters, and a fallback would admit the very value
1664
1671
  * the rule exists to refuse. A value missing either marker is a literal and comes back
1665
1672
  * unchanged.
1666
1673
  *
@@ -1706,7 +1713,7 @@ function decodeSentinel(value) {
1706
1713
  *
1707
1714
  * @remarks
1708
1715
  * 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
1716
+ * inverse rather than as a second list that could drift: a value travels literally when it is
1710
1717
  * plain printable ASCII — every code point in `U+0020`–`U+007E`, the RFC 9110 field-value
1711
1718
  * range this package admits — and {@link decodeSentinel} gives it back unchanged. Every other
1712
1719
  * value travels wrapped in {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX}, the
@@ -1746,7 +1753,7 @@ function encodeSentinel(value) {
1746
1753
  *
1747
1754
  * @remarks
1748
1755
  * The companion of {@link extractHeaderAnnotations}, which reads only the annotations a
1749
- * `properties` chain reaches. Comparing the two answers is how
1756
+ * `properties` chain reaches. Comparing the answers is how
1750
1757
  * {@link buildHeaderParameters} decides reachability without a second walk that would have
1751
1758
  * to re-state which JSON Schema keywords are traversable: an annotation the reachable walk
1752
1759
  * did not read is one sitting under `items`, a composition or conditional keyword, a `$ref`
@@ -1790,12 +1797,12 @@ function countHeaderAnnotations(value) {
1790
1797
  * Reachability is the protocol's own rule: an annotation counts only where a chain of
1791
1798
  * `properties` keys leads to it from the `inputSchema` root, so `path` is both the schema
1792
1799
  * 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
1800
+ * `items` is reachable like any other, because the chain is read by key position rather than
1794
1801
  * by key name.
1795
1802
  *
1796
1803
  * `undefined` means the definition is invalid rather than empty: a reachable annotation whose
1797
1804
  * 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
1805
+ * schema root (which is no property), one on a leaf whose declared type is not an
1799
1806
  * {@link import('./validators.js').isMCPHeaderPrimitive} primitive, or a chain deeper than
1800
1807
  * `DEFAULT_MCP_LIMITS.depth` — which is also what makes a self-referential schema terminate.
1801
1808
  * A node that is not a record carries nothing and answers an empty list, because a leaf the
@@ -1839,8 +1846,8 @@ function extractHeaderAnnotations(schema, path) {
1839
1846
  *
1840
1847
  * @remarks
1841
1848
  * 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.
1849
+ * client excludes a definition this refuses from the `tools/list` result it delivers, and a
1850
+ * server recognizes exactly the `Mcp-Param-*` names this returns for its own definitions.
1844
1851
  *
1845
1852
  * `undefined` means the definition is invalid, and every rule the protocol states produces
1846
1853
  * it: a value that is not an RFC 9110 token, a non-primitive or untyped annotated leaf, a
@@ -1877,11 +1884,11 @@ function buildHeaderParameters(schema) {
1877
1884
  * Renders one projected argument as the text its `Mcp-Param-*` header carries.
1878
1885
  *
1879
1886
  * @remarks
1880
- * The protocol's conversion table, and the ONE place it is stated: a string travels as
1887
+ * The protocol's conversion table, and the one place it is stated: a string travels as
1881
1888
  * itself, an integer in decimal, and a boolean as lowercase `true` or `false`. The value's
1882
1889
  * runtime shape must match the leaf's declared type, so a schema that declares `integer` and
1883
1890
  * 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
1891
+ * range carries nothing — a header that cannot round-trip the body value is worse than an
1885
1892
  * absent one, and the tool's own argument validation owns the disagreement.
1886
1893
  *
1887
1894
  * @param value - The argument value read at the parameter's path
@@ -1966,7 +1973,7 @@ function extractToolSchema(response, name) {
1966
1973
  * {@link buildCancelledNotification}.
1967
1974
  *
1968
1975
  * @remarks
1969
- * `requestId` is the WIRE SPELLING carried verbatim from the dated schema, and it must be a
1976
+ * `requestId` is the wire spelling carried verbatim from the dated schema, and it must be a
1970
1977
  * real {@link JSONRPCId}: `null` is not one, and neither is an absent member, so a
1971
1978
  * malformed frame reads as "cancels nothing" rather than as an error. Anything that is not a
1972
1979
  * `notifications/cancelled` notification — a response, a request that happens to use the
@@ -1987,14 +1994,14 @@ function readCancelledId(message) {
1987
1994
  }
1988
1995
  /**
1989
1996
  * Pumps a controlled serialized exchange onto a transport — every notification in order, then
1990
- * the terminating response — and END the exchange however the pump leaves.
1997
+ * the terminating response — and end the exchange however the pump leaves.
1991
1998
  *
1992
1999
  * @remarks
1993
2000
  * The generator's `return` value is a message like any other on the wire: it is sent
1994
- * LAST and closes the exchange. Sends are awaited one at a time so the transport
2001
+ * last and closes the exchange. Sends are awaited one at a time so the transport
1995
2002
  * receives the sequence in the order the method produced it.
1996
2003
  *
1997
- * The first parameter is the CONTROLLED arm rather than a bare
2004
+ * The first parameter is the controlled arm rather than a bare
1998
2005
  * {@link import('./types.js').MCPTextStream}, and that is the whole point of it: this pump is
1999
2006
  * an owner, and an owner needs a lifecycle member to discharge its obligation with. A bare
2000
2007
  * generator has none, so an exit where nothing was cancelled — a `send` that threw two
@@ -2004,7 +2011,7 @@ function readCancelledId(message) {
2004
2011
  * is a no-op for an exchange that already ended on its terminal.
2005
2012
  *
2006
2013
  * The `finally` is spelled explicitly rather than with `await using` because this package's
2007
- * declared Node floor cannot PARSE `await using` — `target: ESNext` emits the declaration
2014
+ * declared Node floor cannot parse `await using` — `target: ESNext` emits the declaration
2008
2015
  * verbatim, and a floor engine rejects the whole module at load. The obligation discharged is
2009
2016
  * identical either way.
2010
2017
  *
@@ -2039,36 +2046,36 @@ async function sendStream(stream, transport) {
2039
2046
  * `server.handle` already turns a malformed message into a serialized `-32700` /
2040
2047
  * `-32600` reply and a notification into `undefined` (no reply), so this binder parses
2041
2048
  * nothing the server would parse differently: it decodes each inbound message through
2042
- * {@link decodeBoundedMessage} under `server.limit`, the SERVER'S OWN bound, so a message
2049
+ * {@link decodeBoundedMessage} under `server.limit`, the server's own bound, so a message
2043
2050
  * the server would refuse is never parsed here either and still receives its `-32700` from
2044
- * the one place that words it. A HELD-OPEN reply arrives as an
2051
+ * the one place that words it. A held-open reply arrives as an
2045
2052
  * {@link import('./types.js').MCPTextStreamControllerInterface} instead of a string: this is
2046
2053
  * the one place that pumps it, writing each notification in order and then the generator's
2047
2054
  * returned terminating response ({@link sendStream}). A `transport.send` throw or rejection —
2048
2055
  * mid-stream included — is caught and routed
2049
2056
  * to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
2050
2057
  * a listener on that event that itself throws is swallowed (the end of the line —
2051
- * the caller's own bug, never this binder's). A fault raised AFTER its own request was
2058
+ * the caller's own bug, never this binder's). A fault raised after its own request was
2052
2059
  * cancelled reports nothing, because a cancellation is not a fault.
2053
2060
  *
2054
- * **This binder OWNS every exchange it starts, and ends each one on every exit.** It holds one
2061
+ * **This binder owns every exchange it starts, and ends each one on every exit.** It holds one
2055
2062
  * `AbortController` per live request, keyed by the request's id and deleted whenever that
2056
2063
  * request leaves — normally, by a throw, or by cancellation — and it supplies that signal to
2057
2064
  * `handle` as {@link import('./types.js').MCPDispatchOptions}. These consequences follow.
2058
- * An inbound `notifications/cancelled` ABORTS the request it names, which is how the message-
2065
+ * An inbound `notifications/cancelled` aborts the request it names, which is how the message-
2059
2066
  * based cancellation path reaches a tool on the carriers that have one (stdio, WebSocket,
2060
- * `MessagePort`); a cancelled request writes NO response, because a peer that asked for a call
2067
+ * `MessagePort`); a cancelled request writes no response, because a peer that asked for a call
2061
2068
  * to stop is not answered by it; and the transport's `closed` signal aborts every request
2062
2069
  * still in flight, so an exchange being pumped when the carrier dies ends with it instead of
2063
2070
  * writing into a socket nobody is holding.
2064
2071
  *
2065
- * `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
2066
- * DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
2067
- * `bindServer` call on the SAME transport is never double-dispatched by a stale
2072
+ * `listen`/`closed` are replace semantics (§ port contract): the returned unbind
2073
+ * detaches by replacing this binder's own handlers with no-ops, so a subsequent
2074
+ * `bindServer` call on the same transport is never double-dispatched by a stale
2068
2075
  * subscription left behind — an unbind→rebind cycle yields exactly one reply per
2069
2076
  * request. Unbinding is itself an owner exit: it aborts and retires every request still in
2070
2077
  * flight before detaching, so `unbind()` then `close()` and `close()` then `unbind()` end the
2071
- * same exchanges. It does NOT close the transport; that remains the caller's decision.
2078
+ * same exchanges. It does not close the transport; that remains the caller's decision.
2072
2079
  *
2073
2080
  * @param server - The transport-agnostic server to dispatch inbound messages over
2074
2081
  * @param transport - The duplex channel to pipe the server over
@@ -2128,7 +2135,7 @@ function bindServer(server, transport) {
2128
2135
  }
2129
2136
  /**
2130
2137
  * Pipes an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
2131
- * inbound message is decoded and delivered onto the client's OWN transport
2138
+ * inbound message is decoded and delivered onto the client's own transport
2132
2139
  * (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
2133
2140
  * client's correlated pending requests exactly as a direct reply would.
2134
2141
  *
@@ -2136,30 +2143,30 @@ function bindServer(server, transport) {
2136
2143
  * The client's outbound writes flow through `client.transport.send` — its existing,
2137
2144
  * unmodified request/response correlation — so `client` must have been constructed
2138
2145
  * with a {@link import('./types.js').MCPMessageTransportInterface} that itself carries
2139
- * the SAME `transport` (see {@link import('./factories.js').createDuplexClientTransport},
2146
+ * the same `transport` (see {@link import('./factories.js').createDuplexClientTransport},
2140
2147
  * the additive factory that adapts an {@link MCPTransportInterface} into that shape);
2141
2148
  * this binder then completes the inbound half by decoding each message and pushing it
2142
2149
  * onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
2143
2150
  * exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
2144
- * inbound message is DROPPED (total — never throws); a delivery fault is routed to
2151
+ * inbound message is dropped (total — never throws); a delivery fault is routed to
2145
2152
  * `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
2146
- * DETACHES this binder (further inbound messages and the transport's `closed` signal are
2147
- * ignored) WITHOUT closing the transport.
2153
+ * detaches this binder (further inbound messages and the transport's `closed` signal are
2154
+ * ignored) without closing the transport.
2148
2155
  *
2149
- * `listen`/`closed` are REPLACE semantics (§ port contract): the returned unbind
2150
- * DETACHES by replacing this binder's own handlers with no-ops, so a subsequent
2151
- * `bindClient` call on the SAME transport is never double-dispatched by a stale
2156
+ * `listen`/`closed` are replace semantics (§ port contract): the returned unbind
2157
+ * detaches by replacing this binder's own handlers with no-ops, so a subsequent
2158
+ * `bindClient` call on the same transport is never double-dispatched by a stale
2152
2159
  * subscription left behind — an unbind→rebind cycle delivers exactly one `message`
2153
2160
  * emit per inbound reply.
2154
2161
  *
2155
2162
  * **This binder needs no live-request registry, and the asymmetry with {@link bindServer} is
2156
- * real rather than an omission.** A server binder holds the lifetime of work it STARTED, so an
2163
+ * real rather than an omission.** A server binder holds the lifetime of work it started, so an
2157
2164
  * inbound `notifications/cancelled` has something to reach; a client binder starts no work —
2158
2165
  * `MCPClient` already owns its pending entries and already writes the cancellation frame
2159
2166
  * itself when a caller's `signal` aborts, on a carrier declaring `duplex`. Adding a registry
2160
- * here would be a second correlation table for ids the client is already correlating, and two
2161
- * tables for one fact drift. The one obligation this binder does carry is delivery: a
2162
- * malformed / non-JSON-RPC inbound message is DROPPED (total — never throws).
2167
+ * here would be a second correlation table for ids the client is already correlating, and a
2168
+ * pair of tables for one fact drift. The one obligation this binder does carry is delivery: a
2169
+ * malformed / non-JSON-RPC inbound message is dropped (total — never throws).
2163
2170
  *
2164
2171
  * @param client - The transport-agnostic client whose transport to deliver messages onto
2165
2172
  * @param transport - The duplex channel to pipe the client over
@@ -2243,8 +2250,8 @@ function isMCPResultMetaObject(value) {
2243
2250
  * subscription id.
2244
2251
  *
2245
2252
  * @remarks
2246
- * The reserved key is OPTIONAL, so a frame delivered outside a `subscriptions/listen`
2247
- * stream passes with no stamp at all. When the key IS present its value must be a valid
2253
+ * The reserved key is optional, so a frame delivered outside a `subscriptions/listen`
2254
+ * stream passes with no stamp at all. When the key is present its value must be a valid
2248
2255
  * {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
2249
2256
  *
2250
2257
  * @param value - The unknown value to inspect
@@ -2411,7 +2418,7 @@ function isAbsoluteURI(value) {
2411
2418
  * Determines whether a value is one RFC 3339 `full-date` naming a real calendar day.
2412
2419
  *
2413
2420
  * @remarks
2414
- * RFC 3339 §5.6 defines `date-mday` as `01-28`, `29`, `30`, or `31` BASED ON the month and
2421
+ * RFC 3339 §5.6 defines `date-mday` as `01-28`, `29`, `30`, or `31` based on the month and
2415
2422
  * year, so the grammar is not satisfied by shape alone: `2026-02-30` and `2025-02-29` are
2416
2423
  * well-formed triples that name no day, and a downstream `new Date` rolls each of them
2417
2424
  * silently onto a different date rather than refusing it. February's length follows the
@@ -2419,7 +2426,7 @@ function isAbsoluteURI(value) {
2419
2426
  *
2420
2427
  * The check is pure integer arithmetic on the matched fields and never constructs a `Date`,
2421
2428
  * because `Date` is exactly the component that performs the rollover this guard exists to
2422
- * refuse. It is a SYNTAX guard: no time zone, locale, calendar era, or leap second applies.
2429
+ * refuse. It is a syntax guard: no time zone, locale, calendar era, or leap second applies.
2423
2430
  *
2424
2431
  * @param value - The unknown value to inspect
2425
2432
  * @returns True if the value is an RFC 3339 `full-date` for a day that exists; false otherwise
@@ -2893,7 +2900,7 @@ function isMCPContent(value) {
2893
2900
  *
2894
2901
  * @remarks
2895
2902
  * The open contract's guard: a record carrying a string `resultType` and, when
2896
- * present, exact result metadata. It deliberately does NOT narrow `resultType` to a
2903
+ * present, exact result metadata. It deliberately does not narrow `resultType` to a
2897
2904
  * known value, because the dated schema keeps adding them — a caller that needs a
2898
2905
  * specific result uses that result's own guard, which narrows to its literal.
2899
2906
  * Mutually exclusive with {@link isMCPLegacyResult} on every input: this one needs
@@ -2968,7 +2975,7 @@ function isMCPCallResult(value) {
2968
2975
  * proof: this is what stands between a manager that answers a numeric `taskId` and a
2969
2976
  * client that would receive one. `ttlMs` accepts `null` because the schema uses it to
2970
2977
  * mean "no expiry", which is distinct from an absent field, and both durations must be
2971
- * INTEGER milliseconds because the schema formats them `int`.
2978
+ * integer milliseconds because the schema formats them `int`.
2972
2979
  *
2973
2980
  * @param value - The unknown value to inspect
2974
2981
  * @returns True if the value is a well-formed `resultType: 'task'` result; false otherwise
@@ -3019,7 +3026,7 @@ function isMCPTaskStatus(value) {
3019
3026
  * the requests to answer, `completed` owns the deferred call's result, `failed` owns the
3020
3027
  * JSON-RPC error that ended it, and `working` / `cancelled` own nothing further.
3021
3028
  *
3022
- * A `completed` task's `result` is checked as an OBJECT and no further. The schema declares
3029
+ * A `completed` task's `result` is checked as an object and no further. The schema declares
3023
3030
  * it an open record, so its contents belong to whichever method was deferred; a guard that
3024
3031
  * demanded a protocol result here would refuse payloads the extension permits.
3025
3032
  * `ttlMs` and `pollIntervalMs` are integer milliseconds, per the schema's `int` formats.
@@ -3061,11 +3068,11 @@ function isMCPTaskDetail(value) {
3061
3068
  * Determines whether a value is the wire answer to `tasks/get`.
3062
3069
  *
3063
3070
  * @remarks
3064
- * {@link isMCPTaskDetail} plus the stamp the METHOD owes. The schema types a `tasks/get`
3071
+ * {@link isMCPTaskDetail} plus the stamp the method owes. The schema types a `tasks/get`
3065
3072
  * reply as the detail intersected with the standard result, so `resultType: 'complete'` is
3066
3073
  * part of the answer rather than decoration on it — and an unstamped payload, or one
3067
3074
  * carrying the creation answer's `resultType: 'task'`, is a peer answering some other
3068
- * shape. Use this guard wherever a `tasks/get` REPLY is read; use
3075
+ * shape. Use this guard wherever a `tasks/get` reply is read; use
3069
3076
  * {@link isMCPTaskDetail} wherever a consumer's manager answers directly.
3070
3077
  *
3071
3078
  * `_meta` is checked only when present, and only as result metadata: the server identity a
@@ -3095,14 +3102,14 @@ function isMCPTaskDetailResult(value) {
3095
3102
  * Determines whether a value is a `notifications/tasks` frame carrying a task snapshot.
3096
3103
  *
3097
3104
  * @remarks
3098
- * The ADMISSION guard for a task transition: a subscription producer is consumer-written,
3105
+ * The admission guard for a task transition: a subscription producer is consumer-written,
3099
3106
  * so the frame it hands over is foreign input, and this is what stands between a mutated
3100
3107
  * or half-built snapshot and a subscribed client. Both halves are checked — the method
3101
3108
  * literal the extension fixes, and params that hold together as an
3102
3109
  * {@link MCPTaskDetail} — because either alone admits a frame the other rejects.
3103
3110
  *
3104
- * `_meta` is checked for SHAPE WHEN PRESENT and nothing more. The reserved subscription
3105
- * stamp is the SERVER'S to write, after this guard admits the frame and the matcher agrees
3111
+ * `_meta` is checked for shape when present and nothing more. The reserved subscription
3112
+ * stamp is the server's to write, after this guard admits the frame and the matcher agrees
3106
3113
  * to it, so a guard that demanded the stamp would refuse every frame a producer emits.
3107
3114
  *
3108
3115
  * @param value - The unknown value to inspect
@@ -3513,13 +3520,13 @@ function isMCPElicitResult(value) {
3513
3520
  * Determines whether accepted elicitation content satisfies the exact schema that was issued.
3514
3521
  *
3515
3522
  * @remarks
3516
- * {@link isMCPElicitResult} says a response has the SHAPE of a response; this says the
3517
- * response answers the QUESTION that was asked. A server that protects the schema it issued
3523
+ * {@link isMCPElicitResult} says a response has the shape of a response; this says the
3524
+ * response answers the question that was asked. A server that protects the schema it issued
3518
3525
  * and then never enforces it has bought nothing, so this guard closes that gap: it is what
3519
3526
  * turns a bound schema into a checked one.
3520
3527
  *
3521
3528
  * Every own value must be one {@link MCPElicitValue} — a string, a finite number, a boolean,
3522
- * or an array of strings. A value whose name is DECLARED in `schema.properties` must in
3529
+ * or an array of strings. A value whose name is declared in `schema.properties` must in
3523
3530
  * addition satisfy that field's schema: `integer` rejects a fraction, `minimum` / `maximum`
3524
3531
  * bound a number, `minLength` / `maxLength` bound a string by code points, `enum` and `oneOf`
3525
3532
  * bound it to a declared member, `format` is enforced (`uri` by {@link isAbsoluteURI}, `email`
@@ -3528,9 +3535,9 @@ function isMCPElicitResult(value) {
3528
3535
  * `maxItems` with every entry drawn from its `items.enum` or `items.anyOf`. Every name listed
3529
3536
  * in `schema.required` must be present.
3530
3537
  *
3531
- * An UNDECLARED property remains valid: the restricted schema is open by default, so a client
3538
+ * An undeclared property remains valid: the restricted schema is open by default, so a client
3532
3539
  * that answers more than it was asked is not refused for it. A `schema` that is not itself a
3533
- * valid {@link MCPElicitSchema} admits NOTHING — an unenforceable schema is never a permissive
3540
+ * valid {@link MCPElicitSchema} admits nothing — an unenforceable schema is never a permissive
3534
3541
  * one — which is why `schema` is accepted as `unknown` and checked rather than trusted. Total
3535
3542
  * over hostile content and hostile schemas alike.
3536
3543
  *
@@ -3723,10 +3730,11 @@ function isMCPSampleContent(value) {
3723
3730
  *
3724
3731
  * @remarks
3725
3732
  * 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
3733
+ * {@link isMCPSampleContent} block or an array of them, so both are admitted here: a
3727
3734
  * tool-using model answers with `tool_use` and `tool_result` blocks, and a model answering in
3728
3735
  * 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.
3736
+ * names `endTurn`, `stopSequence`, `maxTokens`, and `toolUse` and permits any other a provider
3737
+ * reports. Total over hostile input.
3730
3738
  *
3731
3739
  * @param value - The unknown value to inspect
3732
3740
  * @returns True if `value` has the sampling-completion shape; false otherwise
@@ -3767,12 +3775,12 @@ function isMCPSampleResult(value) {
3767
3775
  * Determines whether a response answers the exact embedded request that was issued.
3768
3776
  *
3769
3777
  * @remarks
3770
- * A response carries no `method` of its own, so the ISSUED request selects which arm applies
3778
+ * A response carries no `method` of its own, so the issued request selects which arm applies
3771
3779
  * — the same way {@link isElicitContent} takes the issued schema rather than trusting the
3772
3780
  * content to describe itself. A form elicitation is checked twice: once for the response
3773
3781
  * shape and once, on `accept`, for the content against the schema that round issued. A
3774
3782
  * 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
3783
+ * package cannot recognize admits nothing, because an unrecognized question has no correct
3776
3784
  * answer. Total over hostile responses and hostile requests alike.
3777
3785
  *
3778
3786
  * @param value - The client's answer to check
@@ -3835,7 +3843,7 @@ function isMCPInputResult(value) {
3835
3843
  *
3836
3844
  * @remarks
3837
3845
  * A request is a record with `jsonrpc === '2.0'`, a string `method`, and an `id`
3838
- * that {@link isJSONRPCId} accepts. An id-less call is NOT a request — it is a
3846
+ * that {@link isJSONRPCId} accepts. An id-less call is not a request — it is a
3839
3847
  * {@link JSONRPCNotification}, which {@link isJSONRPCNotification} answers for. The
3840
3848
  * guards are mutually exclusive on every input: this one requires a valid `id`
3841
3849
  * value, that one requires no own `id` member at all. `params`, when present, must
@@ -3864,7 +3872,7 @@ function isJSONRPCRequest(value) {
3864
3872
  * Determines whether a parsed value is a {@link JSONRPCNotification}.
3865
3873
  *
3866
3874
  * @remarks
3867
- * A notification is a request-shaped call carrying NO `id` member — the protocol
3875
+ * A notification is a request-shaped call carrying no `id` member — the protocol
3868
3876
  * forbids one, because nothing answers a notification. `params`, when present, must
3869
3877
  * be a record. Total: any other input returns `false`.
3870
3878
  *
@@ -3905,8 +3913,8 @@ function isJSONRPCInvocation(value) {
3905
3913
  * arm of a response.
3906
3914
  *
3907
3915
  * @remarks
3908
- * A result answers a request, so `id` is REQUIRED and must be a valid
3909
- * {@link isJSONRPCId}. The envelope must own a `result` and must NOT own an `error`,
3916
+ * A result answers a request, so `id` is required and must be a valid
3917
+ * {@link isJSONRPCId}. The envelope must own a `result` and must not own an `error`,
3910
3918
  * which is what makes this guard and {@link isJSONRPCErrorResponse} mutually
3911
3919
  * exclusive on every input. `result` itself must be an object: either a modern
3912
3920
  * {@link isMCPResult} or a legacy {@link isMCPLegacyResult}, never a bare primitive.
@@ -3934,17 +3942,17 @@ function isJSONRPCResultResponse(value) {
3934
3942
  * Determines whether a value is one JSON-RPC `error` member.
3935
3943
  *
3936
3944
  * @remarks
3937
- * The failure OBJECT, not the envelope carrying it — the shape a failed response owns
3945
+ * The failure object, not the envelope carrying it — the shape a failed response owns
3938
3946
  * under `error`, and the shape a `failed` {@link MCPTaskDetail} owns under the same name,
3939
3947
  * which is why it is one guard rather than the same checks written twice.
3940
3948
  *
3941
- * It is deliberately STRUCTURAL rather than exact-JSON: `data` is declared `unknown`, so
3949
+ * It is deliberately structural rather than exact-JSON: `data` is declared `unknown`, so
3942
3950
  * requiring the whole object to survive a JSON clone would refuse a legal error that
3943
3951
  * carried a non-JSON payload. Both callers here hand it an already-owned value.
3944
3952
  *
3945
3953
  * That choice is why the key reads are guarded. Every sibling guard clones first, and a
3946
3954
  * clone reads each key once behind a boundary that already owns totality; this one is the
3947
- * family's only DIRECT reader, so it meets `code` and `message` exactly as the value defines
3955
+ * family's only direct reader, so it meets `code` and `message` exactly as the value defines
3948
3956
  * them — including as accessors that throw. Reading a named key off an unowned value is
3949
3957
  * itself the hostile step, and it is bounded here rather than allowed to escape. Total.
3950
3958
  *
@@ -3967,9 +3975,9 @@ function isJSONRPCError(value) {
3967
3975
  * arm of a response.
3968
3976
  *
3969
3977
  * @remarks
3970
- * `id` is OPTIONAL here and only here: a peer that could not read the failed
3971
- * request's id OMITS the member rather than sending `null`, so an absent `id` is
3972
- * valid and a `null` one is not. The envelope must own an `error` and must NOT own a
3978
+ * `id` is optional here and only here: a peer that could not read the failed
3979
+ * request's id omits the member rather than sending `null`, so an absent `id` is
3980
+ * valid and a `null` one is not. The envelope must own an `error` and must not own a
3973
3981
  * `result`. `error` carries an integer `code` and a string `message`. Total.
3974
3982
  *
3975
3983
  * @param value - The already-parsed value to test
@@ -4059,9 +4067,9 @@ function isModernRequest(value) {
4059
4067
  * Infers the wire era for an MCP protocol revision.
4060
4068
  *
4061
4069
  * @remarks
4062
- * The era is READ from the two era guards rather than restated here, so a revision added
4070
+ * The era is read from the era guards rather than restated here, so a revision added
4063
4071
  * to {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS} or {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
4064
- * carries its era with it and no third list can disagree with those two.
4072
+ * carries its era with it and no further list can disagree with them.
4065
4073
  *
4066
4074
  * @param version - The protocol revision to classify
4067
4075
  * @returns `'modern'` for a revision a bare server accepts, `'legacy'` for a revision the
@@ -4075,10 +4083,10 @@ function inferEra(version) {
4075
4083
  * Infers the wire era one invocation's own structure selects.
4076
4084
  *
4077
4085
  * @remarks
4078
- * The STRUCTURAL read, distinct from {@link inferEra}'s read of a revision string: era is fixed
4086
+ * The structural read, distinct from {@link inferEra}'s read of a revision string: era is fixed
4079
4087
  * by the reserved modern metadata a request carries, so this answers for a message whose
4080
4088
  * 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
4089
+ * published wire shapes. It is what an observation surface reports and what an ingress
4082
4090
  * routes on, so both derive it here rather than each spelling the ternary out.
4083
4091
  *
4084
4092
  * @param invocation - The invocation whose structure selects the era
@@ -4102,15 +4110,15 @@ function inferVersion(offered) {
4102
4110
  for (const version of SUPPORTED_MODERN_PROTOCOL_VERSIONS) if (offered.includes(version)) return version;
4103
4111
  }
4104
4112
  /**
4105
- * Infers the protocol version an outbound message announces itself with — the ONE
4113
+ * Infers the protocol version an outbound message announces itself with — the one
4106
4114
  * projection every HTTP client transport stamps `mcp-protocol-version` from.
4107
4115
  *
4108
4116
  * @remarks
4109
- * This is deliberately the SAME read the server's own expectation performs
4117
+ * This is deliberately the same read the server's own expectation performs
4110
4118
  * ({@link import('@orkestrel/mcp/server').inferHeaderIssue}): a modern request's reserved
4111
- * `_meta` version, accepted whenever it is a string. It is NOT
4119
+ * `_meta` version, accepted whenever it is a string. It is not
4112
4120
  * {@link import('./parsers.js').parseRequestContext}, and the difference is the whole
4113
- * point. That parser answers a different question — is the modern metadata WELL FORMED
4121
+ * point. That parser answers a different question — is the modern metadata well formed
4114
4122
  * and refuses a request whose capability declaration or logging level is malformed. Such a
4115
4123
  * request is still modern (era is fixed by key presence) and the server still demands the
4116
4124
  * header for it, so projecting through the parser withholds a header the peer requires and
@@ -4119,7 +4127,7 @@ function inferVersion(offered) {
4119
4127
  * A non-modern message projects nothing: a legacy request's version comes from the
4120
4128
  * `initialize` handshake the transport captured, not from the message.
4121
4129
  *
4122
- * Header NAMES stay with the transports that own the wire (see `constants.ts`); core owns
4130
+ * Header names stay with the transports that own the wire (see `constants.ts`); core owns
4123
4131
  * the value this projection derives, which is the part the browser and Node faces disagreed about.
4124
4132
  *
4125
4133
  * @param message - The outbound message about to be written
@@ -4144,10 +4152,10 @@ function inferRequestVersion(message) {
4144
4152
  *
4145
4153
  * @remarks
4146
4154
  * - **One seam.** The server registers its built-in modern methods here at construction
4147
- * and resolves EVERY modern method from here, so a consumer's method and a built-in
4155
+ * and resolves every modern method from here, so a consumer's method and a built-in
4148
4156
  * are the same kind of thing on the same path.
4149
4157
  * - **Registration is a write, not a merge.** `add` under a name already present
4150
- * REPLACES it, which is how a consumer overrides a built-in; there is no precedence
4158
+ * replaces it, which is how a consumer overrides a built-in; there is no precedence
4151
4159
  * rule to remember.
4152
4160
  * - **A narrower contract than a `Map`.** Callers register and resolve; they cannot
4153
4161
  * iterate, clear, or otherwise reach the server's internal state through it.
@@ -4300,16 +4308,16 @@ var MCPProgressReporter = class {
4300
4308
  * through.
4301
4309
  *
4302
4310
  * @remarks
4303
- * A native async generator decides cancellation with a QUEUE: `return()` and `throw()` wait
4311
+ * A native async generator decides cancellation with a queue: `return()` and `throw()` wait
4304
4312
  * behind a `next()` the producer has not answered, so a consumer abandoning a source parked
4305
4313
  * on an event that will never arrive waits forever for its own cancellation. This class
4306
- * arbitrates instead of queueing. It keeps at most ONE read outstanding against the source,
4307
- * settles the consumer's read itself, aborts the request's lifetime BEFORE it delegates
4314
+ * arbitrates instead of queueing. It keeps at most one read outstanding against the source,
4315
+ * settles the consumer's read itself, aborts the request's lifetime before it delegates
4308
4316
  * cleanup to the producer — so a cooperating producer is woken rather than waited on —
4309
4317
  * contains every promise the producer settles late, and makes every closure path idempotent.
4310
4318
  *
4311
4319
  * The closures are deliberately different answers: the source's own return is the
4312
- * terminal RESPONSE, `return(value)` is the consumer saying it has the answer already, and
4320
+ * terminal response, `return(value)` is the consumer saying it has the answer already, and
4313
4321
  * {@link stop} is an owner saying there will be no answer at all. Only the source's own
4314
4322
  * return is a message a peer ever sees.
4315
4323
  *
@@ -4317,7 +4325,7 @@ var MCPProgressReporter = class {
4317
4325
  * generator is suspended inside, so the signal is how an uncooperative producer is asked to
4318
4326
  * finish, and this controller never blocks its consumer on the answer.
4319
4327
  *
4320
- * **What this class does NOT have is an owner of last resort.** No finalizer, no timer, no
4328
+ * **What this class does not have is an owner of last resort.** No finalizer, no timer, no
4321
4329
  * timeout ends an exchange nobody released. That absence is the design: an exchange holds a
4322
4330
  * producer, a request lifetime and a live server slot, so a silent background release would
4323
4331
  * turn "a pump forgot its obligation" from a reproducible defect into a nondeterministic one,
@@ -4498,15 +4506,15 @@ var MCPStreamController = class {
4498
4506
  * serialized.
4499
4507
  *
4500
4508
  * @remarks
4501
- * A TRANSLATION boundary and deliberately nothing else. It serializes each message and the
4509
+ * A translation boundary and deliberately nothing else. It serializes each message and the
4502
4510
  * terminating response, and every lifecycle decision — return, throw, dispose, stop — ends
4503
4511
  * the typed exchange beneath it rather than this face. That is the whole design constraint: a
4504
- * serialized face implemented as its own async generator would add a SECOND operation queue,
4512
+ * serialized face implemented as its own async generator would add a second operation queue,
4505
4513
  * and the queue is exactly the defect the typed controller exists to remove — a `return()`
4506
4514
  * promptly settled at the text face and left queued at the typed one cancels nothing.
4507
4515
  *
4508
4516
  * One member is a narrowing rather than a pass-through, and it is worth knowing before it
4509
- * surprises a producer. `return` receives a STRING; it cannot rebuild the typed
4517
+ * surprises a producer. `return` receives a string; it cannot rebuild the typed
4510
4518
  * `JSONRPCResponse` the typed face would close on, and inventing one by parsing the
4511
4519
  * argument back would make this face decide what the exchange ended with. So it ends the
4512
4520
  * typed exchange with {@link MCPStreamControllerInterface.stop} — no terminal — and answers
@@ -4515,11 +4523,11 @@ var MCPStreamController = class {
4515
4523
  * the honest translation of "the consumer already has its answer" when the answer is opaque
4516
4524
  * text, not a downgrade to work around.
4517
4525
  *
4518
- * It accepts only a CONTROLLED typed stream. A raw generator would have no lifecycle to
4526
+ * It accepts only a controlled typed stream. A raw generator would have no lifecycle to
4519
4527
  * delegate to, and this class refuses to grow one of its own.
4520
4528
  *
4521
4529
  * Delegation is total and it is what makes the ownership obligation transitive: `return`,
4522
- * `throw`, `stop`, and dispose each end the TYPED exchange, so a pump holding only this
4530
+ * `throw`, `stop`, and dispose each end the typed exchange, so a pump holding only this
4523
4531
  * serialized face still releases the producer, the request lifetime, and the live server slot
4524
4532
  * behind it. There is no owner of last resort here either, for the same reason there is none
4525
4533
  * on the typed face.
@@ -4566,7 +4574,7 @@ var MCPTextStreamController = class {
4566
4574
  * @remarks
4567
4575
  * The typed exchange ends with no terminal, because a string is not a
4568
4576
  * `JSONRPCResponse` and this face never parses one back out of its argument. The
4569
- * supplied text is the answer to THIS consumer, and a cooperating producer sees its
4577
+ * supplied text is the answer to this consumer, and a cooperating producer sees its
4570
4578
  * cancellation path rather than its normal return.
4571
4579
  *
4572
4580
  * @param value - The serialized terminal the consumer is ending on
@@ -4603,7 +4611,7 @@ var MCPTextStreamController = class {
4603
4611
  *
4604
4612
  * @remarks
4605
4613
  * Delegates downward exactly as {@link stop} does — disposing the serialized arm is
4606
- * disposing the exchange, never just this adapter.
4614
+ * disposing the exchange, never this adapter alone.
4607
4615
  *
4608
4616
  * @returns Resolves once the typed exchange has ended
4609
4617
  */
@@ -4659,7 +4667,8 @@ var MCPLegacy = class {
4659
4667
  const parsed = parseJSON(message);
4660
4668
  if (isModernRequest(parsed) || !isJSONRPCInvocation(parsed)) return this.#options.dispatcher.handle(message, options);
4661
4669
  const answer = await this.#legacy(parsed, options);
4662
- return answer === void 0 ? void 0 : JSON.stringify(answer);
4670
+ if (answer === void 0) return void 0;
4671
+ return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
4663
4672
  }
4664
4673
  async #legacy(invocation, options) {
4665
4674
  if (invocation.id === void 0) return void 0;
@@ -4683,13 +4692,42 @@ var MCPLegacy = class {
4683
4692
  }
4684
4693
  async #forward(request, options) {
4685
4694
  const translated = legacyInvocationToModern(request);
4686
- const answer = await this.#options.dispatcher.dispatch(translated, options);
4687
- if (Symbol.asyncIterator in answer) {
4688
- answer.stop();
4689
- await answer[Symbol.asyncDispose]();
4690
- return this.#unsupported(request.id, "stream");
4695
+ const metadata = request.method === "tools/call" ? request.params?.["_meta"] : void 0;
4696
+ const candidate = isRecord(metadata) ? metadata["progressToken"] : void 0;
4697
+ const token = isString(candidate) || isInteger(candidate) ? candidate : void 0;
4698
+ if (token === void 0) {
4699
+ const answer = await this.#options.dispatcher.dispatch(translated, options);
4700
+ if (Symbol.asyncIterator in answer) {
4701
+ answer.stop();
4702
+ await answer[Symbol.asyncDispose]();
4703
+ return this.#unsupported(request.id, "stream");
4704
+ }
4705
+ return this.#project(answer, request.id);
4706
+ }
4707
+ const closure = new AbortController();
4708
+ const resolved = buildMethodOptions(options ?? {}, closure.signal);
4709
+ try {
4710
+ const answer = await this.#options.dispatcher.dispatch(translated, resolved);
4711
+ if (Symbol.asyncIterator in answer) return new MCPStreamController(this.#progress(answer, request.id, token), resolved.signal, closure);
4712
+ closure.abort();
4713
+ return this.#project(answer, request.id);
4714
+ } catch (error) {
4715
+ closure.abort(error);
4716
+ throw error;
4717
+ }
4718
+ }
4719
+ async *#progress(stream, id, token) {
4720
+ try {
4721
+ while (true) {
4722
+ const frame = await stream.next();
4723
+ if (frame.done === true) return this.#project(frame.value, id);
4724
+ const params = frame.value.params;
4725
+ if (frame.value.method !== "notifications/progress" || !isRecord(params) || params["progressToken"] !== token || !isMCPProgress(params)) return this.#unsupported(id, "stream");
4726
+ yield frame.value;
4727
+ }
4728
+ } finally {
4729
+ await stream[Symbol.asyncDispose]();
4691
4730
  }
4692
- return this.#project(answer, request.id);
4693
4731
  }
4694
4732
  #project(answer, id) {
4695
4733
  if (answer.error !== void 0) return answer.error.code === -32021 ? this.#unsupported(id, this.#capability(answer)) : answer;
@@ -4935,7 +4973,7 @@ var MCPLegacyClientTransport = class {
4935
4973
  //#endregion
4936
4974
  //#region src/core/MCPServer.ts
4937
4975
  /**
4938
- * Dispatches JSON-RPC 2.0 requests over a live {@link ToolManagerInterface}, with NO
4976
+ * Dispatches JSON-RPC 2.0 requests over a live {@link ToolManagerInterface}, with no
4939
4977
  * transport coupling.
4940
4978
  *
4941
4979
  * @remarks
@@ -4945,7 +4983,7 @@ var MCPLegacyClientTransport = class {
4945
4983
  * `handle(message)` is the string boundary: it
4946
4984
  * `JSON.parse`s the raw message (a failure → a `-32700` response), narrows it to
4947
4985
  * an invocation (a non-invocation → a `-32600` response, with the unreadable `id`
4948
- * OMITTED rather than nulled), dispatches, and serializes the
4986
+ * omitted rather than nulled), dispatches, and serializes the
4949
4987
  * response back to a string (`undefined` for a notification).
4950
4988
  * - **One modern seam.** `server/discover`, `tools/list`, `tools/call`, and
4951
4989
  * `subscriptions/listen` are always registered; `resources/*`, `prompts/*`, and
@@ -5636,19 +5674,19 @@ var MCPServer = class {
5636
5674
  //#endregion
5637
5675
  //#region src/core/MCPTaskClient.ts
5638
5676
  /**
5639
- * Issues the `tasks/*` methods over one correlated-request door — the CLIENT half of the
5677
+ * Issues the `tasks/*` methods over one correlated-request door — the client half of the
5640
5678
  * stable Tasks extension, exposed as an {@link import('./types.js').MCPClientInterface}'s
5641
5679
  * `tasks`.
5642
5680
  *
5643
5681
  * @remarks
5644
5682
  * - **The mirror of the server-side port, minus `start`.** An
5645
5683
  * {@link import('./types.js').MCPTaskManagerInterface} is the consumer's durable store the
5646
- * SERVER creates tasks in; this is the client's read/answer/stop access to the tasks a peer
5684
+ * server creates tasks in; this is the client's read/answer/stop access to the tasks a peer
5647
5685
  * already created. Creation is missing on purpose: the extension gives a client no flag and
5648
5686
  * no parameter to ask for a task, so `start` has no wire method to be.
5649
5687
  * - **No plural accessor, no loop, no cache.** MCP defines no `tasks/list`, so nothing here
5650
5688
  * enumerates. A task snapshot's `pollIntervalMs` is carried untouched and a one-shot read
5651
- * sits beside it; the SCHEDULE is the consumer's, because this package has no durable place
5689
+ * sits beside it; the schedule is the consumer's, because this package has no durable place
5652
5690
  * to keep a task, no idea when the application still cares, and no lifetime to hang a timer
5653
5691
  * on that outlives the request it was born from. An instance left alone writes nothing.
5654
5692
  * - **One channel.** Every request goes through the injected
@@ -5696,20 +5734,20 @@ var MCPTaskClient = class {
5696
5734
  //#endregion
5697
5735
  //#region src/core/MCPClient.ts
5698
5736
  /**
5699
- * Connects to a REMOTE MCP server over any injected {@link MCPMessageTransportInterface},
5737
+ * Connects to a remote MCP server over any injected {@link MCPMessageTransportInterface},
5700
5738
  * negotiates the modern revision, and exposes the server's tools as local
5701
5739
  * {@link ToolInterface}s an agent can run.
5702
5740
  *
5703
5741
  * @remarks
5704
- * - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
5705
- * this client ISSUES them over a transport. `connect` probes `server/discover` and exposes
5742
+ * - **The mirror of `MCPServer`.** The server dispatches requests over a tool registry;
5743
+ * this client issues them over a transport. `connect` probes `server/discover` and exposes
5706
5744
  * the negotiated `version`; a legacy peer requires an explicit transport adapter.
5707
5745
  * `tools()` lists the remote tools and wraps each as a
5708
5746
  * local {@link ToolInterface} whose `execute` calls back through `call`; `call` runs a
5709
5747
  * remote `tools/call` and reports the arm the peer answered with — a value, a durable
5710
5748
  * task, or a request for more input (a remote `isError: true` throws locally, so an
5711
5749
  * agent's {@link import('@orkestrel/tool').ToolManagerInterface} isolates it into a
5712
- * `success: false` result just like a local throw). A wrapped tool cannot hand an agent
5750
+ * `success: false` result exactly like a local throw). A wrapped tool cannot hand an agent
5713
5751
  * a deferred answer, so a non-`'complete'` arm throws there.
5714
5752
  * - **Request↔response correlation.** Each request is tagged with a monotonic numeric
5715
5753
  * `id` ({@link #nextId}); a single transport `message` subscription resolves / rejects
@@ -5717,9 +5755,9 @@ var MCPTaskClient = class {
5717
5755
  * every pending request because the peer could not identify which request failed. A
5718
5756
  * server-initiated message is re-surfaced on the `notification` event, except a progress
5719
5757
  * frame claimed by the request that asked
5720
- * for it; a RESPONSE correlating to nothing pending is discarded, because the request it
5758
+ * for it; a response correlating to nothing pending is discarded, because the request it
5721
5759
  * answers has already settled.
5722
- * - **Per-request cancellation.** `call`'s `options.signal` withdraws ONE caller from ONE
5760
+ * - **Per-request cancellation.** `call`'s `options.signal` withdraws one caller from one
5723
5761
  * request: the pending entry rejects on every carrier, and `notifications/cancelled` goes
5724
5762
  * out only where the transport declares itself duplex — the dated revision defines no
5725
5763
  * client-to-server notification over Streamable HTTP, where closing the response stream
@@ -5736,7 +5774,7 @@ var MCPTaskClient = class {
5736
5774
  * discovery probe uses that same configured deadline, so a silent peer cannot hold
5737
5775
  * negotiation indefinitely.
5738
5776
  * `AbortSignal.timeout` (never a raw `setTimeout`) rejects only that pending request, and the
5739
- * same deadline bounds the WAIT on the transport's `close`, the one wait no drain and no signal
5777
+ * same deadline bounds the wait on the transport's `close`, the one wait no drain and no signal
5740
5778
  * can reach. It bounds the wait rather than the close, which keeps running, so a retry joins it
5741
5779
  * instead of shutting one connection down twice.
5742
5780
  * - **Transport-agnostic.** Imports only core siblings (JSON-RPC + the tool vocabulary);
@@ -6270,7 +6308,7 @@ var MCPClient = class {
6270
6308
  //#endregion
6271
6309
  //#region src/core/transports/HTTPClientTransport.ts
6272
6310
  /**
6273
- * Drives a REMOTE Streamable-HTTP MCP server over `fetch` — a CLIENT
6311
+ * Drives a remote Streamable-HTTP MCP server over `fetch` — a client
6274
6312
  * {@link MCPMessageTransportInterface} for the Model Context Protocol, the egress mirror of
6275
6313
  * the server's `createMCPRoutes`.
6276
6314
  *
@@ -6282,7 +6320,7 @@ var MCPClient = class {
6282
6320
  * class, so a reply reaches a page and a Node process through the same decode.
6283
6321
  * - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
6284
6322
  * 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
6323
+ * `Accept` of both `application/json` and `text/event-stream` (so the server may
6286
6324
  * answer with either framing) — plus any `options.headers` (for example, an `Authorization`
6287
6325
  * bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
6288
6326
  * the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
@@ -6295,12 +6333,12 @@ var MCPClient = class {
6295
6333
  * Accepted (a notification) carries no body and emits nothing.
6296
6334
  * - **Session and protocol headers.** `start()` is a no-op (a
6297
6335
  * 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
6336
+ * `mcp-session-id` response header, when a stateful server sends one (on
6337
+ * `initialize`), is captured into `session` and then echoed as the
6338
+ * `mcp-session-id` request header on every subsequent request — so an
6301
6339
  * `MCPClient` passes a stateful server's session validation. The
6302
6340
  * initialize result's `protocolVersion` is likewise captured, but only
6303
- * when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
6341
+ * when it is a supported value, and echoed as `mcp-protocol-version` alone on
6304
6342
  * subsequent legacy requests. Modern requests instead derive protocol and method
6305
6343
  * headers from the message, plus the name header only for `tools/call` — carried in the
6306
6344
  * protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
@@ -6308,11 +6346,11 @@ var MCPClient = class {
6308
6346
  * `close()` clears the captured protocol so a reconnect's `initialize`
6309
6347
  * POST is headerless; the captured `session` persists across `close()`.
6310
6348
  * - **`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
6349
+ * aborted, which cancels the response body a `send` is reading — an SSE reply the server
6312
6350
  * never ends would otherwise outlive the transport, with nothing left able to reach it. The
6313
6351
  * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
6314
6352
  * 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
6353
+ * - **Total at the boundary, and a non-success reply rejects.** Every reply is narrowed
6316
6354
  * (`parseJSONRPCMessage`, the SSE decoder). A non-message success reply is dropped, never
6317
6355
  * asserted. A non-success reply that carries no valid JSON-RPC message rejects `send` with
6318
6356
  * an error naming its HTTP status and body shape — the peer answered, and answering the
@@ -6502,20 +6540,35 @@ var HTTPClientTransport = class {
6502
6540
  * {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPServerOptions})
6503
6541
  * @returns A working {@link MCPServerInterface}
6504
6542
  *
6505
- * @example
6543
+ * @example Expose a tool registry over MCP
6506
6544
  * ```ts
6507
6545
  * import { createMCPServer } from '@orkestrel/mcp'
6508
6546
  * import { createTool, createToolManager } from '@orkestrel/tool'
6509
6547
  *
6510
6548
  * const tools = createToolManager()
6549
+ * tools.add(
6550
+ * createTool({
6551
+ * name: 'search',
6552
+ * description: 'Search the docs',
6553
+ * execute: (a) => find(String(a.query)),
6554
+ * }),
6555
+ * )
6511
6556
  * tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
6512
6557
  *
6513
- * const server = createMCPServer({ identity: { name: 'calculator', version: '1.0.0' }, tools })
6558
+ * const server = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools })
6514
6559
  * server.emitter.on('request', (method, id) => log(method, id))
6515
6560
  *
6516
- * // A transport pumps message strings through `handle`:
6517
- * const reply = await server.handle('{"jsonrpc":"2.0","method":"tools/list","id":1,"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}')
6518
- * // reply '{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"add","inputSchema":{"type":"object"}}],"resultType":"complete","ttlMs":60000,"cacheScope":"private","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"calculator","version":"1.0.0"}}}}'
6561
+ * // A transport reads a framed message string and writes the reply:
6562
+ * for await (const message of transport) {
6563
+ * const reply = await server.handle(message)
6564
+ * if (reply !== undefined) await transport.send(reply) // a notification has no reply
6565
+ * }
6566
+ *
6567
+ * // `handle` also answers one message string on its own:
6568
+ * const listed = await server.handle(
6569
+ * '{"jsonrpc":"2.0","method":"tools/list","id":1,"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}',
6570
+ * )
6571
+ * // listed → '{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"search","inputSchema":{"type":"object"},"description":"Search the docs"},{"name":"add","inputSchema":{"type":"object"}}],"resultType":"complete","ttlMs":60000,"cacheScope":"private","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"docs","version":"1.0.0"}}}}'
6519
6572
  * ```
6520
6573
  */
6521
6574
  function createMCPServer(options) {
@@ -6524,6 +6577,10 @@ function createMCPServer(options) {
6524
6577
  /**
6525
6578
  * Decorates one MCP server with the fixed legacy method translation.
6526
6579
  *
6580
+ * @remarks
6581
+ * Adds support for the `2025-11-25` and `2025-06-18` legacy revisions. Removing this one
6582
+ * decorator removes that legacy surface while leaving the modern dispatcher unchanged.
6583
+ *
6527
6584
  * @param server - The sole modern dispatcher and handshake identity source
6528
6585
  * @returns A dispatcher accepting both modern and legacy invocations
6529
6586
  */
@@ -6534,14 +6591,14 @@ function createMCPLegacy(server) {
6534
6591
  });
6535
6592
  }
6536
6593
  /**
6537
- * Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
6594
+ * Creates a transport-agnostic Model Context Protocol client — connects to a remote
6538
6595
  * MCP server over an injected {@link import('./types.js').MCPMessageTransportInterface},
6539
6596
  * negotiates the modern revision through `server/discover`, and exposes the server's tools as local
6540
6597
  * {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
6541
6598
  *
6542
6599
  * @remarks
6543
6600
  * The egress mirror of {@link createMCPServer}: where the server exposes a local tool
6544
- * registry over MCP, the client USES a remote server's tools. `connect()` discovers,
6601
+ * registry over MCP, the client uses a remote server's tools. `connect()` discovers,
6545
6602
  * validates, and exposes the negotiated modern protocol; a legacy peer requires
6546
6603
  * {@link createMCPLegacyClientTransport}. `tools()` lists + wraps the remote
6547
6604
  * tools (each `execute` calls back over the wire),
@@ -6551,7 +6608,7 @@ function createMCPLegacy(server) {
6551
6608
  * `fetch`) lives in the published server environment; the client itself is provider-agnostic. Subscribe
6552
6609
  * to `connect` / `disconnect` / `notification` through `client.emitter.on(...)`.
6553
6610
  *
6554
- * @param options - `transport` (the carrier; REQUIRED), an optional `identity`
6611
+ * @param options - `transport` (the carrier; required), an optional `identity`
6555
6612
  * (the client identity), `timeout` (the per-request deadline), and the reserved `on`
6556
6613
  * {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPClientOptions})
6557
6614
  * @returns A working {@link MCPClientInterface}
@@ -6597,7 +6654,7 @@ function createMCPLegacyClientTransport(transport, options) {
6597
6654
  * existing shape.
6598
6655
  *
6599
6656
  * @remarks
6600
- * Hand the RESULT to `createMCPClient({ transport })`, then pass the SAME
6657
+ * Hand the result to `createMCPClient({ transport })`, then pass the same
6601
6658
  * `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
6602
6659
  * wiring: `send` serializes each outbound {@link JSONRPCMessage} and writes it through
6603
6660
  * `transport.send`; `close` closes the underlying
@@ -6605,10 +6662,10 @@ function createMCPLegacyClientTransport(transport, options) {
6605
6662
  * it is handed in — there is no separate connect step at this layer); `session` is
6606
6663
  * always `undefined` (session correlation is a higher-level concern the duplex port
6607
6664
  * does not carry); and `duplex` is always `true`, because carrying frames in both
6608
- * directions at any moment is exactly what the adapted port is — a claim DRIVEN over a real
6665
+ * directions at any moment is exactly what the adapted port is — a claim driven over a real
6609
6666
  * `MessageChannel` and a real scope pair (a client-initiated `notifications/cancelled`
6610
6667
  * observed arriving at the peer) rather than read back off this literal. The literal is
6611
- * true of the PORT, and stays true only while the port has a peer: close the far half and
6668
+ * true of the port, and stays true only while the port has a peer: close the far half and
6612
6669
  * this transport still declares `true` while carrying nothing, which is the one thing a
6613
6670
  * per-carrier declaration cannot express. Inbound delivery (`emitter`'s `message` / `close` events) is
6614
6671
  * `bindClient`'s job, not this factory's — the returned object exposes a `message`-