@orkestrel/mcp 0.0.25 → 0.0.27
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/dist/src/browser/index.d.ts +13 -4
- package/dist/src/browser/index.js +68 -15
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +754 -68
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +657 -44
- package/dist/src/core/index.d.ts +657 -44
- package/dist/src/core/index.js +734 -69
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +212 -33
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +123 -22
- package/dist/src/server/index.d.ts +123 -22
- package/dist/src/server/index.js +213 -36
- package/dist/src/server/index.js.map +1 -1
- package/package.json +12 -11
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
|
+
let _orkestrel_codec = require("@orkestrel/codec");
|
|
3
4
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
4
5
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
5
6
|
//#region src/core/constants.ts
|
|
@@ -50,6 +51,53 @@ var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
|
|
|
50
51
|
* the extension defines no options, so presence is the entire declaration.
|
|
51
52
|
*/
|
|
52
53
|
var MCP_EXTENSION_TASKS = "io.modelcontextprotocol/tasks";
|
|
54
|
+
/**
|
|
55
|
+
* The opening marker of the Base64 sentinel a standard MCP header value travels in.
|
|
56
|
+
*
|
|
57
|
+
* @remarks
|
|
58
|
+
* The markers are LOWERCASE and exact, and this constant with {@link MCP_SENTINEL_SUFFIX} is
|
|
59
|
+
* their ONE spelling in this package: {@link import('@orkestrel/mcp').encodeSentinel} builds a
|
|
60
|
+
* sentinel from them and {@link import('@orkestrel/mcp').decodeSentinel} recognizes one by
|
|
61
|
+
* them, so the two directions cannot drift apart.
|
|
62
|
+
*/
|
|
63
|
+
var MCP_SENTINEL_PREFIX = "=?base64?";
|
|
64
|
+
/** The closing marker of the Base64 sentinel a standard MCP header value travels in. */
|
|
65
|
+
var MCP_SENTINEL_SUFFIX = "?=";
|
|
66
|
+
/**
|
|
67
|
+
* The request-header prefix an `x-mcp-header` annotation projects a tool argument onto.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* The full field name is this prefix followed by the annotation's own value verbatim, so
|
|
71
|
+
* `x-mcp-header: 'Region'` becomes `Mcp-Param-Region`. HTTP field names are case-insensitive,
|
|
72
|
+
* which is why {@link MCP_HEADER_ANNOTATION} values are unique case-insensitively within one
|
|
73
|
+
* `inputSchema`.
|
|
74
|
+
*/
|
|
75
|
+
var MCP_PARAM_PREFIX = "Mcp-Param-";
|
|
76
|
+
/**
|
|
77
|
+
* The tool-schema annotation key naming the header one parameter projects into.
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* It is valid ONLY on a primitive property schema statically reachable from the `inputSchema`
|
|
81
|
+
* root through `properties` keys alone. An occurrence anywhere else — under `items`, a
|
|
82
|
+
* composition or conditional keyword, or a `$ref` target — makes the whole tool definition
|
|
83
|
+
* invalid, which is what {@link import('@orkestrel/mcp').buildHeaderParameters} decides.
|
|
84
|
+
*/
|
|
85
|
+
var MCP_HEADER_ANNOTATION = "x-mcp-header";
|
|
86
|
+
/**
|
|
87
|
+
* The `tools/list` pages one modern `tools/call` walks to reach its own annotations.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* The HTTP POST handler reads a called tool's {@link MCP_HEADER_ANNOTATION} annotations by
|
|
91
|
+
* dispatching `tools/list` fresh on every `tools/call`, following `nextCursor` until the
|
|
92
|
+
* named tool is found or the answer carries no cursor. The walk is bounded because its cost
|
|
93
|
+
* is paid per call: at a page size of 100 this bound reaches 800 definitions, and a consumer
|
|
94
|
+
* whose replacement `tools/list` pages more finely than that pays the extra dispatches on
|
|
95
|
+
* every call it serves. The built-in listing answers the whole registry on one page and
|
|
96
|
+
* never reaches the second. A definition further in than the walk reaches reads as no
|
|
97
|
+
* definition, so its {@link MCP_PARAM_PREFIX} headers are forwarded untouched — the same
|
|
98
|
+
* answer a name no served definition annotates receives.
|
|
99
|
+
*/
|
|
100
|
+
var MCP_LOOKUP_PAGES = 8;
|
|
53
101
|
/** MCP reserved error: required HTTP metadata does not match the request body. */
|
|
54
102
|
var MCP_HEADER_MISMATCH = -32020;
|
|
55
103
|
/**
|
|
@@ -83,10 +131,11 @@ var DEFAULT_MCP_CACHE_TTL = 6e4;
|
|
|
83
131
|
* One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
|
|
84
132
|
* extension-rich modern metadata and signed multi-round state; four MiB admits substantial
|
|
85
133
|
* JSON tool output without allowing an unconfigured service to serialize arbitrary process
|
|
86
|
-
* memory; 64
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* defaults observed by later
|
|
134
|
+
* memory; 64 keys admits `_meta`'s reserved keys plus many extensions, and bounds a produced
|
|
135
|
+
* result's breadth by the same leaf; 128 concurrent streams admits a busy service while
|
|
136
|
+
* bounding retained producers; depth 32 admits ordinary JSON documents while rejecting
|
|
137
|
+
* stack-hostile nesting. Frozen so callers cannot alter the defaults observed by later
|
|
138
|
+
* servers.
|
|
90
139
|
*/
|
|
91
140
|
var DEFAULT_MCP_LIMITS = Object.freeze({
|
|
92
141
|
message: 1048576,
|
|
@@ -432,17 +481,18 @@ function parseRequestContext(value, limits = {
|
|
|
432
481
|
* This parser does not open the opaque continuation carrier; the configured
|
|
433
482
|
* continuation port performs that boundary first. The protected
|
|
434
483
|
* payload binds the authenticated principal, absolute expiry, ORIGINAL request id, version,
|
|
435
|
-
* method,
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
484
|
+
* method, the exact round that was issued, tool name, argument digest, and optional
|
|
485
|
+
* application state. Every member is required except application state: a payload missing its
|
|
486
|
+
* round cannot have the client's answers enforced, so it is refused rather than admitted
|
|
487
|
+
* unenforced. An EMPTY round is refused for the same reason — a retry against it would answer
|
|
488
|
+
* no question at all. Total over malformed or hostile input.
|
|
439
489
|
*
|
|
440
490
|
* @param value - The opened canonical continuation value to parse
|
|
441
491
|
* @returns The protected input state, or `undefined` when malformed
|
|
442
492
|
*
|
|
443
493
|
* @example
|
|
444
494
|
* ```ts
|
|
445
|
-
* parseMCPInputState('{"principal":"user-1","expiry":2000,"id":1,"version":"2026-07-28","method":"tools/call","
|
|
495
|
+
* parseMCPInputState('{"principal":"user-1","expiry":2000,"id":1,"version":"2026-07-28","method":"tools/call","requests":{"k":{"method":"roots/list"}},"name":"reply","digest":"abc"}')
|
|
446
496
|
* ```
|
|
447
497
|
*/
|
|
448
498
|
function parseMCPInputState(value) {
|
|
@@ -455,26 +505,24 @@ function parseMCPInputState(value) {
|
|
|
455
505
|
const id = parsed["id"];
|
|
456
506
|
const version = parsed["version"];
|
|
457
507
|
const method = parsed["method"];
|
|
458
|
-
const
|
|
508
|
+
const requests = parsed["requests"];
|
|
459
509
|
const name = parsed["name"];
|
|
460
510
|
const digest = parsed["digest"];
|
|
461
|
-
const schema = parsed["schema"];
|
|
462
511
|
const state = parsed["state"];
|
|
463
512
|
if (!(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || !(0, _orkestrel_contract.isNumber)(expiry) || !Number.isFinite(expiry)) return;
|
|
464
513
|
if (!isJSONRPCId(id)) return void 0;
|
|
465
|
-
if (!(0, _orkestrel_contract.isString)(version) || !(0, _orkestrel_contract.isString)(method) || !(0, _orkestrel_contract.isString)(
|
|
514
|
+
if (!(0, _orkestrel_contract.isString)(version) || !(0, _orkestrel_contract.isString)(method) || !(0, _orkestrel_contract.isString)(name)) return void 0;
|
|
466
515
|
if (!(0, _orkestrel_contract.isString)(digest) || !(0, _orkestrel_contract.isUndefined)(state) && !(0, _orkestrel_contract.isJSONValue)(state)) return void 0;
|
|
467
|
-
if (!
|
|
516
|
+
if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
|
|
468
517
|
return {
|
|
469
518
|
principal,
|
|
470
519
|
expiry,
|
|
471
520
|
id,
|
|
472
521
|
version,
|
|
473
522
|
method,
|
|
474
|
-
|
|
523
|
+
requests,
|
|
475
524
|
name,
|
|
476
525
|
digest,
|
|
477
|
-
schema,
|
|
478
526
|
...(0, _orkestrel_contract.isUndefined)(state) ? {} : { state }
|
|
479
527
|
};
|
|
480
528
|
} catch {
|
|
@@ -513,6 +561,68 @@ function isFormElicitationSupported(value) {
|
|
|
513
561
|
}
|
|
514
562
|
}
|
|
515
563
|
/**
|
|
564
|
+
* Computes the capabilities one round of input requests needs and the client did not declare.
|
|
565
|
+
*
|
|
566
|
+
* @remarks
|
|
567
|
+
* The protocol's rule is about SENDING: a server never issues a request kind the client's
|
|
568
|
+
* declared capabilities exclude. So this reads the round rather than the method, and it
|
|
569
|
+
* answers with the refusal's own payload — the `requiredCapabilities` record a
|
|
570
|
+
* `MissingRequiredClientCapability` error carries, keyed by each missing capability, in the
|
|
571
|
+
* `ClientCapabilities` shape the schema defines rather than as a list of names.
|
|
572
|
+
*
|
|
573
|
+
* Each kind maps to one declaration: `sampling/createMessage` to `sampling`, `roots/list` to
|
|
574
|
+
* `roots`, a form elicitation to what {@link isFormElicitationSupported} accepts, and a
|
|
575
|
+
* URL-mode elicitation to a record-valued `elicitation.url`. A request this package cannot
|
|
576
|
+
* recognize needs nothing, because {@link import('./validators.js').isMCPInputRequestMap}
|
|
577
|
+
* has already refused the round it would have travelled in. Total over hostile input.
|
|
578
|
+
*
|
|
579
|
+
* The `elicitation` value names the ARM the round needs, so a client can act on the refusal
|
|
580
|
+
* by declaring exactly what the payload asks for. A missing URL arm answers `{ url: {} }`, a
|
|
581
|
+
* missing form arm answers the empty record this package reads as form-only, and a round
|
|
582
|
+
* needing both answers `{ form: {}, url: {} }`. An empty record for a URL round would name
|
|
583
|
+
* the declaration a URL-capable client already sent, and refuse the identical round again.
|
|
584
|
+
*
|
|
585
|
+
* @param requests - The round the server is about to issue
|
|
586
|
+
* @param capabilities - The client capability record the request declared
|
|
587
|
+
* @returns The missing capabilities, or `undefined` when the client declared every one
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* computeMissingCapabilities({ answer: { method: 'roots/list' } }, {}) // { roots: {} }
|
|
592
|
+
* computeMissingCapabilities({ answer: { method: 'roots/list' } }, { roots: {} }) // undefined
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
function computeMissingCapabilities(requests, capabilities) {
|
|
596
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(capabilities));
|
|
597
|
+
const declared = owned.success ? owned.value : {};
|
|
598
|
+
const missing = {};
|
|
599
|
+
let formUndeclared = false;
|
|
600
|
+
let urlUndeclared = false;
|
|
601
|
+
for (const request of Object.values(requests)) {
|
|
602
|
+
if (request.method === "sampling/createMessage") {
|
|
603
|
+
if (!(0, _orkestrel_contract.isRecord)(declared["sampling"])) missing["sampling"] = {};
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (request.method === "roots/list") {
|
|
607
|
+
if (!(0, _orkestrel_contract.isRecord)(declared["roots"])) missing["roots"] = {};
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const elicitation = declared["elicitation"];
|
|
611
|
+
if (request.params.mode === "url") {
|
|
612
|
+
if (!(0, _orkestrel_contract.isRecord)(elicitation) || !(0, _orkestrel_contract.isRecord)(elicitation["url"])) urlUndeclared = true;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (!isFormElicitationSupported(declared)) formUndeclared = true;
|
|
616
|
+
}
|
|
617
|
+
if (formUndeclared && !urlUndeclared) missing["elicitation"] = {};
|
|
618
|
+
if (urlUndeclared && !formUndeclared) missing["elicitation"] = { url: {} };
|
|
619
|
+
if (formUndeclared && urlUndeclared) missing["elicitation"] = {
|
|
620
|
+
form: {},
|
|
621
|
+
url: {}
|
|
622
|
+
};
|
|
623
|
+
return Object.keys(missing).length === 0 ? void 0 : Object.freeze(missing);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
516
626
|
* Determines whether a client capability record declares the stable Tasks extension.
|
|
517
627
|
*
|
|
518
628
|
* @remarks
|
|
@@ -780,7 +890,7 @@ async function digestJSON(value, limits) {
|
|
|
780
890
|
const serialized = serializeJSON(value, limits);
|
|
781
891
|
if (serialized === void 0) return void 0;
|
|
782
892
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized));
|
|
783
|
-
return
|
|
893
|
+
return (0, _orkestrel_codec.encodeHex)(new Uint8Array(digest));
|
|
784
894
|
}
|
|
785
895
|
/**
|
|
786
896
|
* Builds one official progress notification for the original request stream.
|
|
@@ -1355,6 +1465,321 @@ function decodeBoundedMessage(message, limits) {
|
|
|
1355
1465
|
return parsed.success ? parseJSONRPCMessage(parsed.value, limits) : void 0;
|
|
1356
1466
|
}
|
|
1357
1467
|
/**
|
|
1468
|
+
* Reads the value one standard MCP request header carries, decoding the Base64 sentinel.
|
|
1469
|
+
*
|
|
1470
|
+
* @remarks
|
|
1471
|
+
* The sentinel format is `=?base64?{Base64OfUTF8}?=`, spelled once as
|
|
1472
|
+
* {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX} and read from there by both
|
|
1473
|
+
* directions of the codec.
|
|
1474
|
+
* The markers alone decide whether a value is a sentinel: a value carrying the prefix and the
|
|
1475
|
+
* suffix is one, and its payload is then held to `decodeBase64` from `@orkestrel/codec` — the
|
|
1476
|
+
* canonical RFC 4648 § 4 grammar, which admits exactly one spelling per byte sequence — and to
|
|
1477
|
+
* well-formed UTF-8. A payload leaving a non-zero bit in the sextet its padding discards is a
|
|
1478
|
+
* second spelling of a byte, so it is refused: `=?base64?QR==?=` reaches for the byte
|
|
1479
|
+
* `=?base64?QQ==?=` spells canonically, and only the canonical spelling decodes. A malformed
|
|
1480
|
+
* payload answers `undefined` rather than falling back to the literal, because the protocol
|
|
1481
|
+
* requires a server to REJECT invalid characters, and a fallback would admit the very value
|
|
1482
|
+
* the rule exists to refuse. A value missing either marker is a literal and comes back
|
|
1483
|
+
* unchanged.
|
|
1484
|
+
*
|
|
1485
|
+
* `decodeUTF8` from `@orkestrel/codec` reads the bytes back as text: strict RFC 3629, where an
|
|
1486
|
+
* overlong, an encoded surrogate, a code point past U+10FFFF, and a truncated sequence each
|
|
1487
|
+
* answer `undefined` rather than a replacement character, and total, so the refusal arrives as
|
|
1488
|
+
* that value instead of as a throw. It also keeps a leading U+FEFF as a character of the
|
|
1489
|
+
* value, where the platform decoder consumes it as a byte order mark — which is what lets a
|
|
1490
|
+
* value leading with U+FEFF survive {@link encodeSentinel} and come back whole.
|
|
1491
|
+
*
|
|
1492
|
+
* {@link import('./validators.js').isStandardBase64} is a wider and separate rule: it names
|
|
1493
|
+
* JSON Schema `byte` membership for the blob, image, and audio content a peer sends, where
|
|
1494
|
+
* this package receives liberally. It does not govern this payload.
|
|
1495
|
+
*
|
|
1496
|
+
* Optional whitespace is excluded first, per RFC 9110 § 5.5: a recipient parses a field value
|
|
1497
|
+
* with its surrounding spaces and horizontal tabs removed, so a peer that padded a plain value
|
|
1498
|
+
* still matches the body. A value whose own leading or trailing whitespace is significant
|
|
1499
|
+
* cannot survive that, which is what {@link encodeSentinel} encodes it for.
|
|
1500
|
+
*
|
|
1501
|
+
* Total — never throws, whatever the input.
|
|
1502
|
+
*
|
|
1503
|
+
* @param value - The raw header field value the peer sent
|
|
1504
|
+
* @returns The carried value, or `undefined` when the sentinel's payload is invalid
|
|
1505
|
+
*
|
|
1506
|
+
* @example
|
|
1507
|
+
* ```ts
|
|
1508
|
+
* decodeSentinel('=?base64?Y2Fmw6k=?=') // 'café'
|
|
1509
|
+
* decodeSentinel(' search ') // 'search' — optional whitespace excluded
|
|
1510
|
+
* decodeSentinel('=?base64?SGVsbG8?=') // undefined — invalid padding
|
|
1511
|
+
* decodeSentinel('=?base64?QR==?=') // undefined — a non-canonical spelling
|
|
1512
|
+
* ```
|
|
1513
|
+
*/
|
|
1514
|
+
function decodeSentinel(value) {
|
|
1515
|
+
const field = value.replace(/^[ \t]+|[ \t]+$/g, "");
|
|
1516
|
+
if (!(field.length >= 11 && field.startsWith("=?base64?") && field.endsWith("?="))) return field;
|
|
1517
|
+
const payload = field.slice(MCP_SENTINEL_PREFIX.length, field.length - 2);
|
|
1518
|
+
const bytes = (0, _orkestrel_codec.decodeBase64)(payload);
|
|
1519
|
+
if (bytes === void 0) return void 0;
|
|
1520
|
+
return (0, _orkestrel_codec.decodeUTF8)(bytes);
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Builds the wire form one standard MCP request header value must travel as.
|
|
1524
|
+
*
|
|
1525
|
+
* @remarks
|
|
1526
|
+
* The exact inverse of {@link decodeSentinel}, and its membership rule is stated as that
|
|
1527
|
+
* inverse rather than as a second list that could drift: a value travels LITERALLY when it is
|
|
1528
|
+
* plain printable ASCII — every code point in `U+0020`–`U+007E`, the RFC 9110 field-value
|
|
1529
|
+
* range this package admits — and {@link decodeSentinel} gives it back unchanged. Every other
|
|
1530
|
+
* value travels wrapped in {@link MCP_SENTINEL_PREFIX} and {@link MCP_SENTINEL_SUFFIX}, the
|
|
1531
|
+
* same markers the decode recognizes a sentinel by. `encodeBase64` from `@orkestrel/codec`
|
|
1532
|
+
* spells the payload, so the wire form carries the canonical spelling {@link decodeSentinel}
|
|
1533
|
+
* accepts.
|
|
1534
|
+
*
|
|
1535
|
+
* That one rule covers each row of the protocol's encoding table. A non-ASCII value and a
|
|
1536
|
+
* value carrying a control character fail the ASCII test. A value with leading or trailing
|
|
1537
|
+
* whitespace comes back trimmed, so it fails the round trip. A value already wearing the
|
|
1538
|
+
* sentinel markers decodes to something else, or to nothing, so it fails the round trip too
|
|
1539
|
+
* and is encoded rather than read back as a sentinel it never was.
|
|
1540
|
+
*
|
|
1541
|
+
* The bytes come from the platform `TextEncoder`, not from codec's `encodeUTF8`, and that is a
|
|
1542
|
+
* ruling rather than an oversight. `TextEncoder` is total: it spells ill-formed text — a lone
|
|
1543
|
+
* surrogate, which has no UTF-8 spelling — with the replacement character, so this function
|
|
1544
|
+
* answers a `string` for every input. `encodeUTF8` refuses that text with `undefined`, which
|
|
1545
|
+
* would widen this return to `string | undefined` and oblige every header projection to handle
|
|
1546
|
+
* a value it cannot send. The decode side carries no such tension, so it reads back through
|
|
1547
|
+
* codec's strict `decodeUTF8`.
|
|
1548
|
+
*
|
|
1549
|
+
* @param value - The value the header must carry
|
|
1550
|
+
* @returns The literal value, or its Base64 sentinel form
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```ts
|
|
1554
|
+
* encodeSentinel('search') // 'search'
|
|
1555
|
+
* encodeSentinel('café') // '=?base64?Y2Fmw6k=?='
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
function encodeSentinel(value) {
|
|
1559
|
+
if (/^[ -~]*$/.test(value) && decodeSentinel(value) === value) return value;
|
|
1560
|
+
return `${MCP_SENTINEL_PREFIX}${(0, _orkestrel_codec.encodeBase64)(new TextEncoder().encode(value))}?=`;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Counts every {@link MCP_HEADER_ANNOTATION} key one JSON value carries, at any position.
|
|
1564
|
+
*
|
|
1565
|
+
* @remarks
|
|
1566
|
+
* The companion of {@link extractHeaderAnnotations}, which reads only the annotations a
|
|
1567
|
+
* `properties` chain reaches. Comparing the two answers is how
|
|
1568
|
+
* {@link buildHeaderParameters} decides reachability without a second walk that would have
|
|
1569
|
+
* to re-state which JSON Schema keywords are traversable: an annotation the reachable walk
|
|
1570
|
+
* did not read is one sitting under `items`, a composition or conditional keyword, a `$ref`
|
|
1571
|
+
* target, or any other position, and the protocol makes the whole tool definition invalid for
|
|
1572
|
+
* it.
|
|
1573
|
+
*
|
|
1574
|
+
* Iterative and ancestor-tracked, so a deeply nested or self-referential value terminates
|
|
1575
|
+
* rather than exhausting the stack. Total — never throws, whatever the input.
|
|
1576
|
+
*
|
|
1577
|
+
* @param value - The value to scan, normally a tool's `inputSchema`
|
|
1578
|
+
* @returns How many annotation keys the value carries
|
|
1579
|
+
*
|
|
1580
|
+
* @example
|
|
1581
|
+
* ```ts
|
|
1582
|
+
* countHeaderAnnotations({ properties: { region: { 'x-mcp-header': 'Region' } } }) // 1
|
|
1583
|
+
* ```
|
|
1584
|
+
*/
|
|
1585
|
+
function countHeaderAnnotations(value) {
|
|
1586
|
+
let total = 0;
|
|
1587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1588
|
+
const pending = [value];
|
|
1589
|
+
while (pending.length > 0) {
|
|
1590
|
+
const node = pending.pop();
|
|
1591
|
+
if ((0, _orkestrel_contract.isArray)(node)) {
|
|
1592
|
+
if (seen.has(node)) continue;
|
|
1593
|
+
seen.add(node);
|
|
1594
|
+
for (const item of node) pending.push(item);
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
if (!(0, _orkestrel_contract.isRecord)(node) || seen.has(node)) continue;
|
|
1598
|
+
seen.add(node);
|
|
1599
|
+
for (const [key, member] of Object.entries(node)) if (key === "x-mcp-header") total += 1;
|
|
1600
|
+
else pending.push(member);
|
|
1601
|
+
}
|
|
1602
|
+
return total;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Reads every `x-mcp-header` annotation reachable from a schema node through `properties`.
|
|
1606
|
+
*
|
|
1607
|
+
* @remarks
|
|
1608
|
+
* Reachability is the protocol's own rule: an annotation counts only where a chain of
|
|
1609
|
+
* `properties` keys leads to it from the `inputSchema` root, so `path` is both the schema
|
|
1610
|
+
* position and the position the call's `arguments` carry the value at. A property named
|
|
1611
|
+
* `items` is reachable like any other, because the chain is read by key POSITION rather than
|
|
1612
|
+
* by key name.
|
|
1613
|
+
*
|
|
1614
|
+
* `undefined` means the definition is invalid rather than empty: a reachable annotation whose
|
|
1615
|
+
* value is not an {@link import('./validators.js').isFieldToken} token, one sitting on the
|
|
1616
|
+
* schema ROOT (which is no property), one on a leaf whose declared type is not an
|
|
1617
|
+
* {@link import('./validators.js').isMCPHeaderPrimitive} primitive, or a chain deeper than
|
|
1618
|
+
* `DEFAULT_MCP_LIMITS.depth` — which is also what makes a self-referential schema terminate.
|
|
1619
|
+
* A node that is not a record carries nothing and answers an empty list, because a leaf the
|
|
1620
|
+
* walk cannot read is not a violation.
|
|
1621
|
+
*
|
|
1622
|
+
* @param schema - The schema node to read
|
|
1623
|
+
* @param path - The `properties` keys already traversed; the root is called with `[]`
|
|
1624
|
+
* @returns The annotations reachable from this node, or `undefined` when one is invalid
|
|
1625
|
+
*
|
|
1626
|
+
* @example
|
|
1627
|
+
* ```ts
|
|
1628
|
+
* extractHeaderAnnotations({ properties: { region: { type: 'string', 'x-mcp-header': 'Region' } } }, [])
|
|
1629
|
+
* // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
|
|
1630
|
+
* ```
|
|
1631
|
+
*/
|
|
1632
|
+
function extractHeaderAnnotations(schema, path) {
|
|
1633
|
+
if (path.length > DEFAULT_MCP_LIMITS.depth) return void 0;
|
|
1634
|
+
if (!(0, _orkestrel_contract.isRecord)(schema)) return [];
|
|
1635
|
+
const found = [];
|
|
1636
|
+
const annotation = schema[MCP_HEADER_ANNOTATION];
|
|
1637
|
+
if (annotation !== void 0) {
|
|
1638
|
+
if (path.length === 0 || !isFieldToken(annotation)) return void 0;
|
|
1639
|
+
const primitive = schema["type"];
|
|
1640
|
+
if (!isMCPHeaderPrimitive(primitive)) return void 0;
|
|
1641
|
+
found.push({
|
|
1642
|
+
name: annotation,
|
|
1643
|
+
path,
|
|
1644
|
+
primitive
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
const properties = schema["properties"];
|
|
1648
|
+
if ((0, _orkestrel_contract.isRecord)(properties)) for (const [key, leaf] of Object.entries(properties)) {
|
|
1649
|
+
const nested = extractHeaderAnnotations(leaf, [...path, key]);
|
|
1650
|
+
if (nested === void 0) return void 0;
|
|
1651
|
+
found.push(...nested);
|
|
1652
|
+
}
|
|
1653
|
+
return found;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Builds the `x-mcp-header` projections one tool's `inputSchema` declares.
|
|
1657
|
+
*
|
|
1658
|
+
* @remarks
|
|
1659
|
+
* The single decision both sides of the protocol make about an annotated tool: an HTTP
|
|
1660
|
+
* CLIENT excludes a definition this refuses from the `tools/list` result it delivers, and a
|
|
1661
|
+
* SERVER recognizes exactly the `Mcp-Param-*` names this returns for its own definitions.
|
|
1662
|
+
*
|
|
1663
|
+
* `undefined` means the definition is invalid, and every rule the protocol states produces
|
|
1664
|
+
* it: a value that is not an RFC 9110 token, a non-primitive or untyped annotated leaf, a
|
|
1665
|
+
* name repeated case-insensitively within the schema, an annotation the `properties` chain
|
|
1666
|
+
* does not reach, and a schema that is not a record at all. An empty list is the valid answer
|
|
1667
|
+
* for a schema carrying no annotation.
|
|
1668
|
+
*
|
|
1669
|
+
* Total — never throws, and a cyclic or stack-hostile schema is refused rather than followed.
|
|
1670
|
+
*
|
|
1671
|
+
* @param schema - The tool's advertised `inputSchema`
|
|
1672
|
+
* @returns The declared projections, or `undefined` when the definition is invalid
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```ts
|
|
1676
|
+
* buildHeaderParameters({
|
|
1677
|
+
* type: 'object',
|
|
1678
|
+
* properties: { region: { type: 'string', 'x-mcp-header': 'Region' } },
|
|
1679
|
+
* }) // → [{ name: 'Region', path: ['region'], primitive: 'string' }]
|
|
1680
|
+
* ```
|
|
1681
|
+
*/
|
|
1682
|
+
function buildHeaderParameters(schema) {
|
|
1683
|
+
if (!(0, _orkestrel_contract.isRecord)(schema)) return void 0;
|
|
1684
|
+
const found = extractHeaderAnnotations(schema, []);
|
|
1685
|
+
if (found === void 0 || found.length !== countHeaderAnnotations(schema)) return void 0;
|
|
1686
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1687
|
+
for (const parameter of found) {
|
|
1688
|
+
const key = parameter.name.toLowerCase();
|
|
1689
|
+
if (taken.has(key)) return void 0;
|
|
1690
|
+
taken.add(key);
|
|
1691
|
+
}
|
|
1692
|
+
return found;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Renders one projected argument as the text its `Mcp-Param-*` header carries.
|
|
1696
|
+
*
|
|
1697
|
+
* @remarks
|
|
1698
|
+
* The protocol's conversion table, and the ONE place it is stated: a string travels as
|
|
1699
|
+
* itself, an integer in decimal, and a boolean as lowercase `true` or `false`. The value's
|
|
1700
|
+
* runtime shape must match the leaf's declared type, so a schema that declares `integer` and
|
|
1701
|
+
* an argument that supplies a string, a fraction, or a magnitude outside the IEEE 754 safe
|
|
1702
|
+
* range carries NOTHING — a header that cannot round-trip the body value is worse than an
|
|
1703
|
+
* absent one, and the tool's own argument validation owns the disagreement.
|
|
1704
|
+
*
|
|
1705
|
+
* @param value - The argument value read at the parameter's path
|
|
1706
|
+
* @param primitive - The leaf's declared type
|
|
1707
|
+
* @returns The header text, or `undefined` when the value cannot travel as that type
|
|
1708
|
+
*
|
|
1709
|
+
* @example
|
|
1710
|
+
* ```ts
|
|
1711
|
+
* renderHeaderValue(42, 'integer') // '42'
|
|
1712
|
+
* renderHeaderValue(false, 'boolean') // 'false'
|
|
1713
|
+
* ```
|
|
1714
|
+
*/
|
|
1715
|
+
function renderHeaderValue(value, primitive) {
|
|
1716
|
+
if (primitive === "string") return (0, _orkestrel_contract.isString)(value) ? value : void 0;
|
|
1717
|
+
if (primitive === "boolean") return (0, _orkestrel_contract.isBoolean)(value) ? value ? "true" : "false" : void 0;
|
|
1718
|
+
return (0, _orkestrel_contract.isNumber)(value) && Number.isSafeInteger(value) ? String(value) : void 0;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Builds the `Mcp-Param-*` request headers one `tools/call` carries.
|
|
1722
|
+
*
|
|
1723
|
+
* @remarks
|
|
1724
|
+
* The projection SEP-2243 requires of an HTTP client, and the same derivation a server runs
|
|
1725
|
+
* to know what the request should have carried. Each parameter's value is read at its exact
|
|
1726
|
+
* property path in the call's own `arguments`; an absent or `null` value omits its header
|
|
1727
|
+
* entirely, which is the protocol's distinction between "not supplied" and "supplied empty".
|
|
1728
|
+
* The rendered text then travels through {@link encodeSentinel}, so a value carrying
|
|
1729
|
+
* non-ASCII, control, or edge whitespace characters reaches the peer intact.
|
|
1730
|
+
*
|
|
1731
|
+
* @param parameters - The projections the tool's `inputSchema` declares
|
|
1732
|
+
* @param values - The call's `arguments` record
|
|
1733
|
+
* @returns The header field names and values, empty when nothing projects
|
|
1734
|
+
*
|
|
1735
|
+
* @example
|
|
1736
|
+
* ```ts
|
|
1737
|
+
* buildHeaderProjection(
|
|
1738
|
+
* [{ name: 'Region', path: ['region'], primitive: 'string' }],
|
|
1739
|
+
* { region: 'us-west1' },
|
|
1740
|
+
* ) // → { 'Mcp-Param-Region': 'us-west1' }
|
|
1741
|
+
* ```
|
|
1742
|
+
*/
|
|
1743
|
+
function buildHeaderProjection(parameters, values) {
|
|
1744
|
+
const headers = {};
|
|
1745
|
+
for (const parameter of parameters) {
|
|
1746
|
+
let carried = values;
|
|
1747
|
+
for (const key of parameter.path) carried = (0, _orkestrel_contract.isRecord)(carried) ? carried[key] : void 0;
|
|
1748
|
+
if (carried === void 0 || carried === null) continue;
|
|
1749
|
+
const text = renderHeaderValue(carried, parameter.primitive);
|
|
1750
|
+
if (text !== void 0) headers[`${MCP_PARAM_PREFIX}${parameter.name}`] = encodeSentinel(text);
|
|
1751
|
+
}
|
|
1752
|
+
return headers;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Reads one named tool's advertised `inputSchema` out of a `tools/list` answer.
|
|
1756
|
+
*
|
|
1757
|
+
* @remarks
|
|
1758
|
+
* The answer is read as foreign data end to end — a dispatched response, an error envelope,
|
|
1759
|
+
* and a result whose `tools` member is absent or is not an array all read as "no schema"
|
|
1760
|
+
* rather than as a fault. That is what lets the HTTP POST handler ask its own dispatcher
|
|
1761
|
+
* which `Mcp-Param-*` names a `tools/call` may carry without narrowing anything first.
|
|
1762
|
+
*
|
|
1763
|
+
* @param response - The `tools/list` answer, normally a {@link JSONRPCResponse}
|
|
1764
|
+
* @param name - The tool whose schema to read
|
|
1765
|
+
* @returns The advertised `inputSchema`, or `undefined` when the answer carries none
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* ```ts
|
|
1769
|
+
* extractToolSchema(answer, 'search')?.['properties']
|
|
1770
|
+
* ```
|
|
1771
|
+
*/
|
|
1772
|
+
function extractToolSchema(response, name) {
|
|
1773
|
+
const result = (0, _orkestrel_contract.isRecord)(response) ? response["result"] : void 0;
|
|
1774
|
+
const tools = (0, _orkestrel_contract.isRecord)(result) ? result["tools"] : void 0;
|
|
1775
|
+
if (!(0, _orkestrel_contract.isArray)(tools)) return void 0;
|
|
1776
|
+
for (const tool of tools) {
|
|
1777
|
+
if (!(0, _orkestrel_contract.isRecord)(tool) || tool["name"] !== name) continue;
|
|
1778
|
+
const schema = tool["inputSchema"];
|
|
1779
|
+
return (0, _orkestrel_contract.isRecord)(schema) ? schema : void 0;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1358
1783
|
* Reads the request id an inbound `notifications/cancelled` names — the inverse of
|
|
1359
1784
|
* {@link buildCancelledNotification}.
|
|
1360
1785
|
*
|
|
@@ -1670,6 +2095,48 @@ function isStandardBase64(value) {
|
|
|
1670
2095
|
return (0, _orkestrel_contract.isString)(value) && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
1671
2096
|
}
|
|
1672
2097
|
/**
|
|
2098
|
+
* Determines whether a value is one RFC 9110 field token.
|
|
2099
|
+
*
|
|
2100
|
+
* @remarks
|
|
2101
|
+
* A token is one or more `tchar`: the ASCII letters, the digits, and
|
|
2102
|
+
* ``!#$%&'*+-.^_`|~``. That set already excludes the empty string, whitespace, a colon, a
|
|
2103
|
+
* control character, and every non-ASCII code point, so it is the whole constraint an
|
|
2104
|
+
* `x-mcp-header` annotation's value must satisfy — the value is appended verbatim to
|
|
2105
|
+
* {@link MCP_PARAM_PREFIX} and must survive as an HTTP field name.
|
|
2106
|
+
*
|
|
2107
|
+
* @param value - The unknown value to inspect
|
|
2108
|
+
* @returns Whether the value is a non-empty RFC 9110 token
|
|
2109
|
+
*
|
|
2110
|
+
* @example
|
|
2111
|
+
* ```ts
|
|
2112
|
+
* isFieldToken('Region') // true
|
|
2113
|
+
* isFieldToken('My Region') // false
|
|
2114
|
+
* ```
|
|
2115
|
+
*/
|
|
2116
|
+
function isFieldToken(value) {
|
|
2117
|
+
return (0, _orkestrel_contract.isString)(value) && /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value);
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Determines whether a value is a JSON Schema type an `x-mcp-header` annotation may sit on.
|
|
2121
|
+
*
|
|
2122
|
+
* @remarks
|
|
2123
|
+
* `number` is refused deliberately: a JSON number has no interoperable decimal text form, so
|
|
2124
|
+
* a header carrying one could not be compared with the body byte for byte. `integer` renders
|
|
2125
|
+
* exactly, and the server compares it numerically.
|
|
2126
|
+
*
|
|
2127
|
+
* @param value - The unknown value to inspect
|
|
2128
|
+
* @returns Whether the value is one of `'string'`, `'integer'`, or `'boolean'`
|
|
2129
|
+
*
|
|
2130
|
+
* @example
|
|
2131
|
+
* ```ts
|
|
2132
|
+
* isMCPHeaderPrimitive('integer') // true
|
|
2133
|
+
* isMCPHeaderPrimitive('number') // false
|
|
2134
|
+
* ```
|
|
2135
|
+
*/
|
|
2136
|
+
function isMCPHeaderPrimitive(value) {
|
|
2137
|
+
return value === "string" || value === "integer" || value === "boolean";
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
1673
2140
|
* Determines whether a value is one absolute URI under RFC 3986 syntax.
|
|
1674
2141
|
*
|
|
1675
2142
|
* @remarks
|
|
@@ -2793,7 +3260,7 @@ function isMCPElicitRequest(value) {
|
|
|
2793
3260
|
* Determines whether a value is one legal embedded multi-round-trip request.
|
|
2794
3261
|
*
|
|
2795
3262
|
* @param value - The unknown value to inspect
|
|
2796
|
-
* @returns `true` for elicitation,
|
|
3263
|
+
* @returns `true` for an embedded elicitation, sampling, or roots request
|
|
2797
3264
|
*
|
|
2798
3265
|
* @example
|
|
2799
3266
|
* ```ts
|
|
@@ -2814,7 +3281,7 @@ function isMCPInputRequest(value) {
|
|
|
2814
3281
|
}
|
|
2815
3282
|
}
|
|
2816
3283
|
/**
|
|
2817
|
-
* Determines whether a value is a
|
|
3284
|
+
* Determines whether a value is a consumer-keyed map of embedded input requests.
|
|
2818
3285
|
*
|
|
2819
3286
|
* @param value - The unknown value to inspect
|
|
2820
3287
|
* @returns `true` when every own value is a legal {@link MCPInputRequest}
|
|
@@ -2968,6 +3435,187 @@ function isElicitContent(value, schema) {
|
|
|
2968
3435
|
}
|
|
2969
3436
|
}
|
|
2970
3437
|
/**
|
|
3438
|
+
* Determines whether a value is one filesystem root a client exposes.
|
|
3439
|
+
*
|
|
3440
|
+
* @remarks
|
|
3441
|
+
* The dated schema declares `uri` with `format: uri`, so this applies the same RFC 3986
|
|
3442
|
+
* check {@link isAbsoluteURI} gives every other `format: uri` field the package validates,
|
|
3443
|
+
* including a URL-mode elicitation's `url`. Total over hostile input.
|
|
3444
|
+
*
|
|
3445
|
+
* @param value - The unknown value to inspect
|
|
3446
|
+
* @returns `true` when `value` carries an absolute `uri` and an optional string `name`
|
|
3447
|
+
*
|
|
3448
|
+
* @example
|
|
3449
|
+
* ```ts
|
|
3450
|
+
* isMCPRoot({ uri: 'file:///workspace', name: 'workspace' }) // true
|
|
3451
|
+
* isMCPRoot({ uri: 'workspace' }) // false — the schema declares `format: uri`
|
|
3452
|
+
* ```
|
|
3453
|
+
*/
|
|
3454
|
+
function isMCPRoot(value) {
|
|
3455
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
3456
|
+
if (!owned.success) return false;
|
|
3457
|
+
try {
|
|
3458
|
+
const root = owned.value;
|
|
3459
|
+
const name = root["name"];
|
|
3460
|
+
const metadata = root["_meta"];
|
|
3461
|
+
if (!isAbsoluteURI(root["uri"])) return false;
|
|
3462
|
+
if (!(0, _orkestrel_contract.isUndefined)(name) && !(0, _orkestrel_contract.isString)(name)) return false;
|
|
3463
|
+
return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
|
|
3464
|
+
} catch {
|
|
3465
|
+
return false;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Determines whether a value is one client answer to an embedded `roots/list` request.
|
|
3470
|
+
*
|
|
3471
|
+
* @remarks
|
|
3472
|
+
* The dated schema requires the `roots` array, and each root is checked by
|
|
3473
|
+
* {@link isMCPRoot}. Total over hostile input.
|
|
3474
|
+
*
|
|
3475
|
+
* @param value - The unknown value to inspect
|
|
3476
|
+
* @returns `true` when `value` carries an array of valid roots
|
|
3477
|
+
*
|
|
3478
|
+
* @example
|
|
3479
|
+
* ```ts
|
|
3480
|
+
* isMCPRootResult({ roots: [{ uri: 'file:///workspace' }] }) // true
|
|
3481
|
+
* isMCPRootResult({ roots: {} }) // false — the schema requires an array
|
|
3482
|
+
* ```
|
|
3483
|
+
*/
|
|
3484
|
+
function isMCPRootResult(value) {
|
|
3485
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
3486
|
+
if (!owned.success) return false;
|
|
3487
|
+
try {
|
|
3488
|
+
const result = owned.value;
|
|
3489
|
+
const roots = result["roots"];
|
|
3490
|
+
const metadata = result["_meta"];
|
|
3491
|
+
if (!Array.isArray(roots) || !roots.every((root) => isMCPRoot(root))) return false;
|
|
3492
|
+
return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
|
|
3493
|
+
} catch {
|
|
3494
|
+
return false;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Determines whether a value is one block a sampling completion may carry.
|
|
3499
|
+
*
|
|
3500
|
+
* @remarks
|
|
3501
|
+
* The schema's `SamplingMessageContentBlock`: the text, image, and audio blocks
|
|
3502
|
+
* {@link isMCPContent} also admits, plus `tool_use` and `tool_result`. The resource arms of
|
|
3503
|
+
* {@link isMCPContent} are refused, because the schema leaves them out of a sampling
|
|
3504
|
+
* completion. A `tool_result` carries ordinary {@link isMCPContent} blocks and an open
|
|
3505
|
+
* `structuredContent`, which the schema constrains to no shape at all. Total over hostile
|
|
3506
|
+
* input.
|
|
3507
|
+
*
|
|
3508
|
+
* @param value - The unknown value to inspect
|
|
3509
|
+
* @returns `true` when `value` is one legal sampling content block
|
|
3510
|
+
*
|
|
3511
|
+
* @example
|
|
3512
|
+
* ```ts
|
|
3513
|
+
* isMCPSampleContent({ type: 'text', text: 'Paris' }) // true
|
|
3514
|
+
* isMCPSampleContent({ type: 'tool_use', id: 'c1', name: 'lookup', input: {} }) // true
|
|
3515
|
+
* isMCPSampleContent({ type: 'resource_link', name: 'doc', uri: 'file:///doc' }) // false
|
|
3516
|
+
* ```
|
|
3517
|
+
*/
|
|
3518
|
+
function isMCPSampleContent(value) {
|
|
3519
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
3520
|
+
if (!owned.success) return false;
|
|
3521
|
+
try {
|
|
3522
|
+
const block = owned.value;
|
|
3523
|
+
const metadata = block["_meta"];
|
|
3524
|
+
if (!(0, _orkestrel_contract.isUndefined)(metadata) && !isMCPMetaObject(metadata)) return false;
|
|
3525
|
+
if (block["type"] === "tool_use") return (0, _orkestrel_contract.isString)(block["id"]) && (0, _orkestrel_contract.isString)(block["name"]) && (0, _orkestrel_contract.isRecord)(block["input"]);
|
|
3526
|
+
if (block["type"] === "tool_result") {
|
|
3527
|
+
const carried = block["content"];
|
|
3528
|
+
const failed = block["isError"];
|
|
3529
|
+
if (!Array.isArray(carried) || !carried.every((entry) => isMCPContent(entry))) return false;
|
|
3530
|
+
if (!(0, _orkestrel_contract.isUndefined)(failed) && !(0, _orkestrel_contract.isBoolean)(failed)) return false;
|
|
3531
|
+
return (0, _orkestrel_contract.isString)(block["toolUseId"]);
|
|
3532
|
+
}
|
|
3533
|
+
if (!isMCPContent(block)) return false;
|
|
3534
|
+
return block.type === "text" || block.type === "image" || block.type === "audio";
|
|
3535
|
+
} catch {
|
|
3536
|
+
return false;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Determines whether a value is one client answer to an embedded sampling request.
|
|
3541
|
+
*
|
|
3542
|
+
* @remarks
|
|
3543
|
+
* The schema's `CreateMessageResult` types `content` as an `anyOf` over one
|
|
3544
|
+
* {@link isMCPSampleContent} block or an ARRAY of them, so both are admitted here: a
|
|
3545
|
+
* tool-using model answers with `tool_use` and `tool_result` blocks, and a model answering in
|
|
3546
|
+
* several parts answers with the array. `stopReason` stays an open string because the schema
|
|
3547
|
+
* names four values and permits any other a provider reports. Total over hostile input.
|
|
3548
|
+
*
|
|
3549
|
+
* @param value - The unknown value to inspect
|
|
3550
|
+
* @returns `true` when `value` has the sampling-completion shape
|
|
3551
|
+
*
|
|
3552
|
+
* @example
|
|
3553
|
+
* ```ts
|
|
3554
|
+
* isMCPSampleResult({
|
|
3555
|
+
* role: 'assistant',
|
|
3556
|
+
* content: { type: 'text', text: 'Paris' },
|
|
3557
|
+
* model: 'test-model',
|
|
3558
|
+
* }) // true
|
|
3559
|
+
* isMCPSampleResult({
|
|
3560
|
+
* role: 'assistant',
|
|
3561
|
+
* content: [{ type: 'text', text: 'Paris' }],
|
|
3562
|
+
* model: 'test-model',
|
|
3563
|
+
* }) // true
|
|
3564
|
+
* ```
|
|
3565
|
+
*/
|
|
3566
|
+
function isMCPSampleResult(value) {
|
|
3567
|
+
const owned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(value));
|
|
3568
|
+
if (!owned.success) return false;
|
|
3569
|
+
try {
|
|
3570
|
+
const result = owned.value;
|
|
3571
|
+
const role = result["role"];
|
|
3572
|
+
const content = result["content"];
|
|
3573
|
+
const reason = result["stopReason"];
|
|
3574
|
+
const metadata = result["_meta"];
|
|
3575
|
+
if (role !== "user" && role !== "assistant") return false;
|
|
3576
|
+
if (!(0, _orkestrel_contract.isString)(result["model"])) return false;
|
|
3577
|
+
if (!(Array.isArray(content) ? content : [content]).every((block) => isMCPSampleContent(block))) return false;
|
|
3578
|
+
if (!(0, _orkestrel_contract.isUndefined)(reason) && !(0, _orkestrel_contract.isString)(reason)) return false;
|
|
3579
|
+
return (0, _orkestrel_contract.isUndefined)(metadata) || isMCPMetaObject(metadata);
|
|
3580
|
+
} catch {
|
|
3581
|
+
return false;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Determines whether a response answers the exact embedded request that was issued.
|
|
3586
|
+
*
|
|
3587
|
+
* @remarks
|
|
3588
|
+
* A response carries no `method` of its own, so the ISSUED request selects which arm applies
|
|
3589
|
+
* — the same way {@link isElicitContent} takes the issued schema rather than trusting the
|
|
3590
|
+
* content to describe itself. A form elicitation is checked twice: once for the response
|
|
3591
|
+
* shape and once, on `accept`, for the content against the schema that round issued. A
|
|
3592
|
+
* URL-mode elicitation issues no schema, so only the shape is checked. A request this
|
|
3593
|
+
* package cannot recognize admits NOTHING, because an unrecognized question has no correct
|
|
3594
|
+
* answer. Total over hostile responses and hostile requests alike.
|
|
3595
|
+
*
|
|
3596
|
+
* @param value - The client's answer to check
|
|
3597
|
+
* @param request - The exact {@link MCPInputRequest} that was issued under the same key
|
|
3598
|
+
* @returns `true` when the answer is legal for that request
|
|
3599
|
+
*
|
|
3600
|
+
* @example
|
|
3601
|
+
* ```ts
|
|
3602
|
+
* isMCPInputResponse({ roots: [] }, { method: 'roots/list' }) // true
|
|
3603
|
+
* isMCPInputResponse({ roots: [] }, { method: 'sampling/createMessage', params: {} }) // false
|
|
3604
|
+
* ```
|
|
3605
|
+
*/
|
|
3606
|
+
function isMCPInputResponse(value, request) {
|
|
3607
|
+
if (!isMCPInputRequest(request)) return false;
|
|
3608
|
+
try {
|
|
3609
|
+
if (request.method === "roots/list") return isMCPRootResult(value);
|
|
3610
|
+
if (request.method === "sampling/createMessage") return isMCPSampleResult(value);
|
|
3611
|
+
if (!isMCPElicitResult(value)) return false;
|
|
3612
|
+
if (value.action !== "accept" || !isMCPElicitForm(request.params)) return true;
|
|
3613
|
+
return isElicitContent(value.content ?? {}, request.params.requestedSchema);
|
|
3614
|
+
} catch {
|
|
3615
|
+
return false;
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
2971
3619
|
* Determines whether a value is an MCP input-required result.
|
|
2972
3620
|
*
|
|
2973
3621
|
* @remarks
|
|
@@ -4293,13 +4941,7 @@ var MCPServer = class {
|
|
|
4293
4941
|
if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
|
|
4294
4942
|
if (isMCPInputResult(captured[0])) {
|
|
4295
4943
|
const input = captured[0];
|
|
4296
|
-
return
|
|
4297
|
-
...input,
|
|
4298
|
-
_meta: {
|
|
4299
|
-
...input["_meta"] ?? {},
|
|
4300
|
-
[MCP_META_SERVER]: this.#options.identity
|
|
4301
|
-
}
|
|
4302
|
-
});
|
|
4944
|
+
return this.#forward(input, request);
|
|
4303
4945
|
}
|
|
4304
4946
|
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");
|
|
4305
4947
|
const result = buildModernResult({ contents: captured[0] }, this.#options.identity, this.#options.cache?.ttl ?? 6e4, this.#options.cache?.scope);
|
|
@@ -4361,13 +5003,7 @@ var MCPServer = class {
|
|
|
4361
5003
|
if (captured === void 0) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
|
|
4362
5004
|
if (isMCPInputResult(captured[0])) {
|
|
4363
5005
|
const input = captured[0];
|
|
4364
|
-
return
|
|
4365
|
-
...input,
|
|
4366
|
-
_meta: {
|
|
4367
|
-
...input["_meta"] ?? {},
|
|
4368
|
-
[MCP_META_SERVER]: this.#options.identity
|
|
4369
|
-
}
|
|
4370
|
-
});
|
|
5006
|
+
return this.#forward(input, request);
|
|
4371
5007
|
}
|
|
4372
5008
|
if (!isMCPPromptGetResult(captured[0])) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server prompt manager returned an invalid or oversized result");
|
|
4373
5009
|
const result = captured[0];
|
|
@@ -4521,21 +5157,46 @@ var MCPServer = class {
|
|
|
4521
5157
|
});
|
|
4522
5158
|
if (digest === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: tool arguments are too large or unsafe");
|
|
4523
5159
|
if (params?.["requestState"] !== void 0 || params?.["inputResponses"] !== void 0) return this.#retry(request, name, digest, args, options);
|
|
4524
|
-
const
|
|
5160
|
+
const selected = await configured.selector({
|
|
4525
5161
|
request,
|
|
4526
5162
|
name,
|
|
4527
5163
|
arguments: args
|
|
4528
5164
|
}, options);
|
|
4529
|
-
if (
|
|
4530
|
-
const
|
|
5165
|
+
if (selected === void 0) return void 0;
|
|
5166
|
+
const round = this.#round(selected);
|
|
4531
5167
|
const context = parseRequestContext(request, {
|
|
4532
5168
|
bytes: this.#limits.message,
|
|
4533
5169
|
depth: this.#limits.depth
|
|
4534
5170
|
});
|
|
4535
|
-
if (
|
|
4536
|
-
|
|
5171
|
+
if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
|
|
5172
|
+
const refusal = this.#gate(round, context, id);
|
|
5173
|
+
if (refusal !== void 0) return refusal;
|
|
4537
5174
|
const principal = await configured.principal(request, options);
|
|
4538
|
-
return this.#required(request, name, digest,
|
|
5175
|
+
return this.#required(request, name, digest, round, principal, id, void 0);
|
|
5176
|
+
}
|
|
5177
|
+
#gate(round, context, id) {
|
|
5178
|
+
if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
|
|
5179
|
+
const missing = computeMissingCapabilities(round.requests, context.capabilities);
|
|
5180
|
+
if (missing === void 0) return void 0;
|
|
5181
|
+
return buildJSONRPCError(id, MCP_MISSING_CAPABILITY, "Server requires a client capability this request did not declare", { requiredCapabilities: missing });
|
|
5182
|
+
}
|
|
5183
|
+
#forward(input, request) {
|
|
5184
|
+
const requests = input.inputRequests;
|
|
5185
|
+
if (requests !== void 0) {
|
|
5186
|
+
const context = parseRequestContext(request, {
|
|
5187
|
+
bytes: this.#limits.message,
|
|
5188
|
+
depth: this.#limits.depth
|
|
5189
|
+
});
|
|
5190
|
+
const refusal = this.#gate({ requests }, context, request.id);
|
|
5191
|
+
if (refusal !== void 0) return refusal;
|
|
5192
|
+
}
|
|
5193
|
+
return buildJSONRPCResult(request.id, {
|
|
5194
|
+
...input,
|
|
5195
|
+
_meta: {
|
|
5196
|
+
...input["_meta"] ?? {},
|
|
5197
|
+
[MCP_META_SERVER]: this.#options.identity
|
|
5198
|
+
}
|
|
5199
|
+
});
|
|
4539
5200
|
}
|
|
4540
5201
|
async #retry(request, name, digest, args, options) {
|
|
4541
5202
|
const configured = this.#options.input;
|
|
@@ -4548,55 +5209,66 @@ var MCPServer = class {
|
|
|
4548
5209
|
bytes: this.#limits.message,
|
|
4549
5210
|
depth: this.#limits.depth
|
|
4550
5211
|
});
|
|
4551
|
-
if (context === void 0
|
|
5212
|
+
if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
|
|
4552
5213
|
const verified = await configured.continuation.open(requestState);
|
|
4553
5214
|
if (verified === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be recovered");
|
|
4554
5215
|
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);
|
|
4555
5216
|
const state = parseMCPInputState(verified);
|
|
4556
5217
|
if (state === void 0) return this.#contain(/* @__PURE__ */ new Error("Continuation port opened a malformed protected payload"), id);
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
if (
|
|
5218
|
+
if (state.expiry <= Date.now() || state.id === id || state.version !== context.version || state.method !== request.method || state.name !== name || state.digest !== digest) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
5219
|
+
const responses = this.#answers(state.requests, inputResponses);
|
|
5220
|
+
if (responses === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: an input response is missing or malformed");
|
|
4560
5221
|
const principal = await configured.principal(request, options);
|
|
4561
5222
|
if (!(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || state.principal !== principal) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
4562
|
-
const
|
|
5223
|
+
const selected = await configured.selector({
|
|
4563
5224
|
request,
|
|
4564
5225
|
name,
|
|
4565
5226
|
arguments: args,
|
|
4566
|
-
|
|
5227
|
+
responses,
|
|
4567
5228
|
...state.state !== void 0 ? { state: state.state } : {}
|
|
4568
5229
|
}, options);
|
|
4569
5230
|
if (state.expiry <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
4570
|
-
if (
|
|
4571
|
-
const
|
|
4572
|
-
if (
|
|
4573
|
-
|
|
5231
|
+
if (selected === void 0) return void 0;
|
|
5232
|
+
const round = this.#round(selected);
|
|
5233
|
+
if (round === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
|
|
5234
|
+
const refusal = this.#gate(round, context, id);
|
|
5235
|
+
if (refusal !== void 0) return refusal;
|
|
5236
|
+
return this.#required(request, name, digest, round, principal, state.id, state.expiry);
|
|
5237
|
+
}
|
|
5238
|
+
#answers(requests, responses) {
|
|
5239
|
+
const answered = {};
|
|
5240
|
+
for (const [key, issued] of Object.entries(requests)) {
|
|
5241
|
+
const response = responses[key];
|
|
5242
|
+
if (!Object.hasOwn(responses, key) || !isMCPInputResponse(response, issued)) return void 0;
|
|
5243
|
+
answered[key] = response;
|
|
5244
|
+
}
|
|
5245
|
+
return Object.freeze(answered);
|
|
4574
5246
|
}
|
|
4575
|
-
#
|
|
4576
|
-
const owned = snapshotJSON(
|
|
5247
|
+
#round(round) {
|
|
5248
|
+
const owned = snapshotJSON(round, {
|
|
4577
5249
|
bytes: this.#limits.content,
|
|
4578
5250
|
keys: this.#limits.keys,
|
|
4579
5251
|
depth: this.#limits.depth
|
|
4580
5252
|
});
|
|
4581
5253
|
if (owned === void 0 || !(0, _orkestrel_contract.isRecord)(owned[0])) return void 0;
|
|
4582
|
-
const
|
|
5254
|
+
const requests = owned[0]["requests"];
|
|
4583
5255
|
const state = owned[0]["state"];
|
|
4584
|
-
if (!
|
|
4585
|
-
|
|
4586
|
-
|
|
5256
|
+
if (!isMCPInputRequestMap(requests) || Object.keys(requests).length === 0) return void 0;
|
|
5257
|
+
if (!(0, _orkestrel_contract.isUndefined)(state) && !(0, _orkestrel_contract.isJSONValue)(state)) return void 0;
|
|
5258
|
+
return (0, _orkestrel_contract.isUndefined)(state) ? { requests } : {
|
|
5259
|
+
requests,
|
|
4587
5260
|
state
|
|
4588
5261
|
};
|
|
4589
5262
|
}
|
|
4590
|
-
async #required(request, name, digest,
|
|
5263
|
+
async #required(request, name, digest, round, principal, origin, previous) {
|
|
4591
5264
|
const id = request.id;
|
|
4592
5265
|
const configured = this.#options.input;
|
|
4593
5266
|
const context = parseRequestContext(request, {
|
|
4594
5267
|
bytes: this.#limits.message,
|
|
4595
5268
|
depth: this.#limits.depth
|
|
4596
5269
|
});
|
|
4597
|
-
if (configured === void 0 || context === void 0 || !(0, _orkestrel_contract.isString)(principal) || principal.length === 0 || !Number.isFinite(configured.ttl) || configured.ttl <= 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params:
|
|
5270
|
+
if (configured === void 0 || context === void 0 || !(0, _orkestrel_contract.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");
|
|
4598
5271
|
if (previous !== void 0 && previous <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
|
|
4599
|
-
const key = crypto.randomUUID();
|
|
4600
5272
|
const expiry = Date.now() + configured.ttl;
|
|
4601
5273
|
const protectedState = {
|
|
4602
5274
|
principal,
|
|
@@ -4604,11 +5276,10 @@ var MCPServer = class {
|
|
|
4604
5276
|
id: origin,
|
|
4605
5277
|
version: context.version,
|
|
4606
5278
|
method: request.method,
|
|
4607
|
-
|
|
5279
|
+
requests: round.requests,
|
|
4608
5280
|
name,
|
|
4609
5281
|
digest,
|
|
4610
|
-
|
|
4611
|
-
...form.state !== void 0 ? { state: form.state } : {}
|
|
5282
|
+
...round.state !== void 0 ? { state: round.state } : {}
|
|
4612
5283
|
};
|
|
4613
5284
|
if (!isBoundedJSON(protectedState, {
|
|
4614
5285
|
bytes: this.#limits.state,
|
|
@@ -4624,13 +5295,7 @@ var MCPServer = class {
|
|
|
4624
5295
|
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");
|
|
4625
5296
|
return buildJSONRPCResult(id, {
|
|
4626
5297
|
resultType: "input_required",
|
|
4627
|
-
inputRequests:
|
|
4628
|
-
method: "elicitation/create",
|
|
4629
|
-
params: {
|
|
4630
|
-
...form.request,
|
|
4631
|
-
mode: "form"
|
|
4632
|
-
}
|
|
4633
|
-
} },
|
|
5298
|
+
inputRequests: round.requests,
|
|
4634
5299
|
requestState,
|
|
4635
5300
|
_meta: { [MCP_META_SERVER]: this.#options.identity }
|
|
4636
5301
|
});
|
|
@@ -5045,7 +5710,7 @@ var MCPClient = class {
|
|
|
5045
5710
|
name,
|
|
5046
5711
|
arguments: args,
|
|
5047
5712
|
...input === void 0 ? {} : {
|
|
5048
|
-
requestState: input.state,
|
|
5713
|
+
...input.state === void 0 ? {} : { requestState: input.state },
|
|
5049
5714
|
inputResponses: input.responses
|
|
5050
5715
|
}
|
|
5051
5716
|
}, this.#timeout, void 0, options));
|
|
@@ -5595,7 +6260,9 @@ exports.MCPTextStreamController = MCPTextStreamController;
|
|
|
5595
6260
|
exports.MCP_EXTENSION_TASKS = MCP_EXTENSION_TASKS;
|
|
5596
6261
|
exports.MCP_FALLBACK_VERSION = MCP_FALLBACK_VERSION;
|
|
5597
6262
|
exports.MCP_HANDSHAKE_VERSION = MCP_HANDSHAKE_VERSION;
|
|
6263
|
+
exports.MCP_HEADER_ANNOTATION = MCP_HEADER_ANNOTATION;
|
|
5598
6264
|
exports.MCP_HEADER_MISMATCH = MCP_HEADER_MISMATCH;
|
|
6265
|
+
exports.MCP_LOOKUP_PAGES = MCP_LOOKUP_PAGES;
|
|
5599
6266
|
exports.MCP_META_CAPABILITIES = MCP_META_CAPABILITIES;
|
|
5600
6267
|
exports.MCP_META_CLIENT = MCP_META_CLIENT;
|
|
5601
6268
|
exports.MCP_META_SERVER = MCP_META_SERVER;
|
|
@@ -5603,6 +6270,9 @@ exports.MCP_META_SUBSCRIPTION = MCP_META_SUBSCRIPTION;
|
|
|
5603
6270
|
exports.MCP_META_VERSION = MCP_META_VERSION;
|
|
5604
6271
|
exports.MCP_MISSING_CAPABILITY = MCP_MISSING_CAPABILITY;
|
|
5605
6272
|
exports.MCP_MODERN_VERSION = MCP_MODERN_VERSION;
|
|
6273
|
+
exports.MCP_PARAM_PREFIX = MCP_PARAM_PREFIX;
|
|
6274
|
+
exports.MCP_SENTINEL_PREFIX = MCP_SENTINEL_PREFIX;
|
|
6275
|
+
exports.MCP_SENTINEL_SUFFIX = MCP_SENTINEL_SUFFIX;
|
|
5606
6276
|
exports.MCP_UNSUPPORTED_VERSION = MCP_UNSUPPORTED_VERSION;
|
|
5607
6277
|
exports.SUPPORTED_LEGACY_PROTOCOL_VERSIONS = SUPPORTED_LEGACY_PROTOCOL_VERSIONS;
|
|
5608
6278
|
exports.SUPPORTED_MCP_VERSIONS = SUPPORTED_MCP_VERSIONS;
|
|
@@ -5612,6 +6282,8 @@ exports.bindServer = bindServer;
|
|
|
5612
6282
|
exports.buildCallOutcome = buildCallOutcome;
|
|
5613
6283
|
exports.buildCancelledNotification = buildCancelledNotification;
|
|
5614
6284
|
exports.buildDiscoverResult = buildDiscoverResult;
|
|
6285
|
+
exports.buildHeaderParameters = buildHeaderParameters;
|
|
6286
|
+
exports.buildHeaderProjection = buildHeaderProjection;
|
|
5615
6287
|
exports.buildInitializeResult = buildInitializeResult;
|
|
5616
6288
|
exports.buildJSONRPCError = buildJSONRPCError;
|
|
5617
6289
|
exports.buildJSONRPCResult = buildJSONRPCResult;
|
|
@@ -5623,14 +6295,20 @@ exports.buildSubscriptionFilter = buildSubscriptionFilter;
|
|
|
5623
6295
|
exports.buildSubscriptionResult = buildSubscriptionResult;
|
|
5624
6296
|
exports.buildToolCall = buildToolCall;
|
|
5625
6297
|
exports.buildToolDescriptors = buildToolDescriptors;
|
|
6298
|
+
exports.computeMissingCapabilities = computeMissingCapabilities;
|
|
6299
|
+
exports.countHeaderAnnotations = countHeaderAnnotations;
|
|
5626
6300
|
exports.createDuplexClientTransport = createDuplexClientTransport;
|
|
5627
6301
|
exports.createMCPClient = createMCPClient;
|
|
5628
6302
|
exports.createMCPLegacy = createMCPLegacy;
|
|
5629
6303
|
exports.createMCPLegacyClientTransport = createMCPLegacyClientTransport;
|
|
5630
6304
|
exports.createMCPServer = createMCPServer;
|
|
5631
6305
|
exports.decodeBoundedMessage = decodeBoundedMessage;
|
|
6306
|
+
exports.decodeSentinel = decodeSentinel;
|
|
5632
6307
|
exports.digestJSON = digestJSON;
|
|
6308
|
+
exports.encodeSentinel = encodeSentinel;
|
|
5633
6309
|
exports.extractContentText = extractContentText;
|
|
6310
|
+
exports.extractHeaderAnnotations = extractHeaderAnnotations;
|
|
6311
|
+
exports.extractToolSchema = extractToolSchema;
|
|
5634
6312
|
exports.inferEra = inferEra;
|
|
5635
6313
|
exports.inferRequestVersion = inferRequestVersion;
|
|
5636
6314
|
exports.inferVersion = inferVersion;
|
|
@@ -5638,6 +6316,7 @@ exports.isAbsoluteURI = isAbsoluteURI;
|
|
|
5638
6316
|
exports.isBoundedJSON = isBoundedJSON;
|
|
5639
6317
|
exports.isBoundedString = isBoundedString;
|
|
5640
6318
|
exports.isElicitContent = isElicitContent;
|
|
6319
|
+
exports.isFieldToken = isFieldToken;
|
|
5641
6320
|
exports.isFormElicitationSupported = isFormElicitationSupported;
|
|
5642
6321
|
exports.isInitializeRequest = isInitializeRequest;
|
|
5643
6322
|
exports.isJSONObject = isJSONObject;
|
|
@@ -5666,10 +6345,12 @@ exports.isMCPElicitResult = isMCPElicitResult;
|
|
|
5666
6345
|
exports.isMCPElicitSchema = isMCPElicitSchema;
|
|
5667
6346
|
exports.isMCPElicitURL = isMCPElicitURL;
|
|
5668
6347
|
exports.isMCPError = isMCPError;
|
|
6348
|
+
exports.isMCPHeaderPrimitive = isMCPHeaderPrimitive;
|
|
5669
6349
|
exports.isMCPIcon = isMCPIcon;
|
|
5670
6350
|
exports.isMCPIdentity = isMCPIdentity;
|
|
5671
6351
|
exports.isMCPInputRequest = isMCPInputRequest;
|
|
5672
6352
|
exports.isMCPInputRequestMap = isMCPInputRequestMap;
|
|
6353
|
+
exports.isMCPInputResponse = isMCPInputResponse;
|
|
5673
6354
|
exports.isMCPInputResult = isMCPInputResult;
|
|
5674
6355
|
exports.isMCPLegacyResult = isMCPLegacyResult;
|
|
5675
6356
|
exports.isMCPLegacyVersion = isMCPLegacyVersion;
|
|
@@ -5692,6 +6373,10 @@ exports.isMCPResourceTemplate = isMCPResourceTemplate;
|
|
|
5692
6373
|
exports.isMCPResourceTemplatePage = isMCPResourceTemplatePage;
|
|
5693
6374
|
exports.isMCPResult = isMCPResult;
|
|
5694
6375
|
exports.isMCPResultMetaObject = isMCPResultMetaObject;
|
|
6376
|
+
exports.isMCPRoot = isMCPRoot;
|
|
6377
|
+
exports.isMCPRootResult = isMCPRootResult;
|
|
6378
|
+
exports.isMCPSampleContent = isMCPSampleContent;
|
|
6379
|
+
exports.isMCPSampleResult = isMCPSampleResult;
|
|
5695
6380
|
exports.isMCPServerCapabilities = isMCPServerCapabilities;
|
|
5696
6381
|
exports.isMCPStringArguments = isMCPStringArguments;
|
|
5697
6382
|
exports.isMCPSubscriptionFilter = isMCPSubscriptionFilter;
|
|
@@ -5718,6 +6403,7 @@ exports.parseJSONRPCMessage = parseJSONRPCMessage;
|
|
|
5718
6403
|
exports.parseMCPInputState = parseMCPInputState;
|
|
5719
6404
|
exports.parseRequestContext = parseRequestContext;
|
|
5720
6405
|
exports.readCancelledId = readCancelledId;
|
|
6406
|
+
exports.renderHeaderValue = renderHeaderValue;
|
|
5721
6407
|
exports.sendStream = sendStream;
|
|
5722
6408
|
exports.serializeJSON = serializeJSON;
|
|
5723
6409
|
exports.snapshotJSON = snapshotJSON;
|