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