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