@orkestrel/mcp 0.0.19 → 0.0.21
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 +23 -22
- package/dist/src/browser/index.d.ts +92 -70
- package/dist/src/browser/index.js +164 -81
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +269 -257
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +351 -358
- package/dist/src/core/index.d.ts +351 -358
- package/dist/src/core/index.js +270 -257
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +331 -173
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +266 -146
- package/dist/src/server/index.d.ts +266 -146
- package/dist/src/server/index.js +331 -173
- package/dist/src/server/index.js.map +1 -1
- package/package.json +13 -10
package/dist/src/core/index.cjs
CHANGED
|
@@ -57,11 +57,11 @@ var MCP_HEADER_MISMATCH = -32020;
|
|
|
57
57
|
*
|
|
58
58
|
* @remarks
|
|
59
59
|
* The GENERIC code for the whole condition, not one capability's code. This server answers
|
|
60
|
-
* it in
|
|
61
|
-
* client never declared `io.modelcontextprotocol/tasks` — and
|
|
60
|
+
* it in more than one place — a `tools/call` that needs `elicitation`, and a `tasks/*` request
|
|
61
|
+
* whose client never declared `io.modelcontextprotocol/tasks` — and they are told apart by
|
|
62
62
|
* `error.data.requiredCapabilities` alone (`{ elicitation: {} }` against
|
|
63
|
-
* `{ extensions: { 'io.modelcontextprotocol/tasks': {} } }`). They are
|
|
64
|
-
* condition, so a
|
|
63
|
+
* `{ extensions: { 'io.modelcontextprotocol/tasks': {} } }`). They are instances of the same
|
|
64
|
+
* condition, so a separate numeral would describe the same fact twice. The Tasks extension's
|
|
65
65
|
* own draft prose still shows `-32003` in examples; the dated core schema fixes this code,
|
|
66
66
|
* and the dated schema is what a peer implements against.
|
|
67
67
|
*/
|
|
@@ -148,20 +148,18 @@ var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
|
|
|
148
148
|
* is unset — a request the remote server does not answer within it rejects.
|
|
149
149
|
*/
|
|
150
150
|
var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
|
|
151
|
-
/** The maximum discovery-probe deadline used when a client deadline is configured. */
|
|
152
|
-
var DEFAULT_MCP_PROBE_TIMEOUT = 50;
|
|
153
151
|
//#endregion
|
|
154
152
|
//#region src/core/errors.ts
|
|
155
153
|
/**
|
|
156
|
-
* A
|
|
157
|
-
*
|
|
154
|
+
* A Model Context Protocol error preserving its machine-readable numeric code and
|
|
155
|
+
* optional structured context.
|
|
158
156
|
*
|
|
159
157
|
* @remarks
|
|
160
|
-
* {@link MCPClient} throws this error
|
|
161
|
-
* Local lifecycle and transport conditions such
|
|
162
|
-
* remain plain `Error`s.
|
|
163
|
-
* unchanged and is `undefined` when the peer omitted it.
|
|
164
|
-
* reserved paths: `-32020` carries no context, `-32021` may carry
|
|
158
|
+
* {@link MCPClient} throws this error for a remote JSON-RPC `error` response and for a
|
|
159
|
+
* locally detected protocol incompatibility. Local lifecycle and transport conditions such
|
|
160
|
+
* as disconnects and request timeouts remain plain `Error`s. For a remote response, `context`
|
|
161
|
+
* carries the optional `error.data` unchanged and is `undefined` when the peer omitted it.
|
|
162
|
+
* This includes the modern reserved paths: `-32020` carries no context, `-32021` may carry
|
|
165
163
|
* `requiredCapabilities`, and `-32022` carries the peer's `supported` revisions and
|
|
166
164
|
* `requested` revision for negotiation recovery.
|
|
167
165
|
*
|
|
@@ -180,7 +178,7 @@ var MCPError = class extends Error {
|
|
|
180
178
|
code;
|
|
181
179
|
context;
|
|
182
180
|
/**
|
|
183
|
-
*
|
|
181
|
+
* Creates an MCP protocol error.
|
|
184
182
|
*
|
|
185
183
|
* @param message - The human-readable JSON-RPC error message
|
|
186
184
|
* @param code - The machine-readable numeric JSON-RPC error code
|
|
@@ -193,7 +191,7 @@ var MCPError = class extends Error {
|
|
|
193
191
|
}
|
|
194
192
|
};
|
|
195
193
|
/**
|
|
196
|
-
*
|
|
194
|
+
* Determines whether an unknown value is an {@link MCPError}.
|
|
197
195
|
*
|
|
198
196
|
* @param value - The unknown value to inspect
|
|
199
197
|
* @returns `true` only when the value is an `MCPError`
|
|
@@ -214,7 +212,7 @@ function isMCPError(value) {
|
|
|
214
212
|
//#endregion
|
|
215
213
|
//#region src/core/inferers.ts
|
|
216
214
|
/**
|
|
217
|
-
*
|
|
215
|
+
* Infers the wire era for an MCP protocol revision.
|
|
218
216
|
*
|
|
219
217
|
* @param version - The protocol revision to classify
|
|
220
218
|
* @returns `'modern'` for `2026-07-28`, `'legacy'` for either supported legacy
|
|
@@ -229,7 +227,7 @@ function inferEra(version) {
|
|
|
229
227
|
}
|
|
230
228
|
}
|
|
231
229
|
/**
|
|
232
|
-
*
|
|
230
|
+
* Infers the newest supported protocol revision present in a peer's offer.
|
|
233
231
|
*
|
|
234
232
|
* @param offered - The protocol revisions offered by the peer
|
|
235
233
|
* @returns The newest locally supported offered revision, or `undefined`
|
|
@@ -238,12 +236,12 @@ function inferVersion(offered) {
|
|
|
238
236
|
for (const version of SUPPORTED_PROTOCOL_VERSIONS) if (offered.includes(version)) return version;
|
|
239
237
|
}
|
|
240
238
|
/**
|
|
241
|
-
*
|
|
239
|
+
* Infers the protocol version an outbound message announces itself with — the ONE
|
|
242
240
|
* projection every HTTP client transport stamps `mcp-protocol-version` from.
|
|
243
241
|
*
|
|
244
242
|
* @remarks
|
|
245
243
|
* This is deliberately the SAME read the server's own expectation performs
|
|
246
|
-
* ({@link import('@
|
|
244
|
+
* ({@link import('@orkestrel/mcp/server').inferHeaderIssue}): a modern request's reserved
|
|
247
245
|
* `_meta` version, accepted whenever it is a string. It is NOT
|
|
248
246
|
* {@link import('./parsers.js').parseRequestContext}, and the difference is the whole
|
|
249
247
|
* point. That parser answers a different question — is the modern metadata WELL FORMED —
|
|
@@ -256,7 +254,7 @@ function inferVersion(offered) {
|
|
|
256
254
|
* `initialize` handshake the transport captured, not from the message.
|
|
257
255
|
*
|
|
258
256
|
* Header NAMES stay with the transports that own the wire (see `constants.ts`); core owns
|
|
259
|
-
* the value this projection derives, which is the part the
|
|
257
|
+
* the value this projection derives, which is the part the browser and Node faces disagreed about.
|
|
260
258
|
*
|
|
261
259
|
* @param message - The outbound message about to be written
|
|
262
260
|
* @returns The version to announce, or `undefined` when the message announces none
|
|
@@ -274,7 +272,7 @@ function inferRequestVersion(message) {
|
|
|
274
272
|
//#endregion
|
|
275
273
|
//#region src/core/cloners.ts
|
|
276
274
|
/**
|
|
277
|
-
*
|
|
275
|
+
* Snapshots one bounded exact JSON value together with its canonical wire serialization.
|
|
278
276
|
*
|
|
279
277
|
* The returned value is an owned, deeply frozen graph reconstructed from the canonical text;
|
|
280
278
|
* the frozen tuple shares no mutable structure with the input. Invalid exact-JSON shapes,
|
|
@@ -304,10 +302,10 @@ function snapshotJSON(value, limits) {
|
|
|
304
302
|
return Object.freeze(snapshot);
|
|
305
303
|
}
|
|
306
304
|
/**
|
|
307
|
-
*
|
|
305
|
+
* Snapshots one exact Tool result and the canonical wire text of a defined success value.
|
|
308
306
|
*
|
|
309
|
-
* A success must have exactly
|
|
310
|
-
* and `value`. A failure must instead have exactly `id`, `name`, `success: false`, and a string
|
|
307
|
+
* A success must have exactly the own enumerable data properties `id`, `name`,
|
|
308
|
+
* `success: true`, and `value`. A failure must instead have exactly `id`, `name`, `success: false`, and a string
|
|
311
309
|
* `error`. The returned result and tuple are frozen. Only a defined success value crosses the
|
|
312
310
|
* bounded JSON ownership seam; it becomes an owned deeply frozen value and receives canonical
|
|
313
311
|
* text. Value-less successes and failures pair with `undefined` text. Non-records, symbol keys,
|
|
@@ -382,11 +380,11 @@ function snapshotToolResult(value, limits) {
|
|
|
382
380
|
//#endregion
|
|
383
381
|
//#region src/core/parsers.ts
|
|
384
382
|
/**
|
|
385
|
-
*
|
|
383
|
+
* Narrows an already-parsed value to a {@link JSONRPCMessage}, or `undefined` when
|
|
386
384
|
* it is not one.
|
|
387
385
|
*
|
|
388
386
|
* @remarks
|
|
389
|
-
* Total
|
|
387
|
+
* Total — a non-message returns `undefined`, never throws. The input must
|
|
390
388
|
* ALREADY be `JSON.parse`d: the raw-string parse (which can throw on malformed
|
|
391
389
|
* JSON) happens in `MCPServer.handle` inside a try/catch that maps a parse failure
|
|
392
390
|
* to a `-32700` response.
|
|
@@ -400,7 +398,7 @@ function snapshotToolResult(value, limits) {
|
|
|
400
398
|
* result. Identity is not preserved and is not promised.
|
|
401
399
|
*
|
|
402
400
|
* The parser's sound partner is the COMPOSITE `isJSONRPCMessage(value) &&
|
|
403
|
-
* isBoundedJSON(value, limits)`, and against it both halves of the
|
|
401
|
+
* isBoundedJSON(value, limits)`, and against it both halves of the soundness law
|
|
404
402
|
* hold by construction:
|
|
405
403
|
*
|
|
406
404
|
* - Every non-`undefined` result satisfies {@link isJSONRPCMessage}, because the guard
|
|
@@ -436,7 +434,7 @@ function parseJSONRPCMessage(value, limits = {
|
|
|
436
434
|
return snapshot !== void 0 && isJSONRPCMessage(snapshot[0]) ? snapshot[0] : void 0;
|
|
437
435
|
}
|
|
438
436
|
/**
|
|
439
|
-
*
|
|
437
|
+
* Parses the reserved modern request metadata into an {@link MCPRequestContext}.
|
|
440
438
|
*
|
|
441
439
|
* @remarks
|
|
442
440
|
* This is the validity step after {@link isModernRequest}: a defined result can
|
|
@@ -486,7 +484,7 @@ function parseRequestContext(value, limits = {
|
|
|
486
484
|
}
|
|
487
485
|
}
|
|
488
486
|
/**
|
|
489
|
-
*
|
|
487
|
+
* Parses the opened value carried by an opaque `requestState` continuation.
|
|
490
488
|
*
|
|
491
489
|
* @remarks
|
|
492
490
|
* This parser does not open the opaque continuation carrier; the configured
|
|
@@ -544,7 +542,69 @@ function parseMCPInputState(value) {
|
|
|
544
542
|
//#endregion
|
|
545
543
|
//#region src/core/helpers.ts
|
|
546
544
|
/**
|
|
547
|
-
*
|
|
545
|
+
* Determines whether a client capability record declares form-mode elicitation.
|
|
546
|
+
*
|
|
547
|
+
* @remarks
|
|
548
|
+
* The protocol's empty `elicitation` object is the implicit form-only declaration.
|
|
549
|
+
* A non-empty declaration must carry a record-valued `form` member; URL-only support
|
|
550
|
+
* does not authorize a form request. Total over hostile input.
|
|
551
|
+
*
|
|
552
|
+
* @param value - The client capability record to inspect
|
|
553
|
+
* @returns `true` when form-mode elicitation is declared
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
|
|
558
|
+
* isFormElicitationSupported({ elicitation: { url: {} } }) // false
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
function isFormElicitationSupported(value) {
|
|
562
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
563
|
+
if (!owned.success) return false;
|
|
564
|
+
try {
|
|
565
|
+
const elicitation = owned.value["elicitation"];
|
|
566
|
+
if (!(0, _orkestrel_contract.isRecord)(elicitation)) return false;
|
|
567
|
+
if ((0, _orkestrel_contract.isRecord)(elicitation["form"])) return true;
|
|
568
|
+
return Object.keys(elicitation).length === 0;
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Determines whether a client capability record declares the draft Tasks extension.
|
|
575
|
+
*
|
|
576
|
+
* @remarks
|
|
577
|
+
* The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and its value is
|
|
578
|
+
* an empty object, so this is a PRESENCE check: the extension defines no options, and a
|
|
579
|
+
* server that read one would be reading a field no client can meaningfully set. The value
|
|
580
|
+
* must still be a record, because that is the shape the capability record declares — a
|
|
581
|
+
* `true` or a string there is a client speaking a different protocol, not a shorthand.
|
|
582
|
+
*
|
|
583
|
+
* A client declares this PER REQUEST. Nothing here consults a session, because the modern
|
|
584
|
+
* revision is stateless and a capability declared once at connect time says nothing about
|
|
585
|
+
* the request in hand. Total over hostile input.
|
|
586
|
+
*
|
|
587
|
+
* @param value - The client capability record to inspect
|
|
588
|
+
* @returns `true` when the tasks extension is declared
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```ts
|
|
592
|
+
* isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
|
|
593
|
+
* isTaskSupported({ extensions: {} }) // false — the key is the declaration
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
function isTaskSupported(value) {
|
|
597
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
598
|
+
if (!owned.success) return false;
|
|
599
|
+
try {
|
|
600
|
+
const extensions = owned.value["extensions"];
|
|
601
|
+
return (0, _orkestrel_contract.isRecord)(extensions) && (0, _orkestrel_contract.isRecord)(extensions["io.modelcontextprotocol/tasks"]);
|
|
602
|
+
} catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Serializes one exact JSON value deterministically within explicit bounds.
|
|
548
608
|
*
|
|
549
609
|
* @param value - The unknown value to validate and serialize
|
|
550
610
|
* @param limits - Serialized byte, key, and depth limits
|
|
@@ -763,7 +823,7 @@ function serializeJSON(value, limits) {
|
|
|
763
823
|
return serialized.success ? serialized.value : void 0;
|
|
764
824
|
}
|
|
765
825
|
/**
|
|
766
|
-
*
|
|
826
|
+
* Computes a lowercase host-neutral SHA-256 digest of one bounded canonical JSON value.
|
|
767
827
|
*
|
|
768
828
|
* @param value - The unknown value to validate and digest
|
|
769
829
|
* @param limits - Serialized byte, key, and depth limits
|
|
@@ -776,7 +836,7 @@ async function digestJSON(value, limits) {
|
|
|
776
836
|
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
777
837
|
}
|
|
778
838
|
/**
|
|
779
|
-
*
|
|
839
|
+
* Builds one official progress notification for the original request stream.
|
|
780
840
|
*
|
|
781
841
|
* @param token - The request's original opaque progress token
|
|
782
842
|
* @param progress - The finite progress payload
|
|
@@ -795,7 +855,7 @@ function buildProgressNotification(token, progress) {
|
|
|
795
855
|
};
|
|
796
856
|
}
|
|
797
857
|
/**
|
|
798
|
-
*
|
|
858
|
+
* Builds one official cancellation notification for a request already sent.
|
|
799
859
|
*
|
|
800
860
|
* @remarks
|
|
801
861
|
* `requestId` and `reason` are WIRE SPELLINGS carried verbatim from the dated schema's
|
|
@@ -833,15 +893,15 @@ function buildCancelledNotification(id, reason) {
|
|
|
833
893
|
};
|
|
834
894
|
}
|
|
835
895
|
/**
|
|
836
|
-
*
|
|
896
|
+
* Determines whether one method may answer with a given modern `resultType`.
|
|
837
897
|
*
|
|
838
898
|
* @remarks
|
|
839
|
-
* The dated protocol lets a `tools/call` answer
|
|
899
|
+
* The dated protocol lets a `tools/call` answer in more than one way — it COMPLETED, it became a
|
|
840
900
|
* durable task, or it needs another round trip — while every other method this client
|
|
841
901
|
* issues has exactly one legal answer. So the arm a peer chose is only meaningful beside
|
|
842
902
|
* the method it answers, and this is the one place that pairing is decided.
|
|
843
903
|
*
|
|
844
|
-
* The rule is deliberately
|
|
904
|
+
* The rule is deliberately closed: an unrecognized `resultType` is refused for every
|
|
845
905
|
* method, including `tools/call`. A client that carried an arm it cannot name would hand
|
|
846
906
|
* its caller a value whose meaning it invented.
|
|
847
907
|
*
|
|
@@ -862,7 +922,7 @@ function matchesResultType(method, resultType) {
|
|
|
862
922
|
return resultType === "task" || resultType === "input_required";
|
|
863
923
|
}
|
|
864
924
|
/**
|
|
865
|
-
*
|
|
925
|
+
* Concatenates an MCP tool-call result's text content blocks into one string.
|
|
866
926
|
*
|
|
867
927
|
* @remarks
|
|
868
928
|
* The inverse of a server splitting a value into text block(s), and TOTAL: a non-record
|
|
@@ -886,12 +946,12 @@ function extractContentText(result) {
|
|
|
886
946
|
return parts.join("\n");
|
|
887
947
|
}
|
|
888
948
|
/**
|
|
889
|
-
*
|
|
949
|
+
* Narrows one `tools/call` answer to the arm the peer chose.
|
|
890
950
|
*
|
|
891
951
|
* @remarks
|
|
892
|
-
* The
|
|
952
|
+
* The arms {@link matchesResultType} admits are the whole space this sees, because a
|
|
893
953
|
* `resultType` the client cannot name is refused at correlation. What is left is validating
|
|
894
|
-
* the
|
|
954
|
+
* the arms the protocol gives a shape to, and deriving the tool's value from the one it
|
|
895
955
|
* does not:
|
|
896
956
|
*
|
|
897
957
|
* - A peer's `structuredContent` is PREFERRED over the content blocks, because it is the
|
|
@@ -939,7 +999,7 @@ function buildCallOutcome(name, result) {
|
|
|
939
999
|
});
|
|
940
1000
|
}
|
|
941
1001
|
/**
|
|
942
|
-
*
|
|
1002
|
+
* Builds the canonical Tool call for one validated MCP `tools/call` request.
|
|
943
1003
|
*
|
|
944
1004
|
* @param request - The original MCP request
|
|
945
1005
|
* @param caller - Optional consumer-asserted caller context
|
|
@@ -958,7 +1018,7 @@ function buildToolCall(request, caller, args) {
|
|
|
958
1018
|
};
|
|
959
1019
|
}
|
|
960
1020
|
/**
|
|
961
|
-
*
|
|
1021
|
+
* Builds a JSON-RPC success {@link JSONRPCResultResponse} — the `id` echoed, the
|
|
962
1022
|
* method's value as `result`.
|
|
963
1023
|
*
|
|
964
1024
|
* @remarks
|
|
@@ -977,7 +1037,7 @@ function buildJSONRPCResult(id, result) {
|
|
|
977
1037
|
};
|
|
978
1038
|
}
|
|
979
1039
|
/**
|
|
980
|
-
*
|
|
1040
|
+
* Builds a JSON-RPC error {@link JSONRPCErrorResponse} — the `id` echoed, the failure
|
|
981
1041
|
* as an `error` object.
|
|
982
1042
|
*
|
|
983
1043
|
* @remarks
|
|
@@ -1006,7 +1066,7 @@ function buildJSONRPCError(id, code, message, data) {
|
|
|
1006
1066
|
};
|
|
1007
1067
|
}
|
|
1008
1068
|
/**
|
|
1009
|
-
*
|
|
1069
|
+
* Resolves the caller-facing dispatch options into the options a dispatched method
|
|
1010
1070
|
* receives.
|
|
1011
1071
|
*
|
|
1012
1072
|
* @remarks
|
|
@@ -1040,7 +1100,7 @@ function buildMethodOptions(options, lifetime) {
|
|
|
1040
1100
|
};
|
|
1041
1101
|
}
|
|
1042
1102
|
/**
|
|
1043
|
-
*
|
|
1103
|
+
* Maps a {@link ToolManagerInterface}'s definitions to MCP `tools/list` descriptors
|
|
1044
1104
|
* — renaming `parameters` to the wire's `inputSchema`.
|
|
1045
1105
|
*
|
|
1046
1106
|
* @remarks
|
|
@@ -1083,7 +1143,7 @@ function buildModernResult(result, identity, ttl, scope) {
|
|
|
1083
1143
|
};
|
|
1084
1144
|
}
|
|
1085
1145
|
/**
|
|
1086
|
-
*
|
|
1146
|
+
* Intersects a requested subscription filter with the notification families a server supports.
|
|
1087
1147
|
*
|
|
1088
1148
|
* @param requested - The notification families requested by the client
|
|
1089
1149
|
* @param supported - The notification families the server can actually produce
|
|
@@ -1103,7 +1163,7 @@ function buildSubscriptionFilter(requested, supported) {
|
|
|
1103
1163
|
};
|
|
1104
1164
|
}
|
|
1105
1165
|
/**
|
|
1106
|
-
*
|
|
1166
|
+
* Determines whether a produced notification belongs to an honoured subscription filter.
|
|
1107
1167
|
*
|
|
1108
1168
|
* @param notification - The server notification offered by the configured producer
|
|
1109
1169
|
* @param filter - The filter acknowledged to the client
|
|
@@ -1118,7 +1178,7 @@ function matchesSubscriptionNotification(notification, filter) {
|
|
|
1118
1178
|
return typeof uri === "string" && filter.resourceSubscriptions?.includes(uri) === true;
|
|
1119
1179
|
}
|
|
1120
1180
|
/**
|
|
1121
|
-
*
|
|
1181
|
+
* Stamps a subscription notification with the request id reserved for its held-open stream.
|
|
1122
1182
|
*
|
|
1123
1183
|
* @param notification - The notification to copy and stamp
|
|
1124
1184
|
* @param id - The `subscriptions/listen` request id
|
|
@@ -1139,7 +1199,7 @@ function stampSubscriptionNotification(notification, id) {
|
|
|
1139
1199
|
};
|
|
1140
1200
|
}
|
|
1141
1201
|
/**
|
|
1142
|
-
*
|
|
1202
|
+
* Builds the first notification carrying a subscription id for a listen request.
|
|
1143
1203
|
*
|
|
1144
1204
|
* @param notifications - The exact notification filter the server will honour
|
|
1145
1205
|
* @param id - The `subscriptions/listen` request id
|
|
@@ -1153,7 +1213,7 @@ function buildSubscriptionAcknowledgement(notifications, id) {
|
|
|
1153
1213
|
}, id);
|
|
1154
1214
|
}
|
|
1155
1215
|
/**
|
|
1156
|
-
*
|
|
1216
|
+
* Builds the terminating response for a subscription source that closes gracefully.
|
|
1157
1217
|
*
|
|
1158
1218
|
* @param id - The `subscriptions/listen` request id
|
|
1159
1219
|
* @param identity - The server identity included by the modern result stamping site
|
|
@@ -1163,7 +1223,7 @@ function buildSubscriptionResult(id, identity) {
|
|
|
1163
1223
|
return buildJSONRPCResult(id, buildModernResult({ _meta: { [MCP_META_SUBSCRIPTION]: id } }, identity));
|
|
1164
1224
|
}
|
|
1165
1225
|
/**
|
|
1166
|
-
*
|
|
1226
|
+
* Builds the mandatory modern `server/discover` result.
|
|
1167
1227
|
*
|
|
1168
1228
|
* @remarks
|
|
1169
1229
|
* `capabilities.resources` and `capabilities.prompts` appear only for servers with their
|
|
@@ -1195,7 +1255,7 @@ function buildDiscoverResult(options) {
|
|
|
1195
1255
|
}, options.identity, options.cache?.ttl ?? 6e4, options.cache?.scope);
|
|
1196
1256
|
}
|
|
1197
1257
|
/**
|
|
1198
|
-
*
|
|
1258
|
+
* Builds the MCP `initialize` result — the negotiated protocol version, the
|
|
1199
1259
|
* advertised capabilities, and the server identity.
|
|
1200
1260
|
*
|
|
1201
1261
|
* @remarks
|
|
@@ -1222,7 +1282,7 @@ function buildInitializeResult(name, version, requested) {
|
|
|
1222
1282
|
};
|
|
1223
1283
|
}
|
|
1224
1284
|
/**
|
|
1225
|
-
*
|
|
1285
|
+
* Decodes one raw inbound message within an explicit bound — the decode a binder performs
|
|
1226
1286
|
* before it hands the string on.
|
|
1227
1287
|
*
|
|
1228
1288
|
* @remarks
|
|
@@ -1230,14 +1290,14 @@ function buildInitializeResult(name, version, requested) {
|
|
|
1230
1290
|
* `JSON.parse`d at all: a decoder that parses before it measures has already spent the work
|
|
1231
1291
|
* the bound exists to refuse. A message over the bound, malformed JSON, and a well-formed
|
|
1232
1292
|
* value that is not a JSON-RPC message are one answer — `undefined` — because a binder does
|
|
1233
|
-
* exactly the same thing with
|
|
1293
|
+
* exactly the same thing with each of them: nothing, and let
|
|
1234
1294
|
* {@link import('./types.js').MCPServerInterface.handle} produce the wire refusal from the
|
|
1235
1295
|
* same bound.
|
|
1236
1296
|
*
|
|
1237
|
-
* Total
|
|
1297
|
+
* Total — never throws, whatever the input.
|
|
1238
1298
|
*
|
|
1239
1299
|
* @param message - The raw inbound JSON-RPC message string
|
|
1240
|
-
* @param limits - The byte and depth bounds to decode within (the server's own,
|
|
1300
|
+
* @param limits - The byte and depth bounds to decode within (the server's own, from `limit`)
|
|
1241
1301
|
* @returns The decoded message, or `undefined` when it is over the bound or is not one
|
|
1242
1302
|
*
|
|
1243
1303
|
* @example
|
|
@@ -1251,7 +1311,7 @@ function decodeBoundedMessage(message, limits) {
|
|
|
1251
1311
|
return parsed.success ? parseJSONRPCMessage(parsed.value, limits) : void 0;
|
|
1252
1312
|
}
|
|
1253
1313
|
/**
|
|
1254
|
-
*
|
|
1314
|
+
* Reads the request id an inbound `notifications/cancelled` names — the inverse of
|
|
1255
1315
|
* {@link buildCancelledNotification}.
|
|
1256
1316
|
*
|
|
1257
1317
|
* @remarks
|
|
@@ -1259,7 +1319,7 @@ function decodeBoundedMessage(message, limits) {
|
|
|
1259
1319
|
* real {@link JSONRPCId}: `null` is not one, and neither is an absent member, so a
|
|
1260
1320
|
* malformed frame reads as "cancels nothing" rather than as an error. Anything that is not a
|
|
1261
1321
|
* `notifications/cancelled` notification — a response, a request that happens to use the
|
|
1262
|
-
* method name, another notification — reads the same way. Total
|
|
1322
|
+
* method name, another notification — reads the same way. Total.
|
|
1263
1323
|
*
|
|
1264
1324
|
* @param message - The decoded inbound message to read
|
|
1265
1325
|
* @returns The id of the request being cancelled, or `undefined` when the message cancels nothing
|
|
@@ -1275,7 +1335,7 @@ function readCancelledId(message) {
|
|
|
1275
1335
|
return isJSONRPCId(requested) ? requested : void 0;
|
|
1276
1336
|
}
|
|
1277
1337
|
/**
|
|
1278
|
-
*
|
|
1338
|
+
* Pumps a controlled serialized exchange onto a transport — every notification in order, then
|
|
1279
1339
|
* the terminating response — and END the exchange however the pump leaves.
|
|
1280
1340
|
*
|
|
1281
1341
|
* @remarks
|
|
@@ -1320,9 +1380,9 @@ async function sendStream(stream, transport) {
|
|
|
1320
1380
|
}
|
|
1321
1381
|
}
|
|
1322
1382
|
/**
|
|
1323
|
-
*
|
|
1383
|
+
* Pipes an {@link MCPTransportInterface} into an {@link MCPDispatcherInterface} — every
|
|
1324
1384
|
* inbound message runs through `server.handle`, and a defined reply is written back
|
|
1325
|
-
*
|
|
1385
|
+
* through `transport.send`.
|
|
1326
1386
|
*
|
|
1327
1387
|
* @remarks
|
|
1328
1388
|
* `server.handle` already turns a malformed message into a serialized `-32700` /
|
|
@@ -1343,7 +1403,7 @@ async function sendStream(stream, transport) {
|
|
|
1343
1403
|
* **This binder OWNS every exchange it starts, and ends each one on every exit.** It holds one
|
|
1344
1404
|
* `AbortController` per live request, keyed by the request's id and deleted whenever that
|
|
1345
1405
|
* request leaves — normally, by a throw, or by cancellation — and it supplies that signal to
|
|
1346
|
-
* `handle` as {@link import('./types.js').MCPDispatchOptions}.
|
|
1406
|
+
* `handle` as {@link import('./types.js').MCPDispatchOptions}. These consequences follow.
|
|
1347
1407
|
* An inbound `notifications/cancelled` ABORTS the request it names, which is how the message-
|
|
1348
1408
|
* based cancellation path reaches a tool on the carriers that have one (stdio, WebSocket,
|
|
1349
1409
|
* `MessagePort`); a cancelled request writes NO response, because a peer that asked for a call
|
|
@@ -1416,7 +1476,7 @@ function bindServer(server, transport) {
|
|
|
1416
1476
|
};
|
|
1417
1477
|
}
|
|
1418
1478
|
/**
|
|
1419
|
-
*
|
|
1479
|
+
* Pipes an {@link MCPTransportInterface} into an {@link MCPClientInterface} — every
|
|
1420
1480
|
* inbound message is decoded and delivered onto the client's OWN transport
|
|
1421
1481
|
* (`client.transport.emitter`'s `message` / `close` events), resolving/rejecting the
|
|
1422
1482
|
* client's correlated pending requests exactly as a direct reply would.
|
|
@@ -1430,7 +1490,7 @@ function bindServer(server, transport) {
|
|
|
1430
1490
|
* this binder then completes the inbound half by decoding each message and pushing it
|
|
1431
1491
|
* onto `client.transport.emitter` (an {@link import('@orkestrel/emitter').EmitterInterface}
|
|
1432
1492
|
* exposes `emit`, so no client modification is needed). A malformed / non-JSON-RPC
|
|
1433
|
-
* inbound message is DROPPED (
|
|
1493
|
+
* inbound message is DROPPED (total — never throws); a delivery fault is routed to
|
|
1434
1494
|
* `client.transport.emitter`'s `error` event (never rethrown). The returned unbind
|
|
1435
1495
|
* DETACHES this binder (further inbound messages and the transport's `closed` signal are
|
|
1436
1496
|
* ignored) WITHOUT closing the transport.
|
|
@@ -1448,7 +1508,7 @@ function bindServer(server, transport) {
|
|
|
1448
1508
|
* itself when a caller's `signal` aborts, on a carrier declaring `duplex`. Adding a registry
|
|
1449
1509
|
* here would be a second correlation table for ids the client is already correlating, and two
|
|
1450
1510
|
* tables for one fact drift. The one obligation this binder does carry is delivery: a
|
|
1451
|
-
* malformed / non-JSON-RPC inbound message is DROPPED (
|
|
1511
|
+
* malformed / non-JSON-RPC inbound message is DROPPED (total — never throws).
|
|
1452
1512
|
*
|
|
1453
1513
|
* @param client - The transport-agnostic client whose transport to deliver messages onto
|
|
1454
1514
|
* @param transport - The duplex channel to pipe the client over
|
|
@@ -1498,11 +1558,11 @@ function bindClient(client, transport) {
|
|
|
1498
1558
|
}
|
|
1499
1559
|
//#endregion
|
|
1500
1560
|
//#region src/core/validators.ts
|
|
1501
|
-
/**
|
|
1561
|
+
/** Determines whether a value is an exact finite JSON object. */
|
|
1502
1562
|
function isJSONObject(value) {
|
|
1503
1563
|
return (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value)).success;
|
|
1504
1564
|
}
|
|
1505
|
-
/**
|
|
1565
|
+
/** Determines whether a string follows the dated MCP `_meta` key grammar. */
|
|
1506
1566
|
function isMCPMetaKey(value) {
|
|
1507
1567
|
if (!(0, _orkestrel_contract.isString)(value)) return false;
|
|
1508
1568
|
const slash = value.indexOf("/");
|
|
@@ -1515,24 +1575,24 @@ function isMCPMetaKey(value) {
|
|
|
1515
1575
|
}
|
|
1516
1576
|
return name.length === 0 || /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(name);
|
|
1517
1577
|
}
|
|
1518
|
-
/**
|
|
1578
|
+
/** Determines whether a value is exact finite MCP metadata with valid keys. */
|
|
1519
1579
|
function isMCPMetaObject(value) {
|
|
1520
1580
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
1521
1581
|
return owned.success && Object.keys(owned.value).every((key) => isMCPMetaKey(key));
|
|
1522
1582
|
}
|
|
1523
|
-
/**
|
|
1583
|
+
/** Determines whether a value is exact result metadata with a valid reserved server identity. */
|
|
1524
1584
|
function isMCPResultMetaObject(value) {
|
|
1525
1585
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
1526
1586
|
if (!owned.success || !Object.keys(owned.value).every((key) => isMCPMetaKey(key))) return false;
|
|
1527
1587
|
const identity = owned.value[MCP_META_SERVER];
|
|
1528
1588
|
return (0, _orkestrel_contract.isUndefined)(identity) || isMCPIdentity(identity);
|
|
1529
1589
|
}
|
|
1530
|
-
/**
|
|
1590
|
+
/** Determines whether a value is one dated MCP logging level. */
|
|
1531
1591
|
function isMCPLoggingLevel(value) {
|
|
1532
1592
|
return value === "debug" || value === "info" || value === "notice" || value === "warning" || value === "error" || value === "critical" || value === "alert" || value === "emergency";
|
|
1533
1593
|
}
|
|
1534
1594
|
/**
|
|
1535
|
-
*
|
|
1595
|
+
* Determines whether a value is standard padded base64 as required by JSON Schema `byte` format.
|
|
1536
1596
|
*
|
|
1537
1597
|
* @param value - The unknown value to inspect
|
|
1538
1598
|
* @returns Whether the value is an empty or completely padded standard base64 encoding
|
|
@@ -1541,7 +1601,7 @@ function isStandardBase64(value) {
|
|
|
1541
1601
|
return (0, _orkestrel_contract.isString)(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
1542
1602
|
}
|
|
1543
1603
|
/**
|
|
1544
|
-
*
|
|
1604
|
+
* Determines whether a value is one absolute URI under RFC 3986 syntax.
|
|
1545
1605
|
*
|
|
1546
1606
|
* @remarks
|
|
1547
1607
|
* This host-neutral syntax guard does not resolve, normalize, decode, fetch, or apply a
|
|
@@ -1630,7 +1690,7 @@ function isAbsoluteURI(value) {
|
|
|
1630
1690
|
return /^[A-Za-z0-9\-._~!$&'()*+,;=%]*$/.test(host);
|
|
1631
1691
|
}
|
|
1632
1692
|
/**
|
|
1633
|
-
*
|
|
1693
|
+
* Determines whether a value is one RFC 3339 `full-date` naming a real calendar day.
|
|
1634
1694
|
*
|
|
1635
1695
|
* @remarks
|
|
1636
1696
|
* RFC 3339 §5.6 defines `date-mday` as `01-28`, `29`, `30`, or `31` BASED ON the month and
|
|
@@ -1665,7 +1725,7 @@ function isRFC3339Date(value) {
|
|
|
1665
1725
|
return day <= (month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31);
|
|
1666
1726
|
}
|
|
1667
1727
|
/**
|
|
1668
|
-
*
|
|
1728
|
+
* Determines whether a value is one RFC 3339 `date-time` naming a real calendar day.
|
|
1669
1729
|
*
|
|
1670
1730
|
* @remarks
|
|
1671
1731
|
* The `full-date` half is {@link isRFC3339Date}, so an impossible day is refused here for
|
|
@@ -1692,7 +1752,7 @@ function isRFC3339DateTime(value) {
|
|
|
1692
1752
|
return matched !== null && isRFC3339Date(matched[1]);
|
|
1693
1753
|
}
|
|
1694
1754
|
/**
|
|
1695
|
-
*
|
|
1755
|
+
* Determines whether a value is one exact finite MCP progress payload.
|
|
1696
1756
|
*
|
|
1697
1757
|
* @param value - The unknown value to inspect
|
|
1698
1758
|
* @returns Whether required progress and optional total/message fields match the dated schema
|
|
@@ -1711,7 +1771,7 @@ function isMCPProgress(value) {
|
|
|
1711
1771
|
}
|
|
1712
1772
|
}
|
|
1713
1773
|
/**
|
|
1714
|
-
*
|
|
1774
|
+
* Determines whether a value carries valid dated-schema MCP content annotations.
|
|
1715
1775
|
*
|
|
1716
1776
|
* @param value - The unknown value to inspect
|
|
1717
1777
|
* @returns Whether the value is valid MCP annotations
|
|
@@ -1732,7 +1792,7 @@ function isMCPAnnotations(value) {
|
|
|
1732
1792
|
}
|
|
1733
1793
|
}
|
|
1734
1794
|
/**
|
|
1735
|
-
*
|
|
1795
|
+
* Determines whether a value is one exact dated-schema MCP icon.
|
|
1736
1796
|
*
|
|
1737
1797
|
* @param value - The unknown value to inspect
|
|
1738
1798
|
* @returns Whether the value is a valid MCP icon
|
|
@@ -1751,7 +1811,7 @@ function isMCPIcon(value) {
|
|
|
1751
1811
|
return false;
|
|
1752
1812
|
}
|
|
1753
1813
|
}
|
|
1754
|
-
/**
|
|
1814
|
+
/** Determines whether a value is one complete dated MCP implementation identity. */
|
|
1755
1815
|
function isMCPIdentity(value) {
|
|
1756
1816
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
1757
1817
|
if (!owned.success) return false;
|
|
@@ -1767,7 +1827,7 @@ function isMCPIdentity(value) {
|
|
|
1767
1827
|
return false;
|
|
1768
1828
|
}
|
|
1769
1829
|
}
|
|
1770
|
-
/**
|
|
1830
|
+
/** Determines whether a value is one exact open dated client-capability declaration. */
|
|
1771
1831
|
function isMCPClientCapabilities(value) {
|
|
1772
1832
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
1773
1833
|
if (!owned.success) return false;
|
|
@@ -1797,7 +1857,7 @@ function isMCPClientCapabilities(value) {
|
|
|
1797
1857
|
return false;
|
|
1798
1858
|
}
|
|
1799
1859
|
}
|
|
1800
|
-
/**
|
|
1860
|
+
/** Determines whether a value is one exact open dated server-capability declaration. */
|
|
1801
1861
|
function isMCPServerCapabilities(value) {
|
|
1802
1862
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
1803
1863
|
if (!owned.success) return false;
|
|
@@ -1823,7 +1883,7 @@ function isMCPServerCapabilities(value) {
|
|
|
1823
1883
|
}
|
|
1824
1884
|
}
|
|
1825
1885
|
/**
|
|
1826
|
-
*
|
|
1886
|
+
* Determines whether a value is embedded textual MCP resource contents.
|
|
1827
1887
|
*
|
|
1828
1888
|
* @param value - The unknown value to inspect
|
|
1829
1889
|
* @returns Whether the value is embedded textual resource contents
|
|
@@ -1842,7 +1902,7 @@ function isMCPTextResource(value) {
|
|
|
1842
1902
|
}
|
|
1843
1903
|
}
|
|
1844
1904
|
/**
|
|
1845
|
-
*
|
|
1905
|
+
* Determines whether a value is embedded blob MCP resource contents.
|
|
1846
1906
|
*
|
|
1847
1907
|
* @param value - The unknown value to inspect
|
|
1848
1908
|
* @returns Whether the value is embedded blob resource contents
|
|
@@ -1861,7 +1921,7 @@ function isMCPBlobResource(value) {
|
|
|
1861
1921
|
}
|
|
1862
1922
|
}
|
|
1863
1923
|
/**
|
|
1864
|
-
*
|
|
1924
|
+
* Determines whether a value is one `resources/list` descriptor.
|
|
1865
1925
|
*
|
|
1866
1926
|
* @param value - The unknown value to inspect
|
|
1867
1927
|
* @returns Whether the value is a valid resource descriptor
|
|
@@ -1879,7 +1939,7 @@ function isMCPResource(value) {
|
|
|
1879
1939
|
}
|
|
1880
1940
|
}
|
|
1881
1941
|
/**
|
|
1882
|
-
*
|
|
1942
|
+
* Determines whether a value is one resource-template descriptor.
|
|
1883
1943
|
*
|
|
1884
1944
|
* @remarks
|
|
1885
1945
|
* This guard validates the descriptor shape. Template expansion and the RFC 6570 feature
|
|
@@ -1900,7 +1960,7 @@ function isMCPResourceTemplate(value) {
|
|
|
1900
1960
|
}
|
|
1901
1961
|
}
|
|
1902
1962
|
/**
|
|
1903
|
-
*
|
|
1963
|
+
* Determines whether a value is structurally discriminated resource contents.
|
|
1904
1964
|
*
|
|
1905
1965
|
* @param value - The unknown value to inspect
|
|
1906
1966
|
* @returns Whether exactly one of `text` and `blob` is present and valid
|
|
@@ -1914,7 +1974,7 @@ function isMCPResourceContents(value) {
|
|
|
1914
1974
|
return text ? isMCPTextResource(resource) : isMCPBlobResource(resource);
|
|
1915
1975
|
}
|
|
1916
1976
|
/**
|
|
1917
|
-
*
|
|
1977
|
+
* Determines whether a value carries the shared optional pagination cursor.
|
|
1918
1978
|
*
|
|
1919
1979
|
* @param value - The unknown value to inspect
|
|
1920
1980
|
* @returns Whether a present `cursor` is a string
|
|
@@ -1924,7 +1984,7 @@ function isMCPPaginationParams(value) {
|
|
|
1924
1984
|
return owned.success && ((0, _orkestrel_contract.isUndefined)(owned.value["cursor"]) || (0, _orkestrel_contract.isString)(owned.value["cursor"]));
|
|
1925
1985
|
}
|
|
1926
1986
|
/**
|
|
1927
|
-
*
|
|
1987
|
+
* Determines whether a value is one consumer-owned resource page.
|
|
1928
1988
|
*
|
|
1929
1989
|
* @param value - The unknown value to inspect
|
|
1930
1990
|
* @returns Whether the resources and optional following cursor are valid
|
|
@@ -1937,7 +1997,7 @@ function isMCPResourcePage(value) {
|
|
|
1937
1997
|
return Array.isArray(resources) && resources.every((resource) => isMCPResource(resource)) && ((0, _orkestrel_contract.isUndefined)(cursor) || (0, _orkestrel_contract.isString)(cursor));
|
|
1938
1998
|
}
|
|
1939
1999
|
/**
|
|
1940
|
-
*
|
|
2000
|
+
* Determines whether a value is one consumer-owned resource-template page.
|
|
1941
2001
|
*
|
|
1942
2002
|
* @param value - The unknown value to inspect
|
|
1943
2003
|
* @returns Whether the templates and optional following cursor are valid
|
|
@@ -1950,7 +2010,7 @@ function isMCPResourceTemplatePage(value) {
|
|
|
1950
2010
|
return Array.isArray(templates) && templates.every((template) => isMCPResourceTemplate(template)) && ((0, _orkestrel_contract.isUndefined)(cursor) || (0, _orkestrel_contract.isString)(cursor));
|
|
1951
2011
|
}
|
|
1952
2012
|
/**
|
|
1953
|
-
*
|
|
2013
|
+
* Determines whether a value is a string-valued MCP argument record.
|
|
1954
2014
|
*
|
|
1955
2015
|
* @param value - The unknown value to inspect
|
|
1956
2016
|
* @returns Whether every own argument value is a string
|
|
@@ -1960,7 +2020,7 @@ function isMCPStringArguments(value) {
|
|
|
1960
2020
|
return owned.success && Object.values(owned.value).every((argument) => (0, _orkestrel_contract.isString)(argument));
|
|
1961
2021
|
}
|
|
1962
2022
|
/**
|
|
1963
|
-
*
|
|
2023
|
+
* Determines whether a value is one prompt argument descriptor.
|
|
1964
2024
|
*
|
|
1965
2025
|
* @param value - The unknown value to inspect
|
|
1966
2026
|
* @returns Whether the prompt argument descriptor is valid
|
|
@@ -1972,7 +2032,7 @@ function isMCPPromptArgument(value) {
|
|
|
1972
2032
|
return (0, _orkestrel_contract.isString)(argument["name"]) && ((0, _orkestrel_contract.isUndefined)(argument["title"]) || (0, _orkestrel_contract.isString)(argument["title"])) && ((0, _orkestrel_contract.isUndefined)(argument["description"]) || (0, _orkestrel_contract.isString)(argument["description"])) && ((0, _orkestrel_contract.isUndefined)(argument["required"]) || (0, _orkestrel_contract.isBoolean)(argument["required"]));
|
|
1973
2033
|
}
|
|
1974
2034
|
/**
|
|
1975
|
-
*
|
|
2035
|
+
* Determines whether a value is one `prompts/list` descriptor.
|
|
1976
2036
|
*
|
|
1977
2037
|
* @param value - The unknown value to inspect
|
|
1978
2038
|
* @returns Whether the prompt descriptor is valid
|
|
@@ -1986,7 +2046,7 @@ function isMCPPrompt(value) {
|
|
|
1986
2046
|
return (0, _orkestrel_contract.isString)(prompt["name"]) && ((0, _orkestrel_contract.isUndefined)(prompt["title"]) || (0, _orkestrel_contract.isString)(prompt["title"])) && ((0, _orkestrel_contract.isUndefined)(prompt["description"]) || (0, _orkestrel_contract.isString)(prompt["description"])) && ((0, _orkestrel_contract.isUndefined)(argumentsValue) || Array.isArray(argumentsValue) && argumentsValue.every((argument) => isMCPPromptArgument(argument))) && ((0, _orkestrel_contract.isUndefined)(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && ((0, _orkestrel_contract.isUndefined)(prompt["_meta"]) || isMCPMetaObject(prompt["_meta"]));
|
|
1987
2047
|
}
|
|
1988
2048
|
/**
|
|
1989
|
-
*
|
|
2049
|
+
* Determines whether a value is one prompt message with existing rich content.
|
|
1990
2050
|
*
|
|
1991
2051
|
* @param value - The unknown value to inspect
|
|
1992
2052
|
* @returns Whether the role and content are valid
|
|
@@ -1996,7 +2056,7 @@ function isMCPPromptMessage(value) {
|
|
|
1996
2056
|
return owned.success && (owned.value["role"] === "user" || owned.value["role"] === "assistant") && isMCPContent(owned.value["content"]);
|
|
1997
2057
|
}
|
|
1998
2058
|
/**
|
|
1999
|
-
*
|
|
2059
|
+
* Determines whether a value is one consumer-owned prompt page.
|
|
2000
2060
|
*
|
|
2001
2061
|
* @param value - The unknown value to inspect
|
|
2002
2062
|
* @returns Whether the prompts and optional following cursor are valid
|
|
@@ -2009,7 +2069,7 @@ function isMCPPromptPage(value) {
|
|
|
2009
2069
|
return Array.isArray(prompts) && prompts.every((prompt) => isMCPPrompt(prompt)) && ((0, _orkestrel_contract.isUndefined)(cursor) || (0, _orkestrel_contract.isString)(cursor));
|
|
2010
2070
|
}
|
|
2011
2071
|
/**
|
|
2012
|
-
*
|
|
2072
|
+
* Determines whether a value is one complete `prompts/get` result.
|
|
2013
2073
|
*
|
|
2014
2074
|
* @param value - The unknown value to inspect
|
|
2015
2075
|
* @returns Whether the prompt result and all messages are valid
|
|
@@ -2022,7 +2082,7 @@ function isMCPPromptGetResult(value) {
|
|
|
2022
2082
|
return result["resultType"] === "complete" && ((0, _orkestrel_contract.isUndefined)(result["description"]) || (0, _orkestrel_contract.isString)(result["description"])) && Array.isArray(messages) && messages.every((message) => isMCPPromptMessage(message)) && ((0, _orkestrel_contract.isUndefined)(result["_meta"]) || isMCPResultMetaObject(result["_meta"]));
|
|
2023
2083
|
}
|
|
2024
2084
|
/**
|
|
2025
|
-
*
|
|
2085
|
+
* Determines whether a value is a prompt or resource-template completion reference.
|
|
2026
2086
|
*
|
|
2027
2087
|
* @param value - The unknown value to inspect
|
|
2028
2088
|
* @returns Whether the discriminated reference is valid
|
|
@@ -2034,7 +2094,7 @@ function isMCPCompletionReference(value) {
|
|
|
2034
2094
|
return reference["type"] === "ref/prompt" ? (0, _orkestrel_contract.isString)(reference["name"]) : reference["type"] === "ref/resource" && (0, _orkestrel_contract.isString)(reference["uri"]);
|
|
2035
2095
|
}
|
|
2036
2096
|
/**
|
|
2037
|
-
*
|
|
2097
|
+
* Determines whether a value is one `completion/complete` parameter object.
|
|
2038
2098
|
*
|
|
2039
2099
|
* @param value - The unknown value to inspect
|
|
2040
2100
|
* @returns Whether its reference, fragment, and optional string context are valid
|
|
@@ -2053,7 +2113,7 @@ function isMCPCompletionParams(value) {
|
|
|
2053
2113
|
return isMCPCompletionReference(params["ref"]) && (0, _orkestrel_contract.isString)(argument.value["name"]) && (0, _orkestrel_contract.isString)(argument.value["value"]);
|
|
2054
2114
|
}
|
|
2055
2115
|
/**
|
|
2056
|
-
*
|
|
2116
|
+
* Determines whether a value is one host-produced completion candidate set.
|
|
2057
2117
|
*
|
|
2058
2118
|
* @param value - The unknown value to inspect
|
|
2059
2119
|
* @returns Whether its candidates and optional result facts are valid
|
|
@@ -2067,7 +2127,7 @@ function isMCPCompletion(value) {
|
|
|
2067
2127
|
return Array.isArray(values) && values.every((candidate) => (0, _orkestrel_contract.isString)(candidate)) && ((0, _orkestrel_contract.isUndefined)(total) || (0, _orkestrel_contract.isInteger)(total) && total >= 0) && ((0, _orkestrel_contract.isUndefined)(completion["hasMore"]) || (0, _orkestrel_contract.isBoolean)(completion["hasMore"]));
|
|
2068
2128
|
}
|
|
2069
2129
|
/**
|
|
2070
|
-
*
|
|
2130
|
+
* Determines whether a value is one complete, capped `completion/complete` result.
|
|
2071
2131
|
*
|
|
2072
2132
|
* @param value - The unknown value to inspect
|
|
2073
2133
|
* @returns Whether the result is complete and carries at most 100 candidates
|
|
@@ -2079,7 +2139,7 @@ function isMCPCompletionResult(value) {
|
|
|
2079
2139
|
return isMCPCompletion(completion) && completion.values.length <= 100 && ((0, _orkestrel_contract.isUndefined)(owned.value["_meta"]) || isMCPResultMetaObject(owned.value["_meta"]));
|
|
2080
2140
|
}
|
|
2081
2141
|
/**
|
|
2082
|
-
*
|
|
2142
|
+
* Determines whether a value is one exact dated-schema MCP tool content block.
|
|
2083
2143
|
*
|
|
2084
2144
|
* @param value - The unknown value to inspect
|
|
2085
2145
|
* @returns Whether the value is valid MCP content
|
|
@@ -2111,7 +2171,7 @@ function isMCPContent(value) {
|
|
|
2111
2171
|
}
|
|
2112
2172
|
}
|
|
2113
2173
|
/**
|
|
2114
|
-
*
|
|
2174
|
+
* Determines whether a value is one modern MCP result.
|
|
2115
2175
|
*
|
|
2116
2176
|
* @remarks
|
|
2117
2177
|
* The open contract's guard: a record carrying a string `resultType` and, when
|
|
@@ -2141,7 +2201,7 @@ function isMCPResult(value) {
|
|
|
2141
2201
|
return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPResultMetaObject(metadata);
|
|
2142
2202
|
}
|
|
2143
2203
|
/**
|
|
2144
|
-
*
|
|
2204
|
+
* Determines whether a value is one legacy-era MCP result.
|
|
2145
2205
|
*
|
|
2146
2206
|
* @remarks
|
|
2147
2207
|
* The legacy revision has no result discriminator, so the absence of `resultType` is
|
|
@@ -2162,7 +2222,7 @@ function isMCPLegacyResult(value) {
|
|
|
2162
2222
|
return owned.success && !Object.hasOwn(owned.value, "resultType");
|
|
2163
2223
|
}
|
|
2164
2224
|
/**
|
|
2165
|
-
*
|
|
2225
|
+
* Determines whether a value is a complete modern MCP tool result.
|
|
2166
2226
|
*
|
|
2167
2227
|
* @param value - The unknown value to inspect
|
|
2168
2228
|
* @returns Whether the value is a complete MCP call result
|
|
@@ -2182,7 +2242,7 @@ function isMCPCallResult(value) {
|
|
|
2182
2242
|
}
|
|
2183
2243
|
}
|
|
2184
2244
|
/**
|
|
2185
|
-
*
|
|
2245
|
+
* Determines whether a value is a modern MCP task-creation result.
|
|
2186
2246
|
*
|
|
2187
2247
|
* @remarks
|
|
2188
2248
|
* The runtime enforcement of {@link MCPTaskManagerInterface.start}'s declared return
|
|
@@ -2216,7 +2276,7 @@ function isMCPTaskResult(value) {
|
|
|
2216
2276
|
}
|
|
2217
2277
|
}
|
|
2218
2278
|
/**
|
|
2219
|
-
*
|
|
2279
|
+
* Determines whether a value is one of the extension's task lifecycle states.
|
|
2220
2280
|
*
|
|
2221
2281
|
* @param value - The unknown value to inspect
|
|
2222
2282
|
* @returns Whether the value is an {@link MCPTaskStatus}
|
|
@@ -2231,7 +2291,7 @@ function isMCPTaskStatus(value) {
|
|
|
2231
2291
|
return value === "working" || value === "input_required" || value === "completed" || value === "failed" || value === "cancelled";
|
|
2232
2292
|
}
|
|
2233
2293
|
/**
|
|
2234
|
-
*
|
|
2294
|
+
* Determines whether a value is one durable task's full snapshot.
|
|
2235
2295
|
*
|
|
2236
2296
|
* @remarks
|
|
2237
2297
|
* The runtime enforcement of {@link MCPTaskManagerInterface.task}'s declared return shape,
|
|
@@ -2274,7 +2334,7 @@ function isMCPTaskDetail(value) {
|
|
|
2274
2334
|
}
|
|
2275
2335
|
}
|
|
2276
2336
|
/**
|
|
2277
|
-
*
|
|
2337
|
+
* Determines whether a value is a string within a UTF-8 byte bound.
|
|
2278
2338
|
*
|
|
2279
2339
|
* @param value - The unknown value to inspect
|
|
2280
2340
|
* @param bytes - The maximum accepted encoded bytes
|
|
@@ -2305,7 +2365,7 @@ function isBoundedString(value, bytes) {
|
|
|
2305
2365
|
return true;
|
|
2306
2366
|
}
|
|
2307
2367
|
/**
|
|
2308
|
-
*
|
|
2368
|
+
* Determines whether a value is bounded, cycle-free exact JSON.
|
|
2309
2369
|
*
|
|
2310
2370
|
* @remarks
|
|
2311
2371
|
* Traversal is iterative, ancestor-aware, and contained by {@link attempt}; deep input,
|
|
@@ -2325,7 +2385,7 @@ function isBoundedJSON(value, limits) {
|
|
|
2325
2385
|
return serializeJSON(value, limits) !== void 0;
|
|
2326
2386
|
}
|
|
2327
2387
|
/**
|
|
2328
|
-
*
|
|
2388
|
+
* Determines whether a value is a valid JSON-RPC correlation id — a string or a
|
|
2329
2389
|
* finite integer.
|
|
2330
2390
|
*
|
|
2331
2391
|
* @remarks
|
|
@@ -2333,7 +2393,7 @@ function isBoundedJSON(value, limits) {
|
|
|
2333
2393
|
* absence is what makes a call a notification) and `null` is not one either (MCP
|
|
2334
2394
|
* omits an unreadable id rather than nulling it). A runtime numeric id must be a
|
|
2335
2395
|
* finite integer; an empty string is a legal id, because the dated schema imposes
|
|
2336
|
-
* no minimum length. Total
|
|
2396
|
+
* no minimum length. Total: any other input returns `false`.
|
|
2337
2397
|
*
|
|
2338
2398
|
* @param value - The already-parsed value to test
|
|
2339
2399
|
* @returns `true` when `value` is a string or a finite integer
|
|
@@ -2351,7 +2411,7 @@ function isJSONRPCId(value) {
|
|
|
2351
2411
|
return (0, _orkestrel_contract.isString)(value) || (0, _orkestrel_contract.isInteger)(value);
|
|
2352
2412
|
}
|
|
2353
2413
|
/**
|
|
2354
|
-
*
|
|
2414
|
+
* Determines whether a value is a supported {@link MCPVersion}.
|
|
2355
2415
|
*
|
|
2356
2416
|
* @param value - The unknown value to inspect
|
|
2357
2417
|
* @returns `true` when the value is one of {@link SUPPORTED_PROTOCOL_VERSIONS}
|
|
@@ -2360,7 +2420,7 @@ function isMCPVersion(value) {
|
|
|
2360
2420
|
return (0, _orkestrel_contract.isString)(value) && SUPPORTED_PROTOCOL_VERSIONS.some((version) => version === value);
|
|
2361
2421
|
}
|
|
2362
2422
|
/**
|
|
2363
|
-
*
|
|
2423
|
+
* Determines whether a value is an MCP {@link MCPSubscriptionFilter}.
|
|
2364
2424
|
*
|
|
2365
2425
|
* @remarks
|
|
2366
2426
|
* Every filter field is optional. Boolean notification families accept only booleans, and
|
|
@@ -2385,69 +2445,7 @@ function isMCPSubscriptionFilter(value) {
|
|
|
2385
2445
|
return (0, _orkestrel_contract.isUndefined)(subscriptions) || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(subscriptions);
|
|
2386
2446
|
}
|
|
2387
2447
|
/**
|
|
2388
|
-
*
|
|
2389
|
-
*
|
|
2390
|
-
* @remarks
|
|
2391
|
-
* The protocol's empty `elicitation` object is the implicit form-only declaration.
|
|
2392
|
-
* A non-empty declaration must carry a record-valued `form` member; URL-only support
|
|
2393
|
-
* does not authorize a form request. Total over hostile input.
|
|
2394
|
-
*
|
|
2395
|
-
* @param value - The client capability record to inspect
|
|
2396
|
-
* @returns `true` when form-mode elicitation is declared
|
|
2397
|
-
*
|
|
2398
|
-
* @example
|
|
2399
|
-
* ```ts
|
|
2400
|
-
* isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
|
|
2401
|
-
* isFormElicitationSupported({ elicitation: { url: {} } }) // false
|
|
2402
|
-
* ```
|
|
2403
|
-
*/
|
|
2404
|
-
function isFormElicitationSupported(value) {
|
|
2405
|
-
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
2406
|
-
if (!owned.success) return false;
|
|
2407
|
-
try {
|
|
2408
|
-
const elicitation = owned.value["elicitation"];
|
|
2409
|
-
if (!(0, _orkestrel_contract.isRecord)(elicitation)) return false;
|
|
2410
|
-
if ((0, _orkestrel_contract.isRecord)(elicitation["form"])) return true;
|
|
2411
|
-
return Object.keys(elicitation).length === 0;
|
|
2412
|
-
} catch {
|
|
2413
|
-
return false;
|
|
2414
|
-
}
|
|
2415
|
-
}
|
|
2416
|
-
/**
|
|
2417
|
-
* Determine whether a client capability record declares the draft Tasks extension.
|
|
2418
|
-
*
|
|
2419
|
-
* @remarks
|
|
2420
|
-
* The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and its value is
|
|
2421
|
-
* an empty object, so this is a PRESENCE check: the extension defines no options, and a
|
|
2422
|
-
* server that read one would be reading a field no client can meaningfully set. The value
|
|
2423
|
-
* must still be a record, because that is the shape the capability record declares — a
|
|
2424
|
-
* `true` or a string there is a client speaking a different protocol, not a shorthand.
|
|
2425
|
-
*
|
|
2426
|
-
* A client declares this PER REQUEST. Nothing here consults a session, because the modern
|
|
2427
|
-
* revision is stateless and a capability declared once at connect time says nothing about
|
|
2428
|
-
* the request in hand. Total over hostile input.
|
|
2429
|
-
*
|
|
2430
|
-
* @param value - The client capability record to inspect
|
|
2431
|
-
* @returns `true` when the tasks extension is declared
|
|
2432
|
-
*
|
|
2433
|
-
* @example
|
|
2434
|
-
* ```ts
|
|
2435
|
-
* isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
|
|
2436
|
-
* isTaskSupported({ extensions: {} }) // false — the key is the declaration
|
|
2437
|
-
* ```
|
|
2438
|
-
*/
|
|
2439
|
-
function isTaskSupported(value) {
|
|
2440
|
-
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
2441
|
-
if (!owned.success) return false;
|
|
2442
|
-
try {
|
|
2443
|
-
const extensions = owned.value["extensions"];
|
|
2444
|
-
return (0, _orkestrel_contract.isRecord)(extensions) && (0, _orkestrel_contract.isRecord)(extensions["io.modelcontextprotocol/tasks"]);
|
|
2445
|
-
} catch {
|
|
2446
|
-
return false;
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
/**
|
|
2450
|
-
* Determine whether a value is one restricted primitive form-elicitation schema.
|
|
2448
|
+
* Determines whether a value is one restricted primitive form-elicitation schema.
|
|
2451
2449
|
*
|
|
2452
2450
|
* @param value - The unknown value to inspect
|
|
2453
2451
|
* @returns `true` for a supported boolean, numeric, string, or string-array schema
|
|
@@ -2503,7 +2501,7 @@ function isMCPElicitFieldSchema(value) {
|
|
|
2503
2501
|
}
|
|
2504
2502
|
}
|
|
2505
2503
|
/**
|
|
2506
|
-
*
|
|
2504
|
+
* Determines whether a value is the restricted top-level object schema a form elicitation issues.
|
|
2507
2505
|
*
|
|
2508
2506
|
* @remarks
|
|
2509
2507
|
* The schema half of {@link isMCPElicitForm}, exported on its own because the issued schema
|
|
@@ -2537,7 +2535,7 @@ function isMCPElicitSchema(value) {
|
|
|
2537
2535
|
}
|
|
2538
2536
|
}
|
|
2539
2537
|
/**
|
|
2540
|
-
*
|
|
2538
|
+
* Determines whether a value is a form-mode elicitation parameter object.
|
|
2541
2539
|
*
|
|
2542
2540
|
* @param value - The unknown value to inspect
|
|
2543
2541
|
* @returns `true` when `value` has the restricted form elicitation shape
|
|
@@ -2563,7 +2561,7 @@ function isMCPElicitForm(value) {
|
|
|
2563
2561
|
}
|
|
2564
2562
|
}
|
|
2565
2563
|
/**
|
|
2566
|
-
*
|
|
2564
|
+
* Determines whether a value is a URL-mode elicitation parameter object.
|
|
2567
2565
|
*
|
|
2568
2566
|
* @param value - The unknown value to inspect
|
|
2569
2567
|
* @returns `true` when `value` has the URL elicitation shape
|
|
@@ -2584,7 +2582,7 @@ function isMCPElicitURL(value) {
|
|
|
2584
2582
|
}
|
|
2585
2583
|
}
|
|
2586
2584
|
/**
|
|
2587
|
-
*
|
|
2585
|
+
* Determines whether a value is an embedded `elicitation/create` request.
|
|
2588
2586
|
*
|
|
2589
2587
|
* @param value - The unknown value to inspect
|
|
2590
2588
|
* @returns `true` when `value` is a form- or URL-mode elicitation request
|
|
@@ -2609,7 +2607,7 @@ function isMCPElicitRequest(value) {
|
|
|
2609
2607
|
}
|
|
2610
2608
|
}
|
|
2611
2609
|
/**
|
|
2612
|
-
*
|
|
2610
|
+
* Determines whether a value is one legal embedded multi-round-trip request.
|
|
2613
2611
|
*
|
|
2614
2612
|
* @param value - The unknown value to inspect
|
|
2615
2613
|
* @returns `true` for elicitation, deprecated sampling, or deprecated roots requests
|
|
@@ -2633,7 +2631,7 @@ function isMCPInputRequest(value) {
|
|
|
2633
2631
|
}
|
|
2634
2632
|
}
|
|
2635
2633
|
/**
|
|
2636
|
-
*
|
|
2634
|
+
* Determines whether a value is a server-keyed map of embedded input requests.
|
|
2637
2635
|
*
|
|
2638
2636
|
* @param value - The unknown value to inspect
|
|
2639
2637
|
* @returns `true` when every own value is a legal {@link MCPInputRequest}
|
|
@@ -2653,7 +2651,7 @@ function isMCPInputRequestMap(value) {
|
|
|
2653
2651
|
}
|
|
2654
2652
|
}
|
|
2655
2653
|
/**
|
|
2656
|
-
*
|
|
2654
|
+
* Determines whether a value is one elicitation response.
|
|
2657
2655
|
*
|
|
2658
2656
|
* @param value - The unknown value to inspect
|
|
2659
2657
|
* @returns `true` when action/content have the protocol shape
|
|
@@ -2680,7 +2678,7 @@ function isMCPElicitResult(value) {
|
|
|
2680
2678
|
}
|
|
2681
2679
|
}
|
|
2682
2680
|
/**
|
|
2683
|
-
*
|
|
2681
|
+
* Determines whether accepted elicitation content satisfies the exact schema that was issued.
|
|
2684
2682
|
*
|
|
2685
2683
|
* @remarks
|
|
2686
2684
|
* {@link isMCPElicitResult} says a response has the SHAPE of a response; this says the
|
|
@@ -2702,7 +2700,7 @@ function isMCPElicitResult(value) {
|
|
|
2702
2700
|
* that answers more than it was asked is not refused for it. A `schema` that is not itself a
|
|
2703
2701
|
* valid {@link MCPElicitSchema} admits NOTHING — an unenforceable schema is never a permissive
|
|
2704
2702
|
* one — which is why `schema` is accepted as `unknown` and checked rather than trusted. Total
|
|
2705
|
-
*
|
|
2703
|
+
* over hostile content and hostile schemas alike.
|
|
2706
2704
|
*
|
|
2707
2705
|
* @param value - The accepted response content to check
|
|
2708
2706
|
* @param schema - The exact {@link MCPElicitSchema} that was issued with the elicitation
|
|
@@ -2787,7 +2785,7 @@ function isElicitContent(value, schema) {
|
|
|
2787
2785
|
}
|
|
2788
2786
|
}
|
|
2789
2787
|
/**
|
|
2790
|
-
*
|
|
2788
|
+
* Determines whether a value is an MCP input-required result.
|
|
2791
2789
|
*
|
|
2792
2790
|
* @remarks
|
|
2793
2791
|
* Enforces the at-least-one-of rule at runtime: `inputRequests`, `requestState`, or
|
|
@@ -2820,15 +2818,15 @@ function isMCPInputResult(value) {
|
|
|
2820
2818
|
}
|
|
2821
2819
|
}
|
|
2822
2820
|
/**
|
|
2823
|
-
*
|
|
2821
|
+
* Determines whether a parsed value is a {@link JSONRPCRequest}.
|
|
2824
2822
|
*
|
|
2825
2823
|
* @remarks
|
|
2826
2824
|
* A request is a record with `jsonrpc === '2.0'`, a string `method`, and an `id`
|
|
2827
2825
|
* that {@link isJSONRPCId} accepts. An id-less call is NOT a request — it is a
|
|
2828
2826
|
* {@link JSONRPCNotification}, which {@link isJSONRPCNotification} answers for. The
|
|
2829
|
-
*
|
|
2827
|
+
* guards are mutually exclusive on every input: this one requires a valid `id`
|
|
2830
2828
|
* value, that one requires no own `id` member at all. `params`, when present, must
|
|
2831
|
-
* be a record. Total
|
|
2829
|
+
* be a record. Total: any other input returns `false`.
|
|
2832
2830
|
*
|
|
2833
2831
|
* @param value - The already-parsed value to test
|
|
2834
2832
|
* @returns `true` when `value` is a valid JSON-RPC request
|
|
@@ -2850,12 +2848,12 @@ function isJSONRPCRequest(value) {
|
|
|
2850
2848
|
return (0, _orkestrel_contract.isUndefined)(params) || (0, _orkestrel_contract.isRecord)(params);
|
|
2851
2849
|
}
|
|
2852
2850
|
/**
|
|
2853
|
-
*
|
|
2851
|
+
* Determines whether a parsed value is a {@link JSONRPCNotification}.
|
|
2854
2852
|
*
|
|
2855
2853
|
* @remarks
|
|
2856
2854
|
* A notification is a request-shaped call carrying NO `id` member — the protocol
|
|
2857
2855
|
* forbids one, because nothing answers a notification. `params`, when present, must
|
|
2858
|
-
* be a record. Total
|
|
2856
|
+
* be a record. Total: any other input returns `false`.
|
|
2859
2857
|
*
|
|
2860
2858
|
* @param value - The already-parsed value to test
|
|
2861
2859
|
* @returns `true` when `value` is a valid JSON-RPC notification
|
|
@@ -2876,12 +2874,12 @@ function isJSONRPCNotification(value) {
|
|
|
2876
2874
|
return (0, _orkestrel_contract.isUndefined)(params) || (0, _orkestrel_contract.isRecord)(params);
|
|
2877
2875
|
}
|
|
2878
2876
|
/**
|
|
2879
|
-
*
|
|
2877
|
+
* Determines whether a parsed value is a {@link JSONRPCInvocation} — a request or a
|
|
2880
2878
|
* notification.
|
|
2881
2879
|
*
|
|
2882
2880
|
* @remarks
|
|
2883
2881
|
* The union of {@link isJSONRPCRequest} and {@link isJSONRPCNotification}, which are
|
|
2884
|
-
* mutually exclusive, so a positive answer names exactly one arm. Total
|
|
2882
|
+
* mutually exclusive, so a positive answer names exactly one arm. Total.
|
|
2885
2883
|
*
|
|
2886
2884
|
* @param value - The already-parsed value to test
|
|
2887
2885
|
* @returns `true` when `value` is a valid JSON-RPC request or notification
|
|
@@ -2890,7 +2888,7 @@ function isJSONRPCInvocation(value) {
|
|
|
2890
2888
|
return isJSONRPCRequest(value) || isJSONRPCNotification(value);
|
|
2891
2889
|
}
|
|
2892
2890
|
/**
|
|
2893
|
-
*
|
|
2891
|
+
* Determines whether a parsed value is a {@link JSONRPCResultResponse} — the success
|
|
2894
2892
|
* arm of a response.
|
|
2895
2893
|
*
|
|
2896
2894
|
* @remarks
|
|
@@ -2899,7 +2897,7 @@ function isJSONRPCInvocation(value) {
|
|
|
2899
2897
|
* which is what makes this guard and {@link isJSONRPCErrorResponse} mutually
|
|
2900
2898
|
* exclusive on every input. `result` itself must be an object: either a modern
|
|
2901
2899
|
* {@link isMCPResult} or a legacy {@link isMCPLegacyResult}, never a bare primitive.
|
|
2902
|
-
* Total
|
|
2900
|
+
* Total.
|
|
2903
2901
|
*
|
|
2904
2902
|
* @param value - The already-parsed value to test
|
|
2905
2903
|
* @returns `true` when `value` is a valid JSON-RPC result response
|
|
@@ -2920,22 +2918,22 @@ function isJSONRPCResultResponse(value) {
|
|
|
2920
2918
|
return isMCPResult(result) || isMCPLegacyResult(result);
|
|
2921
2919
|
}
|
|
2922
2920
|
/**
|
|
2923
|
-
*
|
|
2921
|
+
* Determines whether a value is one JSON-RPC `error` member.
|
|
2924
2922
|
*
|
|
2925
2923
|
* @remarks
|
|
2926
2924
|
* The failure OBJECT, not the envelope carrying it — the shape a failed response owns
|
|
2927
2925
|
* under `error`, and the shape a `failed` {@link MCPTaskDetail} owns under the same name,
|
|
2928
|
-
* which is why it is one guard rather than the same
|
|
2926
|
+
* which is why it is one guard rather than the same checks written twice.
|
|
2929
2927
|
*
|
|
2930
2928
|
* It is deliberately STRUCTURAL rather than exact-JSON: `data` is declared `unknown`, so
|
|
2931
2929
|
* requiring the whole object to survive a JSON clone would refuse a legal error that
|
|
2932
2930
|
* carried a non-JSON payload. Both callers here hand it an already-owned value.
|
|
2933
2931
|
*
|
|
2934
|
-
* That choice is why the
|
|
2932
|
+
* That choice is why the key reads are guarded. Every sibling guard clones first, and a
|
|
2935
2933
|
* clone reads each key once behind a boundary that already owns totality; this one is the
|
|
2936
2934
|
* family's only DIRECT reader, so it meets `code` and `message` exactly as the value defines
|
|
2937
2935
|
* them — including as accessors that throw. Reading a named key off an unowned value is
|
|
2938
|
-
* itself the hostile step, and it is bounded here rather than allowed to escape. Total
|
|
2936
|
+
* itself the hostile step, and it is bounded here rather than allowed to escape. Total.
|
|
2939
2937
|
*
|
|
2940
2938
|
* @param value - The already-parsed value to test
|
|
2941
2939
|
* @returns `true` when `value` carries an integer `code` and a string `message`
|
|
@@ -2952,14 +2950,14 @@ function isJSONRPCError(value) {
|
|
|
2952
2950
|
return read.success && read.value;
|
|
2953
2951
|
}
|
|
2954
2952
|
/**
|
|
2955
|
-
*
|
|
2953
|
+
* Determines whether a parsed value is a {@link JSONRPCErrorResponse} — the failure
|
|
2956
2954
|
* arm of a response.
|
|
2957
2955
|
*
|
|
2958
2956
|
* @remarks
|
|
2959
2957
|
* `id` is OPTIONAL here and only here: a peer that could not read the failed
|
|
2960
2958
|
* request's id OMITS the member rather than sending `null`, so an absent `id` is
|
|
2961
2959
|
* valid and a `null` one is not. The envelope must own an `error` and must NOT own a
|
|
2962
|
-
* `result`. `error` carries an integer `code` and a string `message`. Total
|
|
2960
|
+
* `result`. `error` carries an integer `code` and a string `message`. Total.
|
|
2963
2961
|
*
|
|
2964
2962
|
* @param value - The already-parsed value to test
|
|
2965
2963
|
* @returns `true` when `value` is a valid JSON-RPC error response
|
|
@@ -2980,10 +2978,10 @@ function isJSONRPCErrorResponse(value) {
|
|
|
2980
2978
|
return isJSONRPCError(response["error"]);
|
|
2981
2979
|
}
|
|
2982
2980
|
/**
|
|
2983
|
-
*
|
|
2981
|
+
* Determines whether a parsed value is a {@link JSONRPCResponse}.
|
|
2984
2982
|
*
|
|
2985
2983
|
* @remarks
|
|
2986
|
-
* The union of the
|
|
2984
|
+
* The union of the mutually exclusive arms. Total.
|
|
2987
2985
|
*
|
|
2988
2986
|
* @param value - The already-parsed value to test
|
|
2989
2987
|
* @returns `true` when `value` is a valid JSON-RPC response
|
|
@@ -2992,11 +2990,11 @@ function isJSONRPCResponse(value) {
|
|
|
2992
2990
|
return isJSONRPCResultResponse(value) || isJSONRPCErrorResponse(value);
|
|
2993
2991
|
}
|
|
2994
2992
|
/**
|
|
2995
|
-
*
|
|
2993
|
+
* Determines whether a parsed value is a {@link JSONRPCMessage} — an invocation or a
|
|
2996
2994
|
* response.
|
|
2997
2995
|
*
|
|
2998
2996
|
* @remarks
|
|
2999
|
-
* The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total
|
|
2997
|
+
* The union of {@link isJSONRPCInvocation} and {@link isJSONRPCResponse}. Total.
|
|
3000
2998
|
*
|
|
3001
2999
|
* @param value - The already-parsed value to test
|
|
3002
3000
|
* @returns `true` when `value` is a valid JSON-RPC message
|
|
@@ -3005,7 +3003,7 @@ function isJSONRPCMessage(value) {
|
|
|
3005
3003
|
return isJSONRPCInvocation(value) || isJSONRPCResponse(value);
|
|
3006
3004
|
}
|
|
3007
3005
|
/**
|
|
3008
|
-
*
|
|
3006
|
+
* Determines whether a parsed value is an MCP `initialize` invocation.
|
|
3009
3007
|
*
|
|
3010
3008
|
* @param value - The already-parsed value to test
|
|
3011
3009
|
* @returns `true` when `value` is a valid `initialize` request or notification
|
|
@@ -3021,7 +3019,7 @@ function isInitializeRequest(value) {
|
|
|
3021
3019
|
return owned.success && isJSONRPCInvocation(owned.value) && owned.value["method"] === "initialize";
|
|
3022
3020
|
}
|
|
3023
3021
|
/**
|
|
3024
|
-
*
|
|
3022
|
+
* Determines whether a JSON-RPC invocation uses the modern per-request MCP wire shape.
|
|
3025
3023
|
*
|
|
3026
3024
|
* @remarks
|
|
3027
3025
|
* Presence routes and validity answers: this guard checks only that
|
|
@@ -3112,7 +3110,7 @@ var MCPProgressReporter = class {
|
|
|
3112
3110
|
#last;
|
|
3113
3111
|
#stopped = false;
|
|
3114
3112
|
/**
|
|
3115
|
-
*
|
|
3113
|
+
* Creates one non-durable progress slot for an active request.
|
|
3116
3114
|
*
|
|
3117
3115
|
* @param token - The opaque progress token copied into each notification
|
|
3118
3116
|
* @param limits - The byte, key, and depth bounds applied to every progress payload
|
|
@@ -3127,7 +3125,7 @@ var MCPProgressReporter = class {
|
|
|
3127
3125
|
else signal.addEventListener("abort", this.#listener, { once: true });
|
|
3128
3126
|
}
|
|
3129
3127
|
/**
|
|
3130
|
-
*
|
|
3128
|
+
* Publishes one bounded, strictly increasing progress value and awaits its consumption.
|
|
3131
3129
|
*
|
|
3132
3130
|
* @param progress - The progress payload to own and hand to the serial consumer
|
|
3133
3131
|
* @returns A promise resolving after {@link take} consumes the item
|
|
@@ -3148,7 +3146,7 @@ var MCPProgressReporter = class {
|
|
|
3148
3146
|
await consumed.promise;
|
|
3149
3147
|
}
|
|
3150
3148
|
/**
|
|
3151
|
-
*
|
|
3149
|
+
* Takes the next progress notification, waiting for the single producer slot when empty.
|
|
3152
3150
|
*
|
|
3153
3151
|
* @returns The official progress notification carrying the original token
|
|
3154
3152
|
* @throws When the reporter stops or another consumer already has a pending take
|
|
@@ -3172,7 +3170,7 @@ var MCPProgressReporter = class {
|
|
|
3172
3170
|
return buildProgressNotification(this.#token, progress);
|
|
3173
3171
|
}
|
|
3174
3172
|
/**
|
|
3175
|
-
*
|
|
3173
|
+
* Stops the reporter permanently, rejects pending work, and detaches its abort listener.
|
|
3176
3174
|
*
|
|
3177
3175
|
* Repeated calls are idempotent. No queued or replayable progress survives the first call.
|
|
3178
3176
|
*
|
|
@@ -3214,10 +3212,10 @@ var MCPProgressReporter = class {
|
|
|
3214
3212
|
* cleanup to the producer — so a cooperating producer is woken rather than waited on —
|
|
3215
3213
|
* contains every promise the producer settles late, and makes every closure path idempotent.
|
|
3216
3214
|
*
|
|
3217
|
-
* The
|
|
3215
|
+
* The closures are deliberately different answers: the source's own return is the
|
|
3218
3216
|
* terminal RESPONSE, `return(value)` is the consumer saying it has the answer already, and
|
|
3219
|
-
* {@link stop} is an owner saying there will be no answer at all. Only the
|
|
3220
|
-
* message a peer ever sees.
|
|
3217
|
+
* {@link stop} is an owner saying there will be no answer at all. Only the source's own
|
|
3218
|
+
* return is a message a peer ever sees.
|
|
3221
3219
|
*
|
|
3222
3220
|
* A producer's own resource cleanup remains the producer's: JavaScript cannot settle work a
|
|
3223
3221
|
* generator is suspended inside, so the signal is how an uncooperative producer is asked to
|
|
@@ -3250,7 +3248,7 @@ var MCPStreamController = class {
|
|
|
3250
3248
|
#error;
|
|
3251
3249
|
#closed = false;
|
|
3252
3250
|
/**
|
|
3253
|
-
*
|
|
3251
|
+
* Controls one produced stream for the lifetime of one request.
|
|
3254
3252
|
*
|
|
3255
3253
|
* @param source - The produced held-open result whose cancellation this controller owns
|
|
3256
3254
|
* @param signal - The request signal every closure settles against; its abort ends the exchange
|
|
@@ -3265,7 +3263,7 @@ var MCPStreamController = class {
|
|
|
3265
3263
|
else signal.addEventListener("abort", this.#listener, { once: true });
|
|
3266
3264
|
}
|
|
3267
3265
|
/**
|
|
3268
|
-
*
|
|
3266
|
+
* Reads the next message, or the terminating response that ends the exchange.
|
|
3269
3267
|
*
|
|
3270
3268
|
* @remarks
|
|
3271
3269
|
* At most one read is outstanding against the source at a time, and a rival read is
|
|
@@ -3300,7 +3298,7 @@ var MCPStreamController = class {
|
|
|
3300
3298
|
};
|
|
3301
3299
|
}
|
|
3302
3300
|
/**
|
|
3303
|
-
*
|
|
3301
|
+
* Ends the exchange because the consumer already has its answer.
|
|
3304
3302
|
*
|
|
3305
3303
|
* @param value - The terminal the consumer is ending on
|
|
3306
3304
|
* @returns That terminal as the iteration's `return`
|
|
@@ -3314,7 +3312,7 @@ var MCPStreamController = class {
|
|
|
3314
3312
|
};
|
|
3315
3313
|
}
|
|
3316
3314
|
/**
|
|
3317
|
-
*
|
|
3315
|
+
* Ends the exchange with a failure the consumer is raising.
|
|
3318
3316
|
*
|
|
3319
3317
|
* @param error - The failure to end the exchange with
|
|
3320
3318
|
* @returns Never — the returned promise always rejects
|
|
@@ -3325,7 +3323,7 @@ var MCPStreamController = class {
|
|
|
3325
3323
|
throw error;
|
|
3326
3324
|
}
|
|
3327
3325
|
/**
|
|
3328
|
-
*
|
|
3326
|
+
* Ends the exchange permanently, with no terminal response.
|
|
3329
3327
|
*
|
|
3330
3328
|
* @remarks
|
|
3331
3329
|
* Idempotent, and the operation an owner that is not the consumer uses: a transport whose
|
|
@@ -3340,7 +3338,7 @@ var MCPStreamController = class {
|
|
|
3340
3338
|
if (!this.#closed) this.#fail(this.#closure.signal.reason);
|
|
3341
3339
|
}
|
|
3342
3340
|
/**
|
|
3343
|
-
*
|
|
3341
|
+
* Ends the exchange when the scope that owns it exits.
|
|
3344
3342
|
*
|
|
3345
3343
|
* @remarks
|
|
3346
3344
|
* {@link stop} under the name a `finally` calls it by, so a pump discharges its ownership
|
|
@@ -3353,7 +3351,7 @@ var MCPStreamController = class {
|
|
|
3353
3351
|
this.stop();
|
|
3354
3352
|
}
|
|
3355
3353
|
/**
|
|
3356
|
-
*
|
|
3354
|
+
* Iterates this exchange — the controller is its own iterator.
|
|
3357
3355
|
*
|
|
3358
3356
|
* @returns This controller
|
|
3359
3357
|
*/
|
|
@@ -3442,7 +3440,7 @@ var MCPStreamController = class {
|
|
|
3442
3440
|
var MCPTextStreamController = class {
|
|
3443
3441
|
#stream;
|
|
3444
3442
|
/**
|
|
3445
|
-
*
|
|
3443
|
+
* Mirrors one controlled typed exchange as its serialized sequence.
|
|
3446
3444
|
*
|
|
3447
3445
|
* @param stream - The controlled typed stream this face serializes and delegates into
|
|
3448
3446
|
*/
|
|
@@ -3450,7 +3448,7 @@ var MCPTextStreamController = class {
|
|
|
3450
3448
|
this.#stream = stream;
|
|
3451
3449
|
}
|
|
3452
3450
|
/**
|
|
3453
|
-
*
|
|
3451
|
+
* Reads the next serialized message, or the serialized terminating response.
|
|
3454
3452
|
*
|
|
3455
3453
|
* @returns The next message as a string, or the terminal as the iteration's `return`
|
|
3456
3454
|
* @throws Whatever ended the typed exchange, unserialized — an abort is not a message
|
|
@@ -3467,7 +3465,7 @@ var MCPTextStreamController = class {
|
|
|
3467
3465
|
};
|
|
3468
3466
|
}
|
|
3469
3467
|
/**
|
|
3470
|
-
*
|
|
3468
|
+
* Ends the exchange because the consumer already has its answer.
|
|
3471
3469
|
*
|
|
3472
3470
|
* @remarks
|
|
3473
3471
|
* The typed exchange ends with no terminal, because a string is not a
|
|
@@ -3486,7 +3484,7 @@ var MCPTextStreamController = class {
|
|
|
3486
3484
|
};
|
|
3487
3485
|
}
|
|
3488
3486
|
/**
|
|
3489
|
-
*
|
|
3487
|
+
* Ends the exchange with a failure the consumer is raising.
|
|
3490
3488
|
*
|
|
3491
3489
|
* @param error - The failure to end the exchange with
|
|
3492
3490
|
* @returns Never — the returned promise always rejects
|
|
@@ -3497,7 +3495,7 @@ var MCPTextStreamController = class {
|
|
|
3497
3495
|
throw error;
|
|
3498
3496
|
}
|
|
3499
3497
|
/**
|
|
3500
|
-
*
|
|
3498
|
+
* Ends the typed exchange permanently, with no terminal response.
|
|
3501
3499
|
*
|
|
3502
3500
|
* @returns Nothing
|
|
3503
3501
|
*/
|
|
@@ -3505,7 +3503,7 @@ var MCPTextStreamController = class {
|
|
|
3505
3503
|
this.#stream.stop();
|
|
3506
3504
|
}
|
|
3507
3505
|
/**
|
|
3508
|
-
*
|
|
3506
|
+
* Ends the typed exchange when the scope that owns this face exits.
|
|
3509
3507
|
*
|
|
3510
3508
|
* @remarks
|
|
3511
3509
|
* Delegates downward exactly as {@link stop} does — disposing the serialized arm is
|
|
@@ -3517,7 +3515,7 @@ var MCPTextStreamController = class {
|
|
|
3517
3515
|
this.#stream.stop();
|
|
3518
3516
|
}
|
|
3519
3517
|
/**
|
|
3520
|
-
*
|
|
3518
|
+
* Iterates this exchange — the controller is its own iterator.
|
|
3521
3519
|
*
|
|
3522
3520
|
* @returns This controller
|
|
3523
3521
|
*/
|
|
@@ -3528,7 +3526,7 @@ var MCPTextStreamController = class {
|
|
|
3528
3526
|
//#endregion
|
|
3529
3527
|
//#region src/core/MCPLegacy.ts
|
|
3530
3528
|
/**
|
|
3531
|
-
*
|
|
3529
|
+
* Translates the fixed legacy method set onto one modern dispatcher.
|
|
3532
3530
|
*
|
|
3533
3531
|
* @remarks
|
|
3534
3532
|
* This decorator owns no execution engine or result normalizer. Modern invocations
|
|
@@ -3539,7 +3537,7 @@ var MCPTextStreamController = class {
|
|
|
3539
3537
|
var MCPLegacy = class {
|
|
3540
3538
|
#options;
|
|
3541
3539
|
/**
|
|
3542
|
-
*
|
|
3540
|
+
* Creates a legacy decorator.
|
|
3543
3541
|
*
|
|
3544
3542
|
* @param options - The sole dispatcher and legacy handshake identity
|
|
3545
3543
|
*/
|
|
@@ -3648,7 +3646,7 @@ var MCPLegacy = class {
|
|
|
3648
3646
|
* requests over a live {@link ToolManagerInterface}, with NO transport coupling.
|
|
3649
3647
|
*
|
|
3650
3648
|
* @remarks
|
|
3651
|
-
* -
|
|
3649
|
+
* - **`dispatch` and `handle`.** `dispatch(invocation)` runs an already-parsed invocation and
|
|
3652
3650
|
* resolves a {@link JSONRPCResponse} for a request — or `undefined` for a
|
|
3653
3651
|
* {@link JSONRPCNotification}, which carries no `id` and is answered by nothing.
|
|
3654
3652
|
* `handle(message)` is the string boundary: it
|
|
@@ -3664,8 +3662,8 @@ var MCPLegacy = class {
|
|
|
3664
3662
|
* every dispatch: the same path a later method or a consumer's own takes, with an
|
|
3665
3663
|
* unregistered method still answering `-32601`.
|
|
3666
3664
|
* - **Provider-agnostic.** Imports only core siblings — JSON-RPC + the tool registry,
|
|
3667
|
-
* no HTTP, no model. Wire fields are narrowed
|
|
3668
|
-
* - **Observable
|
|
3665
|
+
* no HTTP, no model. Wire fields are narrowed with the contract guards (no `as`).
|
|
3666
|
+
* - **Observable.** The owned `emitter` fires `request` at the top of every
|
|
3669
3667
|
* dispatch; the emitter isolates a listener throw and routes it to its `error` handler
|
|
3670
3668
|
* (the `error` option), so a listener throw can never escape the dispatch.
|
|
3671
3669
|
*
|
|
@@ -3786,6 +3784,11 @@ var MCPServer = class {
|
|
|
3786
3784
|
}
|
|
3787
3785
|
async #modern(request, options) {
|
|
3788
3786
|
const id = request.id;
|
|
3787
|
+
const method = request.method;
|
|
3788
|
+
if (!isModernRequest(request)) {
|
|
3789
|
+
if (this.#methods.method(method) === void 0) return buildJSONRPCError(id, JSONRPC_METHOD_NOT_FOUND, `Method not found: ${method}`);
|
|
3790
|
+
return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request declares no protocol version");
|
|
3791
|
+
}
|
|
3789
3792
|
const context = parseRequestContext(request, {
|
|
3790
3793
|
bytes: this.#limits.message,
|
|
3791
3794
|
depth: this.#limits.depth
|
|
@@ -4314,7 +4317,7 @@ var MCPServer = class {
|
|
|
4314
4317
|
//#endregion
|
|
4315
4318
|
//#region src/core/MCPTaskClient.ts
|
|
4316
4319
|
/**
|
|
4317
|
-
* The CLIENT half of the draft Tasks extension — the
|
|
4320
|
+
* The CLIENT half of the draft Tasks extension — the `tasks/*` methods over one
|
|
4318
4321
|
* correlated-request door, exposed as an {@link import('./types.js').MCPClientInterface}'s
|
|
4319
4322
|
* `tasks`.
|
|
4320
4323
|
*
|
|
@@ -4334,7 +4337,7 @@ var MCPServer = class {
|
|
|
4334
4337
|
* client's id space, pending table, deadline policy, and `disconnect` drain rather than
|
|
4335
4338
|
* opening a second path to the same peer.
|
|
4336
4339
|
* - **The peer authorizes.** Nothing is checked locally. A server that never configured the
|
|
4337
|
-
* extension answers
|
|
4340
|
+
* extension answers each of them `-32601`, and a task that never existed, one whose TTL purged
|
|
4338
4341
|
* it, and one belonging to another principal are all the same `-32602` — a refusal this
|
|
4339
4342
|
* client passes on rather than resolving into a distinction the peer refused to publish.
|
|
4340
4343
|
*
|
|
@@ -4411,16 +4414,16 @@ var MCPTaskClient = class {
|
|
|
4411
4414
|
* exit — the deadline, an abort, a rejecting `send`, the answer itself, and the
|
|
4412
4415
|
* teardown's drain — without any of those paths knowing they exist.
|
|
4413
4416
|
* - **Per-request deadline.** An ordinary `#request` carries `this.#timeout`. The initial
|
|
4414
|
-
* discovery probe uses that same
|
|
4415
|
-
*
|
|
4417
|
+
* discovery probe uses that same configured deadline, so a silent peer cannot hold
|
|
4418
|
+
* negotiation indefinitely.
|
|
4416
4419
|
* `AbortSignal.timeout` (never a raw `setTimeout`) rejects only that pending request, and the
|
|
4417
4420
|
* same deadline bounds the WAIT on the transport's `close`, the one wait no drain and no signal
|
|
4418
4421
|
* can reach. It bounds the wait rather than the close, which keeps running, so a retry joins it
|
|
4419
4422
|
* instead of shutting one connection down twice.
|
|
4420
4423
|
* - **Transport-agnostic.** Imports only core siblings (JSON-RPC + the tool vocabulary);
|
|
4421
|
-
* the concrete transport is injected. Wire fields are narrowed
|
|
4424
|
+
* the concrete transport is injected. Wire fields are narrowed with the contract
|
|
4422
4425
|
* guards (no `as`).
|
|
4423
|
-
* - **Observable
|
|
4426
|
+
* - **Observable.** The owned `emitter` fires `connect` / `disconnect` /
|
|
4424
4427
|
* `notification` / `error`; the emitter isolates a listener throw and routes it to its
|
|
4425
4428
|
* `error` handler (the `error` option), so a listener throw can never escape.
|
|
4426
4429
|
*
|
|
@@ -4441,7 +4444,6 @@ var MCPClient = class {
|
|
|
4441
4444
|
#capabilities;
|
|
4442
4445
|
#pin;
|
|
4443
4446
|
#timeout;
|
|
4444
|
-
#probe;
|
|
4445
4447
|
#tasks;
|
|
4446
4448
|
#pending = /* @__PURE__ */ new Map();
|
|
4447
4449
|
#nextId = 0;
|
|
@@ -4456,6 +4458,11 @@ var MCPClient = class {
|
|
|
4456
4458
|
#era = void 0;
|
|
4457
4459
|
#offer;
|
|
4458
4460
|
constructor(options) {
|
|
4461
|
+
const requested = options.version;
|
|
4462
|
+
if (requested !== void 0 && !isMCPVersion(requested)) throw new MCPError("Unsupported protocol version", MCP_UNSUPPORTED_VERSION, {
|
|
4463
|
+
supported: SUPPORTED_PROTOCOL_VERSIONS,
|
|
4464
|
+
requested
|
|
4465
|
+
});
|
|
4459
4466
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
4460
4467
|
...options.on !== void 0 ? { on: options.on } : {},
|
|
4461
4468
|
...options.error !== void 0 ? { error: options.error } : {}
|
|
@@ -4466,10 +4473,9 @@ var MCPClient = class {
|
|
|
4466
4473
|
version: "1.0.0"
|
|
4467
4474
|
};
|
|
4468
4475
|
this.#capabilities = options.capabilities ?? {};
|
|
4469
|
-
this.#pin =
|
|
4470
|
-
this.#offer =
|
|
4476
|
+
this.#pin = requested;
|
|
4477
|
+
this.#offer = requested ?? "2026-07-28";
|
|
4471
4478
|
this.#timeout = options.timeout ?? 3e4;
|
|
4472
|
-
this.#probe = options.timeout === void 0 ? DEFAULT_MCP_REQUEST_TIMEOUT : Math.min(options.timeout, 50);
|
|
4473
4479
|
this.#tasks = new MCPTaskClient({
|
|
4474
4480
|
request: this.#request.bind(this),
|
|
4475
4481
|
timeout: this.#timeout
|
|
@@ -4491,9 +4497,6 @@ var MCPClient = class {
|
|
|
4491
4497
|
get tasks() {
|
|
4492
4498
|
return this.#tasks;
|
|
4493
4499
|
}
|
|
4494
|
-
on(event, handler) {
|
|
4495
|
-
this.#emitter.on(event, handler);
|
|
4496
|
-
}
|
|
4497
4500
|
async connect() {
|
|
4498
4501
|
for (;;) {
|
|
4499
4502
|
const closing = this.#disconnecting;
|
|
@@ -4526,7 +4529,7 @@ var MCPClient = class {
|
|
|
4526
4529
|
}
|
|
4527
4530
|
}
|
|
4528
4531
|
async discover() {
|
|
4529
|
-
const received = await this.#request("server/discover", void 0, this.#
|
|
4532
|
+
const received = await this.#request("server/discover", void 0, this.#timeout, this.#version ?? this.#offer);
|
|
4530
4533
|
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(received));
|
|
4531
4534
|
if (!owned.success) throw new MCPError("MCP server returned a malformed discovery result", JSONRPC_INVALID_PARAMS);
|
|
4532
4535
|
const result = owned.value;
|
|
@@ -4736,7 +4739,13 @@ var MCPClient = class {
|
|
|
4736
4739
|
await this.#initialize(generation, MCP_PROTOCOL_VERSION);
|
|
4737
4740
|
return;
|
|
4738
4741
|
}
|
|
4739
|
-
|
|
4742
|
+
let version;
|
|
4743
|
+
if (this.#pin === void 0) version = inferVersion(discovery.supportedVersions);
|
|
4744
|
+
else if (discovery.supportedVersions.includes(this.#pin)) version = this.#pin;
|
|
4745
|
+
else throw new MCPError("MCP server does not support the pinned protocol version", MCP_UNSUPPORTED_VERSION, {
|
|
4746
|
+
supported: discovery.supportedVersions,
|
|
4747
|
+
requested: this.#pin
|
|
4748
|
+
});
|
|
4740
4749
|
if (version === void 0) throw new MCPError("MCP server supports no compatible protocol version", MCP_UNSUPPORTED_VERSION, { supported: discovery.supportedVersions });
|
|
4741
4750
|
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4742
4751
|
this.#version = version;
|
|
@@ -4812,6 +4821,10 @@ var MCPClient = class {
|
|
|
4812
4821
|
if (protocol === void 0) throw new Error("MCP server returned no protocol version");
|
|
4813
4822
|
if (!(0, _orkestrel_contract.isString)(protocol)) throw new Error("MCP server returned a malformed protocol version");
|
|
4814
4823
|
if (!isMCPVersion(protocol) || inferEra(protocol) !== "legacy") throw new Error(`MCP server negotiated unsupported protocol version '${protocol}'`);
|
|
4824
|
+
if (this.#pin !== void 0 && protocol !== this.#pin) throw new MCPError("MCP server negotiated a different protocol version than the client pinned", MCP_UNSUPPORTED_VERSION, {
|
|
4825
|
+
requested: this.#pin,
|
|
4826
|
+
negotiated: protocol
|
|
4827
|
+
});
|
|
4815
4828
|
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4816
4829
|
await Promise.race([this.#transport.send({
|
|
4817
4830
|
jsonrpc: "2.0",
|
|
@@ -4845,7 +4858,7 @@ var MCPClient = class {
|
|
|
4845
4858
|
//#endregion
|
|
4846
4859
|
//#region src/core/factories.ts
|
|
4847
4860
|
/**
|
|
4848
|
-
*
|
|
4861
|
+
* Creates a transport-agnostic Model Context Protocol server — exposes a live
|
|
4849
4862
|
* {@link import('@orkestrel/tool').ToolManagerInterface} and an optional
|
|
4850
4863
|
* {@link import('./types.js').MCPResourceManagerInterface},
|
|
4851
4864
|
* {@link import('./types.js').MCPPromptManagerInterface}, and
|
|
@@ -4858,7 +4871,7 @@ var MCPClient = class {
|
|
|
4858
4871
|
* and no model. The {@link import('@orkestrel/tool').ToolManagerInterface} already
|
|
4859
4872
|
* isolates a thrown tool into a `success: false` result (surfaced as an MCP
|
|
4860
4873
|
* `isError: true` tool result), so a misbehaving tool never crashes a dispatch. Subscribe to the
|
|
4861
|
-
* `request` event
|
|
4874
|
+
* `request` event through `server.emitter.on('request', …)` for tracing.
|
|
4862
4875
|
*
|
|
4863
4876
|
* @param options - `identity` (the server identity), `tools` (the live tool
|
|
4864
4877
|
* registry), optional `resources` (the consumer-owned resource registry), optional
|
|
@@ -4870,7 +4883,8 @@ var MCPClient = class {
|
|
|
4870
4883
|
*
|
|
4871
4884
|
* @example
|
|
4872
4885
|
* ```ts
|
|
4873
|
-
* import { createMCPServer
|
|
4886
|
+
* import { createMCPServer } from '@orkestrel/mcp'
|
|
4887
|
+
* import { createTool, createToolManager } from '@orkestrel/tool'
|
|
4874
4888
|
*
|
|
4875
4889
|
* const tools = createToolManager()
|
|
4876
4890
|
* tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
|
|
@@ -4887,7 +4901,7 @@ function createMCPServer(options) {
|
|
|
4887
4901
|
return new MCPServer(options);
|
|
4888
4902
|
}
|
|
4889
4903
|
/**
|
|
4890
|
-
*
|
|
4904
|
+
* Decorates one MCP server with the fixed legacy method translation.
|
|
4891
4905
|
*
|
|
4892
4906
|
* @param server - The sole modern dispatcher and handshake identity source
|
|
4893
4907
|
* @returns A dispatcher accepting both modern and legacy invocations
|
|
@@ -4899,7 +4913,7 @@ function createMCPLegacy(server) {
|
|
|
4899
4913
|
});
|
|
4900
4914
|
}
|
|
4901
4915
|
/**
|
|
4902
|
-
*
|
|
4916
|
+
* Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
|
|
4903
4917
|
* MCP server over an injected {@link import('./types.js').MCPClientTransportInterface},
|
|
4904
4918
|
* runs the `initialize` handshake, and exposes the server's tools as local
|
|
4905
4919
|
* {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
|
|
@@ -4913,8 +4927,7 @@ function createMCPLegacy(server) {
|
|
|
4913
4927
|
* locally, so an agent's {@link import('@orkestrel/tool').ToolManagerInterface}
|
|
4914
4928
|
* isolates it). The transport is injected — a concrete one (the HTTP transport over
|
|
4915
4929
|
* `fetch`) lives in the published server environment; the client itself is provider-agnostic. Subscribe
|
|
4916
|
-
* to `connect` / `disconnect` / `notification`
|
|
4917
|
-
* `client.emitter.on(...)`).
|
|
4930
|
+
* to `connect` / `disconnect` / `notification` through `client.emitter.on(...)`.
|
|
4918
4931
|
*
|
|
4919
4932
|
* @param options - `transport` (the carrier; REQUIRED), an optional `identity`
|
|
4920
4933
|
* (the client identity), `timeout` (the per-request deadline), and the reserved `on`
|
|
@@ -4923,7 +4936,7 @@ function createMCPLegacy(server) {
|
|
|
4923
4936
|
*
|
|
4924
4937
|
* @example
|
|
4925
4938
|
* ```ts
|
|
4926
|
-
* import { createMCPClient } from '@
|
|
4939
|
+
* import { createMCPClient } from '@orkestrel/mcp'
|
|
4927
4940
|
* import { createHTTPClientTransport } from '@orkestrel/mcp/server'
|
|
4928
4941
|
*
|
|
4929
4942
|
* const client = createMCPClient({
|
|
@@ -4938,7 +4951,7 @@ function createMCPClient(options) {
|
|
|
4938
4951
|
return new MCPClient(options);
|
|
4939
4952
|
}
|
|
4940
4953
|
/**
|
|
4941
|
-
*
|
|
4954
|
+
* Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
4942
4955
|
* channel) into a {@link MCPClientTransportInterface} — the additive bridge that lets
|
|
4943
4956
|
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
4944
4957
|
* existing shape.
|
|
@@ -4946,7 +4959,7 @@ function createMCPClient(options) {
|
|
|
4946
4959
|
* @remarks
|
|
4947
4960
|
* Hand the RESULT to `createMCPClient({ transport })`, then pass the SAME
|
|
4948
4961
|
* `transport` to {@link import('./helpers.js').bindClient} to complete the inbound
|
|
4949
|
-
* wiring: `send` serializes each outbound {@link JSONRPCMessage} and writes it
|
|
4962
|
+
* wiring: `send` serializes each outbound {@link JSONRPCMessage} and writes it through
|
|
4950
4963
|
* `transport.send`; `close` closes the underlying
|
|
4951
4964
|
* `transport`; `start` is a no-op (the duplex channel is already open by the time
|
|
4952
4965
|
* it is handed in — there is no separate connect step at this layer); `session` is
|
|
@@ -4990,7 +5003,6 @@ exports.DEFAULT_MCP_CACHE_TTL = DEFAULT_MCP_CACHE_TTL;
|
|
|
4990
5003
|
exports.DEFAULT_MCP_CLIENT_NAME = DEFAULT_MCP_CLIENT_NAME;
|
|
4991
5004
|
exports.DEFAULT_MCP_CLIENT_VERSION = DEFAULT_MCP_CLIENT_VERSION;
|
|
4992
5005
|
exports.DEFAULT_MCP_LIMITS = DEFAULT_MCP_LIMITS;
|
|
4993
|
-
exports.DEFAULT_MCP_PROBE_TIMEOUT = DEFAULT_MCP_PROBE_TIMEOUT;
|
|
4994
5006
|
exports.DEFAULT_MCP_REQUEST_TIMEOUT = DEFAULT_MCP_REQUEST_TIMEOUT;
|
|
4995
5007
|
exports.EMPTY_MCP_ARGUMENTS = EMPTY_MCP_ARGUMENTS;
|
|
4996
5008
|
exports.JSONRPC_INTERNAL_ERROR = JSONRPC_INTERNAL_ERROR;
|