@orkestrel/mcp 0.0.27 → 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.
- package/README.md +12 -15
- package/dist/src/browser/index.d.ts +184 -324
- package/dist/src/browser/index.js +166 -469
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +826 -352
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1265 -855
- package/dist/src/core/index.d.ts +1265 -855
- package/dist/src/core/index.js +815 -352
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +364 -680
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +395 -516
- package/dist/src/server/index.d.ts +395 -516
- package/dist/src/server/index.js +358 -665
- package/dist/src/server/index.js.map +1 -1
- package/package.json +26 -27
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_codec = require("@orkestrel/codec");
|
|
4
|
+
let _orkestrel_sse = require("@orkestrel/sse");
|
|
4
5
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
5
6
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
6
7
|
//#region src/core/constants.ts
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Names the revision offered and defaulted to in the legacy `initialize` handshake,
|
|
10
|
+
* `'2025-11-25'`.
|
|
9
11
|
*
|
|
10
12
|
* @remarks
|
|
11
13
|
* This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
|
|
@@ -13,12 +15,15 @@ let _orkestrel_tool = require("@orkestrel/tool");
|
|
|
13
15
|
* it is asking to negotiate a revision with no negotiation.
|
|
14
16
|
*/
|
|
15
17
|
var MCP_HANDSHAKE_VERSION = "2025-11-25";
|
|
16
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Names the older legacy revision the optional legacy decorator accepts and an adapter can pin,
|
|
20
|
+
* `'2025-06-18'`.
|
|
21
|
+
*/
|
|
17
22
|
var MCP_FALLBACK_VERSION = "2025-06-18";
|
|
18
|
-
/**
|
|
23
|
+
/** Names the modern revision offered by an unpinned client during discovery, `'2026-07-28'`. */
|
|
19
24
|
var MCP_MODERN_VERSION = "2026-07-28";
|
|
20
25
|
/**
|
|
21
|
-
*
|
|
26
|
+
* Lists the modern MCP protocol revisions a bare server accepts and advertises, `2026-07-28`.
|
|
22
27
|
*
|
|
23
28
|
* @remarks
|
|
24
29
|
* Frozen in discovery-advertisement order. Legacy revisions are absent because
|
|
@@ -26,45 +31,51 @@ var MCP_MODERN_VERSION = "2026-07-28";
|
|
|
26
31
|
* decorator own them.
|
|
27
32
|
*/
|
|
28
33
|
var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
|
|
29
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Lists the protocol revisions accepted by the optional legacy decorator, `2025-11-25` and
|
|
36
|
+
* `2025-06-18`.
|
|
37
|
+
*/
|
|
30
38
|
var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
|
|
31
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Lists the protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy
|
|
41
|
+
* eras.
|
|
42
|
+
*/
|
|
32
43
|
var SUPPORTED_MCP_VERSIONS = Object.freeze([...SUPPORTED_MODERN_PROTOCOL_VERSIONS, ...SUPPORTED_LEGACY_PROTOCOL_VERSIONS]);
|
|
33
|
-
/**
|
|
44
|
+
/** Names the reserved modern `_meta` key carrying the request's protocol revision. */
|
|
34
45
|
var MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
35
|
-
/**
|
|
46
|
+
/** Names the reserved modern `_meta` key carrying the client's open capability record. */
|
|
36
47
|
var MCP_META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
37
|
-
/**
|
|
48
|
+
/** Names the reserved modern `_meta` key carrying the optional client identity. */
|
|
38
49
|
var MCP_META_CLIENT = "io.modelcontextprotocol/clientInfo";
|
|
39
|
-
/**
|
|
50
|
+
/** Names the reserved modern `_meta` key carrying the server identity on results. */
|
|
40
51
|
var MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
|
|
41
|
-
/**
|
|
52
|
+
/** Names the reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
|
|
42
53
|
var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
|
|
43
54
|
/**
|
|
44
|
-
*
|
|
55
|
+
* Names the reserved extension key identifying the stable Tasks extension.
|
|
45
56
|
*
|
|
46
57
|
* @remarks
|
|
47
|
-
* The
|
|
48
|
-
* 2026-07-28 this package implements. A client declares it per
|
|
58
|
+
* The one spelling of it in this package, and the identity of the immutable snapshot dated
|
|
59
|
+
* 2026-07-28 this package implements. A client declares it per request, under
|
|
49
60
|
* `_meta['io.modelcontextprotocol/clientCapabilities'].extensions`; a server advertises it
|
|
50
61
|
* under `server/discover`'s `capabilities.extensions`. Both sides carry an empty object —
|
|
51
62
|
* the extension defines no options, so presence is the entire declaration.
|
|
52
63
|
*/
|
|
53
64
|
var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
|
|
54
65
|
/**
|
|
55
|
-
*
|
|
66
|
+
* Names the opening marker of the Base64 sentinel a standard MCP header value travels in.
|
|
56
67
|
*
|
|
57
68
|
* @remarks
|
|
58
|
-
* The markers are
|
|
59
|
-
* their
|
|
69
|
+
* The markers are lowercase and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
|
|
70
|
+
* their one spelling in this package: {@link import('@orkestrel/mcp').encodeSentinel} builds a
|
|
60
71
|
* sentinel from them and {@link import('@orkestrel/mcp').decodeSentinel} recognizes one by
|
|
61
|
-
* them, so the
|
|
72
|
+
* them, so the directions cannot drift apart.
|
|
62
73
|
*/
|
|
63
74
|
var MCP_SENTINEL_PREFIX = "=?base64?";
|
|
64
|
-
/**
|
|
75
|
+
/** Names the closing marker of the Base64 sentinel a standard MCP header value travels in. */
|
|
65
76
|
var MCP_SENTINEL_SUFFIX = "?=";
|
|
66
77
|
/**
|
|
67
|
-
*
|
|
78
|
+
* Names the request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
|
|
68
79
|
*
|
|
69
80
|
* @remarks
|
|
70
81
|
* The full field name is this prefix followed by the annotation's own value verbatim, so
|
|
@@ -74,17 +85,66 @@ var MCP_SENTINEL_SUFFIX = "?=";
|
|
|
74
85
|
*/
|
|
75
86
|
var MCP_PARAM_PREFIX = "Mcp-Param-";
|
|
76
87
|
/**
|
|
77
|
-
*
|
|
88
|
+
* Names the Streamable-HTTP transport header that carries the MCP session id.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* A stateful server sends it on the `initialize` reply, and
|
|
92
|
+
* {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} echoes it as a
|
|
93
|
+
* request header on every subsequent request, so a client passes that server's session
|
|
94
|
+
* validation unchanged.
|
|
95
|
+
*/
|
|
96
|
+
var MCP_SESSION_HEADER = "mcp-session-id";
|
|
97
|
+
/**
|
|
98
|
+
* Names the Streamable-HTTP transport header carrying the MCP protocol version.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* A modern request derives it from its own `_meta`; a legacy request echoes the revision the
|
|
102
|
+
* `initialize` result negotiated on each subsequent request.
|
|
103
|
+
*/
|
|
104
|
+
var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
105
|
+
/**
|
|
106
|
+
* Names the modern Streamable-HTTP request header carrying the JSON-RPC method.
|
|
78
107
|
*
|
|
79
108
|
* @remarks
|
|
80
|
-
* It is
|
|
109
|
+
* It is stamped on every modern request and on no legacy request.
|
|
110
|
+
*/
|
|
111
|
+
var MCP_METHOD_HEADER = "mcp-method";
|
|
112
|
+
/**
|
|
113
|
+
* Names the modern Streamable-HTTP request header carrying a named target.
|
|
114
|
+
*
|
|
115
|
+
* @remarks
|
|
116
|
+
* The HTTP client transport stamps it only for `tools/call`, from that request's `params.name`,
|
|
117
|
+
* in the Base64 sentinel form whenever the name cannot ride as plain ASCII.
|
|
118
|
+
*/
|
|
119
|
+
var MCP_NAME_HEADER = "mcp-name";
|
|
120
|
+
/**
|
|
121
|
+
* Identifies the tool-schema annotation key naming the header one parameter projects into.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* It is valid only on a primitive property schema statically reachable from the `inputSchema`
|
|
81
125
|
* root through `properties` keys alone. An occurrence anywhere else — under `items`, a
|
|
82
126
|
* composition or conditional keyword, or a `$ref` target — makes the whole tool definition
|
|
83
127
|
* invalid, which is what {@link import('@orkestrel/mcp').buildHeaderParameters} decides.
|
|
84
128
|
*/
|
|
85
129
|
var MCP_HEADER_ANNOTATION = "x-mcp-header";
|
|
86
130
|
/**
|
|
87
|
-
*
|
|
131
|
+
* Names the WebSocket subprotocol `createWebSocketClientTransport` requests by default —
|
|
132
|
+
* `'mcp'`, which `createWebSocketServer` selects when the client offers it. Per RFC 6455
|
|
133
|
+
* §4.1 a client MUST fail the connection if the server returns
|
|
134
|
+
* a subprotocol it did not request; Node ≥ 22 (undici) enforces this strictly, so the
|
|
135
|
+
* default bakes the correct value in. Override `WebSocketClientTransportOptions.protocols`
|
|
136
|
+
* only when connecting to a foreign server that speaks a different subprotocol (or `[]`
|
|
137
|
+
* for no subprotocol negotiation at all).
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* The client sends it in `Sec-WebSocket-Protocol` and the server echoes it in its `101`
|
|
141
|
+
* handshake, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
|
|
142
|
+
* same path. The default WebSocket upgrade path is the same `'/mcp'` the HTTP transport mounts
|
|
143
|
+
* at — the upgrade is selected by the `Upgrade: websocket` header, not a separate path.
|
|
144
|
+
*/
|
|
145
|
+
var MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
146
|
+
/**
|
|
147
|
+
* Bounds the `tools/list` pages one modern `tools/call` walks to reach its own annotations.
|
|
88
148
|
*
|
|
89
149
|
* @remarks
|
|
90
150
|
* The HTTP POST handler reads a called tool's {@link MCP_HEADER_ANNOTATION} annotations by
|
|
@@ -98,13 +158,14 @@ var MCP_HEADER_ANNOTATION = "x-mcp-header";
|
|
|
98
158
|
* answer a name no served definition annotates receives.
|
|
99
159
|
*/
|
|
100
160
|
var MCP_LOOKUP_PAGES = 8;
|
|
101
|
-
/** MCP reserved error
|
|
161
|
+
/** Names the MCP reserved error for required HTTP metadata that does not match the request body. */
|
|
102
162
|
var MCP_HEADER_MISMATCH = -32020;
|
|
103
163
|
/**
|
|
104
|
-
* MCP reserved error
|
|
164
|
+
* Names the MCP reserved error for an operation needing a client capability that was not
|
|
165
|
+
* declared.
|
|
105
166
|
*
|
|
106
167
|
* @remarks
|
|
107
|
-
* The
|
|
168
|
+
* The generic code for the whole condition, not one capability's code. This server answers
|
|
108
169
|
* it in more than one place — a `tools/call` that needs `elicitation`, and a `tasks/*` request
|
|
109
170
|
* whose client never declared `io.modelcontextprotocol/tasks` — and they are told apart by
|
|
110
171
|
* `error.data.requiredCapabilities` alone (`{ elicitation: {} }` against
|
|
@@ -114,10 +175,10 @@ var MCP_HEADER_MISMATCH = -32020;
|
|
|
114
175
|
* schema is what a peer implements against.
|
|
115
176
|
*/
|
|
116
177
|
var MCP_MISSING_CAPABILITY = -32021;
|
|
117
|
-
/** MCP reserved error
|
|
178
|
+
/** Names the MCP reserved error for a request naming an unsupported protocol revision. */
|
|
118
179
|
var MCP_UNSUPPORTED_VERSION = -32022;
|
|
119
180
|
/**
|
|
120
|
-
*
|
|
181
|
+
* Sets the default modern result freshness lifetime in milliseconds.
|
|
121
182
|
*
|
|
122
183
|
* @remarks
|
|
123
184
|
* `ttlMs` is required on cacheable results, while zero means immediately stale
|
|
@@ -125,7 +186,8 @@ var MCP_UNSUPPORTED_VERSION = -32022;
|
|
|
125
186
|
*/
|
|
126
187
|
var DEFAULT_MCP_CACHE_TTL = 6e4;
|
|
127
188
|
/**
|
|
128
|
-
*
|
|
189
|
+
* Sets the secure server bounds used when the matching `limit` option leaf is absent or
|
|
190
|
+
* malformed.
|
|
129
191
|
*
|
|
130
192
|
* @remarks
|
|
131
193
|
* One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
|
|
@@ -147,10 +209,10 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
|
|
|
147
209
|
depth: 32
|
|
148
210
|
});
|
|
149
211
|
/**
|
|
150
|
-
*
|
|
212
|
+
* Holds the one empty argument record every argument-less modern `tools/call` runs with.
|
|
151
213
|
*
|
|
152
214
|
* @remarks
|
|
153
|
-
* Frozen and null-prototype, and
|
|
215
|
+
* Frozen and null-prototype, and shared: two calls that name no `arguments` receive the same
|
|
154
216
|
* reference, so nothing a tool writes into its own `arguments` can survive into the next
|
|
155
217
|
* call — the write fails instead. That failure is a tool-domain failure like any other: the
|
|
156
218
|
* registry isolates it into a `success: false` result, which reaches the peer as an
|
|
@@ -161,48 +223,55 @@ var DEFAULT_MCP_LIMITS = Object.freeze({
|
|
|
161
223
|
* `arguments.constructor` is `undefined` here rather than a function.
|
|
162
224
|
*/
|
|
163
225
|
var EMPTY_MCP_ARGUMENTS = Object.freeze(Object.create(null));
|
|
164
|
-
/** JSON-RPC 2.0 reserved error
|
|
226
|
+
/** Names the JSON-RPC 2.0 reserved error for invalid JSON received (the message did not parse). */
|
|
165
227
|
var JSONRPC_PARSE_ERROR = -32700;
|
|
166
|
-
/** JSON-RPC 2.0 reserved error
|
|
228
|
+
/** Names the JSON-RPC 2.0 reserved error for a payload that was not a valid Request object. */
|
|
167
229
|
var JSONRPC_INVALID_REQUEST = -32600;
|
|
168
|
-
/** JSON-RPC 2.0 reserved error
|
|
230
|
+
/** Names the JSON-RPC 2.0 reserved error for a requested method that does not exist. */
|
|
169
231
|
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
170
|
-
/** JSON-RPC 2.0 reserved error
|
|
232
|
+
/** Names the JSON-RPC 2.0 reserved error for a method's invalid parameters. */
|
|
171
233
|
var JSONRPC_INVALID_PARAMS = -32602;
|
|
172
234
|
/**
|
|
173
|
-
* JSON-RPC 2.0 reserved error
|
|
235
|
+
* Names the JSON-RPC 2.0 reserved error for a server that failed while handling an otherwise
|
|
236
|
+
* valid request.
|
|
174
237
|
*
|
|
175
238
|
* @remarks
|
|
176
|
-
* The code every
|
|
239
|
+
* The code every modern internal fault answers with — a provider, handler, continuation,
|
|
177
240
|
* capacity, stream-source, normalization, or serialization failure the server contained.
|
|
178
241
|
* It is detail-free on the wire: the caught value reaches the application through the
|
|
179
242
|
* server's `error` event and never through the response.
|
|
180
243
|
*/
|
|
181
244
|
var JSONRPC_INTERNAL_ERROR = -32603;
|
|
182
245
|
/**
|
|
183
|
-
* JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
|
|
246
|
+
* Names the JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range).
|
|
184
247
|
*
|
|
185
248
|
* @remarks
|
|
186
|
-
* Retained for the
|
|
249
|
+
* Retained for the legacy branch alone. A modern fault answers
|
|
187
250
|
* {@link JSONRPC_INTERNAL_ERROR}; this code survives only where an old-wire peer was
|
|
188
251
|
* already characterized against it.
|
|
189
252
|
*/
|
|
190
253
|
var JSONRPC_SERVER_ERROR = -32e3;
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Supplies the default client name reported in the MCP `initialize` handshake
|
|
256
|
+
* (`clientInfo.name`).
|
|
257
|
+
*/
|
|
258
|
+
var DEFAULT_MCP_CLIENT_NAME = "@orkestrel/mcp";
|
|
259
|
+
/**
|
|
260
|
+
* Supplies the default client version reported in the MCP `initialize` handshake
|
|
261
|
+
* (`clientInfo.version`).
|
|
262
|
+
*/
|
|
194
263
|
var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
|
|
195
264
|
/**
|
|
196
|
-
*
|
|
265
|
+
* Sets the default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
|
|
197
266
|
* is unset — a request the remote server does not answer within it rejects.
|
|
198
267
|
*/
|
|
199
268
|
var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
|
|
200
|
-
/**
|
|
269
|
+
/** Sets the default number of subscription frames retained while no client read is parked. */
|
|
201
270
|
var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
|
|
202
271
|
//#endregion
|
|
203
272
|
//#region src/core/errors.ts
|
|
204
273
|
/**
|
|
205
|
-
*
|
|
274
|
+
* Preserves a Model Context Protocol error's machine-readable numeric code and
|
|
206
275
|
* optional structured context.
|
|
207
276
|
*
|
|
208
277
|
* @remarks
|
|
@@ -245,7 +314,7 @@ var MCPError = class extends Error {
|
|
|
245
314
|
* Determines whether an unknown value is an {@link MCPError}.
|
|
246
315
|
*
|
|
247
316
|
* @param value - The unknown value to inspect
|
|
248
|
-
* @returns
|
|
317
|
+
* @returns True if the value is an `MCPError`; false otherwise
|
|
249
318
|
*
|
|
250
319
|
* @example
|
|
251
320
|
* ```ts
|
|
@@ -376,19 +445,19 @@ function snapshotToolResult(value, limits) {
|
|
|
376
445
|
*
|
|
377
446
|
* @remarks
|
|
378
447
|
* Total — a non-message returns `undefined`, never throws. The input must
|
|
379
|
-
*
|
|
448
|
+
* already be `JSON.parse`d: the raw-string parse (which can throw on malformed
|
|
380
449
|
* JSON) happens in `MCPServer.handle` inside a try/catch that maps a parse failure
|
|
381
450
|
* to a `-32700` response.
|
|
382
451
|
*
|
|
383
|
-
* A defined result is an
|
|
452
|
+
* A defined result is an owned canonical snapshot, never the input reference: it is
|
|
384
453
|
* rebuilt from the canonical text and deeply frozen, so `-0` arrives as `0`. Every record
|
|
385
|
-
* was
|
|
454
|
+
* was serialized with its keys sorted, but the rebuilt object enumerates its own keys the
|
|
386
455
|
* way JavaScript does, so an integer-like `'9'` still precedes `'10'`: the result's key
|
|
387
456
|
* order is neither promised nor generally the canonical one. A caller who needs canonical
|
|
388
|
-
*
|
|
457
|
+
* bytes takes them from `serializeJSON`/`snapshotJSON` rather than re-stringifying this
|
|
389
458
|
* result. Identity is not preserved and is not promised.
|
|
390
459
|
*
|
|
391
|
-
* The parser's sound partner is the
|
|
460
|
+
* The parser's sound partner is the composite `isJSONRPCMessage(value) &&
|
|
392
461
|
* isBoundedJSON(value, limits)`, and against it both halves of the soundness law
|
|
393
462
|
* hold by construction:
|
|
394
463
|
*
|
|
@@ -396,12 +465,12 @@ function snapshotToolResult(value, limits) {
|
|
|
396
465
|
* is applied to the exact frozen reference returned.
|
|
397
466
|
* - Every input satisfying the composite is admitted rather than rejected, because
|
|
398
467
|
* `isBoundedJSON` is this parser's own admission test — the same canonical
|
|
399
|
-
* serializer under the same `limits` — so
|
|
468
|
+
* serializer under the same `limits` — so they cannot disagree about the bound.
|
|
400
469
|
*
|
|
401
|
-
* {@link isJSONRPCMessage}
|
|
470
|
+
* {@link isJSONRPCMessage} Alone is not that partner. It is clone-backed and so already
|
|
402
471
|
* exact about shape, but it carries no size or depth bound — so guard-valid values
|
|
403
472
|
* exist that this parser rejects: a message nested deeper than `limits.depth`, and one
|
|
404
|
-
* whose canonical text exceeds `limits.bytes`. Those are named causes,
|
|
473
|
+
* whose canonical text exceeds `limits.bytes`. Those are named causes, not a complete
|
|
405
474
|
* boundary. Among values `isJSONRPCMessage` already admits, the admitted set is exactly
|
|
406
475
|
* what canonical serialization accepts under `limits`, so a caller who needs that line
|
|
407
476
|
* tests it with `isBoundedJSON` rather than inferring it from this list.
|
|
@@ -431,7 +500,7 @@ function parseJSONRPCMessage(value, limits = {
|
|
|
431
500
|
* This is the validity step after {@link isModernRequest}: a defined result can
|
|
432
501
|
* only come from a guard-positive request, while a guard-positive request returns
|
|
433
502
|
* `undefined` when its required modern metadata is malformed — and also when the
|
|
434
|
-
* request falls outside the bound this parser
|
|
503
|
+
* request falls outside the bound this parser inherits by routing through
|
|
435
504
|
* {@link parseJSONRPCMessage} under the same `limits`. The version
|
|
436
505
|
* must be a string but need not be supported; unsupported strings belong to the
|
|
437
506
|
* dedicated protocol-version error path. Client identity is optional, but when
|
|
@@ -480,11 +549,11 @@ function parseRequestContext(value, limits = {
|
|
|
480
549
|
* @remarks
|
|
481
550
|
* This parser does not open the opaque continuation carrier; the configured
|
|
482
551
|
* continuation port performs that boundary first. The protected
|
|
483
|
-
* payload binds the authenticated principal, absolute expiry,
|
|
552
|
+
* payload binds the authenticated principal, absolute expiry, original request id, version,
|
|
484
553
|
* method, the exact round that was issued, tool name, argument digest, and optional
|
|
485
554
|
* application state. Every member is required except application state: a payload missing its
|
|
486
555
|
* round cannot have the client's answers enforced, so it is refused rather than admitted
|
|
487
|
-
* unenforced. An
|
|
556
|
+
* unenforced. An empty round is refused for the same reason — a retry against it would answer
|
|
488
557
|
* no question at all. Total over malformed or hostile input.
|
|
489
558
|
*
|
|
490
559
|
* @param value - The opened canonical continuation value to parse
|
|
@@ -498,7 +567,7 @@ function parseRequestContext(value, limits = {
|
|
|
498
567
|
function parseMCPInputState(value) {
|
|
499
568
|
try {
|
|
500
569
|
if (!(0, _orkestrel_contract.isString)(value)) return void 0;
|
|
501
|
-
const parsed =
|
|
570
|
+
const parsed = (0, _orkestrel_contract.parseJSON)(value);
|
|
502
571
|
if (!(0, _orkestrel_contract.isRecord)(parsed)) return void 0;
|
|
503
572
|
const principal = parsed["principal"];
|
|
504
573
|
const expiry = parsed["expiry"];
|
|
@@ -540,15 +609,15 @@ function parseMCPInputState(value) {
|
|
|
540
609
|
* does not authorize a form request. Total over hostile input.
|
|
541
610
|
*
|
|
542
611
|
* @param value - The client capability record to inspect
|
|
543
|
-
* @returns
|
|
612
|
+
* @returns True if form-mode elicitation is declared; false otherwise
|
|
544
613
|
*
|
|
545
614
|
* @example
|
|
546
615
|
* ```ts
|
|
547
|
-
*
|
|
548
|
-
*
|
|
616
|
+
* supportsFormElicitation({ elicitation: {} }) // true — implicit form mode
|
|
617
|
+
* supportsFormElicitation({ elicitation: { url: {} } }) // false
|
|
549
618
|
* ```
|
|
550
619
|
*/
|
|
551
|
-
function
|
|
620
|
+
function supportsFormElicitation(value) {
|
|
552
621
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
553
622
|
if (!owned.success) return false;
|
|
554
623
|
try {
|
|
@@ -564,19 +633,19 @@ function isFormElicitationSupported(value) {
|
|
|
564
633
|
* Computes the capabilities one round of input requests needs and the client did not declare.
|
|
565
634
|
*
|
|
566
635
|
* @remarks
|
|
567
|
-
* The protocol's rule is about
|
|
636
|
+
* The protocol's rule is about sending: a server never issues a request kind the client's
|
|
568
637
|
* declared capabilities exclude. So this reads the round rather than the method, and it
|
|
569
638
|
* answers with the refusal's own payload — the `requiredCapabilities` record a
|
|
570
639
|
* `MissingRequiredClientCapability` error carries, keyed by each missing capability, in the
|
|
571
640
|
* `ClientCapabilities` shape the schema defines rather than as a list of names.
|
|
572
641
|
*
|
|
573
642
|
* Each kind maps to one declaration: `sampling/createMessage` to `sampling`, `roots/list` to
|
|
574
|
-
* `roots`, a form elicitation to what {@link
|
|
643
|
+
* `roots`, a form elicitation to what {@link supportsFormElicitation} accepts, and a
|
|
575
644
|
* URL-mode elicitation to a record-valued `elicitation.url`. A request this package cannot
|
|
576
645
|
* recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
|
|
577
646
|
* has already refused the round it would have travelled in. Total over hostile input.
|
|
578
647
|
*
|
|
579
|
-
* The `elicitation` value names the
|
|
648
|
+
* The `elicitation` value names the arm the round needs, so a client can act on the refusal
|
|
580
649
|
* by declaring exactly what the payload asks for. A missing URL arm answers `{ url: {} }`, a
|
|
581
650
|
* missing form arm answers the empty record this package reads as form-only, and a round
|
|
582
651
|
* needing both answers `{ form: {}, url: {} }`. An empty record for a URL round would name
|
|
@@ -612,7 +681,7 @@ function computeMissingCapabilities(requests, capabilities) {
|
|
|
612
681
|
if (!(0, _orkestrel_contract.isRecord)(elicitation) || !(0, _orkestrel_contract.isRecord)(elicitation["url"])) urlUndeclared = true;
|
|
613
682
|
continue;
|
|
614
683
|
}
|
|
615
|
-
if (!
|
|
684
|
+
if (!supportsFormElicitation(declared)) formUndeclared = true;
|
|
616
685
|
}
|
|
617
686
|
if (formUndeclared && !urlUndeclared) missing["elicitation"] = {};
|
|
618
687
|
if (urlUndeclared && !formUndeclared) missing["elicitation"] = { url: {} };
|
|
@@ -627,28 +696,28 @@ function computeMissingCapabilities(requests, capabilities) {
|
|
|
627
696
|
*
|
|
628
697
|
* @remarks
|
|
629
698
|
* The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and the schema
|
|
630
|
-
* types its value
|
|
699
|
+
* types its value exactly empty — `Record<string, never>`, an object with no additional
|
|
631
700
|
* properties. So the key's presence is the whole declaration, and the value carries the
|
|
632
701
|
* whole of the check: a `true` or a string there is a client speaking a different protocol
|
|
633
702
|
* rather than a shorthand, and a member inside the object is a client declaring an option
|
|
634
703
|
* this extension does not define. Both are refused, because a server that accepted either
|
|
635
704
|
* would be reading a shape no peer can produce from the snapshot's own schema.
|
|
636
705
|
*
|
|
637
|
-
* A client declares this
|
|
706
|
+
* A client declares this per request. Nothing here consults a session, because the modern
|
|
638
707
|
* revision is stateless and a capability declared once at connect time says nothing about
|
|
639
708
|
* the request in hand. Total over hostile input.
|
|
640
709
|
*
|
|
641
710
|
* @param value - The client capability record to inspect
|
|
642
|
-
* @returns
|
|
711
|
+
* @returns True if the tasks extension is declared as the schema's empty object; false otherwise
|
|
643
712
|
*
|
|
644
713
|
* @example
|
|
645
714
|
* ```ts
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
715
|
+
* supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
|
|
716
|
+
* supportsTask({ extensions: {} }) // false — the key is the declaration
|
|
717
|
+
* supportsTask({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
|
|
649
718
|
* ```
|
|
650
719
|
*/
|
|
651
|
-
function
|
|
720
|
+
function supportsTask(value) {
|
|
652
721
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
653
722
|
if (!owned.success) return false;
|
|
654
723
|
try {
|
|
@@ -915,7 +984,7 @@ function buildProgressNotification(token, progress) {
|
|
|
915
984
|
* Builds one official cancellation notification for a request already sent.
|
|
916
985
|
*
|
|
917
986
|
* @remarks
|
|
918
|
-
* `requestId` and `reason` are
|
|
987
|
+
* `requestId` and `reason` are wire spellings carried verbatim from the dated schema's
|
|
919
988
|
* `CancelledNotificationParams`, and so is the `cancelled` in the method name — this
|
|
920
989
|
* package's own vocabulary says `abort`, but the method is the protocol's and does not
|
|
921
990
|
* change. The notification is FIRE-AND-FORGET in the strongest sense: it carries no id,
|
|
@@ -925,7 +994,7 @@ function buildProgressNotification(token, progress) {
|
|
|
925
994
|
* rather than as a violation.
|
|
926
995
|
*
|
|
927
996
|
* Only write one on a carrier that accepts a client-initiated notification — see
|
|
928
|
-
* {@link import('./types.js').
|
|
997
|
+
* {@link import('./types.js').MCPMessageTransportInterface.duplex}. On Streamable HTTP the
|
|
929
998
|
* dated revision defines no such frame, and closing the response stream is the
|
|
930
999
|
* cancellation signal instead.
|
|
931
1000
|
*
|
|
@@ -953,7 +1022,7 @@ function buildCancelledNotification(id, reason) {
|
|
|
953
1022
|
* Determines whether one method may answer with a given modern `resultType`.
|
|
954
1023
|
*
|
|
955
1024
|
* @remarks
|
|
956
|
-
* The dated protocol lets a `tools/call` answer in more than one way — it
|
|
1025
|
+
* The dated protocol lets a `tools/call` answer in more than one way — it completed, it became a
|
|
957
1026
|
* durable task, or it needs another round trip — while every other method this client
|
|
958
1027
|
* issues has exactly one legal answer. So the arm a peer chose is only meaningful beside
|
|
959
1028
|
* the method it answers, and this is the one place that pairing is decided.
|
|
@@ -964,7 +1033,7 @@ function buildCancelledNotification(id, reason) {
|
|
|
964
1033
|
*
|
|
965
1034
|
* @param method - The method the pending request was issued for
|
|
966
1035
|
* @param resultType - The unknown `resultType` the peer answered with
|
|
967
|
-
* @returns
|
|
1036
|
+
* @returns True if that method may legally answer with that `resultType`; false otherwise
|
|
968
1037
|
*
|
|
969
1038
|
* @example
|
|
970
1039
|
* ```ts
|
|
@@ -982,9 +1051,9 @@ function matchesResultType(method, resultType) {
|
|
|
982
1051
|
* Concatenates an MCP tool-call result's text content blocks into one string.
|
|
983
1052
|
*
|
|
984
1053
|
* @remarks
|
|
985
|
-
* The inverse of a server splitting a value into text block(s), and
|
|
1054
|
+
* The inverse of a server splitting a value into text block(s), and total: a non-record
|
|
986
1055
|
* result, a non-array `content`, or a non-string `text` contributes nothing rather than
|
|
987
|
-
* throwing. What it returns is a
|
|
1056
|
+
* throwing. What it returns is a rendering — the prose a model reads — and not the tool's
|
|
988
1057
|
* value, which travels as `structuredContent` whenever the peer sent one.
|
|
989
1058
|
*
|
|
990
1059
|
* @param result - The unknown result payload to read content blocks from
|
|
@@ -1011,12 +1080,12 @@ function extractContentText(result) {
|
|
|
1011
1080
|
* the arms the protocol gives a shape to, and deriving the tool's value from the one it
|
|
1012
1081
|
* does not:
|
|
1013
1082
|
*
|
|
1014
|
-
* - A peer's `structuredContent` is
|
|
1083
|
+
* - A peer's `structuredContent` is preferred over the content blocks, because it is the
|
|
1015
1084
|
* tool's value in its original structure while the blocks are a rendering beside it. Its
|
|
1016
1085
|
* mere presence decides — an explicit `null` is a value the tool returned, not an absence.
|
|
1017
1086
|
* - With no structured value the legacy shape applies: the value was JSON-serialized into
|
|
1018
1087
|
* the text block(s), so parse them and fall back to the raw string when they are not JSON.
|
|
1019
|
-
* - A remote tool
|
|
1088
|
+
* - A remote tool failure (`isError: true`) throws the error text, so an agent's tool
|
|
1020
1089
|
* registry isolates it into a failure result exactly as it would a local throw.
|
|
1021
1090
|
*
|
|
1022
1091
|
* @param name - The tool's name, used only to describe a failure that carried no text
|
|
@@ -1098,14 +1167,14 @@ function buildJSONRPCResult(id, result) {
|
|
|
1098
1167
|
* as an `error` object.
|
|
1099
1168
|
*
|
|
1100
1169
|
* @remarks
|
|
1101
|
-
* An `undefined` `id` is
|
|
1170
|
+
* An `undefined` `id` is omitted from the envelope rather than serialized as `null`:
|
|
1102
1171
|
* MCP overrides the base specification here, so a peer that could not have its id
|
|
1103
1172
|
* read receives a response with no `id` member at all.
|
|
1104
1173
|
*
|
|
1105
1174
|
* @param id - The failed request's id, or `undefined` when none could be read
|
|
1106
1175
|
* @param code - One of the reserved JSON-RPC codes (see `./constants.js`)
|
|
1107
1176
|
* @param message - A short human description of the failure
|
|
1108
|
-
* @param data - An
|
|
1177
|
+
* @param data - An optional machine-readable payload (omitted from the envelope when absent)
|
|
1109
1178
|
* @returns The error response envelope
|
|
1110
1179
|
*/
|
|
1111
1180
|
function buildJSONRPCError(id, code, message, data) {
|
|
@@ -1127,11 +1196,11 @@ function buildJSONRPCError(id, code, message, data) {
|
|
|
1127
1196
|
* receives.
|
|
1128
1197
|
*
|
|
1129
1198
|
* @remarks
|
|
1130
|
-
* The
|
|
1199
|
+
* The one place a cancellation signal is resolved. A caller may have no signal to
|
|
1131
1200
|
* offer; a dispatched method always has one to observe, so a missing signal becomes
|
|
1132
1201
|
* a real signal rather than an absence every downstream handler would have to case on.
|
|
1133
1202
|
*
|
|
1134
|
-
* The resolved signal is the request's
|
|
1203
|
+
* The resolved signal is the request's lifetime, which is strictly wider than the
|
|
1135
1204
|
* caller's: it composes the caller's signal, when there is one, with the `lifetime`
|
|
1136
1205
|
* dispatch owns and aborts once the answer this request produced is finished. That is
|
|
1137
1206
|
* what wakes a stream producer parked on an event that will never arrive after its
|
|
@@ -1319,7 +1388,7 @@ function buildSubscriptionFilter(requested, supported, enabled = false) {
|
|
|
1319
1388
|
*
|
|
1320
1389
|
* @param notification - The server notification offered by the configured producer
|
|
1321
1390
|
* @param filter - The filter acknowledged to the client
|
|
1322
|
-
* @returns
|
|
1391
|
+
* @returns True if the notification belongs on this subscription stream; false otherwise
|
|
1323
1392
|
*/
|
|
1324
1393
|
function matchesSubscriptionNotification(notification, filter) {
|
|
1325
1394
|
if (notification.method === "notifications/tools/list_changed") return filter.toolsListChanged === true;
|
|
@@ -1384,7 +1453,7 @@ function buildSubscriptionResult(id, identity) {
|
|
|
1384
1453
|
* `capabilities.resources` and `capabilities.prompts` appear only for servers with their
|
|
1385
1454
|
* respective managers and derive notification flags from the configured subscription filter.
|
|
1386
1455
|
* `capabilities.completions` is independent and appears only with a completion provider.
|
|
1387
|
-
* `capabilities.extensions` appears only for a server that
|
|
1456
|
+
* `capabilities.extensions` appears only for a server that configured the extension it
|
|
1388
1457
|
* would name. An advertisement is a promise a client is entitled to act on, so a server
|
|
1389
1458
|
* with no `task` policy omits the member entirely rather than advertising an empty
|
|
1390
1459
|
* record — and its discovery answer stays byte-for-byte what it was before the extension
|
|
@@ -1440,8 +1509,8 @@ function buildInitializeResult(name, version, requested) {
|
|
|
1440
1509
|
* before it hands the string on.
|
|
1441
1510
|
*
|
|
1442
1511
|
* @remarks
|
|
1443
|
-
* The bound is checked
|
|
1444
|
-
*
|
|
1512
|
+
* The bound is checked first, against the raw string, so an oversized message is never
|
|
1513
|
+
* parsed at all: a decoder that parses before it measures has already spent the work
|
|
1445
1514
|
* the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
|
|
1446
1515
|
* value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
|
|
1447
1516
|
* exactly the same thing with each of them: nothing, and let
|
|
@@ -1461,8 +1530,129 @@ function buildInitializeResult(name, version, requested) {
|
|
|
1461
1530
|
*/
|
|
1462
1531
|
function decodeBoundedMessage(message, limits) {
|
|
1463
1532
|
if (!isBoundedString(message, limits.bytes)) return void 0;
|
|
1464
|
-
|
|
1465
|
-
|
|
1533
|
+
return parseJSONRPCMessage((0, _orkestrel_contract.parseJSON)(message), limits);
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Decodes one inbound frame and delivers it onto a transport emitter as `message` or `error`.
|
|
1537
|
+
*
|
|
1538
|
+
* @remarks
|
|
1539
|
+
* The one inbound fold every message-carrying transport in this package runs: parse the frame,
|
|
1540
|
+
* narrow it with `parseJSONRPCMessage`, emit `message` for a well-formed
|
|
1541
|
+
* {@link JSONRPCMessage}, and emit `error` for anything else. Total — an adversarial frame
|
|
1542
|
+
* produces an `error` emission and never a throw.
|
|
1543
|
+
*
|
|
1544
|
+
* The failures report differently on purpose. Unparsable text emits the caught parse
|
|
1545
|
+
* error, which names the offending position; well-formed JSON that is not a JSON-RPC message
|
|
1546
|
+
* has no caught value to report, so it emits `fault` — the carrier's own wording, passed in
|
|
1547
|
+
* rather than forked into a second copy of this body.
|
|
1548
|
+
*
|
|
1549
|
+
* @param emitter - The transport's emitter to deliver onto
|
|
1550
|
+
* @param text - One inbound frame's raw text
|
|
1551
|
+
* @param fault - The message for the error emitted when the frame parses but is not JSON-RPC
|
|
1552
|
+
*
|
|
1553
|
+
* @example
|
|
1554
|
+
* ```ts
|
|
1555
|
+
* deliverMessage(transport.emitter, frame, 'non-JSON-RPC WebSocket frame')
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
function deliverMessage(emitter, text, fault) {
|
|
1559
|
+
let parsed;
|
|
1560
|
+
try {
|
|
1561
|
+
parsed = JSON.parse(text);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
emitter.emit("error", error);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const message = parseJSONRPCMessage(parsed);
|
|
1567
|
+
if (message === void 0) {
|
|
1568
|
+
emitter.emit("error", new Error(fault));
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
emitter.emit("message", message);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
|
|
1575
|
+
* when it is not one — the per-event step {@link readEventStream} folds over.
|
|
1576
|
+
*
|
|
1577
|
+
* @remarks
|
|
1578
|
+
* Parses the `data` (a peer serializes the JSON-RPC envelope as the event's `data`) with
|
|
1579
|
+
* `@orkestrel/contract`'s `parseJSON` — the declared JSON boundary, which answers `undefined`
|
|
1580
|
+
* instead of throwing — and narrows the parsed value with `parseJSONRPCMessage`. Total:
|
|
1581
|
+
* malformed JSON or a non-message value yields `undefined`, never throws.
|
|
1582
|
+
*
|
|
1583
|
+
* @param data - One SSE event's `data` payload
|
|
1584
|
+
* @returns The decoded {@link JSONRPCMessage}, or `undefined`
|
|
1585
|
+
*
|
|
1586
|
+
* @example
|
|
1587
|
+
* ```ts
|
|
1588
|
+
* decodeEvent('{"jsonrpc":"2.0","id":1,"result":{}}') // the decoded response
|
|
1589
|
+
* ```
|
|
1590
|
+
*/
|
|
1591
|
+
function decodeEvent(data) {
|
|
1592
|
+
return parseJSONRPCMessage((0, _orkestrel_contract.parseJSON)(data));
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
|
|
1596
|
+
* carried — the client-side inverse of a server's Streamable-HTTP SSE response.
|
|
1597
|
+
*
|
|
1598
|
+
* @remarks
|
|
1599
|
+
* Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({ stream: true
|
|
1600
|
+
* })` (handling a multi-byte character split across reads) and `@orkestrel/sse`'s
|
|
1601
|
+
* {@link SSEParserInterface} (handling a partial line or in-progress event split across
|
|
1602
|
+
* reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} through
|
|
1603
|
+
* {@link decodeEvent} (so a non-message or non-JSON `data:` event is dropped, never thrown —
|
|
1604
|
+
* total). It reuses the same `SSEParser` a server's `createStream` seam serializes against, so
|
|
1605
|
+
* the wire round-trips. A `null` body (no stream) yields no messages;
|
|
1606
|
+
* {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport} reads a
|
|
1607
|
+
* request/response SSE reply (the server sends one `data:` event then ends), so this drains to
|
|
1608
|
+
* completion.
|
|
1609
|
+
*
|
|
1610
|
+
* @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
|
|
1611
|
+
* @returns Every {@link JSONRPCMessage} the stream carried, in order
|
|
1612
|
+
*
|
|
1613
|
+
* @example
|
|
1614
|
+
* ```ts
|
|
1615
|
+
* const messages = await readEventStream(await fetch(url, { method: 'POST', body }))
|
|
1616
|
+
* ```
|
|
1617
|
+
*/
|
|
1618
|
+
async function readEventStream(response) {
|
|
1619
|
+
const body = response.body;
|
|
1620
|
+
if (body === null) return [];
|
|
1621
|
+
const reader = body.getReader();
|
|
1622
|
+
const decoder = new TextDecoder();
|
|
1623
|
+
const parser = (0, _orkestrel_sse.createSSEParser)();
|
|
1624
|
+
const messages = [];
|
|
1625
|
+
try {
|
|
1626
|
+
for (;;) {
|
|
1627
|
+
const { done, value } = await reader.read();
|
|
1628
|
+
if (done) break;
|
|
1629
|
+
for (const event of parser.parse(decoder.decode(value, { stream: true }))) {
|
|
1630
|
+
const message = decodeEvent(event.data);
|
|
1631
|
+
if (message !== void 0) messages.push(message);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
} finally {
|
|
1635
|
+
reader.releaseLock();
|
|
1636
|
+
}
|
|
1637
|
+
return messages;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Builds the error for a non-success HTTP response that carried no JSON-RPC message.
|
|
1641
|
+
*
|
|
1642
|
+
* @param response - The response whose status is reported
|
|
1643
|
+
* @param type - The response's content type, or an empty string when absent
|
|
1644
|
+
* @returns An error naming the HTTP status and unsupported response shape
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* ```ts
|
|
1648
|
+
* const error = buildResponseError(new Response('', { status: 500 }), '')
|
|
1649
|
+
* ```
|
|
1650
|
+
*/
|
|
1651
|
+
function buildResponseError(response, type) {
|
|
1652
|
+
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`);
|
|
1653
|
+
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`);
|
|
1654
|
+
const shape = type === "" ? "a body without a content type" : `an unsupported '${type}' body`;
|
|
1655
|
+
return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained ${shape}`);
|
|
1466
1656
|
}
|
|
1467
1657
|
/**
|
|
1468
1658
|
* Reads the value one standard MCP request header carries, decoding the Base64 sentinel.
|
|
@@ -1478,7 +1668,7 @@ function decodeBoundedMessage(message, limits) {
|
|
|
1478
1668
|
* second spelling of a byte, so it is refused: `=?base64?QR==?=` reaches for the byte
|
|
1479
1669
|
* `=?base64?QQ==?=` spells canonically, and only the canonical spelling decodes. A malformed
|
|
1480
1670
|
* payload answers `undefined` rather than falling back to the literal, because the protocol
|
|
1481
|
-
* requires a server to
|
|
1671
|
+
* requires a server to reject invalid characters, and a fallback would admit the very value
|
|
1482
1672
|
* the rule exists to refuse. A value missing either marker is a literal and comes back
|
|
1483
1673
|
* unchanged.
|
|
1484
1674
|
*
|
|
@@ -1524,7 +1714,7 @@ function decodeSentinel(value) {
|
|
|
1524
1714
|
*
|
|
1525
1715
|
* @remarks
|
|
1526
1716
|
* The exact inverse of {@link decodeSentinel}, and its membership rule is stated as that
|
|
1527
|
-
* inverse rather than as a second list that could drift: a value travels
|
|
1717
|
+
* inverse rather than as a second list that could drift: a value travels literally when it is
|
|
1528
1718
|
* plain printable ASCII — every code point in `U+0020`–`U+007E`, the RFC 9110 field-value
|
|
1529
1719
|
* range this package admits — and {@link decodeSentinel} gives it back unchanged. Every other
|
|
1530
1720
|
* value travels wrapped in {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX}, the
|
|
@@ -1564,7 +1754,7 @@ function encodeSentinel(value) {
|
|
|
1564
1754
|
*
|
|
1565
1755
|
* @remarks
|
|
1566
1756
|
* The companion of {@link extractHeaderAnnotations}, which reads only the annotations a
|
|
1567
|
-
* `properties` chain reaches. Comparing the
|
|
1757
|
+
* `properties` chain reaches. Comparing the answers is how
|
|
1568
1758
|
* {@link buildHeaderParameters} decides reachability without a second walk that would have
|
|
1569
1759
|
* to re-state which JSON Schema keywords are traversable: an annotation the reachable walk
|
|
1570
1760
|
* did not read is one sitting under `items`, a composition or conditional keyword, a `$ref`
|
|
@@ -1608,12 +1798,12 @@ function countHeaderAnnotations(value) {
|
|
|
1608
1798
|
* Reachability is the protocol's own rule: an annotation counts only where a chain of
|
|
1609
1799
|
* `properties` keys leads to it from the `inputSchema` root, so `path` is both the schema
|
|
1610
1800
|
* position and the position the call's `arguments` carry the value at. A property named
|
|
1611
|
-
* `items` is reachable like any other, because the chain is read by key
|
|
1801
|
+
* `items` is reachable like any other, because the chain is read by key position rather than
|
|
1612
1802
|
* by key name.
|
|
1613
1803
|
*
|
|
1614
1804
|
* `undefined` means the definition is invalid rather than empty: a reachable annotation whose
|
|
1615
1805
|
* value is not an {@link import('./validators.js').isFieldToken} token, one sitting on the
|
|
1616
|
-
* schema
|
|
1806
|
+
* schema root (which is no property), one on a leaf whose declared type is not an
|
|
1617
1807
|
* {@link import('./validators.js').isMCPHeaderPrimitive} primitive, or a chain deeper than
|
|
1618
1808
|
* `DEFAULT_MCP_LIMITS.depth` — which is also what makes a self-referential schema terminate.
|
|
1619
1809
|
* A node that is not a record carries nothing and answers an empty list, because a leaf the
|
|
@@ -1657,8 +1847,8 @@ function extractHeaderAnnotations(schema, path) {
|
|
|
1657
1847
|
*
|
|
1658
1848
|
* @remarks
|
|
1659
1849
|
* The single decision both sides of the protocol make about an annotated tool: an HTTP
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1850
|
+
* client excludes a definition this refuses from the `tools/list` result it delivers, and a
|
|
1851
|
+
* server recognizes exactly the `Mcp-Param-*` names this returns for its own definitions.
|
|
1662
1852
|
*
|
|
1663
1853
|
* `undefined` means the definition is invalid, and every rule the protocol states produces
|
|
1664
1854
|
* it: a value that is not an RFC 9110 token, a non-primitive or untyped annotated leaf, a
|
|
@@ -1695,11 +1885,11 @@ function buildHeaderParameters(schema) {
|
|
|
1695
1885
|
* Renders one projected argument as the text its `Mcp-Param-*` header carries.
|
|
1696
1886
|
*
|
|
1697
1887
|
* @remarks
|
|
1698
|
-
* The protocol's conversion table, and the
|
|
1888
|
+
* The protocol's conversion table, and the one place it is stated: a string travels as
|
|
1699
1889
|
* itself, an integer in decimal, and a boolean as lowercase `true` or `false`. The value's
|
|
1700
1890
|
* runtime shape must match the leaf's declared type, so a schema that declares `integer` and
|
|
1701
1891
|
* an argument that supplies a string, a fraction, or a magnitude outside the IEEE 754 safe
|
|
1702
|
-
* range carries
|
|
1892
|
+
* range carries nothing — a header that cannot round-trip the body value is worse than an
|
|
1703
1893
|
* absent one, and the tool's own argument validation owns the disagreement.
|
|
1704
1894
|
*
|
|
1705
1895
|
* @param value - The argument value read at the parameter's path
|
|
@@ -1722,7 +1912,7 @@ function renderHeaderValue(value, primitive) {
|
|
|
1722
1912
|
*
|
|
1723
1913
|
* @remarks
|
|
1724
1914
|
* The projection SEP-2243 requires of an HTTP client, and the same derivation a server runs
|
|
1725
|
-
* to know what the request
|
|
1915
|
+
* to know what the request must carry. Each parameter's value is read at its exact
|
|
1726
1916
|
* property path in the call's own `arguments`; an absent or `null` value omits its header
|
|
1727
1917
|
* entirely, which is the protocol's distinction between "not supplied" and "supplied empty".
|
|
1728
1918
|
* The rendered text then travels through {@link encodeSentinel}, so a value carrying
|
|
@@ -1784,7 +1974,7 @@ function extractToolSchema(response, name) {
|
|
|
1784
1974
|
* {@link buildCancelledNotification}.
|
|
1785
1975
|
*
|
|
1786
1976
|
* @remarks
|
|
1787
|
-
* `requestId` is the
|
|
1977
|
+
* `requestId` is the wire spelling carried verbatim from the dated schema, and it must be a
|
|
1788
1978
|
* real {@link JSONRPCId}: `null` is not one, and neither is an absent member, so a
|
|
1789
1979
|
* malformed frame reads as "cancels nothing" rather than as an error. Anything that is not a
|
|
1790
1980
|
* `notifications/cancelled` notification — a response, a request that happens to use the
|
|
@@ -1805,14 +1995,14 @@ function readCancelledId(message) {
|
|
|
1805
1995
|
}
|
|
1806
1996
|
/**
|
|
1807
1997
|
* Pumps a controlled serialized exchange onto a transport — every notification in order, then
|
|
1808
|
-
* the terminating response — and
|
|
1998
|
+
* the terminating response — and end the exchange however the pump leaves.
|
|
1809
1999
|
*
|
|
1810
2000
|
* @remarks
|
|
1811
2001
|
* The generator's `return` value is a message like any other on the wire: it is sent
|
|
1812
|
-
*
|
|
2002
|
+
* last and closes the exchange. Sends are awaited one at a time so the transport
|
|
1813
2003
|
* receives the sequence in the order the method produced it.
|
|
1814
2004
|
*
|
|
1815
|
-
* The first parameter is the
|
|
2005
|
+
* The first parameter is the controlled arm rather than a bare
|
|
1816
2006
|
* {@link import('./types.js').MCPTextStream}, and that is the whole point of it: this pump is
|
|
1817
2007
|
* an owner, and an owner needs a lifecycle member to discharge its obligation with. A bare
|
|
1818
2008
|
* generator has none, so an exit where nothing was cancelled — a `send` that threw two
|
|
@@ -1822,7 +2012,7 @@ function readCancelledId(message) {
|
|
|
1822
2012
|
* is a no-op for an exchange that already ended on its terminal.
|
|
1823
2013
|
*
|
|
1824
2014
|
* The `finally` is spelled explicitly rather than with `await using` because this package's
|
|
1825
|
-
* declared Node floor cannot
|
|
2015
|
+
* declared Node floor cannot parse `await using` — `target: ESNext` emits the declaration
|
|
1826
2016
|
* verbatim, and a floor engine rejects the whole module at load. The obligation discharged is
|
|
1827
2017
|
* identical either way.
|
|
1828
2018
|
*
|
|
@@ -1857,36 +2047,36 @@ async function sendStream(stream, transport) {
|
|
|
1857
2047
|
* `server.handle` already turns a malformed message into a serialized `-32700` /
|
|
1858
2048
|
* `-32600` reply and a notification into `undefined` (no reply), so this binder parses
|
|
1859
2049
|
* nothing the server would parse differently: it decodes each inbound message through
|
|
1860
|
-
* {@link decodeBoundedMessage} under `server.limit`, the
|
|
2050
|
+
* {@link decodeBoundedMessage} under `server.limit`, the server's own bound, so a message
|
|
1861
2051
|
* the server would refuse is never parsed here either and still receives its `-32700` from
|
|
1862
|
-
* the one place that words it. A
|
|
2052
|
+
* the one place that words it. A held-open reply arrives as an
|
|
1863
2053
|
* {@link import('./types.js').MCPTextStreamControllerInterface} instead of a string: this is
|
|
1864
2054
|
* the one place that pumps it, writing each notification in order and then the generator's
|
|
1865
2055
|
* returned terminating response ({@link sendStream}). A `transport.send` throw or rejection —
|
|
1866
2056
|
* mid-stream included — is caught and routed
|
|
1867
2057
|
* to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
|
|
1868
2058
|
* a listener on that event that itself throws is swallowed (the end of the line —
|
|
1869
|
-
* the caller's own bug, never this binder's). A fault raised
|
|
2059
|
+
* the caller's own bug, never this binder's). A fault raised after its own request was
|
|
1870
2060
|
* cancelled reports nothing, because a cancellation is not a fault.
|
|
1871
2061
|
*
|
|
1872
|
-
* **This binder
|
|
2062
|
+
* **This binder owns every exchange it starts, and ends each one on every exit.** It holds one
|
|
1873
2063
|
* `AbortController` per live request, keyed by the request's id and deleted whenever that
|
|
1874
2064
|
* request leaves — normally, by a throw, or by cancellation — and it supplies that signal to
|
|
1875
2065
|
* `handle` as {@link import('./types.js').MCPDispatchOptions}. These consequences follow.
|
|
1876
|
-
* An inbound `notifications/cancelled`
|
|
2066
|
+
* An inbound `notifications/cancelled` aborts the request it names, which is how the message-
|
|
1877
2067
|
* based cancellation path reaches a tool on the carriers that have one (stdio, WebSocket,
|
|
1878
|
-
* `MessagePort`); a cancelled request writes
|
|
2068
|
+
* `MessagePort`); a cancelled request writes no response, because a peer that asked for a call
|
|
1879
2069
|
* to stop is not answered by it; and the transport's `closed` signal aborts every request
|
|
1880
2070
|
* still in flight, so an exchange being pumped when the carrier dies ends with it instead of
|
|
1881
2071
|
* writing into a socket nobody is holding.
|
|
1882
2072
|
*
|
|
1883
|
-
* `listen`/`closed` are
|
|
1884
|
-
*
|
|
1885
|
-
* `bindServer` call on the
|
|
2073
|
+
* `listen`/`closed` are replace semantics (§ port contract): the returned unbind
|
|
2074
|
+
* detaches by replacing this binder's own handlers with no-ops, so a subsequent
|
|
2075
|
+
* `bindServer` call on the same transport is never double-dispatched by a stale
|
|
1886
2076
|
* subscription left behind — an unbind→rebind cycle yields exactly one reply per
|
|
1887
2077
|
* request. Unbinding is itself an owner exit: it aborts and retires every request still in
|
|
1888
2078
|
* flight before detaching, so `unbind()` then `close()` and `close()` then `unbind()` end the
|
|
1889
|
-
* same exchanges. It does
|
|
2079
|
+
* same exchanges. It does not close the transport; that remains the caller's decision.
|
|
1890
2080
|
*
|
|
1891
2081
|
* @param server - The transport-agnostic server to dispatch inbound messages over
|
|
1892
2082
|
* @param transport - The duplex channel to pipe the server over
|
|
@@ -1946,38 +2136,38 @@ function bindServer(server, transport) {
|
|
|
1946
2136
|
}
|
|
1947
2137
|
/**
|
|
1948
2138
|
* Pipes an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
|
|
1949
|
-
* inbound message is decoded and delivered onto the client's
|
|
2139
|
+
* inbound message is decoded and delivered onto the client's own transport
|
|
1950
2140
|
* (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
|
|
1951
2141
|
* client's correlated pending requests exactly as a direct reply would.
|
|
1952
2142
|
*
|
|
1953
2143
|
* @remarks
|
|
1954
2144
|
* The client's outbound writes flow through `client.transport.send` — its existing,
|
|
1955
2145
|
* unmodified request/response correlation — so `client` must have been constructed
|
|
1956
|
-
* with a {@link import('./types.js').
|
|
1957
|
-
* the
|
|
2146
|
+
* with a {@link import('./types.js').MCPMessageTransportInterface} that itself carries
|
|
2147
|
+
* the same `transport` (see {@link import('./factories.js').createDuplexClientTransport},
|
|
1958
2148
|
* the additive factory that adapts an {@link MCPTransportInterface} into that shape);
|
|
1959
2149
|
* this binder then completes the inbound half by decoding each message and pushing it
|
|
1960
2150
|
* onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
|
|
1961
2151
|
* exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
|
|
1962
|
-
* inbound message is
|
|
2152
|
+
* inbound message is dropped (total — never throws); a delivery fault is routed to
|
|
1963
2153
|
* `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
|
|
1964
|
-
*
|
|
1965
|
-
* ignored)
|
|
2154
|
+
* detaches this binder (further inbound messages and the transport's `closed` signal are
|
|
2155
|
+
* ignored) without closing the transport.
|
|
1966
2156
|
*
|
|
1967
|
-
* `listen`/`closed` are
|
|
1968
|
-
*
|
|
1969
|
-
* `bindClient` call on the
|
|
2157
|
+
* `listen`/`closed` are replace semantics (§ port contract): the returned unbind
|
|
2158
|
+
* detaches by replacing this binder's own handlers with no-ops, so a subsequent
|
|
2159
|
+
* `bindClient` call on the same transport is never double-dispatched by a stale
|
|
1970
2160
|
* subscription left behind — an unbind→rebind cycle delivers exactly one `message`
|
|
1971
2161
|
* emit per inbound reply.
|
|
1972
2162
|
*
|
|
1973
2163
|
* **This binder needs no live-request registry, and the asymmetry with {@link bindServer} is
|
|
1974
|
-
* real rather than an omission.** A server binder holds the lifetime of work it
|
|
2164
|
+
* real rather than an omission.** A server binder holds the lifetime of work it started, so an
|
|
1975
2165
|
* inbound `notifications/cancelled` has something to reach; a client binder starts no work —
|
|
1976
2166
|
* `MCPClient` already owns its pending entries and already writes the cancellation frame
|
|
1977
2167
|
* itself when a caller's `signal` aborts, on a carrier declaring `duplex`. Adding a registry
|
|
1978
|
-
* here would be a second correlation table for ids the client is already correlating, and
|
|
1979
|
-
* tables for one fact drift. The one obligation this binder does carry is delivery: a
|
|
1980
|
-
* malformed / non-JSON-RPC inbound message is
|
|
2168
|
+
* here would be a second correlation table for ids the client is already correlating, and a
|
|
2169
|
+
* pair of tables for one fact drift. The one obligation this binder does carry is delivery: a
|
|
2170
|
+
* malformed / non-JSON-RPC inbound message is dropped (total — never throws).
|
|
1981
2171
|
*
|
|
1982
2172
|
* @param client - The transport-agnostic client whose transport to deliver messages onto
|
|
1983
2173
|
* @param transport - The duplex channel to pipe the client over
|
|
@@ -2061,12 +2251,12 @@ function isMCPResultMetaObject(value) {
|
|
|
2061
2251
|
* subscription id.
|
|
2062
2252
|
*
|
|
2063
2253
|
* @remarks
|
|
2064
|
-
* The reserved key is
|
|
2065
|
-
* stream passes with no stamp at all. When the key
|
|
2254
|
+
* The reserved key is optional, so a frame delivered outside a `subscriptions/listen`
|
|
2255
|
+
* stream passes with no stamp at all. When the key is present its value must be a valid
|
|
2066
2256
|
* {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
|
|
2067
2257
|
*
|
|
2068
2258
|
* @param value - The unknown value to inspect
|
|
2069
|
-
* @returns
|
|
2259
|
+
* @returns True if the value is exact metadata whose subscription stamp, if present, is valid; false otherwise
|
|
2070
2260
|
*
|
|
2071
2261
|
* @example
|
|
2072
2262
|
* ```ts
|
|
@@ -2089,7 +2279,7 @@ function isMCPLoggingLevel(value) {
|
|
|
2089
2279
|
* Determines whether a value is standard padded base64 as required by JSON Schema `byte` format.
|
|
2090
2280
|
*
|
|
2091
2281
|
* @param value - The unknown value to inspect
|
|
2092
|
-
* @returns
|
|
2282
|
+
* @returns True if the value is an empty or completely padded standard base64 encoding; false otherwise
|
|
2093
2283
|
*/
|
|
2094
2284
|
function isStandardBase64(value) {
|
|
2095
2285
|
return (0, _orkestrel_contract.isString)(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
@@ -2105,7 +2295,7 @@ function isStandardBase64(value) {
|
|
|
2105
2295
|
* {@link MCP_PARAM_PREFIX} and must survive as an HTTP field name.
|
|
2106
2296
|
*
|
|
2107
2297
|
* @param value - The unknown value to inspect
|
|
2108
|
-
* @returns
|
|
2298
|
+
* @returns True if the value is a non-empty RFC 9110 token; false otherwise
|
|
2109
2299
|
*
|
|
2110
2300
|
* @example
|
|
2111
2301
|
* ```ts
|
|
@@ -2125,7 +2315,7 @@ function isFieldToken(value) {
|
|
|
2125
2315
|
* exactly, and the server compares it numerically.
|
|
2126
2316
|
*
|
|
2127
2317
|
* @param value - The unknown value to inspect
|
|
2128
|
-
* @returns
|
|
2318
|
+
* @returns True if the value is one of `'string'`, `'integer'`, or `'boolean'`; false otherwise
|
|
2129
2319
|
*
|
|
2130
2320
|
* @example
|
|
2131
2321
|
* ```ts
|
|
@@ -2144,7 +2334,7 @@ function isMCPHeaderPrimitive(value) {
|
|
|
2144
2334
|
* scheme allowlist. Component scanning is bounded by the input length.
|
|
2145
2335
|
*
|
|
2146
2336
|
* @param value - The unknown value to inspect
|
|
2147
|
-
* @returns
|
|
2337
|
+
* @returns True if the value is an RFC 3986 URI rather than a relative reference; false otherwise
|
|
2148
2338
|
*/
|
|
2149
2339
|
function isAbsoluteURI(value) {
|
|
2150
2340
|
if (!(0, _orkestrel_contract.isString)(value) || value.length === 0) return false;
|
|
@@ -2229,7 +2419,7 @@ function isAbsoluteURI(value) {
|
|
|
2229
2419
|
* Determines whether a value is one RFC 3339 `full-date` naming a real calendar day.
|
|
2230
2420
|
*
|
|
2231
2421
|
* @remarks
|
|
2232
|
-
* RFC 3339 §5.6 defines `date-mday` as `01-28`, `29`, `30`, or `31`
|
|
2422
|
+
* RFC 3339 §5.6 defines `date-mday` as `01-28`, `29`, `30`, or `31` based on the month and
|
|
2233
2423
|
* year, so the grammar is not satisfied by shape alone: `2026-02-30` and `2025-02-29` are
|
|
2234
2424
|
* well-formed triples that name no day, and a downstream `new Date` rolls each of them
|
|
2235
2425
|
* silently onto a different date rather than refusing it. February's length follows the
|
|
@@ -2237,10 +2427,10 @@ function isAbsoluteURI(value) {
|
|
|
2237
2427
|
*
|
|
2238
2428
|
* The check is pure integer arithmetic on the matched fields and never constructs a `Date`,
|
|
2239
2429
|
* because `Date` is exactly the component that performs the rollover this guard exists to
|
|
2240
|
-
* refuse. It is a
|
|
2430
|
+
* refuse. It is a syntax guard: no time zone, locale, calendar era, or leap second applies.
|
|
2241
2431
|
*
|
|
2242
2432
|
* @param value - The unknown value to inspect
|
|
2243
|
-
* @returns
|
|
2433
|
+
* @returns True if the value is an RFC 3339 `full-date` for a day that exists; false otherwise
|
|
2244
2434
|
*
|
|
2245
2435
|
* @example
|
|
2246
2436
|
* ```ts
|
|
@@ -2273,7 +2463,7 @@ function isRFC3339Date(value) {
|
|
|
2273
2463
|
* second.
|
|
2274
2464
|
*
|
|
2275
2465
|
* @param value - The unknown value to inspect
|
|
2276
|
-
* @returns
|
|
2466
|
+
* @returns True if the value is an RFC 3339 `date-time` for a day that exists; false otherwise
|
|
2277
2467
|
*
|
|
2278
2468
|
* @example
|
|
2279
2469
|
* ```ts
|
|
@@ -2291,7 +2481,7 @@ function isRFC3339DateTime(value) {
|
|
|
2291
2481
|
* Determines whether a value is one exact finite MCP progress payload.
|
|
2292
2482
|
*
|
|
2293
2483
|
* @param value - The unknown value to inspect
|
|
2294
|
-
* @returns
|
|
2484
|
+
* @returns True if required progress and optional total/message fields match the dated schema; false otherwise
|
|
2295
2485
|
*/
|
|
2296
2486
|
function isMCPProgress(value) {
|
|
2297
2487
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2310,7 +2500,7 @@ function isMCPProgress(value) {
|
|
|
2310
2500
|
* Determines whether a value carries valid dated-schema MCP content annotations.
|
|
2311
2501
|
*
|
|
2312
2502
|
* @param value - The unknown value to inspect
|
|
2313
|
-
* @returns
|
|
2503
|
+
* @returns True if the value is valid MCP annotations; false otherwise
|
|
2314
2504
|
*/
|
|
2315
2505
|
function isMCPAnnotations(value) {
|
|
2316
2506
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2331,7 +2521,7 @@ function isMCPAnnotations(value) {
|
|
|
2331
2521
|
* Determines whether a value is one exact dated-schema MCP icon.
|
|
2332
2522
|
*
|
|
2333
2523
|
* @param value - The unknown value to inspect
|
|
2334
|
-
* @returns
|
|
2524
|
+
* @returns True if the value is a valid MCP icon; false otherwise
|
|
2335
2525
|
*/
|
|
2336
2526
|
function isMCPIcon(value) {
|
|
2337
2527
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2422,7 +2612,7 @@ function isMCPServerCapabilities(value) {
|
|
|
2422
2612
|
* Determines whether a value is embedded textual MCP resource contents.
|
|
2423
2613
|
*
|
|
2424
2614
|
* @param value - The unknown value to inspect
|
|
2425
|
-
* @returns
|
|
2615
|
+
* @returns True if the value is embedded textual resource contents; false otherwise
|
|
2426
2616
|
*/
|
|
2427
2617
|
function isMCPTextResource(value) {
|
|
2428
2618
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2441,7 +2631,7 @@ function isMCPTextResource(value) {
|
|
|
2441
2631
|
* Determines whether a value is embedded blob MCP resource contents.
|
|
2442
2632
|
*
|
|
2443
2633
|
* @param value - The unknown value to inspect
|
|
2444
|
-
* @returns
|
|
2634
|
+
* @returns True if the value is embedded blob resource contents; false otherwise
|
|
2445
2635
|
*/
|
|
2446
2636
|
function isMCPBlobResource(value) {
|
|
2447
2637
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2460,7 +2650,7 @@ function isMCPBlobResource(value) {
|
|
|
2460
2650
|
* Determines whether a value is one `resources/list` descriptor.
|
|
2461
2651
|
*
|
|
2462
2652
|
* @param value - The unknown value to inspect
|
|
2463
|
-
* @returns
|
|
2653
|
+
* @returns True if the value is a valid resource descriptor; false otherwise
|
|
2464
2654
|
*/
|
|
2465
2655
|
function isMCPResource(value) {
|
|
2466
2656
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2482,7 +2672,7 @@ function isMCPResource(value) {
|
|
|
2482
2672
|
* level belong to the consumer-supplied resource manager; this package projects the string.
|
|
2483
2673
|
*
|
|
2484
2674
|
* @param value - The unknown value to inspect
|
|
2485
|
-
* @returns
|
|
2675
|
+
* @returns True if the value is a valid resource-template descriptor; false otherwise
|
|
2486
2676
|
*/
|
|
2487
2677
|
function isMCPResourceTemplate(value) {
|
|
2488
2678
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2499,7 +2689,7 @@ function isMCPResourceTemplate(value) {
|
|
|
2499
2689
|
* Determines whether a value is structurally discriminated resource contents.
|
|
2500
2690
|
*
|
|
2501
2691
|
* @param value - The unknown value to inspect
|
|
2502
|
-
* @returns
|
|
2692
|
+
* @returns True if exactly one of `text` and `blob` is present and valid; false otherwise
|
|
2503
2693
|
*/
|
|
2504
2694
|
function isMCPResourceContents(value) {
|
|
2505
2695
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2513,7 +2703,7 @@ function isMCPResourceContents(value) {
|
|
|
2513
2703
|
* Determines whether a value carries the shared optional pagination cursor.
|
|
2514
2704
|
*
|
|
2515
2705
|
* @param value - The unknown value to inspect
|
|
2516
|
-
* @returns
|
|
2706
|
+
* @returns True if a present `cursor` is a string; false otherwise
|
|
2517
2707
|
*/
|
|
2518
2708
|
function isMCPPaginationParams(value) {
|
|
2519
2709
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2523,7 +2713,7 @@ function isMCPPaginationParams(value) {
|
|
|
2523
2713
|
* Determines whether a value is one consumer-owned resource page.
|
|
2524
2714
|
*
|
|
2525
2715
|
* @param value - The unknown value to inspect
|
|
2526
|
-
* @returns
|
|
2716
|
+
* @returns True if the resources and optional following cursor are valid; false otherwise
|
|
2527
2717
|
*/
|
|
2528
2718
|
function isMCPResourcePage(value) {
|
|
2529
2719
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2536,7 +2726,7 @@ function isMCPResourcePage(value) {
|
|
|
2536
2726
|
* Determines whether a value is one consumer-owned resource-template page.
|
|
2537
2727
|
*
|
|
2538
2728
|
* @param value - The unknown value to inspect
|
|
2539
|
-
* @returns
|
|
2729
|
+
* @returns True if the templates and optional following cursor are valid; false otherwise
|
|
2540
2730
|
*/
|
|
2541
2731
|
function isMCPResourceTemplatePage(value) {
|
|
2542
2732
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2549,7 +2739,7 @@ function isMCPResourceTemplatePage(value) {
|
|
|
2549
2739
|
* Determines whether a value is a string-valued MCP argument record.
|
|
2550
2740
|
*
|
|
2551
2741
|
* @param value - The unknown value to inspect
|
|
2552
|
-
* @returns
|
|
2742
|
+
* @returns True if every own argument value is a string; false otherwise
|
|
2553
2743
|
*/
|
|
2554
2744
|
function isMCPStringArguments(value) {
|
|
2555
2745
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2559,7 +2749,7 @@ function isMCPStringArguments(value) {
|
|
|
2559
2749
|
* Determines whether a value is one prompt argument descriptor.
|
|
2560
2750
|
*
|
|
2561
2751
|
* @param value - The unknown value to inspect
|
|
2562
|
-
* @returns
|
|
2752
|
+
* @returns True if the prompt argument descriptor is valid; false otherwise
|
|
2563
2753
|
*/
|
|
2564
2754
|
function isMCPPromptArgument(value) {
|
|
2565
2755
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2571,7 +2761,7 @@ function isMCPPromptArgument(value) {
|
|
|
2571
2761
|
* Determines whether a value is one `prompts/list` descriptor.
|
|
2572
2762
|
*
|
|
2573
2763
|
* @param value - The unknown value to inspect
|
|
2574
|
-
* @returns
|
|
2764
|
+
* @returns True if the prompt descriptor is valid; false otherwise
|
|
2575
2765
|
*/
|
|
2576
2766
|
function isMCPPrompt(value) {
|
|
2577
2767
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2585,7 +2775,7 @@ function isMCPPrompt(value) {
|
|
|
2585
2775
|
* Determines whether a value is one prompt message with existing rich content.
|
|
2586
2776
|
*
|
|
2587
2777
|
* @param value - The unknown value to inspect
|
|
2588
|
-
* @returns
|
|
2778
|
+
* @returns True if the role and content are valid; false otherwise
|
|
2589
2779
|
*/
|
|
2590
2780
|
function isMCPPromptMessage(value) {
|
|
2591
2781
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2595,7 +2785,7 @@ function isMCPPromptMessage(value) {
|
|
|
2595
2785
|
* Determines whether a value is one consumer-owned prompt page.
|
|
2596
2786
|
*
|
|
2597
2787
|
* @param value - The unknown value to inspect
|
|
2598
|
-
* @returns
|
|
2788
|
+
* @returns True if the prompts and optional following cursor are valid; false otherwise
|
|
2599
2789
|
*/
|
|
2600
2790
|
function isMCPPromptPage(value) {
|
|
2601
2791
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2608,7 +2798,7 @@ function isMCPPromptPage(value) {
|
|
|
2608
2798
|
* Determines whether a value is one complete `prompts/get` result.
|
|
2609
2799
|
*
|
|
2610
2800
|
* @param value - The unknown value to inspect
|
|
2611
|
-
* @returns
|
|
2801
|
+
* @returns True if the prompt result and all messages are valid; false otherwise
|
|
2612
2802
|
*/
|
|
2613
2803
|
function isMCPPromptGetResult(value) {
|
|
2614
2804
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2621,7 +2811,7 @@ function isMCPPromptGetResult(value) {
|
|
|
2621
2811
|
* Determines whether a value is a prompt or resource-template completion reference.
|
|
2622
2812
|
*
|
|
2623
2813
|
* @param value - The unknown value to inspect
|
|
2624
|
-
* @returns
|
|
2814
|
+
* @returns True if the discriminated reference is valid; false otherwise
|
|
2625
2815
|
*/
|
|
2626
2816
|
function isMCPCompletionReference(value) {
|
|
2627
2817
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2633,7 +2823,7 @@ function isMCPCompletionReference(value) {
|
|
|
2633
2823
|
* Determines whether a value is one `completion/complete` parameter object.
|
|
2634
2824
|
*
|
|
2635
2825
|
* @param value - The unknown value to inspect
|
|
2636
|
-
* @returns
|
|
2826
|
+
* @returns True if its reference, fragment, and optional string context are valid; false otherwise
|
|
2637
2827
|
*/
|
|
2638
2828
|
function isMCPCompletionParams(value) {
|
|
2639
2829
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2652,7 +2842,7 @@ function isMCPCompletionParams(value) {
|
|
|
2652
2842
|
* Determines whether a value is one host-produced completion candidate set.
|
|
2653
2843
|
*
|
|
2654
2844
|
* @param value - The unknown value to inspect
|
|
2655
|
-
* @returns
|
|
2845
|
+
* @returns True if its candidates and optional result facts are valid; false otherwise
|
|
2656
2846
|
*/
|
|
2657
2847
|
function isMCPCompletion(value) {
|
|
2658
2848
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2666,7 +2856,7 @@ function isMCPCompletion(value) {
|
|
|
2666
2856
|
* Determines whether a value is one complete, capped `completion/complete` result.
|
|
2667
2857
|
*
|
|
2668
2858
|
* @param value - The unknown value to inspect
|
|
2669
|
-
* @returns
|
|
2859
|
+
* @returns True if the result is complete and carries at most 100 candidates; false otherwise
|
|
2670
2860
|
*/
|
|
2671
2861
|
function isMCPCompletionResult(value) {
|
|
2672
2862
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2678,7 +2868,7 @@ function isMCPCompletionResult(value) {
|
|
|
2678
2868
|
* Determines whether a value is one exact dated-schema MCP tool content block.
|
|
2679
2869
|
*
|
|
2680
2870
|
* @param value - The unknown value to inspect
|
|
2681
|
-
* @returns
|
|
2871
|
+
* @returns True if the value is valid MCP content; false otherwise
|
|
2682
2872
|
*/
|
|
2683
2873
|
function isMCPContent(value) {
|
|
2684
2874
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2711,7 +2901,7 @@ function isMCPContent(value) {
|
|
|
2711
2901
|
*
|
|
2712
2902
|
* @remarks
|
|
2713
2903
|
* The open contract's guard: a record carrying a string `resultType` and, when
|
|
2714
|
-
* present, exact result metadata. It deliberately does
|
|
2904
|
+
* present, exact result metadata. It deliberately does not narrow `resultType` to a
|
|
2715
2905
|
* known value, because the dated schema keeps adding them — a caller that needs a
|
|
2716
2906
|
* specific result uses that result's own guard, which narrows to its literal.
|
|
2717
2907
|
* Mutually exclusive with {@link isMCPLegacyResult} on every input: this one needs
|
|
@@ -2719,7 +2909,7 @@ function isMCPContent(value) {
|
|
|
2719
2909
|
* input.
|
|
2720
2910
|
*
|
|
2721
2911
|
* @param value - The unknown value to inspect
|
|
2722
|
-
* @returns
|
|
2912
|
+
* @returns True if the value is a modern result; false otherwise
|
|
2723
2913
|
*
|
|
2724
2914
|
* @example
|
|
2725
2915
|
* ```ts
|
|
@@ -2745,7 +2935,7 @@ function isMCPResult(value) {
|
|
|
2745
2935
|
* hostile input.
|
|
2746
2936
|
*
|
|
2747
2937
|
* @param value - The unknown value to inspect
|
|
2748
|
-
* @returns
|
|
2938
|
+
* @returns True if the value is a legacy result; false otherwise
|
|
2749
2939
|
*
|
|
2750
2940
|
* @example
|
|
2751
2941
|
* ```ts
|
|
@@ -2761,7 +2951,7 @@ function isMCPLegacyResult(value) {
|
|
|
2761
2951
|
* Determines whether a value is a complete modern MCP tool result.
|
|
2762
2952
|
*
|
|
2763
2953
|
* @param value - The unknown value to inspect
|
|
2764
|
-
* @returns
|
|
2954
|
+
* @returns True if the value is a complete MCP call result; false otherwise
|
|
2765
2955
|
*/
|
|
2766
2956
|
function isMCPCallResult(value) {
|
|
2767
2957
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -2786,10 +2976,10 @@ function isMCPCallResult(value) {
|
|
|
2786
2976
|
* proof: this is what stands between a manager that answers a numeric `taskId` and a
|
|
2787
2977
|
* client that would receive one. `ttlMs` accepts `null` because the schema uses it to
|
|
2788
2978
|
* mean "no expiry", which is distinct from an absent field, and both durations must be
|
|
2789
|
-
*
|
|
2979
|
+
* integer milliseconds because the schema formats them `int`.
|
|
2790
2980
|
*
|
|
2791
2981
|
* @param value - The unknown value to inspect
|
|
2792
|
-
* @returns
|
|
2982
|
+
* @returns True if the value is a well-formed `resultType: 'task'` result; false otherwise
|
|
2793
2983
|
*
|
|
2794
2984
|
* @example
|
|
2795
2985
|
* ```ts
|
|
@@ -2816,7 +3006,7 @@ function isMCPTaskResult(value) {
|
|
|
2816
3006
|
* Determines whether a value is one of the extension's task lifecycle states.
|
|
2817
3007
|
*
|
|
2818
3008
|
* @param value - The unknown value to inspect
|
|
2819
|
-
* @returns
|
|
3009
|
+
* @returns True if the value is an {@link MCPTaskStatus}; false otherwise
|
|
2820
3010
|
*
|
|
2821
3011
|
* @example
|
|
2822
3012
|
* ```ts
|
|
@@ -2837,7 +3027,7 @@ function isMCPTaskStatus(value) {
|
|
|
2837
3027
|
* the requests to answer, `completed` owns the deferred call's result, `failed` owns the
|
|
2838
3028
|
* JSON-RPC error that ended it, and `working` / `cancelled` own nothing further.
|
|
2839
3029
|
*
|
|
2840
|
-
* A `completed` task's `result` is checked as an
|
|
3030
|
+
* A `completed` task's `result` is checked as an object and no further. The schema declares
|
|
2841
3031
|
* it an open record, so its contents belong to whichever method was deferred; a guard that
|
|
2842
3032
|
* demanded a protocol result here would refuse payloads the extension permits.
|
|
2843
3033
|
* `ttlMs` and `pollIntervalMs` are integer milliseconds, per the schema's `int` formats.
|
|
@@ -2847,7 +3037,7 @@ function isMCPTaskStatus(value) {
|
|
|
2847
3037
|
* What is checked is what this package publishes as the contract.
|
|
2848
3038
|
*
|
|
2849
3039
|
* @param value - The unknown value to inspect
|
|
2850
|
-
* @returns
|
|
3040
|
+
* @returns True if the value is a well-formed {@link MCPTaskDetail}; false otherwise
|
|
2851
3041
|
*
|
|
2852
3042
|
* @example
|
|
2853
3043
|
* ```ts
|
|
@@ -2879,18 +3069,18 @@ function isMCPTaskDetail(value) {
|
|
|
2879
3069
|
* Determines whether a value is the wire answer to `tasks/get`.
|
|
2880
3070
|
*
|
|
2881
3071
|
* @remarks
|
|
2882
|
-
* {@link isMCPTaskDetail} plus the stamp the
|
|
3072
|
+
* {@link isMCPTaskDetail} plus the stamp the method owes. The schema types a `tasks/get`
|
|
2883
3073
|
* reply as the detail intersected with the standard result, so `resultType: 'complete'` is
|
|
2884
3074
|
* part of the answer rather than decoration on it — and an unstamped payload, or one
|
|
2885
3075
|
* carrying the creation answer's `resultType: 'task'`, is a peer answering some other
|
|
2886
|
-
* shape. Use this guard wherever a `tasks/get`
|
|
3076
|
+
* shape. Use this guard wherever a `tasks/get` reply is read; use
|
|
2887
3077
|
* {@link isMCPTaskDetail} wherever a consumer's manager answers directly.
|
|
2888
3078
|
*
|
|
2889
3079
|
* `_meta` is checked only when present, and only as result metadata: the server identity a
|
|
2890
3080
|
* peer stamps there is the peer's to write.
|
|
2891
3081
|
*
|
|
2892
3082
|
* @param value - The unknown value to inspect
|
|
2893
|
-
* @returns
|
|
3083
|
+
* @returns True if the value is a well-formed {@link MCPTaskDetailResult}; false otherwise
|
|
2894
3084
|
*
|
|
2895
3085
|
* @example
|
|
2896
3086
|
* ```ts
|
|
@@ -2913,18 +3103,18 @@ function isMCPTaskDetailResult(value) {
|
|
|
2913
3103
|
* Determines whether a value is a `notifications/tasks` frame carrying a task snapshot.
|
|
2914
3104
|
*
|
|
2915
3105
|
* @remarks
|
|
2916
|
-
* The
|
|
3106
|
+
* The admission guard for a task transition: a subscription producer is consumer-written,
|
|
2917
3107
|
* so the frame it hands over is foreign input, and this is what stands between a mutated
|
|
2918
3108
|
* or half-built snapshot and a subscribed client. Both halves are checked — the method
|
|
2919
3109
|
* literal the extension fixes, and params that hold together as an
|
|
2920
3110
|
* {@link MCPTaskDetail} — because either alone admits a frame the other rejects.
|
|
2921
3111
|
*
|
|
2922
|
-
* `_meta` is checked for
|
|
2923
|
-
* stamp is the
|
|
3112
|
+
* `_meta` is checked for shape when present and nothing more. The reserved subscription
|
|
3113
|
+
* stamp is the server's to write, after this guard admits the frame and the matcher agrees
|
|
2924
3114
|
* to it, so a guard that demanded the stamp would refuse every frame a producer emits.
|
|
2925
3115
|
*
|
|
2926
3116
|
* @param value - The unknown value to inspect
|
|
2927
|
-
* @returns
|
|
3117
|
+
* @returns True if the value is a well-formed `notifications/tasks` notification; false otherwise
|
|
2928
3118
|
*
|
|
2929
3119
|
* @example
|
|
2930
3120
|
* ```ts
|
|
@@ -2952,7 +3142,7 @@ function isMCPTaskNotification(value) {
|
|
|
2952
3142
|
*
|
|
2953
3143
|
* @param value - The unknown value to inspect
|
|
2954
3144
|
* @param bytes - The maximum accepted encoded bytes
|
|
2955
|
-
* @returns `
|
|
3145
|
+
* @returns True if `value` is a string whose UTF-8 representation fits the bound; false otherwise
|
|
2956
3146
|
*
|
|
2957
3147
|
* @example
|
|
2958
3148
|
* ```ts
|
|
@@ -2988,7 +3178,7 @@ function isBoundedString(value, bytes) {
|
|
|
2988
3178
|
*
|
|
2989
3179
|
* @param value - The unknown value to inspect
|
|
2990
3180
|
* @param limits - Serialized byte, optional key, and nesting-depth bounds
|
|
2991
|
-
* @returns `
|
|
3181
|
+
* @returns True if `value` is safe JSON satisfying every bound; false otherwise
|
|
2992
3182
|
*
|
|
2993
3183
|
* @example
|
|
2994
3184
|
* ```ts
|
|
@@ -3010,7 +3200,7 @@ function isBoundedJSON(value, limits) {
|
|
|
3010
3200
|
* no minimum length. Total: any other input returns `false`.
|
|
3011
3201
|
*
|
|
3012
3202
|
* @param value - The already-parsed value to test
|
|
3013
|
-
* @returns
|
|
3203
|
+
* @returns True if `value` is a string or a finite integer; false otherwise
|
|
3014
3204
|
*
|
|
3015
3205
|
* @example
|
|
3016
3206
|
* ```ts
|
|
@@ -3028,7 +3218,7 @@ function isJSONRPCId(value) {
|
|
|
3028
3218
|
* Determines whether a value is a supported {@link MCPVersion}.
|
|
3029
3219
|
*
|
|
3030
3220
|
* @param value - The unknown value to inspect
|
|
3031
|
-
* @returns
|
|
3221
|
+
* @returns True if the value is one of {@link SUPPORTED_MCP_VERSIONS}; false otherwise
|
|
3032
3222
|
*/
|
|
3033
3223
|
function isMCPVersion(value) {
|
|
3034
3224
|
return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MCP_VERSIONS.some((version) => version === value);
|
|
@@ -3037,7 +3227,7 @@ function isMCPVersion(value) {
|
|
|
3037
3227
|
* Determines whether a value is a modern protocol revision accepted by a bare server.
|
|
3038
3228
|
*
|
|
3039
3229
|
* @param value - The unknown value to inspect
|
|
3040
|
-
* @returns
|
|
3230
|
+
* @returns True if the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}; false otherwise
|
|
3041
3231
|
*/
|
|
3042
3232
|
function isMCPModernVersion(value) {
|
|
3043
3233
|
return (0, _orkestrel_contract.isString)(value) && SUPPORTED_MODERN_PROTOCOL_VERSIONS.some((version) => version === value);
|
|
@@ -3046,7 +3236,7 @@ function isMCPModernVersion(value) {
|
|
|
3046
3236
|
* Determines whether a value is a revision accepted by the optional legacy decorator.
|
|
3047
3237
|
*
|
|
3048
3238
|
* @param value - The unknown value to inspect
|
|
3049
|
-
* @returns
|
|
3239
|
+
* @returns True if the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}; false otherwise
|
|
3050
3240
|
*/
|
|
3051
3241
|
function isMCPLegacyVersion(value) {
|
|
3052
3242
|
return (0, _orkestrel_contract.isString)(value) && SUPPORTED_LEGACY_PROTOCOL_VERSIONS.some((version) => version === value);
|
|
@@ -3065,7 +3255,7 @@ function isMCPLegacyVersion(value) {
|
|
|
3065
3255
|
* the caller asked for.
|
|
3066
3256
|
*
|
|
3067
3257
|
* @param value - The unknown value to inspect
|
|
3068
|
-
* @returns
|
|
3258
|
+
* @returns True if every recognized filter field has its protocol shape; false otherwise
|
|
3069
3259
|
*/
|
|
3070
3260
|
function isMCPSubscriptionFilter(value) {
|
|
3071
3261
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -3086,7 +3276,7 @@ function isMCPSubscriptionFilter(value) {
|
|
|
3086
3276
|
* Determines whether a value is a graceful `subscriptions/listen` result.
|
|
3087
3277
|
*
|
|
3088
3278
|
* @param value - The unknown value to inspect
|
|
3089
|
-
* @returns
|
|
3279
|
+
* @returns True if the result is complete and carries a valid subscription id; false otherwise
|
|
3090
3280
|
*/
|
|
3091
3281
|
function isMCPSubscriptionResult(value) {
|
|
3092
3282
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -3098,7 +3288,7 @@ function isMCPSubscriptionResult(value) {
|
|
|
3098
3288
|
* Determines whether a value is one restricted primitive form-elicitation schema.
|
|
3099
3289
|
*
|
|
3100
3290
|
* @param value - The unknown value to inspect
|
|
3101
|
-
* @returns `
|
|
3291
|
+
* @returns True if `value` is a supported boolean, numeric, string, or string-array schema; false otherwise
|
|
3102
3292
|
*
|
|
3103
3293
|
* @example
|
|
3104
3294
|
* ```ts
|
|
@@ -3161,7 +3351,7 @@ function isMCPElicitFieldSchema(value) {
|
|
|
3161
3351
|
* an unrecognized top-level annotation is data rather than a rejection.
|
|
3162
3352
|
*
|
|
3163
3353
|
* @param value - The unknown value to inspect
|
|
3164
|
-
* @returns
|
|
3354
|
+
* @returns True if `value` is a restricted object schema of supported field schemas; false otherwise
|
|
3165
3355
|
*
|
|
3166
3356
|
* @example
|
|
3167
3357
|
* ```ts
|
|
@@ -3188,7 +3378,7 @@ function isMCPElicitSchema(value) {
|
|
|
3188
3378
|
* Determines whether a value is a form-mode elicitation parameter object.
|
|
3189
3379
|
*
|
|
3190
3380
|
* @param value - The unknown value to inspect
|
|
3191
|
-
* @returns
|
|
3381
|
+
* @returns True if `value` has the restricted form elicitation shape; false otherwise
|
|
3192
3382
|
*
|
|
3193
3383
|
* @example
|
|
3194
3384
|
* ```ts
|
|
@@ -3214,7 +3404,7 @@ function isMCPElicitForm(value) {
|
|
|
3214
3404
|
* Determines whether a value is a URL-mode elicitation parameter object.
|
|
3215
3405
|
*
|
|
3216
3406
|
* @param value - The unknown value to inspect
|
|
3217
|
-
* @returns
|
|
3407
|
+
* @returns True if `value` has the URL elicitation shape; false otherwise
|
|
3218
3408
|
*
|
|
3219
3409
|
* @example
|
|
3220
3410
|
* ```ts
|
|
@@ -3235,7 +3425,7 @@ function isMCPElicitURL(value) {
|
|
|
3235
3425
|
* Determines whether a value is an embedded `elicitation/create` request.
|
|
3236
3426
|
*
|
|
3237
3427
|
* @param value - The unknown value to inspect
|
|
3238
|
-
* @returns
|
|
3428
|
+
* @returns True if `value` is a form- or URL-mode elicitation request; false otherwise
|
|
3239
3429
|
*
|
|
3240
3430
|
* @example
|
|
3241
3431
|
* ```ts
|
|
@@ -3260,7 +3450,7 @@ function isMCPElicitRequest(value) {
|
|
|
3260
3450
|
* Determines whether a value is one legal embedded multi-round-trip request.
|
|
3261
3451
|
*
|
|
3262
3452
|
* @param value - The unknown value to inspect
|
|
3263
|
-
* @returns `
|
|
3453
|
+
* @returns True if `value` is an embedded elicitation, sampling, or roots request; false otherwise
|
|
3264
3454
|
*
|
|
3265
3455
|
* @example
|
|
3266
3456
|
* ```ts
|
|
@@ -3284,7 +3474,7 @@ function isMCPInputRequest(value) {
|
|
|
3284
3474
|
* Determines whether a value is a consumer-keyed map of embedded input requests.
|
|
3285
3475
|
*
|
|
3286
3476
|
* @param value - The unknown value to inspect
|
|
3287
|
-
* @returns
|
|
3477
|
+
* @returns True if every own value is a legal {@link MCPInputRequest}; false otherwise
|
|
3288
3478
|
*
|
|
3289
3479
|
* @example
|
|
3290
3480
|
* ```ts
|
|
@@ -3304,7 +3494,7 @@ function isMCPInputRequestMap(value) {
|
|
|
3304
3494
|
* Determines whether a value is one elicitation response.
|
|
3305
3495
|
*
|
|
3306
3496
|
* @param value - The unknown value to inspect
|
|
3307
|
-
* @returns
|
|
3497
|
+
* @returns True if action/content have the protocol shape; false otherwise
|
|
3308
3498
|
*
|
|
3309
3499
|
* @example
|
|
3310
3500
|
* ```ts
|
|
@@ -3331,13 +3521,13 @@ function isMCPElicitResult(value) {
|
|
|
3331
3521
|
* Determines whether accepted elicitation content satisfies the exact schema that was issued.
|
|
3332
3522
|
*
|
|
3333
3523
|
* @remarks
|
|
3334
|
-
* {@link isMCPElicitResult} says a response has the
|
|
3335
|
-
* response answers the
|
|
3524
|
+
* {@link isMCPElicitResult} says a response has the shape of a response; this says the
|
|
3525
|
+
* response answers the question that was asked. A server that protects the schema it issued
|
|
3336
3526
|
* and then never enforces it has bought nothing, so this guard closes that gap: it is what
|
|
3337
3527
|
* turns a bound schema into a checked one.
|
|
3338
3528
|
*
|
|
3339
3529
|
* Every own value must be one {@link MCPElicitValue} — a string, a finite number, a boolean,
|
|
3340
|
-
* or an array of strings. A value whose name is
|
|
3530
|
+
* or an array of strings. A value whose name is declared in `schema.properties` must in
|
|
3341
3531
|
* addition satisfy that field's schema: `integer` rejects a fraction, `minimum` / `maximum`
|
|
3342
3532
|
* bound a number, `minLength` / `maxLength` bound a string by code points, `enum` and `oneOf`
|
|
3343
3533
|
* bound it to a declared member, `format` is enforced (`uri` by {@link isAbsoluteURI}, `email`
|
|
@@ -3346,15 +3536,15 @@ function isMCPElicitResult(value) {
|
|
|
3346
3536
|
* `maxItems` with every entry drawn from its `items.enum` or `items.anyOf`. Every name listed
|
|
3347
3537
|
* in `schema.required` must be present.
|
|
3348
3538
|
*
|
|
3349
|
-
* An
|
|
3539
|
+
* An undeclared property remains valid: the restricted schema is open by default, so a client
|
|
3350
3540
|
* that answers more than it was asked is not refused for it. A `schema` that is not itself a
|
|
3351
|
-
* valid {@link MCPElicitSchema} admits
|
|
3541
|
+
* valid {@link MCPElicitSchema} admits nothing — an unenforceable schema is never a permissive
|
|
3352
3542
|
* one — which is why `schema` is accepted as `unknown` and checked rather than trusted. Total
|
|
3353
3543
|
* over hostile content and hostile schemas alike.
|
|
3354
3544
|
*
|
|
3355
3545
|
* @param value - The accepted response content to check
|
|
3356
3546
|
* @param schema - The exact {@link MCPElicitSchema} that was issued with the elicitation
|
|
3357
|
-
* @returns
|
|
3547
|
+
* @returns True if every declared and undeclared value is legal under `schema`; false otherwise
|
|
3358
3548
|
*
|
|
3359
3549
|
* @example
|
|
3360
3550
|
* ```ts
|
|
@@ -3443,7 +3633,7 @@ function isElicitContent(value, schema) {
|
|
|
3443
3633
|
* including a URL-mode elicitation's `url`. Total over hostile input.
|
|
3444
3634
|
*
|
|
3445
3635
|
* @param value - The unknown value to inspect
|
|
3446
|
-
* @returns
|
|
3636
|
+
* @returns True if `value` carries an absolute `uri` and an optional string `name`; false otherwise
|
|
3447
3637
|
*
|
|
3448
3638
|
* @example
|
|
3449
3639
|
* ```ts
|
|
@@ -3473,7 +3663,7 @@ function isMCPRoot(value) {
|
|
|
3473
3663
|
* {@link isMCPRoot}. Total over hostile input.
|
|
3474
3664
|
*
|
|
3475
3665
|
* @param value - The unknown value to inspect
|
|
3476
|
-
* @returns
|
|
3666
|
+
* @returns True if `value` carries an array of valid roots; false otherwise
|
|
3477
3667
|
*
|
|
3478
3668
|
* @example
|
|
3479
3669
|
* ```ts
|
|
@@ -3506,7 +3696,7 @@ function isMCPRootResult(value) {
|
|
|
3506
3696
|
* input.
|
|
3507
3697
|
*
|
|
3508
3698
|
* @param value - The unknown value to inspect
|
|
3509
|
-
* @returns
|
|
3699
|
+
* @returns True if `value` is one legal sampling content block; false otherwise
|
|
3510
3700
|
*
|
|
3511
3701
|
* @example
|
|
3512
3702
|
* ```ts
|
|
@@ -3541,13 +3731,14 @@ function isMCPSampleContent(value) {
|
|
|
3541
3731
|
*
|
|
3542
3732
|
* @remarks
|
|
3543
3733
|
* The schema's `CreateMessageResult` types `content` as an `anyOf` over one
|
|
3544
|
-
* {@link isMCPSampleContent} block or an
|
|
3734
|
+
* {@link isMCPSampleContent} block or an array of them, so both are admitted here: a
|
|
3545
3735
|
* tool-using model answers with `tool_use` and `tool_result` blocks, and a model answering in
|
|
3546
3736
|
* several parts answers with the array. `stopReason` stays an open string because the schema
|
|
3547
|
-
* names
|
|
3737
|
+
* names `endTurn`, `stopSequence`, `maxTokens`, and `toolUse` and permits any other a provider
|
|
3738
|
+
* reports. Total over hostile input.
|
|
3548
3739
|
*
|
|
3549
3740
|
* @param value - The unknown value to inspect
|
|
3550
|
-
* @returns
|
|
3741
|
+
* @returns True if `value` has the sampling-completion shape; false otherwise
|
|
3551
3742
|
*
|
|
3552
3743
|
* @example
|
|
3553
3744
|
* ```ts
|
|
@@ -3585,17 +3776,17 @@ function isMCPSampleResult(value) {
|
|
|
3585
3776
|
* Determines whether a response answers the exact embedded request that was issued.
|
|
3586
3777
|
*
|
|
3587
3778
|
* @remarks
|
|
3588
|
-
* A response carries no `method` of its own, so the
|
|
3779
|
+
* A response carries no `method` of its own, so the issued request selects which arm applies
|
|
3589
3780
|
* — the same way {@link isElicitContent} takes the issued schema rather than trusting the
|
|
3590
3781
|
* content to describe itself. A form elicitation is checked twice: once for the response
|
|
3591
3782
|
* shape and once, on `accept`, for the content against the schema that round issued. A
|
|
3592
3783
|
* URL-mode elicitation issues no schema, so only the shape is checked. A request this
|
|
3593
|
-
* package cannot recognize admits
|
|
3784
|
+
* package cannot recognize admits nothing, because an unrecognized question has no correct
|
|
3594
3785
|
* answer. Total over hostile responses and hostile requests alike.
|
|
3595
3786
|
*
|
|
3596
3787
|
* @param value - The client's answer to check
|
|
3597
3788
|
* @param request - The exact {@link MCPInputRequest} that was issued under the same key
|
|
3598
|
-
* @returns
|
|
3789
|
+
* @returns True if the answer is legal for that request; false otherwise
|
|
3599
3790
|
*
|
|
3600
3791
|
* @example
|
|
3601
3792
|
* ```ts
|
|
@@ -3623,7 +3814,7 @@ function isMCPInputResponse(value, request) {
|
|
|
3623
3814
|
* both must be present and valid. Total over hostile input.
|
|
3624
3815
|
*
|
|
3625
3816
|
* @param value - The unknown value to inspect
|
|
3626
|
-
* @returns
|
|
3817
|
+
* @returns True if `value` is a valid input-required result; false otherwise
|
|
3627
3818
|
*
|
|
3628
3819
|
* @example
|
|
3629
3820
|
* ```ts
|
|
@@ -3653,14 +3844,14 @@ function isMCPInputResult(value) {
|
|
|
3653
3844
|
*
|
|
3654
3845
|
* @remarks
|
|
3655
3846
|
* A request is a record with `jsonrpc === '2.0'`, a string `method`, and an `id`
|
|
3656
|
-
* that {@link isJSONRPCId} accepts. An id-less call is
|
|
3847
|
+
* that {@link isJSONRPCId} accepts. An id-less call is not a request — it is a
|
|
3657
3848
|
* {@link JSONRPCNotification}, which {@link isJSONRPCNotification} answers for. The
|
|
3658
3849
|
* guards are mutually exclusive on every input: this one requires a valid `id`
|
|
3659
3850
|
* value, that one requires no own `id` member at all. `params`, when present, must
|
|
3660
3851
|
* be a record. Total: any other input returns `false`.
|
|
3661
3852
|
*
|
|
3662
3853
|
* @param value - The already-parsed value to test
|
|
3663
|
-
* @returns
|
|
3854
|
+
* @returns True if `value` is a valid JSON-RPC request; false otherwise
|
|
3664
3855
|
*
|
|
3665
3856
|
* @example
|
|
3666
3857
|
* ```ts
|
|
@@ -3682,12 +3873,12 @@ function isJSONRPCRequest(value) {
|
|
|
3682
3873
|
* Determines whether a parsed value is a {@link JSONRPCNotification}.
|
|
3683
3874
|
*
|
|
3684
3875
|
* @remarks
|
|
3685
|
-
* A notification is a request-shaped call carrying
|
|
3876
|
+
* A notification is a request-shaped call carrying no `id` member — the protocol
|
|
3686
3877
|
* forbids one, because nothing answers a notification. `params`, when present, must
|
|
3687
3878
|
* be a record. Total: any other input returns `false`.
|
|
3688
3879
|
*
|
|
3689
3880
|
* @param value - The already-parsed value to test
|
|
3690
|
-
* @returns
|
|
3881
|
+
* @returns True if `value` is a valid JSON-RPC notification; false otherwise
|
|
3691
3882
|
*
|
|
3692
3883
|
* @example
|
|
3693
3884
|
* ```ts
|
|
@@ -3713,7 +3904,7 @@ function isJSONRPCNotification(value) {
|
|
|
3713
3904
|
* mutually exclusive, so a positive answer names exactly one arm. Total.
|
|
3714
3905
|
*
|
|
3715
3906
|
* @param value - The already-parsed value to test
|
|
3716
|
-
* @returns
|
|
3907
|
+
* @returns True if `value` is a valid JSON-RPC request or notification; false otherwise
|
|
3717
3908
|
*/
|
|
3718
3909
|
function isJSONRPCInvocation(value) {
|
|
3719
3910
|
return isJSONRPCRequest(value) || isJSONRPCNotification(value);
|
|
@@ -3723,15 +3914,15 @@ function isJSONRPCInvocation(value) {
|
|
|
3723
3914
|
* arm of a response.
|
|
3724
3915
|
*
|
|
3725
3916
|
* @remarks
|
|
3726
|
-
* A result answers a request, so `id` is
|
|
3727
|
-
* {@link isJSONRPCId}. The envelope must own a `result` and must
|
|
3917
|
+
* A result answers a request, so `id` is required and must be a valid
|
|
3918
|
+
* {@link isJSONRPCId}. The envelope must own a `result` and must not own an `error`,
|
|
3728
3919
|
* which is what makes this guard and {@link isJSONRPCErrorResponse} mutually
|
|
3729
3920
|
* exclusive on every input. `result` itself must be an object: either a modern
|
|
3730
3921
|
* {@link isMCPResult} or a legacy {@link isMCPLegacyResult}, never a bare primitive.
|
|
3731
3922
|
* Total.
|
|
3732
3923
|
*
|
|
3733
3924
|
* @param value - The already-parsed value to test
|
|
3734
|
-
* @returns
|
|
3925
|
+
* @returns True if `value` is a valid JSON-RPC result response; false otherwise
|
|
3735
3926
|
*
|
|
3736
3927
|
* @example
|
|
3737
3928
|
* ```ts
|
|
@@ -3752,22 +3943,22 @@ function isJSONRPCResultResponse(value) {
|
|
|
3752
3943
|
* Determines whether a value is one JSON-RPC `error` member.
|
|
3753
3944
|
*
|
|
3754
3945
|
* @remarks
|
|
3755
|
-
* The failure
|
|
3946
|
+
* The failure object, not the envelope carrying it — the shape a failed response owns
|
|
3756
3947
|
* under `error`, and the shape a `failed` {@link MCPTaskDetail} owns under the same name,
|
|
3757
3948
|
* which is why it is one guard rather than the same checks written twice.
|
|
3758
3949
|
*
|
|
3759
|
-
* It is deliberately
|
|
3950
|
+
* It is deliberately structural rather than exact-JSON: `data` is declared `unknown`, so
|
|
3760
3951
|
* requiring the whole object to survive a JSON clone would refuse a legal error that
|
|
3761
3952
|
* carried a non-JSON payload. Both callers here hand it an already-owned value.
|
|
3762
3953
|
*
|
|
3763
3954
|
* That choice is why the key reads are guarded. Every sibling guard clones first, and a
|
|
3764
3955
|
* clone reads each key once behind a boundary that already owns totality; this one is the
|
|
3765
|
-
* family's only
|
|
3956
|
+
* family's only direct reader, so it meets `code` and `message` exactly as the value defines
|
|
3766
3957
|
* them — including as accessors that throw. Reading a named key off an unowned value is
|
|
3767
3958
|
* itself the hostile step, and it is bounded here rather than allowed to escape. Total.
|
|
3768
3959
|
*
|
|
3769
3960
|
* @param value - The already-parsed value to test
|
|
3770
|
-
* @returns
|
|
3961
|
+
* @returns True if `value` carries an integer `code` and a string `message`; false otherwise
|
|
3771
3962
|
*
|
|
3772
3963
|
* @example
|
|
3773
3964
|
* ```ts
|
|
@@ -3785,13 +3976,13 @@ function isJSONRPCError(value) {
|
|
|
3785
3976
|
* arm of a response.
|
|
3786
3977
|
*
|
|
3787
3978
|
* @remarks
|
|
3788
|
-
* `id` is
|
|
3789
|
-
* request's id
|
|
3790
|
-
* valid and a `null` one is not. The envelope must own an `error` and must
|
|
3979
|
+
* `id` is optional here and only here: a peer that could not read the failed
|
|
3980
|
+
* request's id omits the member rather than sending `null`, so an absent `id` is
|
|
3981
|
+
* valid and a `null` one is not. The envelope must own an `error` and must not own a
|
|
3791
3982
|
* `result`. `error` carries an integer `code` and a string `message`. Total.
|
|
3792
3983
|
*
|
|
3793
3984
|
* @param value - The already-parsed value to test
|
|
3794
|
-
* @returns
|
|
3985
|
+
* @returns True if `value` is a valid JSON-RPC error response; false otherwise
|
|
3795
3986
|
*
|
|
3796
3987
|
* @example
|
|
3797
3988
|
* ```ts
|
|
@@ -3815,7 +4006,7 @@ function isJSONRPCErrorResponse(value) {
|
|
|
3815
4006
|
* The union of the mutually exclusive arms. Total.
|
|
3816
4007
|
*
|
|
3817
4008
|
* @param value - The already-parsed value to test
|
|
3818
|
-
* @returns
|
|
4009
|
+
* @returns True if `value` is a valid JSON-RPC response; false otherwise
|
|
3819
4010
|
*/
|
|
3820
4011
|
function isJSONRPCResponse(value) {
|
|
3821
4012
|
return isJSONRPCResultResponse(value) || isJSONRPCErrorResponse(value);
|
|
@@ -3828,7 +4019,7 @@ function isJSONRPCResponse(value) {
|
|
|
3828
4019
|
* The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total.
|
|
3829
4020
|
*
|
|
3830
4021
|
* @param value - The already-parsed value to test
|
|
3831
|
-
* @returns
|
|
4022
|
+
* @returns True if `value` is a valid JSON-RPC message; false otherwise
|
|
3832
4023
|
*/
|
|
3833
4024
|
function isJSONRPCMessage(value) {
|
|
3834
4025
|
return isJSONRPCInvocation(value) || isJSONRPCResponse(value);
|
|
@@ -3837,7 +4028,7 @@ function isJSONRPCMessage(value) {
|
|
|
3837
4028
|
* Determines whether a parsed value is an MCP `initialize` invocation.
|
|
3838
4029
|
*
|
|
3839
4030
|
* @param value - The already-parsed value to test
|
|
3840
|
-
* @returns
|
|
4031
|
+
* @returns True if `value` is a valid `initialize` request or notification; false otherwise
|
|
3841
4032
|
*
|
|
3842
4033
|
* @example
|
|
3843
4034
|
* ```ts
|
|
@@ -3860,7 +4051,7 @@ function isInitializeRequest(value) {
|
|
|
3860
4051
|
* legacy dispatch. Total over hostile and malformed input.
|
|
3861
4052
|
*
|
|
3862
4053
|
* @param value - The already-parsed value to inspect
|
|
3863
|
-
* @returns
|
|
4054
|
+
* @returns True if the value is an invocation carrying the reserved version key; false otherwise
|
|
3864
4055
|
*/
|
|
3865
4056
|
function isModernRequest(value) {
|
|
3866
4057
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
@@ -3877,9 +4068,9 @@ function isModernRequest(value) {
|
|
|
3877
4068
|
* Infers the wire era for an MCP protocol revision.
|
|
3878
4069
|
*
|
|
3879
4070
|
* @remarks
|
|
3880
|
-
* The era is
|
|
4071
|
+
* The era is read from the era guards rather than restated here, so a revision added
|
|
3881
4072
|
* to {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS} or {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
|
|
3882
|
-
* carries its era with it and no
|
|
4073
|
+
* carries its era with it and no further list can disagree with them.
|
|
3883
4074
|
*
|
|
3884
4075
|
* @param version - The protocol revision to classify
|
|
3885
4076
|
* @returns `'modern'` for a revision a bare server accepts, `'legacy'` for a revision the
|
|
@@ -3890,6 +4081,27 @@ function inferEra(version) {
|
|
|
3890
4081
|
if (isMCPLegacyVersion(version)) return "legacy";
|
|
3891
4082
|
}
|
|
3892
4083
|
/**
|
|
4084
|
+
* Infers the wire era one invocation's own structure selects.
|
|
4085
|
+
*
|
|
4086
|
+
* @remarks
|
|
4087
|
+
* The structural read, distinct from {@link inferEra}'s read of a revision string: era is fixed
|
|
4088
|
+
* by the reserved modern metadata a request carries, so this answers for a message whose
|
|
4089
|
+
* revision has not been read and cannot answer `undefined` — every invocation took one of the
|
|
4090
|
+
* published wire shapes. It is what an observation surface reports and what an ingress
|
|
4091
|
+
* routes on, so both derive it here rather than each spelling the ternary out.
|
|
4092
|
+
*
|
|
4093
|
+
* @param invocation - The invocation whose structure selects the era
|
|
4094
|
+
* @returns `'modern'` when the invocation carries the modern request shape, `'legacy'` otherwise
|
|
4095
|
+
*
|
|
4096
|
+
* @example
|
|
4097
|
+
* ```ts
|
|
4098
|
+
* inferRequestEra({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
|
|
4099
|
+
* ```
|
|
4100
|
+
*/
|
|
4101
|
+
function inferRequestEra(invocation) {
|
|
4102
|
+
return isModernRequest(invocation) ? "modern" : "legacy";
|
|
4103
|
+
}
|
|
4104
|
+
/**
|
|
3893
4105
|
* Infers the newest supported modern protocol revision present in a peer's offer.
|
|
3894
4106
|
*
|
|
3895
4107
|
* @param offered - The protocol revisions offered by the peer
|
|
@@ -3899,15 +4111,15 @@ function inferVersion(offered) {
|
|
|
3899
4111
|
for (const version of SUPPORTED_MODERN_PROTOCOL_VERSIONS) if (offered.includes(version)) return version;
|
|
3900
4112
|
}
|
|
3901
4113
|
/**
|
|
3902
|
-
* Infers the protocol version an outbound message announces itself with — the
|
|
4114
|
+
* Infers the protocol version an outbound message announces itself with — the one
|
|
3903
4115
|
* projection every HTTP client transport stamps `mcp-protocol-version` from.
|
|
3904
4116
|
*
|
|
3905
4117
|
* @remarks
|
|
3906
|
-
* This is deliberately the
|
|
4118
|
+
* This is deliberately the same read the server's own expectation performs
|
|
3907
4119
|
* ({@link import('@orkestrel/mcp/server').inferHeaderIssue}): a modern request's reserved
|
|
3908
|
-
* `_meta` version, accepted whenever it is a string. It is
|
|
4120
|
+
* `_meta` version, accepted whenever it is a string. It is not
|
|
3909
4121
|
* {@link import('./parsers.js').parseRequestContext}, and the difference is the whole
|
|
3910
|
-
* point. That parser answers a different question — is the modern metadata
|
|
4122
|
+
* point. That parser answers a different question — is the modern metadata well formed —
|
|
3911
4123
|
* and refuses a request whose capability declaration or logging level is malformed. Such a
|
|
3912
4124
|
* request is still modern (era is fixed by key presence) and the server still demands the
|
|
3913
4125
|
* header for it, so projecting through the parser withholds a header the peer requires and
|
|
@@ -3916,7 +4128,7 @@ function inferVersion(offered) {
|
|
|
3916
4128
|
* A non-modern message projects nothing: a legacy request's version comes from the
|
|
3917
4129
|
* `initialize` handshake the transport captured, not from the message.
|
|
3918
4130
|
*
|
|
3919
|
-
* Header
|
|
4131
|
+
* Header names stay with the transports that own the wire (see `constants.ts`); core owns
|
|
3920
4132
|
* the value this projection derives, which is the part the browser and Node faces disagreed about.
|
|
3921
4133
|
*
|
|
3922
4134
|
* @param message - The outbound message about to be written
|
|
@@ -3935,16 +4147,16 @@ function inferRequestVersion(message) {
|
|
|
3935
4147
|
//#endregion
|
|
3936
4148
|
//#region src/core/MCPMethodManager.ts
|
|
3937
4149
|
/**
|
|
3938
|
-
*
|
|
4150
|
+
* Holds the modern methods an {@link import('./types.js').MCPServerInterface}
|
|
3939
4151
|
* dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
|
|
3940
4152
|
* map rather than exposing one.
|
|
3941
4153
|
*
|
|
3942
4154
|
* @remarks
|
|
3943
4155
|
* - **One seam.** The server registers its built-in modern methods here at construction
|
|
3944
|
-
* and resolves
|
|
4156
|
+
* and resolves every modern method from here, so a consumer's method and a built-in
|
|
3945
4157
|
* are the same kind of thing on the same path.
|
|
3946
4158
|
* - **Registration is a write, not a merge.** `add` under a name already present
|
|
3947
|
-
*
|
|
4159
|
+
* replaces it, which is how a consumer overrides a built-in; there is no precedence
|
|
3948
4160
|
* rule to remember.
|
|
3949
4161
|
* - **A narrower contract than a `Map`.** Callers register and resolve; they cannot
|
|
3950
4162
|
* iterate, clear, or otherwise reach the server's internal state through it.
|
|
@@ -3969,7 +4181,7 @@ var MCPMethodManager = class {
|
|
|
3969
4181
|
//#endregion
|
|
3970
4182
|
//#region src/core/MCPProgressReporter.ts
|
|
3971
4183
|
/**
|
|
3972
|
-
*
|
|
4184
|
+
* Hands bounded, request-scoped progress from one producer to one serial consumer.
|
|
3973
4185
|
*
|
|
3974
4186
|
* The reporter holds at most one owned progress item. {@link report} applies backpressure until
|
|
3975
4187
|
* {@link take} consumes that slot. It has no replay, queue, concurrent-consumer coordination,
|
|
@@ -4093,19 +4305,20 @@ var MCPProgressReporter = class {
|
|
|
4093
4305
|
//#endregion
|
|
4094
4306
|
//#region src/core/MCPStreamController.ts
|
|
4095
4307
|
/**
|
|
4096
|
-
*
|
|
4308
|
+
* Provides the one cancellation engine every modern held-open result leaves `MCPServer`
|
|
4309
|
+
* through.
|
|
4097
4310
|
*
|
|
4098
4311
|
* @remarks
|
|
4099
|
-
* A native async generator decides cancellation with a
|
|
4312
|
+
* A native async generator decides cancellation with a queue: `return()` and `throw()` wait
|
|
4100
4313
|
* behind a `next()` the producer has not answered, so a consumer abandoning a source parked
|
|
4101
4314
|
* on an event that will never arrive waits forever for its own cancellation. This class
|
|
4102
|
-
* arbitrates instead of queueing. It keeps at most
|
|
4103
|
-
* settles the consumer's read itself, aborts the request's lifetime
|
|
4315
|
+
* arbitrates instead of queueing. It keeps at most one read outstanding against the source,
|
|
4316
|
+
* settles the consumer's read itself, aborts the request's lifetime before it delegates
|
|
4104
4317
|
* cleanup to the producer — so a cooperating producer is woken rather than waited on —
|
|
4105
4318
|
* contains every promise the producer settles late, and makes every closure path idempotent.
|
|
4106
4319
|
*
|
|
4107
4320
|
* The closures are deliberately different answers: the source's own return is the
|
|
4108
|
-
* terminal
|
|
4321
|
+
* terminal response, `return(value)` is the consumer saying it has the answer already, and
|
|
4109
4322
|
* {@link stop} is an owner saying there will be no answer at all. Only the source's own
|
|
4110
4323
|
* return is a message a peer ever sees.
|
|
4111
4324
|
*
|
|
@@ -4113,7 +4326,7 @@ var MCPProgressReporter = class {
|
|
|
4113
4326
|
* generator is suspended inside, so the signal is how an uncooperative producer is asked to
|
|
4114
4327
|
* finish, and this controller never blocks its consumer on the answer.
|
|
4115
4328
|
*
|
|
4116
|
-
* **What this class does
|
|
4329
|
+
* **What this class does not have is an owner of last resort.** No finalizer, no timer, no
|
|
4117
4330
|
* timeout ends an exchange nobody released. That absence is the design: an exchange holds a
|
|
4118
4331
|
* producer, a request lifetime and a live server slot, so a silent background release would
|
|
4119
4332
|
* turn "a pump forgot its obligation" from a reproducible defect into a nondeterministic one,
|
|
@@ -4290,19 +4503,19 @@ var MCPStreamController = class {
|
|
|
4290
4503
|
//#endregion
|
|
4291
4504
|
//#region src/core/MCPTextStreamController.ts
|
|
4292
4505
|
/**
|
|
4293
|
-
*
|
|
4506
|
+
* Mirrors a controlled held-open result at the string boundary — the same exchange, already
|
|
4294
4507
|
* serialized.
|
|
4295
4508
|
*
|
|
4296
4509
|
* @remarks
|
|
4297
|
-
* A
|
|
4510
|
+
* A translation boundary and deliberately nothing else. It serializes each message and the
|
|
4298
4511
|
* terminating response, and every lifecycle decision — return, throw, dispose, stop — ends
|
|
4299
4512
|
* the typed exchange beneath it rather than this face. That is the whole design constraint: a
|
|
4300
|
-
* serialized face implemented as its own async generator would add a
|
|
4513
|
+
* serialized face implemented as its own async generator would add a second operation queue,
|
|
4301
4514
|
* and the queue is exactly the defect the typed controller exists to remove — a `return()`
|
|
4302
4515
|
* promptly settled at the text face and left queued at the typed one cancels nothing.
|
|
4303
4516
|
*
|
|
4304
4517
|
* One member is a narrowing rather than a pass-through, and it is worth knowing before it
|
|
4305
|
-
* surprises a producer. `return` receives a
|
|
4518
|
+
* surprises a producer. `return` receives a string; it cannot rebuild the typed
|
|
4306
4519
|
* `JSONRPCResponse` the typed face would close on, and inventing one by parsing the
|
|
4307
4520
|
* argument back would make this face decide what the exchange ended with. So it ends the
|
|
4308
4521
|
* typed exchange with {@link MCPStreamControllerInterface.stop} — no terminal — and answers
|
|
@@ -4311,11 +4524,11 @@ var MCPStreamController = class {
|
|
|
4311
4524
|
* the honest translation of "the consumer already has its answer" when the answer is opaque
|
|
4312
4525
|
* text, not a downgrade to work around.
|
|
4313
4526
|
*
|
|
4314
|
-
* It accepts only a
|
|
4527
|
+
* It accepts only a controlled typed stream. A raw generator would have no lifecycle to
|
|
4315
4528
|
* delegate to, and this class refuses to grow one of its own.
|
|
4316
4529
|
*
|
|
4317
4530
|
* Delegation is total and it is what makes the ownership obligation transitive: `return`,
|
|
4318
|
-
* `throw`, `stop`, and dispose each end the
|
|
4531
|
+
* `throw`, `stop`, and dispose each end the typed exchange, so a pump holding only this
|
|
4319
4532
|
* serialized face still releases the producer, the request lifetime, and the live server slot
|
|
4320
4533
|
* behind it. There is no owner of last resort here either, for the same reason there is none
|
|
4321
4534
|
* on the typed face.
|
|
@@ -4362,7 +4575,7 @@ var MCPTextStreamController = class {
|
|
|
4362
4575
|
* @remarks
|
|
4363
4576
|
* The typed exchange ends with no terminal, because a string is not a
|
|
4364
4577
|
* `JSONRPCResponse` and this face never parses one back out of its argument. The
|
|
4365
|
-
* supplied text is the answer to
|
|
4578
|
+
* supplied text is the answer to this consumer, and a cooperating producer sees its
|
|
4366
4579
|
* cancellation path rather than its normal return.
|
|
4367
4580
|
*
|
|
4368
4581
|
* @param value - The serialized terminal the consumer is ending on
|
|
@@ -4399,7 +4612,7 @@ var MCPTextStreamController = class {
|
|
|
4399
4612
|
*
|
|
4400
4613
|
* @remarks
|
|
4401
4614
|
* Delegates downward exactly as {@link stop} does — disposing the serialized arm is
|
|
4402
|
-
* disposing the exchange, never
|
|
4615
|
+
* disposing the exchange, never this adapter alone.
|
|
4403
4616
|
*
|
|
4404
4617
|
* @returns Resolves once the typed exchange has ended
|
|
4405
4618
|
*/
|
|
@@ -4452,15 +4665,11 @@ var MCPLegacy = class {
|
|
|
4452
4665
|
}
|
|
4453
4666
|
async handle(message, options) {
|
|
4454
4667
|
if (!isBoundedString(message, this.limit.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
|
|
4455
|
-
|
|
4456
|
-
try {
|
|
4457
|
-
parsed = JSON.parse(message);
|
|
4458
|
-
} catch {
|
|
4459
|
-
return this.#options.dispatcher.handle(message, options);
|
|
4460
|
-
}
|
|
4668
|
+
const parsed = (0, _orkestrel_contract.parseJSON)(message);
|
|
4461
4669
|
if (isModernRequest(parsed) || !isJSONRPCInvocation(parsed)) return this.#options.dispatcher.handle(message, options);
|
|
4462
4670
|
const answer = await this.#legacy(parsed, options);
|
|
4463
|
-
|
|
4671
|
+
if (answer === void 0) return void 0;
|
|
4672
|
+
return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
|
|
4464
4673
|
}
|
|
4465
4674
|
async #legacy(invocation, options) {
|
|
4466
4675
|
if (invocation.id === void 0) return void 0;
|
|
@@ -4484,13 +4693,42 @@ var MCPLegacy = class {
|
|
|
4484
4693
|
}
|
|
4485
4694
|
async #forward(request, options) {
|
|
4486
4695
|
const translated = legacyInvocationToModern(request);
|
|
4487
|
-
const
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4696
|
+
const metadata = request.method === "tools/call" ? request.params?.["_meta"] : void 0;
|
|
4697
|
+
const candidate = (0, _orkestrel_contract.isRecord)(metadata) ? metadata["progressToken"] : void 0;
|
|
4698
|
+
const token = (0, _orkestrel_contract.isString)(candidate) || (0, _orkestrel_contract.isInteger)(candidate) ? candidate : void 0;
|
|
4699
|
+
if (token === void 0) {
|
|
4700
|
+
const answer = await this.#options.dispatcher.dispatch(translated, options);
|
|
4701
|
+
if (Symbol.asyncIterator in answer) {
|
|
4702
|
+
answer.stop();
|
|
4703
|
+
await answer[Symbol.asyncDispose]();
|
|
4704
|
+
return this.#unsupported(request.id, "stream");
|
|
4705
|
+
}
|
|
4706
|
+
return this.#project(answer, request.id);
|
|
4707
|
+
}
|
|
4708
|
+
const closure = new AbortController();
|
|
4709
|
+
const resolved = buildMethodOptions(options ?? {}, closure.signal);
|
|
4710
|
+
try {
|
|
4711
|
+
const answer = await this.#options.dispatcher.dispatch(translated, resolved);
|
|
4712
|
+
if (Symbol.asyncIterator in answer) return new MCPStreamController(this.#progress(answer, request.id, token), resolved.signal, closure);
|
|
4713
|
+
closure.abort();
|
|
4714
|
+
return this.#project(answer, request.id);
|
|
4715
|
+
} catch (error) {
|
|
4716
|
+
closure.abort(error);
|
|
4717
|
+
throw error;
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
async *#progress(stream, id, token) {
|
|
4721
|
+
try {
|
|
4722
|
+
while (true) {
|
|
4723
|
+
const frame = await stream.next();
|
|
4724
|
+
if (frame.done === true) return this.#project(frame.value, id);
|
|
4725
|
+
const params = frame.value.params;
|
|
4726
|
+
if (frame.value.method !== "notifications/progress" || !(0, _orkestrel_contract.isRecord)(params) || params["progressToken"] !== token || !isMCPProgress(params)) return this.#unsupported(id, "stream");
|
|
4727
|
+
yield frame.value;
|
|
4728
|
+
}
|
|
4729
|
+
} finally {
|
|
4730
|
+
await stream[Symbol.asyncDispose]();
|
|
4492
4731
|
}
|
|
4493
|
-
return this.#project(answer, request.id);
|
|
4494
4732
|
}
|
|
4495
4733
|
#project(answer, id) {
|
|
4496
4734
|
if (answer.error !== void 0) return answer.error.code === -32021 ? this.#unsupported(id, this.#capability(answer)) : answer;
|
|
@@ -4544,7 +4782,7 @@ var MCPLegacyClientTransport = class {
|
|
|
4544
4782
|
if (requested !== void 0 && !isMCPLegacyVersion(requested)) throw new MCPError("Unsupported legacy protocol version", MCP_UNSUPPORTED_VERSION, { requested });
|
|
4545
4783
|
this.#transport = transport;
|
|
4546
4784
|
this.#client = options?.identity ?? {
|
|
4547
|
-
name: "
|
|
4785
|
+
name: "@orkestrel/mcp",
|
|
4548
4786
|
version: "1.0.0"
|
|
4549
4787
|
};
|
|
4550
4788
|
this.#capabilities = options?.capabilities ?? {};
|
|
@@ -4736,8 +4974,8 @@ var MCPLegacyClientTransport = class {
|
|
|
4736
4974
|
//#endregion
|
|
4737
4975
|
//#region src/core/MCPServer.ts
|
|
4738
4976
|
/**
|
|
4739
|
-
*
|
|
4740
|
-
*
|
|
4977
|
+
* Dispatches JSON-RPC 2.0 requests over a live {@link ToolManagerInterface}, with no
|
|
4978
|
+
* transport coupling.
|
|
4741
4979
|
*
|
|
4742
4980
|
* @remarks
|
|
4743
4981
|
* - **`dispatch` and `handle`.** `dispatch(invocation)` runs an already-parsed invocation and
|
|
@@ -4746,7 +4984,7 @@ var MCPLegacyClientTransport = class {
|
|
|
4746
4984
|
* `handle(message)` is the string boundary: it
|
|
4747
4985
|
* `JSON.parse`s the raw message (a failure → a `-32700` response), narrows it to
|
|
4748
4986
|
* an invocation (a non-invocation → a `-32600` response, with the unreadable `id`
|
|
4749
|
-
*
|
|
4987
|
+
* omitted rather than nulled), dispatches, and serializes the
|
|
4750
4988
|
* response back to a string (`undefined` for a notification).
|
|
4751
4989
|
* - **One modern seam.** `server/discover`, `tools/list`, `tools/call`, and
|
|
4752
4990
|
* `subscriptions/listen` are always registered; `resources/*`, `prompts/*`, and
|
|
@@ -4814,8 +5052,21 @@ var MCPServer = class {
|
|
|
4814
5052
|
if (decoded === void 0 || !("method" in decoded)) return buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request");
|
|
4815
5053
|
return this.#dispatch(decoded, options);
|
|
4816
5054
|
}
|
|
5055
|
+
async handle(message, options) {
|
|
5056
|
+
if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
|
|
5057
|
+
const parsed = (0, _orkestrel_contract.parseJSON)(message);
|
|
5058
|
+
if (parsed === void 0) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
|
|
5059
|
+
const decoded = parseJSONRPCMessage(parsed, {
|
|
5060
|
+
bytes: this.#limits.message,
|
|
5061
|
+
depth: this.#limits.depth
|
|
5062
|
+
});
|
|
5063
|
+
if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
|
|
5064
|
+
const answer = await this.#dispatch(decoded, options ?? {});
|
|
5065
|
+
if (answer === void 0) return void 0;
|
|
5066
|
+
return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
|
|
5067
|
+
}
|
|
4817
5068
|
async #dispatch(invocation, options) {
|
|
4818
|
-
this.#emitter.emit("request", invocation.method, invocation.id,
|
|
5069
|
+
this.#emitter.emit("request", invocation.method, invocation.id, inferRequestEra(invocation));
|
|
4819
5070
|
if (invocation.id === void 0) return;
|
|
4820
5071
|
const id = invocation.id;
|
|
4821
5072
|
const metadata = invocation.params?.["_meta"];
|
|
@@ -4833,26 +5084,9 @@ var MCPServer = class {
|
|
|
4833
5084
|
return this.#contain(error, id);
|
|
4834
5085
|
}
|
|
4835
5086
|
}
|
|
4836
|
-
async handle(message, options) {
|
|
4837
|
-
if (!isBoundedString(message, this.#limits.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
|
|
4838
|
-
let parsed;
|
|
4839
|
-
try {
|
|
4840
|
-
parsed = JSON.parse(message);
|
|
4841
|
-
} catch {
|
|
4842
|
-
return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_PARSE_ERROR, "Parse error"));
|
|
4843
|
-
}
|
|
4844
|
-
const decoded = parseJSONRPCMessage(parsed, {
|
|
4845
|
-
bytes: this.#limits.message,
|
|
4846
|
-
depth: this.#limits.depth
|
|
4847
|
-
});
|
|
4848
|
-
if (decoded === void 0 || !("method" in decoded)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
|
|
4849
|
-
const answer = await this.#dispatch(decoded, options ?? {});
|
|
4850
|
-
if (answer === void 0) return void 0;
|
|
4851
|
-
return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
|
|
4852
|
-
}
|
|
4853
5087
|
#register() {
|
|
4854
|
-
this.#methods.add("server/discover", async (request
|
|
4855
|
-
this.#methods.add("tools/list", async (request
|
|
5088
|
+
this.#methods.add("server/discover", async (request) => this.#discover(request));
|
|
5089
|
+
this.#methods.add("tools/list", async (request) => this.#list(request));
|
|
4856
5090
|
this.#methods.add("tools/call", async (request, options) => this.#call(request, options));
|
|
4857
5091
|
this.#methods.add("subscriptions/listen", async (request, options) => this.#subscribe(request, options));
|
|
4858
5092
|
const resources = this.#options.resources;
|
|
@@ -5066,20 +5300,20 @@ var MCPServer = class {
|
|
|
5066
5300
|
async #defer(request, call, options) {
|
|
5067
5301
|
const configured = this.#options.task;
|
|
5068
5302
|
if (configured === void 0) return void 0;
|
|
5069
|
-
const
|
|
5303
|
+
const deferred = {
|
|
5070
5304
|
request,
|
|
5071
5305
|
call,
|
|
5072
5306
|
tools: this.#options.tools
|
|
5073
5307
|
};
|
|
5074
|
-
const key = await configured.
|
|
5308
|
+
const key = await configured.deferral(deferred, options);
|
|
5075
5309
|
if ((0, _orkestrel_contract.isUndefined)(key)) return void 0;
|
|
5076
5310
|
if (!(0, _orkestrel_contract.isString)(key) || key.length === 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server execution returned an invalid task key");
|
|
5077
5311
|
const context = parseRequestContext(request, {
|
|
5078
5312
|
bytes: this.#limits.message,
|
|
5079
5313
|
depth: this.#limits.depth
|
|
5080
5314
|
});
|
|
5081
|
-
if (context === void 0 || !
|
|
5082
|
-
const created = await configured.tasks.start(key,
|
|
5315
|
+
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]: {} } } });
|
|
5316
|
+
const created = await configured.tasks.start(key, deferred, options);
|
|
5083
5317
|
const captured = snapshotJSON({
|
|
5084
5318
|
resultType: "task",
|
|
5085
5319
|
taskId: created.taskId,
|
|
@@ -5163,7 +5397,7 @@ var MCPServer = class {
|
|
|
5163
5397
|
arguments: args
|
|
5164
5398
|
}, options);
|
|
5165
5399
|
if (selected === void 0) return void 0;
|
|
5166
|
-
const round = this.#
|
|
5400
|
+
const round = this.#ownRound(selected);
|
|
5167
5401
|
const context = parseRequestContext(request, {
|
|
5168
5402
|
bytes: this.#limits.message,
|
|
5169
5403
|
depth: this.#limits.depth
|
|
@@ -5216,7 +5450,7 @@ var MCPServer = class {
|
|
|
5216
5450
|
const state = parseMCPInputState(verified);
|
|
5217
5451
|
if (state === void 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a malformed protected payload"), id);
|
|
5218
5452
|
if (state.expiry <= Date.now() || state.id === id || state.version !== context.version || state.method !== request.method || state.name !== name || state.digest !== digest) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
5219
|
-
const responses = this.#
|
|
5453
|
+
const responses = this.#checkAnswers(state.requests, inputResponses);
|
|
5220
5454
|
if (responses === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an input response is missing or malformed");
|
|
5221
5455
|
const principal = await configured.principal(request, options);
|
|
5222
5456
|
if (!(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || state.principal !== principal) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
@@ -5229,13 +5463,13 @@ var MCPServer = class {
|
|
|
5229
5463
|
}, options);
|
|
5230
5464
|
if (state.expiry <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
5231
5465
|
if (selected === void 0) return void 0;
|
|
5232
|
-
const round = this.#
|
|
5466
|
+
const round = this.#ownRound(selected);
|
|
5233
5467
|
if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
|
|
5234
5468
|
const refusal = this.#gate(round, context, id);
|
|
5235
5469
|
if (refusal !== void 0) return refusal;
|
|
5236
5470
|
return this.#required(request, name, digest, round, principal, state.id, state.expiry);
|
|
5237
5471
|
}
|
|
5238
|
-
#
|
|
5472
|
+
#checkAnswers(requests, responses) {
|
|
5239
5473
|
const answered = {};
|
|
5240
5474
|
for (const [key, issued] of Object.entries(requests)) {
|
|
5241
5475
|
const response = responses[key];
|
|
@@ -5244,7 +5478,7 @@ var MCPServer = class {
|
|
|
5244
5478
|
}
|
|
5245
5479
|
return Object.freeze(answered);
|
|
5246
5480
|
}
|
|
5247
|
-
#
|
|
5481
|
+
#ownRound(round) {
|
|
5248
5482
|
const owned = snapshotJSON(round, {
|
|
5249
5483
|
bytes: this.#limits.content,
|
|
5250
5484
|
keys: this.#limits.keys,
|
|
@@ -5332,7 +5566,7 @@ var MCPServer = class {
|
|
|
5332
5566
|
}
|
|
5333
5567
|
yield buildSubscriptionAcknowledgement(notifications, id);
|
|
5334
5568
|
if (configured !== void 0) {
|
|
5335
|
-
const iterator = (await configured.
|
|
5569
|
+
const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
|
|
5336
5570
|
options.signal.addEventListener("abort", () => void iterator.return?.(void 0)?.catch(() => void 0), { once: true });
|
|
5337
5571
|
for (let next = await iterator.next(); next.done !== true; next = await iterator.next()) {
|
|
5338
5572
|
const owned = parseJSONRPCMessage(next.value, {
|
|
@@ -5351,22 +5585,22 @@ var MCPServer = class {
|
|
|
5351
5585
|
slot.abort();
|
|
5352
5586
|
}
|
|
5353
5587
|
}
|
|
5354
|
-
#
|
|
5588
|
+
#readTaskId(request) {
|
|
5355
5589
|
const id = request.id;
|
|
5356
5590
|
const context = parseRequestContext(request, {
|
|
5357
5591
|
bytes: this.#limits.message,
|
|
5358
5592
|
depth: this.#limits.depth
|
|
5359
5593
|
});
|
|
5360
|
-
if (context === void 0 || !
|
|
5594
|
+
if (context === void 0 || !supportsTask(context.capabilities)) return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires the tasks extension capability for this request", { requiredCapabilities: { extensions: { [MCP_EXTENSION_TASKS]: {} } } });
|
|
5361
5595
|
const taskId = request.params?.["taskId"];
|
|
5362
5596
|
if (!isBoundedString(taskId, this.#limits.state) || taskId.length === 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: a bounded string `taskId` is required");
|
|
5363
5597
|
return taskId;
|
|
5364
5598
|
}
|
|
5365
5599
|
async #task(request, tasks, options) {
|
|
5366
5600
|
const id = request.id;
|
|
5367
|
-
const
|
|
5368
|
-
if (!(0, _orkestrel_contract.isString)(
|
|
5369
|
-
const found = await tasks.task(
|
|
5601
|
+
const taskId = this.#readTaskId(request);
|
|
5602
|
+
if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
|
|
5603
|
+
const found = await tasks.task(taskId, options);
|
|
5370
5604
|
if (found === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
|
|
5371
5605
|
const owned = snapshotJSON(found, {
|
|
5372
5606
|
bytes: this.#limits.content,
|
|
@@ -5378,20 +5612,20 @@ var MCPServer = class {
|
|
|
5378
5612
|
}
|
|
5379
5613
|
async #update(request, tasks, options) {
|
|
5380
5614
|
const id = request.id;
|
|
5381
|
-
const
|
|
5382
|
-
if (!(0, _orkestrel_contract.isString)(
|
|
5615
|
+
const taskId = this.#readTaskId(request);
|
|
5616
|
+
if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
|
|
5383
5617
|
const responses = request.params?.["inputResponses"];
|
|
5384
5618
|
if (!(0, _orkestrel_contract.isRecord)(responses)) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an `inputResponses` object is required");
|
|
5385
|
-
if (!isMCPTaskDetail(await tasks.task(
|
|
5386
|
-
await tasks.update(
|
|
5619
|
+
if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
|
|
5620
|
+
await tasks.update(taskId, responses, options);
|
|
5387
5621
|
return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
|
|
5388
5622
|
}
|
|
5389
5623
|
async #abort(request, tasks, options) {
|
|
5390
5624
|
const id = request.id;
|
|
5391
|
-
const
|
|
5392
|
-
if (!(0, _orkestrel_contract.isString)(
|
|
5393
|
-
if (!isMCPTaskDetail(await tasks.task(
|
|
5394
|
-
await tasks.abort(
|
|
5625
|
+
const taskId = this.#readTaskId(request);
|
|
5626
|
+
if (!(0, _orkestrel_contract.isString)(taskId)) return taskId;
|
|
5627
|
+
if (!isMCPTaskDetail(await tasks.task(taskId, options))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: no task is available for that `taskId`");
|
|
5628
|
+
await tasks.abort(taskId, options);
|
|
5395
5629
|
return buildJSONRPCResult(id, buildModernResult({}, this.#options.identity));
|
|
5396
5630
|
}
|
|
5397
5631
|
#contain(error, id) {
|
|
@@ -5441,19 +5675,19 @@ var MCPServer = class {
|
|
|
5441
5675
|
//#endregion
|
|
5442
5676
|
//#region src/core/MCPTaskClient.ts
|
|
5443
5677
|
/**
|
|
5444
|
-
*
|
|
5445
|
-
*
|
|
5678
|
+
* Issues the `tasks/*` methods over one correlated-request door — the client half of the
|
|
5679
|
+
* stable Tasks extension, exposed as an {@link import('./types.js').MCPClientInterface}'s
|
|
5446
5680
|
* `tasks`.
|
|
5447
5681
|
*
|
|
5448
5682
|
* @remarks
|
|
5449
5683
|
* - **The mirror of the server-side port, minus `start`.** An
|
|
5450
5684
|
* {@link import('./types.js').MCPTaskManagerInterface} is the consumer's durable store the
|
|
5451
|
-
*
|
|
5685
|
+
* server creates tasks in; this is the client's read/answer/stop access to the tasks a peer
|
|
5452
5686
|
* already created. Creation is missing on purpose: the extension gives a client no flag and
|
|
5453
5687
|
* no parameter to ask for a task, so `start` has no wire method to be.
|
|
5454
5688
|
* - **No plural accessor, no loop, no cache.** MCP defines no `tasks/list`, so nothing here
|
|
5455
5689
|
* enumerates. A task snapshot's `pollIntervalMs` is carried untouched and a one-shot read
|
|
5456
|
-
* sits beside it; the
|
|
5690
|
+
* sits beside it; the schedule is the consumer's, because this package has no durable place
|
|
5457
5691
|
* to keep a task, no idea when the application still cares, and no lifetime to hang a timer
|
|
5458
5692
|
* on that outlives the request it was born from. An instance left alone writes nothing.
|
|
5459
5693
|
* - **One channel.** Every request goes through the injected
|
|
@@ -5501,20 +5735,20 @@ var MCPTaskClient = class {
|
|
|
5501
5735
|
//#endregion
|
|
5502
5736
|
//#region src/core/MCPClient.ts
|
|
5503
5737
|
/**
|
|
5504
|
-
*
|
|
5505
|
-
*
|
|
5506
|
-
*
|
|
5738
|
+
* Connects to a remote MCP server over any injected {@link MCPMessageTransportInterface},
|
|
5739
|
+
* negotiates the modern revision, and exposes the server's tools as local
|
|
5740
|
+
* {@link ToolInterface}s an agent can run.
|
|
5507
5741
|
*
|
|
5508
5742
|
* @remarks
|
|
5509
|
-
* - **The mirror of `MCPServer`.** The server
|
|
5510
|
-
* this client
|
|
5743
|
+
* - **The mirror of `MCPServer`.** The server dispatches requests over a tool registry;
|
|
5744
|
+
* this client issues them over a transport. `connect` probes `server/discover` and exposes
|
|
5511
5745
|
* the negotiated `version`; a legacy peer requires an explicit transport adapter.
|
|
5512
5746
|
* `tools()` lists the remote tools and wraps each as a
|
|
5513
5747
|
* local {@link ToolInterface} whose `execute` calls back through `call`; `call` runs a
|
|
5514
5748
|
* remote `tools/call` and reports the arm the peer answered with — a value, a durable
|
|
5515
5749
|
* task, or a request for more input (a remote `isError: true` throws locally, so an
|
|
5516
5750
|
* agent's {@link import('@orkestrel/tool').ToolManagerInterface} isolates it into a
|
|
5517
|
-
* `success: false` result
|
|
5751
|
+
* `success: false` result exactly like a local throw). A wrapped tool cannot hand an agent
|
|
5518
5752
|
* a deferred answer, so a non-`'complete'` arm throws there.
|
|
5519
5753
|
* - **Request↔response correlation.** Each request is tagged with a monotonic numeric
|
|
5520
5754
|
* `id` ({@link #nextId}); a single transport `message` subscription resolves / rejects
|
|
@@ -5522,9 +5756,9 @@ var MCPTaskClient = class {
|
|
|
5522
5756
|
* every pending request because the peer could not identify which request failed. A
|
|
5523
5757
|
* server-initiated message is re-surfaced on the `notification` event, except a progress
|
|
5524
5758
|
* frame claimed by the request that asked
|
|
5525
|
-
* for it; a
|
|
5759
|
+
* for it; a response correlating to nothing pending is discarded, because the request it
|
|
5526
5760
|
* answers has already settled.
|
|
5527
|
-
* - **Per-request cancellation.** `call`'s `options.signal` withdraws
|
|
5761
|
+
* - **Per-request cancellation.** `call`'s `options.signal` withdraws one caller from one
|
|
5528
5762
|
* request: the pending entry rejects on every carrier, and `notifications/cancelled` goes
|
|
5529
5763
|
* out only where the transport declares itself duplex — the dated revision defines no
|
|
5530
5764
|
* client-to-server notification over Streamable HTTP, where closing the response stream
|
|
@@ -5541,7 +5775,7 @@ var MCPTaskClient = class {
|
|
|
5541
5775
|
* discovery probe uses that same configured deadline, so a silent peer cannot hold
|
|
5542
5776
|
* negotiation indefinitely.
|
|
5543
5777
|
* `AbortSignal.timeout` (never a raw `setTimeout`) rejects only that pending request, and the
|
|
5544
|
-
* same deadline bounds the
|
|
5778
|
+
* same deadline bounds the wait on the transport's `close`, the one wait no drain and no signal
|
|
5545
5779
|
* can reach. It bounds the wait rather than the close, which keeps running, so a retry joins it
|
|
5546
5780
|
* instead of shutting one connection down twice.
|
|
5547
5781
|
* - **Transport-agnostic.** Imports only core siblings (JSON-RPC + the tool vocabulary);
|
|
@@ -5594,7 +5828,7 @@ var MCPClient = class {
|
|
|
5594
5828
|
});
|
|
5595
5829
|
this.#transport = options.transport;
|
|
5596
5830
|
this.#identity = options.identity ?? {
|
|
5597
|
-
name: "
|
|
5831
|
+
name: "@orkestrel/mcp",
|
|
5598
5832
|
version: "1.0.0"
|
|
5599
5833
|
};
|
|
5600
5834
|
this.#capabilities = options.capabilities ?? {};
|
|
@@ -5735,18 +5969,19 @@ var MCPClient = class {
|
|
|
5735
5969
|
} }
|
|
5736
5970
|
}
|
|
5737
5971
|
};
|
|
5738
|
-
const subscription = {
|
|
5739
|
-
queue: [],
|
|
5740
|
-
capacity
|
|
5741
|
-
};
|
|
5742
5972
|
const abort = this.#abortSubscription.bind(this, id, signal);
|
|
5743
5973
|
signal.addEventListener("abort", abort, { once: true });
|
|
5744
5974
|
this.#pending.set(id, {
|
|
5745
5975
|
method,
|
|
5746
5976
|
signal,
|
|
5747
5977
|
abort,
|
|
5748
|
-
subscription
|
|
5978
|
+
subscription: {
|
|
5979
|
+
queue: [],
|
|
5980
|
+
capacity
|
|
5981
|
+
}
|
|
5749
5982
|
});
|
|
5983
|
+
const subscription = this.#pending.get(id)?.subscription;
|
|
5984
|
+
if (subscription === void 0) throw new Error("MCP subscription state is missing");
|
|
5750
5985
|
this.#transport.send(request).catch((error) => this.#settle(id, error, true));
|
|
5751
5986
|
try {
|
|
5752
5987
|
for (;;) {
|
|
@@ -6072,13 +6307,222 @@ var MCPClient = class {
|
|
|
6072
6307
|
}
|
|
6073
6308
|
};
|
|
6074
6309
|
//#endregion
|
|
6310
|
+
//#region src/core/transports/HTTPClientTransport.ts
|
|
6311
|
+
/**
|
|
6312
|
+
* Drives a remote Streamable-HTTP MCP server over `fetch` — a client
|
|
6313
|
+
* {@link MCPMessageTransportInterface} for the Model Context Protocol, the egress mirror of
|
|
6314
|
+
* the server's `createMCPRoutes`.
|
|
6315
|
+
*
|
|
6316
|
+
* @remarks
|
|
6317
|
+
* - **One class, both faces.** It touches `fetch`, `Response`, `AbortController`,
|
|
6318
|
+
* `AbortSignal`, and `WeakMap` alone, so it is host-independent and lives in core. Each
|
|
6319
|
+
* environment face publishes its own `createHTTPClientTransport` over it —
|
|
6320
|
+
* `@orkestrel/mcp/browser` and `@orkestrel/mcp/server` — and both factories return this
|
|
6321
|
+
* class, so a reply reaches a page and a Node process through the same decode.
|
|
6322
|
+
* - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
|
|
6323
|
+
* message to `options.url` with `content-type: application/json` and an
|
|
6324
|
+
* `Accept` of both `application/json` and `text/event-stream` (so the server may
|
|
6325
|
+
* answer with either framing) — plus any `options.headers` (for example, an `Authorization`
|
|
6326
|
+
* bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
|
|
6327
|
+
* the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
|
|
6328
|
+
* to.
|
|
6329
|
+
* - **Both reply framings.** A `200` with an `application/json` body is parsed with
|
|
6330
|
+
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
|
|
6331
|
+
* `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
|
|
6332
|
+
* readEventStream}) — the inverse of the server's `createStream` seam, so the wire
|
|
6333
|
+
* round-trips. A `202`
|
|
6334
|
+
* Accepted (a notification) carries no body and emits nothing.
|
|
6335
|
+
* - **Session and protocol headers.** `start()` is a no-op (a
|
|
6336
|
+
* request/response transport opens no long-lived connection). The
|
|
6337
|
+
* `mcp-session-id` response header, when a stateful server sends one (on
|
|
6338
|
+
* `initialize`), is captured into `session` and then echoed as the
|
|
6339
|
+
* `mcp-session-id` request header on every subsequent request — so an
|
|
6340
|
+
* `MCPClient` passes a stateful server's session validation. The
|
|
6341
|
+
* initialize result's `protocolVersion` is likewise captured, but only
|
|
6342
|
+
* when it is a supported value, and echoed as `mcp-protocol-version` alone on
|
|
6343
|
+
* subsequent legacy requests. Modern requests instead derive protocol and method
|
|
6344
|
+
* headers from the message, plus the name header only for `tools/call` — carried in the
|
|
6345
|
+
* protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
|
|
6346
|
+
* Before initialize returns, neither captured legacy header is sent.
|
|
6347
|
+
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
6348
|
+
* POST is headerless; the captured `session` persists across `close()`.
|
|
6349
|
+
* - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
|
|
6350
|
+
* aborted, which cancels the response body a `send` is reading — an SSE reply the server
|
|
6351
|
+
* never ends would otherwise outlive the transport, with nothing left able to reach it. The
|
|
6352
|
+
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
6353
|
+
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
6354
|
+
* - **Total at the boundary, and a non-success reply rejects.** Every reply is narrowed
|
|
6355
|
+
* (`parseJSONRPCMessage`, the SSE decoder). A non-message success reply is dropped, never
|
|
6356
|
+
* asserted. A non-success reply that carries no valid JSON-RPC message rejects `send` with
|
|
6357
|
+
* an error naming its HTTP status and body shape — the peer answered, and answering the
|
|
6358
|
+
* caller's request with silence would leave it waiting out its own deadline for a failure
|
|
6359
|
+
* the transport already read. A valid JSON-RPC error body is emitted at any HTTP status,
|
|
6360
|
+
* because the protocol carries that outcome in band. A `fetch` or decode failure on a
|
|
6361
|
+
* success response surfaces on the `error` event rather than escaping `send`.
|
|
6362
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); fires
|
|
6363
|
+
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
6364
|
+
*
|
|
6365
|
+
* @example
|
|
6366
|
+
* ```ts
|
|
6367
|
+
* const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
|
|
6368
|
+
* const client = new MCPClient({ transport })
|
|
6369
|
+
* await client.connect()
|
|
6370
|
+
* ```
|
|
6371
|
+
*/
|
|
6372
|
+
var HTTPClientTransport = class {
|
|
6373
|
+
#emitter;
|
|
6374
|
+
#url;
|
|
6375
|
+
#headers;
|
|
6376
|
+
#fetch;
|
|
6377
|
+
#timeout;
|
|
6378
|
+
#pending = /* @__PURE__ */ new Set();
|
|
6379
|
+
#parameters = /* @__PURE__ */ new Map();
|
|
6380
|
+
#stamps = /* @__PURE__ */ new WeakMap();
|
|
6381
|
+
#session = void 0;
|
|
6382
|
+
#protocol = void 0;
|
|
6383
|
+
#generation = 0;
|
|
6384
|
+
#closed = false;
|
|
6385
|
+
constructor(options) {
|
|
6386
|
+
this.#emitter = new _orkestrel_emitter.Emitter();
|
|
6387
|
+
this.#url = options.url;
|
|
6388
|
+
this.#headers = options.headers ?? {};
|
|
6389
|
+
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
6390
|
+
this.#timeout = options.timeout;
|
|
6391
|
+
}
|
|
6392
|
+
get emitter() {
|
|
6393
|
+
return this.#emitter;
|
|
6394
|
+
}
|
|
6395
|
+
get session() {
|
|
6396
|
+
return this.#session;
|
|
6397
|
+
}
|
|
6398
|
+
get duplex() {
|
|
6399
|
+
return false;
|
|
6400
|
+
}
|
|
6401
|
+
async start() {
|
|
6402
|
+
this.#closed = false;
|
|
6403
|
+
}
|
|
6404
|
+
async send(message) {
|
|
6405
|
+
this.#stamp(message);
|
|
6406
|
+
const request = new AbortController();
|
|
6407
|
+
this.#pending.add(request);
|
|
6408
|
+
try {
|
|
6409
|
+
await this.#exchange(message, request.signal);
|
|
6410
|
+
} finally {
|
|
6411
|
+
this.#pending.delete(request);
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
async close() {
|
|
6415
|
+
if (this.#closed) return;
|
|
6416
|
+
this.#closed = true;
|
|
6417
|
+
for (const request of this.#pending) request.abort();
|
|
6418
|
+
this.#pending.clear();
|
|
6419
|
+
this.#protocol = void 0;
|
|
6420
|
+
this.#emitter.emit("close");
|
|
6421
|
+
}
|
|
6422
|
+
#stamp(message) {
|
|
6423
|
+
if (!isModernRequest(message) || message.method !== "tools/list") return;
|
|
6424
|
+
if (message.params?.["cursor"] === void 0) this.#generation += 1;
|
|
6425
|
+
this.#stamps.set(message, this.#generation);
|
|
6426
|
+
}
|
|
6427
|
+
async #exchange(message, signal) {
|
|
6428
|
+
let response;
|
|
6429
|
+
try {
|
|
6430
|
+
response = await this.#fetch(this.#url, {
|
|
6431
|
+
method: "POST",
|
|
6432
|
+
headers: {
|
|
6433
|
+
"content-type": "application/json",
|
|
6434
|
+
accept: "application/json, text/event-stream",
|
|
6435
|
+
...this.#session === void 0 ? {} : { [MCP_SESSION_HEADER]: this.#session },
|
|
6436
|
+
...this.#buildHeaders(message),
|
|
6437
|
+
...this.#headers
|
|
6438
|
+
},
|
|
6439
|
+
body: JSON.stringify(message),
|
|
6440
|
+
signal: this.#timeout === void 0 ? signal : AbortSignal.any([signal, AbortSignal.timeout(this.#timeout)])
|
|
6441
|
+
});
|
|
6442
|
+
} catch (error) {
|
|
6443
|
+
this.#emitter.emit("error", error);
|
|
6444
|
+
return;
|
|
6445
|
+
}
|
|
6446
|
+
const session = response.headers.get(MCP_SESSION_HEADER);
|
|
6447
|
+
if (session !== null) this.#session = session;
|
|
6448
|
+
await this.#deliver(response, message);
|
|
6449
|
+
}
|
|
6450
|
+
#buildHeaders(message) {
|
|
6451
|
+
if (isModernRequest(message)) {
|
|
6452
|
+
const version = inferRequestVersion(message);
|
|
6453
|
+
const name = message.params?.["name"];
|
|
6454
|
+
return {
|
|
6455
|
+
...version === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version },
|
|
6456
|
+
[MCP_METHOD_HEADER]: message.method,
|
|
6457
|
+
...message.method === "tools/call" && (0, _orkestrel_contract.isString)(name) ? {
|
|
6458
|
+
[MCP_NAME_HEADER]: encodeSentinel(name),
|
|
6459
|
+
...buildHeaderProjection(this.#parameters.get(name) ?? [], message.params?.["arguments"])
|
|
6460
|
+
} : {}
|
|
6461
|
+
};
|
|
6462
|
+
}
|
|
6463
|
+
return this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol };
|
|
6464
|
+
}
|
|
6465
|
+
async #deliver(response, sent) {
|
|
6466
|
+
if (response.status === 202) return;
|
|
6467
|
+
const type = response.headers.get("content-type") ?? "";
|
|
6468
|
+
let messages = [];
|
|
6469
|
+
let failure;
|
|
6470
|
+
try {
|
|
6471
|
+
if (type.includes("text/event-stream")) messages = await readEventStream(response);
|
|
6472
|
+
else if (type.includes("application/json")) {
|
|
6473
|
+
const message = parseJSONRPCMessage(await response.json());
|
|
6474
|
+
if (message !== void 0) messages = [message];
|
|
6475
|
+
}
|
|
6476
|
+
} catch (error) {
|
|
6477
|
+
failure = { error };
|
|
6478
|
+
}
|
|
6479
|
+
for (const message of messages) this.#capture(message, sent);
|
|
6480
|
+
if (!response.ok && messages.length === 0) throw buildResponseError(response, type);
|
|
6481
|
+
if (failure !== void 0) this.#emitter.emit("error", failure.error);
|
|
6482
|
+
}
|
|
6483
|
+
#capture(message, sent) {
|
|
6484
|
+
if (isJSONRPCResponse(message) && (0, _orkestrel_contract.isRecord)(message.result) && isMCPVersion(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
|
|
6485
|
+
this.#emitter.emit("message", this.#select(message, sent));
|
|
6486
|
+
}
|
|
6487
|
+
#select(message, sent) {
|
|
6488
|
+
if (!isModernRequest(sent) || sent.method !== "tools/list") return message;
|
|
6489
|
+
if (!isJSONRPCResponse(message) || message.error !== void 0) return message;
|
|
6490
|
+
const result = message.result;
|
|
6491
|
+
const listed = (0, _orkestrel_contract.isRecord)(result) ? result["tools"] : void 0;
|
|
6492
|
+
if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isArray)(listed)) return message;
|
|
6493
|
+
const current = this.#stamps.get(sent) === this.#generation;
|
|
6494
|
+
if (current && sent.params?.["cursor"] === void 0) this.#parameters.clear();
|
|
6495
|
+
const kept = [];
|
|
6496
|
+
for (const tool of listed) {
|
|
6497
|
+
if (!(0, _orkestrel_contract.isRecord)(tool) || !(0, _orkestrel_contract.isString)(tool["name"])) {
|
|
6498
|
+
kept.push(tool);
|
|
6499
|
+
continue;
|
|
6500
|
+
}
|
|
6501
|
+
const parameters = buildHeaderParameters(tool["inputSchema"]);
|
|
6502
|
+
if (parameters === void 0) {
|
|
6503
|
+
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`));
|
|
6504
|
+
continue;
|
|
6505
|
+
}
|
|
6506
|
+
if (current) this.#parameters.set(tool["name"], parameters);
|
|
6507
|
+
kept.push(tool);
|
|
6508
|
+
}
|
|
6509
|
+
return {
|
|
6510
|
+
...message,
|
|
6511
|
+
result: {
|
|
6512
|
+
...result,
|
|
6513
|
+
tools: kept
|
|
6514
|
+
}
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
};
|
|
6518
|
+
//#endregion
|
|
6075
6519
|
//#region src/core/factories.ts
|
|
6076
6520
|
/**
|
|
6077
6521
|
* Creates a transport-agnostic Model Context Protocol server — exposes a live
|
|
6078
6522
|
* {@link import('@orkestrel/tool').ToolManagerInterface} and an optional
|
|
6079
6523
|
* {@link import('./types.js').MCPResourceManagerInterface},
|
|
6080
6524
|
* {@link import('./types.js').MCPPromptManagerInterface}, and
|
|
6081
|
-
* {@link import('./types.js').
|
|
6525
|
+
* {@link import('./types.js').MCPCompletionInterface} over JSON-RPC 2.0.
|
|
6082
6526
|
*
|
|
6083
6527
|
* @remarks
|
|
6084
6528
|
* Pump raw message strings through `handle` (parse → dispatch → serialize) from a
|
|
@@ -6097,20 +6541,35 @@ var MCPClient = class {
|
|
|
6097
6541
|
* {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPServerOptions})
|
|
6098
6542
|
* @returns A working {@link MCPServerInterface}
|
|
6099
6543
|
*
|
|
6100
|
-
* @example
|
|
6544
|
+
* @example Expose a tool registry over MCP
|
|
6101
6545
|
* ```ts
|
|
6102
6546
|
* import { createMCPServer } from '@orkestrel/mcp'
|
|
6103
6547
|
* import { createTool, createToolManager } from '@orkestrel/tool'
|
|
6104
6548
|
*
|
|
6105
6549
|
* const tools = createToolManager()
|
|
6550
|
+
* tools.add(
|
|
6551
|
+
* createTool({
|
|
6552
|
+
* name: 'search',
|
|
6553
|
+
* description: 'Search the docs',
|
|
6554
|
+
* execute: (a) => find(String(a.query)),
|
|
6555
|
+
* }),
|
|
6556
|
+
* )
|
|
6106
6557
|
* tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
|
|
6107
6558
|
*
|
|
6108
|
-
* const server = createMCPServer({ identity: { name: '
|
|
6559
|
+
* const server = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools })
|
|
6109
6560
|
* server.emitter.on('request', (method, id) => log(method, id))
|
|
6110
6561
|
*
|
|
6111
|
-
* // A transport
|
|
6112
|
-
* const
|
|
6113
|
-
*
|
|
6562
|
+
* // A transport reads a framed message string and writes the reply:
|
|
6563
|
+
* for await (const message of transport) {
|
|
6564
|
+
* const reply = await server.handle(message)
|
|
6565
|
+
* if (reply !== undefined) await transport.send(reply) // a notification has no reply
|
|
6566
|
+
* }
|
|
6567
|
+
*
|
|
6568
|
+
* // `handle` also answers one message string on its own:
|
|
6569
|
+
* const listed = await server.handle(
|
|
6570
|
+
* '{"jsonrpc":"2.0","method":"tools/list","id":1,"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}',
|
|
6571
|
+
* )
|
|
6572
|
+
* // 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"}}}}'
|
|
6114
6573
|
* ```
|
|
6115
6574
|
*/
|
|
6116
6575
|
function createMCPServer(options) {
|
|
@@ -6119,6 +6578,10 @@ function createMCPServer(options) {
|
|
|
6119
6578
|
/**
|
|
6120
6579
|
* Decorates one MCP server with the fixed legacy method translation.
|
|
6121
6580
|
*
|
|
6581
|
+
* @remarks
|
|
6582
|
+
* Adds support for the `2025-11-25` and `2025-06-18` legacy revisions. Removing this one
|
|
6583
|
+
* decorator removes that legacy surface while leaving the modern dispatcher unchanged.
|
|
6584
|
+
*
|
|
6122
6585
|
* @param server - The sole modern dispatcher and handshake identity source
|
|
6123
6586
|
* @returns A dispatcher accepting both modern and legacy invocations
|
|
6124
6587
|
*/
|
|
@@ -6129,14 +6592,14 @@ function createMCPLegacy(server) {
|
|
|
6129
6592
|
});
|
|
6130
6593
|
}
|
|
6131
6594
|
/**
|
|
6132
|
-
* Creates a transport-agnostic Model Context Protocol
|
|
6133
|
-
* MCP server over an injected {@link import('./types.js').
|
|
6595
|
+
* Creates a transport-agnostic Model Context Protocol client — connects to a remote
|
|
6596
|
+
* MCP server over an injected {@link import('./types.js').MCPMessageTransportInterface},
|
|
6134
6597
|
* negotiates the modern revision through `server/discover`, and exposes the server's tools as local
|
|
6135
6598
|
* {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
|
|
6136
6599
|
*
|
|
6137
6600
|
* @remarks
|
|
6138
6601
|
* The egress mirror of {@link createMCPServer}: where the server exposes a local tool
|
|
6139
|
-
* registry over MCP, the client
|
|
6602
|
+
* registry over MCP, the client uses a remote server's tools. `connect()` discovers,
|
|
6140
6603
|
* validates, and exposes the negotiated modern protocol; a legacy peer requires
|
|
6141
6604
|
* {@link createMCPLegacyClientTransport}. `tools()` lists + wraps the remote
|
|
6142
6605
|
* tools (each `execute` calls back over the wire),
|
|
@@ -6146,7 +6609,7 @@ function createMCPLegacy(server) {
|
|
|
6146
6609
|
* `fetch`) lives in the published server environment; the client itself is provider-agnostic. Subscribe
|
|
6147
6610
|
* to `connect` / `disconnect` / `notification` through `client.emitter.on(...)`.
|
|
6148
6611
|
*
|
|
6149
|
-
* @param options - `transport` (the carrier;
|
|
6612
|
+
* @param options - `transport` (the carrier; required), an optional `identity`
|
|
6150
6613
|
* (the client identity), `timeout` (the per-request deadline), and the reserved `on`
|
|
6151
6614
|
* {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPClientOptions})
|
|
6152
6615
|
* @returns A working {@link MCPClientInterface}
|
|
@@ -6161,7 +6624,7 @@ function createMCPLegacy(server) {
|
|
|
6161
6624
|
* })
|
|
6162
6625
|
* await client.connect()
|
|
6163
6626
|
* agent.context.tools.add(await client.tools()) // give the agent the remote tools
|
|
6164
|
-
* const
|
|
6627
|
+
* const outcome = await client.call('search', { query: 'mcp' })
|
|
6165
6628
|
* ```
|
|
6166
6629
|
*/
|
|
6167
6630
|
function createMCPClient(options) {
|
|
@@ -6187,12 +6650,12 @@ function createMCPLegacyClientTransport(transport, options) {
|
|
|
6187
6650
|
}
|
|
6188
6651
|
/**
|
|
6189
6652
|
* Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
6190
|
-
* channel) into a {@link
|
|
6653
|
+
* channel) into a {@link MCPMessageTransportInterface} — the additive bridge that lets
|
|
6191
6654
|
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
6192
6655
|
* existing shape.
|
|
6193
6656
|
*
|
|
6194
6657
|
* @remarks
|
|
6195
|
-
* Hand the
|
|
6658
|
+
* Hand the result to `createMCPClient({ transport })`, then pass the same
|
|
6196
6659
|
* `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
|
|
6197
6660
|
* wiring: `send` serializes each outbound {@link JSONRPCMessage} and writes it through
|
|
6198
6661
|
* `transport.send`; `close` closes the underlying
|
|
@@ -6200,17 +6663,17 @@ function createMCPLegacyClientTransport(transport, options) {
|
|
|
6200
6663
|
* it is handed in — there is no separate connect step at this layer); `session` is
|
|
6201
6664
|
* always `undefined` (session correlation is a higher-level concern the duplex port
|
|
6202
6665
|
* does not carry); and `duplex` is always `true`, because carrying frames in both
|
|
6203
|
-
* directions at any moment is exactly what the adapted port is — a claim
|
|
6666
|
+
* directions at any moment is exactly what the adapted port is — a claim driven over a real
|
|
6204
6667
|
* `MessageChannel` and a real scope pair (a client-initiated `notifications/cancelled`
|
|
6205
6668
|
* observed arriving at the peer) rather than read back off this literal. The literal is
|
|
6206
|
-
* true of the
|
|
6669
|
+
* true of the port, and stays true only while the port has a peer: close the far half and
|
|
6207
6670
|
* this transport still declares `true` while carrying nothing, which is the one thing a
|
|
6208
6671
|
* per-carrier declaration cannot express. Inbound delivery (`emitter`'s `message` / `close` events) is
|
|
6209
6672
|
* `bindClient`'s job, not this factory's — the returned object exposes a `message`-
|
|
6210
6673
|
* capable emitter for `bindClient` to push onto.
|
|
6211
6674
|
*
|
|
6212
6675
|
* @param transport - The duplex channel to adapt
|
|
6213
|
-
* @returns A {@link
|
|
6676
|
+
* @returns A {@link MCPMessageTransportInterface} `createMCPClient` can drive
|
|
6214
6677
|
*
|
|
6215
6678
|
* @example
|
|
6216
6679
|
* ```ts
|
|
@@ -6241,6 +6704,7 @@ exports.DEFAULT_MCP_LIMITS = DEFAULT_MCP_LIMITS;
|
|
|
6241
6704
|
exports.DEFAULT_MCP_REQUEST_TIMEOUT = DEFAULT_MCP_REQUEST_TIMEOUT;
|
|
6242
6705
|
exports.DEFAULT_MCP_SUBSCRIPTION_CAPACITY = DEFAULT_MCP_SUBSCRIPTION_CAPACITY;
|
|
6243
6706
|
exports.EMPTY_MCP_ARGUMENTS = EMPTY_MCP_ARGUMENTS;
|
|
6707
|
+
exports.HTTPClientTransport = HTTPClientTransport;
|
|
6244
6708
|
exports.JSONRPC_INTERNAL_ERROR = JSONRPC_INTERNAL_ERROR;
|
|
6245
6709
|
exports.JSONRPC_INVALID_PARAMS = JSONRPC_INVALID_PARAMS;
|
|
6246
6710
|
exports.JSONRPC_INVALID_REQUEST = JSONRPC_INVALID_REQUEST;
|
|
@@ -6268,12 +6732,17 @@ exports.MCP_META_CLIENT = MCP_META_CLIENT;
|
|
|
6268
6732
|
exports.MCP_META_SERVER = MCP_META_SERVER;
|
|
6269
6733
|
exports.MCP_META_SUBSCRIPTION = MCP_META_SUBSCRIPTION;
|
|
6270
6734
|
exports.MCP_META_VERSION = MCP_META_VERSION;
|
|
6735
|
+
exports.MCP_METHOD_HEADER = MCP_METHOD_HEADER;
|
|
6271
6736
|
exports.MCP_MISSING_CAPABILITY = MCP_MISSING_CAPABILITY;
|
|
6272
6737
|
exports.MCP_MODERN_VERSION = MCP_MODERN_VERSION;
|
|
6738
|
+
exports.MCP_NAME_HEADER = MCP_NAME_HEADER;
|
|
6273
6739
|
exports.MCP_PARAM_PREFIX = MCP_PARAM_PREFIX;
|
|
6740
|
+
exports.MCP_PROTOCOL_VERSION_HEADER = MCP_PROTOCOL_VERSION_HEADER;
|
|
6274
6741
|
exports.MCP_SENTINEL_PREFIX = MCP_SENTINEL_PREFIX;
|
|
6275
6742
|
exports.MCP_SENTINEL_SUFFIX = MCP_SENTINEL_SUFFIX;
|
|
6743
|
+
exports.MCP_SESSION_HEADER = MCP_SESSION_HEADER;
|
|
6276
6744
|
exports.MCP_UNSUPPORTED_VERSION = MCP_UNSUPPORTED_VERSION;
|
|
6745
|
+
exports.MCP_WEBSOCKET_SUBPROTOCOL = MCP_WEBSOCKET_SUBPROTOCOL;
|
|
6277
6746
|
exports.SUPPORTED_LEGACY_PROTOCOL_VERSIONS = SUPPORTED_LEGACY_PROTOCOL_VERSIONS;
|
|
6278
6747
|
exports.SUPPORTED_MCP_VERSIONS = SUPPORTED_MCP_VERSIONS;
|
|
6279
6748
|
exports.SUPPORTED_MODERN_PROTOCOL_VERSIONS = SUPPORTED_MODERN_PROTOCOL_VERSIONS;
|
|
@@ -6290,6 +6759,7 @@ exports.buildJSONRPCResult = buildJSONRPCResult;
|
|
|
6290
6759
|
exports.buildMethodOptions = buildMethodOptions;
|
|
6291
6760
|
exports.buildModernResult = buildModernResult;
|
|
6292
6761
|
exports.buildProgressNotification = buildProgressNotification;
|
|
6762
|
+
exports.buildResponseError = buildResponseError;
|
|
6293
6763
|
exports.buildSubscriptionAcknowledgement = buildSubscriptionAcknowledgement;
|
|
6294
6764
|
exports.buildSubscriptionFilter = buildSubscriptionFilter;
|
|
6295
6765
|
exports.buildSubscriptionResult = buildSubscriptionResult;
|
|
@@ -6303,13 +6773,16 @@ exports.createMCPLegacy = createMCPLegacy;
|
|
|
6303
6773
|
exports.createMCPLegacyClientTransport = createMCPLegacyClientTransport;
|
|
6304
6774
|
exports.createMCPServer = createMCPServer;
|
|
6305
6775
|
exports.decodeBoundedMessage = decodeBoundedMessage;
|
|
6776
|
+
exports.decodeEvent = decodeEvent;
|
|
6306
6777
|
exports.decodeSentinel = decodeSentinel;
|
|
6778
|
+
exports.deliverMessage = deliverMessage;
|
|
6307
6779
|
exports.digestJSON = digestJSON;
|
|
6308
6780
|
exports.encodeSentinel = encodeSentinel;
|
|
6309
6781
|
exports.extractContentText = extractContentText;
|
|
6310
6782
|
exports.extractHeaderAnnotations = extractHeaderAnnotations;
|
|
6311
6783
|
exports.extractToolSchema = extractToolSchema;
|
|
6312
6784
|
exports.inferEra = inferEra;
|
|
6785
|
+
exports.inferRequestEra = inferRequestEra;
|
|
6313
6786
|
exports.inferRequestVersion = inferRequestVersion;
|
|
6314
6787
|
exports.inferVersion = inferVersion;
|
|
6315
6788
|
exports.isAbsoluteURI = isAbsoluteURI;
|
|
@@ -6317,7 +6790,6 @@ exports.isBoundedJSON = isBoundedJSON;
|
|
|
6317
6790
|
exports.isBoundedString = isBoundedString;
|
|
6318
6791
|
exports.isElicitContent = isElicitContent;
|
|
6319
6792
|
exports.isFieldToken = isFieldToken;
|
|
6320
|
-
exports.isFormElicitationSupported = isFormElicitationSupported;
|
|
6321
6793
|
exports.isInitializeRequest = isInitializeRequest;
|
|
6322
6794
|
exports.isJSONObject = isJSONObject;
|
|
6323
6795
|
exports.isJSONRPCError = isJSONRPCError;
|
|
@@ -6392,7 +6864,6 @@ exports.isModernRequest = isModernRequest;
|
|
|
6392
6864
|
exports.isRFC3339Date = isRFC3339Date;
|
|
6393
6865
|
exports.isRFC3339DateTime = isRFC3339DateTime;
|
|
6394
6866
|
exports.isStandardBase64 = isStandardBase64;
|
|
6395
|
-
exports.isTaskSupported = isTaskSupported;
|
|
6396
6867
|
exports.legacyInvocationToModern = legacyInvocationToModern;
|
|
6397
6868
|
exports.legacyResultToModern = legacyResultToModern;
|
|
6398
6869
|
exports.matchesResultType = matchesResultType;
|
|
@@ -6403,11 +6874,14 @@ exports.parseJSONRPCMessage = parseJSONRPCMessage;
|
|
|
6403
6874
|
exports.parseMCPInputState = parseMCPInputState;
|
|
6404
6875
|
exports.parseRequestContext = parseRequestContext;
|
|
6405
6876
|
exports.readCancelledId = readCancelledId;
|
|
6877
|
+
exports.readEventStream = readEventStream;
|
|
6406
6878
|
exports.renderHeaderValue = renderHeaderValue;
|
|
6407
6879
|
exports.sendStream = sendStream;
|
|
6408
6880
|
exports.serializeJSON = serializeJSON;
|
|
6409
6881
|
exports.snapshotJSON = snapshotJSON;
|
|
6410
6882
|
exports.snapshotToolResult = snapshotToolResult;
|
|
6411
6883
|
exports.stampSubscriptionNotification = stampSubscriptionNotification;
|
|
6884
|
+
exports.supportsFormElicitation = supportsFormElicitation;
|
|
6885
|
+
exports.supportsTask = supportsTask;
|
|
6412
6886
|
|
|
6413
6887
|
//# sourceMappingURL=index.cjs.map
|