@modelcontextprotocol/inspector 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/clients/cli/build/index.js +2336 -242
- package/clients/tui/build/index.js +3388 -971
- package/clients/web/build/index.js +725 -73
- package/clients/web/dist/assets/index-DZkZ6KYt.js +1598 -0
- package/clients/web/dist/assets/{prism-light-_bvDBTQE.js → prism-light-CoQfbOXL.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/clients/web/static/sandbox_proxy.html +30 -4
- package/package.json +15 -10
- package/clients/web/dist/assets/index-ZYqA4f7P.js +0 -1486
|
@@ -33,6 +33,7 @@ var INACTIVE_SUBSCRIPTION_STREAM_STATE = {
|
|
|
33
33
|
honoredUris: []
|
|
34
34
|
};
|
|
35
35
|
var DEFAULT_TASK_TTL_MS = 6e4;
|
|
36
|
+
var DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
|
|
36
37
|
var DEFAULT_MAX_FETCH_REQUESTS = 1e3;
|
|
37
38
|
var DEFAULT_MODERN_LOG_LEVEL = "debug";
|
|
38
39
|
function resolveModernLogLevel(settings) {
|
|
@@ -390,6 +391,656 @@ async function deleteStoreFile(filePath) {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
394
|
+
// ../../core/mcp/skillsSchemas.ts
|
|
395
|
+
import { z } from "zod/v4";
|
|
396
|
+
import { ResourceSchema } from "@modelcontextprotocol/core";
|
|
397
|
+
var SKILLS_EXTENSION_KEY = "io.modelcontextprotocol/skills";
|
|
398
|
+
var SKILLS_LIST_METHOD = "skills/list";
|
|
399
|
+
var SKILLS_GET_METHOD = "skills/get";
|
|
400
|
+
var RESOURCES_DIRECTORY_READ_METHOD = "resources/directory/read";
|
|
401
|
+
var DYNAMIC_RESOURCES = "dynamic";
|
|
402
|
+
var SkillFrontmatterSchema = z.looseObject({
|
|
403
|
+
name: z.string().optional(),
|
|
404
|
+
description: z.string().optional()
|
|
405
|
+
});
|
|
406
|
+
var SkillResourceSchema = z.looseObject({
|
|
407
|
+
uri: z.string(),
|
|
408
|
+
digest: z.string().optional(),
|
|
409
|
+
size: z.number().optional()
|
|
410
|
+
});
|
|
411
|
+
var SkillEntrySchema = z.looseObject({
|
|
412
|
+
uri: z.string(),
|
|
413
|
+
frontmatter: SkillFrontmatterSchema,
|
|
414
|
+
resources: z.union([
|
|
415
|
+
z.literal(DYNAMIC_RESOURCES),
|
|
416
|
+
z.array(SkillResourceSchema)
|
|
417
|
+
])
|
|
418
|
+
});
|
|
419
|
+
var ListSkillsResultSchema = z.looseObject({
|
|
420
|
+
skills: z.array(SkillEntrySchema),
|
|
421
|
+
nextCursor: z.string().optional()
|
|
422
|
+
});
|
|
423
|
+
var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
|
|
424
|
+
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
425
|
+
// envelope violation, and accepting it here would hide exactly the kind of
|
|
426
|
+
// defect this schema exists to surface.
|
|
427
|
+
ttlMs: z.int().min(0),
|
|
428
|
+
cacheScope: z.enum(["public", "private"])
|
|
429
|
+
});
|
|
430
|
+
var GetSkillEnvelopeSchema = z.looseObject({
|
|
431
|
+
skill: SkillEntrySchema
|
|
432
|
+
});
|
|
433
|
+
var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
|
|
434
|
+
(result) => result.skill
|
|
435
|
+
);
|
|
436
|
+
var DirectoryChildSchema = ResourceSchema;
|
|
437
|
+
var DirectoryReadResultSchema = z.looseObject({
|
|
438
|
+
resources: z.array(DirectoryChildSchema),
|
|
439
|
+
nextCursor: z.string().optional()
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ../../core/mcp/sha256.ts
|
|
443
|
+
var K = new Uint32Array([
|
|
444
|
+
1116352408,
|
|
445
|
+
1899447441,
|
|
446
|
+
3049323471,
|
|
447
|
+
3921009573,
|
|
448
|
+
961987163,
|
|
449
|
+
1508970993,
|
|
450
|
+
2453635748,
|
|
451
|
+
2870763221,
|
|
452
|
+
3624381080,
|
|
453
|
+
310598401,
|
|
454
|
+
607225278,
|
|
455
|
+
1426881987,
|
|
456
|
+
1925078388,
|
|
457
|
+
2162078206,
|
|
458
|
+
2614888103,
|
|
459
|
+
3248222580,
|
|
460
|
+
3835390401,
|
|
461
|
+
4022224774,
|
|
462
|
+
264347078,
|
|
463
|
+
604807628,
|
|
464
|
+
770255983,
|
|
465
|
+
1249150122,
|
|
466
|
+
1555081692,
|
|
467
|
+
1996064986,
|
|
468
|
+
2554220882,
|
|
469
|
+
2821834349,
|
|
470
|
+
2952996808,
|
|
471
|
+
3210313671,
|
|
472
|
+
3336571891,
|
|
473
|
+
3584528711,
|
|
474
|
+
113926993,
|
|
475
|
+
338241895,
|
|
476
|
+
666307205,
|
|
477
|
+
773529912,
|
|
478
|
+
1294757372,
|
|
479
|
+
1396182291,
|
|
480
|
+
1695183700,
|
|
481
|
+
1986661051,
|
|
482
|
+
2177026350,
|
|
483
|
+
2456956037,
|
|
484
|
+
2730485921,
|
|
485
|
+
2820302411,
|
|
486
|
+
3259730800,
|
|
487
|
+
3345764771,
|
|
488
|
+
3516065817,
|
|
489
|
+
3600352804,
|
|
490
|
+
4094571909,
|
|
491
|
+
275423344,
|
|
492
|
+
430227734,
|
|
493
|
+
506948616,
|
|
494
|
+
659060556,
|
|
495
|
+
883997877,
|
|
496
|
+
958139571,
|
|
497
|
+
1322822218,
|
|
498
|
+
1537002063,
|
|
499
|
+
1747873779,
|
|
500
|
+
1955562222,
|
|
501
|
+
2024104815,
|
|
502
|
+
2227730452,
|
|
503
|
+
2361852424,
|
|
504
|
+
2428436474,
|
|
505
|
+
2756734187,
|
|
506
|
+
3204031479,
|
|
507
|
+
3329325298
|
|
508
|
+
]);
|
|
509
|
+
var H0 = new Uint32Array([
|
|
510
|
+
1779033703,
|
|
511
|
+
3144134277,
|
|
512
|
+
1013904242,
|
|
513
|
+
2773480762,
|
|
514
|
+
1359893119,
|
|
515
|
+
2600822924,
|
|
516
|
+
528734635,
|
|
517
|
+
1541459225
|
|
518
|
+
]);
|
|
519
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
520
|
+
function sha256Bytes(bytes) {
|
|
521
|
+
const message = new Uint8Array(bytes);
|
|
522
|
+
const bitLength = message.length * 8;
|
|
523
|
+
const withLength = message.length + 9;
|
|
524
|
+
const padded = new Uint8Array(Math.ceil(withLength / 64) * 64);
|
|
525
|
+
padded.set(message);
|
|
526
|
+
padded[message.length] = 128;
|
|
527
|
+
const view = new DataView(padded.buffer);
|
|
528
|
+
view.setUint32(padded.length - 8, Math.floor(bitLength / 4294967296));
|
|
529
|
+
view.setUint32(padded.length - 4, bitLength >>> 0);
|
|
530
|
+
const h = new Uint32Array(H0);
|
|
531
|
+
const w = new Uint32Array(64);
|
|
532
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
533
|
+
for (let i = 0; i < 16; i += 1) w[i] = view.getUint32(offset + i * 4);
|
|
534
|
+
for (let i = 16; i < 64; i += 1) {
|
|
535
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
536
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
537
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
538
|
+
}
|
|
539
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
540
|
+
for (let i = 0; i < 64; i += 1) {
|
|
541
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
542
|
+
const ch = e & f ^ ~e & g;
|
|
543
|
+
const temp1 = hh + S1 + ch + K[i] + w[i] >>> 0;
|
|
544
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
545
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
546
|
+
const temp2 = S0 + maj >>> 0;
|
|
547
|
+
hh = g;
|
|
548
|
+
g = f;
|
|
549
|
+
f = e;
|
|
550
|
+
e = d + temp1 >>> 0;
|
|
551
|
+
d = c;
|
|
552
|
+
c = b;
|
|
553
|
+
b = a;
|
|
554
|
+
a = temp1 + temp2 >>> 0;
|
|
555
|
+
}
|
|
556
|
+
h[0] = h[0] + a >>> 0;
|
|
557
|
+
h[1] = h[1] + b >>> 0;
|
|
558
|
+
h[2] = h[2] + c >>> 0;
|
|
559
|
+
h[3] = h[3] + d >>> 0;
|
|
560
|
+
h[4] = h[4] + e >>> 0;
|
|
561
|
+
h[5] = h[5] + f >>> 0;
|
|
562
|
+
h[6] = h[6] + g >>> 0;
|
|
563
|
+
h[7] = h[7] + hh >>> 0;
|
|
564
|
+
}
|
|
565
|
+
const digest = new Uint8Array(32);
|
|
566
|
+
const out = new DataView(digest.buffer);
|
|
567
|
+
for (let i = 0; i < 8; i += 1) out.setUint32(i * 4, h[i]);
|
|
568
|
+
return digest;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ../../core/mcp/skillFile.ts
|
|
572
|
+
import { parse as parseYaml } from "yaml";
|
|
573
|
+
function splitSkillFile(text) {
|
|
574
|
+
if (!/^---[ \t]*\r?\n/.test(text)) return { body: text };
|
|
575
|
+
const rest = text.slice(text.indexOf("\n") + 1);
|
|
576
|
+
const close = rest.search(/^---[ \t]*\r?$/m);
|
|
577
|
+
if (close === -1) return { body: text };
|
|
578
|
+
const frontmatter = rest.slice(0, close).replace(/\r?\n$/, "");
|
|
579
|
+
const after = rest.slice(close);
|
|
580
|
+
const newline = after.indexOf("\n");
|
|
581
|
+
if (newline === -1) return { frontmatter, body: "" };
|
|
582
|
+
return { frontmatter, body: after.slice(newline + 1).replace(/^\r?\n/, "") };
|
|
583
|
+
}
|
|
584
|
+
function hasContent(yamlText) {
|
|
585
|
+
return yamlText.split("\n").some((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
|
|
586
|
+
}
|
|
587
|
+
var MAX_FRONTMATTER_DEPTH = 64;
|
|
588
|
+
function jsonGraphError(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
589
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
590
|
+
if (depth > MAX_FRONTMATTER_DEPTH) {
|
|
591
|
+
return `Frontmatter nests deeper than ${MAX_FRONTMATTER_DEPTH} levels, which cannot be compared field by field.`;
|
|
592
|
+
}
|
|
593
|
+
const node = value;
|
|
594
|
+
if (seen.has(node)) {
|
|
595
|
+
return "Frontmatter contains a cyclic YAML alias, which has no JSON equivalent and cannot be compared against the listing.";
|
|
596
|
+
}
|
|
597
|
+
seen.add(node);
|
|
598
|
+
const members = Array.isArray(node) ? node : Object.values(node);
|
|
599
|
+
for (const member of members) {
|
|
600
|
+
const error = jsonGraphError(member, seen, depth + 1);
|
|
601
|
+
if (error) return error;
|
|
602
|
+
}
|
|
603
|
+
seen.delete(node);
|
|
604
|
+
return void 0;
|
|
605
|
+
}
|
|
606
|
+
function parseSkillFrontmatter(yamlText) {
|
|
607
|
+
let parsed;
|
|
608
|
+
try {
|
|
609
|
+
parsed = parseYaml(yamlText);
|
|
610
|
+
} catch (err) {
|
|
611
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
612
|
+
}
|
|
613
|
+
if (parsed === null || parsed === void 0) {
|
|
614
|
+
return hasContent(yamlText) ? { error: "Frontmatter is not a YAML mapping of fields." } : { fields: {} };
|
|
615
|
+
}
|
|
616
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
617
|
+
return {
|
|
618
|
+
error: "Frontmatter is not a YAML mapping of fields."
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const graphError = jsonGraphError(parsed, /* @__PURE__ */ new Set(), 0);
|
|
622
|
+
if (graphError) return { error: graphError };
|
|
623
|
+
return { fields: parsed };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ../../core/mcp/skills.ts
|
|
627
|
+
var SKILL_MAX_RESOURCE_ENTRIES = 512;
|
|
628
|
+
var SKILL_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
629
|
+
var SKILL_MAX_CATALOG_SKILLS = 256;
|
|
630
|
+
var SKILL_MAX_CATALOG_BYTES = 64 * 1024 * 1024;
|
|
631
|
+
function isSkillCatalogLimit(value) {
|
|
632
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
633
|
+
}
|
|
634
|
+
function resolveSkillCatalogBudget(settings) {
|
|
635
|
+
return {
|
|
636
|
+
maxSkills: isSkillCatalogLimit(settings?.skillCatalogMaxSkills) ? settings.skillCatalogMaxSkills : SKILL_MAX_CATALOG_SKILLS,
|
|
637
|
+
maxBytes: isSkillCatalogLimit(settings?.skillCatalogMaxBytes) ? settings.skillCatalogMaxBytes : SKILL_MAX_CATALOG_BYTES
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
var SKILL_FILE_SUFFIX = "/SKILL.md";
|
|
641
|
+
var DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
642
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
643
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
644
|
+
var SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
645
|
+
function codePointLength(value) {
|
|
646
|
+
return [...value].length;
|
|
647
|
+
}
|
|
648
|
+
function getSkillsExtension(capabilities) {
|
|
649
|
+
const declared = capabilities?.extensions?.[SKILLS_EXTENSION_KEY];
|
|
650
|
+
if (typeof declared !== "object" || declared === null) return void 0;
|
|
651
|
+
const directoryRead = declared.directoryRead === true;
|
|
652
|
+
return { directoryRead };
|
|
653
|
+
}
|
|
654
|
+
function normalizeSkillUri(uri) {
|
|
655
|
+
let parsed;
|
|
656
|
+
try {
|
|
657
|
+
parsed = new URL(uri);
|
|
658
|
+
} catch {
|
|
659
|
+
return void 0;
|
|
660
|
+
}
|
|
661
|
+
const pathlessRoot = parsed.pathname === "" && parsed.host !== "";
|
|
662
|
+
if (!pathlessRoot && !parsed.pathname.startsWith("/")) return void 0;
|
|
663
|
+
return canonicalizePercentEncoding(parsed.href);
|
|
664
|
+
}
|
|
665
|
+
function canonicalizePercentEncoding(value) {
|
|
666
|
+
return value.replace(/%[0-9a-fA-F]{2}/g, (escape) => {
|
|
667
|
+
const char = String.fromCharCode(Number.parseInt(escape.slice(1), 16));
|
|
668
|
+
return /[A-Za-z0-9\-._~]/.test(char) ? char : escape.toUpperCase();
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
function skillNameFromUri(uri) {
|
|
672
|
+
const normalized = normalizeSkillUri(uri);
|
|
673
|
+
if (normalized === void 0 || !normalized.endsWith(SKILL_FILE_SUFFIX)) {
|
|
674
|
+
return void 0;
|
|
675
|
+
}
|
|
676
|
+
const path5 = normalized.slice(0, -SKILL_FILE_SUFFIX.length);
|
|
677
|
+
const segment = path5.slice(path5.lastIndexOf("/") + 1);
|
|
678
|
+
return segment.length > 0 ? segment : void 0;
|
|
679
|
+
}
|
|
680
|
+
function skillUriIdentity(uri) {
|
|
681
|
+
return normalizeSkillUri(uri) ?? uri;
|
|
682
|
+
}
|
|
683
|
+
function skillDisplayName(entry) {
|
|
684
|
+
const declared = entry.frontmatter.name?.trim();
|
|
685
|
+
if (declared) return declared;
|
|
686
|
+
return skillNameFromUri(entry.uri) ?? entry.uri;
|
|
687
|
+
}
|
|
688
|
+
function checkSkillConformance(entry) {
|
|
689
|
+
const issues = [];
|
|
690
|
+
const rawName = entry.frontmatter.name;
|
|
691
|
+
const declaredName = rawName?.trim() ? rawName : void 0;
|
|
692
|
+
const uriName = skillNameFromUri(entry.uri);
|
|
693
|
+
if (!declaredName) {
|
|
694
|
+
issues.push({
|
|
695
|
+
code: "missing-name",
|
|
696
|
+
severity: "error",
|
|
697
|
+
message: "frontmatter.name is required but missing or empty."
|
|
698
|
+
});
|
|
699
|
+
} else if (codePointLength(declaredName) > SKILL_NAME_MAX_LENGTH || !SKILL_NAME_PATTERN.test(declaredName)) {
|
|
700
|
+
issues.push({
|
|
701
|
+
code: "malformed-name",
|
|
702
|
+
severity: "error",
|
|
703
|
+
message: `frontmatter.name "${declaredName}" is not a valid Agent Skills name: 1\u2013${SKILL_NAME_MAX_LENGTH} lowercase alphanumerics and hyphens, with no leading, trailing or consecutive hyphen.`
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const rawDescription = entry.frontmatter.description;
|
|
707
|
+
if (!rawDescription?.trim()) {
|
|
708
|
+
issues.push({
|
|
709
|
+
code: "missing-description",
|
|
710
|
+
severity: "error",
|
|
711
|
+
message: "frontmatter.description is required but missing or empty."
|
|
712
|
+
});
|
|
713
|
+
} else if (codePointLength(rawDescription) > SKILL_DESCRIPTION_MAX_LENGTH) {
|
|
714
|
+
issues.push({
|
|
715
|
+
code: "malformed-description",
|
|
716
|
+
severity: "error",
|
|
717
|
+
message: `frontmatter.description is ${codePointLength(rawDescription)} characters, above the ${SKILL_DESCRIPTION_MAX_LENGTH}-character limit.`
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (uriName === void 0) {
|
|
721
|
+
issues.push({
|
|
722
|
+
code: "malformed-uri",
|
|
723
|
+
severity: "error",
|
|
724
|
+
message: `Skill URI must be a hierarchical URI ending with "${SKILL_FILE_SUFFIX}" and carrying a non-empty path segment before it.`
|
|
725
|
+
});
|
|
726
|
+
} else if (declaredName && uriName !== declaredName) {
|
|
727
|
+
issues.push({
|
|
728
|
+
code: "name-path-mismatch",
|
|
729
|
+
severity: "error",
|
|
730
|
+
message: `URI path segment "${uriName}" does not match frontmatter.name "${declaredName}".`
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (entry.resources === DYNAMIC_RESOURCES) {
|
|
734
|
+
issues.push({
|
|
735
|
+
code: "dynamic-resources",
|
|
736
|
+
severity: "warning",
|
|
737
|
+
message: 'resources is "dynamic": the file set is generated, so no digest is advertised and integrity cannot be verified.'
|
|
738
|
+
});
|
|
739
|
+
return issues;
|
|
740
|
+
}
|
|
741
|
+
if (entry.resources.length > SKILL_MAX_RESOURCE_ENTRIES) {
|
|
742
|
+
issues.push({
|
|
743
|
+
code: "resource-limit-exceeded",
|
|
744
|
+
severity: "warning",
|
|
745
|
+
message: `Skill declares ${entry.resources.length} resource entries, above the ${SKILL_MAX_RESOURCE_ENTRIES}-entry interoperability limit; a host is only required to support up to it.`
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
const totalBytes = totalSkillBytes(entry.resources);
|
|
749
|
+
if (totalBytes > SKILL_MAX_TOTAL_BYTES) {
|
|
750
|
+
issues.push({
|
|
751
|
+
code: "size-limit-exceeded",
|
|
752
|
+
severity: "warning",
|
|
753
|
+
message: `Skill resources total ${totalBytes} bytes, above the ${SKILL_MAX_TOTAL_BYTES}-byte (16 MiB) interoperability limit; a host is only required to support up to it.`
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
const entryIdentity = skillUriIdentity(entry.uri);
|
|
757
|
+
if (!entry.resources.some(
|
|
758
|
+
(resource) => skillUriIdentity(resource.uri) === entryIdentity
|
|
759
|
+
)) {
|
|
760
|
+
issues.push({
|
|
761
|
+
code: "manifest-missing-self",
|
|
762
|
+
severity: "error",
|
|
763
|
+
message: `Manifest does not list the skill's own entry file (${entry.uri}); a manifest must be the complete file set.`
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const seenUris = /* @__PURE__ */ new Set();
|
|
767
|
+
const normalizedEntryUri = normalizeSkillUri(entry.uri);
|
|
768
|
+
const root = normalizedEntryUri !== void 0 && normalizedEntryUri.endsWith(SKILL_FILE_SUFFIX) ? `${normalizedEntryUri.slice(0, -SKILL_FILE_SUFFIX.length)}/` : void 0;
|
|
769
|
+
for (const resource of entry.resources) {
|
|
770
|
+
const identity2 = skillUriIdentity(resource.uri);
|
|
771
|
+
if (seenUris.has(identity2)) {
|
|
772
|
+
issues.push({
|
|
773
|
+
code: "duplicate-resource",
|
|
774
|
+
severity: "error",
|
|
775
|
+
message: "Manifest lists this URI more than once; entries must be unique.",
|
|
776
|
+
resourceUri: resource.uri
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
seenUris.add(identity2);
|
|
780
|
+
if (root !== void 0) {
|
|
781
|
+
const normalized = normalizeSkillUri(resource.uri);
|
|
782
|
+
if (normalized === void 0 || !normalized.startsWith(root)) {
|
|
783
|
+
issues.push({
|
|
784
|
+
code: "resource-outside-skill-root",
|
|
785
|
+
severity: "error",
|
|
786
|
+
message: `Manifest entry does not resolve inside the skill root "${root}".`,
|
|
787
|
+
resourceUri: resource.uri
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (resource.digest === void 0) {
|
|
792
|
+
issues.push({
|
|
793
|
+
code: "missing-digest",
|
|
794
|
+
severity: "error",
|
|
795
|
+
message: "Manifest entry declares no digest, which is required \u2014 and without it the file cannot be verified.",
|
|
796
|
+
resourceUri: resource.uri
|
|
797
|
+
});
|
|
798
|
+
} else if (!DIGEST_PATTERN.test(resource.digest)) {
|
|
799
|
+
issues.push({
|
|
800
|
+
code: "malformed-digest",
|
|
801
|
+
severity: "error",
|
|
802
|
+
message: `Digest "${resource.digest}" is not "sha256:" followed by 64 lowercase hex characters.`,
|
|
803
|
+
resourceUri: resource.uri
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
if (resource.size === void 0) {
|
|
807
|
+
issues.push({
|
|
808
|
+
code: "missing-size",
|
|
809
|
+
severity: "error",
|
|
810
|
+
message: "Manifest entry declares no size, which is required \u2014 and without it the entry is excluded from the 16 MiB total and its length cannot be cross-checked.",
|
|
811
|
+
resourceUri: resource.uri
|
|
812
|
+
});
|
|
813
|
+
} else if (!isUsableSize(resource.size)) {
|
|
814
|
+
issues.push({
|
|
815
|
+
code: "malformed-size",
|
|
816
|
+
severity: "error",
|
|
817
|
+
message: `Size ${resource.size} is not a non-negative integer byte length.`,
|
|
818
|
+
resourceUri: resource.uri
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return issues;
|
|
823
|
+
}
|
|
824
|
+
function isUsableSize(size) {
|
|
825
|
+
return size !== void 0 && Number.isSafeInteger(size) && size >= 0;
|
|
826
|
+
}
|
|
827
|
+
function totalSkillBytes(resources) {
|
|
828
|
+
return resources.reduce(
|
|
829
|
+
(sum, r) => sum + (isUsableSize(r.size) ? r.size : 0),
|
|
830
|
+
0
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
function canonicalize(value) {
|
|
834
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
835
|
+
if (value === null || typeof value !== "object") return value;
|
|
836
|
+
return sortKeys(value);
|
|
837
|
+
}
|
|
838
|
+
function sortKeys(value) {
|
|
839
|
+
return Object.fromEntries(
|
|
840
|
+
Object.entries(value).map(([key, member]) => [key, canonicalize(member)]).sort(([x], [y]) => x.localeCompare(y))
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
function toHex(bytes) {
|
|
844
|
+
let out = "";
|
|
845
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
async function sha256Digest(bytes) {
|
|
849
|
+
const subtle = globalThis.crypto?.subtle;
|
|
850
|
+
if (!subtle) return `sha256:${toHex(sha256Bytes(bytes))}`;
|
|
851
|
+
const copy = new Uint8Array(bytes);
|
|
852
|
+
const hash = await subtle.digest("SHA-256", copy.buffer);
|
|
853
|
+
return `sha256:${toHex(new Uint8Array(hash))}`;
|
|
854
|
+
}
|
|
855
|
+
function textToBytes(text) {
|
|
856
|
+
return new TextEncoder().encode(text);
|
|
857
|
+
}
|
|
858
|
+
function bytesToText(bytes) {
|
|
859
|
+
return new TextDecoder().decode(bytes);
|
|
860
|
+
}
|
|
861
|
+
function base64ToBytes(blob) {
|
|
862
|
+
const binary = atob(blob);
|
|
863
|
+
const bytes = new Uint8Array(binary.length);
|
|
864
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
865
|
+
return bytes;
|
|
866
|
+
}
|
|
867
|
+
function skillFileBytes(contents) {
|
|
868
|
+
if (typeof contents.text === "string") return textToBytes(contents.text);
|
|
869
|
+
if (typeof contents.blob === "string") return base64ToBytes(contents.blob);
|
|
870
|
+
throw new Error("resources/read returned neither text nor blob content.");
|
|
871
|
+
}
|
|
872
|
+
async function verifySkillResource(resource, bytes) {
|
|
873
|
+
const expectedSize = resource.size;
|
|
874
|
+
if (expectedSize !== void 0 && expectedSize !== bytes.byteLength) {
|
|
875
|
+
return {
|
|
876
|
+
status: "mismatch",
|
|
877
|
+
expectedSize,
|
|
878
|
+
actualSize: bytes.byteLength,
|
|
879
|
+
...resource.digest !== void 0 ? { expectedDigest: resource.digest } : {},
|
|
880
|
+
reason: `Manifest declares ${expectedSize} bytes but the fetched file is ${bytes.byteLength}.`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
const expectedDigest = resource.digest;
|
|
884
|
+
if (expectedDigest === void 0) {
|
|
885
|
+
return {
|
|
886
|
+
status: "unverifiable",
|
|
887
|
+
reason: "The manifest entry advertises no digest."
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
if (!DIGEST_PATTERN.test(expectedDigest)) {
|
|
891
|
+
return {
|
|
892
|
+
status: "unverifiable",
|
|
893
|
+
expectedDigest,
|
|
894
|
+
reason: 'The advertised digest is not "sha256:" followed by 64 lowercase hex characters.'
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const actualDigest = await sha256Digest(bytes);
|
|
898
|
+
return {
|
|
899
|
+
status: actualDigest === expectedDigest ? "verified" : "mismatch",
|
|
900
|
+
actualDigest,
|
|
901
|
+
expectedDigest,
|
|
902
|
+
...expectedSize !== void 0 ? { expectedSize, actualSize: bytes.byteLength } : {}
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function jsonLikeEqual(a, b) {
|
|
906
|
+
if (typeof a === "number" || typeof b === "number") {
|
|
907
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
908
|
+
return Number.isFinite(a) && Number.isFinite(b) ? a === b : Object.is(a, b);
|
|
909
|
+
}
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
if (a === null || b === null) return a === b;
|
|
913
|
+
if (typeof a !== "object" || typeof b !== "object") return Object.is(a, b);
|
|
914
|
+
const aArray = Array.isArray(a);
|
|
915
|
+
if (aArray !== Array.isArray(b)) return false;
|
|
916
|
+
if (aArray) {
|
|
917
|
+
const x2 = a;
|
|
918
|
+
const y2 = b;
|
|
919
|
+
return x2.length === y2.length && x2.every((v, i) => jsonLikeEqual(v, y2[i]));
|
|
920
|
+
}
|
|
921
|
+
const x = a;
|
|
922
|
+
const y = b;
|
|
923
|
+
const keys = Object.keys(x);
|
|
924
|
+
return keys.length === Object.keys(y).length && keys.every((k) => Object.hasOwn(y, k) && jsonLikeEqual(x[k], y[k]));
|
|
925
|
+
}
|
|
926
|
+
function displayValue(value) {
|
|
927
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
928
|
+
return String(value);
|
|
929
|
+
}
|
|
930
|
+
return JSON.stringify(
|
|
931
|
+
canonicalize(value),
|
|
932
|
+
(_key, member) => typeof member === "number" && !Number.isFinite(member) ? `<${String(member)}>` : member
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
function checkSkillFrontmatterMatch(entry, skillFileText) {
|
|
936
|
+
const { frontmatter } = splitSkillFile(skillFileText);
|
|
937
|
+
if (frontmatter === void 0) {
|
|
938
|
+
return [
|
|
939
|
+
{
|
|
940
|
+
code: "frontmatter-absent",
|
|
941
|
+
severity: "error",
|
|
942
|
+
message: "The served SKILL.md carries no YAML frontmatter block, so the listing's frontmatter cannot be the file's.",
|
|
943
|
+
resourceUri: entry.uri
|
|
944
|
+
}
|
|
945
|
+
];
|
|
946
|
+
}
|
|
947
|
+
const listedError = jsonGraphError(entry.frontmatter);
|
|
948
|
+
if (listedError) {
|
|
949
|
+
return [
|
|
950
|
+
{
|
|
951
|
+
code: "frontmatter-unparsable",
|
|
952
|
+
severity: "error",
|
|
953
|
+
message: `The listing's own frontmatter cannot be compared: ${listedError}`,
|
|
954
|
+
resourceUri: entry.uri
|
|
955
|
+
}
|
|
956
|
+
];
|
|
957
|
+
}
|
|
958
|
+
const parsed = parseSkillFrontmatter(frontmatter);
|
|
959
|
+
if ("error" in parsed) {
|
|
960
|
+
return [
|
|
961
|
+
{
|
|
962
|
+
code: "frontmatter-unparsable",
|
|
963
|
+
severity: "error",
|
|
964
|
+
message: `The served SKILL.md's frontmatter is not valid YAML: ${parsed.error}`,
|
|
965
|
+
resourceUri: entry.uri
|
|
966
|
+
}
|
|
967
|
+
];
|
|
968
|
+
}
|
|
969
|
+
const issues = [];
|
|
970
|
+
const fields = [
|
|
971
|
+
.../* @__PURE__ */ new Set([
|
|
972
|
+
...Object.keys(entry.frontmatter),
|
|
973
|
+
...Object.keys(parsed.fields)
|
|
974
|
+
])
|
|
975
|
+
].sort();
|
|
976
|
+
for (const field of fields) {
|
|
977
|
+
const listed = entry.frontmatter[field];
|
|
978
|
+
const served = parsed.fields[field];
|
|
979
|
+
if (listed === void 0) {
|
|
980
|
+
issues.push({
|
|
981
|
+
code: "frontmatter-mismatch",
|
|
982
|
+
severity: "error",
|
|
983
|
+
message: `The served SKILL.md declares "${field}" but the listing's frontmatter omits it.`,
|
|
984
|
+
resourceUri: entry.uri
|
|
985
|
+
});
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (served === void 0) {
|
|
989
|
+
issues.push({
|
|
990
|
+
code: "frontmatter-mismatch",
|
|
991
|
+
severity: "error",
|
|
992
|
+
message: `The listing declares "${field}" but the served SKILL.md omits it.`,
|
|
993
|
+
resourceUri: entry.uri
|
|
994
|
+
});
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (!jsonLikeEqual(listed, served)) {
|
|
998
|
+
issues.push({
|
|
999
|
+
code: "frontmatter-mismatch",
|
|
1000
|
+
severity: "error",
|
|
1001
|
+
message: `Field "${field}" differs: the listing says ${displayValue(listed)} but the served SKILL.md says ${displayValue(served)}.`,
|
|
1002
|
+
resourceUri: entry.uri
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return issues;
|
|
1007
|
+
}
|
|
1008
|
+
var COLLISION_SAMPLE = 3;
|
|
1009
|
+
function checkSkillNameCollisions(entries) {
|
|
1010
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1011
|
+
for (const entry of entries) {
|
|
1012
|
+
const name = entry.frontmatter.name;
|
|
1013
|
+
if (typeof name !== "string" || name.trim() === "") continue;
|
|
1014
|
+
const group = byName.get(name);
|
|
1015
|
+
if (group) group.push(entry);
|
|
1016
|
+
else byName.set(name, [entry]);
|
|
1017
|
+
}
|
|
1018
|
+
const issues = /* @__PURE__ */ new Map();
|
|
1019
|
+
for (const [name, group] of byName) {
|
|
1020
|
+
const identities = new Set(group.map((e) => skillUriIdentity(e.uri)));
|
|
1021
|
+
if (identities.size < 2) continue;
|
|
1022
|
+
const uris = [...identities].sort();
|
|
1023
|
+
for (const entry of group) {
|
|
1024
|
+
const self = skillUriIdentity(entry.uri);
|
|
1025
|
+
const sample = [];
|
|
1026
|
+
for (const uri of uris) {
|
|
1027
|
+
if (uri === self) continue;
|
|
1028
|
+
sample.push(uri);
|
|
1029
|
+
if (sample.length === COLLISION_SAMPLE) break;
|
|
1030
|
+
}
|
|
1031
|
+
const unshown = identities.size - 1 - sample.length;
|
|
1032
|
+
const others = unshown > 0 ? `${sample.join(", ")}, and ${unshown} more` : sample.join(", ");
|
|
1033
|
+
const subject = identities.size === 2 ? "Another skill in this listing also declares" : `${identities.size - 1} other skills in this listing also declare`;
|
|
1034
|
+
issues.set(self, {
|
|
1035
|
+
code: "duplicate-name",
|
|
1036
|
+
severity: "warning",
|
|
1037
|
+
message: `${subject} the name "${name}" (${others}). This is legal \u2014 a consumer must tell them apart by their URIs rather than collapsing or preferring one.`
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return issues;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
393
1044
|
// ../../core/json/nullableUnion.ts
|
|
394
1045
|
var RENDERABLE_TYPES = [
|
|
395
1046
|
"string",
|
|
@@ -616,7 +1267,11 @@ function isOfferable(branch, merged) {
|
|
|
616
1267
|
return false;
|
|
617
1268
|
}
|
|
618
1269
|
if (!admitsObject(branch)) return false;
|
|
619
|
-
const
|
|
1270
|
+
const mergedProperties = propertiesOf(merged);
|
|
1271
|
+
if (requiredOf(merged).some((name) => !Object.hasOwn(mergedProperties, name))) {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
const properties = Object.values(mergedProperties);
|
|
620
1275
|
return properties.length > 0 && // Every value has to be something a renderer can read AND something a
|
|
621
1276
|
// caller can satisfy. A `null` or an array is not a schema at all, and the
|
|
622
1277
|
// web form dereferences one on the way to choosing a widget. JSON Schema's
|
|
@@ -1176,16 +1831,19 @@ function oauthEndpointOverridesFromSettings(settings) {
|
|
|
1176
1831
|
};
|
|
1177
1832
|
}
|
|
1178
1833
|
function storedFieldsToInspectorSettings(stored) {
|
|
1179
|
-
const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
|
|
1834
|
+
const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
|
|
1180
1835
|
if (!hasAny) return void 0;
|
|
1181
1836
|
const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
|
|
1182
1837
|
const settings = {
|
|
1183
1838
|
headers: headersPairs,
|
|
1184
1839
|
env: envRecordToPairs(stored.env),
|
|
1185
1840
|
metadata: normalizeStoredMetadata(stored.metadata),
|
|
1186
|
-
|
|
1841
|
+
// Concrete product default, like taskTtl below: an absent field reads back
|
|
1842
|
+
// as 30 s so the form shows the bound that will actually apply. An explicit
|
|
1843
|
+
// 0 is preserved as-is — it is the user's opt-out, not a sentinel (#2320).
|
|
1844
|
+
connectionTimeout: stored.connectionTimeout ?? DEFAULT_CONNECTION_TIMEOUT_MS,
|
|
1187
1845
|
requestTimeout: stored.requestTimeout ?? 0,
|
|
1188
|
-
// Unlike
|
|
1846
|
+
// Unlike requestTimeout (0 = "SDK default"), task TTL has a concrete product
|
|
1189
1847
|
// default so the form shows it and "Run as task" has a value to send.
|
|
1190
1848
|
taskTtl: stored.taskTtl ?? DEFAULT_TASK_TTL_MS,
|
|
1191
1849
|
autoRefreshOnListChanged: stored.autoRefreshOnListChanged ?? false,
|
|
@@ -1199,6 +1857,12 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
1199
1857
|
// `[]`, which `inspectorSettingsToStoredFields` then omits on write.
|
|
1200
1858
|
roots: stored.roots ?? []
|
|
1201
1859
|
};
|
|
1860
|
+
if (isSkillCatalogLimit(stored.skillCatalogMaxSkills)) {
|
|
1861
|
+
settings.skillCatalogMaxSkills = stored.skillCatalogMaxSkills;
|
|
1862
|
+
}
|
|
1863
|
+
if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
|
|
1864
|
+
settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
|
|
1865
|
+
}
|
|
1202
1866
|
if (isProtocolEra(stored.protocolEra)) {
|
|
1203
1867
|
settings.protocolEra = stored.protocolEra;
|
|
1204
1868
|
}
|
|
@@ -1252,6 +1916,8 @@ var INSPECTOR_FIELD_KEY_MAP = {
|
|
|
1252
1916
|
paginatedLists: true,
|
|
1253
1917
|
advertisedExtensions: true,
|
|
1254
1918
|
maxFetchRequests: true,
|
|
1919
|
+
skillCatalogMaxSkills: true,
|
|
1920
|
+
skillCatalogMaxBytes: true,
|
|
1255
1921
|
oauth: true,
|
|
1256
1922
|
roots: true
|
|
1257
1923
|
};
|
|
@@ -1352,6 +2018,14 @@ function parseHeaderPair(value, previous = {}) {
|
|
|
1352
2018
|
}
|
|
1353
2019
|
return { ...previous, [key]: val };
|
|
1354
2020
|
}
|
|
2021
|
+
function parseProtocolEra(value) {
|
|
2022
|
+
if (!isProtocolEra(value)) {
|
|
2023
|
+
throw new Error(
|
|
2024
|
+
`Invalid protocol era: ${value}. Valid eras are: legacy, auto, modern`
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
return value;
|
|
2028
|
+
}
|
|
1355
2029
|
var EMPTY_CATALOG_CONTENT = `${JSON.stringify({ mcpServers: {} }, null, 2)}
|
|
1356
2030
|
`;
|
|
1357
2031
|
function seedEmptyCatalog(resolvedPath) {
|
|
@@ -2753,10 +3427,16 @@ function headersToServerSettings(headers) {
|
|
|
2753
3427
|
return void 0;
|
|
2754
3428
|
}
|
|
2755
3429
|
return {
|
|
2756
|
-
|
|
3430
|
+
...defaultServerSettings(),
|
|
3431
|
+
headers: Object.entries(headers).map(([key, value]) => ({ key, value }))
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
function defaultServerSettings() {
|
|
3435
|
+
return {
|
|
3436
|
+
headers: [],
|
|
2757
3437
|
env: [],
|
|
2758
3438
|
metadata: {},
|
|
2759
|
-
connectionTimeout:
|
|
3439
|
+
connectionTimeout: DEFAULT_CONNECTION_TIMEOUT_MS,
|
|
2760
3440
|
requestTimeout: 0,
|
|
2761
3441
|
taskTtl: DEFAULT_TASK_TTL_MS,
|
|
2762
3442
|
maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
|
|
@@ -2765,11 +3445,19 @@ function headersToServerSettings(headers) {
|
|
|
2765
3445
|
roots: []
|
|
2766
3446
|
};
|
|
2767
3447
|
}
|
|
2768
|
-
function mergeSettings(base,
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
if (
|
|
2772
|
-
|
|
3448
|
+
function mergeSettings(base, overrides) {
|
|
3449
|
+
let settings = base;
|
|
3450
|
+
const fromHeaders = headersToServerSettings(overrides.headers);
|
|
3451
|
+
if (fromHeaders) {
|
|
3452
|
+
settings = settings ? { ...settings, headers: fromHeaders.headers } : fromHeaders;
|
|
3453
|
+
}
|
|
3454
|
+
if (overrides.protocolEra) {
|
|
3455
|
+
settings = {
|
|
3456
|
+
...settings ?? defaultServerSettings(),
|
|
3457
|
+
protocolEra: overrides.protocolEra
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
return settings;
|
|
2773
3461
|
}
|
|
2774
3462
|
async function loadServerEntries(serverOptions) {
|
|
2775
3463
|
serverOptions = withDefaultCatalogPath(serverOptions);
|
|
@@ -2794,10 +3482,11 @@ async function loadServerEntries(serverOptions) {
|
|
|
2794
3482
|
env: serverOptions.env,
|
|
2795
3483
|
cwd: serverOptions.cwd
|
|
2796
3484
|
}),
|
|
2797
|
-
// Deliberate broadcast: a single `--header` set
|
|
2798
|
-
// server in the catalog/config (fine for the
|
|
2799
|
-
// for multi-server files, prefer per-server
|
|
2800
|
-
|
|
3485
|
+
// Deliberate broadcast: a single `--header` set (and `--protocol-era`)
|
|
3486
|
+
// is merged into EVERY server in the catalog/config (fine for the
|
|
3487
|
+
// common single-server case; for multi-server files, prefer per-server
|
|
3488
|
+
// settings in the file itself).
|
|
3489
|
+
settings: mergeSettings(entry.settings, serverOptions)
|
|
2801
3490
|
};
|
|
2802
3491
|
}
|
|
2803
3492
|
return result;
|
|
@@ -2811,7 +3500,7 @@ async function loadServerEntries(serverOptions) {
|
|
|
2811
3500
|
return {
|
|
2812
3501
|
default: {
|
|
2813
3502
|
config: configs[0],
|
|
2814
|
-
settings:
|
|
3503
|
+
settings: mergeSettings(void 0, serverOptions)
|
|
2815
3504
|
}
|
|
2816
3505
|
};
|
|
2817
3506
|
}
|
|
@@ -2961,6 +3650,7 @@ function redactBody(body, contentType) {
|
|
|
2961
3650
|
return body;
|
|
2962
3651
|
}
|
|
2963
3652
|
function findHeader(headers, name) {
|
|
3653
|
+
if (!headers) return void 0;
|
|
2964
3654
|
const target = name.toLowerCase();
|
|
2965
3655
|
for (const [key, value] of Object.entries(headers)) {
|
|
2966
3656
|
if (key.toLowerCase() === target) return value;
|
|
@@ -2968,9 +3658,146 @@ function findHeader(headers, name) {
|
|
|
2968
3658
|
return void 0;
|
|
2969
3659
|
}
|
|
2970
3660
|
function isLongLivedStreamResponse(method, contentType) {
|
|
2971
|
-
if (method !== "GET") return false;
|
|
3661
|
+
if (method.toUpperCase() !== "GET") return false;
|
|
2972
3662
|
if (!contentType) return false;
|
|
2973
|
-
|
|
3663
|
+
const type = contentType.toLowerCase();
|
|
3664
|
+
return type.includes("text/event-stream") || type.includes("application/x-ndjson");
|
|
3665
|
+
}
|
|
3666
|
+
var CHALLENGE_BODY_MAX_BYTES = 64 * 1024;
|
|
3667
|
+
var CHALLENGE_BODY_READ_MS = 2e3;
|
|
3668
|
+
var TRUNCATED_BODY_SUFFIX = "\n[truncated]";
|
|
3669
|
+
var WITHHELD_TRUNCATED_JSON_BODY = "[truncated JSON body withheld: an incomplete body cannot be redacted]";
|
|
3670
|
+
function recordableChallengeBody(read, contentType) {
|
|
3671
|
+
if (!read.truncated) return redactBody(read.text, contentType);
|
|
3672
|
+
const looksLikeJson = (contentType ?? "").toLowerCase().includes("json") || /^\s*[{[]/.test(read.text);
|
|
3673
|
+
if (looksLikeJson) return WITHHELD_TRUNCATED_JSON_BODY;
|
|
3674
|
+
return redactBody(read.text, contentType) + TRUNCATED_BODY_SUFFIX;
|
|
3675
|
+
}
|
|
3676
|
+
async function readChallengeBody(response) {
|
|
3677
|
+
let reader;
|
|
3678
|
+
try {
|
|
3679
|
+
const body = response.clone().body;
|
|
3680
|
+
if (!body) return void 0;
|
|
3681
|
+
reader = body.getReader();
|
|
3682
|
+
} catch {
|
|
3683
|
+
return void 0;
|
|
3684
|
+
}
|
|
3685
|
+
const decoder = new TextDecoder();
|
|
3686
|
+
let text = "";
|
|
3687
|
+
let bytes = 0;
|
|
3688
|
+
let timer;
|
|
3689
|
+
const deadline = new Promise((resolve7) => {
|
|
3690
|
+
timer = setTimeout(() => resolve7("timeout"), CHALLENGE_BODY_READ_MS);
|
|
3691
|
+
});
|
|
3692
|
+
try {
|
|
3693
|
+
while (bytes < CHALLENGE_BODY_MAX_BYTES) {
|
|
3694
|
+
const next = await Promise.race([reader.read(), deadline]);
|
|
3695
|
+
if (next === "timeout") break;
|
|
3696
|
+
if (next.done) return { text: text + decoder.decode(), truncated: false };
|
|
3697
|
+
const chunk = next.value.subarray(0, CHALLENGE_BODY_MAX_BYTES - bytes);
|
|
3698
|
+
bytes += chunk.byteLength;
|
|
3699
|
+
text += decoder.decode(chunk, { stream: true });
|
|
3700
|
+
}
|
|
3701
|
+
reader.cancel().catch(() => {
|
|
3702
|
+
});
|
|
3703
|
+
return { text: text + decoder.decode(), truncated: true };
|
|
3704
|
+
} catch {
|
|
3705
|
+
return void 0;
|
|
3706
|
+
} finally {
|
|
3707
|
+
clearTimeout(timer);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
var DEFAULT_STREAM_UPDATE_INTERVAL_MS = 250;
|
|
3711
|
+
function longLivedStreamFraming(contentType) {
|
|
3712
|
+
return contentType?.toLowerCase().includes("application/x-ndjson") ? "ndjson" : "sse";
|
|
3713
|
+
}
|
|
3714
|
+
function watchLongLivedStream(response, id, framing, callbacks) {
|
|
3715
|
+
const listener = callbacks.updateStream;
|
|
3716
|
+
if (!listener) return;
|
|
3717
|
+
const report = (state) => {
|
|
3718
|
+
try {
|
|
3719
|
+
listener(id, state);
|
|
3720
|
+
} catch {
|
|
3721
|
+
}
|
|
3722
|
+
};
|
|
3723
|
+
const intervalMs = callbacks.streamUpdateIntervalMs ?? DEFAULT_STREAM_UPDATE_INTERVAL_MS;
|
|
3724
|
+
let body;
|
|
3725
|
+
try {
|
|
3726
|
+
body = response.clone().body;
|
|
3727
|
+
} catch {
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
if (!body) return;
|
|
3731
|
+
const reader = body.getReader();
|
|
3732
|
+
const decoder = new TextDecoder();
|
|
3733
|
+
let eventCount = 0;
|
|
3734
|
+
let pending = "";
|
|
3735
|
+
let blockHasData = false;
|
|
3736
|
+
let reportTimer = null;
|
|
3737
|
+
const reportCount = () => {
|
|
3738
|
+
if (intervalMs <= 0) {
|
|
3739
|
+
report({ eventCount });
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
if (reportTimer !== null) return;
|
|
3743
|
+
reportTimer = setTimeout(() => {
|
|
3744
|
+
reportTimer = null;
|
|
3745
|
+
report({ eventCount });
|
|
3746
|
+
}, intervalMs);
|
|
3747
|
+
};
|
|
3748
|
+
report({ eventCount });
|
|
3749
|
+
const consumeLine = (line) => {
|
|
3750
|
+
if (framing === "ndjson") {
|
|
3751
|
+
if (line.trim() === "") return;
|
|
3752
|
+
eventCount += 1;
|
|
3753
|
+
reportCount();
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
3756
|
+
if (line === "") {
|
|
3757
|
+
if (blockHasData) {
|
|
3758
|
+
eventCount += 1;
|
|
3759
|
+
blockHasData = false;
|
|
3760
|
+
reportCount();
|
|
3761
|
+
}
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
if (line === "data" || line.startsWith("data:")) blockHasData = true;
|
|
3765
|
+
};
|
|
3766
|
+
const consumeCompleteLines = () => {
|
|
3767
|
+
for (; ; ) {
|
|
3768
|
+
const cr = pending.indexOf("\r");
|
|
3769
|
+
const lf = pending.indexOf("\n");
|
|
3770
|
+
const end = cr === -1 ? lf : lf === -1 ? cr : Math.min(cr, lf);
|
|
3771
|
+
if (end === -1) return;
|
|
3772
|
+
if (pending[end] === "\r") {
|
|
3773
|
+
if (end === pending.length - 1) return;
|
|
3774
|
+
const width = pending[end + 1] === "\n" ? 2 : 1;
|
|
3775
|
+
consumeLine(pending.slice(0, end));
|
|
3776
|
+
pending = pending.slice(end + width);
|
|
3777
|
+
} else {
|
|
3778
|
+
consumeLine(pending.slice(0, end));
|
|
3779
|
+
pending = pending.slice(end + 1);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
const pump = async () => {
|
|
3784
|
+
try {
|
|
3785
|
+
for (; ; ) {
|
|
3786
|
+
const { value, done } = await reader.read();
|
|
3787
|
+
if (done) break;
|
|
3788
|
+
pending += decoder.decode(value, { stream: true });
|
|
3789
|
+
consumeCompleteLines();
|
|
3790
|
+
}
|
|
3791
|
+
if (pending.endsWith("\r")) consumeLine(pending.slice(0, -1));
|
|
3792
|
+
} catch {
|
|
3793
|
+
}
|
|
3794
|
+
if (reportTimer !== null) {
|
|
3795
|
+
clearTimeout(reportTimer);
|
|
3796
|
+
reportTimer = null;
|
|
3797
|
+
}
|
|
3798
|
+
report({ eventCount, closedAt: /* @__PURE__ */ new Date() });
|
|
3799
|
+
};
|
|
3800
|
+
void pump();
|
|
2974
3801
|
}
|
|
2975
3802
|
function createFetchTracker(baseFetch, callbacks) {
|
|
2976
3803
|
return async (input, init) => {
|
|
@@ -2978,7 +3805,7 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
2978
3805
|
const timestamp = /* @__PURE__ */ new Date();
|
|
2979
3806
|
const id = `${timestamp.getTime()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
2980
3807
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
2981
|
-
const method = init?.method
|
|
3808
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
2982
3809
|
const rawRequestHeaders = {};
|
|
2983
3810
|
if (input instanceof Request) {
|
|
2984
3811
|
input.headers.forEach((value, key) => {
|
|
@@ -3058,7 +3885,21 @@ function createFetchTracker(baseFetch, callbacks) {
|
|
|
3058
3885
|
responseBody: void 0,
|
|
3059
3886
|
duration
|
|
3060
3887
|
};
|
|
3888
|
+
if ((responseStatus === 401 || responseStatus === 403) && response.body && !response.bodyUsed) {
|
|
3889
|
+
const read = await readChallengeBody(response);
|
|
3890
|
+
entry.responseBody = read === void 0 ? void 0 : recordableChallengeBody(read, response.headers.get("content-type"));
|
|
3891
|
+
callbacks.trackRequest?.(entry);
|
|
3892
|
+
return response;
|
|
3893
|
+
}
|
|
3061
3894
|
callbacks.trackRequest?.(entry);
|
|
3895
|
+
if (isLongLivedStream && response.body && !response.bodyUsed) {
|
|
3896
|
+
watchLongLivedStream(
|
|
3897
|
+
response,
|
|
3898
|
+
id,
|
|
3899
|
+
longLivedStreamFraming(response.headers.get("content-type")),
|
|
3900
|
+
callbacks
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3062
3903
|
if (!isLongLivedStream && response.body && !response.bodyUsed) {
|
|
3063
3904
|
const responseContentType = response.headers.get("content-type");
|
|
3064
3905
|
try {
|
|
@@ -3380,7 +4221,7 @@ function createAuthChallengeInterceptFetch(baseFetch) {
|
|
|
3380
4221
|
if (!challenge) {
|
|
3381
4222
|
return response;
|
|
3382
4223
|
}
|
|
3383
|
-
|
|
4224
|
+
void response.body?.cancel().catch(() => {
|
|
3384
4225
|
});
|
|
3385
4226
|
throw new AuthChallengeError(
|
|
3386
4227
|
challenge,
|
|
@@ -3528,14 +4369,24 @@ function createTransportNode(config, options = {}) {
|
|
|
3528
4369
|
pipeStderr = false,
|
|
3529
4370
|
onFetchRequest,
|
|
3530
4371
|
onFetchResponseBody,
|
|
4372
|
+
onFetchStreamUpdate,
|
|
3531
4373
|
authProvider,
|
|
3532
4374
|
settings,
|
|
3533
4375
|
interceptAuthChallenges = false,
|
|
3534
4376
|
onAuthChallengeObserved
|
|
3535
4377
|
} = options;
|
|
4378
|
+
const wantsFetchTracking = Boolean(
|
|
4379
|
+
onFetchRequest || onFetchResponseBody || onFetchStreamUpdate
|
|
4380
|
+
);
|
|
3536
4381
|
const baseFetch = optionsFetchFn ?? createProxyFetch() ?? globalThis.fetch;
|
|
3537
4382
|
const withChallengeObserver = (inner) => onAuthChallengeObserved ? createAuthChallengeObserverFetch(inner, onAuthChallengeObserved) : inner;
|
|
3538
|
-
const
|
|
4383
|
+
const withTracking = (inner) => wantsFetchTracking ? createFetchTracker(inner, {
|
|
4384
|
+
trackRequest: onFetchRequest,
|
|
4385
|
+
updateResponseBody: onFetchResponseBody,
|
|
4386
|
+
updateStream: onFetchStreamUpdate
|
|
4387
|
+
}) : inner;
|
|
4388
|
+
const trackedBase = withTracking(withChallengeObserver(baseFetch));
|
|
4389
|
+
const fetchWithOptionalAuthIntercept = interceptAuthChallenges ? createAuthChallengeInterceptFetch(trackedBase) : trackedBase;
|
|
3539
4390
|
if (serverType === "stdio") {
|
|
3540
4391
|
const stdioConfig = config;
|
|
3541
4392
|
const transport = new StdioClientTransport({
|
|
@@ -3561,11 +4412,7 @@ function createTransportNode(config, options = {}) {
|
|
|
3561
4412
|
const sseConfig = config;
|
|
3562
4413
|
const url = new URL(sseConfig.url);
|
|
3563
4414
|
const configuredSseFetch = sseConfig.eventSourceInit?.fetch;
|
|
3564
|
-
const
|
|
3565
|
-
const trackedFetch = onFetchRequest ? createFetchTracker(sseFetch, {
|
|
3566
|
-
trackRequest: onFetchRequest,
|
|
3567
|
-
updateResponseBody: onFetchResponseBody
|
|
3568
|
-
}) : sseFetch;
|
|
4415
|
+
const trackedFetch = configuredSseFetch ? withTracking(withChallengeObserver(configuredSseFetch)) : fetchWithOptionalAuthIntercept;
|
|
3569
4416
|
const headers = headersFromSettings(settings);
|
|
3570
4417
|
const eventSourceInit = {
|
|
3571
4418
|
...sseConfig.eventSourceInit,
|
|
@@ -3576,15 +4423,11 @@ function createTransportNode(config, options = {}) {
|
|
|
3576
4423
|
...sseConfig.requestInit,
|
|
3577
4424
|
...headers && { headers }
|
|
3578
4425
|
};
|
|
3579
|
-
const postFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
|
|
3580
|
-
trackRequest: onFetchRequest,
|
|
3581
|
-
updateResponseBody: onFetchResponseBody
|
|
3582
|
-
}) : fetchWithOptionalAuthIntercept;
|
|
3583
4426
|
const transport = new SSEClientTransport(url, {
|
|
3584
4427
|
authProvider,
|
|
3585
4428
|
eventSourceInit,
|
|
3586
4429
|
requestInit,
|
|
3587
|
-
fetch:
|
|
4430
|
+
fetch: fetchWithOptionalAuthIntercept
|
|
3588
4431
|
});
|
|
3589
4432
|
return { transport };
|
|
3590
4433
|
} else {
|
|
@@ -3595,14 +4438,10 @@ function createTransportNode(config, options = {}) {
|
|
|
3595
4438
|
...httpConfig.requestInit,
|
|
3596
4439
|
...headers && { headers }
|
|
3597
4440
|
};
|
|
3598
|
-
const transportFetch = onFetchRequest ? createFetchTracker(fetchWithOptionalAuthIntercept, {
|
|
3599
|
-
trackRequest: onFetchRequest,
|
|
3600
|
-
updateResponseBody: onFetchResponseBody
|
|
3601
|
-
}) : fetchWithOptionalAuthIntercept;
|
|
3602
4441
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
3603
4442
|
authProvider,
|
|
3604
4443
|
requestInit,
|
|
3605
|
-
fetch:
|
|
4444
|
+
fetch: fetchWithOptionalAuthIntercept,
|
|
3606
4445
|
// SEP-2350: how the transport reacts to a `403 insufficient_scope`
|
|
3607
4446
|
// challenge. Defaults to the SDK's `reauthorize` when unset.
|
|
3608
4447
|
...settings?.oauthOnInsufficientScope && {
|
|
@@ -4238,6 +5077,7 @@ import {
|
|
|
4238
5077
|
OAuthTokensSchema as OAuthTokensSchema2
|
|
4239
5078
|
} from "@modelcontextprotocol/core";
|
|
4240
5079
|
var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
|
|
5080
|
+
var MIN_REVOCATION_REQUEST_BUDGET_MS = 5;
|
|
4241
5081
|
function selectRevocableToken(tokens) {
|
|
4242
5082
|
if (!tokens) return null;
|
|
4243
5083
|
if (tokens.refresh_token) {
|
|
@@ -4285,7 +5125,10 @@ function base64Encode(value) {
|
|
|
4285
5125
|
return Buffer.from(value, "utf8").toString("base64");
|
|
4286
5126
|
}
|
|
4287
5127
|
async function revokeToken(params) {
|
|
4288
|
-
const timeoutMs =
|
|
5128
|
+
const timeoutMs = Math.max(
|
|
5129
|
+
0,
|
|
5130
|
+
Math.round(params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS)
|
|
5131
|
+
);
|
|
4289
5132
|
try {
|
|
4290
5133
|
const { url, init } = buildRevocationRequest(params);
|
|
4291
5134
|
const response = await withDeadline(
|
|
@@ -4473,7 +5316,7 @@ async function runPlan(plan, params) {
|
|
|
4473
5316
|
if (!endpoint) return { status: "skipped", reason: "no_endpoint" };
|
|
4474
5317
|
const outcomes = [...plan.failures];
|
|
4475
5318
|
const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
|
|
4476
|
-
const deadlineAt =
|
|
5319
|
+
const deadlineAt = performance.now() + timeoutMs;
|
|
4477
5320
|
for (const grant of plan.grants) {
|
|
4478
5321
|
if (grant.issuer === void 0) {
|
|
4479
5322
|
outcomes.push({
|
|
@@ -4489,8 +5332,8 @@ async function runPlan(plan, params) {
|
|
|
4489
5332
|
});
|
|
4490
5333
|
continue;
|
|
4491
5334
|
}
|
|
4492
|
-
const remainingMs = deadlineAt -
|
|
4493
|
-
if (remainingMs <=
|
|
5335
|
+
const remainingMs = deadlineAt - performance.now();
|
|
5336
|
+
if (remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
|
|
4494
5337
|
outcomes.push({
|
|
4495
5338
|
status: "failed",
|
|
4496
5339
|
endpoint,
|
|
@@ -4542,13 +5385,13 @@ async function clearStoredAuthForRelogin(serverUrl, options) {
|
|
|
4542
5385
|
}
|
|
4543
5386
|
async function sendPlans(plans, budgetMs) {
|
|
4544
5387
|
const fetchFn = createProxyFetch() ?? fetch;
|
|
4545
|
-
const deadlineAt =
|
|
5388
|
+
const deadlineAt = performance.now() + budgetMs;
|
|
4546
5389
|
let reported;
|
|
4547
5390
|
let lastSkip;
|
|
4548
5391
|
for (const plan of plans) {
|
|
4549
|
-
const remainingMs = deadlineAt -
|
|
5392
|
+
const remainingMs = deadlineAt - performance.now();
|
|
4550
5393
|
const needsNetwork = plan.outcome === void 0;
|
|
4551
|
-
if (needsNetwork && remainingMs <=
|
|
5394
|
+
if (needsNetwork && remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
|
|
4552
5395
|
const exhausted = {
|
|
4553
5396
|
status: "failed",
|
|
4554
5397
|
detail: `the ${budgetMs}ms revocation budget was exhausted before "${plan.serverUrl}" was attempted`
|
|
@@ -4576,7 +5419,7 @@ async function sendPlans(plans, budgetMs) {
|
|
|
4576
5419
|
// ../../core/mcp/inspectorClient.ts
|
|
4577
5420
|
import { Client } from "@modelcontextprotocol/client";
|
|
4578
5421
|
import {
|
|
4579
|
-
ProgressTokenSchema,
|
|
5422
|
+
ProgressTokenSchema as ProgressTokenSchema2,
|
|
4580
5423
|
RequestMetaSchema
|
|
4581
5424
|
} from "@modelcontextprotocol/core";
|
|
4582
5425
|
|
|
@@ -4769,7 +5612,15 @@ var MessageTrackingTransport = class {
|
|
|
4769
5612
|
"client"
|
|
4770
5613
|
);
|
|
4771
5614
|
} else if ("method" in message) {
|
|
4772
|
-
|
|
5615
|
+
const request = message;
|
|
5616
|
+
this.callbacks.trackRequest?.(request, "client");
|
|
5617
|
+
const abortedBeforeSend = options?.requestSignal?.aborted === true;
|
|
5618
|
+
return this.baseTransport.send(message, options).catch((err) => {
|
|
5619
|
+
if (abortedBeforeSend || !options?.requestSignal?.aborted) {
|
|
5620
|
+
this.callbacks.trackSendFailure?.(request, err);
|
|
5621
|
+
}
|
|
5622
|
+
throw err;
|
|
5623
|
+
});
|
|
4773
5624
|
}
|
|
4774
5625
|
} else if ("method" in message) {
|
|
4775
5626
|
this.callbacks.trackNotification?.(
|
|
@@ -4890,7 +5741,7 @@ import {
|
|
|
4890
5741
|
} from "@modelcontextprotocol/client";
|
|
4891
5742
|
|
|
4892
5743
|
// ../../core/mcp/modernTaskSchemas.ts
|
|
4893
|
-
import { z } from "zod/v4";
|
|
5744
|
+
import { z as z2 } from "zod/v4";
|
|
4894
5745
|
var TASKS_EXTENSION_KEY = "io.modelcontextprotocol/tasks";
|
|
4895
5746
|
var MODERN_PROTOCOL_VERSION2 = "2026-07-28";
|
|
4896
5747
|
var TASKS_EXTENSION_CLIENT_CAPABILITY = {
|
|
@@ -4900,31 +5751,31 @@ var MODERN_TASK_HANDLE_META = "io.modelcontextprotocol/inspector/modernTaskHandl
|
|
|
4900
5751
|
function isModernCreateTaskResult(result) {
|
|
4901
5752
|
return typeof result === "object" && result !== null && result.resultType === "task" && typeof result.taskId === "string";
|
|
4902
5753
|
}
|
|
4903
|
-
var ModernTaskStatusSchema =
|
|
5754
|
+
var ModernTaskStatusSchema = z2.enum([
|
|
4904
5755
|
"working",
|
|
4905
5756
|
"input_required",
|
|
4906
5757
|
"completed",
|
|
4907
5758
|
"failed",
|
|
4908
5759
|
"cancelled"
|
|
4909
5760
|
]);
|
|
4910
|
-
var ModernDetailedTaskSchema =
|
|
4911
|
-
taskId:
|
|
5761
|
+
var ModernDetailedTaskSchema = z2.looseObject({
|
|
5762
|
+
taskId: z2.string(),
|
|
4912
5763
|
status: ModernTaskStatusSchema,
|
|
4913
|
-
statusMessage:
|
|
4914
|
-
createdAt:
|
|
4915
|
-
lastUpdatedAt:
|
|
4916
|
-
ttlMs:
|
|
4917
|
-
pollIntervalMs:
|
|
5764
|
+
statusMessage: z2.string().optional(),
|
|
5765
|
+
createdAt: z2.string(),
|
|
5766
|
+
lastUpdatedAt: z2.string(),
|
|
5767
|
+
ttlMs: z2.number().nullable().optional(),
|
|
5768
|
+
pollIntervalMs: z2.number().optional(),
|
|
4918
5769
|
/** Present on `completed`: the original request's result (e.g. CallToolResult). */
|
|
4919
|
-
result:
|
|
5770
|
+
result: z2.record(z2.string(), z2.unknown()).optional(),
|
|
4920
5771
|
/** Present on `failed`: the JSON-RPC error that ended the task. */
|
|
4921
|
-
error:
|
|
5772
|
+
error: z2.record(z2.string(), z2.unknown()).optional(),
|
|
4922
5773
|
/** Present on `input_required`: embedded server→client requests, keyed by id. */
|
|
4923
|
-
inputRequests:
|
|
5774
|
+
inputRequests: z2.record(z2.string(), z2.unknown()).optional()
|
|
4924
5775
|
});
|
|
4925
5776
|
var ModernGetTaskResultSchema = ModernDetailedTaskSchema;
|
|
4926
|
-
var ModernUpdateTaskResultSchema =
|
|
4927
|
-
var ModernCancelTaskResultSchema =
|
|
5777
|
+
var ModernUpdateTaskResultSchema = z2.looseObject({});
|
|
5778
|
+
var ModernCancelTaskResultSchema = z2.looseObject({});
|
|
4928
5779
|
function normalizeModernTask(modern) {
|
|
4929
5780
|
const { ttlMs, pollIntervalMs, ...rest } = modern;
|
|
4930
5781
|
const normalized = { ...rest };
|
|
@@ -4937,9 +5788,10 @@ function readInputRequests(modern) {
|
|
|
4937
5788
|
}
|
|
4938
5789
|
|
|
4939
5790
|
// ../../core/mcp/extensions.ts
|
|
5791
|
+
import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
4940
5792
|
var EMA_EXTENSION_KEY = "io.modelcontextprotocol/enterprise-managed-authorization";
|
|
4941
5793
|
var UI_EXTENSION_KEY = "io.modelcontextprotocol/ui";
|
|
4942
|
-
var MCP_APP_MIME_TYPE =
|
|
5794
|
+
var MCP_APP_MIME_TYPE = RESOURCE_MIME_TYPE;
|
|
4943
5795
|
var ADVERTISABLE_EXTENSIONS = [
|
|
4944
5796
|
{
|
|
4945
5797
|
key: TASKS_EXTENSION_KEY,
|
|
@@ -4958,6 +5810,18 @@ var ADVERTISABLE_EXTENSIONS = [
|
|
|
4958
5810
|
// a conforming server checks the `mimeTypes` before serving a UI resource.
|
|
4959
5811
|
defaultAdvertised: true,
|
|
4960
5812
|
advertisement: { mimeTypes: [MCP_APP_MIME_TYPE] }
|
|
5813
|
+
},
|
|
5814
|
+
{
|
|
5815
|
+
key: SKILLS_EXTENSION_KEY,
|
|
5816
|
+
label: "Skills (io.modelcontextprotocol/skills)",
|
|
5817
|
+
// The Skills extension (SEP-2640). SEP-2133 negotiates an extension from
|
|
5818
|
+
// both sides, so a server may refuse `skills/list`, `skills/get` and
|
|
5819
|
+
// `resources/directory/read` to a client that did not declare it — and the
|
|
5820
|
+
// Inspector calls all three once the server declares its half. Advertised
|
|
5821
|
+
// by default for that reason; turning it off is how to check a server's
|
|
5822
|
+
// refusal path. Declared with no settings: SEP-2640 defines none for the
|
|
5823
|
+
// client side. (#2373)
|
|
5824
|
+
defaultAdvertised: true
|
|
4961
5825
|
}
|
|
4962
5826
|
];
|
|
4963
5827
|
function buildClientExtensions(input) {
|
|
@@ -5065,7 +5929,7 @@ import {
|
|
|
5065
5929
|
ListResourcesResultSchema,
|
|
5066
5930
|
ListResourceTemplatesResultSchema,
|
|
5067
5931
|
ListPromptsResultSchema,
|
|
5068
|
-
ResourceSchema,
|
|
5932
|
+
ResourceSchema as ResourceSchema2,
|
|
5069
5933
|
ResourceTemplateSchema,
|
|
5070
5934
|
PromptSchema
|
|
5071
5935
|
} from "@modelcontextprotocol/core";
|
|
@@ -5088,7 +5952,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
5088
5952
|
}
|
|
5089
5953
|
|
|
5090
5954
|
// ../../core/mcp/listSalvage.ts
|
|
5091
|
-
import { z as
|
|
5955
|
+
import { z as z3 } from "zod/v4";
|
|
5092
5956
|
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
|
|
5093
5957
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
5094
5958
|
var LIST_MAX_PAGES = 64;
|
|
@@ -5104,20 +5968,20 @@ function isSalvageableRejection(err) {
|
|
|
5104
5968
|
return SdkError2.isInstance(err) && err.code === SdkErrorCode2.InvalidResult;
|
|
5105
5969
|
}
|
|
5106
5970
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
5107
|
-
return resultSchema.extend({ [itemsKey]:
|
|
5971
|
+
return resultSchema.extend({ [itemsKey]: z3.array(z3.unknown()) });
|
|
5108
5972
|
}
|
|
5109
5973
|
function nextCursorOf(page) {
|
|
5110
5974
|
if (typeof page !== "object" || page === null) return void 0;
|
|
5111
5975
|
const value = page.nextCursor;
|
|
5112
5976
|
return typeof value === "string" ? value : void 0;
|
|
5113
5977
|
}
|
|
5114
|
-
var ModernResultEnvelopeSchema =
|
|
5115
|
-
resultType:
|
|
5978
|
+
var ModernResultEnvelopeSchema = z3.looseObject({
|
|
5979
|
+
resultType: z3.literal("complete"),
|
|
5116
5980
|
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
5117
5981
|
// envelope violation the strict path rejects, and accepting it here on the
|
|
5118
5982
|
// strength of an unrelated bad entry would hide it.
|
|
5119
|
-
ttlMs:
|
|
5120
|
-
cacheScope:
|
|
5983
|
+
ttlMs: z3.int().min(0),
|
|
5984
|
+
cacheScope: z3.enum(["public", "private"])
|
|
5121
5985
|
});
|
|
5122
5986
|
function rawItemsOf(page, itemsKey) {
|
|
5123
5987
|
if (typeof page !== "object" || page === null) return void 0;
|
|
@@ -5186,9 +6050,9 @@ var LegacyToolWireSchema = ToolSchema.extend({
|
|
|
5186
6050
|
outputSchema: ToolSchema.shape.inputSchema.optional()
|
|
5187
6051
|
});
|
|
5188
6052
|
var ModernToolWireSchema = ToolSchema.extend({
|
|
5189
|
-
inputSchema:
|
|
5190
|
-
type:
|
|
5191
|
-
$schema:
|
|
6053
|
+
inputSchema: z3.looseObject({
|
|
6054
|
+
type: z3.literal("object"),
|
|
6055
|
+
$schema: z3.string().optional()
|
|
5192
6056
|
})
|
|
5193
6057
|
});
|
|
5194
6058
|
function stripExecution(tool) {
|
|
@@ -5202,10 +6066,10 @@ function eraToolItemSchema(wire, normalize) {
|
|
|
5202
6066
|
|
|
5203
6067
|
// ../../core/mcp/taskNotificationSchemas.ts
|
|
5204
6068
|
import { NotificationSchema } from "@modelcontextprotocol/core";
|
|
5205
|
-
import { z as
|
|
6069
|
+
import { z as z4 } from "zod/v4";
|
|
5206
6070
|
var TasksListChangedNotificationSchema = NotificationSchema.extend({
|
|
5207
|
-
method:
|
|
5208
|
-
params:
|
|
6071
|
+
method: z4.literal("notifications/tasks/list_changed"),
|
|
6072
|
+
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
5209
6073
|
});
|
|
5210
6074
|
|
|
5211
6075
|
// ../../core/mcp/uriTemplate.ts
|
|
@@ -5845,42 +6709,191 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
|
5845
6709
|
};
|
|
5846
6710
|
}
|
|
5847
6711
|
|
|
5848
|
-
// ../../core/auth/
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
6712
|
+
// ../../core/auth/requestTimeout.ts
|
|
6713
|
+
var DEFAULT_OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
6714
|
+
var OAuthRequestTimeoutError = class extends Error {
|
|
6715
|
+
/**
|
|
6716
|
+
* The endpoint that stalled, with sensitive query values redacted.
|
|
6717
|
+
*
|
|
6718
|
+
* Redacted in the constructor rather than at each display site, so every
|
|
6719
|
+
* consumer is covered by construction: this error's message is recorded
|
|
6720
|
+
* verbatim by `createFetchTracker` into the Network log and the persisted
|
|
6721
|
+
* session, and an OAuth endpoint can carry a `code`, an `access_token` or a
|
|
6722
|
+
* `client_secret` in its query string — which `fetchTracking` already
|
|
6723
|
+
* deliberately redacts everywhere else it records a URL (Copilot). The path
|
|
6724
|
+
* and every non-sensitive parameter survive, so the endpoint stays
|
|
6725
|
+
* identifiable, which was the point of naming it.
|
|
6726
|
+
*/
|
|
6727
|
+
url;
|
|
6728
|
+
timeoutMs;
|
|
6729
|
+
constructor(url, timeoutMs) {
|
|
6730
|
+
const safeUrl = redactUrlQuery(url);
|
|
6731
|
+
super(`OAuth request to ${safeUrl} timed out after ${timeoutMs}ms`);
|
|
6732
|
+
this.name = "OAuthRequestTimeoutError";
|
|
6733
|
+
this.url = safeUrl;
|
|
6734
|
+
this.timeoutMs = timeoutMs;
|
|
6735
|
+
}
|
|
6736
|
+
};
|
|
6737
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
6738
|
+
function normalizeBudget(timeoutMs) {
|
|
6739
|
+
if (!Number.isFinite(timeoutMs)) return DEFAULT_OAUTH_REQUEST_TIMEOUT_MS;
|
|
6740
|
+
return Math.min(MAX_TIMER_DELAY_MS, Math.max(0, Math.round(timeoutMs)));
|
|
6741
|
+
}
|
|
6742
|
+
function readNegotiatesEndpoint(hint) {
|
|
6743
|
+
if (!hint) return true;
|
|
5858
6744
|
try {
|
|
5859
|
-
|
|
6745
|
+
return hint();
|
|
5860
6746
|
} catch {
|
|
5861
|
-
return
|
|
6747
|
+
return true;
|
|
5862
6748
|
}
|
|
5863
|
-
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
|
|
5864
|
-
const path5 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
|
|
5865
|
-
if (path5 !== "" && !path5.startsWith("/")) return [];
|
|
5866
|
-
if (path5 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
|
|
5867
|
-
return [
|
|
5868
|
-
new URL(`${OIDC_WELL_KNOWN}${path5}`, url.origin).href,
|
|
5869
|
-
new URL(`${path5}${OIDC_WELL_KNOWN}`, url.origin).href
|
|
5870
|
-
];
|
|
5871
6749
|
}
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
6750
|
+
var OAUTH_FORM_MEDIA_TYPE = "application/x-www-form-urlencoded";
|
|
6751
|
+
function exemptMcpEndpoint(getServerUrl, negotiatesEndpoint) {
|
|
6752
|
+
return (url, headers) => {
|
|
6753
|
+
if ((headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase() === OAUTH_FORM_MEDIA_TYPE) {
|
|
6754
|
+
return false;
|
|
6755
|
+
}
|
|
6756
|
+
let serverUrl;
|
|
6757
|
+
try {
|
|
6758
|
+
serverUrl = getServerUrl();
|
|
6759
|
+
} catch {
|
|
6760
|
+
return true;
|
|
6761
|
+
}
|
|
6762
|
+
if (!serverUrl) return true;
|
|
6763
|
+
const negotiated = readNegotiatesEndpoint(negotiatesEndpoint);
|
|
6764
|
+
try {
|
|
6765
|
+
const a = new URL(url);
|
|
6766
|
+
const b = new URL(serverUrl);
|
|
6767
|
+
if (a.origin !== b.origin) return false;
|
|
6768
|
+
return negotiated || a.pathname === b.pathname;
|
|
6769
|
+
} catch {
|
|
6770
|
+
return true;
|
|
6771
|
+
}
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
var REQUEST_DEADLINES = /* @__PURE__ */ new WeakMap();
|
|
6775
|
+
function requestHeadersOf(input, init) {
|
|
6776
|
+
if (init?.headers) return new Headers(init.headers);
|
|
6777
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
6778
|
+
return new Headers(input.headers);
|
|
6779
|
+
}
|
|
6780
|
+
return new Headers();
|
|
6781
|
+
}
|
|
6782
|
+
async function drainToEnd(reader) {
|
|
6783
|
+
if (!reader) return;
|
|
6784
|
+
for (; ; ) {
|
|
6785
|
+
const { done } = await reader.read();
|
|
6786
|
+
if (done) return;
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
function cancelBoth(reader, response) {
|
|
6790
|
+
void reader?.cancel().catch(() => {
|
|
6791
|
+
});
|
|
6792
|
+
void response?.body?.cancel().catch(() => {
|
|
6793
|
+
});
|
|
6794
|
+
}
|
|
6795
|
+
function requestUrlOf2(input) {
|
|
6796
|
+
if (typeof input === "string") return input;
|
|
6797
|
+
if (input instanceof URL) return input.href;
|
|
6798
|
+
return input.url;
|
|
6799
|
+
}
|
|
6800
|
+
function callerSignalOf(input, init) {
|
|
6801
|
+
const explicit = init?.signal;
|
|
6802
|
+
if (explicit !== void 0) return explicit ?? void 0;
|
|
6803
|
+
if (typeof input !== "string" && !(input instanceof URL)) return input.signal;
|
|
6804
|
+
return void 0;
|
|
6805
|
+
}
|
|
6806
|
+
function withOAuthRequestTimeout(fetchFn, timeoutMs = DEFAULT_OAUTH_REQUEST_TIMEOUT_MS, isExempt) {
|
|
6807
|
+
const budget = normalizeBudget(timeoutMs);
|
|
6808
|
+
return async (input, init) => {
|
|
6809
|
+
const url = requestUrlOf2(input);
|
|
6810
|
+
if (isExempt?.(url, requestHeadersOf(input, init))) {
|
|
6811
|
+
return fetchFn(input, init);
|
|
6812
|
+
}
|
|
6813
|
+
const controller = new AbortController();
|
|
6814
|
+
const callerSignal = callerSignalOf(input, init);
|
|
6815
|
+
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
6816
|
+
const signal = callerSignal ? AbortSignal.any([controller.signal, callerSignal]) : controller.signal;
|
|
6817
|
+
let timer;
|
|
6818
|
+
let timedOut = false;
|
|
6819
|
+
let onCallerAbort;
|
|
6820
|
+
const abandoned = new Promise((_resolve, reject) => {
|
|
6821
|
+
timer = setTimeout(() => {
|
|
6822
|
+
timedOut = true;
|
|
6823
|
+
controller.abort(new OAuthRequestTimeoutError(url, budget));
|
|
6824
|
+
reject(new OAuthRequestTimeoutError(url, budget));
|
|
6825
|
+
}, budget);
|
|
6826
|
+
if (callerSignal) {
|
|
6827
|
+
onCallerAbort = () => reject(callerSignal.reason);
|
|
6828
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
6829
|
+
}
|
|
6830
|
+
});
|
|
6831
|
+
const nextInit = { ...init, signal };
|
|
6832
|
+
REQUEST_DEADLINES.set(nextInit, budget);
|
|
6833
|
+
let settled;
|
|
6834
|
+
let drainReader;
|
|
6835
|
+
try {
|
|
6836
|
+
const response = await Promise.race([
|
|
6837
|
+
fetchFn(input, nextInit),
|
|
6838
|
+
abandoned
|
|
6839
|
+
]);
|
|
6840
|
+
settled = response;
|
|
6841
|
+
const copy = response.clone();
|
|
6842
|
+
drainReader = copy.body?.getReader();
|
|
6843
|
+
await Promise.race([drainToEnd(drainReader), abandoned]);
|
|
6844
|
+
return response;
|
|
6845
|
+
} catch (err) {
|
|
6846
|
+
cancelBoth(drainReader, settled);
|
|
6847
|
+
if (timedOut) throw new OAuthRequestTimeoutError(url, budget);
|
|
6848
|
+
throw err;
|
|
6849
|
+
} finally {
|
|
6850
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
6851
|
+
if (onCallerAbort && callerSignal) {
|
|
6852
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6855
|
+
};
|
|
6856
|
+
}
|
|
6857
|
+
|
|
6858
|
+
// ../../core/auth/oidcDiscoveryCompat.ts
|
|
6859
|
+
import {
|
|
6860
|
+
OAuthMetadataSchema,
|
|
6861
|
+
OpenIdProviderDiscoveryMetadataSchema
|
|
6862
|
+
} from "@modelcontextprotocol/core";
|
|
6863
|
+
var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
6864
|
+
var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
|
|
6865
|
+
var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
|
|
6866
|
+
function oidcDiscoveryCandidates(rfc8414Url) {
|
|
6867
|
+
let url;
|
|
6868
|
+
try {
|
|
6869
|
+
url = new URL(rfc8414Url);
|
|
6870
|
+
} catch {
|
|
6871
|
+
return [];
|
|
6872
|
+
}
|
|
6873
|
+
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
|
|
6874
|
+
const path5 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
|
|
6875
|
+
if (path5 !== "" && !path5.startsWith("/")) return [];
|
|
6876
|
+
if (path5 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
|
|
6877
|
+
return [
|
|
6878
|
+
new URL(`${OIDC_WELL_KNOWN}${path5}`, url.origin).href,
|
|
6879
|
+
new URL(`${path5}${OIDC_WELL_KNOWN}`, url.origin).href
|
|
6880
|
+
];
|
|
6881
|
+
}
|
|
6882
|
+
function continuesDiscovery(status) {
|
|
6883
|
+
return status >= 400 && status < 500 || status === 502;
|
|
6884
|
+
}
|
|
6885
|
+
async function releaseBody(response) {
|
|
6886
|
+
await response.body?.cancel().catch(() => {
|
|
6887
|
+
});
|
|
6888
|
+
}
|
|
6889
|
+
function releaseBodyDetached(response) {
|
|
6890
|
+
void releaseBody(response);
|
|
6891
|
+
}
|
|
6892
|
+
function isRfc8414OnlyMetadata(value) {
|
|
5880
6893
|
if (!OAuthMetadataSchema.safeParse(value).success) return false;
|
|
5881
6894
|
return !OpenIdProviderDiscoveryMetadataSchema.safeParse(value).success;
|
|
5882
6895
|
}
|
|
5883
|
-
function
|
|
6896
|
+
function requestUrlOf3(input) {
|
|
5884
6897
|
if (typeof input === "string") return input;
|
|
5885
6898
|
if (input instanceof URL) return input.href;
|
|
5886
6899
|
return input.url;
|
|
@@ -5905,18 +6918,35 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
5905
6918
|
if (response.ok) return response;
|
|
5906
6919
|
if (!continuesDiscovery(response.status)) return response;
|
|
5907
6920
|
if (requestMethodOf(input, init) !== "GET") return response;
|
|
5908
|
-
const candidates = oidcDiscoveryCandidates(
|
|
6921
|
+
const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
|
|
5909
6922
|
if (candidates.length === 0) return response;
|
|
5910
6923
|
const headers = discoveryHeaders(input, init);
|
|
6924
|
+
const callerSignal = callerSignalOf(input, init);
|
|
5911
6925
|
for (const candidate of candidates) {
|
|
6926
|
+
if (callerSignal?.aborted) {
|
|
6927
|
+
releaseBodyDetached(response);
|
|
6928
|
+
throw callerSignal.reason;
|
|
6929
|
+
}
|
|
5912
6930
|
let probe;
|
|
5913
6931
|
try {
|
|
5914
|
-
probe = await fetchFn(candidate, { headers });
|
|
5915
|
-
} catch {
|
|
6932
|
+
probe = await fetchFn(candidate, { headers, signal: callerSignal });
|
|
6933
|
+
} catch (err) {
|
|
6934
|
+
if (err instanceof OAuthRequestTimeoutError) {
|
|
6935
|
+
releaseBodyDetached(response);
|
|
6936
|
+
throw err;
|
|
6937
|
+
}
|
|
6938
|
+
if (callerSignal?.aborted) {
|
|
6939
|
+
releaseBodyDetached(response);
|
|
6940
|
+
throw callerSignal.reason;
|
|
6941
|
+
}
|
|
5916
6942
|
return response;
|
|
5917
6943
|
}
|
|
5918
6944
|
if (!probe.ok) {
|
|
5919
6945
|
await releaseBody(probe);
|
|
6946
|
+
if (callerSignal?.aborted) {
|
|
6947
|
+
releaseBodyDetached(response);
|
|
6948
|
+
throw callerSignal.reason;
|
|
6949
|
+
}
|
|
5920
6950
|
if (continuesDiscovery(probe.status)) continue;
|
|
5921
6951
|
return response;
|
|
5922
6952
|
}
|
|
@@ -5926,6 +6956,10 @@ function withRfc8414OidcCompat(fetchFn) {
|
|
|
5926
6956
|
body = await probe.text();
|
|
5927
6957
|
parsed = JSON.parse(body);
|
|
5928
6958
|
} catch {
|
|
6959
|
+
if (callerSignal?.aborted) {
|
|
6960
|
+
releaseBodyDetached(response);
|
|
6961
|
+
throw callerSignal.reason;
|
|
6962
|
+
}
|
|
5929
6963
|
return response;
|
|
5930
6964
|
}
|
|
5931
6965
|
if (!isRfc8414OnlyMetadata(parsed)) {
|
|
@@ -5973,6 +7007,82 @@ function createSilentLogger() {
|
|
|
5973
7007
|
}
|
|
5974
7008
|
var silentLogger = createSilentLogger();
|
|
5975
7009
|
|
|
7010
|
+
// ../../core/mcp/inspectorClient.ts
|
|
7011
|
+
import { SdkError as SdkError5, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
|
|
7012
|
+
|
|
7013
|
+
// ../../core/mcp/connectionDiagnostics.ts
|
|
7014
|
+
import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
|
|
7015
|
+
function isRequestTimeoutData(value) {
|
|
7016
|
+
return typeof value === "object" && value !== null && typeof value.timeout === "number";
|
|
7017
|
+
}
|
|
7018
|
+
function isRequestTimeoutError(err) {
|
|
7019
|
+
return SdkError3.isInstance(err) && err.code === SdkErrorCode3.RequestTimeout && isRequestTimeoutData(err.data);
|
|
7020
|
+
}
|
|
7021
|
+
function formatDuration(ms) {
|
|
7022
|
+
const clamped = Math.max(0, ms);
|
|
7023
|
+
if (clamped < 1e3) return `${Math.round(clamped)}ms`;
|
|
7024
|
+
const totalSeconds = Math.round(clamped / 1e3);
|
|
7025
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
7026
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
7027
|
+
const seconds = totalSeconds % 60;
|
|
7028
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
7029
|
+
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
7030
|
+
return `${seconds}s`;
|
|
7031
|
+
}
|
|
7032
|
+
var MAX_LISTED_OUTSTANDING_REQUESTS = 5;
|
|
7033
|
+
function describeStreamEndpoint(url) {
|
|
7034
|
+
try {
|
|
7035
|
+
return `GET ${new URL(url).pathname}`;
|
|
7036
|
+
} catch {
|
|
7037
|
+
return `GET ${url}`;
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
function plural(count, noun) {
|
|
7041
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
7042
|
+
}
|
|
7043
|
+
function describeOutstanding(requests, now) {
|
|
7044
|
+
if (requests.length === 0) return "No requests are unanswered.";
|
|
7045
|
+
const listed = requests.slice(0, MAX_LISTED_OUTSTANDING_REQUESTS).map((r) => `${r.method} (sent ${formatDuration(now - r.sentAt)} ago)`);
|
|
7046
|
+
const rest = requests.length - listed.length;
|
|
7047
|
+
const tail = rest > 0 ? `, and ${rest} more` : "";
|
|
7048
|
+
const verb = requests.length === 1 ? "is" : "are";
|
|
7049
|
+
return `${plural(requests.length, "request")} ${verb} unanswered: ${listed.join(", ")}${tail}.`;
|
|
7050
|
+
}
|
|
7051
|
+
function describeLastResponse(lastResponse, now) {
|
|
7052
|
+
if (!lastResponse) return "No response has been received this session.";
|
|
7053
|
+
return `Last response received ${formatDuration(now - lastResponse.receivedAt)} ago (${lastResponse.method}).`;
|
|
7054
|
+
}
|
|
7055
|
+
function describeStream(stream, now) {
|
|
7056
|
+
if (!stream) return void 0;
|
|
7057
|
+
const endpoint = describeStreamEndpoint(stream.url);
|
|
7058
|
+
const events = plural(stream.eventCount, "event");
|
|
7059
|
+
if (stream.closedAt !== void 0) {
|
|
7060
|
+
return `Notification stream (${endpoint}) closed ${formatDuration(now - stream.closedAt)} ago after ${formatDuration(stream.closedAt - stream.openedAt)}, ${events} delivered.`;
|
|
7061
|
+
}
|
|
7062
|
+
return `Notification stream (${endpoint}) open for ${formatDuration(now - stream.openedAt)}, ${events} delivered.`;
|
|
7063
|
+
}
|
|
7064
|
+
function describeConnectionDiagnostics(diagnostics) {
|
|
7065
|
+
const now = diagnostics.capturedAt;
|
|
7066
|
+
const parts = [
|
|
7067
|
+
describeOutstanding(diagnostics.outstandingRequests, now),
|
|
7068
|
+
describeLastResponse(diagnostics.lastResponse, now),
|
|
7069
|
+
describeStream(diagnostics.notificationStream, now)
|
|
7070
|
+
];
|
|
7071
|
+
return parts.filter((p) => p !== void 0).join(" ");
|
|
7072
|
+
}
|
|
7073
|
+
function annotateRequestTimeout(err, method, diagnostics) {
|
|
7074
|
+
if (!isRequestTimeoutError(err)) return err;
|
|
7075
|
+
const data = {
|
|
7076
|
+
...err.data,
|
|
7077
|
+
method,
|
|
7078
|
+
diagnostics
|
|
7079
|
+
};
|
|
7080
|
+
const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
|
|
7081
|
+
const annotated = new SdkError3(SdkErrorCode3.RequestTimeout, message, data);
|
|
7082
|
+
annotated.cause = err;
|
|
7083
|
+
return annotated;
|
|
7084
|
+
}
|
|
7085
|
+
|
|
5976
7086
|
// ../../core/auth/authorizationParams.ts
|
|
5977
7087
|
var RESERVED_AUTHORIZATION_PARAMS = [
|
|
5978
7088
|
"client_id",
|
|
@@ -6186,8 +7296,8 @@ var BaseOAuthClientProvider = class {
|
|
|
6186
7296
|
);
|
|
6187
7297
|
}
|
|
6188
7298
|
async saveClientInformation(clientInformation, options) {
|
|
6189
|
-
const registrationKind = options && "registrationKind" in options ? options.registrationKind : "dcr";
|
|
6190
7299
|
const issuer = options && "issuer" in options ? options.issuer : void 0;
|
|
7300
|
+
const registrationKind = options && "registrationKind" in options ? options.registrationKind : await this.resolveSdkRegistrationKind(clientInformation, issuer);
|
|
6191
7301
|
await this.storage.saveClientInformation(
|
|
6192
7302
|
this.serverUrl,
|
|
6193
7303
|
clientInformation,
|
|
@@ -6197,6 +7307,70 @@ var BaseOAuthClientProvider = class {
|
|
|
6197
7307
|
}
|
|
6198
7308
|
);
|
|
6199
7309
|
}
|
|
7310
|
+
/**
|
|
7311
|
+
* Resolve the registration kind for a save that carries no explicit one — that
|
|
7312
|
+
* is, one the SDK made. SDK v2's `saveClientInformation` contract passes only
|
|
7313
|
+
* `{ issuer }`, so the mechanism cannot be handed to us; treating every such
|
|
7314
|
+
* save as DCR is what relabeled a CIMD registration `Dynamic (DCR)` in
|
|
7315
|
+
* Connection Info the moment the SDK bound it to an issuer (#2242).
|
|
7316
|
+
*
|
|
7317
|
+
* Two cases reach here, and they are told apart by whether a registration
|
|
7318
|
+
* already exists for this issuer:
|
|
7319
|
+
*
|
|
7320
|
+
* - **A back-stamp.** A registration is already stored for this issuer under
|
|
7321
|
+
* this `client_id`, and the SDK is only adding the `issuer` to it. Its
|
|
7322
|
+
* recorded kind is the answer — kind and credential are written and cleared
|
|
7323
|
+
* together, so a stored registration always has one.
|
|
7324
|
+
* - **A new registration.** Nothing is stored for this issuer, so this save
|
|
7325
|
+
* creates it. SDK v2 `auth()` reaches its URL-based-client-ID branch — rather
|
|
7326
|
+
* than `registerClient` — exactly when the AS advertises
|
|
7327
|
+
* `client_id_metadata_document_supported` and a `clientMetadataUrl` is
|
|
7328
|
+
* configured, and it persists the AS metadata via `saveDiscoveryState`
|
|
7329
|
+
* *before* it reads or writes client information. So the branch it took is
|
|
7330
|
+
* not inferred here, it is read back from the state it just wrote.
|
|
7331
|
+
*
|
|
7332
|
+
* This is why the check is not "the `client_id` looks like our metadata URL".
|
|
7333
|
+
* RFC 7591 §3.2 makes a dynamically issued `client_id` opaque, so an AS may
|
|
7334
|
+
* mint that very URL from `POST /register`; the URL comparison only decides
|
|
7335
|
+
* whether CIMD is *in play* for this connection, and the two cases above decide
|
|
7336
|
+
* what actually happened (#2242, Copilot).
|
|
7337
|
+
*
|
|
7338
|
+
* Consequences worth stating, since each was a defect on the way here:
|
|
7339
|
+
*
|
|
7340
|
+
* - An existing DCR whose `client_id` happens to be the metadata URL stays
|
|
7341
|
+
* `dcr` — it takes the back-stamp path and its recorded kind says so.
|
|
7342
|
+
* - `invalidateCredentials("client")`, which SDK `auth()` calls on
|
|
7343
|
+
* `invalid_client` before retrying, clears the registration and its kind. The
|
|
7344
|
+
* retry therefore takes the new-registration path and is answered from
|
|
7345
|
+
* discovery state, which that clear does not touch.
|
|
7346
|
+
* - A second AS behind one resource gets its own answer, since discovery state
|
|
7347
|
+
* describes the issuer the SDK actually resolved. One that does not advertise
|
|
7348
|
+
* CIMD is `dcr` even when it mints the metadata URL as its `client_id`.
|
|
7349
|
+
* - A transient failure in our own CIMD preflight costs nothing: the SDK's own
|
|
7350
|
+
* discovery is what this reads.
|
|
7351
|
+
*/
|
|
7352
|
+
async resolveSdkRegistrationKind(clientInformation, issuer) {
|
|
7353
|
+
const clientMetadataUrl = this.clientMetadataUrl?.trim();
|
|
7354
|
+
if (!clientMetadataUrl || clientInformation.client_id !== clientMetadataUrl) {
|
|
7355
|
+
return "dcr";
|
|
7356
|
+
}
|
|
7357
|
+
const stored = await this.storage.getClientInformation(
|
|
7358
|
+
this.serverUrl,
|
|
7359
|
+
false,
|
|
7360
|
+
issuer
|
|
7361
|
+
);
|
|
7362
|
+
if (stored?.client_id === clientMetadataUrl) {
|
|
7363
|
+
const storedKind = await this.storage.getClientRegistrationKind(
|
|
7364
|
+
this.serverUrl,
|
|
7365
|
+
issuer
|
|
7366
|
+
);
|
|
7367
|
+
return storedKind === "cimd" ? "cimd" : "dcr";
|
|
7368
|
+
}
|
|
7369
|
+
const discovery = await this.storage.getDiscoveryState(this.serverUrl);
|
|
7370
|
+
const metadata = discovery?.authorizationServerMetadata;
|
|
7371
|
+
if (issuer !== void 0 && metadata?.issuer !== issuer) return "dcr";
|
|
7372
|
+
return metadata?.client_id_metadata_document_supported === true ? "cimd" : "dcr";
|
|
7373
|
+
}
|
|
6200
7374
|
async saveScope(scope) {
|
|
6201
7375
|
await this.storage.saveScope(this.serverUrl, scope);
|
|
6202
7376
|
this.cachedScope = scope;
|
|
@@ -7075,35 +8249,49 @@ import { discoverOAuthProtectedResourceMetadata as discoverOAuthProtectedResourc
|
|
|
7075
8249
|
async function ensureCimdClientRegistration(params) {
|
|
7076
8250
|
const clientMetadataUrl = params.provider.clientMetadataUrl?.trim();
|
|
7077
8251
|
if (!clientMetadataUrl) return;
|
|
7078
|
-
|
|
7079
|
-
if (
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
8252
|
+
let metadata = (await params.provider.discoveryState())?.authorizationServerMetadata;
|
|
8253
|
+
if (!metadata) {
|
|
8254
|
+
let resourceMetadata;
|
|
8255
|
+
try {
|
|
8256
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata2(
|
|
8257
|
+
params.serverUrl,
|
|
8258
|
+
{ resourceMetadataUrl: params.resourceMetadataUrl },
|
|
8259
|
+
// The same fetch the AS-metadata leg below uses. On web that is
|
|
8260
|
+
// `createRemoteFetch`, which proxies through the backend to sidestep
|
|
8261
|
+
// CORS — on the global `fetch` this leg would fail in the browser, be
|
|
8262
|
+
// swallowed by the catch, and leave CIMD probing the wrong
|
|
8263
|
+
// authorization server (Copilot).
|
|
8264
|
+
params.fetchFn
|
|
8265
|
+
);
|
|
8266
|
+
} catch {
|
|
8267
|
+
resourceMetadata = void 0;
|
|
8268
|
+
}
|
|
8269
|
+
try {
|
|
8270
|
+
metadata = await discoverAuthorizationServerMetadataForServer(
|
|
8271
|
+
params.serverUrl,
|
|
8272
|
+
resourceMetadata,
|
|
8273
|
+
params.fetchFn
|
|
8274
|
+
);
|
|
8275
|
+
} catch {
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
7094
8278
|
}
|
|
7095
|
-
const
|
|
7096
|
-
params.serverUrl,
|
|
7097
|
-
resourceMetadata,
|
|
7098
|
-
params.fetchFn
|
|
7099
|
-
);
|
|
8279
|
+
const issuer = metadata?.issuer;
|
|
7100
8280
|
if (!metadata?.client_id_metadata_document_supported) return;
|
|
7101
|
-
const
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
8281
|
+
const existing = await params.provider.clientInformation(
|
|
8282
|
+
issuer ? { issuer } : void 0
|
|
8283
|
+
);
|
|
8284
|
+
if (existing?.client_id) return;
|
|
8285
|
+
await params.provider.saveClientInformation(
|
|
8286
|
+
{ client_id: clientMetadataUrl },
|
|
8287
|
+
{
|
|
8288
|
+
registrationKind: "cimd",
|
|
8289
|
+
// An AS metadata document without an `issuer` is malformed (RFC 8414 §2),
|
|
8290
|
+
// but the type allows it; fall back to the unkeyed slot rather than
|
|
8291
|
+
// inventing a key.
|
|
8292
|
+
...issuer && { issuer }
|
|
8293
|
+
}
|
|
8294
|
+
);
|
|
7107
8295
|
}
|
|
7108
8296
|
|
|
7109
8297
|
// ../../core/auth/ema/transportProvider.ts
|
|
@@ -7130,11 +8318,26 @@ var EmaTransportOAuthProvider = class {
|
|
|
7130
8318
|
state() {
|
|
7131
8319
|
return this.inner.state();
|
|
7132
8320
|
}
|
|
7133
|
-
|
|
7134
|
-
|
|
8321
|
+
// SEP-2352: `ctx` carries the authorization-server `issuer` the SDK resolved,
|
|
8322
|
+
// and the inner provider keys registrations by it. Dropping it here made every
|
|
8323
|
+
// EMA read and write land on the unkeyed slot — and, since #2242, left
|
|
8324
|
+
// `resolveSdkRegistrationKind` with no issuer to check, so a CIMD registration
|
|
8325
|
+
// made over an EMA connection was recorded as DCR (Copilot).
|
|
8326
|
+
clientInformation(ctx) {
|
|
8327
|
+
return this.inner.clientInformation(ctx);
|
|
8328
|
+
}
|
|
8329
|
+
saveClientInformation(clientInformation, ctx) {
|
|
8330
|
+
return this.inner.saveClientInformation(clientInformation, ctx);
|
|
7135
8331
|
}
|
|
7136
|
-
|
|
7137
|
-
|
|
8332
|
+
// Without these the SDK persists no discovery state for an EMA connection, so
|
|
8333
|
+
// it re-discovers on every call, cannot perform its SEP-2352 callback-leg
|
|
8334
|
+
// authorization-server binding check (it warns as much), and — since #2242 —
|
|
8335
|
+
// leaves the registration-kind resolver nothing to read back.
|
|
8336
|
+
saveDiscoveryState(state) {
|
|
8337
|
+
return this.inner.saveDiscoveryState(state);
|
|
8338
|
+
}
|
|
8339
|
+
discoveryState() {
|
|
8340
|
+
return this.inner.discoveryState();
|
|
7138
8341
|
}
|
|
7139
8342
|
async tokens() {
|
|
7140
8343
|
const stored = await this.inner.tokens();
|
|
@@ -7203,8 +8406,11 @@ function issuerMismatchTitle() {
|
|
|
7203
8406
|
return "Authorization server mismatch";
|
|
7204
8407
|
}
|
|
7205
8408
|
var MAX_DISPLAYED_ISSUER_LENGTH = 120;
|
|
8409
|
+
function truncateUrlForDisplay(url) {
|
|
8410
|
+
return url.length > MAX_DISPLAYED_ISSUER_LENGTH ? `${url.slice(0, MAX_DISPLAYED_ISSUER_LENGTH)}\u2026` : url;
|
|
8411
|
+
}
|
|
7206
8412
|
function truncateIssuerForDisplay(issuer) {
|
|
7207
|
-
return issuer
|
|
8413
|
+
return truncateUrlForDisplay(issuer);
|
|
7208
8414
|
}
|
|
7209
8415
|
function issuerMismatchMessage(options) {
|
|
7210
8416
|
const target = options.serverName ? `"${options.serverName}"` : "this server";
|
|
@@ -7994,6 +9200,9 @@ var OAuthManager = class {
|
|
|
7994
9200
|
}
|
|
7995
9201
|
};
|
|
7996
9202
|
|
|
9203
|
+
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
9204
|
+
import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
|
|
9205
|
+
|
|
7997
9206
|
// ../../core/mcp/remote/types.ts
|
|
7998
9207
|
function oauthTokensToRemoteAuthState(tokens) {
|
|
7999
9208
|
return {
|
|
@@ -8008,6 +9217,20 @@ function oauthTokensToRemoteAuthState(tokens) {
|
|
|
8008
9217
|
};
|
|
8009
9218
|
}
|
|
8010
9219
|
|
|
9220
|
+
// ../../core/mcp/remote/progressToken.ts
|
|
9221
|
+
import { ProgressTokenSchema } from "@modelcontextprotocol/core";
|
|
9222
|
+
function progressTokenOf(message) {
|
|
9223
|
+
if ("method" in message && message.method === "notifications/progress" && "params" in message && typeof message.params === "object" && message.params !== null) {
|
|
9224
|
+
const parsed = ProgressTokenSchema.safeParse(
|
|
9225
|
+
message.params.progressToken
|
|
9226
|
+
);
|
|
9227
|
+
if (parsed.success) {
|
|
9228
|
+
return parsed.data;
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
9231
|
+
return void 0;
|
|
9232
|
+
}
|
|
9233
|
+
|
|
8011
9234
|
// ../../core/mcp/remote/remoteClientTransport.ts
|
|
8012
9235
|
var DEFAULT_SSE_RESPONSE_TIMEOUT_MS = 6e4;
|
|
8013
9236
|
function requestIdForMessage(message) {
|
|
@@ -8079,6 +9302,14 @@ var RemoteClientTransport = class {
|
|
|
8079
9302
|
eventStreamConsumeTask = null;
|
|
8080
9303
|
restartingEventStream = false;
|
|
8081
9304
|
closed = false;
|
|
9305
|
+
/**
|
|
9306
|
+
* Long-lived streams the backend has reported open, by fetch entry id, with
|
|
9307
|
+
* the last event count seen for each. `close()` reports them closed
|
|
9308
|
+
* itself: it aborts the event channel before asking the backend to
|
|
9309
|
+
* disconnect, so the backend watcher's own close report lands on an
|
|
9310
|
+
* unconsumed queue and is lost with the session (#2318).
|
|
9311
|
+
*/
|
|
9312
|
+
openStreams = /* @__PURE__ */ new Map();
|
|
8082
9313
|
sseResponseWaits = /* @__PURE__ */ new Map();
|
|
8083
9314
|
options;
|
|
8084
9315
|
config;
|
|
@@ -8301,10 +9532,20 @@ var RemoteClientTransport = class {
|
|
|
8301
9532
|
if (parsed.type === "message") {
|
|
8302
9533
|
const msg = parsed.data;
|
|
8303
9534
|
this.settleSseResponseWait(msg);
|
|
9535
|
+
const progressToken = progressTokenOf(msg);
|
|
9536
|
+
if (progressToken !== void 0) {
|
|
9537
|
+
this.resetSseResponseWait(progressToken);
|
|
9538
|
+
}
|
|
8304
9539
|
this.onmessage?.(msg, void 0);
|
|
8305
|
-
} else if (parsed.type === "fetch_request"
|
|
9540
|
+
} else if (parsed.type === "fetch_request") {
|
|
8306
9541
|
const entry = parsed.data;
|
|
8307
|
-
this.options.
|
|
9542
|
+
if (this.options.onFetchStreamUpdate && isLongLivedStreamResponse(
|
|
9543
|
+
entry.method,
|
|
9544
|
+
findHeader(entry.responseHeaders, "content-type")
|
|
9545
|
+
)) {
|
|
9546
|
+
this.openStreams.set(entry.id, 0);
|
|
9547
|
+
}
|
|
9548
|
+
this.options.onFetchRequest?.({
|
|
8308
9549
|
...entry,
|
|
8309
9550
|
timestamp: typeof entry.timestamp === "string" ? new Date(entry.timestamp) : entry.timestamp
|
|
8310
9551
|
});
|
|
@@ -8313,6 +9554,16 @@ var RemoteClientTransport = class {
|
|
|
8313
9554
|
parsed.data.id,
|
|
8314
9555
|
parsed.data.responseBody
|
|
8315
9556
|
);
|
|
9557
|
+
} else if (parsed.type === "fetch_stream_update" && this.options.onFetchStreamUpdate) {
|
|
9558
|
+
const { id, eventCount, closedAt } = parsed.data;
|
|
9559
|
+
if (closedAt !== void 0) this.openStreams.delete(id);
|
|
9560
|
+
else if (this.openStreams.has(id)) {
|
|
9561
|
+
this.openStreams.set(id, eventCount);
|
|
9562
|
+
}
|
|
9563
|
+
this.options.onFetchStreamUpdate(id, {
|
|
9564
|
+
eventCount,
|
|
9565
|
+
...closedAt !== void 0 && { closedAt: new Date(closedAt) }
|
|
9566
|
+
});
|
|
8316
9567
|
} else if (parsed.type === "stdio_log" && this.options.onStderr) {
|
|
8317
9568
|
this.options.onStderr({
|
|
8318
9569
|
timestamp: new Date(parsed.data.timestamp),
|
|
@@ -8423,14 +9674,18 @@ var RemoteClientTransport = class {
|
|
|
8423
9674
|
}
|
|
8424
9675
|
waitForSseResponse(requestId) {
|
|
8425
9676
|
return new Promise((resolve7, reject) => {
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
9677
|
+
let timer;
|
|
9678
|
+
const arm = () => {
|
|
9679
|
+
timer = setTimeout(() => {
|
|
9680
|
+
this.sseResponseWaits.delete(requestId);
|
|
9681
|
+
reject(
|
|
9682
|
+
new SdkError4(SdkErrorCode4.RequestTimeout, "Request timed out", {
|
|
9683
|
+
timeout: this.sseResponseTimeoutMs
|
|
9684
|
+
})
|
|
9685
|
+
);
|
|
9686
|
+
}, this.sseResponseTimeoutMs);
|
|
9687
|
+
};
|
|
9688
|
+
arm();
|
|
8434
9689
|
this.sseResponseWaits.set(requestId, {
|
|
8435
9690
|
resolve: () => {
|
|
8436
9691
|
clearTimeout(timer);
|
|
@@ -8439,10 +9694,21 @@ var RemoteClientTransport = class {
|
|
|
8439
9694
|
reject: (error) => {
|
|
8440
9695
|
clearTimeout(timer);
|
|
8441
9696
|
reject(error);
|
|
9697
|
+
},
|
|
9698
|
+
// A progress notification for this request re-arms the full window so
|
|
9699
|
+
// the browser-side deadline tracks the SDK client's progress-aware one
|
|
9700
|
+
// rather than firing at a flat 60s (#2028).
|
|
9701
|
+
resetTimeout: () => {
|
|
9702
|
+
clearTimeout(timer);
|
|
9703
|
+
arm();
|
|
8442
9704
|
}
|
|
8443
9705
|
});
|
|
8444
9706
|
});
|
|
8445
9707
|
}
|
|
9708
|
+
/** Re-arm a pending request's SSE wait timeout on a matching progress note. */
|
|
9709
|
+
resetSseResponseWait(requestId) {
|
|
9710
|
+
this.sseResponseWaits.get(requestId)?.resetTimeout();
|
|
9711
|
+
}
|
|
8446
9712
|
async postSend(message, options, retryCount) {
|
|
8447
9713
|
const requestId = requestIdForMessage(message);
|
|
8448
9714
|
let sseWait;
|
|
@@ -8525,6 +9791,11 @@ var RemoteClientTransport = class {
|
|
|
8525
9791
|
this.closed = true;
|
|
8526
9792
|
this.cancelAllSseWaits(new Error("Transport closed"));
|
|
8527
9793
|
await this.stopEventStream();
|
|
9794
|
+
const closedAt = /* @__PURE__ */ new Date();
|
|
9795
|
+
for (const [id, eventCount] of this.openStreams) {
|
|
9796
|
+
this.options.onFetchStreamUpdate?.(id, { eventCount, closedAt });
|
|
9797
|
+
}
|
|
9798
|
+
this.openStreams.clear();
|
|
8528
9799
|
if (this._sessionId) {
|
|
8529
9800
|
try {
|
|
8530
9801
|
await this.fetchFn(`${this.baseUrl}/api/mcp/disconnect`, {
|
|
@@ -8549,6 +9820,9 @@ var corePackageJson = {
|
|
|
8549
9820
|
version: "0.0.0"
|
|
8550
9821
|
};
|
|
8551
9822
|
var MAX_URL_ELICITATION_RETRIES = 5;
|
|
9823
|
+
function connectionTimeoutMessage(timeoutMs) {
|
|
9824
|
+
return `Connection timed out after ${timeoutMs} ms. To accommodate a slower server, you may increase the timeout value in Server Settings.`;
|
|
9825
|
+
}
|
|
8552
9826
|
function createPendingAbortError() {
|
|
8553
9827
|
return new Error("Pending request aborted");
|
|
8554
9828
|
}
|
|
@@ -8560,20 +9834,13 @@ async function closeSubscriptionBestEffort(subscription) {
|
|
|
8560
9834
|
} catch {
|
|
8561
9835
|
}
|
|
8562
9836
|
}
|
|
8563
|
-
function notificationMethodFromSchema(schema) {
|
|
8564
|
-
if (schema !== null && typeof schema === "object") {
|
|
8565
|
-
const literal = schema.shape?.method?.value;
|
|
8566
|
-
if (typeof literal === "string") return literal;
|
|
8567
|
-
}
|
|
8568
|
-
return void 0;
|
|
8569
|
-
}
|
|
8570
9837
|
function describeJsonType2(value) {
|
|
8571
9838
|
if (value === null) return "null";
|
|
8572
9839
|
if (Array.isArray(value)) return "array";
|
|
8573
9840
|
return typeof value;
|
|
8574
9841
|
}
|
|
8575
9842
|
function isProgressToken(value) {
|
|
8576
|
-
return
|
|
9843
|
+
return ProgressTokenSchema2.safeParse(value).success;
|
|
8577
9844
|
}
|
|
8578
9845
|
function dropInvalidReservedMeta(meta, logger) {
|
|
8579
9846
|
const result = RequestMetaSchema.safeParse(meta);
|
|
@@ -8603,19 +9870,28 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
8603
9870
|
*/
|
|
8604
9871
|
static MRTR_MAX_ROUNDS = 10;
|
|
8605
9872
|
client = null;
|
|
8606
|
-
appRendererClientProxy = null;
|
|
8607
9873
|
// Lazily-built validator used only on the skipOutputValidation path to detect
|
|
8608
9874
|
// (non-fatally) when a delivered result violates the tool's outputSchema.
|
|
8609
9875
|
outputValidator = null;
|
|
8610
9876
|
transport = null;
|
|
8611
9877
|
baseTransport = null;
|
|
8612
|
-
//
|
|
8613
|
-
//
|
|
8614
|
-
// the
|
|
8615
|
-
//
|
|
8616
|
-
//
|
|
8617
|
-
|
|
9878
|
+
// Every outbound request still awaiting a response, keyed by JSON-RPC id.
|
|
9879
|
+
// Serves two readers: the `markResponseRejected` correlation (#1953), which
|
|
9880
|
+
// needs the method, and the connection diagnostics (#2318), which need the
|
|
9881
|
+
// method and when it went out. Entries are dropped as responses arrive; a
|
|
9882
|
+
// request the SDK gave up on (a timeout, a cancel) stays until the session
|
|
9883
|
+
// resets, because it is still unanswered and that is what the diagnostics
|
|
9884
|
+
// report.
|
|
9885
|
+
outstandingRequests = /* @__PURE__ */ new Map();
|
|
9886
|
+
// The id of the most recently answered request per method, for
|
|
9887
|
+
// `markResponseRejected` (#1953). At most one id per method, so it does not
|
|
9888
|
+
// grow with the session.
|
|
8618
9889
|
lastAnsweredRequestByMethod = /* @__PURE__ */ new Map();
|
|
9890
|
+
// Connection diagnostics (#2318): the most recent response to one of our
|
|
9891
|
+
// requests, and the most recent long-lived transport stream (the standalone
|
|
9892
|
+
// `GET` on Streamable HTTP). Both per-session, reset with the maps above.
|
|
9893
|
+
lastResponse;
|
|
9894
|
+
notificationStream;
|
|
8619
9895
|
/** True when the cached transport was built with an OAuth authProvider attached. */
|
|
8620
9896
|
transportHasAuthProvider = false;
|
|
8621
9897
|
/** Dedupes concurrent ambient auth challenges (reason + scopes). */
|
|
@@ -8883,12 +10159,27 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
8883
10159
|
resources: options.listChangedNotifications?.resources ?? true,
|
|
8884
10160
|
prompts: options.listChangedNotifications?.prompts ?? true
|
|
8885
10161
|
};
|
|
8886
|
-
|
|
10162
|
+
const withOverrides = withOAuthEndpointOverrides(
|
|
8887
10163
|
this.fetchFn ?? fetch,
|
|
8888
10164
|
() => this.oauthManager?.getEndpointOverrides()
|
|
8889
10165
|
);
|
|
8890
|
-
this.fetchFn = withRfc8414OidcCompat(
|
|
8891
|
-
|
|
10166
|
+
this.fetchFn = withRfc8414OidcCompat(
|
|
10167
|
+
withOAuthRequestTimeout(
|
|
10168
|
+
withOverrides,
|
|
10169
|
+
DEFAULT_OAUTH_REQUEST_TIMEOUT_MS,
|
|
10170
|
+
exemptMcpEndpoint(
|
|
10171
|
+
() => this.getServerUrl(),
|
|
10172
|
+
// Legacy SSE negotiates its message endpoint inside the stream, so
|
|
10173
|
+
// its path is unknowable here and the exemption has to widen to the
|
|
10174
|
+
// origin. Streamable HTTP sends everything to the configured URL, so
|
|
10175
|
+
// the path rule is exact there and same-origin OAuth stays bounded.
|
|
10176
|
+
() => this.transportConfig?.type === "sse"
|
|
10177
|
+
)
|
|
10178
|
+
)
|
|
10179
|
+
);
|
|
10180
|
+
this.effectiveAuthFetch = this.buildEffectiveAuthFetch(
|
|
10181
|
+
withRfc8414OidcCompat(withOAuthRequestTimeout(withOverrides))
|
|
10182
|
+
);
|
|
8892
10183
|
this.sessionId = options.sessionId;
|
|
8893
10184
|
if (options.oauth || options.environment.oauth) {
|
|
8894
10185
|
const oauthConfig = {
|
|
@@ -8996,7 +10287,6 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
8996
10287
|
this.tasksCapabilityAdvertised = capabilities.tasks !== void 0;
|
|
8997
10288
|
this.urlElicitationCapabilityAdvertised = capabilities.elicitation?.url !== void 0;
|
|
8998
10289
|
this.rootsListChangedCapabilityAdvertised = capabilities.roots?.listChanged === true;
|
|
8999
|
-
this.appRendererClientProxy = null;
|
|
9000
10290
|
this.clientInfo = options.clientIdentity ?? {
|
|
9001
10291
|
name: corePackageJson.name.split("/")[1] ?? corePackageJson.name,
|
|
9002
10292
|
version: corePackageJson.version
|
|
@@ -9005,9 +10295,60 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9005
10295
|
this.clientInfo,
|
|
9006
10296
|
Object.keys(clientOptions).length > 0 ? clientOptions : void 0
|
|
9007
10297
|
);
|
|
10298
|
+
this.annotateSdkRequestTimeouts(this.client);
|
|
9008
10299
|
}
|
|
9009
|
-
|
|
9010
|
-
|
|
10300
|
+
/**
|
|
10301
|
+
* Decorate the SDK Client's `request` so a per-request timeout rejects with
|
|
10302
|
+
* the connection's state at that moment instead of a bare `Request timed
|
|
10303
|
+
* out` (#2318).
|
|
10304
|
+
*
|
|
10305
|
+
* `Protocol.request` is the one funnel every SDK verb goes through —
|
|
10306
|
+
* `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
|
|
10307
|
+
* and this class's own raw `client.request` calls all reach the wire
|
|
10308
|
+
* through it — so decorating it here covers the whole surface without a
|
|
10309
|
+
* `catch` at each of the several dozen call sites, and covers the next
|
|
10310
|
+
* verb added without anyone remembering to. The SDK offers no hook on the
|
|
10311
|
+
* rejection path: the timeout error is constructed and thrown inside
|
|
10312
|
+
* `request()` itself.
|
|
10313
|
+
*
|
|
10314
|
+
* Replaced on the instance rather than through a subclass because
|
|
10315
|
+
* `request` is declared with two overloads (spec method with a keyed
|
|
10316
|
+
* result, custom method with an explicit schema), and an `override` would
|
|
10317
|
+
* have to restate both against types that move with every SDK release. One
|
|
10318
|
+
* implementation signature serves both at runtime; the single cast on the
|
|
10319
|
+
* way back is what the overloads cost.
|
|
10320
|
+
*/
|
|
10321
|
+
annotateSdkRequestTimeouts(client) {
|
|
10322
|
+
const sdkRequest = client.request.bind(client);
|
|
10323
|
+
const decorated = async (...args) => {
|
|
10324
|
+
try {
|
|
10325
|
+
return await sdkRequest(...args);
|
|
10326
|
+
} catch (err) {
|
|
10327
|
+
throw annotateRequestTimeout(
|
|
10328
|
+
err,
|
|
10329
|
+
args[0].method,
|
|
10330
|
+
this.getConnectionDiagnostics()
|
|
10331
|
+
);
|
|
10332
|
+
}
|
|
10333
|
+
};
|
|
10334
|
+
client.request = decorated;
|
|
10335
|
+
}
|
|
10336
|
+
/**
|
|
10337
|
+
* @param base - the composed OAuth-path fetch, deadline innermost (#2319).
|
|
10338
|
+
*
|
|
10339
|
+
* Passed in rather than read off `this.fetchFn` because the two chains are
|
|
10340
|
+
* bounded on different terms, not because one of them is unbounded. Both
|
|
10341
|
+
* carry a deadline; the transport chain additionally exempts the MCP endpoint
|
|
10342
|
+
* (`exemptMcpEndpoint`), since its bodies are streams meant to stay open and
|
|
10343
|
+
* a long-running tool call may withhold its headers for minutes. Reusing it
|
|
10344
|
+
* here would extend that exemption to the OAuth manager's own requests — and
|
|
10345
|
+
* an authorization server published at the MCP endpoint's path would then go
|
|
10346
|
+
* unbounded on the one chain that exists to bound it.
|
|
10347
|
+
*
|
|
10348
|
+
* The SDK's transport-internal discovery (the 401/refresh path) is covered by
|
|
10349
|
+
* the transport chain's own deadline, not by this one.
|
|
10350
|
+
*/
|
|
10351
|
+
buildEffectiveAuthFetch(base) {
|
|
9011
10352
|
return createFetchTracker(base, {
|
|
9012
10353
|
trackRequest: (entry) => this.dispatchFetchRequest({ ...entry, category: "auth" }),
|
|
9013
10354
|
updateResponseBody: (id, body) => this.dispatchFetchRequestBodyUpdate(id, body)
|
|
@@ -9017,7 +10358,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9017
10358
|
return {
|
|
9018
10359
|
trackRequest: (message, origin) => {
|
|
9019
10360
|
if (origin === "client") {
|
|
9020
|
-
this.
|
|
10361
|
+
this.outstandingRequests.set(message.id, {
|
|
10362
|
+
id: message.id,
|
|
10363
|
+
method: message.method,
|
|
10364
|
+
sentAt: Date.now()
|
|
10365
|
+
});
|
|
10366
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
9021
10367
|
}
|
|
9022
10368
|
const entry = {
|
|
9023
10369
|
id: crypto.randomUUID(),
|
|
@@ -9028,14 +10374,22 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9028
10374
|
};
|
|
9029
10375
|
this.dispatchTypedEvent("message", entry);
|
|
9030
10376
|
},
|
|
10377
|
+
trackSendFailure: (message, error) => {
|
|
10378
|
+
if (isRequestTimeoutError(error)) return;
|
|
10379
|
+
if (this.outstandingRequests.delete(message.id)) {
|
|
10380
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
10381
|
+
}
|
|
10382
|
+
},
|
|
9031
10383
|
trackResponse: (message, origin) => {
|
|
9032
10384
|
const responseId = message.id;
|
|
9033
10385
|
if (origin === "server" && responseId !== void 0) {
|
|
9034
|
-
const method = this.
|
|
9035
|
-
this.
|
|
10386
|
+
const method = this.outstandingRequests.get(responseId)?.method;
|
|
10387
|
+
this.outstandingRequests.delete(responseId);
|
|
9036
10388
|
if (method !== void 0) {
|
|
9037
10389
|
this.lastAnsweredRequestByMethod.set(method, responseId);
|
|
10390
|
+
this.lastResponse = { method, receivedAt: Date.now() };
|
|
9038
10391
|
}
|
|
10392
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
9039
10393
|
}
|
|
9040
10394
|
const entry = {
|
|
9041
10395
|
id: crypto.randomUUID(),
|
|
@@ -9642,8 +10996,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9642
10996
|
this.clearReceiverTasks();
|
|
9643
10997
|
this.resetSubscriptionStream();
|
|
9644
10998
|
this.cancelledTaskIds.clear();
|
|
9645
|
-
this.
|
|
10999
|
+
this.outstandingRequests.clear();
|
|
9646
11000
|
this.lastAnsweredRequestByMethod.clear();
|
|
11001
|
+
this.lastResponse = void 0;
|
|
11002
|
+
this.notificationStream = void 0;
|
|
11003
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
9647
11004
|
if (this.malformedListItems.size > 0) {
|
|
9648
11005
|
this.malformedListItems.clear();
|
|
9649
11006
|
this.dispatchTypedEvent("malformedListItemsChange", []);
|
|
@@ -9744,10 +11101,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9744
11101
|
},
|
|
9745
11102
|
onFetchRequest: (entry) => {
|
|
9746
11103
|
this.dispatchFetchRequest({ ...entry, category: "transport" });
|
|
11104
|
+
this.noteTransportStream(entry);
|
|
9747
11105
|
},
|
|
9748
11106
|
onFetchResponseBody: (id, body) => {
|
|
9749
11107
|
this.dispatchFetchRequestBodyUpdate(id, body);
|
|
9750
11108
|
},
|
|
11109
|
+
onFetchStreamUpdate: (id, stream) => {
|
|
11110
|
+
this.dispatchFetchRequestStreamUpdate(id, stream);
|
|
11111
|
+
},
|
|
9751
11112
|
...this.serverSettings && { settings: this.serverSettings }
|
|
9752
11113
|
};
|
|
9753
11114
|
if (this.isHttpOAuthConfig() && oauthManager) {
|
|
@@ -9834,7 +11195,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9834
11195
|
this.dispatchTypedEvent("statusChange", this.status);
|
|
9835
11196
|
this.registerPeerRequestHandlers();
|
|
9836
11197
|
this.registerPeerNotificationHandlers();
|
|
9837
|
-
const connectTimeoutMs = this.serverSettings?.connectionTimeout ??
|
|
11198
|
+
const connectTimeoutMs = this.serverSettings?.connectionTimeout ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
11199
|
+
let connectTimedOut = false;
|
|
9838
11200
|
const connectPromise = this.client.connect(this.transport).catch((err) => {
|
|
9839
11201
|
throw findNestedAuthError(err) ?? err;
|
|
9840
11202
|
});
|
|
@@ -9854,14 +11216,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9854
11216
|
});
|
|
9855
11217
|
let timer;
|
|
9856
11218
|
const timeoutPromise = new Promise((_, reject) => {
|
|
9857
|
-
timer = setTimeout(
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
)
|
|
9862
|
-
),
|
|
9863
|
-
connectTimeoutMs
|
|
9864
|
-
);
|
|
11219
|
+
timer = setTimeout(() => {
|
|
11220
|
+
connectTimedOut = true;
|
|
11221
|
+
reject(new Error(connectionTimeoutMessage(connectTimeoutMs)));
|
|
11222
|
+
}, connectTimeoutMs);
|
|
9865
11223
|
});
|
|
9866
11224
|
try {
|
|
9867
11225
|
await Promise.race([connectPromise, timeoutPromise]);
|
|
@@ -9875,7 +11233,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9875
11233
|
try {
|
|
9876
11234
|
await this.invokeMcpClient(runConnect);
|
|
9877
11235
|
} catch (err) {
|
|
9878
|
-
if (
|
|
11236
|
+
if (connectTimedOut) {
|
|
9879
11237
|
await this.disconnect().catch(() => {
|
|
9880
11238
|
});
|
|
9881
11239
|
}
|
|
@@ -10024,7 +11382,6 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10024
11382
|
this.activeToolCallAbortController?.abort("Disconnected");
|
|
10025
11383
|
this.activeToolCallAbortController = void 0;
|
|
10026
11384
|
this.clearReceiverTasks();
|
|
10027
|
-
this.appRendererClientProxy = null;
|
|
10028
11385
|
this.capabilities = void 0;
|
|
10029
11386
|
this.serverInfo = void 0;
|
|
10030
11387
|
this.instructions = void 0;
|
|
@@ -10049,31 +11406,19 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10049
11406
|
this.dispatchTypedEvent("malformedListItemsChange", []);
|
|
10050
11407
|
}
|
|
10051
11408
|
/**
|
|
10052
|
-
*
|
|
10053
|
-
*
|
|
10054
|
-
*
|
|
11409
|
+
* The SDK client for the MCP Apps host bridge, or null when not connected.
|
|
11410
|
+
*
|
|
11411
|
+
* ext-apps' `AppBridge` (2.0.0+, an SDK v2 peer) takes the v2 `Client`
|
|
11412
|
+
* directly and registers its list-changed forwarding on it in the
|
|
11413
|
+
* method-string form, so this hands out the real client. The Proxy that
|
|
11414
|
+
* translated the 1.x peer's schema-first `setNotificationHandler(Schema,
|
|
11415
|
+
* handler)` into that form is gone with it (#1745). Kept as the seam rather
|
|
11416
|
+
* than exposing the field so the connected-status gate lives in one place
|
|
11417
|
+
* and hooks, fakes and tests depend on the protocol, not the class.
|
|
10055
11418
|
*/
|
|
10056
11419
|
getAppRendererClient() {
|
|
10057
11420
|
if (!this.client || this.status !== "connected") return null;
|
|
10058
|
-
|
|
10059
|
-
return this.appRendererClientProxy;
|
|
10060
|
-
const target = this.client;
|
|
10061
|
-
this.appRendererClientProxy = new Proxy(this.client, {
|
|
10062
|
-
get(proxyTarget, prop, receiver) {
|
|
10063
|
-
const value = Reflect.get(proxyTarget, prop, receiver);
|
|
10064
|
-
if (prop === "setNotificationHandler" && typeof value === "function") {
|
|
10065
|
-
return (schemaOrMethod, ...rest) => {
|
|
10066
|
-
const method = typeof schemaOrMethod === "string" ? schemaOrMethod : notificationMethodFromSchema(schemaOrMethod) ?? schemaOrMethod;
|
|
10067
|
-
return value.apply(target, [
|
|
10068
|
-
method,
|
|
10069
|
-
...rest
|
|
10070
|
-
]);
|
|
10071
|
-
};
|
|
10072
|
-
}
|
|
10073
|
-
return value;
|
|
10074
|
-
}
|
|
10075
|
-
});
|
|
10076
|
-
return this.appRendererClientProxy;
|
|
11421
|
+
return this.client;
|
|
10077
11422
|
}
|
|
10078
11423
|
/**
|
|
10079
11424
|
* Send a ping request to the server. Resolves when the server responds.
|
|
@@ -10235,7 +11580,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10235
11580
|
const timer = setTimeout(() => {
|
|
10236
11581
|
this.pendingRawWireRequests.delete(id);
|
|
10237
11582
|
reject(
|
|
10238
|
-
|
|
11583
|
+
annotateRequestTimeout(
|
|
11584
|
+
new SdkError5(SdkErrorCode5.RequestTimeout, "Request timed out", {
|
|
11585
|
+
timeout: timeoutMs
|
|
11586
|
+
}),
|
|
11587
|
+
method,
|
|
11588
|
+
this.getConnectionDiagnostics()
|
|
11589
|
+
)
|
|
10239
11590
|
);
|
|
10240
11591
|
}, timeoutMs);
|
|
10241
11592
|
this.pendingRawWireRequests.set(id, { resolve: resolve7, reject, timer });
|
|
@@ -10245,7 +11596,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10245
11596
|
clearTimeout(pending.timer);
|
|
10246
11597
|
this.pendingRawWireRequests.delete(id);
|
|
10247
11598
|
}
|
|
10248
|
-
reject(
|
|
11599
|
+
reject(
|
|
11600
|
+
annotateRequestTimeout(
|
|
11601
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
11602
|
+
method,
|
|
11603
|
+
this.getConnectionDiagnostics()
|
|
11604
|
+
)
|
|
11605
|
+
);
|
|
10249
11606
|
});
|
|
10250
11607
|
});
|
|
10251
11608
|
return resultSchema.parse(raw);
|
|
@@ -10418,7 +11775,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10418
11775
|
throw new Error("Client is not connected");
|
|
10419
11776
|
}
|
|
10420
11777
|
const result = await this.client.request(
|
|
10421
|
-
{
|
|
11778
|
+
{
|
|
11779
|
+
method: "tasks/list",
|
|
11780
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
11781
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
11782
|
+
// again, so a caller walking pages would loop on the first page.
|
|
11783
|
+
params: cursor !== void 0 ? { cursor } : {}
|
|
11784
|
+
},
|
|
10422
11785
|
ListTasksResultSchema,
|
|
10423
11786
|
this.getRequestOptions()
|
|
10424
11787
|
);
|
|
@@ -12012,7 +13375,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12012
13375
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12013
13376
|
const params = {
|
|
12014
13377
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12015
|
-
|
|
13378
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
13379
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
13380
|
+
// again, so a caller walking pages would loop on the first page.
|
|
13381
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12016
13382
|
};
|
|
12017
13383
|
const response = await this.invokeMcpClient(
|
|
12018
13384
|
() => this.client.request(
|
|
@@ -12040,7 +13406,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12040
13406
|
method: "resources/list",
|
|
12041
13407
|
itemsKey: "resources",
|
|
12042
13408
|
resultSchema: ListResourcesResultSchema,
|
|
12043
|
-
itemSchema:
|
|
13409
|
+
itemSchema: ResourceSchema2,
|
|
12044
13410
|
metadata: options?.metadata,
|
|
12045
13411
|
aggregate: async () => [
|
|
12046
13412
|
...(await this.client.listResources(
|
|
@@ -12142,7 +13508,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12142
13508
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12143
13509
|
const params = {
|
|
12144
13510
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12145
|
-
|
|
13511
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
13512
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
13513
|
+
// again, so a caller walking pages would loop on the first page.
|
|
13514
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12146
13515
|
};
|
|
12147
13516
|
const response = await this.invokeMcpClient(
|
|
12148
13517
|
() => this.client.request(
|
|
@@ -12195,7 +13564,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12195
13564
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12196
13565
|
const params = {
|
|
12197
13566
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12198
|
-
|
|
13567
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
13568
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
13569
|
+
// again, so a caller walking pages would loop on the first page.
|
|
13570
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12199
13571
|
};
|
|
12200
13572
|
const response = await this.invokeMcpClient(
|
|
12201
13573
|
() => this.client.request(
|
|
@@ -12234,6 +13606,155 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12234
13606
|
});
|
|
12235
13607
|
return { prompts };
|
|
12236
13608
|
}
|
|
13609
|
+
/**
|
|
13610
|
+
* The Skills extension (SEP-2640) the server declared, or `undefined` when it
|
|
13611
|
+
* declared none. Gates the Skills tab and the skills store the same way
|
|
13612
|
+
* {@link isTasksExtensionNegotiated} gates Tasks — but deliberately without an
|
|
13613
|
+
* era check: `skills/*` are not spec method names in either codec, so a
|
|
13614
|
+
* legacy-era server that declares the extension is serving it (#2234).
|
|
13615
|
+
*/
|
|
13616
|
+
getSkillsExtension() {
|
|
13617
|
+
return getSkillsExtension(this.capabilities);
|
|
13618
|
+
}
|
|
13619
|
+
/**
|
|
13620
|
+
* One page of `skills/list` (SEP-2640).
|
|
13621
|
+
*
|
|
13622
|
+
* An ordinary `client.request` with an explicit result schema — the SDK's era
|
|
13623
|
+
* gate skips methods neither codec defines, and `assertCapabilityForMethod`
|
|
13624
|
+
* falls through to a no-op for them, so this is all the extension needs. The
|
|
13625
|
+
* raw-wire path modern `tasks/*` uses would be wrong here: it exists for spec
|
|
13626
|
+
* names the 2026 codec deleted, and taking it would bypass the SDK's response
|
|
13627
|
+
* correlation for nothing (#2234).
|
|
13628
|
+
*/
|
|
13629
|
+
async listSkills(cursor, metadata) {
|
|
13630
|
+
if (!this.client) {
|
|
13631
|
+
throw new Error("Client is not connected");
|
|
13632
|
+
}
|
|
13633
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13634
|
+
const params = {
|
|
13635
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
13636
|
+
// `!== undefined`, not truthiness: a cursor is opaque and the empty
|
|
13637
|
+
// string is a legal value. Dropping `""` would silently re-request page
|
|
13638
|
+
// one, which the store then reports as a repeated-cursor failure — a
|
|
13639
|
+
// conforming server made to look broken.
|
|
13640
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
13641
|
+
};
|
|
13642
|
+
const resultSchema = this.isModernEra() ? ModernListSkillsResultSchema : ListSkillsResultSchema;
|
|
13643
|
+
const response = await this.invokeMcpClient(
|
|
13644
|
+
() => this.client.request(
|
|
13645
|
+
{ method: SKILLS_LIST_METHOD, params },
|
|
13646
|
+
resultSchema,
|
|
13647
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13648
|
+
),
|
|
13649
|
+
{ method: SKILLS_LIST_METHOD }
|
|
13650
|
+
);
|
|
13651
|
+
return {
|
|
13652
|
+
skills: response.skills,
|
|
13653
|
+
nextCursor: response.nextCursor
|
|
13654
|
+
};
|
|
13655
|
+
}
|
|
13656
|
+
/**
|
|
13657
|
+
* One skill entry by URI (`skills/get`, SEP-2640). The result envelope is
|
|
13658
|
+
* required — `GetSkillEnvelopeSchema` requires `{ skill }` and rejects an entry
|
|
13659
|
+
* returned inline, so a non-conforming shape fails here rather than being
|
|
13660
|
+
* silently normalized past the conformance checks.
|
|
13661
|
+
*/
|
|
13662
|
+
async getSkill(uri, metadata) {
|
|
13663
|
+
return (await this.getSkillResult(uri, metadata)).skill;
|
|
13664
|
+
}
|
|
13665
|
+
/**
|
|
13666
|
+
* `skills/get` as the SDK decoded it — the `{ skill }` envelope **and any
|
|
13667
|
+
* other members the server sent**, such as the caching attributes. On a
|
|
13668
|
+
* modern connection `resultType` is not among them: the codec checks it and
|
|
13669
|
+
* removes it before the result gets here (#2373).
|
|
13670
|
+
*
|
|
13671
|
+
* Separate from {@link getSkill} because the callers differ: the UIs want the
|
|
13672
|
+
* entry, while the CLI prints the result and must not reshape it. SEP-2640
|
|
13673
|
+
* explicitly leaves open whether this result carries `ttlMs` / `cacheScope`,
|
|
13674
|
+
* so a server may send them — and unwrapping to the entry discards exactly
|
|
13675
|
+
* those (Copilot).
|
|
13676
|
+
*/
|
|
13677
|
+
async getSkillResult(uri, metadata) {
|
|
13678
|
+
if (!this.client) {
|
|
13679
|
+
throw new Error("Client is not connected");
|
|
13680
|
+
}
|
|
13681
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13682
|
+
const params = {
|
|
13683
|
+
uri,
|
|
13684
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {}
|
|
13685
|
+
};
|
|
13686
|
+
try {
|
|
13687
|
+
return await this.invokeMcpClient(
|
|
13688
|
+
() => this.client.request(
|
|
13689
|
+
{ method: SKILLS_GET_METHOD, params },
|
|
13690
|
+
GetSkillEnvelopeSchema,
|
|
13691
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13692
|
+
),
|
|
13693
|
+
{ method: SKILLS_GET_METHOD }
|
|
13694
|
+
);
|
|
13695
|
+
} catch (err) {
|
|
13696
|
+
if (isClientDecodeRejection(err)) {
|
|
13697
|
+
this.markResponseRejected(
|
|
13698
|
+
SKILLS_GET_METHOD,
|
|
13699
|
+
err instanceof Error ? err.message : String(err)
|
|
13700
|
+
);
|
|
13701
|
+
}
|
|
13702
|
+
throw err;
|
|
13703
|
+
}
|
|
13704
|
+
}
|
|
13705
|
+
/**
|
|
13706
|
+
* One page of `resources/directory/read` (SEP-2640): the direct children of
|
|
13707
|
+
* a directory resource.
|
|
13708
|
+
*
|
|
13709
|
+
* **Gated on the server's own declaration, and the gate throws rather than
|
|
13710
|
+
* asks.** SEP-2640 is explicit that *"clients MUST NOT call
|
|
13711
|
+
* `resources/directory/read` against a server that has not declared
|
|
13712
|
+
* `directoryRead: true`"*, so this refuses locally instead of sending a call
|
|
13713
|
+
* the spec forbids and letting the server answer `-32601`. Refusing here also
|
|
13714
|
+
* keeps the Protocol tab honest: a request we were never allowed to make
|
|
13715
|
+
* should not appear in the exchange log as a server-side failure.
|
|
13716
|
+
*
|
|
13717
|
+
* Not recursive — the SEP says clients descend by calling the method again on
|
|
13718
|
+
* a child directory, so the walking is the caller's, not this method's.
|
|
13719
|
+
*/
|
|
13720
|
+
async readResourceDirectory(uri, cursor, metadata) {
|
|
13721
|
+
if (!this.client) {
|
|
13722
|
+
throw new Error("Client is not connected");
|
|
13723
|
+
}
|
|
13724
|
+
const extension = this.getSkillsExtension();
|
|
13725
|
+
if (!extension?.directoryRead) {
|
|
13726
|
+
throw new Error(
|
|
13727
|
+
`Server did not declare directoryRead in ${SKILLS_EXTENSION_KEY}; ${RESOURCES_DIRECTORY_READ_METHOD} must not be called.`
|
|
13728
|
+
);
|
|
13729
|
+
}
|
|
13730
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13731
|
+
const params = {
|
|
13732
|
+
uri,
|
|
13733
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
13734
|
+
// `!== undefined` for the same reason `listSkills` uses it: a cursor is
|
|
13735
|
+
// opaque and `""` is a legal value, so truthiness would silently re-ask
|
|
13736
|
+
// for page one.
|
|
13737
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
13738
|
+
};
|
|
13739
|
+
try {
|
|
13740
|
+
return await this.invokeMcpClient(
|
|
13741
|
+
() => this.client.request(
|
|
13742
|
+
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
13743
|
+
DirectoryReadResultSchema,
|
|
13744
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13745
|
+
),
|
|
13746
|
+
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
13747
|
+
);
|
|
13748
|
+
} catch (err) {
|
|
13749
|
+
if (isClientDecodeRejection(err)) {
|
|
13750
|
+
this.markResponseRejected(
|
|
13751
|
+
RESOURCES_DIRECTORY_READ_METHOD,
|
|
13752
|
+
err instanceof Error ? err.message : String(err)
|
|
13753
|
+
);
|
|
13754
|
+
}
|
|
13755
|
+
throw err;
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
12237
13758
|
/**
|
|
12238
13759
|
* Get a prompt by name
|
|
12239
13760
|
* @param name Prompt name
|
|
@@ -12380,6 +13901,72 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12380
13901
|
dispatchFetchRequestBodyUpdate(id, responseBody) {
|
|
12381
13902
|
this.dispatchTypedEvent("fetchRequestBodyUpdate", { id, responseBody });
|
|
12382
13903
|
}
|
|
13904
|
+
dispatchFetchRequestStreamUpdate(id, stream) {
|
|
13905
|
+
this.dispatchTypedEvent("fetchRequestStreamUpdate", { id, stream });
|
|
13906
|
+
if (this.notificationStream?.id !== id) return;
|
|
13907
|
+
this.notificationStream = {
|
|
13908
|
+
...this.notificationStream,
|
|
13909
|
+
eventCount: stream.eventCount,
|
|
13910
|
+
...stream.closedAt && { closedAt: stream.closedAt.getTime() }
|
|
13911
|
+
};
|
|
13912
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
13913
|
+
}
|
|
13914
|
+
/**
|
|
13915
|
+
* Adopt a transport fetch as the notification stream when it is one — a
|
|
13916
|
+
* `GET` answered with an unbounded event stream — so the diagnostics can
|
|
13917
|
+
* say how long it has been open and what it has delivered (#2318). The
|
|
13918
|
+
* newest such stream wins: the SDK reopens the standalone `GET` after a
|
|
13919
|
+
* drop, and the current one is the one a stalled request is queued behind.
|
|
13920
|
+
*/
|
|
13921
|
+
noteTransportStream(entry) {
|
|
13922
|
+
if (!isLongLivedStreamResponse(
|
|
13923
|
+
entry.method,
|
|
13924
|
+
findHeader(entry.responseHeaders, "content-type")
|
|
13925
|
+
)) {
|
|
13926
|
+
return;
|
|
13927
|
+
}
|
|
13928
|
+
this.notificationStream = {
|
|
13929
|
+
id: entry.id,
|
|
13930
|
+
url: entry.url,
|
|
13931
|
+
// The entry's `timestamp` is when the request went out; the stream is
|
|
13932
|
+
// open from when the response headers arrived, `duration` later.
|
|
13933
|
+
openedAt: entry.timestamp.getTime() + (entry.duration ?? 0),
|
|
13934
|
+
eventCount: 0
|
|
13935
|
+
};
|
|
13936
|
+
this.dispatchConnectionDiagnosticsChange();
|
|
13937
|
+
}
|
|
13938
|
+
/**
|
|
13939
|
+
* What this client is still waiting on, when it last heard back, and the
|
|
13940
|
+
* state of the notification stream (#2318). A fresh snapshot per call —
|
|
13941
|
+
* the same one `annotateRequestTimeout` writes a timeout message from, and
|
|
13942
|
+
* the one `connectionDiagnosticsChange` carries.
|
|
13943
|
+
*/
|
|
13944
|
+
getConnectionDiagnostics() {
|
|
13945
|
+
return {
|
|
13946
|
+
capturedAt: Date.now(),
|
|
13947
|
+
// Copies, not the tracked records: the snapshot is handed to callers
|
|
13948
|
+
// and carried on errors, and a mutation there must not reach the
|
|
13949
|
+
// correlation state.
|
|
13950
|
+
outstandingRequests: [...this.outstandingRequests.values()].map(
|
|
13951
|
+
(request) => ({ ...request })
|
|
13952
|
+
),
|
|
13953
|
+
...this.lastResponse && { lastResponse: { ...this.lastResponse } },
|
|
13954
|
+
...this.notificationStream && {
|
|
13955
|
+
notificationStream: this.publicStreamState(this.notificationStream)
|
|
13956
|
+
}
|
|
13957
|
+
};
|
|
13958
|
+
}
|
|
13959
|
+
/** The stream state minus the entry id, which is bookkeeping, not a fact. */
|
|
13960
|
+
publicStreamState(stream) {
|
|
13961
|
+
const { id: _id, ...state } = stream;
|
|
13962
|
+
return state;
|
|
13963
|
+
}
|
|
13964
|
+
dispatchConnectionDiagnosticsChange() {
|
|
13965
|
+
this.dispatchTypedEvent(
|
|
13966
|
+
"connectionDiagnosticsChange",
|
|
13967
|
+
this.getConnectionDiagnostics()
|
|
13968
|
+
);
|
|
13969
|
+
}
|
|
12383
13970
|
/**
|
|
12384
13971
|
* Get current session ID (from OAuth state authId)
|
|
12385
13972
|
*/
|
|
@@ -13259,7 +14846,30 @@ var EXIT_CODES = {
|
|
|
13259
14846
|
AUTH_REQUIRED: 3,
|
|
13260
14847
|
UNREACHABLE: 4,
|
|
13261
14848
|
TOOL_ERROR: 5,
|
|
13262
|
-
SCHEMA_UNPORTABLE: 6
|
|
14849
|
+
SCHEMA_UNPORTABLE: 6,
|
|
14850
|
+
/**
|
|
14851
|
+
* `--verify` found a SEP-2640 violation: a conformance error, a digest or
|
|
14852
|
+
* size mismatch, or a manifest file that could not be read (#2248).
|
|
14853
|
+
*
|
|
14854
|
+
* Its own code rather than reusing `SCHEMA_UNPORTABLE`, for the reason that
|
|
14855
|
+
* one exists at all: a CI job that fails on an unportable tool schema and a
|
|
14856
|
+
* CI job that fails on a tampered skill digest are different jobs, and
|
|
14857
|
+
* collapsing them would make `if [ $? -eq 6 ]` ambiguous.
|
|
14858
|
+
*/
|
|
14859
|
+
SKILL_NONCONFORMANT: 7,
|
|
14860
|
+
/**
|
|
14861
|
+
* `--verify` could not check the whole catalog: the read bounds stopped the
|
|
14862
|
+
* walk before it finished (#2248).
|
|
14863
|
+
*
|
|
14864
|
+
* Distinct from `SKILL_NONCONFORMANT` because the server has broken no
|
|
14865
|
+
* **MUST** — SEP-2640 states the 512-entry and 16 MiB limits as SHOULD NOT,
|
|
14866
|
+
* with hosts free to support more — so exiting 7 would call a conforming
|
|
14867
|
+
* server nonconformant. It is still non-zero, because reporting success for
|
|
14868
|
+
* a manifest whose unread entries were never fetched is a false pass. A CI
|
|
14869
|
+
* job that wants to tolerate oversized catalogs can allow 8 and still fail
|
|
14870
|
+
* on 7.
|
|
14871
|
+
*/
|
|
14872
|
+
SKILL_INCOMPLETE: 8
|
|
13263
14873
|
};
|
|
13264
14874
|
var CliExitCodeError = class extends Error {
|
|
13265
14875
|
constructor(exitCode, message, envelope) {
|
|
@@ -13293,7 +14903,7 @@ function statusOf(error) {
|
|
|
13293
14903
|
}
|
|
13294
14904
|
return void 0;
|
|
13295
14905
|
}
|
|
13296
|
-
var UNREACHABLE_PATTERN = /ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|ETIMEDOUT|fetch failed|getaddrinfo|connect timed out|aborted/i;
|
|
14906
|
+
var UNREACHABLE_PATTERN = /ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|ETIMEDOUT|fetch failed|getaddrinfo|connect(?:ion)? timed out|aborted/i;
|
|
13297
14907
|
function classifyError(error, context) {
|
|
13298
14908
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
13299
14909
|
const cause = causeOf(error);
|
|
@@ -13663,12 +15273,12 @@ function countFindings(results) {
|
|
|
13663
15273
|
}
|
|
13664
15274
|
return { errors, warnings };
|
|
13665
15275
|
}
|
|
13666
|
-
function
|
|
15276
|
+
function plural2(count, noun) {
|
|
13667
15277
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
13668
15278
|
}
|
|
13669
15279
|
function summarizeFindings(results) {
|
|
13670
15280
|
const { errors, warnings } = countFindings(results);
|
|
13671
|
-
return `${
|
|
15281
|
+
return `${plural2(errors, "error")}, ${plural2(warnings, "warning")} across ${plural2(results.length, "tool")}`;
|
|
13672
15282
|
}
|
|
13673
15283
|
function formatSchemaLintReport(results) {
|
|
13674
15284
|
const lines = [];
|
|
@@ -13767,6 +15377,15 @@ async function consumeMethodOutcome(outcome, args) {
|
|
|
13767
15377
|
for (const line of outcome.lines) {
|
|
13768
15378
|
await awaitableLog(JSON.stringify(line) + "\n");
|
|
13769
15379
|
}
|
|
15380
|
+
if (outcome.summary) await awaitableError(`${outcome.summary}
|
|
15381
|
+
`);
|
|
15382
|
+
if (outcome.exitCode) {
|
|
15383
|
+
throw new CliExitCodeError(outcome.exitCode, outcome.summary ?? "", {
|
|
15384
|
+
// The envelope's `code` follows the exit code, so a caller reading one
|
|
15385
|
+
// never has to reconcile it against the other.
|
|
15386
|
+
code: outcome.exitCode === EXIT_CODES.SKILL_INCOMPLETE ? "skills_incomplete" : "skills_nonconformant"
|
|
15387
|
+
});
|
|
15388
|
+
}
|
|
13770
15389
|
return;
|
|
13771
15390
|
}
|
|
13772
15391
|
await new Promise((resolve7) => {
|
|
@@ -14332,7 +15951,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14332
15951
|
const page = result.tasks.filter((t) => !this.dismissedTaskIds.has(t.taskId)).map(
|
|
14333
15952
|
(t) => this.cancelledTaskIds.has(t.taskId) ? { ...t, status: "cancelled" } : t
|
|
14334
15953
|
);
|
|
14335
|
-
this.tasks = cursor ? [...this.tasks, ...page]
|
|
15954
|
+
this.tasks = cursor === void 0 ? page : [...this.tasks, ...page];
|
|
14336
15955
|
cursor = result.nextCursor;
|
|
14337
15956
|
pageCount++;
|
|
14338
15957
|
if (pageCount >= MAX_PAGES) {
|
|
@@ -14340,7 +15959,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14340
15959
|
`Maximum pagination limit (${MAX_PAGES} pages) reached while listing requestor tasks`
|
|
14341
15960
|
);
|
|
14342
15961
|
}
|
|
14343
|
-
} while (cursor);
|
|
15962
|
+
} while (cursor !== void 0);
|
|
14344
15963
|
this.dispatchTypedEvent("tasksChange", this.tasks);
|
|
14345
15964
|
return this.getTasks();
|
|
14346
15965
|
}
|
|
@@ -14393,12 +16012,150 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14393
16012
|
}
|
|
14394
16013
|
};
|
|
14395
16014
|
|
|
16015
|
+
// ../../core/mcp/state/managedSkillsState.ts
|
|
16016
|
+
var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
|
|
16017
|
+
var SKILLS_MAX_PAGES = LIST_MAX_PAGES;
|
|
16018
|
+
var SKILLS_PAGE_LIMIT_MESSAGE = `skills/list exceeded ${SKILLS_MAX_PAGES} pages without the server's pagination converging`;
|
|
16019
|
+
var ManagedSkillsState = class extends TypedEventTarget {
|
|
16020
|
+
skills = [];
|
|
16021
|
+
pageCount = 0;
|
|
16022
|
+
error = null;
|
|
16023
|
+
client = null;
|
|
16024
|
+
unsubscribe = null;
|
|
16025
|
+
// Overlap guard, held as the generation whose walk is in flight (or `null`).
|
|
16026
|
+
// See `refresh` for why this is not a boolean.
|
|
16027
|
+
runningGeneration = null;
|
|
16028
|
+
// Session counter, advanced by `reset` (disconnect) and by `destroy`. A walk
|
|
16029
|
+
// captures it and abandons its writes when it no longer matches.
|
|
16030
|
+
generation = 0;
|
|
16031
|
+
constructor(client) {
|
|
16032
|
+
super();
|
|
16033
|
+
this.client = client;
|
|
16034
|
+
const onConnect = () => {
|
|
16035
|
+
void this.refresh().catch(() => {
|
|
16036
|
+
});
|
|
16037
|
+
};
|
|
16038
|
+
const onStatusChange = () => {
|
|
16039
|
+
if (isTerminalStatus(this.client?.getStatus())) {
|
|
16040
|
+
this.reset();
|
|
16041
|
+
}
|
|
16042
|
+
};
|
|
16043
|
+
this.client.addEventListener("connect", onConnect);
|
|
16044
|
+
this.client.addEventListener("statusChange", onStatusChange);
|
|
16045
|
+
this.unsubscribe = () => {
|
|
16046
|
+
if (this.client) {
|
|
16047
|
+
this.client.removeEventListener("connect", onConnect);
|
|
16048
|
+
this.client.removeEventListener("statusChange", onStatusChange);
|
|
16049
|
+
}
|
|
16050
|
+
this.client = null;
|
|
16051
|
+
};
|
|
16052
|
+
}
|
|
16053
|
+
/** Defensive copy of the current list. */
|
|
16054
|
+
getSkills() {
|
|
16055
|
+
return [...this.skills];
|
|
16056
|
+
}
|
|
16057
|
+
getPagination() {
|
|
16058
|
+
return { pageCount: this.pageCount };
|
|
16059
|
+
}
|
|
16060
|
+
/** The last walk's failure, or `null` when it succeeded. */
|
|
16061
|
+
getError() {
|
|
16062
|
+
return this.error;
|
|
16063
|
+
}
|
|
16064
|
+
// Compared by identity rather than message: two distinct failures with the
|
|
16065
|
+
// same text are still two events, and a re-render on a repeat failure is
|
|
16066
|
+
// cheap next to silently coalescing them.
|
|
16067
|
+
setError(value) {
|
|
16068
|
+
if (this.error === value) return;
|
|
16069
|
+
this.error = value;
|
|
16070
|
+
this.dispatchTypedEvent("errorChange", value);
|
|
16071
|
+
}
|
|
16072
|
+
reset() {
|
|
16073
|
+
this.generation += 1;
|
|
16074
|
+
this.skills = [];
|
|
16075
|
+
this.pageCount = 0;
|
|
16076
|
+
this.dispatchTypedEvent("skillsChange", this.getSkills());
|
|
16077
|
+
this.dispatchTypedEvent("paginationChange", this.getPagination());
|
|
16078
|
+
this.setError(null);
|
|
16079
|
+
}
|
|
16080
|
+
/**
|
|
16081
|
+
* Walk every page of `skills/list` and commit the result.
|
|
16082
|
+
*
|
|
16083
|
+
* A failure is recorded as observable state (`getError`) AND re-thrown: the
|
|
16084
|
+
* state drives the panel's error rendering, while the rejection is what a
|
|
16085
|
+
* caller's auth-recovery wrapper keys off to detect a 401 and start a
|
|
16086
|
+
* re-authorization. The connect-time load, which has no such caller, catches
|
|
16087
|
+
* it above.
|
|
16088
|
+
*/
|
|
16089
|
+
async refresh(metadata) {
|
|
16090
|
+
const client = this.client;
|
|
16091
|
+
if (!client || client.getStatus() !== "connected") return this.getSkills();
|
|
16092
|
+
if (!client.getSkillsExtension()) {
|
|
16093
|
+
this.applyPages([], 0);
|
|
16094
|
+
return this.getSkills();
|
|
16095
|
+
}
|
|
16096
|
+
const generation = this.generation;
|
|
16097
|
+
const isCurrent = () => this.generation === generation;
|
|
16098
|
+
if (this.runningGeneration === generation) return this.getSkills();
|
|
16099
|
+
this.runningGeneration = generation;
|
|
16100
|
+
try {
|
|
16101
|
+
const collected = [];
|
|
16102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16103
|
+
let cursor;
|
|
16104
|
+
let pages = 0;
|
|
16105
|
+
for (; ; ) {
|
|
16106
|
+
const page = await client.listSkills(cursor, metadata);
|
|
16107
|
+
if (!isCurrent()) return this.getSkills();
|
|
16108
|
+
collected.push(...page.skills);
|
|
16109
|
+
pages += 1;
|
|
16110
|
+
if (page.nextCursor === void 0) break;
|
|
16111
|
+
if (pages >= SKILLS_MAX_PAGES) {
|
|
16112
|
+
throw new Error(SKILLS_PAGE_LIMIT_MESSAGE);
|
|
16113
|
+
}
|
|
16114
|
+
if (seen.has(page.nextCursor)) {
|
|
16115
|
+
throw new Error(REPEATED_CURSOR_MESSAGE);
|
|
16116
|
+
}
|
|
16117
|
+
seen.add(page.nextCursor);
|
|
16118
|
+
cursor = page.nextCursor;
|
|
16119
|
+
}
|
|
16120
|
+
this.setError(null);
|
|
16121
|
+
this.applyPages(collected, pages);
|
|
16122
|
+
return this.getSkills();
|
|
16123
|
+
} catch (err) {
|
|
16124
|
+
if (isCurrent()) {
|
|
16125
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
16126
|
+
this.setError(error);
|
|
16127
|
+
if (isClientDecodeRejection(err)) {
|
|
16128
|
+
client.markResponseRejected?.(SKILLS_LIST_METHOD, error.message);
|
|
16129
|
+
}
|
|
16130
|
+
}
|
|
16131
|
+
throw err;
|
|
16132
|
+
} finally {
|
|
16133
|
+
if (this.runningGeneration === generation) {
|
|
16134
|
+
this.runningGeneration = null;
|
|
16135
|
+
}
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
applyPages(skills, pageCount) {
|
|
16139
|
+
this.skills = skills;
|
|
16140
|
+
this.pageCount = pageCount;
|
|
16141
|
+
this.dispatchTypedEvent("skillsChange", this.getSkills());
|
|
16142
|
+
this.dispatchTypedEvent("paginationChange", this.getPagination());
|
|
16143
|
+
}
|
|
16144
|
+
/** Unsubscribe from the client and drop the list; idempotent. */
|
|
16145
|
+
destroy() {
|
|
16146
|
+
this.generation += 1;
|
|
16147
|
+
this.unsubscribe?.();
|
|
16148
|
+
this.unsubscribe = null;
|
|
16149
|
+
this.skills = [];
|
|
16150
|
+
this.pageCount = 0;
|
|
16151
|
+
this.error = null;
|
|
16152
|
+
}
|
|
16153
|
+
};
|
|
16154
|
+
|
|
14396
16155
|
// ../../core/mcp/apps.ts
|
|
14397
16156
|
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
14398
16157
|
function getAppResourceUri(tool) {
|
|
14399
|
-
return getToolUiResourceUri(
|
|
14400
|
-
tool
|
|
14401
|
-
);
|
|
16158
|
+
return getToolUiResourceUri(tool);
|
|
14402
16159
|
}
|
|
14403
16160
|
function readUiMeta(carrier) {
|
|
14404
16161
|
const ui = carrier !== null && typeof carrier === "object" ? carrier._meta?.ui : void 0;
|
|
@@ -14463,13 +16220,245 @@ async function collectAppInfo(client, tool, metadata) {
|
|
|
14463
16220
|
}
|
|
14464
16221
|
}
|
|
14465
16222
|
|
|
16223
|
+
// src/handlers/skills-verify.ts
|
|
16224
|
+
function summarizeSkillVerification(reports) {
|
|
16225
|
+
const failed = reports.filter((report) => report.outcome === "failed").length;
|
|
16226
|
+
const incomplete = reports.filter(
|
|
16227
|
+
(report) => report.outcome === "incomplete"
|
|
16228
|
+
).length;
|
|
16229
|
+
const files = reports.reduce((sum, report) => sum + report.files.length, 0);
|
|
16230
|
+
const mismatched = reports.reduce(
|
|
16231
|
+
(sum, report) => sum + report.files.filter((file) => file.status === "mismatch").length,
|
|
16232
|
+
0
|
|
16233
|
+
);
|
|
16234
|
+
const skillWord = reports.length === 1 ? "skill" : "skills";
|
|
16235
|
+
const fileWord = files === 1 ? "file" : "files";
|
|
16236
|
+
const incompleteClause = incomplete === 0 ? "" : ` ${incomplete} of ${reports.length} ${skillWord} could not be fully checked: the read bounds stopped the walk.`;
|
|
16237
|
+
const headline = failed === 0 ? incomplete === 0 ? `Verified ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors.` : `Checked ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors in what was read.` : `${failed} of ${reports.length} ${skillWord} failed verification (${mismatched} digest/size mismatch across ${files} ${fileWord}).`;
|
|
16238
|
+
return `${headline}${incompleteClause}`;
|
|
16239
|
+
}
|
|
16240
|
+
|
|
16241
|
+
// ../../core/mcp/skillsVerification.ts
|
|
16242
|
+
function reasonOf(err) {
|
|
16243
|
+
return err instanceof Error ? err.message : String(err);
|
|
16244
|
+
}
|
|
16245
|
+
function boundedManifest(declared) {
|
|
16246
|
+
const kept = [];
|
|
16247
|
+
let bytes = 0;
|
|
16248
|
+
for (const resource of declared) {
|
|
16249
|
+
if (kept.length >= SKILL_MAX_RESOURCE_ENTRIES) break;
|
|
16250
|
+
const size = typeof resource.size === "number" && Number.isSafeInteger(resource.size) && resource.size >= 0 ? resource.size : 0;
|
|
16251
|
+
if (bytes + size > SKILL_MAX_TOTAL_BYTES) break;
|
|
16252
|
+
bytes += size;
|
|
16253
|
+
kept.push(resource);
|
|
16254
|
+
}
|
|
16255
|
+
return kept;
|
|
16256
|
+
}
|
|
16257
|
+
function contentsFor(result, uri) {
|
|
16258
|
+
const contents = result?.contents;
|
|
16259
|
+
if (!Array.isArray(contents)) return void 0;
|
|
16260
|
+
const wanted = skillUriIdentity(uri);
|
|
16261
|
+
for (const block of contents) {
|
|
16262
|
+
if (typeof block !== "object" || block === null) continue;
|
|
16263
|
+
const got = block.uri;
|
|
16264
|
+
if (typeof got !== "string") continue;
|
|
16265
|
+
if (skillUriIdentity(got) === wanted) return block;
|
|
16266
|
+
}
|
|
16267
|
+
return void 0;
|
|
16268
|
+
}
|
|
16269
|
+
function utf8Length(value) {
|
|
16270
|
+
let total = 0;
|
|
16271
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
16272
|
+
const code = value.charCodeAt(i);
|
|
16273
|
+
if (code < 128) {
|
|
16274
|
+
total += 1;
|
|
16275
|
+
} else if (code < 2048) {
|
|
16276
|
+
total += 2;
|
|
16277
|
+
} else if (code >= 55296 && code <= 56319) {
|
|
16278
|
+
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
|
|
16279
|
+
if (next >= 56320 && next <= 57343) {
|
|
16280
|
+
total += 4;
|
|
16281
|
+
i += 1;
|
|
16282
|
+
} else {
|
|
16283
|
+
total += 3;
|
|
16284
|
+
}
|
|
16285
|
+
} else {
|
|
16286
|
+
total += 3;
|
|
16287
|
+
}
|
|
16288
|
+
}
|
|
16289
|
+
return total;
|
|
16290
|
+
}
|
|
16291
|
+
function responseBytes(result) {
|
|
16292
|
+
const contents = result?.contents;
|
|
16293
|
+
if (!Array.isArray(contents)) return 0;
|
|
16294
|
+
let total = 0;
|
|
16295
|
+
for (const block of contents) {
|
|
16296
|
+
if (typeof block !== "object" || block === null) continue;
|
|
16297
|
+
const { text, blob } = block;
|
|
16298
|
+
if (typeof text === "string") total += utf8Length(text);
|
|
16299
|
+
if (typeof blob === "string") total += blob.length;
|
|
16300
|
+
}
|
|
16301
|
+
return total;
|
|
16302
|
+
}
|
|
16303
|
+
async function verifySkills(client, entries, metadata) {
|
|
16304
|
+
const collisions = checkSkillNameCollisions(entries);
|
|
16305
|
+
const reports = [];
|
|
16306
|
+
const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
|
|
16307
|
+
let walkedSkills = 0;
|
|
16308
|
+
let catalogBytes = 0;
|
|
16309
|
+
for (const entry of entries) {
|
|
16310
|
+
const withinBudget = walkedSkills < budget.maxSkills && catalogBytes < budget.maxBytes;
|
|
16311
|
+
let entryBytes;
|
|
16312
|
+
const files = [];
|
|
16313
|
+
const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
|
|
16314
|
+
const manifest = withinBudget ? boundedManifest(declared) : [];
|
|
16315
|
+
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it in the server's Skills settings). Nothing about this skill's files has been checked \u2014 verify it on its own with \`--method skills/get --uri\` to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
|
|
16316
|
+
let receivedBytes = 0;
|
|
16317
|
+
const entryIdentity = skillUriIdentity(entry.uri);
|
|
16318
|
+
let selfAttempted = false;
|
|
16319
|
+
for (const [index, resource] of manifest.entries()) {
|
|
16320
|
+
if (skillUriIdentity(resource.uri) === entryIdentity) {
|
|
16321
|
+
selfAttempted = true;
|
|
16322
|
+
}
|
|
16323
|
+
let charged = 0;
|
|
16324
|
+
try {
|
|
16325
|
+
const invocation = await client.readResource(resource.uri, metadata);
|
|
16326
|
+
charged = responseBytes(invocation.result);
|
|
16327
|
+
const contents = contentsFor(invocation.result, resource.uri);
|
|
16328
|
+
if (!contents) {
|
|
16329
|
+
files.push({
|
|
16330
|
+
uri: resource.uri,
|
|
16331
|
+
status: "read-error",
|
|
16332
|
+
reason: "resources/read returned no content block for this URI, so there are no bytes that can be checked against its digest."
|
|
16333
|
+
});
|
|
16334
|
+
} else {
|
|
16335
|
+
let bytes;
|
|
16336
|
+
try {
|
|
16337
|
+
bytes = skillFileBytes(contents);
|
|
16338
|
+
} catch (err) {
|
|
16339
|
+
files.push({
|
|
16340
|
+
uri: resource.uri,
|
|
16341
|
+
status: "read-error",
|
|
16342
|
+
reason: reasonOf(err)
|
|
16343
|
+
});
|
|
16344
|
+
}
|
|
16345
|
+
if (bytes) {
|
|
16346
|
+
charged = Math.max(charged, bytes.byteLength);
|
|
16347
|
+
if (skillUriIdentity(resource.uri) === entryIdentity)
|
|
16348
|
+
entryBytes = bytes;
|
|
16349
|
+
const verification = await verifySkillResource(resource, bytes);
|
|
16350
|
+
files.push({ uri: resource.uri, ...verification });
|
|
16351
|
+
}
|
|
16352
|
+
}
|
|
16353
|
+
} catch (err) {
|
|
16354
|
+
if (err instanceof AuthRecoveryRequiredError) throw err;
|
|
16355
|
+
files.push({
|
|
16356
|
+
uri: resource.uri,
|
|
16357
|
+
status: "read-error",
|
|
16358
|
+
reason: reasonOf(err)
|
|
16359
|
+
});
|
|
16360
|
+
}
|
|
16361
|
+
receivedBytes += charged;
|
|
16362
|
+
if (receivedBytes > SKILL_MAX_TOTAL_BYTES) {
|
|
16363
|
+
if (index < manifest.length - 1) {
|
|
16364
|
+
incomplete = `Stopped after ${index + 1} of ${declared.length} manifest entries: the files actually served exceed the ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limit, whatever sizes the manifest declared.`;
|
|
16365
|
+
}
|
|
16366
|
+
break;
|
|
16367
|
+
}
|
|
16368
|
+
}
|
|
16369
|
+
if (withinBudget && !selfAttempted) {
|
|
16370
|
+
const fail = (reason) => files.push({ uri: entry.uri, status: "read-error", reason });
|
|
16371
|
+
try {
|
|
16372
|
+
const invocation = await client.readResource(entry.uri, metadata);
|
|
16373
|
+
let charged = responseBytes(invocation.result);
|
|
16374
|
+
const contents = contentsFor(invocation.result, entry.uri);
|
|
16375
|
+
if (!contents) {
|
|
16376
|
+
fail(
|
|
16377
|
+
"resources/read returned no content block for this skill's own SKILL.md, so its frontmatter cannot be checked against the listing."
|
|
16378
|
+
);
|
|
16379
|
+
} else {
|
|
16380
|
+
try {
|
|
16381
|
+
entryBytes = skillFileBytes(contents);
|
|
16382
|
+
charged = Math.max(charged, entryBytes.byteLength);
|
|
16383
|
+
} catch (err) {
|
|
16384
|
+
fail(reasonOf(err));
|
|
16385
|
+
}
|
|
16386
|
+
}
|
|
16387
|
+
receivedBytes += charged;
|
|
16388
|
+
const declaredSelf = declared.find(
|
|
16389
|
+
(resource) => skillUriIdentity(resource.uri) === entryIdentity
|
|
16390
|
+
);
|
|
16391
|
+
if (declaredSelf && entryBytes !== void 0) {
|
|
16392
|
+
files.push({
|
|
16393
|
+
// The DECLARED spelling, not the entry's. They can differ — a
|
|
16394
|
+
// manifest may write its self-entry in a normalized-equivalent
|
|
16395
|
+
// form — and a consumer matching rows against the manifest then
|
|
16396
|
+
// finds nothing, while a normalized "extra files" filter suppresses
|
|
16397
|
+
// it as already covered. The result was a verdict that existed in
|
|
16398
|
+
// the report and appeared nowhere on screen (Copilot).
|
|
16399
|
+
uri: declaredSelf.uri,
|
|
16400
|
+
...await verifySkillResource(declaredSelf, entryBytes)
|
|
16401
|
+
});
|
|
16402
|
+
}
|
|
16403
|
+
} catch (err) {
|
|
16404
|
+
if (err instanceof AuthRecoveryRequiredError) throw err;
|
|
16405
|
+
fail(reasonOf(err));
|
|
16406
|
+
}
|
|
16407
|
+
}
|
|
16408
|
+
if (withinBudget) {
|
|
16409
|
+
walkedSkills += 1;
|
|
16410
|
+
catalogBytes += receivedBytes;
|
|
16411
|
+
}
|
|
16412
|
+
const entryText = entryBytes === void 0 ? void 0 : bytesToText(entryBytes);
|
|
16413
|
+
const collision = collisions.get(skillUriIdentity(entry.uri));
|
|
16414
|
+
const conformance = [
|
|
16415
|
+
...checkSkillConformance(entry),
|
|
16416
|
+
...collision ? [collision] : []
|
|
16417
|
+
];
|
|
16418
|
+
const frontmatter = entryText === void 0 ? [] : checkSkillFrontmatterMatch(entry, entryText);
|
|
16419
|
+
const hasError = [...conformance, ...frontmatter].some(
|
|
16420
|
+
(issue) => issue.severity === "error"
|
|
16421
|
+
);
|
|
16422
|
+
const fileFailed = files.some(
|
|
16423
|
+
(file) => file.status === "mismatch" || file.status === "read-error"
|
|
16424
|
+
);
|
|
16425
|
+
reports.push({
|
|
16426
|
+
uri: entry.uri,
|
|
16427
|
+
name: skillDisplayName(entry),
|
|
16428
|
+
conformance,
|
|
16429
|
+
frontmatter,
|
|
16430
|
+
files,
|
|
16431
|
+
...incomplete ? { incomplete } : {},
|
|
16432
|
+
ok: !hasError && !fileFailed,
|
|
16433
|
+
outcome: hasError || fileFailed ? "failed" : incomplete !== void 0 ? "incomplete" : "verified"
|
|
16434
|
+
});
|
|
16435
|
+
}
|
|
16436
|
+
return reports;
|
|
16437
|
+
}
|
|
16438
|
+
function allSkillsVerified(reports) {
|
|
16439
|
+
return reports.every((report) => report.outcome === "verified");
|
|
16440
|
+
}
|
|
16441
|
+
function anySkillFailed(reports) {
|
|
16442
|
+
return reports.some((report) => report.outcome === "failed");
|
|
16443
|
+
}
|
|
16444
|
+
|
|
14466
16445
|
// src/handlers/run-method.ts
|
|
16446
|
+
function assertSkillsSupported(inspectorClient, method) {
|
|
16447
|
+
if (!inspectorClient.getSkillsExtension()) {
|
|
16448
|
+
throw new CliExitCodeError(
|
|
16449
|
+
EXIT_CODES.USAGE,
|
|
16450
|
+
`Server does not declare the ${SKILLS_EXTENSION_KEY} extension, so ${method} is not available.`,
|
|
16451
|
+
{ code: "skills_unsupported" }
|
|
16452
|
+
);
|
|
16453
|
+
}
|
|
16454
|
+
}
|
|
14467
16455
|
async function runMethod(inspectorClient, args) {
|
|
14468
16456
|
let managedToolsState = null;
|
|
14469
16457
|
let managedResourcesState = null;
|
|
14470
16458
|
let managedResourceTemplatesState = null;
|
|
14471
16459
|
let managedPromptsState = null;
|
|
14472
16460
|
let managedTasksState = null;
|
|
16461
|
+
let managedSkillsState = null;
|
|
14473
16462
|
try {
|
|
14474
16463
|
let result;
|
|
14475
16464
|
let appInfo;
|
|
@@ -14688,6 +16677,73 @@ async function runMethod(inspectorClient, args) {
|
|
|
14688
16677
|
result = await inspectorClient.getRequestorTaskResult(
|
|
14689
16678
|
args.taskId
|
|
14690
16679
|
);
|
|
16680
|
+
} else if (args.method === "skills/list") {
|
|
16681
|
+
assertSkillsSupported(inspectorClient, args.method);
|
|
16682
|
+
managedSkillsState = new ManagedSkillsState(inspectorClient);
|
|
16683
|
+
const skills = await managedSkillsState.refresh(args.metadata);
|
|
16684
|
+
if (args.verify) {
|
|
16685
|
+
const reports = await verifySkills(
|
|
16686
|
+
inspectorClient,
|
|
16687
|
+
skills,
|
|
16688
|
+
args.metadata
|
|
16689
|
+
);
|
|
16690
|
+
return {
|
|
16691
|
+
kind: "ndjson",
|
|
16692
|
+
lines: reports,
|
|
16693
|
+
summary: summarizeSkillVerification(reports),
|
|
16694
|
+
// Three outcomes, three exit codes: a broken MUST is 7, a walk the
|
|
16695
|
+
// read bounds cut short is 8, and everything checked and passing is
|
|
16696
|
+
// 0. Collapsing the middle case into either of the others reports
|
|
16697
|
+
// something untrue about the server (Copilot).
|
|
16698
|
+
...allSkillsVerified(reports) ? {} : {
|
|
16699
|
+
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
16700
|
+
}
|
|
16701
|
+
};
|
|
16702
|
+
}
|
|
16703
|
+
result = { skills };
|
|
16704
|
+
} else if (args.method === "skills/get") {
|
|
16705
|
+
if (!args.uri) {
|
|
16706
|
+
throw new Error(
|
|
16707
|
+
"URI is required for skills/get method. Use --uri to specify the skill URI."
|
|
16708
|
+
);
|
|
16709
|
+
}
|
|
16710
|
+
assertSkillsSupported(inspectorClient, args.method);
|
|
16711
|
+
const envelope = await inspectorClient.getSkillResult(
|
|
16712
|
+
args.uri,
|
|
16713
|
+
args.metadata
|
|
16714
|
+
);
|
|
16715
|
+
const skill = envelope.skill;
|
|
16716
|
+
if (args.verify) {
|
|
16717
|
+
const reports = await verifySkills(
|
|
16718
|
+
inspectorClient,
|
|
16719
|
+
[skill],
|
|
16720
|
+
args.metadata
|
|
16721
|
+
);
|
|
16722
|
+
return {
|
|
16723
|
+
kind: "ndjson",
|
|
16724
|
+
lines: reports,
|
|
16725
|
+
summary: summarizeSkillVerification(reports),
|
|
16726
|
+
// Three outcomes, three exit codes: a broken MUST is 7, a walk the
|
|
16727
|
+
// read bounds cut short is 8, and everything checked and passing is
|
|
16728
|
+
// 0. Collapsing the middle case into either of the others reports
|
|
16729
|
+
// something untrue about the server (Copilot).
|
|
16730
|
+
...allSkillsVerified(reports) ? {} : {
|
|
16731
|
+
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
16732
|
+
}
|
|
16733
|
+
};
|
|
16734
|
+
}
|
|
16735
|
+
result = envelope;
|
|
16736
|
+
} else if (args.method === "resources/directory/read") {
|
|
16737
|
+
if (!args.uri) {
|
|
16738
|
+
throw new Error(
|
|
16739
|
+
"URI is required for resources/directory/read. Use --uri to specify the directory URI."
|
|
16740
|
+
);
|
|
16741
|
+
}
|
|
16742
|
+
result = await inspectorClient.readResourceDirectory(
|
|
16743
|
+
args.uri,
|
|
16744
|
+
args.cursor,
|
|
16745
|
+
args.metadata
|
|
16746
|
+
);
|
|
14691
16747
|
} else if (args.method === "roots/list") {
|
|
14692
16748
|
result = { roots: inspectorClient.getRoots() };
|
|
14693
16749
|
} else if (args.method === "roots/set") {
|
|
@@ -14722,6 +16778,7 @@ async function runMethod(inspectorClient, args) {
|
|
|
14722
16778
|
managedResourcesState?.destroy();
|
|
14723
16779
|
managedResourceTemplatesState?.destroy();
|
|
14724
16780
|
managedPromptsState?.destroy();
|
|
16781
|
+
managedSkillsState?.destroy();
|
|
14725
16782
|
managedTasksState?.destroy();
|
|
14726
16783
|
}
|
|
14727
16784
|
}
|
|
@@ -14736,7 +16793,13 @@ var ONE_SHOT_METHODS = [
|
|
|
14736
16793
|
"resources/templates/list",
|
|
14737
16794
|
"prompts/list",
|
|
14738
16795
|
"prompts/get",
|
|
14739
|
-
"logging/setLevel"
|
|
16796
|
+
"logging/setLevel",
|
|
16797
|
+
// SEP-2640. All three are ordinary one-shot request/response calls — no
|
|
16798
|
+
// stream, no long-lived subscription — so they belong here alongside the
|
|
16799
|
+
// other list verbs rather than being reachable only from the session CLI.
|
|
16800
|
+
"skills/list",
|
|
16801
|
+
"skills/get",
|
|
16802
|
+
"resources/directory/read"
|
|
14740
16803
|
];
|
|
14741
16804
|
function isOneShotMethod(method) {
|
|
14742
16805
|
return ONE_SHOT_METHODS.includes(method);
|
|
@@ -15181,7 +17244,7 @@ function createCliOAuthNavigation(options = {}) {
|
|
|
15181
17244
|
}
|
|
15182
17245
|
|
|
15183
17246
|
// ../../core/client/config-parse.ts
|
|
15184
|
-
import { z as
|
|
17247
|
+
import { z as z5 } from "zod";
|
|
15185
17248
|
function isAbsoluteHttpUrl(value) {
|
|
15186
17249
|
let url;
|
|
15187
17250
|
try {
|
|
@@ -15198,17 +17261,21 @@ function isRealHost(hostname) {
|
|
|
15198
17261
|
const labels = hostname.split(".");
|
|
15199
17262
|
return labels.length >= 2 && labels.every((label) => label !== "");
|
|
15200
17263
|
}
|
|
15201
|
-
var HttpUrlStringSchema =
|
|
17264
|
+
var HttpUrlStringSchema = z5.string().min(1).superRefine((val, ctx) => {
|
|
15202
17265
|
const trimmed = val.trim();
|
|
15203
17266
|
if (!isAbsoluteHttpUrl(trimmed)) {
|
|
15204
17267
|
ctx.addIssue({
|
|
15205
|
-
code:
|
|
17268
|
+
code: z5.ZodIssueCode.custom,
|
|
15206
17269
|
message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
|
|
15207
17270
|
});
|
|
15208
17271
|
}
|
|
15209
17272
|
});
|
|
15210
17273
|
var CIMD_METADATA_URL_INVALID_ERROR = "Must be a valid URL, like https://example.com/oauth/client.json";
|
|
15211
|
-
var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS";
|
|
17274
|
+
var CIMD_METADATA_URL_HTTPS_ERROR = "CIMD client metadata URL must use HTTPS, except on localhost, 127.0.0.1 or [::1]";
|
|
17275
|
+
var CIMD_HTTP_EXEMPT_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
17276
|
+
function isCimdHttpExemptHost(hostname) {
|
|
17277
|
+
return CIMD_HTTP_EXEMPT_HOSTS.has(hostname);
|
|
17278
|
+
}
|
|
15212
17279
|
var CIMD_METADATA_URL_PATH_ERROR = "Must include a path (not the site root), like https://example.com/oauth/client.json";
|
|
15213
17280
|
function getCimdClientMetadataUrlError(value) {
|
|
15214
17281
|
const trimmed = value.trim();
|
|
@@ -15218,7 +17285,7 @@ function getCimdClientMetadataUrlError(value) {
|
|
|
15218
17285
|
}
|
|
15219
17286
|
try {
|
|
15220
17287
|
const url = new URL(trimmed);
|
|
15221
|
-
if (url.protocol !== "https:") {
|
|
17288
|
+
if (url.protocol !== "https:" && !isCimdHttpExemptHost(url.hostname)) {
|
|
15222
17289
|
return CIMD_METADATA_URL_HTTPS_ERROR;
|
|
15223
17290
|
}
|
|
15224
17291
|
if (url.pathname === "/" || url.pathname === "") {
|
|
@@ -15234,7 +17301,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
15234
17301
|
if (trimmed === "") {
|
|
15235
17302
|
if (required) {
|
|
15236
17303
|
ctx.addIssue({
|
|
15237
|
-
code:
|
|
17304
|
+
code: z5.ZodIssueCode.custom,
|
|
15238
17305
|
message: "CIMD client metadata URL is required when CIMD is enabled"
|
|
15239
17306
|
});
|
|
15240
17307
|
}
|
|
@@ -15243,25 +17310,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
15243
17310
|
const error = getCimdClientMetadataUrlError(trimmed);
|
|
15244
17311
|
if (error) {
|
|
15245
17312
|
ctx.addIssue({
|
|
15246
|
-
code:
|
|
17313
|
+
code: z5.ZodIssueCode.custom,
|
|
15247
17314
|
message: error
|
|
15248
17315
|
});
|
|
15249
17316
|
}
|
|
15250
17317
|
}
|
|
15251
|
-
var CimdConfigSchema =
|
|
15252
|
-
enabled:
|
|
15253
|
-
clientMetadataUrl:
|
|
17318
|
+
var CimdConfigSchema = z5.object({
|
|
17319
|
+
enabled: z5.boolean(),
|
|
17320
|
+
clientMetadataUrl: z5.string()
|
|
15254
17321
|
}).superRefine((data, ctx) => {
|
|
15255
17322
|
refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
|
|
15256
17323
|
});
|
|
15257
|
-
var EnterpriseManagedAuthIdpConfigSchema =
|
|
17324
|
+
var EnterpriseManagedAuthIdpConfigSchema = z5.object({
|
|
15258
17325
|
issuer: HttpUrlStringSchema,
|
|
15259
|
-
clientId:
|
|
15260
|
-
clientSecret:
|
|
17326
|
+
clientId: z5.string().min(1),
|
|
17327
|
+
clientSecret: z5.string().optional()
|
|
15261
17328
|
});
|
|
15262
|
-
var ClientConfigSchema =
|
|
15263
|
-
enterpriseManagedAuth:
|
|
15264
|
-
enabled:
|
|
17329
|
+
var ClientConfigSchema = z5.object({
|
|
17330
|
+
enterpriseManagedAuth: z5.object({
|
|
17331
|
+
enabled: z5.boolean().optional(),
|
|
15265
17332
|
idp: EnterpriseManagedAuthIdpConfigSchema
|
|
15266
17333
|
}).optional(),
|
|
15267
17334
|
cimd: CimdConfigSchema.optional()
|
|
@@ -15865,7 +17932,9 @@ function findStoredToken(servers, serverUrl) {
|
|
|
15865
17932
|
}
|
|
15866
17933
|
async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
|
|
15867
17934
|
const refresh = deps.refresh ?? refreshAuthorization;
|
|
15868
|
-
const storedAuthFetch = withRfc8414OidcCompat(
|
|
17935
|
+
const storedAuthFetch = withRfc8414OidcCompat(
|
|
17936
|
+
withOAuthRequestTimeout(createProxyFetch() ?? fetch)
|
|
17937
|
+
);
|
|
15869
17938
|
const discover = deps.discover ?? ((authorizationServerUrl, options) => discoverAuthorizationServerMetadata4(authorizationServerUrl, {
|
|
15870
17939
|
...options,
|
|
15871
17940
|
// A caller-supplied fetch is left alone — it is theirs to compose. The
|
|
@@ -16040,7 +18109,13 @@ async function parseArgs(argv) {
|
|
|
16040
18109
|
"Tool argument as key=value pair",
|
|
16041
18110
|
parseKeyValuePair2,
|
|
16042
18111
|
{}
|
|
16043
|
-
).option(
|
|
18112
|
+
).option(
|
|
18113
|
+
"--uri <uri>",
|
|
18114
|
+
"URI of the resource (resources/read, resources/directory/read) or of the skill (skills/get)"
|
|
18115
|
+
).option(
|
|
18116
|
+
"--cursor <cursor>",
|
|
18117
|
+
"Opaque pagination cursor (for resources/directory/read; pass back the nextCursor from the previous page)."
|
|
18118
|
+
).option(
|
|
16044
18119
|
"--prompt-name <promptName>",
|
|
16045
18120
|
"Name of the prompt (for prompts/get method)"
|
|
16046
18121
|
).option(
|
|
@@ -16092,6 +18167,9 @@ async function parseArgs(argv) {
|
|
|
16092
18167
|
).option(
|
|
16093
18168
|
"--strict",
|
|
16094
18169
|
"Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead."
|
|
18170
|
+
).option(
|
|
18171
|
+
"--verify",
|
|
18172
|
+
"Run the SEP-2640 conformance and digest checks over the skills returned, emit one JSON report per skill on stdout, and exit 7 if any fails or 8 if any could not be fully checked within the read bounds. Use with --method skills/list or --method skills/get."
|
|
16095
18173
|
).option(
|
|
16096
18174
|
"--connect-timeout <ms>",
|
|
16097
18175
|
`Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
|
|
@@ -16102,6 +18180,10 @@ async function parseArgs(argv) {
|
|
|
16102
18180
|
}
|
|
16103
18181
|
return n;
|
|
16104
18182
|
}
|
|
18183
|
+
).option(
|
|
18184
|
+
"--protocol-era <era>",
|
|
18185
|
+
"Protocol era to negotiate: legacy, auto, or modern. Overrides the file-level protocolEra for --catalog/--config runs; ad-hoc --server-url / target runs otherwise default to legacy.",
|
|
18186
|
+
parseProtocolEra
|
|
16105
18187
|
).option(
|
|
16106
18188
|
"--format <format>",
|
|
16107
18189
|
"Output format: text (default; pretty-printed) or json (one JSON object on stdout, no banners).",
|
|
@@ -16195,6 +18277,13 @@ async function parseArgs(argv) {
|
|
|
16195
18277
|
);
|
|
16196
18278
|
}
|
|
16197
18279
|
}
|
|
18280
|
+
if (options.verify) {
|
|
18281
|
+
if (options.method !== "skills/list" && options.method !== "skills/get") {
|
|
18282
|
+
throw new Error(
|
|
18283
|
+
"--verify requires --method skills/list or --method skills/get."
|
|
18284
|
+
);
|
|
18285
|
+
}
|
|
18286
|
+
}
|
|
16198
18287
|
const oauthStatePath = getStateFilePath();
|
|
16199
18288
|
if (options.listStoredAuth) {
|
|
16200
18289
|
const servers = await readOAuthServers(oauthStatePath);
|
|
@@ -16240,7 +18329,10 @@ async function parseArgs(argv) {
|
|
|
16240
18329
|
env: options.e,
|
|
16241
18330
|
// `--header` is merged into the resolved server's settings (overriding any
|
|
16242
18331
|
// file-level headers); file timeouts/OAuth are preserved. See #1482.
|
|
16243
|
-
headers: options.header
|
|
18332
|
+
headers: options.header,
|
|
18333
|
+
// `--protocol-era` feeds `settings.protocolEra` the same way, so an ad-hoc
|
|
18334
|
+
// launch can pick a non-legacy era without an mcp.json entry (#2208).
|
|
18335
|
+
protocolEra: options.protocolEra
|
|
16244
18336
|
};
|
|
16245
18337
|
if (options.method === "servers/list") {
|
|
16246
18338
|
const servers = await listServerEntries(serverOptions);
|
|
@@ -16358,6 +18450,8 @@ async function parseArgs(argv) {
|
|
|
16358
18450
|
toolMeta: options.toolMetadata,
|
|
16359
18451
|
appInfo: options.appInfo === true,
|
|
16360
18452
|
strict: options.strict === true,
|
|
18453
|
+
verify: options.verify === true,
|
|
18454
|
+
cursor: options.cursor,
|
|
16361
18455
|
format: options.format
|
|
16362
18456
|
};
|
|
16363
18457
|
return {
|