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