@modelcontextprotocol/inspector 2.4.0 → 2.6.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.
@@ -390,6 +390,495 @@ async function deleteStoreFile(filePath) {
390
390
  }
391
391
  }
392
392
 
393
+ // ../../core/json/nullableUnion.ts
394
+ var RENDERABLE_TYPES = [
395
+ "string",
396
+ "number",
397
+ "integer",
398
+ "boolean",
399
+ "array",
400
+ "object"
401
+ ];
402
+ function isRenderableType(type) {
403
+ return RENDERABLE_TYPES.includes(type);
404
+ }
405
+ function toBranch(value) {
406
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
407
+ return null;
408
+ }
409
+ return value;
410
+ }
411
+ function isStringEnum(value) {
412
+ return Array.isArray(value) && value.length > 0 && value.every((member) => typeof member === "string");
413
+ }
414
+ function nullExcludedBySiblings(schema) {
415
+ if (Array.isArray(schema.enum) && !schema.enum.includes(null)) {
416
+ return true;
417
+ }
418
+ return schema.const !== void 0 && schema.const !== null;
419
+ }
420
+ var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
421
+ "title",
422
+ "description",
423
+ "default",
424
+ "enumNames",
425
+ "examples",
426
+ "deprecated",
427
+ "readOnly",
428
+ "writeOnly",
429
+ "$comment",
430
+ "$schema",
431
+ "$id"
432
+ ]);
433
+ function wrapperCarriesConstraints(schema) {
434
+ return Object.keys(schema).some(
435
+ (key) => key !== "anyOf" && !ANNOTATION_KEYWORDS.has(key)
436
+ );
437
+ }
438
+ function typeNamesNull(type) {
439
+ return Array.isArray(type) ? type.includes("null") : type === "null";
440
+ }
441
+ function hasOpaqueApplicator(schema) {
442
+ return schema.not !== void 0 || schema.allOf !== void 0 || schema.oneOf !== void 0 || // `$ref` points at a schema this module never resolves, and it applies
443
+ // *alongside* its siblings — `{ type: "string", $ref: "#/$defs/intOnly" }`
444
+ // can be unsatisfiable. The wrapper path already declines on it (it is not
445
+ // an annotation); this is the same rule for every other position.
446
+ schema.$ref !== void 0;
447
+ }
448
+ function hasUnevaluatedComposition(schema) {
449
+ return schema.anyOf !== void 0 || hasOpaqueApplicator(schema);
450
+ }
451
+ function stripNullEnumMembers(members, names) {
452
+ if (!Array.isArray(members)) {
453
+ return { kind: "unchanged" };
454
+ }
455
+ const kept = members.map((member, index) => ({ member, index })).filter((entry) => entry.member !== null);
456
+ if (kept.length === 0) {
457
+ return { kind: "unselectable" };
458
+ }
459
+ if (kept.length === members.length) {
460
+ return { kind: "unchanged" };
461
+ }
462
+ const wasParallel = Array.isArray(names) && names.length === members.length;
463
+ const aligned = wasParallel ? kept.map((entry) => names[entry.index]) : void 0;
464
+ return {
465
+ kind: "filtered",
466
+ members: kept.map((entry) => entry.member),
467
+ names: aligned
468
+ };
469
+ }
470
+ function collapsed(schema, branch, type) {
471
+ const merged = { ...schema, ...branch };
472
+ const strip = stripNullEnumMembers(
473
+ merged.enum,
474
+ merged.enumNames
475
+ );
476
+ if (strip.kind === "unselectable") {
477
+ return null;
478
+ }
479
+ return {
480
+ ...merged,
481
+ type,
482
+ anyOf: void 0,
483
+ // Read off the ORIGINAL schema, not the merge: a hoisted branch `enum` is
484
+ // not a sibling constraint, and treating it as one would mark the very
485
+ // shape #1928 is about as non-nullable. See `nullExcludedBySiblings`.
486
+ nullable: admitsNull(schema),
487
+ ...strip.kind === "filtered" ? { enum: strip.members, enumNames: strip.names } : {}
488
+ };
489
+ }
490
+ function typeAdmitsNull(type) {
491
+ return type === void 0 || typeNamesNull(type);
492
+ }
493
+ function admitsNull(schema) {
494
+ if (nullExcludedBySiblings(schema)) {
495
+ return false;
496
+ }
497
+ if (hasOpaqueApplicator(schema)) {
498
+ return false;
499
+ }
500
+ if (schema.const === null) {
501
+ if (schema.nullable !== true && !typeAdmitsNull(schema.type)) return false;
502
+ return schema.anyOf === void 0 || anyOfAdmitsNull(schema);
503
+ }
504
+ if (Array.isArray(schema.enum) && schema.enum.includes(null)) {
505
+ return (schema.nullable === true || typeAdmitsNull(schema.type)) && (schema.anyOf === void 0 || anyOfAdmitsNull(schema));
506
+ }
507
+ if (schema.nullable === true) {
508
+ return true;
509
+ }
510
+ if (schema.type !== void 0) {
511
+ if (schema.anyOf !== void 0) {
512
+ return false;
513
+ }
514
+ return typeNamesNull(schema.type);
515
+ }
516
+ return anyOfAdmitsNull(schema);
517
+ }
518
+ var ANNOTATION_ONLY_KEYWORDS = /* @__PURE__ */ new Set([
519
+ "title",
520
+ "description",
521
+ "examples",
522
+ "default",
523
+ "deprecated",
524
+ "readOnly",
525
+ "writeOnly",
526
+ "$comment",
527
+ // Core keywords that identify or declare rather than assert. `$ref` is
528
+ // deliberately absent: it applies whatever it points at, which is exactly an
529
+ // assertion this module cannot see.
530
+ "$defs",
531
+ "$id",
532
+ "$schema",
533
+ "$anchor",
534
+ "$dynamicAnchor",
535
+ "$vocabulary"
536
+ ]);
537
+ function constrainsNothing(schema) {
538
+ return Object.keys(schema).every(
539
+ (keyword) => ANNOTATION_ONLY_KEYWORDS.has(keyword)
540
+ );
541
+ }
542
+ function anyOfAdmitsNull(schema) {
543
+ const branches = schema.anyOf;
544
+ if (!Array.isArray(branches)) return false;
545
+ return branches.some((entry) => {
546
+ if (typeof entry === "boolean") return entry;
547
+ const branch = toBranch(entry);
548
+ if (branch === null) return false;
549
+ const branchSchema = branch;
550
+ if (hasUnevaluatedComposition(branchSchema)) return false;
551
+ if (constrainsNothing(branchSchema)) return true;
552
+ return admitsNull(branchSchema);
553
+ });
554
+ }
555
+ function normalizeNullableUnion(schema) {
556
+ if (schema.anyOf?.length === 2) {
557
+ const branches = schema.anyOf.map(toBranch);
558
+ const nullBranch = branches.find((entry) => entry?.type === "null");
559
+ const branch = branches.find(
560
+ (entry) => entry !== null && entry.type !== "null"
561
+ );
562
+ if (nullBranch && branch && !wrapperCarriesConstraints(schema) && !hasUnevaluatedComposition(branch)) {
563
+ const type = branch.type ?? (isStringEnum(branch.enum) ? "string" : void 0);
564
+ if (isRenderableType(type)) {
565
+ const result = collapsed(schema, branch, type);
566
+ if (result !== null) {
567
+ return result;
568
+ }
569
+ }
570
+ }
571
+ }
572
+ if (Array.isArray(schema.type) && schema.type.length === 2 && schema.type.includes("null") && // This path keeps the schema's own keywords, but `collapsed` clears `anyOf`
573
+ // and the renderers ignore the other applicators — so a compound schema
574
+ // like `{ type: ["string", "null"], anyOf: [{ const: "a" }] }` would be
575
+ // widened into an unconstrained string field. Leave it for the JSON editor.
576
+ !hasUnevaluatedComposition(schema)) {
577
+ const type = schema.type.find((member) => member !== "null");
578
+ if (isRenderableType(type)) {
579
+ const result = collapsed(schema, void 0, type);
580
+ if (result !== null) {
581
+ return result;
582
+ }
583
+ }
584
+ }
585
+ return schema;
586
+ }
587
+
588
+ // ../../core/json/rootUnion.ts
589
+ function membersOf(value) {
590
+ return Array.isArray(value) ? value : [];
591
+ }
592
+ function requiredOf(schema) {
593
+ const { required } = schema;
594
+ return Array.isArray(required) ? required.filter((name) => typeof name === "string") : [];
595
+ }
596
+ function toBranch2(value) {
597
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
598
+ return null;
599
+ }
600
+ return value;
601
+ }
602
+ function admitsObject(schema) {
603
+ const { type } = schema;
604
+ if (type === void 0) return true;
605
+ return Array.isArray(type) ? type.includes("object") : type === "object";
606
+ }
607
+ function hasReadableProperties(schema) {
608
+ const { properties } = schema;
609
+ return typeof properties === "object" && properties !== null && !Array.isArray(properties);
610
+ }
611
+ function propertiesOf(schema) {
612
+ return hasReadableProperties(schema) ? schema.properties : {};
613
+ }
614
+ function isOfferable(branch, merged) {
615
+ if (branch.properties !== void 0 && !hasReadableProperties(branch)) {
616
+ return false;
617
+ }
618
+ if (!admitsObject(branch)) return false;
619
+ const mergedProperties = propertiesOf(merged);
620
+ if (requiredOf(merged).some((name) => !Object.hasOwn(mergedProperties, name))) {
621
+ return false;
622
+ }
623
+ const properties = Object.values(mergedProperties);
624
+ return properties.length > 0 && // Every value has to be something a renderer can read AND something a
625
+ // caller can satisfy. A `null` or an array is not a schema at all, and the
626
+ // web form dereferences one on the way to choosing a widget. JSON Schema's
627
+ // boolean form is legal, but only `true` is harmless — it constrains
628
+ // nothing and answers every keyword lookup with `undefined`, while `false`
629
+ // admits no value whatsoever, so a field declared with it can never be
630
+ // filled and a required one makes the whole branch unsatisfiable. Either
631
+ // way the branch is declined rather than offered as a callable shape.
632
+ properties.every(
633
+ (property) => property === true || typeof property === "object" && property !== null && !Array.isArray(property)
634
+ );
635
+ }
636
+ var ANNOTATION_KEYWORDS2 = /* @__PURE__ */ new Set([
637
+ "title",
638
+ "description",
639
+ "examples",
640
+ "deprecated",
641
+ "readOnly",
642
+ "writeOnly",
643
+ "$comment",
644
+ // An annotation too: a suggested initial value constrains nothing, so two
645
+ // declarations suggesting different ones still accept the same values.
646
+ "default"
647
+ ]);
648
+ function canonicalJson(value) {
649
+ if (Array.isArray(value)) {
650
+ return `[${value.map(canonicalJson).join(",")}]`;
651
+ }
652
+ if (typeof value === "object" && value !== null) {
653
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(
654
+ ([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`
655
+ );
656
+ return `{${entries.join(",")}}`;
657
+ }
658
+ return JSON.stringify(value) ?? "undefined";
659
+ }
660
+ function sameJsonValue(a, b) {
661
+ return sameValue(a, b);
662
+ }
663
+ function sameValue(a, b) {
664
+ return a === b || canonicalJson(a) === canonicalJson(b);
665
+ }
666
+ function conflicts(baseProperty, branchProperty) {
667
+ const a = toBranch2(baseProperty);
668
+ const b = toBranch2(branchProperty);
669
+ if (a === null || b === null) {
670
+ return !sameValue(baseProperty, branchProperty);
671
+ }
672
+ const left = a;
673
+ const right = b;
674
+ if (Object.keys(right).some(
675
+ (keyword) => !ANNOTATION_KEYWORDS2.has(keyword) && keyword in left && !sameValue(left[keyword], right[keyword])
676
+ )) {
677
+ return true;
678
+ }
679
+ return !constSatisfiesSiblings({ ...left, ...right });
680
+ }
681
+ function jsonTypeOf(value) {
682
+ if (value === null) return "null";
683
+ if (Array.isArray(value)) return "array";
684
+ if (typeof value === "number") {
685
+ return Number.isInteger(value) ? "integer" : "number";
686
+ }
687
+ return typeof value;
688
+ }
689
+ var CONST_CHECKABLE = /* @__PURE__ */ new Set(["const", "type", "enum"]);
690
+ function constSatisfiesSiblings(schema) {
691
+ if (!("const" in schema)) return true;
692
+ const value = schema.const;
693
+ const { type, enum: allowed } = schema;
694
+ if (Object.keys(schema).some(
695
+ (keyword) => !CONST_CHECKABLE.has(keyword) && !ANNOTATION_KEYWORDS2.has(keyword)
696
+ )) {
697
+ return false;
698
+ }
699
+ const actual = jsonTypeOf(value);
700
+ const admits = (name) => name === actual || name === "number" && actual === "integer";
701
+ if (typeof type === "string" && !admits(type)) return false;
702
+ if (Array.isArray(type) && !type.some(admits)) return false;
703
+ if (Array.isArray(allowed) && !allowed.some((member) => sameValue(member, value))) {
704
+ return false;
705
+ }
706
+ return true;
707
+ }
708
+ function mergeProperty(baseProperty, branchProperty) {
709
+ const a = toBranch2(baseProperty);
710
+ const b = toBranch2(branchProperty);
711
+ if (a === null || b === null) {
712
+ return branchProperty;
713
+ }
714
+ return { ...a, ...b };
715
+ }
716
+ var MERGEABLE_KEYWORDS = /* @__PURE__ */ new Set(["type", "properties", "required"]);
717
+ function isFlattenable(member) {
718
+ const branch = toBranch2(member);
719
+ if (branch === null || !admitsObject(branch)) return false;
720
+ return Object.keys(branch).every(
721
+ (keyword) => MERGEABLE_KEYWORDS.has(keyword) || ANNOTATION_KEYWORDS2.has(keyword)
722
+ );
723
+ }
724
+ function conflictsWithBase(base, branch) {
725
+ const baseProperties = propertiesOf(base);
726
+ const branchProperties = propertiesOf(branch);
727
+ return Object.entries(branchProperties).some(
728
+ ([name, branchProperty]) => (
729
+ // `hasOwn`, not `in`: `properties` is a JSON record, so `constructor` and
730
+ // `toString` are legal argument names that `in` would find on
731
+ // `Object.prototype` and report as collisions the root never declared.
732
+ Object.hasOwn(baseProperties, name) && conflicts(baseProperties[name], branchProperty)
733
+ )
734
+ );
735
+ }
736
+ function hasDiscriminator(members, named, rootRequired) {
737
+ const first = propertiesOf(members[0] ?? {});
738
+ const candidates = named !== void 0 ? [named] : Object.keys(first);
739
+ return candidates.some((name) => {
740
+ const constants = members.map((member) => {
741
+ if (!rootRequired.includes(name) && !requiredOf(member).includes(name)) {
742
+ return void 0;
743
+ }
744
+ const property = toBranch2(propertiesOf(member)[name]);
745
+ return property?.const;
746
+ });
747
+ if (constants.some((value) => value === void 0)) return false;
748
+ const seen = new Set(constants.map(canonicalJson));
749
+ return seen.size === constants.length;
750
+ });
751
+ }
752
+ function restrictsAdditional(schema) {
753
+ const additional = schema.additionalProperties;
754
+ return additional === false || typeof additional === "object" && additional !== null && Object.keys(additional).some(
755
+ (keyword) => !ANNOTATION_KEYWORDS2.has(keyword)
756
+ );
757
+ }
758
+ function addsForbiddenNames(base, member) {
759
+ if (!restrictsAdditional(base)) return false;
760
+ const baseNames = propertiesOf(base);
761
+ return Object.keys(propertiesOf(member)).some(
762
+ (name) => !Object.hasOwn(baseNames, name)
763
+ );
764
+ }
765
+ function mergeBranch(base, branch) {
766
+ const baseProperties = propertiesOf(base);
767
+ const branchProperties = propertiesOf(branch);
768
+ const properties = Object.fromEntries([
769
+ ...Object.entries(baseProperties),
770
+ ...Object.entries(branchProperties).map(([name, branchProperty]) => [
771
+ name,
772
+ Object.hasOwn(baseProperties, name) ? mergeProperty(baseProperties[name], branchProperty) : branchProperty
773
+ ])
774
+ ]);
775
+ const baseRequired = requiredOf(base);
776
+ const required = [
777
+ ...baseRequired,
778
+ ...requiredOf(branch).filter((name) => !baseRequired.includes(name))
779
+ ];
780
+ return {
781
+ ...base,
782
+ ...Object.keys(properties).length > 0 ? { properties } : {},
783
+ ...required.length > 0 ? { required } : {}
784
+ };
785
+ }
786
+ function withoutComposition(schema) {
787
+ const { allOf: _allOf, anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
788
+ return rest;
789
+ }
790
+ function branchLabel(branch, index, discriminatorProperty) {
791
+ if (typeof branch.title === "string" && branch.title.trim() !== "") {
792
+ return branch.title;
793
+ }
794
+ const properties = propertiesOf(branch);
795
+ const constOf = (name) => {
796
+ const property = toBranch2(properties[name]);
797
+ if (property === null || !("const" in property)) return void 0;
798
+ const value = property.const;
799
+ return value === null || typeof value !== "object" ? String(value) : void 0;
800
+ };
801
+ if (discriminatorProperty !== void 0) {
802
+ const value = constOf(discriminatorProperty);
803
+ if (value !== void 0) return value;
804
+ }
805
+ const constants = Object.keys(properties).map((name) => constOf(name)).filter((value) => value !== void 0);
806
+ if (constants.length === 1) return constants[0];
807
+ return `Option ${index + 1}`;
808
+ }
809
+ function resolveRootUnion(schema) {
810
+ const allOfMembers = membersOf(schema.allOf);
811
+ let merged = schema;
812
+ for (const member of allOfMembers) {
813
+ const branch = toBranch2(member);
814
+ if (branch === null || !isFlattenable(member) || conflictsWithBase(merged, branch) || addsForbiddenNames(merged, branch)) {
815
+ return { base: schema, branches: [] };
816
+ }
817
+ merged = mergeBranch(merged, branch);
818
+ }
819
+ const base = withoutComposition(merged);
820
+ if (schema.oneOf !== void 0 && schema.anyOf !== void 0) {
821
+ return { base, branches: [] };
822
+ }
823
+ const isExclusiveUnion = schema.oneOf !== void 0;
824
+ const members = membersOf(schema.oneOf ?? schema.anyOf);
825
+ const branches = members.map(toBranch2);
826
+ if (branches.length === 0 || branches.some(
827
+ (branch) => branch === null || !isOfferable(branch, mergeBranch(base, branch)) || // The same faithfulness test the `allOf` fold applies: a member
828
+ // carrying a constraint the merge does not copy — a nested `allOf`, a
829
+ // `not`, a `$ref` — would have it erased along with the union keyword,
830
+ // which can present an unsatisfiable branch as a callable one.
831
+ !isFlattenable(branch) || conflictsWithBase(base, branch)
832
+ )) {
833
+ return { base, branches: [] };
834
+ }
835
+ if (branches.some(
836
+ (branch) => addsForbiddenNames(base, branch)
837
+ )) {
838
+ return { base, branches: [] };
839
+ }
840
+ if (isExclusiveUnion && !hasDiscriminator(
841
+ branches,
842
+ schema.discriminator?.propertyName,
843
+ requiredOf(base)
844
+ )) {
845
+ return { base, branches: [] };
846
+ }
847
+ const discriminatorProperty = schema.discriminator?.propertyName;
848
+ return {
849
+ base,
850
+ branches: branches.filter((branch) => branch !== null).map((branch, index) => ({
851
+ // `base` is already composition-free and `mergeBranch` copies only
852
+ // `properties`/`required` off the branch, so the merge stays that way.
853
+ schema: mergeBranch(base, branch),
854
+ label: branchLabel(branch, index, discriminatorProperty),
855
+ declaredFields: Object.keys(propertiesOf(branch))
856
+ }))
857
+ };
858
+ }
859
+ function narrowBySuppliedNames(branches, candidates, suppliedNames) {
860
+ if (candidates.length === 1) return candidates[0];
861
+ if (candidates.length === 0 || suppliedNames.length === 0) return null;
862
+ const supplied = new Set(suppliedNames);
863
+ const satisfied = candidates.filter((index) => {
864
+ const branch = branches[index];
865
+ const required = requiredOf(branch.schema);
866
+ const own = required.filter((name) => branch.declaredFields.includes(name));
867
+ return own.length > 0 && own.every((name) => supplied.has(name));
868
+ });
869
+ if (satisfied.length === 1) return satisfied[0];
870
+ const exclusiveTo = /* @__PURE__ */ new Map();
871
+ for (const index of candidates) {
872
+ for (const name of branches[index].declaredFields) {
873
+ exclusiveTo.set(name, exclusiveTo.has(name) ? -1 : index);
874
+ }
875
+ }
876
+ const named = new Set(
877
+ suppliedNames.map((name) => exclusiveTo.get(name)).filter((index) => index !== void 0 && index >= 0)
878
+ );
879
+ return named.size === 1 ? [...named][0] : null;
880
+ }
881
+
393
882
  // ../../core/json/jsonUtils.ts
394
883
  function isSerializableJson(value) {
395
884
  if (value === null) return true;
@@ -429,16 +918,90 @@ function convertParameterValue(value, schema) {
429
918
  }
430
919
  return value;
431
920
  }
921
+ function coercionProperties(base, branches, params) {
922
+ if (branches.length === 0) {
923
+ return { ...base.properties };
924
+ }
925
+ const candidates = branches.map((branch, index) => ({ branch, index })).filter(
926
+ ({ branch }) => matchesConstants(branch.schema.properties ?? {}, params)
927
+ ).map(({ index }) => index);
928
+ const selected = narrowBySuppliedNames(
929
+ branches,
930
+ candidates,
931
+ Object.keys(params)
932
+ );
933
+ if (selected !== null) {
934
+ return { ...branches[selected].schema.properties };
935
+ }
936
+ const properties = Object.fromEntries(
937
+ Object.entries(base.properties ?? {})
938
+ );
939
+ const pool = candidates.length > 0 ? candidates.map((i) => branches[i]) : branches;
940
+ for (const name of new Set(pool.flatMap((b) => b.declaredFields))) {
941
+ const declarations = pool.filter((branch) => branch.declaredFields.includes(name)).map((branch) => branch.schema.properties?.[name]).filter((schema) => typeof schema === "object" && schema !== null).map((schema) => normalizeNullableUnion(schema));
942
+ const types = new Set(declarations.map((schema) => typeNameOf(schema)));
943
+ const pinnedOf = (schema) => typeof schema === "object" && schema !== null && "const" in schema ? schema.const : void 0;
944
+ const constsAgree = declarations.every(
945
+ (schema) => sameJsonValue(pinnedOf(schema), pinnedOf(declarations[0]))
946
+ );
947
+ if (types.size === 1 && constsAgree && declarations.length > 0) {
948
+ Object.defineProperty(properties, name, {
949
+ value: declarations[0],
950
+ writable: true,
951
+ enumerable: true,
952
+ configurable: true
953
+ });
954
+ } else {
955
+ delete properties[name];
956
+ }
957
+ }
958
+ return properties;
959
+ }
960
+ function typeNameOf(schema) {
961
+ if (typeof schema !== "object" || schema === null) return "";
962
+ const { type } = schema;
963
+ return Array.isArray(type) ? [...type].map(String).sort().join(",") : typeof type === "string" ? type : "";
964
+ }
965
+ function matchesConstants(properties, params) {
966
+ return Object.entries(properties).every(([name, schema]) => {
967
+ if (typeof schema !== "object" || schema === null) return true;
968
+ const constValue = schema.const;
969
+ if (constValue === void 0) return true;
970
+ if (!Object.hasOwn(params, name)) return true;
971
+ const supplied = params[name];
972
+ return supplied === void 0 || suppliedMatchesConst(supplied, constValue);
973
+ });
974
+ }
975
+ function suppliedMatchesConst(value, constValue) {
976
+ if (constValue === null || typeof constValue !== "object") {
977
+ return value === String(constValue);
978
+ }
979
+ try {
980
+ return sameJsonValue(JSON.parse(value), constValue);
981
+ } catch {
982
+ return false;
983
+ }
984
+ }
432
985
  function convertToolParameters(tool, params) {
986
+ return convertParametersForSchema(tool.inputSchema, params);
987
+ }
988
+ function convertParametersForSchema(inputSchema, params) {
433
989
  const result = {};
434
- const properties = tool.inputSchema?.properties || {};
990
+ const { base, branches } = resolveRootUnion(
991
+ inputSchema ?? {}
992
+ );
993
+ const properties = coercionProperties(base, branches, params);
435
994
  for (const [key, value] of Object.entries(params)) {
436
- const paramSchema = properties[key];
437
- if (paramSchema) {
438
- result[key] = convertParameterValue(value, paramSchema);
439
- } else {
440
- result[key] = value;
441
- }
995
+ const declared = properties[key];
996
+ const paramSchema = typeof declared === "object" && declared !== null ? normalizeNullableUnion(declared) : declared;
997
+ const pinned = paramSchema?.const;
998
+ const converted = pinned !== void 0 && suppliedMatchesConst(value, pinned) ? pinned : paramSchema ? convertParameterValue(value, paramSchema) : value;
999
+ Object.defineProperty(result, key, {
1000
+ value: converted,
1001
+ writable: true,
1002
+ enumerable: true,
1003
+ configurable: true
1004
+ });
442
1005
  }
443
1006
  return result;
444
1007
  }
@@ -675,6 +1238,9 @@ function storedFieldsToInspectorSettings(stored) {
675
1238
  if (stored.oauth?.requestRefreshToken === false) {
676
1239
  settings.oauthRequestRefreshToken = false;
677
1240
  }
1241
+ if (stored.oauth?.revokeOnClear === false) {
1242
+ settings.oauthRevokeOnClear = false;
1243
+ }
678
1244
  if (stored.cwd) settings.cwd = stored.cwd;
679
1245
  return settings;
680
1246
  }
@@ -3460,6 +4026,25 @@ var OAuthStorageBase = class {
3460
4026
  this.memory.getState().setServerState(serverUrl, { discoveryState: void 0 });
3461
4027
  await this.persist();
3462
4028
  }
4029
+ async takeRevocationSnapshot(serverUrl) {
4030
+ await this.ensureLoaded();
4031
+ const state = this.memory.getState().getServerState(serverUrl);
4032
+ const snapshot = {
4033
+ byIssuer: Object.fromEntries(
4034
+ Object.entries(state.byIssuer ?? {}).map(([issuer, slot]) => [
4035
+ issuer,
4036
+ { tokens: slot?.tokens, clientInformation: slot?.clientInformation }
4037
+ ])
4038
+ ),
4039
+ legacyTokens: state.tokens,
4040
+ legacyClientInformation: state.clientInformation,
4041
+ preregisteredClientInformation: state.preregisteredClientInformation,
4042
+ serverMetadata: state.serverMetadata
4043
+ };
4044
+ this.memory.getState().clearServerState(serverUrl);
4045
+ await this.persist();
4046
+ return snapshot;
4047
+ }
3463
4048
  async clear(serverUrl) {
3464
4049
  await this.ensureLoaded();
3465
4050
  this.memory.getState().clearServerState(serverUrl);
@@ -3650,6 +4235,292 @@ var NodeOAuthStorage = class extends OAuthStorageBase {
3650
4235
  }
3651
4236
  };
3652
4237
 
4238
+ // ../../core/auth/revocation.ts
4239
+ import { selectClientAuthMethod } from "@modelcontextprotocol/client";
4240
+ import {
4241
+ OAuthClientInformationSchema as OAuthClientInformationSchema2,
4242
+ OAuthTokensSchema as OAuthTokensSchema2
4243
+ } from "@modelcontextprotocol/core";
4244
+ var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
4245
+ var MIN_REVOCATION_REQUEST_BUDGET_MS = 5;
4246
+ function selectRevocableToken(tokens) {
4247
+ if (!tokens) return null;
4248
+ if (tokens.refresh_token) {
4249
+ return { token: tokens.refresh_token, tokenTypeHint: "refresh_token" };
4250
+ }
4251
+ if (tokens.access_token) {
4252
+ return { token: tokens.access_token, tokenTypeHint: "access_token" };
4253
+ }
4254
+ return null;
4255
+ }
4256
+ function revocationAuthMethods(metadata) {
4257
+ return metadata.revocation_endpoint_auth_methods_supported ?? [
4258
+ "client_secret_basic"
4259
+ ];
4260
+ }
4261
+ function buildRevocationRequest(params) {
4262
+ const body = new URLSearchParams({
4263
+ token: params.token,
4264
+ token_type_hint: params.tokenTypeHint
4265
+ });
4266
+ const headers = {
4267
+ "Content-Type": "application/x-www-form-urlencoded",
4268
+ Accept: "application/json"
4269
+ };
4270
+ const client = params.clientInformation;
4271
+ if (client) {
4272
+ const method = selectClientAuthMethod(client, params.supportedAuthMethods);
4273
+ if (method === "client_secret_basic" && client.client_secret) {
4274
+ const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
4275
+ headers.Authorization = `Basic ${base64Encode(credentials)}`;
4276
+ } else if (method === "client_secret_post" && client.client_secret) {
4277
+ body.set("client_id", client.client_id);
4278
+ body.set("client_secret", client.client_secret);
4279
+ } else {
4280
+ body.set("client_id", client.client_id);
4281
+ }
4282
+ }
4283
+ return {
4284
+ url: params.endpoint,
4285
+ init: { method: "POST", headers, body: body.toString() }
4286
+ };
4287
+ }
4288
+ function base64Encode(value) {
4289
+ if (typeof btoa === "function") return btoa(value);
4290
+ return Buffer.from(value, "utf8").toString("base64");
4291
+ }
4292
+ async function revokeToken(params) {
4293
+ const timeoutMs = Math.max(
4294
+ 0,
4295
+ Math.round(params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS)
4296
+ );
4297
+ try {
4298
+ const { url, init } = buildRevocationRequest(params);
4299
+ const response = await withDeadline(
4300
+ params.fetchFn(url, { ...init, signal: AbortSignal.timeout(timeoutMs) }),
4301
+ timeoutMs
4302
+ );
4303
+ if (!response.ok) {
4304
+ return {
4305
+ status: "failed",
4306
+ endpoint: url,
4307
+ detail: `revocation endpoint responded ${response.status} ${response.statusText}`.trim()
4308
+ };
4309
+ }
4310
+ return {
4311
+ status: "revoked",
4312
+ tokenTypeHint: params.tokenTypeHint,
4313
+ endpoint: url
4314
+ };
4315
+ } catch (err) {
4316
+ return {
4317
+ // `params.endpoint`, not the built `url`: construction is inside the try
4318
+ // now, so `url` may not exist on this path.
4319
+ status: "failed",
4320
+ endpoint: params.endpoint,
4321
+ detail: err instanceof Error ? err.message : String(err)
4322
+ };
4323
+ }
4324
+ }
4325
+ async function withDeadline(promise, timeoutMs) {
4326
+ let timer;
4327
+ const deadline = new Promise((_resolve, reject) => {
4328
+ timer = setTimeout(
4329
+ () => reject(new Error(`revocation request timed out after ${timeoutMs}ms`)),
4330
+ timeoutMs
4331
+ );
4332
+ });
4333
+ try {
4334
+ return await Promise.race([promise, deadline]);
4335
+ } finally {
4336
+ if (timer !== void 0) clearTimeout(timer);
4337
+ }
4338
+ }
4339
+ function emptyPlan(serverUrl, outcome, failures = []) {
4340
+ return {
4341
+ serverUrl,
4342
+ grants: [],
4343
+ failures,
4344
+ supportedAuthMethods: [],
4345
+ outcome: failures.length > 0 ? aggregateOutcomes([...failures, outcome]) : outcome
4346
+ };
4347
+ }
4348
+ async function clearAndPlanRevocation(params) {
4349
+ const { serverUrl, storage } = params;
4350
+ let snapshot;
4351
+ try {
4352
+ snapshot = await storage.takeRevocationSnapshot(serverUrl);
4353
+ } catch (err) {
4354
+ throw err instanceof Error ? err : new Error(String(err));
4355
+ }
4356
+ if (params.enabled === false) {
4357
+ return emptyPlan(serverUrl, { status: "skipped", reason: "disabled" });
4358
+ }
4359
+ try {
4360
+ const { grants, failures } = await collectGrants(snapshot);
4361
+ if (grants.length === 0) {
4362
+ return emptyPlan(
4363
+ serverUrl,
4364
+ { status: "skipped", reason: "no_tokens" },
4365
+ failures
4366
+ );
4367
+ }
4368
+ const metadata = parseServerMetadata(snapshot.serverMetadata);
4369
+ if (!metadata) {
4370
+ return emptyPlan(
4371
+ serverUrl,
4372
+ { status: "skipped", reason: "no_metadata" },
4373
+ failures
4374
+ );
4375
+ }
4376
+ if (!metadata.revocation_endpoint) {
4377
+ return emptyPlan(
4378
+ serverUrl,
4379
+ { status: "skipped", reason: "no_endpoint" },
4380
+ failures
4381
+ );
4382
+ }
4383
+ return {
4384
+ serverUrl,
4385
+ grants,
4386
+ failures,
4387
+ endpoint: metadata.revocation_endpoint,
4388
+ supportedAuthMethods: revocationAuthMethods(metadata),
4389
+ metadataIssuer: metadata.issuer
4390
+ };
4391
+ } catch (err) {
4392
+ return emptyPlan(serverUrl, {
4393
+ status: "failed",
4394
+ detail: err instanceof Error ? err.message : String(err)
4395
+ });
4396
+ }
4397
+ }
4398
+ function parseServerMetadata(raw) {
4399
+ if (raw === null || typeof raw !== "object") return void 0;
4400
+ return raw;
4401
+ }
4402
+ async function executeOAuthRevocation(plan, params) {
4403
+ const outcome = await runPlan(plan, params);
4404
+ const { logger } = params;
4405
+ if (outcome.status === "failed") {
4406
+ logger?.warn(
4407
+ {
4408
+ serverUrl: plan.serverUrl,
4409
+ endpoint: outcome.endpoint,
4410
+ detail: outcome.detail
4411
+ },
4412
+ "Token revocation failed; local OAuth state was cleared anyway"
4413
+ );
4414
+ } else if (outcome.status === "skipped" && outcome.reason === "no_endpoint") {
4415
+ logger?.debug(
4416
+ { serverUrl: plan.serverUrl },
4417
+ "Skipping token revocation: authorization server metadata has no revocation_endpoint"
4418
+ );
4419
+ }
4420
+ return outcome;
4421
+ }
4422
+ async function collectGrants(snapshot) {
4423
+ const grants = [];
4424
+ const failures = [];
4425
+ const seenKeys = /* @__PURE__ */ new Set();
4426
+ let preregistered;
4427
+ try {
4428
+ preregistered = await parseClient(snapshot.preregisteredClientInformation);
4429
+ } catch (err) {
4430
+ failures.push({
4431
+ status: "failed",
4432
+ detail: `could not read the preconfigured client registration: ${err instanceof Error ? err.message : String(err)}`
4433
+ });
4434
+ }
4435
+ const add2 = async (issuer, rawTokens, rawClient) => {
4436
+ const revocable = selectRevocableToken(await parseTokens(rawTokens));
4437
+ if (!revocable) return;
4438
+ const key = `${issuer ?? "\0legacy"}\0${revocable.token}`;
4439
+ if (seenKeys.has(key)) return;
4440
+ seenKeys.add(key);
4441
+ const bound = await parseClient(rawClient);
4442
+ grants.push({
4443
+ issuer,
4444
+ ...revocable,
4445
+ clientInformation: bound ?? preregistered
4446
+ });
4447
+ };
4448
+ const addSafely = async (issuer, rawTokens, rawClient) => {
4449
+ try {
4450
+ await add2(issuer, rawTokens, rawClient);
4451
+ } catch (err) {
4452
+ const where = issuer === void 0 ? "the unkeyed slot" : `issuer ${issuer}`;
4453
+ failures.push({
4454
+ status: "failed",
4455
+ detail: `could not read the stored grant for ${where}: ${err instanceof Error ? err.message : String(err)}`
4456
+ });
4457
+ }
4458
+ };
4459
+ for (const [issuer, slot] of Object.entries(snapshot.byIssuer)) {
4460
+ await addSafely(issuer, slot?.tokens, slot?.clientInformation);
4461
+ }
4462
+ await addSafely(
4463
+ void 0,
4464
+ snapshot.legacyTokens,
4465
+ snapshot.legacyClientInformation
4466
+ );
4467
+ return { grants, failures };
4468
+ }
4469
+ async function parseTokens(raw) {
4470
+ return raw === void 0 || raw === null ? void 0 : await OAuthTokensSchema2.parseAsync(raw);
4471
+ }
4472
+ async function parseClient(raw) {
4473
+ return raw === void 0 || raw === null ? void 0 : await OAuthClientInformationSchema2.parseAsync(raw);
4474
+ }
4475
+ function aggregateOutcomes(outcomes) {
4476
+ return outcomes.find((o) => o.status === "failed") ?? outcomes.find((o) => o.status === "revoked") ?? outcomes[0] ?? { status: "skipped", reason: "no_tokens" };
4477
+ }
4478
+ async function runPlan(plan, params) {
4479
+ if (plan.outcome) return plan.outcome;
4480
+ const endpoint = plan.endpoint;
4481
+ if (!endpoint) return { status: "skipped", reason: "no_endpoint" };
4482
+ const outcomes = [...plan.failures];
4483
+ const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
4484
+ const deadlineAt = performance.now() + timeoutMs;
4485
+ for (const grant of plan.grants) {
4486
+ if (grant.issuer === void 0) {
4487
+ outcomes.push({
4488
+ status: "failed",
4489
+ detail: "the stored grant is not bound to an authorization server (it predates issuer binding), so it could not be matched to this revocation endpoint and was cleared without revocation \u2014 re-authorize to make future clears revocable"
4490
+ });
4491
+ continue;
4492
+ }
4493
+ if (plan.metadataIssuer !== grant.issuer) {
4494
+ outcomes.push({
4495
+ status: "failed",
4496
+ detail: plan.metadataIssuer === void 0 ? `the cached authorization-server metadata names no issuer, so the grant bound to ${grant.issuer} could not be matched to this revocation endpoint and was cleared without revocation` : `the cached authorization-server metadata is for ${plan.metadataIssuer}, so the grant bound to ${grant.issuer} was cleared without revocation`
4497
+ });
4498
+ continue;
4499
+ }
4500
+ const remainingMs = deadlineAt - performance.now();
4501
+ if (remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
4502
+ outcomes.push({
4503
+ status: "failed",
4504
+ endpoint,
4505
+ detail: `the ${timeoutMs}ms revocation budget was exhausted before this grant was attempted${grant.issuer === void 0 ? "" : ` (issuer ${grant.issuer})`}`
4506
+ });
4507
+ continue;
4508
+ }
4509
+ outcomes.push(
4510
+ await revokeToken({
4511
+ endpoint,
4512
+ token: grant.token,
4513
+ tokenTypeHint: grant.tokenTypeHint,
4514
+ clientInformation: grant.clientInformation,
4515
+ supportedAuthMethods: plan.supportedAuthMethods,
4516
+ fetchFn: params.fetchFn,
4517
+ timeoutMs: remainingMs
4518
+ })
4519
+ );
4520
+ }
4521
+ return aggregateOutcomes(outcomes);
4522
+ }
4523
+
3653
4524
  // src/clear-stored-auth-for-relogin.ts
3654
4525
  function normalizeServerUrl(serverUrl) {
3655
4526
  try {
@@ -3658,16 +4529,56 @@ function normalizeServerUrl(serverUrl) {
3658
4529
  return serverUrl;
3659
4530
  }
3660
4531
  }
3661
- async function clearStoredAuthForRelogin(serverUrl) {
3662
- if (!serverUrl?.trim()) return;
4532
+ async function clearStoredAuthForRelogin(serverUrl, options) {
4533
+ if (!serverUrl?.trim()) return void 0;
3663
4534
  const raw = serverUrl.trim();
3664
4535
  const normalized = normalizeServerUrl(raw);
3665
4536
  const storage = new NodeOAuthStorage();
3666
- await storage.clear(raw);
3667
- if (normalized !== raw) {
3668
- await storage.clear(normalized);
4537
+ const keys = normalized === raw ? [raw] : [normalized, raw];
4538
+ const plans = [];
4539
+ for (const key of keys) {
4540
+ plans.push(
4541
+ await clearAndPlanRevocation({
4542
+ serverUrl: key,
4543
+ storage,
4544
+ enabled: options?.revoke !== false
4545
+ })
4546
+ );
3669
4547
  }
3670
4548
  resetNodeOAuthStorageCache();
4549
+ return options?.revoke === false ? void 0 : sendPlans(plans, options?.budgetMs ?? DEFAULT_REVOCATION_TIMEOUT_MS);
4550
+ }
4551
+ async function sendPlans(plans, budgetMs) {
4552
+ const fetchFn = createProxyFetch() ?? fetch;
4553
+ const deadlineAt = performance.now() + budgetMs;
4554
+ let reported;
4555
+ let lastSkip;
4556
+ for (const plan of plans) {
4557
+ const remainingMs = deadlineAt - performance.now();
4558
+ const needsNetwork = plan.outcome === void 0;
4559
+ if (needsNetwork && remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
4560
+ const exhausted = {
4561
+ status: "failed",
4562
+ detail: `the ${budgetMs}ms revocation budget was exhausted before "${plan.serverUrl}" was attempted`
4563
+ };
4564
+ if (reported?.status !== "failed") reported = exhausted;
4565
+ continue;
4566
+ }
4567
+ const outcome = await executeOAuthRevocation(plan, {
4568
+ fetchFn,
4569
+ timeoutMs: needsNetwork ? remainingMs : void 0
4570
+ });
4571
+ if (outcome.status === "skipped" && outcome.reason === "no_tokens") {
4572
+ lastSkip = outcome;
4573
+ continue;
4574
+ }
4575
+ if (outcome.status === "failed") {
4576
+ if (reported?.status !== "failed") reported = outcome;
4577
+ } else {
4578
+ reported ??= outcome;
4579
+ }
4580
+ }
4581
+ return reported ?? lastSkip;
3671
4582
  }
3672
4583
 
3673
4584
  // ../../core/mcp/inspectorClient.ts
@@ -3830,6 +4741,20 @@ function mcpParamHeadersForTool(tool, args) {
3830
4741
  return buildMcpParamHeaders(scan.declarations, args);
3831
4742
  }
3832
4743
 
4744
+ // ../../core/mcp/subscriptionAck.ts
4745
+ import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
4746
+ var NEVER_ACKNOWLEDGED_SDK_MESSAGE = "closed the subscription gracefully before acknowledging";
4747
+ var NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE = "The server closed the subscription without acknowledging it. A subscriptions/listen request must first be answered with a notifications/subscriptions/acknowledged notification; a JSON-RPC result for the listen id means graceful closure. Not retrying \u2014 the server would answer the same way again.";
4748
+ function isNeverAcknowledgedSubscriptionClose(error) {
4749
+ return SdkError.isInstance(error) && error.code === SdkErrorCode.ConnectionClosed && error.message.includes(NEVER_ACKNOWLEDGED_SDK_MESSAGE);
4750
+ }
4751
+ function subscriptionFailureMessage(error) {
4752
+ if (isNeverAcknowledgedSubscriptionClose(error)) {
4753
+ return NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE;
4754
+ }
4755
+ return error instanceof Error ? error.message : String(error);
4756
+ }
4757
+
3833
4758
  // ../../core/mcp/messageTrackingTransport.ts
3834
4759
  var MessageTrackingTransport = class {
3835
4760
  baseTransport;
@@ -3921,6 +4846,29 @@ var MessageTrackingTransport = class {
3921
4846
  get sessionId() {
3922
4847
  return this.baseTransport.sessionId;
3923
4848
  }
4849
+ /**
4850
+ * Forward the base transport's per-request-stream capability (#2140).
4851
+ *
4852
+ * The SDK's `Protocol.request` reads this off the transport it was handed —
4853
+ * which is always this wrapper — to decide how a cancelled request is
4854
+ * signalled: abort that request's own stream (the 2026-07-28 signal for
4855
+ * Streamable HTTP) or POST a `notifications/cancelled` (the stdio one).
4856
+ *
4857
+ * Not forwarding it made the wrapper *answer* the question, `undefined`, for
4858
+ * every client. So the Cancel button POSTed a notification even on a modern
4859
+ * Streamable HTTP connection, where a spec-compliant server acknowledges it
4860
+ * `202` and drops it — the tool kept running and only a disconnect really
4861
+ * cancelled anything. That hit the CLI and TUI as much as the web client:
4862
+ * their real `StreamableHTTPClientTransport` advertises the flag correctly
4863
+ * and this wrapper hid it.
4864
+ *
4865
+ * A base transport that says nothing stays `undefined` rather than becoming
4866
+ * `false`, since the SDK's check is `=== true` either way and inventing a
4867
+ * value here would misreport what the transport claimed.
4868
+ */
4869
+ get hasPerRequestStream() {
4870
+ return this.baseTransport.hasPerRequestStream;
4871
+ }
3924
4872
  // Implemented as a concrete method (rather than delegating the base
3925
4873
  // transport's optional `setProtocolVersion`) so the SDK Client always
3926
4874
  // invokes it after the initialize handshake — including for stdio, whose
@@ -4028,14 +4976,661 @@ function buildClientExtensions(input) {
4028
4976
  map[ext.key] = ext.advertisement ? structuredClone(ext.advertisement) : {};
4029
4977
  }
4030
4978
  }
4031
- const uiAdvertisement = map[UI_EXTENSION_KEY];
4032
- if (input.appElicitation && uiAdvertisement) {
4033
- map[UI_EXTENSION_KEY] = { ...uiAdvertisement, elicitation: {} };
4979
+ const uiAdvertisement = map[UI_EXTENSION_KEY];
4980
+ if (input.appElicitation && uiAdvertisement) {
4981
+ map[UI_EXTENSION_KEY] = { ...uiAdvertisement, elicitation: {} };
4982
+ }
4983
+ if (input.enterpriseManaged) {
4984
+ map[EMA_EXTENSION_KEY] = {};
4985
+ }
4986
+ return map;
4987
+ }
4988
+
4989
+ // ../../core/mcp/skillsSchemas.ts
4990
+ import { z as z2 } from "zod/v4";
4991
+ import { ResourceSchema } from "@modelcontextprotocol/core";
4992
+ var SKILLS_EXTENSION_KEY = "io.modelcontextprotocol/skills";
4993
+ var SKILLS_LIST_METHOD = "skills/list";
4994
+ var SKILLS_GET_METHOD = "skills/get";
4995
+ var RESOURCES_DIRECTORY_READ_METHOD = "resources/directory/read";
4996
+ var DYNAMIC_RESOURCES = "dynamic";
4997
+ var SkillFrontmatterSchema = z2.looseObject({
4998
+ name: z2.string().optional(),
4999
+ description: z2.string().optional()
5000
+ });
5001
+ var SkillResourceSchema = z2.looseObject({
5002
+ uri: z2.string(),
5003
+ digest: z2.string().optional(),
5004
+ size: z2.number().optional()
5005
+ });
5006
+ var SkillEntrySchema = z2.looseObject({
5007
+ uri: z2.string(),
5008
+ frontmatter: SkillFrontmatterSchema,
5009
+ resources: z2.union([
5010
+ z2.literal(DYNAMIC_RESOURCES),
5011
+ z2.array(SkillResourceSchema)
5012
+ ])
5013
+ });
5014
+ var ListSkillsResultSchema = z2.looseObject({
5015
+ skills: z2.array(SkillEntrySchema),
5016
+ nextCursor: z2.string().optional()
5017
+ });
5018
+ var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
5019
+ resultType: z2.literal("complete"),
5020
+ // A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
5021
+ // envelope violation, and accepting it here would hide exactly the kind of
5022
+ // defect this schema exists to surface.
5023
+ ttlMs: z2.int().min(0),
5024
+ cacheScope: z2.enum(["public", "private"])
5025
+ });
5026
+ var GetSkillEnvelopeSchema = z2.looseObject({
5027
+ skill: SkillEntrySchema
5028
+ });
5029
+ var ModernGetSkillEnvelopeSchema = GetSkillEnvelopeSchema.extend({
5030
+ resultType: z2.literal("complete")
5031
+ });
5032
+ var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
5033
+ (result) => result.skill
5034
+ );
5035
+ var DirectoryChildSchema = ResourceSchema;
5036
+ var DirectoryReadResultSchema = z2.looseObject({
5037
+ resources: z2.array(DirectoryChildSchema),
5038
+ nextCursor: z2.string().optional()
5039
+ });
5040
+ var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
5041
+ { resultType: z2.literal("complete") }
5042
+ );
5043
+
5044
+ // ../../core/mcp/sha256.ts
5045
+ var K = new Uint32Array([
5046
+ 1116352408,
5047
+ 1899447441,
5048
+ 3049323471,
5049
+ 3921009573,
5050
+ 961987163,
5051
+ 1508970993,
5052
+ 2453635748,
5053
+ 2870763221,
5054
+ 3624381080,
5055
+ 310598401,
5056
+ 607225278,
5057
+ 1426881987,
5058
+ 1925078388,
5059
+ 2162078206,
5060
+ 2614888103,
5061
+ 3248222580,
5062
+ 3835390401,
5063
+ 4022224774,
5064
+ 264347078,
5065
+ 604807628,
5066
+ 770255983,
5067
+ 1249150122,
5068
+ 1555081692,
5069
+ 1996064986,
5070
+ 2554220882,
5071
+ 2821834349,
5072
+ 2952996808,
5073
+ 3210313671,
5074
+ 3336571891,
5075
+ 3584528711,
5076
+ 113926993,
5077
+ 338241895,
5078
+ 666307205,
5079
+ 773529912,
5080
+ 1294757372,
5081
+ 1396182291,
5082
+ 1695183700,
5083
+ 1986661051,
5084
+ 2177026350,
5085
+ 2456956037,
5086
+ 2730485921,
5087
+ 2820302411,
5088
+ 3259730800,
5089
+ 3345764771,
5090
+ 3516065817,
5091
+ 3600352804,
5092
+ 4094571909,
5093
+ 275423344,
5094
+ 430227734,
5095
+ 506948616,
5096
+ 659060556,
5097
+ 883997877,
5098
+ 958139571,
5099
+ 1322822218,
5100
+ 1537002063,
5101
+ 1747873779,
5102
+ 1955562222,
5103
+ 2024104815,
5104
+ 2227730452,
5105
+ 2361852424,
5106
+ 2428436474,
5107
+ 2756734187,
5108
+ 3204031479,
5109
+ 3329325298
5110
+ ]);
5111
+ var H0 = new Uint32Array([
5112
+ 1779033703,
5113
+ 3144134277,
5114
+ 1013904242,
5115
+ 2773480762,
5116
+ 1359893119,
5117
+ 2600822924,
5118
+ 528734635,
5119
+ 1541459225
5120
+ ]);
5121
+ var rotr = (x, n) => x >>> n | x << 32 - n;
5122
+ function sha256Bytes(bytes) {
5123
+ const message = new Uint8Array(bytes);
5124
+ const bitLength = message.length * 8;
5125
+ const withLength = message.length + 9;
5126
+ const padded = new Uint8Array(Math.ceil(withLength / 64) * 64);
5127
+ padded.set(message);
5128
+ padded[message.length] = 128;
5129
+ const view = new DataView(padded.buffer);
5130
+ view.setUint32(padded.length - 8, Math.floor(bitLength / 4294967296));
5131
+ view.setUint32(padded.length - 4, bitLength >>> 0);
5132
+ const h = new Uint32Array(H0);
5133
+ const w = new Uint32Array(64);
5134
+ for (let offset = 0; offset < padded.length; offset += 64) {
5135
+ for (let i = 0; i < 16; i += 1) w[i] = view.getUint32(offset + i * 4);
5136
+ for (let i = 16; i < 64; i += 1) {
5137
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
5138
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
5139
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
5140
+ }
5141
+ let [a, b, c, d, e, f, g, hh] = h;
5142
+ for (let i = 0; i < 64; i += 1) {
5143
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
5144
+ const ch = e & f ^ ~e & g;
5145
+ const temp1 = hh + S1 + ch + K[i] + w[i] >>> 0;
5146
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
5147
+ const maj = a & b ^ a & c ^ b & c;
5148
+ const temp2 = S0 + maj >>> 0;
5149
+ hh = g;
5150
+ g = f;
5151
+ f = e;
5152
+ e = d + temp1 >>> 0;
5153
+ d = c;
5154
+ c = b;
5155
+ b = a;
5156
+ a = temp1 + temp2 >>> 0;
5157
+ }
5158
+ h[0] = h[0] + a >>> 0;
5159
+ h[1] = h[1] + b >>> 0;
5160
+ h[2] = h[2] + c >>> 0;
5161
+ h[3] = h[3] + d >>> 0;
5162
+ h[4] = h[4] + e >>> 0;
5163
+ h[5] = h[5] + f >>> 0;
5164
+ h[6] = h[6] + g >>> 0;
5165
+ h[7] = h[7] + hh >>> 0;
5166
+ }
5167
+ const digest = new Uint8Array(32);
5168
+ const out = new DataView(digest.buffer);
5169
+ for (let i = 0; i < 8; i += 1) out.setUint32(i * 4, h[i]);
5170
+ return digest;
5171
+ }
5172
+
5173
+ // ../../core/mcp/skillFile.ts
5174
+ import { parse as parseYaml } from "yaml";
5175
+ function splitSkillFile(text) {
5176
+ if (!/^---[ \t]*\r?\n/.test(text)) return { body: text };
5177
+ const rest = text.slice(text.indexOf("\n") + 1);
5178
+ const close = rest.search(/^---[ \t]*\r?$/m);
5179
+ if (close === -1) return { body: text };
5180
+ const frontmatter = rest.slice(0, close).replace(/\r?\n$/, "");
5181
+ const after = rest.slice(close);
5182
+ const newline = after.indexOf("\n");
5183
+ if (newline === -1) return { frontmatter, body: "" };
5184
+ return { frontmatter, body: after.slice(newline + 1).replace(/^\r?\n/, "") };
5185
+ }
5186
+ function hasContent(yamlText) {
5187
+ return yamlText.split("\n").some((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
5188
+ }
5189
+ var MAX_FRONTMATTER_DEPTH = 64;
5190
+ function jsonGraphError(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
5191
+ if (typeof value !== "object" || value === null) return void 0;
5192
+ if (depth > MAX_FRONTMATTER_DEPTH) {
5193
+ return `Frontmatter nests deeper than ${MAX_FRONTMATTER_DEPTH} levels, which cannot be compared field by field.`;
5194
+ }
5195
+ const node = value;
5196
+ if (seen.has(node)) {
5197
+ return "Frontmatter contains a cyclic YAML alias, which has no JSON equivalent and cannot be compared against the listing.";
5198
+ }
5199
+ seen.add(node);
5200
+ const members = Array.isArray(node) ? node : Object.values(node);
5201
+ for (const member of members) {
5202
+ const error = jsonGraphError(member, seen, depth + 1);
5203
+ if (error) return error;
5204
+ }
5205
+ seen.delete(node);
5206
+ return void 0;
5207
+ }
5208
+ function parseSkillFrontmatter(yamlText) {
5209
+ let parsed;
5210
+ try {
5211
+ parsed = parseYaml(yamlText);
5212
+ } catch (err) {
5213
+ return { error: err instanceof Error ? err.message : String(err) };
5214
+ }
5215
+ if (parsed === null || parsed === void 0) {
5216
+ return hasContent(yamlText) ? { error: "Frontmatter is not a YAML mapping of fields." } : { fields: {} };
5217
+ }
5218
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
5219
+ return {
5220
+ error: "Frontmatter is not a YAML mapping of fields."
5221
+ };
5222
+ }
5223
+ const graphError = jsonGraphError(parsed, /* @__PURE__ */ new Set(), 0);
5224
+ if (graphError) return { error: graphError };
5225
+ return { fields: parsed };
5226
+ }
5227
+
5228
+ // ../../core/mcp/skills.ts
5229
+ var SKILL_MAX_RESOURCE_ENTRIES = 512;
5230
+ var SKILL_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
5231
+ var SKILL_MAX_CATALOG_SKILLS = 256;
5232
+ var SKILL_MAX_CATALOG_BYTES = 64 * 1024 * 1024;
5233
+ var SKILL_FILE_SUFFIX = "/SKILL.md";
5234
+ var DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
5235
+ var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5236
+ var SKILL_NAME_MAX_LENGTH = 64;
5237
+ var SKILL_DESCRIPTION_MAX_LENGTH = 1024;
5238
+ function codePointLength(value) {
5239
+ return [...value].length;
5240
+ }
5241
+ function getSkillsExtension(capabilities) {
5242
+ const declared = capabilities?.extensions?.[SKILLS_EXTENSION_KEY];
5243
+ if (typeof declared !== "object" || declared === null) return void 0;
5244
+ const directoryRead = declared.directoryRead === true;
5245
+ return { directoryRead };
5246
+ }
5247
+ function normalizeSkillUri(uri) {
5248
+ let parsed;
5249
+ try {
5250
+ parsed = new URL(uri);
5251
+ } catch {
5252
+ return void 0;
5253
+ }
5254
+ if (!parsed.pathname.startsWith("/")) return void 0;
5255
+ return canonicalizePercentEncoding(parsed.href);
5256
+ }
5257
+ function canonicalizePercentEncoding(value) {
5258
+ return value.replace(/%[0-9a-fA-F]{2}/g, (escape) => {
5259
+ const char = String.fromCharCode(Number.parseInt(escape.slice(1), 16));
5260
+ return /[A-Za-z0-9\-._~]/.test(char) ? char : escape.toUpperCase();
5261
+ });
5262
+ }
5263
+ function skillNameFromUri(uri) {
5264
+ const normalized = normalizeSkillUri(uri);
5265
+ if (normalized === void 0 || !normalized.endsWith(SKILL_FILE_SUFFIX)) {
5266
+ return void 0;
5267
+ }
5268
+ const path5 = normalized.slice(0, -SKILL_FILE_SUFFIX.length);
5269
+ const segment = path5.slice(path5.lastIndexOf("/") + 1);
5270
+ return segment.length > 0 ? segment : void 0;
5271
+ }
5272
+ function skillUriIdentity(uri) {
5273
+ return normalizeSkillUri(uri) ?? uri;
5274
+ }
5275
+ function skillDisplayName(entry) {
5276
+ const declared = entry.frontmatter.name?.trim();
5277
+ if (declared) return declared;
5278
+ return skillNameFromUri(entry.uri) ?? entry.uri;
5279
+ }
5280
+ function checkSkillConformance(entry) {
5281
+ const issues = [];
5282
+ const rawName = entry.frontmatter.name;
5283
+ const declaredName = rawName?.trim() ? rawName : void 0;
5284
+ const uriName = skillNameFromUri(entry.uri);
5285
+ if (!declaredName) {
5286
+ issues.push({
5287
+ code: "missing-name",
5288
+ severity: "error",
5289
+ message: "frontmatter.name is required but missing or empty."
5290
+ });
5291
+ } else if (codePointLength(declaredName) > SKILL_NAME_MAX_LENGTH || !SKILL_NAME_PATTERN.test(declaredName)) {
5292
+ issues.push({
5293
+ code: "malformed-name",
5294
+ severity: "error",
5295
+ 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.`
5296
+ });
5297
+ }
5298
+ const rawDescription = entry.frontmatter.description;
5299
+ if (!rawDescription?.trim()) {
5300
+ issues.push({
5301
+ code: "missing-description",
5302
+ severity: "error",
5303
+ message: "frontmatter.description is required but missing or empty."
5304
+ });
5305
+ } else if (codePointLength(rawDescription) > SKILL_DESCRIPTION_MAX_LENGTH) {
5306
+ issues.push({
5307
+ code: "malformed-description",
5308
+ severity: "error",
5309
+ message: `frontmatter.description is ${codePointLength(rawDescription)} characters, above the ${SKILL_DESCRIPTION_MAX_LENGTH}-character limit.`
5310
+ });
5311
+ }
5312
+ if (uriName === void 0) {
5313
+ issues.push({
5314
+ code: "malformed-uri",
5315
+ severity: "error",
5316
+ message: `Skill URI must be a hierarchical URI ending with "${SKILL_FILE_SUFFIX}" and carrying a non-empty path segment before it.`
5317
+ });
5318
+ } else if (declaredName && uriName !== declaredName) {
5319
+ issues.push({
5320
+ code: "name-path-mismatch",
5321
+ severity: "error",
5322
+ message: `URI path segment "${uriName}" does not match frontmatter.name "${declaredName}".`
5323
+ });
5324
+ }
5325
+ if (entry.resources === DYNAMIC_RESOURCES) {
5326
+ issues.push({
5327
+ code: "dynamic-resources",
5328
+ severity: "warning",
5329
+ message: 'resources is "dynamic": the file set is generated, so no digest is advertised and integrity cannot be verified.'
5330
+ });
5331
+ return issues;
5332
+ }
5333
+ if (entry.resources.length > SKILL_MAX_RESOURCE_ENTRIES) {
5334
+ issues.push({
5335
+ code: "resource-limit-exceeded",
5336
+ severity: "warning",
5337
+ 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.`
5338
+ });
5339
+ }
5340
+ const totalBytes = totalSkillBytes(entry.resources);
5341
+ if (totalBytes > SKILL_MAX_TOTAL_BYTES) {
5342
+ issues.push({
5343
+ code: "size-limit-exceeded",
5344
+ severity: "warning",
5345
+ 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.`
5346
+ });
5347
+ }
5348
+ const entryIdentity = skillUriIdentity(entry.uri);
5349
+ if (!entry.resources.some(
5350
+ (resource) => skillUriIdentity(resource.uri) === entryIdentity
5351
+ )) {
5352
+ issues.push({
5353
+ code: "manifest-missing-self",
5354
+ severity: "error",
5355
+ message: `Manifest does not list the skill's own entry file (${entry.uri}); a manifest must be the complete file set.`
5356
+ });
5357
+ }
5358
+ const seenUris = /* @__PURE__ */ new Set();
5359
+ const normalizedEntryUri = normalizeSkillUri(entry.uri);
5360
+ const root = normalizedEntryUri !== void 0 && normalizedEntryUri.endsWith(SKILL_FILE_SUFFIX) ? `${normalizedEntryUri.slice(0, -SKILL_FILE_SUFFIX.length)}/` : void 0;
5361
+ for (const resource of entry.resources) {
5362
+ const identity2 = skillUriIdentity(resource.uri);
5363
+ if (seenUris.has(identity2)) {
5364
+ issues.push({
5365
+ code: "duplicate-resource",
5366
+ severity: "error",
5367
+ message: "Manifest lists this URI more than once; entries must be unique.",
5368
+ resourceUri: resource.uri
5369
+ });
5370
+ }
5371
+ seenUris.add(identity2);
5372
+ if (root !== void 0) {
5373
+ const normalized = normalizeSkillUri(resource.uri);
5374
+ if (normalized === void 0 || !normalized.startsWith(root)) {
5375
+ issues.push({
5376
+ code: "resource-outside-skill-root",
5377
+ severity: "error",
5378
+ message: `Manifest entry does not resolve inside the skill root "${root}".`,
5379
+ resourceUri: resource.uri
5380
+ });
5381
+ }
5382
+ }
5383
+ if (resource.digest === void 0) {
5384
+ issues.push({
5385
+ code: "missing-digest",
5386
+ severity: "error",
5387
+ message: "Manifest entry declares no digest, which is required \u2014 and without it the file cannot be verified.",
5388
+ resourceUri: resource.uri
5389
+ });
5390
+ } else if (!DIGEST_PATTERN.test(resource.digest)) {
5391
+ issues.push({
5392
+ code: "malformed-digest",
5393
+ severity: "error",
5394
+ message: `Digest "${resource.digest}" is not "sha256:" followed by 64 lowercase hex characters.`,
5395
+ resourceUri: resource.uri
5396
+ });
5397
+ }
5398
+ if (resource.size === void 0) {
5399
+ issues.push({
5400
+ code: "missing-size",
5401
+ severity: "error",
5402
+ 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.",
5403
+ resourceUri: resource.uri
5404
+ });
5405
+ } else if (!isUsableSize(resource.size)) {
5406
+ issues.push({
5407
+ code: "malformed-size",
5408
+ severity: "error",
5409
+ message: `Size ${resource.size} is not a non-negative integer byte length.`,
5410
+ resourceUri: resource.uri
5411
+ });
5412
+ }
5413
+ }
5414
+ return issues;
5415
+ }
5416
+ function isUsableSize(size) {
5417
+ return size !== void 0 && Number.isSafeInteger(size) && size >= 0;
5418
+ }
5419
+ function totalSkillBytes(resources) {
5420
+ return resources.reduce(
5421
+ (sum, r) => sum + (isUsableSize(r.size) ? r.size : 0),
5422
+ 0
5423
+ );
5424
+ }
5425
+ function canonicalize(value) {
5426
+ if (Array.isArray(value)) return value.map(canonicalize);
5427
+ if (value === null || typeof value !== "object") return value;
5428
+ return sortKeys(value);
5429
+ }
5430
+ function sortKeys(value) {
5431
+ return Object.fromEntries(
5432
+ Object.entries(value).map(([key, member]) => [key, canonicalize(member)]).sort(([x], [y]) => x.localeCompare(y))
5433
+ );
5434
+ }
5435
+ function toHex(bytes) {
5436
+ let out = "";
5437
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
5438
+ return out;
5439
+ }
5440
+ async function sha256Digest(bytes) {
5441
+ const subtle = globalThis.crypto?.subtle;
5442
+ if (!subtle) return `sha256:${toHex(sha256Bytes(bytes))}`;
5443
+ const copy = new Uint8Array(bytes);
5444
+ const hash = await subtle.digest("SHA-256", copy.buffer);
5445
+ return `sha256:${toHex(new Uint8Array(hash))}`;
5446
+ }
5447
+ function textToBytes(text) {
5448
+ return new TextEncoder().encode(text);
5449
+ }
5450
+ function bytesToText(bytes) {
5451
+ return new TextDecoder().decode(bytes);
5452
+ }
5453
+ function base64ToBytes(blob) {
5454
+ const binary = atob(blob);
5455
+ const bytes = new Uint8Array(binary.length);
5456
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
5457
+ return bytes;
5458
+ }
5459
+ function skillFileBytes(contents) {
5460
+ if (typeof contents.text === "string") return textToBytes(contents.text);
5461
+ if (typeof contents.blob === "string") return base64ToBytes(contents.blob);
5462
+ throw new Error("resources/read returned neither text nor blob content.");
5463
+ }
5464
+ async function verifySkillResource(resource, bytes) {
5465
+ const expectedSize = resource.size;
5466
+ if (expectedSize !== void 0 && expectedSize !== bytes.byteLength) {
5467
+ return {
5468
+ status: "mismatch",
5469
+ expectedSize,
5470
+ actualSize: bytes.byteLength,
5471
+ ...resource.digest !== void 0 ? { expectedDigest: resource.digest } : {},
5472
+ reason: `Manifest declares ${expectedSize} bytes but the fetched file is ${bytes.byteLength}.`
5473
+ };
5474
+ }
5475
+ const expectedDigest = resource.digest;
5476
+ if (expectedDigest === void 0) {
5477
+ return {
5478
+ status: "unverifiable",
5479
+ reason: "The manifest entry advertises no digest."
5480
+ };
5481
+ }
5482
+ if (!DIGEST_PATTERN.test(expectedDigest)) {
5483
+ return {
5484
+ status: "unverifiable",
5485
+ expectedDigest,
5486
+ reason: 'The advertised digest is not "sha256:" followed by 64 lowercase hex characters.'
5487
+ };
5488
+ }
5489
+ const actualDigest = await sha256Digest(bytes);
5490
+ return {
5491
+ status: actualDigest === expectedDigest ? "verified" : "mismatch",
5492
+ actualDigest,
5493
+ expectedDigest,
5494
+ ...expectedSize !== void 0 ? { expectedSize, actualSize: bytes.byteLength } : {}
5495
+ };
5496
+ }
5497
+ function jsonLikeEqual(a, b) {
5498
+ if (typeof a === "number" || typeof b === "number") {
5499
+ if (typeof a === "number" && typeof b === "number") {
5500
+ return Number.isFinite(a) && Number.isFinite(b) ? a === b : Object.is(a, b);
5501
+ }
5502
+ return false;
5503
+ }
5504
+ if (a === null || b === null) return a === b;
5505
+ if (typeof a !== "object" || typeof b !== "object") return Object.is(a, b);
5506
+ const aArray = Array.isArray(a);
5507
+ if (aArray !== Array.isArray(b)) return false;
5508
+ if (aArray) {
5509
+ const x2 = a;
5510
+ const y2 = b;
5511
+ return x2.length === y2.length && x2.every((v, i) => jsonLikeEqual(v, y2[i]));
5512
+ }
5513
+ const x = a;
5514
+ const y = b;
5515
+ const keys = Object.keys(x);
5516
+ return keys.length === Object.keys(y).length && keys.every((k) => Object.hasOwn(y, k) && jsonLikeEqual(x[k], y[k]));
5517
+ }
5518
+ function displayValue(value) {
5519
+ if (typeof value === "number" && !Number.isFinite(value)) {
5520
+ return String(value);
4034
5521
  }
4035
- if (input.enterpriseManaged) {
4036
- map[EMA_EXTENSION_KEY] = {};
5522
+ return JSON.stringify(
5523
+ canonicalize(value),
5524
+ (_key, member) => typeof member === "number" && !Number.isFinite(member) ? `<${String(member)}>` : member
5525
+ );
5526
+ }
5527
+ function checkSkillFrontmatterMatch(entry, skillFileText) {
5528
+ const { frontmatter } = splitSkillFile(skillFileText);
5529
+ if (frontmatter === void 0) {
5530
+ return [
5531
+ {
5532
+ code: "frontmatter-absent",
5533
+ severity: "error",
5534
+ message: "The served SKILL.md carries no YAML frontmatter block, so the listing's frontmatter cannot be the file's.",
5535
+ resourceUri: entry.uri
5536
+ }
5537
+ ];
4037
5538
  }
4038
- return map;
5539
+ const listedError = jsonGraphError(entry.frontmatter);
5540
+ if (listedError) {
5541
+ return [
5542
+ {
5543
+ code: "frontmatter-unparsable",
5544
+ severity: "error",
5545
+ message: `The listing's own frontmatter cannot be compared: ${listedError}`,
5546
+ resourceUri: entry.uri
5547
+ }
5548
+ ];
5549
+ }
5550
+ const parsed = parseSkillFrontmatter(frontmatter);
5551
+ if ("error" in parsed) {
5552
+ return [
5553
+ {
5554
+ code: "frontmatter-unparsable",
5555
+ severity: "error",
5556
+ message: `The served SKILL.md's frontmatter is not valid YAML: ${parsed.error}`,
5557
+ resourceUri: entry.uri
5558
+ }
5559
+ ];
5560
+ }
5561
+ const issues = [];
5562
+ const fields = [
5563
+ .../* @__PURE__ */ new Set([
5564
+ ...Object.keys(entry.frontmatter),
5565
+ ...Object.keys(parsed.fields)
5566
+ ])
5567
+ ].sort();
5568
+ for (const field of fields) {
5569
+ const listed = entry.frontmatter[field];
5570
+ const served = parsed.fields[field];
5571
+ if (listed === void 0) {
5572
+ issues.push({
5573
+ code: "frontmatter-mismatch",
5574
+ severity: "error",
5575
+ message: `The served SKILL.md declares "${field}" but the listing's frontmatter omits it.`,
5576
+ resourceUri: entry.uri
5577
+ });
5578
+ continue;
5579
+ }
5580
+ if (served === void 0) {
5581
+ issues.push({
5582
+ code: "frontmatter-mismatch",
5583
+ severity: "error",
5584
+ message: `The listing declares "${field}" but the served SKILL.md omits it.`,
5585
+ resourceUri: entry.uri
5586
+ });
5587
+ continue;
5588
+ }
5589
+ if (!jsonLikeEqual(listed, served)) {
5590
+ issues.push({
5591
+ code: "frontmatter-mismatch",
5592
+ severity: "error",
5593
+ message: `Field "${field}" differs: the listing says ${displayValue(listed)} but the served SKILL.md says ${displayValue(served)}.`,
5594
+ resourceUri: entry.uri
5595
+ });
5596
+ }
5597
+ }
5598
+ return issues;
5599
+ }
5600
+ var COLLISION_SAMPLE = 3;
5601
+ function checkSkillNameCollisions(entries) {
5602
+ const byName = /* @__PURE__ */ new Map();
5603
+ for (const entry of entries) {
5604
+ const name = entry.frontmatter.name;
5605
+ if (typeof name !== "string" || name.trim() === "") continue;
5606
+ const group = byName.get(name);
5607
+ if (group) group.push(entry);
5608
+ else byName.set(name, [entry]);
5609
+ }
5610
+ const issues = /* @__PURE__ */ new Map();
5611
+ for (const [name, group] of byName) {
5612
+ const identities = new Set(group.map((e) => skillUriIdentity(e.uri)));
5613
+ if (identities.size < 2) continue;
5614
+ const uris = [...identities].sort();
5615
+ for (const entry of group) {
5616
+ const self = skillUriIdentity(entry.uri);
5617
+ const sample = [];
5618
+ for (const uri of uris) {
5619
+ if (uri === self) continue;
5620
+ sample.push(uri);
5621
+ if (sample.length === COLLISION_SAMPLE) break;
5622
+ }
5623
+ const unshown = identities.size - 1 - sample.length;
5624
+ const others = unshown > 0 ? `${sample.join(", ")}, and ${unshown} more` : sample.join(", ");
5625
+ const subject = identities.size === 2 ? "Another skill in this listing also declares" : `${identities.size - 1} other skills in this listing also declare`;
5626
+ issues.set(self, {
5627
+ code: "duplicate-name",
5628
+ severity: "warning",
5629
+ 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.`
5630
+ });
5631
+ }
5632
+ }
5633
+ return issues;
4039
5634
  }
4040
5635
 
4041
5636
  // ../../core/mcp/appElicitation.ts
@@ -4125,7 +5720,7 @@ import {
4125
5720
  ListResourcesResultSchema,
4126
5721
  ListResourceTemplatesResultSchema,
4127
5722
  ListPromptsResultSchema,
4128
- ResourceSchema,
5723
+ ResourceSchema as ResourceSchema2,
4129
5724
  ResourceTemplateSchema,
4130
5725
  PromptSchema
4131
5726
  } from "@modelcontextprotocol/core";
@@ -4148,8 +5743,8 @@ function validateToolOutput(provider, tool, result) {
4148
5743
  }
4149
5744
 
4150
5745
  // ../../core/mcp/listSalvage.ts
4151
- import { z as z2 } from "zod/v4";
4152
- import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
5746
+ import { z as z3 } from "zod/v4";
5747
+ import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
4153
5748
  import { ToolSchema } from "@modelcontextprotocol/core";
4154
5749
  var LIST_MAX_PAGES = 64;
4155
5750
  function listPaginationExceeded(method) {
@@ -4158,26 +5753,26 @@ function listPaginationExceeded(method) {
4158
5753
  );
4159
5754
  }
4160
5755
  function isClientDecodeRejection(err) {
4161
- return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
5756
+ return SdkError2.isInstance(err) && (err.code === SdkErrorCode2.InvalidResult || err.code === SdkErrorCode2.UnsupportedResultType);
4162
5757
  }
4163
5758
  function isSalvageableRejection(err) {
4164
- return SdkError.isInstance(err) && err.code === SdkErrorCode.InvalidResult;
5759
+ return SdkError2.isInstance(err) && err.code === SdkErrorCode2.InvalidResult;
4165
5760
  }
4166
5761
  function lenientListPageSchema(resultSchema, itemsKey) {
4167
- return resultSchema.extend({ [itemsKey]: z2.array(z2.unknown()) });
5762
+ return resultSchema.extend({ [itemsKey]: z3.array(z3.unknown()) });
4168
5763
  }
4169
5764
  function nextCursorOf(page) {
4170
5765
  if (typeof page !== "object" || page === null) return void 0;
4171
5766
  const value = page.nextCursor;
4172
5767
  return typeof value === "string" ? value : void 0;
4173
5768
  }
4174
- var ModernResultEnvelopeSchema = z2.looseObject({
4175
- resultType: z2.literal("complete"),
5769
+ var ModernResultEnvelopeSchema = z3.looseObject({
5770
+ resultType: z3.literal("complete"),
4176
5771
  // A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
4177
5772
  // envelope violation the strict path rejects, and accepting it here on the
4178
5773
  // strength of an unrelated bad entry would hide it.
4179
- ttlMs: z2.int().min(0),
4180
- cacheScope: z2.enum(["public", "private"])
5774
+ ttlMs: z3.int().min(0),
5775
+ cacheScope: z3.enum(["public", "private"])
4181
5776
  });
4182
5777
  function rawItemsOf(page, itemsKey) {
4183
5778
  if (typeof page !== "object" || page === null) return void 0;
@@ -4246,9 +5841,9 @@ var LegacyToolWireSchema = ToolSchema.extend({
4246
5841
  outputSchema: ToolSchema.shape.inputSchema.optional()
4247
5842
  });
4248
5843
  var ModernToolWireSchema = ToolSchema.extend({
4249
- inputSchema: z2.looseObject({
4250
- type: z2.literal("object"),
4251
- $schema: z2.string().optional()
5844
+ inputSchema: z3.looseObject({
5845
+ type: z3.literal("object"),
5846
+ $schema: z3.string().optional()
4252
5847
  })
4253
5848
  });
4254
5849
  function stripExecution(tool) {
@@ -4262,10 +5857,10 @@ function eraToolItemSchema(wire, normalize) {
4262
5857
 
4263
5858
  // ../../core/mcp/taskNotificationSchemas.ts
4264
5859
  import { NotificationSchema } from "@modelcontextprotocol/core";
4265
- import { z as z3 } from "zod/v4";
5860
+ import { z as z4 } from "zod/v4";
4266
5861
  var TasksListChangedNotificationSchema = NotificationSchema.extend({
4267
- method: z3.literal("notifications/tasks/list_changed"),
4268
- params: z3.record(z3.string(), z3.unknown()).optional()
5862
+ method: z4.literal("notifications/tasks/list_changed"),
5863
+ params: z4.record(z4.string(), z4.unknown()).optional()
4269
5864
  });
4270
5865
 
4271
5866
  // ../../core/mcp/uriTemplate.ts
@@ -4471,6 +6066,52 @@ var TypedEventGeneric = class extends CustomEvent {
4471
6066
  }
4472
6067
  };
4473
6068
  var TypedEventTarget = class extends EventTarget {
6069
+ /**
6070
+ * Per-event-type dispatch counter. Read through `getEventRevision` by
6071
+ * `useStoreSnapshot` (core/react) to tell "nothing has happened on this
6072
+ * event since I last looked" from "something has", without comparing the
6073
+ * snapshot's contents (#1955).
6074
+ */
6075
+ eventRevisions = /* @__PURE__ */ new Map();
6076
+ /**
6077
+ * How many times an event of this type has been dispatched from this
6078
+ * target. Monotonic per type, and per type only: an `errorChange` does not
6079
+ * advance `toolsChange`, so a subscriber to one is not re-read because of
6080
+ * the other.
6081
+ *
6082
+ * The counter — not the list's contents — is what a `useSyncExternalStore`
6083
+ * snapshot is cached against. That distinction is load-bearing rather than
6084
+ * an optimization: these stores hand out a defensive copy, so comparing
6085
+ * contents is the only alternative, and a contents comparison cannot see an
6086
+ * in-place mutation of an entry the list already holds. `MessageLogState`
6087
+ * does exactly that when a response is folded into its request entry
6088
+ * (filling in `response`/`duration`), and it dispatches — so the counter
6089
+ * moves and the fold reaches the UI, where a shallow compare would report
6090
+ * "unchanged" and never re-render it.
6091
+ */
6092
+ getEventRevision(type) {
6093
+ return this.eventRevisions.get(type) ?? 0;
6094
+ }
6095
+ /**
6096
+ * Overridden solely to advance the counter. It sits on `dispatchEvent`
6097
+ * rather than on `dispatchTypedEvent` because that is the one funnel every
6098
+ * dispatch passes through — the typed helper below delegates to it, and so
6099
+ * does a hand-rolled `dispatchEvent(new CustomEvent(...))`, which the tests
6100
+ * use extensively. A counter bumped only in the typed helper would be
6101
+ * silently stale for those, i.e. exactly where nobody would look for it.
6102
+ *
6103
+ * Bumped BEFORE dispatching: the state mutation always precedes the
6104
+ * dispatch, so by the time a listener runs the counter must already reflect
6105
+ * it, or a snapshot read from inside that listener would return the cached
6106
+ * pre-change value.
6107
+ */
6108
+ dispatchEvent(event) {
6109
+ this.eventRevisions.set(
6110
+ event.type,
6111
+ (this.eventRevisions.get(event.type) ?? 0) + 1
6112
+ );
6113
+ return super.dispatchEvent(event);
6114
+ }
4474
6115
  dispatchTypedEvent(type, ...args) {
4475
6116
  const detail = args[0] ?? void 0;
4476
6117
  this.dispatchEvent(new TypedEventGeneric(type, detail));
@@ -4859,6 +6500,115 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
4859
6500
  };
4860
6501
  }
4861
6502
 
6503
+ // ../../core/auth/oidcDiscoveryCompat.ts
6504
+ import {
6505
+ OAuthMetadataSchema,
6506
+ OpenIdProviderDiscoveryMetadataSchema
6507
+ } from "@modelcontextprotocol/core";
6508
+ var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
6509
+ var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
6510
+ var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
6511
+ function oidcDiscoveryCandidates(rfc8414Url) {
6512
+ let url;
6513
+ try {
6514
+ url = new URL(rfc8414Url);
6515
+ } catch {
6516
+ return [];
6517
+ }
6518
+ if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
6519
+ const path5 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
6520
+ if (path5 !== "" && !path5.startsWith("/")) return [];
6521
+ if (path5 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
6522
+ return [
6523
+ new URL(`${OIDC_WELL_KNOWN}${path5}`, url.origin).href,
6524
+ new URL(`${path5}${OIDC_WELL_KNOWN}`, url.origin).href
6525
+ ];
6526
+ }
6527
+ function continuesDiscovery(status) {
6528
+ return status >= 400 && status < 500 || status === 502;
6529
+ }
6530
+ async function releaseBody(response) {
6531
+ await response.body?.cancel().catch(() => {
6532
+ });
6533
+ }
6534
+ function isRfc8414OnlyMetadata(value) {
6535
+ if (!OAuthMetadataSchema.safeParse(value).success) return false;
6536
+ return !OpenIdProviderDiscoveryMetadataSchema.safeParse(value).success;
6537
+ }
6538
+ function requestUrlOf2(input) {
6539
+ if (typeof input === "string") return input;
6540
+ if (input instanceof URL) return input.href;
6541
+ return input.url;
6542
+ }
6543
+ function discoveryHeaders(input, init) {
6544
+ if (init?.headers) return new Headers(init.headers);
6545
+ if (typeof input !== "string" && !(input instanceof URL)) {
6546
+ return new Headers(input.headers);
6547
+ }
6548
+ return new Headers();
6549
+ }
6550
+ function requestMethodOf(input, init) {
6551
+ if (init?.method) return init.method.toUpperCase();
6552
+ if (typeof input !== "string" && !(input instanceof URL)) {
6553
+ return input.method.toUpperCase();
6554
+ }
6555
+ return "GET";
6556
+ }
6557
+ function withRfc8414OidcCompat(fetchFn) {
6558
+ return async (input, init) => {
6559
+ const response = await fetchFn(input, init);
6560
+ if (response.ok) return response;
6561
+ if (!continuesDiscovery(response.status)) return response;
6562
+ if (requestMethodOf(input, init) !== "GET") return response;
6563
+ const candidates = oidcDiscoveryCandidates(requestUrlOf2(input));
6564
+ if (candidates.length === 0) return response;
6565
+ const headers = discoveryHeaders(input, init);
6566
+ for (const candidate of candidates) {
6567
+ let probe;
6568
+ try {
6569
+ probe = await fetchFn(candidate, { headers });
6570
+ } catch {
6571
+ return response;
6572
+ }
6573
+ if (!probe.ok) {
6574
+ await releaseBody(probe);
6575
+ if (continuesDiscovery(probe.status)) continue;
6576
+ return response;
6577
+ }
6578
+ let parsed;
6579
+ let body;
6580
+ try {
6581
+ body = await probe.text();
6582
+ parsed = JSON.parse(body);
6583
+ } catch {
6584
+ return response;
6585
+ }
6586
+ if (!isRfc8414OnlyMetadata(parsed)) {
6587
+ return response;
6588
+ }
6589
+ const source = probe.url || candidate;
6590
+ await releaseBody(response);
6591
+ console.warn(
6592
+ `[oauth] ${source} returned RFC 8414 OAuth 2.0 authorization server metadata, not an OpenID provider document. The MCP TypeScript SDK validates that path as OpenID Connect Discovery and would reject it (modelcontextprotocol/typescript-sdk#2733), so the Inspector is handing it to discovery as the RFC 8414 document it is.`
6593
+ );
6594
+ return new Response(body, {
6595
+ status: 200,
6596
+ statusText: "OK",
6597
+ headers: {
6598
+ "content-type": "application/json",
6599
+ // The substituted response is what the fetch trackers above this
6600
+ // wrapper record, so it says where its body actually came from
6601
+ // rather than letting the Network tab imply the RFC 8414 path
6602
+ // answered. Named on the response, not logged only, so it survives
6603
+ // into the captured entry.
6604
+ [COMPAT_SOURCE_HEADER]: source
6605
+ }
6606
+ });
6607
+ }
6608
+ return response;
6609
+ };
6610
+ }
6611
+
4862
6612
  // ../../core/logging/logger.ts
4863
6613
  var noop = () => {
4864
6614
  };
@@ -5091,8 +6841,8 @@ var BaseOAuthClientProvider = class {
5091
6841
  );
5092
6842
  }
5093
6843
  async saveClientInformation(clientInformation, options) {
5094
- const registrationKind = options && "registrationKind" in options ? options.registrationKind : "dcr";
5095
6844
  const issuer = options && "issuer" in options ? options.issuer : void 0;
6845
+ const registrationKind = options && "registrationKind" in options ? options.registrationKind : await this.resolveSdkRegistrationKind(clientInformation, issuer);
5096
6846
  await this.storage.saveClientInformation(
5097
6847
  this.serverUrl,
5098
6848
  clientInformation,
@@ -5102,6 +6852,70 @@ var BaseOAuthClientProvider = class {
5102
6852
  }
5103
6853
  );
5104
6854
  }
6855
+ /**
6856
+ * Resolve the registration kind for a save that carries no explicit one — that
6857
+ * is, one the SDK made. SDK v2's `saveClientInformation` contract passes only
6858
+ * `{ issuer }`, so the mechanism cannot be handed to us; treating every such
6859
+ * save as DCR is what relabeled a CIMD registration `Dynamic (DCR)` in
6860
+ * Connection Info the moment the SDK bound it to an issuer (#2242).
6861
+ *
6862
+ * Two cases reach here, and they are told apart by whether a registration
6863
+ * already exists for this issuer:
6864
+ *
6865
+ * - **A back-stamp.** A registration is already stored for this issuer under
6866
+ * this `client_id`, and the SDK is only adding the `issuer` to it. Its
6867
+ * recorded kind is the answer — kind and credential are written and cleared
6868
+ * together, so a stored registration always has one.
6869
+ * - **A new registration.** Nothing is stored for this issuer, so this save
6870
+ * creates it. SDK v2 `auth()` reaches its URL-based-client-ID branch — rather
6871
+ * than `registerClient` — exactly when the AS advertises
6872
+ * `client_id_metadata_document_supported` and a `clientMetadataUrl` is
6873
+ * configured, and it persists the AS metadata via `saveDiscoveryState`
6874
+ * *before* it reads or writes client information. So the branch it took is
6875
+ * not inferred here, it is read back from the state it just wrote.
6876
+ *
6877
+ * This is why the check is not "the `client_id` looks like our metadata URL".
6878
+ * RFC 7591 §3.2 makes a dynamically issued `client_id` opaque, so an AS may
6879
+ * mint that very URL from `POST /register`; the URL comparison only decides
6880
+ * whether CIMD is *in play* for this connection, and the two cases above decide
6881
+ * what actually happened (#2242, Copilot).
6882
+ *
6883
+ * Consequences worth stating, since each was a defect on the way here:
6884
+ *
6885
+ * - An existing DCR whose `client_id` happens to be the metadata URL stays
6886
+ * `dcr` — it takes the back-stamp path and its recorded kind says so.
6887
+ * - `invalidateCredentials("client")`, which SDK `auth()` calls on
6888
+ * `invalid_client` before retrying, clears the registration and its kind. The
6889
+ * retry therefore takes the new-registration path and is answered from
6890
+ * discovery state, which that clear does not touch.
6891
+ * - A second AS behind one resource gets its own answer, since discovery state
6892
+ * describes the issuer the SDK actually resolved. One that does not advertise
6893
+ * CIMD is `dcr` even when it mints the metadata URL as its `client_id`.
6894
+ * - A transient failure in our own CIMD preflight costs nothing: the SDK's own
6895
+ * discovery is what this reads.
6896
+ */
6897
+ async resolveSdkRegistrationKind(clientInformation, issuer) {
6898
+ const clientMetadataUrl = this.clientMetadataUrl?.trim();
6899
+ if (!clientMetadataUrl || clientInformation.client_id !== clientMetadataUrl) {
6900
+ return "dcr";
6901
+ }
6902
+ const stored = await this.storage.getClientInformation(
6903
+ this.serverUrl,
6904
+ false,
6905
+ issuer
6906
+ );
6907
+ if (stored?.client_id === clientMetadataUrl) {
6908
+ const storedKind = await this.storage.getClientRegistrationKind(
6909
+ this.serverUrl,
6910
+ issuer
6911
+ );
6912
+ return storedKind === "cimd" ? "cimd" : "dcr";
6913
+ }
6914
+ const discovery = await this.storage.getDiscoveryState(this.serverUrl);
6915
+ const metadata = discovery?.authorizationServerMetadata;
6916
+ if (issuer !== void 0 && metadata?.issuer !== issuer) return "dcr";
6917
+ return metadata?.client_id_metadata_document_supported === true ? "cimd" : "dcr";
6918
+ }
5105
6919
  async saveScope(scope) {
5106
6920
  await this.storage.saveScope(this.serverUrl, scope);
5107
6921
  this.cachedScope = scope;
@@ -5277,7 +7091,7 @@ import {
5277
7091
  exchangeAuthorization,
5278
7092
  startAuthorization
5279
7093
  } from "@modelcontextprotocol/client";
5280
- import { OAuthMetadataSchema } from "@modelcontextprotocol/core";
7094
+ import { OAuthMetadataSchema as OAuthMetadataSchema2 } from "@modelcontextprotocol/core";
5281
7095
 
5282
7096
  // ../../core/auth/ema/constants.ts
5283
7097
  var GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
@@ -5326,7 +7140,7 @@ function idpOAuthStorageKey(issuer) {
5326
7140
  }
5327
7141
 
5328
7142
  // ../../core/auth/ema/tokenEndpoint.ts
5329
- import { selectClientAuthMethod } from "@modelcontextprotocol/client";
7143
+ import { selectClientAuthMethod as selectClientAuthMethod2 } from "@modelcontextprotocol/client";
5330
7144
  async function parseOAuthTokenErrorResponse(response, step) {
5331
7145
  let body;
5332
7146
  try {
@@ -5347,7 +7161,7 @@ async function parseOAuthTokenErrorResponse(response, step) {
5347
7161
  }
5348
7162
  function applyClientAuth(metadata, clientInformation, headers, body) {
5349
7163
  const supported = metadata?.token_endpoint_auth_methods_supported ?? [];
5350
- const method = selectClientAuthMethod(clientInformation, supported);
7164
+ const method = selectClientAuthMethod2(clientInformation, supported);
5351
7165
  if (method === "client_secret_basic" && clientInformation.client_secret) {
5352
7166
  const credentials = btoa(
5353
7167
  `${clientInformation.client_id}:${clientInformation.client_secret}`
@@ -5401,7 +7215,7 @@ async function discoverIdpMetadata(issuer, fetchFn) {
5401
7215
  `Failed to discover OIDC metadata for IdP issuer ${issuer}`
5402
7216
  );
5403
7217
  }
5404
- return OAuthMetadataSchema.parse(metadata);
7218
+ return OAuthMetadataSchema2.parse(metadata);
5405
7219
  }
5406
7220
  async function startIdpOidcAuthorization(params) {
5407
7221
  const issuer = normalizeIdpIssuer(params.idp.issuer);
@@ -5657,7 +7471,7 @@ async function discoverResourceAsMetadata(resourceAsUrl, fetchFn) {
5657
7471
 
5658
7472
  // ../../core/auth/ema/wire.ts
5659
7473
  import { discoverAndRequestJwtAuthGrant } from "@modelcontextprotocol/client";
5660
- import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
7474
+ import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
5661
7475
  async function exchangeIdJag(params) {
5662
7476
  const issuer = normalizeIdpIssuer(params.idp.issuer);
5663
7477
  const resource = params.resource.trim();
@@ -5736,7 +7550,7 @@ async function redeemIdJagForAccessToken(params) {
5736
7550
  "EMA leg 3 (resource AS JWT bearer grant)"
5737
7551
  );
5738
7552
  }
5739
- return OAuthTokensSchema2.parse(await response.json());
7553
+ return OAuthTokensSchema3.parse(await response.json());
5740
7554
  }
5741
7555
 
5742
7556
  // ../../core/auth/ema/emaFlow.ts
@@ -5980,35 +7794,49 @@ import { discoverOAuthProtectedResourceMetadata as discoverOAuthProtectedResourc
5980
7794
  async function ensureCimdClientRegistration(params) {
5981
7795
  const clientMetadataUrl = params.provider.clientMetadataUrl?.trim();
5982
7796
  if (!clientMetadataUrl) return;
5983
- const existing = await params.provider.clientInformation();
5984
- if (existing?.client_id) return;
5985
- let resourceMetadata;
5986
- try {
5987
- resourceMetadata = await discoverOAuthProtectedResourceMetadata2(
5988
- params.serverUrl,
5989
- { resourceMetadataUrl: params.resourceMetadataUrl },
5990
- // The same fetch the AS-metadata leg below uses. On web that is
5991
- // `createRemoteFetch`, which proxies through the backend to sidestep
5992
- // CORS — on the global `fetch` this leg would fail in the browser, be
5993
- // swallowed by the catch, and leave CIMD probing the wrong
5994
- // authorization server (Copilot).
5995
- params.fetchFn
5996
- );
5997
- } catch {
5998
- resourceMetadata = void 0;
7797
+ let metadata = (await params.provider.discoveryState())?.authorizationServerMetadata;
7798
+ if (!metadata) {
7799
+ let resourceMetadata;
7800
+ try {
7801
+ resourceMetadata = await discoverOAuthProtectedResourceMetadata2(
7802
+ params.serverUrl,
7803
+ { resourceMetadataUrl: params.resourceMetadataUrl },
7804
+ // The same fetch the AS-metadata leg below uses. On web that is
7805
+ // `createRemoteFetch`, which proxies through the backend to sidestep
7806
+ // CORS — on the global `fetch` this leg would fail in the browser, be
7807
+ // swallowed by the catch, and leave CIMD probing the wrong
7808
+ // authorization server (Copilot).
7809
+ params.fetchFn
7810
+ );
7811
+ } catch {
7812
+ resourceMetadata = void 0;
7813
+ }
7814
+ try {
7815
+ metadata = await discoverAuthorizationServerMetadataForServer(
7816
+ params.serverUrl,
7817
+ resourceMetadata,
7818
+ params.fetchFn
7819
+ );
7820
+ } catch {
7821
+ return;
7822
+ }
5999
7823
  }
6000
- const metadata = await discoverAuthorizationServerMetadataForServer(
6001
- params.serverUrl,
6002
- resourceMetadata,
6003
- params.fetchFn
6004
- );
7824
+ const issuer = metadata?.issuer;
6005
7825
  if (!metadata?.client_id_metadata_document_supported) return;
6006
- const clientInformation = {
6007
- client_id: clientMetadataUrl
6008
- };
6009
- await params.provider.saveClientInformation(clientInformation, {
6010
- registrationKind: "cimd"
6011
- });
7826
+ const existing = await params.provider.clientInformation(
7827
+ issuer ? { issuer } : void 0
7828
+ );
7829
+ if (existing?.client_id) return;
7830
+ await params.provider.saveClientInformation(
7831
+ { client_id: clientMetadataUrl },
7832
+ {
7833
+ registrationKind: "cimd",
7834
+ // An AS metadata document without an `issuer` is malformed (RFC 8414 §2),
7835
+ // but the type allows it; fall back to the unkeyed slot rather than
7836
+ // inventing a key.
7837
+ ...issuer && { issuer }
7838
+ }
7839
+ );
6012
7840
  }
6013
7841
 
6014
7842
  // ../../core/auth/ema/transportProvider.ts
@@ -6035,11 +7863,26 @@ var EmaTransportOAuthProvider = class {
6035
7863
  state() {
6036
7864
  return this.inner.state();
6037
7865
  }
6038
- clientInformation() {
6039
- return this.inner.clientInformation();
7866
+ // SEP-2352: `ctx` carries the authorization-server `issuer` the SDK resolved,
7867
+ // and the inner provider keys registrations by it. Dropping it here made every
7868
+ // EMA read and write land on the unkeyed slot — and, since #2242, left
7869
+ // `resolveSdkRegistrationKind` with no issuer to check, so a CIMD registration
7870
+ // made over an EMA connection was recorded as DCR (Copilot).
7871
+ clientInformation(ctx) {
7872
+ return this.inner.clientInformation(ctx);
7873
+ }
7874
+ saveClientInformation(clientInformation, ctx) {
7875
+ return this.inner.saveClientInformation(clientInformation, ctx);
7876
+ }
7877
+ // Without these the SDK persists no discovery state for an EMA connection, so
7878
+ // it re-discovers on every call, cannot perform its SEP-2352 callback-leg
7879
+ // authorization-server binding check (it warns as much), and — since #2242 —
7880
+ // leaves the registration-kind resolver nothing to read back.
7881
+ saveDiscoveryState(state) {
7882
+ return this.inner.saveDiscoveryState(state);
6040
7883
  }
6041
- saveClientInformation(clientInformation) {
6042
- return this.inner.saveClientInformation(clientInformation);
7884
+ discoveryState() {
7885
+ return this.inner.discoveryState();
6043
7886
  }
6044
7887
  async tokens() {
6045
7888
  const stored = await this.inner.tokens();
@@ -6108,8 +7951,11 @@ function issuerMismatchTitle() {
6108
7951
  return "Authorization server mismatch";
6109
7952
  }
6110
7953
  var MAX_DISPLAYED_ISSUER_LENGTH = 120;
7954
+ function truncateUrlForDisplay(url) {
7955
+ return url.length > MAX_DISPLAYED_ISSUER_LENGTH ? `${url.slice(0, MAX_DISPLAYED_ISSUER_LENGTH)}\u2026` : url;
7956
+ }
6111
7957
  function truncateIssuerForDisplay(issuer) {
6112
- return issuer.length > MAX_DISPLAYED_ISSUER_LENGTH ? `${issuer.slice(0, MAX_DISPLAYED_ISSUER_LENGTH)}\u2026` : issuer;
7958
+ return truncateUrlForDisplay(issuer);
6113
7959
  }
6114
7960
  function issuerMismatchMessage(options) {
6115
7961
  const target = options.serverName ? `"${options.serverName}"` : "this server";
@@ -6465,14 +8311,41 @@ var OAuthManager = class {
6465
8311
  return void 0;
6466
8312
  }
6467
8313
  }
6468
- async clearOAuthTokens() {
8314
+ /**
8315
+ * Drop this server's local OAuth state, and revoke the grant at the
8316
+ * authorization server (RFC 7009).
8317
+ *
8318
+ * The state is taken and deleted in one atomic step, and the requests are
8319
+ * sent afterwards from what was taken. Both halves matter: the requests need
8320
+ * state the clear destroys, and the clear must not wait on the network, or a
8321
+ * fresh authorization completing during a five-second revocation would be
8322
+ * deleted by a clear reasoning about the grant it replaced.
8323
+ *
8324
+ * Best-effort by construction: every failure is reported through the returned
8325
+ * outcome and the clear has already happened regardless, because forgetting
8326
+ * the tokens is what the caller actually asked for (#2144).
8327
+ *
8328
+ * `options.revoke === false` skips the request. That is not only an escape
8329
+ * hatch for an authorization server that mishandles it — disconnecting while
8330
+ * still holding live tokens is a case a user may want to reproduce
8331
+ * deliberately, to watch how a server under test copes with it.
8332
+ */
8333
+ async clearOAuthTokens(options) {
6469
8334
  if (!this.oauthConfig?.storage) {
6470
- return;
8335
+ return { status: "skipped", reason: "no_tokens" };
6471
8336
  }
6472
8337
  const serverUrl = this.getServerUrl();
6473
- await this.oauthConfig.storage.clear(serverUrl);
8338
+ const plan = await clearAndPlanRevocation({
8339
+ serverUrl,
8340
+ storage: this.oauthConfig.storage,
8341
+ enabled: options?.revoke
8342
+ });
6474
8343
  this.oauthFlowState = null;
6475
8344
  this.pendingAuthorizationScope = void 0;
8345
+ return executeOAuthRevocation(plan, {
8346
+ fetchFn: this.params.effectiveAuthFetch,
8347
+ logger: this.params.logger
8348
+ });
6476
8349
  }
6477
8350
  async isOAuthAuthorized() {
6478
8351
  const tokens = await this.getOAuthTokens();
@@ -6799,10 +8672,11 @@ var OAuthManager = class {
6799
8672
  await this.params.onBeforeOAuthRedirect(parsedState.authId);
6800
8673
  }
6801
8674
  }
6802
- this.requireNavigation().navigateToAuthorization(authorizationUrl);
8675
+ const navigation = this.requireNavigation();
6803
8676
  const provider = await this.createOAuthProvider();
6804
8677
  const clientInfo = await provider.clientInformation();
6805
8678
  await this.recordAuthorizationCodeFlowState(authorizationUrl, clientInfo);
8679
+ navigation.navigateToAuthorization(authorizationUrl);
6806
8680
  this.params.dispatchOAuthAuthorizationRequired({ url: authorizationUrl });
6807
8681
  }
6808
8682
  requireNavigation() {
@@ -6959,6 +8833,24 @@ var RemoteClientTransport = class {
6959
8833
  sseResponseWaits = /* @__PURE__ */ new Map();
6960
8834
  options;
6961
8835
  config;
8836
+ /**
8837
+ * Whether the upstream connection gives each request its own response stream
8838
+ * (#2140). The SDK's `Protocol.request` reads this: on a 2026-era connection
8839
+ * a transport advertising it has its per-request stream **aborted** as the
8840
+ * spec's cancellation signal, and no `notifications/cancelled` is sent; a
8841
+ * transport that does not gets the stdio mechanism instead.
8842
+ *
8843
+ * The real upstream transport lives on the backend, so this transport has to
8844
+ * answer for it. Streamable HTTP opens one POST — and one SSE response
8845
+ * stream — per request, so it qualifies; stdio and SSE multiplex every
8846
+ * request over one shared channel and must keep sending the notification.
8847
+ *
8848
+ * Advertising it is only half the fix: `requestSend` applies the SDK's
8849
+ * `requestSignal` to the browser-to-backend fetch, and `/api/mcp/send`
8850
+ * forwards that disconnect to the upstream `transport.send`, which is what
8851
+ * actually closes the stream the server is watching.
8852
+ */
8853
+ hasPerRequestStream;
6962
8854
  /**
6963
8855
  * Intentionally returns undefined. The MCP Client checks transport.sessionId to detect
6964
8856
  * reconnects and skip initialize. Our _sessionId is the remote server's session ID, not
@@ -7003,6 +8895,7 @@ var RemoteClientTransport = class {
7003
8895
  constructor(options, config) {
7004
8896
  this.options = options;
7005
8897
  this.config = config;
8898
+ this.hasPerRequestStream = config.type === "streamable-http";
7006
8899
  }
7007
8900
  setAuthRecovery(handlers) {
7008
8901
  this.options.authRecovery = handlers;
@@ -7367,7 +9260,8 @@ var RemoteClientTransport = class {
7367
9260
  const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
7368
9261
  method: "POST",
7369
9262
  headers: this.headers,
7370
- body: JSON.stringify(body)
9263
+ body: JSON.stringify(body),
9264
+ ...options?.requestSignal && { signal: options.requestSignal }
7371
9265
  });
7372
9266
  if (!res.ok) {
7373
9267
  const text = await res.text();
@@ -7604,6 +9498,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7604
9498
  // pending re-listen timer.
7605
9499
  modernReconnectAttempts = 0;
7606
9500
  modernReconnectTimer;
9501
+ // Set when the last `listen()` was rejected because the server answered it
9502
+ // with a bare graceful-closure result instead of acknowledging (#2097). That
9503
+ // condition is deterministic, so the reconnect machinery must not treat it as
9504
+ // a drop to retry; the flag is what carries the distinction from the rejection
9505
+ // site to the two failure handlers. Cleared by any user-initiated refresh (a
9506
+ // subscribe/unsubscribe is a fresh attempt, and the server may have changed).
9507
+ modernNeverAcknowledged = false;
7607
9508
  // Task ids the user explicitly cancelled. A cancel makes the in-flight
7608
9509
  // `callToolStream` reject with a generic -32603 error, which the stream's
7609
9510
  // error path would otherwise report as a *failed* task — flashing "failed"
@@ -7625,9 +9526,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7625
9526
  pendingRawWireRequests = /* @__PURE__ */ new Map();
7626
9527
  rawWireRequestCounter = 0;
7627
9528
  // Abort controller for the in-flight ordinary (non-task) tool call. Aborting
7628
- // it makes the SDK send a `notifications/cancelled` for that request (the MCP
7629
- // cancellation flow) and reject the pending call, which `callTool` surfaces as
7630
- // a `ToolCallCancelledError`. Undefined when no ordinary call is in flight.
9529
+ // it hands the SDK the MCP cancellation flow for that request and rejects the
9530
+ // pending call, which `callTool` surfaces as a `ToolCallCancelledError`. Which
9531
+ // signal reaches the server is the transport's business, not ours: a
9532
+ // per-request-stream transport on a 2026-era connection has that request's
9533
+ // stream aborted, and everything else gets `notifications/cancelled` (#2140).
9534
+ // Undefined when no ordinary call is in flight.
7631
9535
  // Task-augmented calls have a server-side task and are cancelled via
7632
9536
  // `cancelRequestorTask` instead, so they don't use this (#1458).
7633
9537
  activeToolCallAbortController;
@@ -7734,6 +9638,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7734
9638
  this.fetchFn ?? fetch,
7735
9639
  () => this.oauthManager?.getEndpointOverrides()
7736
9640
  );
9641
+ this.fetchFn = withRfc8414OidcCompat(this.fetchFn);
7737
9642
  this.effectiveAuthFetch = this.buildEffectiveAuthFetch();
7738
9643
  this.sessionId = options.sessionId;
7739
9644
  if (options.oauth || options.environment.oauth) {
@@ -7753,6 +9658,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7753
9658
  return Promise.resolve();
7754
9659
  },
7755
9660
  initialConfig: oauthConfig,
9661
+ logger: this.logger,
7756
9662
  enterpriseManagedAuth: options.enterpriseManagedAuth,
7757
9663
  installEnterpriseManagedAuth: options.installEnterpriseManagedAuth,
7758
9664
  dispatchOAuthComplete: (detail) => this.dispatchTypedEvent("oauthComplete", detail),
@@ -8443,6 +10349,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8443
10349
  this.modernListenGeneration++;
8444
10350
  this.clearModernReconnectTimer();
8445
10351
  this.modernReconnectAttempts = 0;
10352
+ this.modernNeverAcknowledged = false;
8446
10353
  this.modernSubscription = null;
8447
10354
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
8448
10355
  this.dispatchSubscriptionsChange();
@@ -9229,10 +11136,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
9229
11136
  }
9230
11137
  /**
9231
11138
  * Cancel the in-flight ordinary (non-task) tool call started by
9232
- * {@link callTool}. Aborting its request makes the SDK send a
9233
- * `notifications/cancelled` to the server (the MCP cancellation flow) and
9234
- * reject the pending call, which `callTool` surfaces as a
9235
- * {@link ToolCallCancelledError}.
11139
+ * {@link callTool}. Aborting its request runs the MCP cancellation flow and
11140
+ * rejects the pending call, which `callTool` surfaces as a
11141
+ * {@link ToolCallCancelledError}. The SDK chooses the wire signal from the
11142
+ * transport: on a 2026-era Streamable HTTP connection it aborts that
11143
+ * request's own SSE response stream — the spec's cancellation signal —
11144
+ * rather than sending `notifications/cancelled`, which is the stdio
11145
+ * mechanism and remains in use there (#2140).
9236
11146
  *
9237
11147
  * Task-augmented calls have a server-side task and are cancelled via
9238
11148
  * {@link cancelRequestorTask} instead — this is a no-op for them (and whenever
@@ -9259,7 +11169,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
9259
11169
  throw new Error("Client is not connected");
9260
11170
  }
9261
11171
  const result = await this.client.request(
9262
- { method: "tasks/list", params: cursor ? { cursor } : {} },
11172
+ {
11173
+ method: "tasks/list",
11174
+ // `!== undefined`, not truthiness: a cursor is opaque and `""` is a
11175
+ // legal value a server may hand back. Dropping it asks for page one
11176
+ // again, so a caller walking pages would loop on the first page.
11177
+ params: cursor !== void 0 ? { cursor } : {}
11178
+ },
9263
11179
  ListTasksResultSchema,
9264
11180
  this.getRequestOptions()
9265
11181
  );
@@ -10853,7 +12769,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10853
12769
  const effectiveMeta = this.mergeMeta(metadata);
10854
12770
  const params = {
10855
12771
  ...effectiveMeta ? { _meta: effectiveMeta } : {},
10856
- ...cursor ? { cursor } : {}
12772
+ // `!== undefined`, not truthiness: a cursor is opaque and `""` is a
12773
+ // legal value a server may hand back. Dropping it asks for page one
12774
+ // again, so a caller walking pages would loop on the first page.
12775
+ ...cursor !== void 0 ? { cursor } : {}
10857
12776
  };
10858
12777
  const response = await this.invokeMcpClient(
10859
12778
  () => this.client.request(
@@ -10881,7 +12800,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10881
12800
  method: "resources/list",
10882
12801
  itemsKey: "resources",
10883
12802
  resultSchema: ListResourcesResultSchema,
10884
- itemSchema: ResourceSchema,
12803
+ itemSchema: ResourceSchema2,
10885
12804
  metadata: options?.metadata,
10886
12805
  aggregate: async () => [
10887
12806
  ...(await this.client.listResources(
@@ -10983,7 +12902,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10983
12902
  const effectiveMeta = this.mergeMeta(metadata);
10984
12903
  const params = {
10985
12904
  ...effectiveMeta ? { _meta: effectiveMeta } : {},
10986
- ...cursor ? { cursor } : {}
12905
+ // `!== undefined`, not truthiness: a cursor is opaque and `""` is a
12906
+ // legal value a server may hand back. Dropping it asks for page one
12907
+ // again, so a caller walking pages would loop on the first page.
12908
+ ...cursor !== void 0 ? { cursor } : {}
10987
12909
  };
10988
12910
  const response = await this.invokeMcpClient(
10989
12911
  () => this.client.request(
@@ -11029,51 +12951,203 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11029
12951
  * @param metadata Optional metadata to include in the request
11030
12952
  * @returns Object containing prompts array and optional nextCursor
11031
12953
  */
11032
- async listPrompts(cursor, metadata) {
12954
+ async listPrompts(cursor, metadata) {
12955
+ if (!this.client) {
12956
+ throw new Error("Client is not connected");
12957
+ }
12958
+ const effectiveMeta = this.mergeMeta(metadata);
12959
+ const params = {
12960
+ ...effectiveMeta ? { _meta: effectiveMeta } : {},
12961
+ // `!== undefined`, not truthiness: a cursor is opaque and `""` is a
12962
+ // legal value a server may hand back. Dropping it asks for page one
12963
+ // again, so a caller walking pages would loop on the first page.
12964
+ ...cursor !== void 0 ? { cursor } : {}
12965
+ };
12966
+ const response = await this.invokeMcpClient(
12967
+ () => this.client.request(
12968
+ { method: "prompts/list", params },
12969
+ ListPromptsResultSchema,
12970
+ this.getRequestOptions(this.progressTokenOf(metadata))
12971
+ )
12972
+ );
12973
+ return {
12974
+ prompts: response.prompts || [],
12975
+ nextCursor: response.nextCursor
12976
+ };
12977
+ }
12978
+ /**
12979
+ * Aggregate ALL pages of `prompts/list` via the SDK's high-level
12980
+ * cache-aware `client.listPrompts()`. See {@link listAllTools} for the
12981
+ * `cacheMode` semantics; this is the path the managed prompt list uses on
12982
+ * refresh.
12983
+ */
12984
+ async listAllPrompts(options) {
12985
+ if (!this.client) {
12986
+ throw new Error("Client is not connected");
12987
+ }
12988
+ const prompts = await this.listAllSalvaging({
12989
+ method: "prompts/list",
12990
+ itemsKey: "prompts",
12991
+ resultSchema: ListPromptsResultSchema,
12992
+ itemSchema: PromptSchema,
12993
+ metadata: options?.metadata,
12994
+ aggregate: async () => [
12995
+ ...(await this.client.listPrompts(
12996
+ this.aggregateListParams(options?.metadata),
12997
+ this.getCacheableRequestOptions(options?.cacheMode)
12998
+ )).prompts
12999
+ ]
13000
+ });
13001
+ return { prompts };
13002
+ }
13003
+ /**
13004
+ * The Skills extension (SEP-2640) the server declared, or `undefined` when it
13005
+ * declared none. Gates the Skills tab and the skills store the same way
13006
+ * {@link isTasksExtensionNegotiated} gates Tasks — but deliberately without an
13007
+ * era check: `skills/*` are not spec method names in either codec, so a
13008
+ * legacy-era server that declares the extension is serving it (#2234).
13009
+ */
13010
+ getSkillsExtension() {
13011
+ return getSkillsExtension(this.capabilities);
13012
+ }
13013
+ /**
13014
+ * One page of `skills/list` (SEP-2640).
13015
+ *
13016
+ * An ordinary `client.request` with an explicit result schema — the SDK's era
13017
+ * gate skips methods neither codec defines, and `assertCapabilityForMethod`
13018
+ * falls through to a no-op for them, so this is all the extension needs. The
13019
+ * raw-wire path modern `tasks/*` uses would be wrong here: it exists for spec
13020
+ * names the 2026 codec deleted, and taking it would bypass the SDK's response
13021
+ * correlation for nothing (#2234).
13022
+ */
13023
+ async listSkills(cursor, metadata) {
13024
+ if (!this.client) {
13025
+ throw new Error("Client is not connected");
13026
+ }
13027
+ const effectiveMeta = this.mergeMeta(metadata);
13028
+ const params = {
13029
+ ...effectiveMeta ? { _meta: effectiveMeta } : {},
13030
+ // `!== undefined`, not truthiness: a cursor is opaque and the empty
13031
+ // string is a legal value. Dropping `""` would silently re-request page
13032
+ // one, which the store then reports as a repeated-cursor failure — a
13033
+ // conforming server made to look broken.
13034
+ ...cursor !== void 0 ? { cursor } : {}
13035
+ };
13036
+ const resultSchema = this.isModernEra() ? ModernListSkillsResultSchema : ListSkillsResultSchema;
13037
+ const response = await this.invokeMcpClient(
13038
+ () => this.client.request(
13039
+ { method: SKILLS_LIST_METHOD, params },
13040
+ resultSchema,
13041
+ this.getRequestOptions(this.progressTokenOf(metadata))
13042
+ ),
13043
+ { method: SKILLS_LIST_METHOD }
13044
+ );
13045
+ return {
13046
+ skills: response.skills,
13047
+ nextCursor: response.nextCursor
13048
+ };
13049
+ }
13050
+ /**
13051
+ * One skill entry by URI (`skills/get`, SEP-2640). The result envelope is
13052
+ * required — `GetSkillEnvelopeSchema` requires `{ skill }` and rejects an entry
13053
+ * returned inline, so a non-conforming shape fails here rather than being
13054
+ * silently normalized past the conformance checks.
13055
+ */
13056
+ async getSkill(uri, metadata) {
13057
+ return (await this.getSkillResult(uri, metadata)).skill;
13058
+ }
13059
+ /**
13060
+ * `skills/get` as the server sent it — the `{ skill }` envelope **and any
13061
+ * other members it carried**.
13062
+ *
13063
+ * Separate from {@link getSkill} because the callers differ: the UIs want the
13064
+ * entry, while the CLI prints the result and must not reshape it. SEP-2640
13065
+ * explicitly leaves open whether this result carries `ttlMs` / `cacheScope`,
13066
+ * so a server may send them — and unwrapping to the entry discards exactly
13067
+ * those (Copilot).
13068
+ */
13069
+ async getSkillResult(uri, metadata) {
13070
+ if (!this.client) {
13071
+ throw new Error("Client is not connected");
13072
+ }
13073
+ const effectiveMeta = this.mergeMeta(metadata);
13074
+ const params = {
13075
+ uri,
13076
+ ...effectiveMeta ? { _meta: effectiveMeta } : {}
13077
+ };
13078
+ const resultSchema = this.isModernEra() ? ModernGetSkillEnvelopeSchema : GetSkillEnvelopeSchema;
13079
+ try {
13080
+ return await this.invokeMcpClient(
13081
+ () => this.client.request(
13082
+ { method: SKILLS_GET_METHOD, params },
13083
+ resultSchema,
13084
+ this.getRequestOptions(this.progressTokenOf(metadata))
13085
+ ),
13086
+ { method: SKILLS_GET_METHOD }
13087
+ );
13088
+ } catch (err) {
13089
+ if (isClientDecodeRejection(err)) {
13090
+ this.markResponseRejected(
13091
+ SKILLS_GET_METHOD,
13092
+ err instanceof Error ? err.message : String(err)
13093
+ );
13094
+ }
13095
+ throw err;
13096
+ }
13097
+ }
13098
+ /**
13099
+ * One page of `resources/directory/read` (SEP-2640): the direct children of
13100
+ * a directory resource.
13101
+ *
13102
+ * **Gated on the server's own declaration, and the gate throws rather than
13103
+ * asks.** SEP-2640 is explicit that *"clients MUST NOT call
13104
+ * `resources/directory/read` against a server that has not declared
13105
+ * `directoryRead: true`"*, so this refuses locally instead of sending a call
13106
+ * the spec forbids and letting the server answer `-32601`. Refusing here also
13107
+ * keeps the Protocol tab honest: a request we were never allowed to make
13108
+ * should not appear in the exchange log as a server-side failure.
13109
+ *
13110
+ * Not recursive — the SEP says clients descend by calling the method again on
13111
+ * a child directory, so the walking is the caller's, not this method's.
13112
+ */
13113
+ async readResourceDirectory(uri, cursor, metadata) {
11033
13114
  if (!this.client) {
11034
13115
  throw new Error("Client is not connected");
11035
13116
  }
13117
+ const extension = this.getSkillsExtension();
13118
+ if (!extension?.directoryRead) {
13119
+ throw new Error(
13120
+ `Server did not declare directoryRead in ${SKILLS_EXTENSION_KEY}; ${RESOURCES_DIRECTORY_READ_METHOD} must not be called.`
13121
+ );
13122
+ }
11036
13123
  const effectiveMeta = this.mergeMeta(metadata);
11037
13124
  const params = {
13125
+ uri,
11038
13126
  ...effectiveMeta ? { _meta: effectiveMeta } : {},
11039
- ...cursor ? { cursor } : {}
11040
- };
11041
- const response = await this.invokeMcpClient(
11042
- () => this.client.request(
11043
- { method: "prompts/list", params },
11044
- ListPromptsResultSchema,
11045
- this.getRequestOptions(this.progressTokenOf(metadata))
11046
- )
11047
- );
11048
- return {
11049
- prompts: response.prompts || [],
11050
- nextCursor: response.nextCursor
13127
+ // `!== undefined` for the same reason `listSkills` uses it: a cursor is
13128
+ // opaque and `""` is a legal value, so truthiness would silently re-ask
13129
+ // for page one.
13130
+ ...cursor !== void 0 ? { cursor } : {}
11051
13131
  };
11052
- }
11053
- /**
11054
- * Aggregate ALL pages of `prompts/list` via the SDK's high-level
11055
- * cache-aware `client.listPrompts()`. See {@link listAllTools} for the
11056
- * `cacheMode` semantics; this is the path the managed prompt list uses on
11057
- * refresh.
11058
- */
11059
- async listAllPrompts(options) {
11060
- if (!this.client) {
11061
- throw new Error("Client is not connected");
13132
+ const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
13133
+ try {
13134
+ return await this.invokeMcpClient(
13135
+ () => this.client.request(
13136
+ { method: RESOURCES_DIRECTORY_READ_METHOD, params },
13137
+ resultSchema,
13138
+ this.getRequestOptions(this.progressTokenOf(metadata))
13139
+ ),
13140
+ { method: RESOURCES_DIRECTORY_READ_METHOD }
13141
+ );
13142
+ } catch (err) {
13143
+ if (isClientDecodeRejection(err)) {
13144
+ this.markResponseRejected(
13145
+ RESOURCES_DIRECTORY_READ_METHOD,
13146
+ err instanceof Error ? err.message : String(err)
13147
+ );
13148
+ }
13149
+ throw err;
11062
13150
  }
11063
- const prompts = await this.listAllSalvaging({
11064
- method: "prompts/list",
11065
- itemsKey: "prompts",
11066
- resultSchema: ListPromptsResultSchema,
11067
- itemSchema: PromptSchema,
11068
- metadata: options?.metadata,
11069
- aggregate: async () => [
11070
- ...(await this.client.listPrompts(
11071
- this.aggregateListParams(options?.metadata),
11072
- this.getCacheableRequestOptions(options?.cacheMode)
11073
- )).prompts
11074
- ]
11075
- });
11076
- return { prompts };
11077
13151
  }
11078
13152
  /**
11079
13153
  * Get a prompt by name
@@ -11448,6 +13522,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11448
13522
  if (!fromReconnect) {
11449
13523
  this.clearModernReconnectTimer();
11450
13524
  this.modernReconnectAttempts = 0;
13525
+ this.modernNeverAcknowledged = false;
11451
13526
  }
11452
13527
  const generation = ++this.modernListenGeneration;
11453
13528
  const previous = this.modernSubscription;
@@ -11459,10 +13534,18 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11459
13534
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
11460
13535
  return;
11461
13536
  }
11462
- const subscription = await this.client.listen(
11463
- this.buildSubscriptionFilter(),
11464
- this.getRequestOptions()
11465
- );
13537
+ let subscription;
13538
+ try {
13539
+ subscription = await this.client.listen(
13540
+ this.buildSubscriptionFilter(),
13541
+ this.getRequestOptions()
13542
+ );
13543
+ } catch (error) {
13544
+ if (generation === this.modernListenGeneration && isNeverAcknowledgedSubscriptionClose(error)) {
13545
+ this.modernNeverAcknowledged = true;
13546
+ }
13547
+ throw error;
13548
+ }
11466
13549
  if (generation !== this.modernListenGeneration) {
11467
13550
  await closeSubscriptionBestEffort(subscription);
11468
13551
  return;
@@ -11544,8 +13627,34 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11544
13627
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
11545
13628
  return;
11546
13629
  }
13630
+ if (this.modernNeverAcknowledged) {
13631
+ this.endModernStreamNeverAcknowledged();
13632
+ return;
13633
+ }
11547
13634
  this.scheduleModernReconnect();
11548
13635
  }
13636
+ /**
13637
+ * Settle the stream on the never-acknowledged close (#2097): a status of its
13638
+ * own so the UI can say what happened, no reconnect, and a log line carrying
13639
+ * the same sentence the UI shows.
13640
+ *
13641
+ * The state is *not* `"ended"` — that badge covers the two expected closes (a
13642
+ * server shutting an established stream down, and reconnection abandoned after
13643
+ * repeated failures), and reading this case as either of them is exactly the
13644
+ * silence #2063 reported.
13645
+ */
13646
+ endModernStreamNeverAcknowledged() {
13647
+ this.clearModernReconnectTimer();
13648
+ this.logger.warn(
13649
+ { message: NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE },
13650
+ "subscriptions/listen closed without an acknowledgement"
13651
+ );
13652
+ this.setModernStreamState({
13653
+ active: this.modernStreamActive(),
13654
+ status: "never-acknowledged",
13655
+ honoredUris: []
13656
+ });
13657
+ }
11549
13658
  /**
11550
13659
  * Schedule a reconnect re-listen after the current backoff delay (#1630).
11551
13660
  * `modernReconnectAttempts` reflects the number of *consecutive failed*
@@ -11579,6 +13688,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11579
13688
  * stream ended (re-subscribing resets the run and tries again).
11580
13689
  */
11581
13690
  onModernReconnectFailed() {
13691
+ if (this.modernNeverAcknowledged && !isTerminalStatus(this.status) && this.wantsModernStream()) {
13692
+ this.endModernStreamNeverAcknowledged();
13693
+ return;
13694
+ }
11582
13695
  this.modernReconnectAttempts += 1;
11583
13696
  if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
11584
13697
  this.setModernStreamState({
@@ -11635,7 +13748,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11635
13748
  this.dispatchSubscriptionsChange();
11636
13749
  } catch (error) {
11637
13750
  throw new Error(
11638
- `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`,
13751
+ `Failed to subscribe to resource: ${subscriptionFailureMessage(error)}`,
11639
13752
  { cause: error }
11640
13753
  );
11641
13754
  }
@@ -11680,7 +13793,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11680
13793
  }
11681
13794
  } catch (error) {
11682
13795
  throw new Error(
11683
- `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`,
13796
+ `Failed to unsubscribe from resource: ${subscriptionFailureMessage(error)}`,
11684
13797
  { cause: error }
11685
13798
  );
11686
13799
  }
@@ -11951,10 +14064,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11951
14064
  return this.oauthManager.getOAuthTokens();
11952
14065
  }
11953
14066
  /**
11954
- * Clears OAuth tokens and client information
14067
+ * Drop this server's stored OAuth state and revoke the grant at the
14068
+ * authorization server (RFC 7009, #2144).
14069
+ *
14070
+ * The request is planned from the stored state, the state is cleared, and
14071
+ * only then is the request sent — so the clear never waits on the network.
14072
+ *
14073
+ * The revocation is best-effort — an authorization server that advertises no
14074
+ * `revocation_endpoint` is left behaving exactly as before, and a network
14075
+ * error, a non-2xx or a timeout is reported in the returned outcome rather
14076
+ * than thrown — so the local clear always completes. Pass
14077
+ * `{ revoke: false }` to skip it.
11955
14078
  */
11956
- async clearOAuthTokens() {
11957
- await this.oauthManager?.clearOAuthTokens();
14079
+ async clearOAuthTokens(options) {
14080
+ return await this.oauthManager?.clearOAuthTokens(options) ?? {
14081
+ status: "skipped",
14082
+ reason: "no_tokens"
14083
+ };
11958
14084
  }
11959
14085
  /**
11960
14086
  * Checks if client is currently OAuth authorized
@@ -12048,7 +14174,30 @@ var EXIT_CODES = {
12048
14174
  AUTH_REQUIRED: 3,
12049
14175
  UNREACHABLE: 4,
12050
14176
  TOOL_ERROR: 5,
12051
- SCHEMA_UNPORTABLE: 6
14177
+ SCHEMA_UNPORTABLE: 6,
14178
+ /**
14179
+ * `--verify` found a SEP-2640 violation: a conformance error, a digest or
14180
+ * size mismatch, or a manifest file that could not be read (#2248).
14181
+ *
14182
+ * Its own code rather than reusing `SCHEMA_UNPORTABLE`, for the reason that
14183
+ * one exists at all: a CI job that fails on an unportable tool schema and a
14184
+ * CI job that fails on a tampered skill digest are different jobs, and
14185
+ * collapsing them would make `if [ $? -eq 6 ]` ambiguous.
14186
+ */
14187
+ SKILL_NONCONFORMANT: 7,
14188
+ /**
14189
+ * `--verify` could not check the whole catalog: the read bounds stopped the
14190
+ * walk before it finished (#2248).
14191
+ *
14192
+ * Distinct from `SKILL_NONCONFORMANT` because the server has broken no
14193
+ * **MUST** — SEP-2640 states the 512-entry and 16 MiB limits as SHOULD NOT,
14194
+ * with hosts free to support more — so exiting 7 would call a conforming
14195
+ * server nonconformant. It is still non-zero, because reporting success for
14196
+ * a manifest whose unread entries were never fetched is a false pass. A CI
14197
+ * job that wants to tolerate oversized catalogs can allow 8 and still fail
14198
+ * on 7.
14199
+ */
14200
+ SKILL_INCOMPLETE: 8
12052
14201
  };
12053
14202
  var CliExitCodeError = class extends Error {
12054
14203
  constructor(exitCode, message, envelope) {
@@ -12556,6 +14705,15 @@ async function consumeMethodOutcome(outcome, args) {
12556
14705
  for (const line of outcome.lines) {
12557
14706
  await awaitableLog(JSON.stringify(line) + "\n");
12558
14707
  }
14708
+ if (outcome.summary) await awaitableError(`${outcome.summary}
14709
+ `);
14710
+ if (outcome.exitCode) {
14711
+ throw new CliExitCodeError(outcome.exitCode, outcome.summary ?? "", {
14712
+ // The envelope's `code` follows the exit code, so a caller reading one
14713
+ // never has to reconcile it against the other.
14714
+ code: outcome.exitCode === EXIT_CODES.SKILL_INCOMPLETE ? "skills_incomplete" : "skills_nonconformant"
14715
+ });
14716
+ }
12559
14717
  return;
12560
14718
  }
12561
14719
  await new Promise((resolve7) => {
@@ -13121,7 +15279,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
13121
15279
  const page = result.tasks.filter((t) => !this.dismissedTaskIds.has(t.taskId)).map(
13122
15280
  (t) => this.cancelledTaskIds.has(t.taskId) ? { ...t, status: "cancelled" } : t
13123
15281
  );
13124
- this.tasks = cursor ? [...this.tasks, ...page] : page;
15282
+ this.tasks = cursor === void 0 ? page : [...this.tasks, ...page];
13125
15283
  cursor = result.nextCursor;
13126
15284
  pageCount++;
13127
15285
  if (pageCount >= MAX_PAGES) {
@@ -13129,7 +15287,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
13129
15287
  `Maximum pagination limit (${MAX_PAGES} pages) reached while listing requestor tasks`
13130
15288
  );
13131
15289
  }
13132
- } while (cursor);
15290
+ } while (cursor !== void 0);
13133
15291
  this.dispatchTypedEvent("tasksChange", this.tasks);
13134
15292
  return this.getTasks();
13135
15293
  }
@@ -13182,6 +15340,146 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
13182
15340
  }
13183
15341
  };
13184
15342
 
15343
+ // ../../core/mcp/state/managedSkillsState.ts
15344
+ var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
15345
+ var SKILLS_MAX_PAGES = LIST_MAX_PAGES;
15346
+ var SKILLS_PAGE_LIMIT_MESSAGE = `skills/list exceeded ${SKILLS_MAX_PAGES} pages without the server's pagination converging`;
15347
+ var ManagedSkillsState = class extends TypedEventTarget {
15348
+ skills = [];
15349
+ pageCount = 0;
15350
+ error = null;
15351
+ client = null;
15352
+ unsubscribe = null;
15353
+ // Overlap guard, held as the generation whose walk is in flight (or `null`).
15354
+ // See `refresh` for why this is not a boolean.
15355
+ runningGeneration = null;
15356
+ // Session counter, advanced by `reset` (disconnect) and by `destroy`. A walk
15357
+ // captures it and abandons its writes when it no longer matches.
15358
+ generation = 0;
15359
+ constructor(client) {
15360
+ super();
15361
+ this.client = client;
15362
+ const onConnect = () => {
15363
+ void this.refresh().catch(() => {
15364
+ });
15365
+ };
15366
+ const onStatusChange = () => {
15367
+ if (isTerminalStatus(this.client?.getStatus())) {
15368
+ this.reset();
15369
+ }
15370
+ };
15371
+ this.client.addEventListener("connect", onConnect);
15372
+ this.client.addEventListener("statusChange", onStatusChange);
15373
+ this.unsubscribe = () => {
15374
+ if (this.client) {
15375
+ this.client.removeEventListener("connect", onConnect);
15376
+ this.client.removeEventListener("statusChange", onStatusChange);
15377
+ }
15378
+ this.client = null;
15379
+ };
15380
+ }
15381
+ /** Defensive copy of the current list. */
15382
+ getSkills() {
15383
+ return [...this.skills];
15384
+ }
15385
+ getPagination() {
15386
+ return { pageCount: this.pageCount };
15387
+ }
15388
+ /** The last walk's failure, or `null` when it succeeded. */
15389
+ getError() {
15390
+ return this.error;
15391
+ }
15392
+ // Compared by identity rather than message: two distinct failures with the
15393
+ // same text are still two events, and a re-render on a repeat failure is
15394
+ // cheap next to silently coalescing them.
15395
+ setError(value) {
15396
+ if (this.error === value) return;
15397
+ this.error = value;
15398
+ this.dispatchTypedEvent("errorChange", value);
15399
+ }
15400
+ reset() {
15401
+ this.generation += 1;
15402
+ this.skills = [];
15403
+ this.pageCount = 0;
15404
+ this.dispatchTypedEvent("skillsChange", this.getSkills());
15405
+ this.dispatchTypedEvent("paginationChange", this.getPagination());
15406
+ this.setError(null);
15407
+ }
15408
+ /**
15409
+ * Walk every page of `skills/list` and commit the result.
15410
+ *
15411
+ * A failure is recorded as observable state (`getError`) AND re-thrown: the
15412
+ * state drives the panel's error rendering, while the rejection is what a
15413
+ * caller's auth-recovery wrapper keys off to detect a 401 and start a
15414
+ * re-authorization. The connect-time load, which has no such caller, catches
15415
+ * it above.
15416
+ */
15417
+ async refresh(metadata) {
15418
+ const client = this.client;
15419
+ if (!client || client.getStatus() !== "connected") return this.getSkills();
15420
+ if (!client.getSkillsExtension()) {
15421
+ this.applyPages([], 0);
15422
+ return this.getSkills();
15423
+ }
15424
+ const generation = this.generation;
15425
+ const isCurrent = () => this.generation === generation;
15426
+ if (this.runningGeneration === generation) return this.getSkills();
15427
+ this.runningGeneration = generation;
15428
+ try {
15429
+ const collected = [];
15430
+ const seen = /* @__PURE__ */ new Set();
15431
+ let cursor;
15432
+ let pages = 0;
15433
+ for (; ; ) {
15434
+ const page = await client.listSkills(cursor, metadata);
15435
+ if (!isCurrent()) return this.getSkills();
15436
+ collected.push(...page.skills);
15437
+ pages += 1;
15438
+ if (page.nextCursor === void 0) break;
15439
+ if (pages >= SKILLS_MAX_PAGES) {
15440
+ throw new Error(SKILLS_PAGE_LIMIT_MESSAGE);
15441
+ }
15442
+ if (seen.has(page.nextCursor)) {
15443
+ throw new Error(REPEATED_CURSOR_MESSAGE);
15444
+ }
15445
+ seen.add(page.nextCursor);
15446
+ cursor = page.nextCursor;
15447
+ }
15448
+ this.setError(null);
15449
+ this.applyPages(collected, pages);
15450
+ return this.getSkills();
15451
+ } catch (err) {
15452
+ if (isCurrent()) {
15453
+ const error = err instanceof Error ? err : new Error(String(err));
15454
+ this.setError(error);
15455
+ if (isClientDecodeRejection(err)) {
15456
+ client.markResponseRejected?.(SKILLS_LIST_METHOD, error.message);
15457
+ }
15458
+ }
15459
+ throw err;
15460
+ } finally {
15461
+ if (this.runningGeneration === generation) {
15462
+ this.runningGeneration = null;
15463
+ }
15464
+ }
15465
+ }
15466
+ applyPages(skills, pageCount) {
15467
+ this.skills = skills;
15468
+ this.pageCount = pageCount;
15469
+ this.dispatchTypedEvent("skillsChange", this.getSkills());
15470
+ this.dispatchTypedEvent("paginationChange", this.getPagination());
15471
+ }
15472
+ /** Unsubscribe from the client and drop the list; idempotent. */
15473
+ destroy() {
15474
+ this.generation += 1;
15475
+ this.unsubscribe?.();
15476
+ this.unsubscribe = null;
15477
+ this.skills = [];
15478
+ this.pageCount = 0;
15479
+ this.error = null;
15480
+ }
15481
+ };
15482
+
13185
15483
  // ../../core/mcp/apps.ts
13186
15484
  import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
13187
15485
  function getAppResourceUri(tool) {
@@ -13252,13 +15550,241 @@ async function collectAppInfo(client, tool, metadata) {
13252
15550
  }
13253
15551
  }
13254
15552
 
15553
+ // src/handlers/skills-verify.ts
15554
+ function summarizeSkillVerification(reports) {
15555
+ const failed = reports.filter((report) => report.outcome === "failed").length;
15556
+ const incomplete = reports.filter(
15557
+ (report) => report.outcome === "incomplete"
15558
+ ).length;
15559
+ const files = reports.reduce((sum, report) => sum + report.files.length, 0);
15560
+ const mismatched = reports.reduce(
15561
+ (sum, report) => sum + report.files.filter((file) => file.status === "mismatch").length,
15562
+ 0
15563
+ );
15564
+ const skillWord = reports.length === 1 ? "skill" : "skills";
15565
+ const fileWord = files === 1 ? "file" : "files";
15566
+ const incompleteClause = incomplete === 0 ? "" : ` ${incomplete} of ${reports.length} ${skillWord} could not be fully checked: the read bounds stopped the walk.`;
15567
+ 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}).`;
15568
+ return `${headline}${incompleteClause}`;
15569
+ }
15570
+
15571
+ // ../../core/mcp/skillsVerification.ts
15572
+ function reasonOf(err) {
15573
+ return err instanceof Error ? err.message : String(err);
15574
+ }
15575
+ function boundedManifest(declared) {
15576
+ const kept = [];
15577
+ let bytes = 0;
15578
+ for (const resource of declared) {
15579
+ if (kept.length >= SKILL_MAX_RESOURCE_ENTRIES) break;
15580
+ const size = typeof resource.size === "number" && Number.isSafeInteger(resource.size) && resource.size >= 0 ? resource.size : 0;
15581
+ if (bytes + size > SKILL_MAX_TOTAL_BYTES) break;
15582
+ bytes += size;
15583
+ kept.push(resource);
15584
+ }
15585
+ return kept;
15586
+ }
15587
+ function contentsFor(result, uri) {
15588
+ const contents = result?.contents;
15589
+ if (!Array.isArray(contents)) return void 0;
15590
+ const wanted = skillUriIdentity(uri);
15591
+ for (const block of contents) {
15592
+ if (typeof block !== "object" || block === null) continue;
15593
+ const got = block.uri;
15594
+ if (typeof got !== "string") continue;
15595
+ if (skillUriIdentity(got) === wanted) return block;
15596
+ }
15597
+ return void 0;
15598
+ }
15599
+ function utf8Length(value) {
15600
+ let total = 0;
15601
+ for (let i = 0; i < value.length; i += 1) {
15602
+ const code = value.charCodeAt(i);
15603
+ if (code < 128) {
15604
+ total += 1;
15605
+ } else if (code < 2048) {
15606
+ total += 2;
15607
+ } else if (code >= 55296 && code <= 56319) {
15608
+ const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
15609
+ if (next >= 56320 && next <= 57343) {
15610
+ total += 4;
15611
+ i += 1;
15612
+ } else {
15613
+ total += 3;
15614
+ }
15615
+ } else {
15616
+ total += 3;
15617
+ }
15618
+ }
15619
+ return total;
15620
+ }
15621
+ function responseBytes(result) {
15622
+ const contents = result?.contents;
15623
+ if (!Array.isArray(contents)) return 0;
15624
+ let total = 0;
15625
+ for (const block of contents) {
15626
+ if (typeof block !== "object" || block === null) continue;
15627
+ const { text, blob } = block;
15628
+ if (typeof text === "string") total += utf8Length(text);
15629
+ if (typeof blob === "string") total += blob.length;
15630
+ }
15631
+ return total;
15632
+ }
15633
+ async function verifySkills(client, entries, metadata) {
15634
+ const collisions = checkSkillNameCollisions(entries);
15635
+ const reports = [];
15636
+ let walkedSkills = 0;
15637
+ let catalogBytes = 0;
15638
+ for (const entry of entries) {
15639
+ const withinBudget = walkedSkills < SKILL_MAX_CATALOG_SKILLS && catalogBytes <= SKILL_MAX_CATALOG_BYTES;
15640
+ let entryBytes;
15641
+ const files = [];
15642
+ const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
15643
+ const manifest = withinBudget ? boundedManifest(declared) : [];
15644
+ let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${SKILL_MAX_CATALOG_SKILLS} skills / ${SKILL_MAX_CATALOG_BYTES} bytes. 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;
15645
+ let receivedBytes = 0;
15646
+ const entryIdentity = skillUriIdentity(entry.uri);
15647
+ let selfAttempted = false;
15648
+ for (const [index, resource] of manifest.entries()) {
15649
+ if (skillUriIdentity(resource.uri) === entryIdentity) {
15650
+ selfAttempted = true;
15651
+ }
15652
+ let charged = 0;
15653
+ try {
15654
+ const invocation = await client.readResource(resource.uri, metadata);
15655
+ charged = responseBytes(invocation.result);
15656
+ const contents = contentsFor(invocation.result, resource.uri);
15657
+ if (!contents) {
15658
+ files.push({
15659
+ uri: resource.uri,
15660
+ status: "read-error",
15661
+ reason: "resources/read returned no content block for this URI, so there are no bytes that can be checked against its digest."
15662
+ });
15663
+ } else {
15664
+ let bytes;
15665
+ try {
15666
+ bytes = skillFileBytes(contents);
15667
+ } catch (err) {
15668
+ files.push({
15669
+ uri: resource.uri,
15670
+ status: "read-error",
15671
+ reason: reasonOf(err)
15672
+ });
15673
+ }
15674
+ if (bytes) {
15675
+ charged = Math.max(charged, bytes.byteLength);
15676
+ if (skillUriIdentity(resource.uri) === entryIdentity)
15677
+ entryBytes = bytes;
15678
+ const verification = await verifySkillResource(resource, bytes);
15679
+ files.push({ uri: resource.uri, ...verification });
15680
+ }
15681
+ }
15682
+ } catch (err) {
15683
+ if (err instanceof AuthRecoveryRequiredError) throw err;
15684
+ files.push({
15685
+ uri: resource.uri,
15686
+ status: "read-error",
15687
+ reason: reasonOf(err)
15688
+ });
15689
+ }
15690
+ receivedBytes += charged;
15691
+ if (receivedBytes > SKILL_MAX_TOTAL_BYTES) {
15692
+ if (index < manifest.length - 1) {
15693
+ 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.`;
15694
+ }
15695
+ break;
15696
+ }
15697
+ }
15698
+ if (withinBudget && !selfAttempted) {
15699
+ const fail = (reason) => files.push({ uri: entry.uri, status: "read-error", reason });
15700
+ try {
15701
+ const invocation = await client.readResource(entry.uri, metadata);
15702
+ const contents = contentsFor(invocation.result, entry.uri);
15703
+ if (!contents) {
15704
+ fail(
15705
+ "resources/read returned no content block for this skill's own SKILL.md, so its frontmatter cannot be checked against the listing."
15706
+ );
15707
+ } else {
15708
+ try {
15709
+ entryBytes = skillFileBytes(contents);
15710
+ } catch (err) {
15711
+ fail(reasonOf(err));
15712
+ }
15713
+ }
15714
+ const declaredSelf = declared.find(
15715
+ (resource) => skillUriIdentity(resource.uri) === entryIdentity
15716
+ );
15717
+ if (declaredSelf && entryBytes !== void 0) {
15718
+ files.push({
15719
+ // The DECLARED spelling, not the entry's. They can differ — a
15720
+ // manifest may write its self-entry in a normalized-equivalent
15721
+ // form — and a consumer matching rows against the manifest then
15722
+ // finds nothing, while a normalized "extra files" filter suppresses
15723
+ // it as already covered. The result was a verdict that existed in
15724
+ // the report and appeared nowhere on screen (Copilot).
15725
+ uri: declaredSelf.uri,
15726
+ ...await verifySkillResource(declaredSelf, entryBytes)
15727
+ });
15728
+ }
15729
+ } catch (err) {
15730
+ if (err instanceof AuthRecoveryRequiredError) throw err;
15731
+ fail(reasonOf(err));
15732
+ }
15733
+ }
15734
+ if (withinBudget) {
15735
+ walkedSkills += 1;
15736
+ catalogBytes += receivedBytes;
15737
+ }
15738
+ const entryText = entryBytes === void 0 ? void 0 : bytesToText(entryBytes);
15739
+ const collision = collisions.get(skillUriIdentity(entry.uri));
15740
+ const conformance = [
15741
+ ...checkSkillConformance(entry),
15742
+ ...collision ? [collision] : []
15743
+ ];
15744
+ const frontmatter = entryText === void 0 ? [] : checkSkillFrontmatterMatch(entry, entryText);
15745
+ const hasError = [...conformance, ...frontmatter].some(
15746
+ (issue) => issue.severity === "error"
15747
+ );
15748
+ const fileFailed = files.some(
15749
+ (file) => file.status === "mismatch" || file.status === "read-error"
15750
+ );
15751
+ reports.push({
15752
+ uri: entry.uri,
15753
+ name: skillDisplayName(entry),
15754
+ conformance,
15755
+ frontmatter,
15756
+ files,
15757
+ ...incomplete ? { incomplete } : {},
15758
+ ok: !hasError && !fileFailed,
15759
+ outcome: hasError || fileFailed ? "failed" : incomplete !== void 0 ? "incomplete" : "verified"
15760
+ });
15761
+ }
15762
+ return reports;
15763
+ }
15764
+ function allSkillsVerified(reports) {
15765
+ return reports.every((report) => report.outcome === "verified");
15766
+ }
15767
+ function anySkillFailed(reports) {
15768
+ return reports.some((report) => report.outcome === "failed");
15769
+ }
15770
+
13255
15771
  // src/handlers/run-method.ts
15772
+ function assertSkillsSupported(inspectorClient, method) {
15773
+ if (!inspectorClient.getSkillsExtension()) {
15774
+ throw new CliExitCodeError(
15775
+ EXIT_CODES.USAGE,
15776
+ `Server does not declare the ${SKILLS_EXTENSION_KEY} extension, so ${method} is not available.`,
15777
+ { code: "skills_unsupported" }
15778
+ );
15779
+ }
15780
+ }
13256
15781
  async function runMethod(inspectorClient, args) {
13257
15782
  let managedToolsState = null;
13258
15783
  let managedResourcesState = null;
13259
15784
  let managedResourceTemplatesState = null;
13260
15785
  let managedPromptsState = null;
13261
15786
  let managedTasksState = null;
15787
+ let managedSkillsState = null;
13262
15788
  try {
13263
15789
  let result;
13264
15790
  let appInfo;
@@ -13477,6 +16003,73 @@ async function runMethod(inspectorClient, args) {
13477
16003
  result = await inspectorClient.getRequestorTaskResult(
13478
16004
  args.taskId
13479
16005
  );
16006
+ } else if (args.method === "skills/list") {
16007
+ assertSkillsSupported(inspectorClient, args.method);
16008
+ managedSkillsState = new ManagedSkillsState(inspectorClient);
16009
+ const skills = await managedSkillsState.refresh(args.metadata);
16010
+ if (args.verify) {
16011
+ const reports = await verifySkills(
16012
+ inspectorClient,
16013
+ skills,
16014
+ args.metadata
16015
+ );
16016
+ return {
16017
+ kind: "ndjson",
16018
+ lines: reports,
16019
+ summary: summarizeSkillVerification(reports),
16020
+ // Three outcomes, three exit codes: a broken MUST is 7, a walk the
16021
+ // read bounds cut short is 8, and everything checked and passing is
16022
+ // 0. Collapsing the middle case into either of the others reports
16023
+ // something untrue about the server (Copilot).
16024
+ ...allSkillsVerified(reports) ? {} : {
16025
+ exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
16026
+ }
16027
+ };
16028
+ }
16029
+ result = { skills };
16030
+ } else if (args.method === "skills/get") {
16031
+ if (!args.uri) {
16032
+ throw new Error(
16033
+ "URI is required for skills/get method. Use --uri to specify the skill URI."
16034
+ );
16035
+ }
16036
+ assertSkillsSupported(inspectorClient, args.method);
16037
+ const envelope = await inspectorClient.getSkillResult(
16038
+ args.uri,
16039
+ args.metadata
16040
+ );
16041
+ const skill = envelope.skill;
16042
+ if (args.verify) {
16043
+ const reports = await verifySkills(
16044
+ inspectorClient,
16045
+ [skill],
16046
+ args.metadata
16047
+ );
16048
+ return {
16049
+ kind: "ndjson",
16050
+ lines: reports,
16051
+ summary: summarizeSkillVerification(reports),
16052
+ // Three outcomes, three exit codes: a broken MUST is 7, a walk the
16053
+ // read bounds cut short is 8, and everything checked and passing is
16054
+ // 0. Collapsing the middle case into either of the others reports
16055
+ // something untrue about the server (Copilot).
16056
+ ...allSkillsVerified(reports) ? {} : {
16057
+ exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
16058
+ }
16059
+ };
16060
+ }
16061
+ result = envelope;
16062
+ } else if (args.method === "resources/directory/read") {
16063
+ if (!args.uri) {
16064
+ throw new Error(
16065
+ "URI is required for resources/directory/read. Use --uri to specify the directory URI."
16066
+ );
16067
+ }
16068
+ result = await inspectorClient.readResourceDirectory(
16069
+ args.uri,
16070
+ args.cursor,
16071
+ args.metadata
16072
+ );
13480
16073
  } else if (args.method === "roots/list") {
13481
16074
  result = { roots: inspectorClient.getRoots() };
13482
16075
  } else if (args.method === "roots/set") {
@@ -13511,6 +16104,7 @@ async function runMethod(inspectorClient, args) {
13511
16104
  managedResourcesState?.destroy();
13512
16105
  managedResourceTemplatesState?.destroy();
13513
16106
  managedPromptsState?.destroy();
16107
+ managedSkillsState?.destroy();
13514
16108
  managedTasksState?.destroy();
13515
16109
  }
13516
16110
  }
@@ -13525,7 +16119,13 @@ var ONE_SHOT_METHODS = [
13525
16119
  "resources/templates/list",
13526
16120
  "prompts/list",
13527
16121
  "prompts/get",
13528
- "logging/setLevel"
16122
+ "logging/setLevel",
16123
+ // SEP-2640. All three are ordinary one-shot request/response calls — no
16124
+ // stream, no long-lived subscription — so they belong here alongside the
16125
+ // other list verbs rather than being reachable only from the session CLI.
16126
+ "skills/list",
16127
+ "skills/get",
16128
+ "resources/directory/read"
13529
16129
  ];
13530
16130
  function isOneShotMethod(method) {
13531
16131
  return ONE_SHOT_METHODS.includes(method);
@@ -13970,7 +16570,7 @@ function createCliOAuthNavigation(options = {}) {
13970
16570
  }
13971
16571
 
13972
16572
  // ../../core/client/config-parse.ts
13973
- import { z as z4 } from "zod";
16573
+ import { z as z5 } from "zod";
13974
16574
  function isAbsoluteHttpUrl(value) {
13975
16575
  let url;
13976
16576
  try {
@@ -13987,11 +16587,11 @@ function isRealHost(hostname) {
13987
16587
  const labels = hostname.split(".");
13988
16588
  return labels.length >= 2 && labels.every((label) => label !== "");
13989
16589
  }
13990
- var HttpUrlStringSchema = z4.string().min(1).superRefine((val, ctx) => {
16590
+ var HttpUrlStringSchema = z5.string().min(1).superRefine((val, ctx) => {
13991
16591
  const trimmed = val.trim();
13992
16592
  if (!isAbsoluteHttpUrl(trimmed)) {
13993
16593
  ctx.addIssue({
13994
- code: z4.ZodIssueCode.custom,
16594
+ code: z5.ZodIssueCode.custom,
13995
16595
  message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
13996
16596
  });
13997
16597
  }
@@ -14023,7 +16623,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
14023
16623
  if (trimmed === "") {
14024
16624
  if (required) {
14025
16625
  ctx.addIssue({
14026
- code: z4.ZodIssueCode.custom,
16626
+ code: z5.ZodIssueCode.custom,
14027
16627
  message: "CIMD client metadata URL is required when CIMD is enabled"
14028
16628
  });
14029
16629
  }
@@ -14032,25 +16632,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
14032
16632
  const error = getCimdClientMetadataUrlError(trimmed);
14033
16633
  if (error) {
14034
16634
  ctx.addIssue({
14035
- code: z4.ZodIssueCode.custom,
16635
+ code: z5.ZodIssueCode.custom,
14036
16636
  message: error
14037
16637
  });
14038
16638
  }
14039
16639
  }
14040
- var CimdConfigSchema = z4.object({
14041
- enabled: z4.boolean(),
14042
- clientMetadataUrl: z4.string()
16640
+ var CimdConfigSchema = z5.object({
16641
+ enabled: z5.boolean(),
16642
+ clientMetadataUrl: z5.string()
14043
16643
  }).superRefine((data, ctx) => {
14044
16644
  refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
14045
16645
  });
14046
- var EnterpriseManagedAuthIdpConfigSchema = z4.object({
16646
+ var EnterpriseManagedAuthIdpConfigSchema = z5.object({
14047
16647
  issuer: HttpUrlStringSchema,
14048
- clientId: z4.string().min(1),
14049
- clientSecret: z4.string().optional()
16648
+ clientId: z5.string().min(1),
16649
+ clientSecret: z5.string().optional()
14050
16650
  });
14051
- var ClientConfigSchema = z4.object({
14052
- enterpriseManagedAuth: z4.object({
14053
- enabled: z4.boolean().optional(),
16651
+ var ClientConfigSchema = z5.object({
16652
+ enterpriseManagedAuth: z5.object({
16653
+ enabled: z5.boolean().optional(),
14054
16654
  idp: EnterpriseManagedAuthIdpConfigSchema
14055
16655
  }).optional(),
14056
16656
  cimd: CimdConfigSchema.optional()
@@ -14518,14 +17118,22 @@ var validLogLevels = Object.values(
14518
17118
  LoggingLevelSchema.enum
14519
17119
  );
14520
17120
  var CLI_CLIENT_NAME = "inspector-cli";
14521
- async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin) {
17121
+ async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin, revoke) {
14522
17122
  if (relogin) {
14523
17123
  if (!("url" in serverConfig && serverConfig.url)) {
14524
17124
  throw new Error(
14525
17125
  "--relogin requires an HTTP/SSE server URL (no OAuth store entry for stdio)"
14526
17126
  );
14527
17127
  }
14528
- await clearStoredAuthForRelogin(serverConfig.url);
17128
+ const revocation = await clearStoredAuthForRelogin(serverConfig.url, {
17129
+ revoke: revoke && serverSettings?.oauthRevokeOnClear !== false
17130
+ });
17131
+ if (revocation?.status === "failed") {
17132
+ process.stderr.write(
17133
+ `Warning: could not revoke the OAuth grant at the authorization server (${revocation.detail}); it may still be valid there.
17134
+ `
17135
+ );
17136
+ }
14529
17137
  }
14530
17138
  const clientIdentity = {
14531
17139
  name: CLI_CLIENT_NAME,
@@ -14646,7 +17254,13 @@ function findStoredToken(servers, serverUrl) {
14646
17254
  }
14647
17255
  async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
14648
17256
  const refresh = deps.refresh ?? refreshAuthorization;
14649
- const discover = deps.discover ?? discoverAuthorizationServerMetadata4;
17257
+ const storedAuthFetch = withRfc8414OidcCompat(createProxyFetch() ?? fetch);
17258
+ const discover = deps.discover ?? ((authorizationServerUrl, options) => discoverAuthorizationServerMetadata4(authorizationServerUrl, {
17259
+ ...options,
17260
+ // A caller-supplied fetch is left alone — it is theirs to compose. The
17261
+ // walker below passes no options, so in practice this is ours.
17262
+ fetchFn: options?.fetchFn ?? storedAuthFetch
17263
+ }));
14650
17264
  const snapshot = await readOAuthSnapshot(statePath);
14651
17265
  const servers = snapshot.servers;
14652
17266
  const found = findStoredServerState(servers, serverUrl);
@@ -14684,7 +17298,8 @@ async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
14684
17298
  metadata,
14685
17299
  clientInformation,
14686
17300
  refreshToken,
14687
- resource: new URL(serverUrl)
17301
+ resource: new URL(serverUrl),
17302
+ fetchFn: storedAuthFetch
14688
17303
  });
14689
17304
  } catch (err) {
14690
17305
  const message = err instanceof Error ? err.message : String(err);
@@ -14814,7 +17429,13 @@ async function parseArgs(argv) {
14814
17429
  "Tool argument as key=value pair",
14815
17430
  parseKeyValuePair2,
14816
17431
  {}
14817
- ).option("--uri <uri>", "URI of the resource (for resources/read method)").option(
17432
+ ).option(
17433
+ "--uri <uri>",
17434
+ "URI of the resource (resources/read, resources/directory/read) or of the skill (skills/get)"
17435
+ ).option(
17436
+ "--cursor <cursor>",
17437
+ "Opaque pagination cursor (for resources/directory/read; pass back the nextCursor from the previous page)."
17438
+ ).option(
14818
17439
  "--prompt-name <promptName>",
14819
17440
  "Name of the prompt (for prompts/get method)"
14820
17441
  ).option(
@@ -14866,6 +17487,9 @@ async function parseArgs(argv) {
14866
17487
  ).option(
14867
17488
  "--strict",
14868
17489
  "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."
17490
+ ).option(
17491
+ "--verify",
17492
+ "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."
14869
17493
  ).option(
14870
17494
  "--connect-timeout <ms>",
14871
17495
  `Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
@@ -14912,6 +17536,9 @@ async function parseArgs(argv) {
14912
17536
  ).option(
14913
17537
  "--relogin",
14914
17538
  "Delete stored OAuth for this server URL from the shared store before connect (HTTP/SSE URL keys only); interactive login runs only if the server requires auth. Rejected for stdio (no URL-keyed store entry)"
17539
+ ).option(
17540
+ "--no-revoke",
17541
+ "Requires --relogin. Skips the RFC 7009 revocation request that would otherwise end the grant at the authorization server when the local state is deleted. Also skipped when the server entry sets oauth.revokeOnClear to false."
14915
17542
  ).option(
14916
17543
  "--wait-for-auth <sec>",
14917
17544
  "Poll the OAuth state file until a token for --server-url appears (or the timeout elapses), then proceed as if --use-stored-auth were set. Use after handing off to a human to complete OAuth in a browser.",
@@ -14953,6 +17580,9 @@ async function parseArgs(argv) {
14953
17580
  );
14954
17581
  }
14955
17582
  }
17583
+ if (options.revoke === false && !options.relogin) {
17584
+ throw new Error("--no-revoke requires --relogin (it has no other effect).");
17585
+ }
14956
17586
  if (options.strict) {
14957
17587
  if (options.method !== "tools/list") {
14958
17588
  throw new Error("--strict requires --method tools/list.");
@@ -14963,6 +17593,13 @@ async function parseArgs(argv) {
14963
17593
  );
14964
17594
  }
14965
17595
  }
17596
+ if (options.verify) {
17597
+ if (options.method !== "skills/list" && options.method !== "skills/get") {
17598
+ throw new Error(
17599
+ "--verify requires --method skills/list or --method skills/get."
17600
+ );
17601
+ }
17602
+ }
14966
17603
  const oauthStatePath = getStateFilePath();
14967
17604
  if (options.listStoredAuth) {
14968
17605
  const servers = await readOAuthServers(oauthStatePath);
@@ -15126,6 +17763,8 @@ async function parseArgs(argv) {
15126
17763
  toolMeta: options.toolMetadata,
15127
17764
  appInfo: options.appInfo === true,
15128
17765
  strict: options.strict === true,
17766
+ verify: options.verify === true,
17767
+ cursor: options.cursor,
15129
17768
  format: options.format
15130
17769
  };
15131
17770
  return {
@@ -15138,7 +17777,10 @@ async function parseArgs(argv) {
15138
17777
  clientMetadataUrl: options.clientMetadataUrl,
15139
17778
  callbackUrl: options.callbackUrl,
15140
17779
  storedAuthOnly: options.storedAuthOnly === true,
15141
- relogin: options.relogin === true
17780
+ relogin: options.relogin === true,
17781
+ // Commander's `--no-revoke` defaults this to true; only an explicit
17782
+ // `--no-revoke` makes it false.
17783
+ revoke: options.revoke !== false
15142
17784
  };
15143
17785
  }
15144
17786
  async function runCli(argv) {
@@ -15154,7 +17796,8 @@ async function runCli(argv) {
15154
17796
  clientMetadataUrl,
15155
17797
  callbackUrl,
15156
17798
  storedAuthOnly,
15157
- relogin
17799
+ relogin,
17800
+ revoke
15158
17801
  } = parsed;
15159
17802
  const clientConfig = await loadRunnerClientConfig({ clientConfigPath });
15160
17803
  let callbackUrlConfig;
@@ -15178,7 +17821,8 @@ async function runCli(argv) {
15178
17821
  },
15179
17822
  callbackUrlConfig,
15180
17823
  storedAuthOnly === true,
15181
- relogin === true
17824
+ relogin === true,
17825
+ revoke !== false
15182
17826
  );
15183
17827
  }
15184
17828