@modelcontextprotocol/inspector 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -577
- package/clients/cli/build/index.js +1286 -49
- package/clients/tui/build/index.js +1887 -759
- package/clients/web/build/index.js +126 -37
- package/clients/web/dist/assets/index-ZYqA4f7P.js +1486 -0
- package/clients/web/dist/assets/{index-DQVN73FG.css → index-dqp8Sd8W.css} +1 -1
- package/clients/web/dist/assets/{prism-light-HLcnHJHw.js → prism-light-_bvDBTQE.js} +1 -1
- package/clients/web/dist/index.html +2 -2
- package/package.json +24 -12
- package/clients/web/dist/assets/index-JCsLrWVa.js +0 -1484
- package/clients/web/dist/assets/json-BJ8dCynk.js +0 -1
|
@@ -390,6 +390,491 @@ 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 properties = Object.values(propertiesOf(merged));
|
|
620
|
+
return properties.length > 0 && // Every value has to be something a renderer can read AND something a
|
|
621
|
+
// caller can satisfy. A `null` or an array is not a schema at all, and the
|
|
622
|
+
// web form dereferences one on the way to choosing a widget. JSON Schema's
|
|
623
|
+
// boolean form is legal, but only `true` is harmless — it constrains
|
|
624
|
+
// nothing and answers every keyword lookup with `undefined`, while `false`
|
|
625
|
+
// admits no value whatsoever, so a field declared with it can never be
|
|
626
|
+
// filled and a required one makes the whole branch unsatisfiable. Either
|
|
627
|
+
// way the branch is declined rather than offered as a callable shape.
|
|
628
|
+
properties.every(
|
|
629
|
+
(property) => property === true || typeof property === "object" && property !== null && !Array.isArray(property)
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
var ANNOTATION_KEYWORDS2 = /* @__PURE__ */ new Set([
|
|
633
|
+
"title",
|
|
634
|
+
"description",
|
|
635
|
+
"examples",
|
|
636
|
+
"deprecated",
|
|
637
|
+
"readOnly",
|
|
638
|
+
"writeOnly",
|
|
639
|
+
"$comment",
|
|
640
|
+
// An annotation too: a suggested initial value constrains nothing, so two
|
|
641
|
+
// declarations suggesting different ones still accept the same values.
|
|
642
|
+
"default"
|
|
643
|
+
]);
|
|
644
|
+
function canonicalJson(value) {
|
|
645
|
+
if (Array.isArray(value)) {
|
|
646
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
647
|
+
}
|
|
648
|
+
if (typeof value === "object" && value !== null) {
|
|
649
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(
|
|
650
|
+
([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`
|
|
651
|
+
);
|
|
652
|
+
return `{${entries.join(",")}}`;
|
|
653
|
+
}
|
|
654
|
+
return JSON.stringify(value) ?? "undefined";
|
|
655
|
+
}
|
|
656
|
+
function sameJsonValue(a, b) {
|
|
657
|
+
return sameValue(a, b);
|
|
658
|
+
}
|
|
659
|
+
function sameValue(a, b) {
|
|
660
|
+
return a === b || canonicalJson(a) === canonicalJson(b);
|
|
661
|
+
}
|
|
662
|
+
function conflicts(baseProperty, branchProperty) {
|
|
663
|
+
const a = toBranch2(baseProperty);
|
|
664
|
+
const b = toBranch2(branchProperty);
|
|
665
|
+
if (a === null || b === null) {
|
|
666
|
+
return !sameValue(baseProperty, branchProperty);
|
|
667
|
+
}
|
|
668
|
+
const left = a;
|
|
669
|
+
const right = b;
|
|
670
|
+
if (Object.keys(right).some(
|
|
671
|
+
(keyword) => !ANNOTATION_KEYWORDS2.has(keyword) && keyword in left && !sameValue(left[keyword], right[keyword])
|
|
672
|
+
)) {
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
return !constSatisfiesSiblings({ ...left, ...right });
|
|
676
|
+
}
|
|
677
|
+
function jsonTypeOf(value) {
|
|
678
|
+
if (value === null) return "null";
|
|
679
|
+
if (Array.isArray(value)) return "array";
|
|
680
|
+
if (typeof value === "number") {
|
|
681
|
+
return Number.isInteger(value) ? "integer" : "number";
|
|
682
|
+
}
|
|
683
|
+
return typeof value;
|
|
684
|
+
}
|
|
685
|
+
var CONST_CHECKABLE = /* @__PURE__ */ new Set(["const", "type", "enum"]);
|
|
686
|
+
function constSatisfiesSiblings(schema) {
|
|
687
|
+
if (!("const" in schema)) return true;
|
|
688
|
+
const value = schema.const;
|
|
689
|
+
const { type, enum: allowed } = schema;
|
|
690
|
+
if (Object.keys(schema).some(
|
|
691
|
+
(keyword) => !CONST_CHECKABLE.has(keyword) && !ANNOTATION_KEYWORDS2.has(keyword)
|
|
692
|
+
)) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
const actual = jsonTypeOf(value);
|
|
696
|
+
const admits = (name) => name === actual || name === "number" && actual === "integer";
|
|
697
|
+
if (typeof type === "string" && !admits(type)) return false;
|
|
698
|
+
if (Array.isArray(type) && !type.some(admits)) return false;
|
|
699
|
+
if (Array.isArray(allowed) && !allowed.some((member) => sameValue(member, value))) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
function mergeProperty(baseProperty, branchProperty) {
|
|
705
|
+
const a = toBranch2(baseProperty);
|
|
706
|
+
const b = toBranch2(branchProperty);
|
|
707
|
+
if (a === null || b === null) {
|
|
708
|
+
return branchProperty;
|
|
709
|
+
}
|
|
710
|
+
return { ...a, ...b };
|
|
711
|
+
}
|
|
712
|
+
var MERGEABLE_KEYWORDS = /* @__PURE__ */ new Set(["type", "properties", "required"]);
|
|
713
|
+
function isFlattenable(member) {
|
|
714
|
+
const branch = toBranch2(member);
|
|
715
|
+
if (branch === null || !admitsObject(branch)) return false;
|
|
716
|
+
return Object.keys(branch).every(
|
|
717
|
+
(keyword) => MERGEABLE_KEYWORDS.has(keyword) || ANNOTATION_KEYWORDS2.has(keyword)
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
function conflictsWithBase(base, branch) {
|
|
721
|
+
const baseProperties = propertiesOf(base);
|
|
722
|
+
const branchProperties = propertiesOf(branch);
|
|
723
|
+
return Object.entries(branchProperties).some(
|
|
724
|
+
([name, branchProperty]) => (
|
|
725
|
+
// `hasOwn`, not `in`: `properties` is a JSON record, so `constructor` and
|
|
726
|
+
// `toString` are legal argument names that `in` would find on
|
|
727
|
+
// `Object.prototype` and report as collisions the root never declared.
|
|
728
|
+
Object.hasOwn(baseProperties, name) && conflicts(baseProperties[name], branchProperty)
|
|
729
|
+
)
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
function hasDiscriminator(members, named, rootRequired) {
|
|
733
|
+
const first = propertiesOf(members[0] ?? {});
|
|
734
|
+
const candidates = named !== void 0 ? [named] : Object.keys(first);
|
|
735
|
+
return candidates.some((name) => {
|
|
736
|
+
const constants = members.map((member) => {
|
|
737
|
+
if (!rootRequired.includes(name) && !requiredOf(member).includes(name)) {
|
|
738
|
+
return void 0;
|
|
739
|
+
}
|
|
740
|
+
const property = toBranch2(propertiesOf(member)[name]);
|
|
741
|
+
return property?.const;
|
|
742
|
+
});
|
|
743
|
+
if (constants.some((value) => value === void 0)) return false;
|
|
744
|
+
const seen = new Set(constants.map(canonicalJson));
|
|
745
|
+
return seen.size === constants.length;
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
function restrictsAdditional(schema) {
|
|
749
|
+
const additional = schema.additionalProperties;
|
|
750
|
+
return additional === false || typeof additional === "object" && additional !== null && Object.keys(additional).some(
|
|
751
|
+
(keyword) => !ANNOTATION_KEYWORDS2.has(keyword)
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
function addsForbiddenNames(base, member) {
|
|
755
|
+
if (!restrictsAdditional(base)) return false;
|
|
756
|
+
const baseNames = propertiesOf(base);
|
|
757
|
+
return Object.keys(propertiesOf(member)).some(
|
|
758
|
+
(name) => !Object.hasOwn(baseNames, name)
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
function mergeBranch(base, branch) {
|
|
762
|
+
const baseProperties = propertiesOf(base);
|
|
763
|
+
const branchProperties = propertiesOf(branch);
|
|
764
|
+
const properties = Object.fromEntries([
|
|
765
|
+
...Object.entries(baseProperties),
|
|
766
|
+
...Object.entries(branchProperties).map(([name, branchProperty]) => [
|
|
767
|
+
name,
|
|
768
|
+
Object.hasOwn(baseProperties, name) ? mergeProperty(baseProperties[name], branchProperty) : branchProperty
|
|
769
|
+
])
|
|
770
|
+
]);
|
|
771
|
+
const baseRequired = requiredOf(base);
|
|
772
|
+
const required = [
|
|
773
|
+
...baseRequired,
|
|
774
|
+
...requiredOf(branch).filter((name) => !baseRequired.includes(name))
|
|
775
|
+
];
|
|
776
|
+
return {
|
|
777
|
+
...base,
|
|
778
|
+
...Object.keys(properties).length > 0 ? { properties } : {},
|
|
779
|
+
...required.length > 0 ? { required } : {}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function withoutComposition(schema) {
|
|
783
|
+
const { allOf: _allOf, anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
|
|
784
|
+
return rest;
|
|
785
|
+
}
|
|
786
|
+
function branchLabel(branch, index, discriminatorProperty) {
|
|
787
|
+
if (typeof branch.title === "string" && branch.title.trim() !== "") {
|
|
788
|
+
return branch.title;
|
|
789
|
+
}
|
|
790
|
+
const properties = propertiesOf(branch);
|
|
791
|
+
const constOf = (name) => {
|
|
792
|
+
const property = toBranch2(properties[name]);
|
|
793
|
+
if (property === null || !("const" in property)) return void 0;
|
|
794
|
+
const value = property.const;
|
|
795
|
+
return value === null || typeof value !== "object" ? String(value) : void 0;
|
|
796
|
+
};
|
|
797
|
+
if (discriminatorProperty !== void 0) {
|
|
798
|
+
const value = constOf(discriminatorProperty);
|
|
799
|
+
if (value !== void 0) return value;
|
|
800
|
+
}
|
|
801
|
+
const constants = Object.keys(properties).map((name) => constOf(name)).filter((value) => value !== void 0);
|
|
802
|
+
if (constants.length === 1) return constants[0];
|
|
803
|
+
return `Option ${index + 1}`;
|
|
804
|
+
}
|
|
805
|
+
function resolveRootUnion(schema) {
|
|
806
|
+
const allOfMembers = membersOf(schema.allOf);
|
|
807
|
+
let merged = schema;
|
|
808
|
+
for (const member of allOfMembers) {
|
|
809
|
+
const branch = toBranch2(member);
|
|
810
|
+
if (branch === null || !isFlattenable(member) || conflictsWithBase(merged, branch) || addsForbiddenNames(merged, branch)) {
|
|
811
|
+
return { base: schema, branches: [] };
|
|
812
|
+
}
|
|
813
|
+
merged = mergeBranch(merged, branch);
|
|
814
|
+
}
|
|
815
|
+
const base = withoutComposition(merged);
|
|
816
|
+
if (schema.oneOf !== void 0 && schema.anyOf !== void 0) {
|
|
817
|
+
return { base, branches: [] };
|
|
818
|
+
}
|
|
819
|
+
const isExclusiveUnion = schema.oneOf !== void 0;
|
|
820
|
+
const members = membersOf(schema.oneOf ?? schema.anyOf);
|
|
821
|
+
const branches = members.map(toBranch2);
|
|
822
|
+
if (branches.length === 0 || branches.some(
|
|
823
|
+
(branch) => branch === null || !isOfferable(branch, mergeBranch(base, branch)) || // The same faithfulness test the `allOf` fold applies: a member
|
|
824
|
+
// carrying a constraint the merge does not copy — a nested `allOf`, a
|
|
825
|
+
// `not`, a `$ref` — would have it erased along with the union keyword,
|
|
826
|
+
// which can present an unsatisfiable branch as a callable one.
|
|
827
|
+
!isFlattenable(branch) || conflictsWithBase(base, branch)
|
|
828
|
+
)) {
|
|
829
|
+
return { base, branches: [] };
|
|
830
|
+
}
|
|
831
|
+
if (branches.some(
|
|
832
|
+
(branch) => addsForbiddenNames(base, branch)
|
|
833
|
+
)) {
|
|
834
|
+
return { base, branches: [] };
|
|
835
|
+
}
|
|
836
|
+
if (isExclusiveUnion && !hasDiscriminator(
|
|
837
|
+
branches,
|
|
838
|
+
schema.discriminator?.propertyName,
|
|
839
|
+
requiredOf(base)
|
|
840
|
+
)) {
|
|
841
|
+
return { base, branches: [] };
|
|
842
|
+
}
|
|
843
|
+
const discriminatorProperty = schema.discriminator?.propertyName;
|
|
844
|
+
return {
|
|
845
|
+
base,
|
|
846
|
+
branches: branches.filter((branch) => branch !== null).map((branch, index) => ({
|
|
847
|
+
// `base` is already composition-free and `mergeBranch` copies only
|
|
848
|
+
// `properties`/`required` off the branch, so the merge stays that way.
|
|
849
|
+
schema: mergeBranch(base, branch),
|
|
850
|
+
label: branchLabel(branch, index, discriminatorProperty),
|
|
851
|
+
declaredFields: Object.keys(propertiesOf(branch))
|
|
852
|
+
}))
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function narrowBySuppliedNames(branches, candidates, suppliedNames) {
|
|
856
|
+
if (candidates.length === 1) return candidates[0];
|
|
857
|
+
if (candidates.length === 0 || suppliedNames.length === 0) return null;
|
|
858
|
+
const supplied = new Set(suppliedNames);
|
|
859
|
+
const satisfied = candidates.filter((index) => {
|
|
860
|
+
const branch = branches[index];
|
|
861
|
+
const required = requiredOf(branch.schema);
|
|
862
|
+
const own = required.filter((name) => branch.declaredFields.includes(name));
|
|
863
|
+
return own.length > 0 && own.every((name) => supplied.has(name));
|
|
864
|
+
});
|
|
865
|
+
if (satisfied.length === 1) return satisfied[0];
|
|
866
|
+
const exclusiveTo = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const index of candidates) {
|
|
868
|
+
for (const name of branches[index].declaredFields) {
|
|
869
|
+
exclusiveTo.set(name, exclusiveTo.has(name) ? -1 : index);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const named = new Set(
|
|
873
|
+
suppliedNames.map((name) => exclusiveTo.get(name)).filter((index) => index !== void 0 && index >= 0)
|
|
874
|
+
);
|
|
875
|
+
return named.size === 1 ? [...named][0] : null;
|
|
876
|
+
}
|
|
877
|
+
|
|
393
878
|
// ../../core/json/jsonUtils.ts
|
|
394
879
|
function isSerializableJson(value) {
|
|
395
880
|
if (value === null) return true;
|
|
@@ -429,16 +914,90 @@ function convertParameterValue(value, schema) {
|
|
|
429
914
|
}
|
|
430
915
|
return value;
|
|
431
916
|
}
|
|
917
|
+
function coercionProperties(base, branches, params) {
|
|
918
|
+
if (branches.length === 0) {
|
|
919
|
+
return { ...base.properties };
|
|
920
|
+
}
|
|
921
|
+
const candidates = branches.map((branch, index) => ({ branch, index })).filter(
|
|
922
|
+
({ branch }) => matchesConstants(branch.schema.properties ?? {}, params)
|
|
923
|
+
).map(({ index }) => index);
|
|
924
|
+
const selected = narrowBySuppliedNames(
|
|
925
|
+
branches,
|
|
926
|
+
candidates,
|
|
927
|
+
Object.keys(params)
|
|
928
|
+
);
|
|
929
|
+
if (selected !== null) {
|
|
930
|
+
return { ...branches[selected].schema.properties };
|
|
931
|
+
}
|
|
932
|
+
const properties = Object.fromEntries(
|
|
933
|
+
Object.entries(base.properties ?? {})
|
|
934
|
+
);
|
|
935
|
+
const pool = candidates.length > 0 ? candidates.map((i) => branches[i]) : branches;
|
|
936
|
+
for (const name of new Set(pool.flatMap((b) => b.declaredFields))) {
|
|
937
|
+
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));
|
|
938
|
+
const types = new Set(declarations.map((schema) => typeNameOf(schema)));
|
|
939
|
+
const pinnedOf = (schema) => typeof schema === "object" && schema !== null && "const" in schema ? schema.const : void 0;
|
|
940
|
+
const constsAgree = declarations.every(
|
|
941
|
+
(schema) => sameJsonValue(pinnedOf(schema), pinnedOf(declarations[0]))
|
|
942
|
+
);
|
|
943
|
+
if (types.size === 1 && constsAgree && declarations.length > 0) {
|
|
944
|
+
Object.defineProperty(properties, name, {
|
|
945
|
+
value: declarations[0],
|
|
946
|
+
writable: true,
|
|
947
|
+
enumerable: true,
|
|
948
|
+
configurable: true
|
|
949
|
+
});
|
|
950
|
+
} else {
|
|
951
|
+
delete properties[name];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return properties;
|
|
955
|
+
}
|
|
956
|
+
function typeNameOf(schema) {
|
|
957
|
+
if (typeof schema !== "object" || schema === null) return "";
|
|
958
|
+
const { type } = schema;
|
|
959
|
+
return Array.isArray(type) ? [...type].map(String).sort().join(",") : typeof type === "string" ? type : "";
|
|
960
|
+
}
|
|
961
|
+
function matchesConstants(properties, params) {
|
|
962
|
+
return Object.entries(properties).every(([name, schema]) => {
|
|
963
|
+
if (typeof schema !== "object" || schema === null) return true;
|
|
964
|
+
const constValue = schema.const;
|
|
965
|
+
if (constValue === void 0) return true;
|
|
966
|
+
if (!Object.hasOwn(params, name)) return true;
|
|
967
|
+
const supplied = params[name];
|
|
968
|
+
return supplied === void 0 || suppliedMatchesConst(supplied, constValue);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
function suppliedMatchesConst(value, constValue) {
|
|
972
|
+
if (constValue === null || typeof constValue !== "object") {
|
|
973
|
+
return value === String(constValue);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
return sameJsonValue(JSON.parse(value), constValue);
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
432
981
|
function convertToolParameters(tool, params) {
|
|
982
|
+
return convertParametersForSchema(tool.inputSchema, params);
|
|
983
|
+
}
|
|
984
|
+
function convertParametersForSchema(inputSchema, params) {
|
|
433
985
|
const result = {};
|
|
434
|
-
const
|
|
986
|
+
const { base, branches } = resolveRootUnion(
|
|
987
|
+
inputSchema ?? {}
|
|
988
|
+
);
|
|
989
|
+
const properties = coercionProperties(base, branches, params);
|
|
435
990
|
for (const [key, value] of Object.entries(params)) {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
991
|
+
const declared = properties[key];
|
|
992
|
+
const paramSchema = typeof declared === "object" && declared !== null ? normalizeNullableUnion(declared) : declared;
|
|
993
|
+
const pinned = paramSchema?.const;
|
|
994
|
+
const converted = pinned !== void 0 && suppliedMatchesConst(value, pinned) ? pinned : paramSchema ? convertParameterValue(value, paramSchema) : value;
|
|
995
|
+
Object.defineProperty(result, key, {
|
|
996
|
+
value: converted,
|
|
997
|
+
writable: true,
|
|
998
|
+
enumerable: true,
|
|
999
|
+
configurable: true
|
|
1000
|
+
});
|
|
442
1001
|
}
|
|
443
1002
|
return result;
|
|
444
1003
|
}
|
|
@@ -675,6 +1234,9 @@ function storedFieldsToInspectorSettings(stored) {
|
|
|
675
1234
|
if (stored.oauth?.requestRefreshToken === false) {
|
|
676
1235
|
settings.oauthRequestRefreshToken = false;
|
|
677
1236
|
}
|
|
1237
|
+
if (stored.oauth?.revokeOnClear === false) {
|
|
1238
|
+
settings.oauthRevokeOnClear = false;
|
|
1239
|
+
}
|
|
678
1240
|
if (stored.cwd) settings.cwd = stored.cwd;
|
|
679
1241
|
return settings;
|
|
680
1242
|
}
|
|
@@ -3460,6 +4022,25 @@ var OAuthStorageBase = class {
|
|
|
3460
4022
|
this.memory.getState().setServerState(serverUrl, { discoveryState: void 0 });
|
|
3461
4023
|
await this.persist();
|
|
3462
4024
|
}
|
|
4025
|
+
async takeRevocationSnapshot(serverUrl) {
|
|
4026
|
+
await this.ensureLoaded();
|
|
4027
|
+
const state = this.memory.getState().getServerState(serverUrl);
|
|
4028
|
+
const snapshot = {
|
|
4029
|
+
byIssuer: Object.fromEntries(
|
|
4030
|
+
Object.entries(state.byIssuer ?? {}).map(([issuer, slot]) => [
|
|
4031
|
+
issuer,
|
|
4032
|
+
{ tokens: slot?.tokens, clientInformation: slot?.clientInformation }
|
|
4033
|
+
])
|
|
4034
|
+
),
|
|
4035
|
+
legacyTokens: state.tokens,
|
|
4036
|
+
legacyClientInformation: state.clientInformation,
|
|
4037
|
+
preregisteredClientInformation: state.preregisteredClientInformation,
|
|
4038
|
+
serverMetadata: state.serverMetadata
|
|
4039
|
+
};
|
|
4040
|
+
this.memory.getState().clearServerState(serverUrl);
|
|
4041
|
+
await this.persist();
|
|
4042
|
+
return snapshot;
|
|
4043
|
+
}
|
|
3463
4044
|
async clear(serverUrl) {
|
|
3464
4045
|
await this.ensureLoaded();
|
|
3465
4046
|
this.memory.getState().clearServerState(serverUrl);
|
|
@@ -3650,6 +4231,288 @@ var NodeOAuthStorage = class extends OAuthStorageBase {
|
|
|
3650
4231
|
}
|
|
3651
4232
|
};
|
|
3652
4233
|
|
|
4234
|
+
// ../../core/auth/revocation.ts
|
|
4235
|
+
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
|
|
4236
|
+
import {
|
|
4237
|
+
OAuthClientInformationSchema as OAuthClientInformationSchema2,
|
|
4238
|
+
OAuthTokensSchema as OAuthTokensSchema2
|
|
4239
|
+
} from "@modelcontextprotocol/core";
|
|
4240
|
+
var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
|
|
4241
|
+
function selectRevocableToken(tokens) {
|
|
4242
|
+
if (!tokens) return null;
|
|
4243
|
+
if (tokens.refresh_token) {
|
|
4244
|
+
return { token: tokens.refresh_token, tokenTypeHint: "refresh_token" };
|
|
4245
|
+
}
|
|
4246
|
+
if (tokens.access_token) {
|
|
4247
|
+
return { token: tokens.access_token, tokenTypeHint: "access_token" };
|
|
4248
|
+
}
|
|
4249
|
+
return null;
|
|
4250
|
+
}
|
|
4251
|
+
function revocationAuthMethods(metadata) {
|
|
4252
|
+
return metadata.revocation_endpoint_auth_methods_supported ?? [
|
|
4253
|
+
"client_secret_basic"
|
|
4254
|
+
];
|
|
4255
|
+
}
|
|
4256
|
+
function buildRevocationRequest(params) {
|
|
4257
|
+
const body = new URLSearchParams({
|
|
4258
|
+
token: params.token,
|
|
4259
|
+
token_type_hint: params.tokenTypeHint
|
|
4260
|
+
});
|
|
4261
|
+
const headers = {
|
|
4262
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
4263
|
+
Accept: "application/json"
|
|
4264
|
+
};
|
|
4265
|
+
const client = params.clientInformation;
|
|
4266
|
+
if (client) {
|
|
4267
|
+
const method = selectClientAuthMethod(client, params.supportedAuthMethods);
|
|
4268
|
+
if (method === "client_secret_basic" && client.client_secret) {
|
|
4269
|
+
const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
|
|
4270
|
+
headers.Authorization = `Basic ${base64Encode(credentials)}`;
|
|
4271
|
+
} else if (method === "client_secret_post" && client.client_secret) {
|
|
4272
|
+
body.set("client_id", client.client_id);
|
|
4273
|
+
body.set("client_secret", client.client_secret);
|
|
4274
|
+
} else {
|
|
4275
|
+
body.set("client_id", client.client_id);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
return {
|
|
4279
|
+
url: params.endpoint,
|
|
4280
|
+
init: { method: "POST", headers, body: body.toString() }
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
function base64Encode(value) {
|
|
4284
|
+
if (typeof btoa === "function") return btoa(value);
|
|
4285
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
4286
|
+
}
|
|
4287
|
+
async function revokeToken(params) {
|
|
4288
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
|
|
4289
|
+
try {
|
|
4290
|
+
const { url, init } = buildRevocationRequest(params);
|
|
4291
|
+
const response = await withDeadline(
|
|
4292
|
+
params.fetchFn(url, { ...init, signal: AbortSignal.timeout(timeoutMs) }),
|
|
4293
|
+
timeoutMs
|
|
4294
|
+
);
|
|
4295
|
+
if (!response.ok) {
|
|
4296
|
+
return {
|
|
4297
|
+
status: "failed",
|
|
4298
|
+
endpoint: url,
|
|
4299
|
+
detail: `revocation endpoint responded ${response.status} ${response.statusText}`.trim()
|
|
4300
|
+
};
|
|
4301
|
+
}
|
|
4302
|
+
return {
|
|
4303
|
+
status: "revoked",
|
|
4304
|
+
tokenTypeHint: params.tokenTypeHint,
|
|
4305
|
+
endpoint: url
|
|
4306
|
+
};
|
|
4307
|
+
} catch (err) {
|
|
4308
|
+
return {
|
|
4309
|
+
// `params.endpoint`, not the built `url`: construction is inside the try
|
|
4310
|
+
// now, so `url` may not exist on this path.
|
|
4311
|
+
status: "failed",
|
|
4312
|
+
endpoint: params.endpoint,
|
|
4313
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
async function withDeadline(promise, timeoutMs) {
|
|
4318
|
+
let timer;
|
|
4319
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
4320
|
+
timer = setTimeout(
|
|
4321
|
+
() => reject(new Error(`revocation request timed out after ${timeoutMs}ms`)),
|
|
4322
|
+
timeoutMs
|
|
4323
|
+
);
|
|
4324
|
+
});
|
|
4325
|
+
try {
|
|
4326
|
+
return await Promise.race([promise, deadline]);
|
|
4327
|
+
} finally {
|
|
4328
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
function emptyPlan(serverUrl, outcome, failures = []) {
|
|
4332
|
+
return {
|
|
4333
|
+
serverUrl,
|
|
4334
|
+
grants: [],
|
|
4335
|
+
failures,
|
|
4336
|
+
supportedAuthMethods: [],
|
|
4337
|
+
outcome: failures.length > 0 ? aggregateOutcomes([...failures, outcome]) : outcome
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
async function clearAndPlanRevocation(params) {
|
|
4341
|
+
const { serverUrl, storage } = params;
|
|
4342
|
+
let snapshot;
|
|
4343
|
+
try {
|
|
4344
|
+
snapshot = await storage.takeRevocationSnapshot(serverUrl);
|
|
4345
|
+
} catch (err) {
|
|
4346
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
4347
|
+
}
|
|
4348
|
+
if (params.enabled === false) {
|
|
4349
|
+
return emptyPlan(serverUrl, { status: "skipped", reason: "disabled" });
|
|
4350
|
+
}
|
|
4351
|
+
try {
|
|
4352
|
+
const { grants, failures } = await collectGrants(snapshot);
|
|
4353
|
+
if (grants.length === 0) {
|
|
4354
|
+
return emptyPlan(
|
|
4355
|
+
serverUrl,
|
|
4356
|
+
{ status: "skipped", reason: "no_tokens" },
|
|
4357
|
+
failures
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
const metadata = parseServerMetadata(snapshot.serverMetadata);
|
|
4361
|
+
if (!metadata) {
|
|
4362
|
+
return emptyPlan(
|
|
4363
|
+
serverUrl,
|
|
4364
|
+
{ status: "skipped", reason: "no_metadata" },
|
|
4365
|
+
failures
|
|
4366
|
+
);
|
|
4367
|
+
}
|
|
4368
|
+
if (!metadata.revocation_endpoint) {
|
|
4369
|
+
return emptyPlan(
|
|
4370
|
+
serverUrl,
|
|
4371
|
+
{ status: "skipped", reason: "no_endpoint" },
|
|
4372
|
+
failures
|
|
4373
|
+
);
|
|
4374
|
+
}
|
|
4375
|
+
return {
|
|
4376
|
+
serverUrl,
|
|
4377
|
+
grants,
|
|
4378
|
+
failures,
|
|
4379
|
+
endpoint: metadata.revocation_endpoint,
|
|
4380
|
+
supportedAuthMethods: revocationAuthMethods(metadata),
|
|
4381
|
+
metadataIssuer: metadata.issuer
|
|
4382
|
+
};
|
|
4383
|
+
} catch (err) {
|
|
4384
|
+
return emptyPlan(serverUrl, {
|
|
4385
|
+
status: "failed",
|
|
4386
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
4387
|
+
});
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
function parseServerMetadata(raw) {
|
|
4391
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
4392
|
+
return raw;
|
|
4393
|
+
}
|
|
4394
|
+
async function executeOAuthRevocation(plan, params) {
|
|
4395
|
+
const outcome = await runPlan(plan, params);
|
|
4396
|
+
const { logger } = params;
|
|
4397
|
+
if (outcome.status === "failed") {
|
|
4398
|
+
logger?.warn(
|
|
4399
|
+
{
|
|
4400
|
+
serverUrl: plan.serverUrl,
|
|
4401
|
+
endpoint: outcome.endpoint,
|
|
4402
|
+
detail: outcome.detail
|
|
4403
|
+
},
|
|
4404
|
+
"Token revocation failed; local OAuth state was cleared anyway"
|
|
4405
|
+
);
|
|
4406
|
+
} else if (outcome.status === "skipped" && outcome.reason === "no_endpoint") {
|
|
4407
|
+
logger?.debug(
|
|
4408
|
+
{ serverUrl: plan.serverUrl },
|
|
4409
|
+
"Skipping token revocation: authorization server metadata has no revocation_endpoint"
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
return outcome;
|
|
4413
|
+
}
|
|
4414
|
+
async function collectGrants(snapshot) {
|
|
4415
|
+
const grants = [];
|
|
4416
|
+
const failures = [];
|
|
4417
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
4418
|
+
let preregistered;
|
|
4419
|
+
try {
|
|
4420
|
+
preregistered = await parseClient(snapshot.preregisteredClientInformation);
|
|
4421
|
+
} catch (err) {
|
|
4422
|
+
failures.push({
|
|
4423
|
+
status: "failed",
|
|
4424
|
+
detail: `could not read the preconfigured client registration: ${err instanceof Error ? err.message : String(err)}`
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
const add2 = async (issuer, rawTokens, rawClient) => {
|
|
4428
|
+
const revocable = selectRevocableToken(await parseTokens(rawTokens));
|
|
4429
|
+
if (!revocable) return;
|
|
4430
|
+
const key = `${issuer ?? "\0legacy"}\0${revocable.token}`;
|
|
4431
|
+
if (seenKeys.has(key)) return;
|
|
4432
|
+
seenKeys.add(key);
|
|
4433
|
+
const bound = await parseClient(rawClient);
|
|
4434
|
+
grants.push({
|
|
4435
|
+
issuer,
|
|
4436
|
+
...revocable,
|
|
4437
|
+
clientInformation: bound ?? preregistered
|
|
4438
|
+
});
|
|
4439
|
+
};
|
|
4440
|
+
const addSafely = async (issuer, rawTokens, rawClient) => {
|
|
4441
|
+
try {
|
|
4442
|
+
await add2(issuer, rawTokens, rawClient);
|
|
4443
|
+
} catch (err) {
|
|
4444
|
+
const where = issuer === void 0 ? "the unkeyed slot" : `issuer ${issuer}`;
|
|
4445
|
+
failures.push({
|
|
4446
|
+
status: "failed",
|
|
4447
|
+
detail: `could not read the stored grant for ${where}: ${err instanceof Error ? err.message : String(err)}`
|
|
4448
|
+
});
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
for (const [issuer, slot] of Object.entries(snapshot.byIssuer)) {
|
|
4452
|
+
await addSafely(issuer, slot?.tokens, slot?.clientInformation);
|
|
4453
|
+
}
|
|
4454
|
+
await addSafely(
|
|
4455
|
+
void 0,
|
|
4456
|
+
snapshot.legacyTokens,
|
|
4457
|
+
snapshot.legacyClientInformation
|
|
4458
|
+
);
|
|
4459
|
+
return { grants, failures };
|
|
4460
|
+
}
|
|
4461
|
+
async function parseTokens(raw) {
|
|
4462
|
+
return raw === void 0 || raw === null ? void 0 : await OAuthTokensSchema2.parseAsync(raw);
|
|
4463
|
+
}
|
|
4464
|
+
async function parseClient(raw) {
|
|
4465
|
+
return raw === void 0 || raw === null ? void 0 : await OAuthClientInformationSchema2.parseAsync(raw);
|
|
4466
|
+
}
|
|
4467
|
+
function aggregateOutcomes(outcomes) {
|
|
4468
|
+
return outcomes.find((o) => o.status === "failed") ?? outcomes.find((o) => o.status === "revoked") ?? outcomes[0] ?? { status: "skipped", reason: "no_tokens" };
|
|
4469
|
+
}
|
|
4470
|
+
async function runPlan(plan, params) {
|
|
4471
|
+
if (plan.outcome) return plan.outcome;
|
|
4472
|
+
const endpoint = plan.endpoint;
|
|
4473
|
+
if (!endpoint) return { status: "skipped", reason: "no_endpoint" };
|
|
4474
|
+
const outcomes = [...plan.failures];
|
|
4475
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
|
|
4476
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
4477
|
+
for (const grant of plan.grants) {
|
|
4478
|
+
if (grant.issuer === void 0) {
|
|
4479
|
+
outcomes.push({
|
|
4480
|
+
status: "failed",
|
|
4481
|
+
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"
|
|
4482
|
+
});
|
|
4483
|
+
continue;
|
|
4484
|
+
}
|
|
4485
|
+
if (plan.metadataIssuer !== grant.issuer) {
|
|
4486
|
+
outcomes.push({
|
|
4487
|
+
status: "failed",
|
|
4488
|
+
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`
|
|
4489
|
+
});
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
const remainingMs = deadlineAt - Date.now();
|
|
4493
|
+
if (remainingMs <= 0) {
|
|
4494
|
+
outcomes.push({
|
|
4495
|
+
status: "failed",
|
|
4496
|
+
endpoint,
|
|
4497
|
+
detail: `the ${timeoutMs}ms revocation budget was exhausted before this grant was attempted${grant.issuer === void 0 ? "" : ` (issuer ${grant.issuer})`}`
|
|
4498
|
+
});
|
|
4499
|
+
continue;
|
|
4500
|
+
}
|
|
4501
|
+
outcomes.push(
|
|
4502
|
+
await revokeToken({
|
|
4503
|
+
endpoint,
|
|
4504
|
+
token: grant.token,
|
|
4505
|
+
tokenTypeHint: grant.tokenTypeHint,
|
|
4506
|
+
clientInformation: grant.clientInformation,
|
|
4507
|
+
supportedAuthMethods: plan.supportedAuthMethods,
|
|
4508
|
+
fetchFn: params.fetchFn,
|
|
4509
|
+
timeoutMs: remainingMs
|
|
4510
|
+
})
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
return aggregateOutcomes(outcomes);
|
|
4514
|
+
}
|
|
4515
|
+
|
|
3653
4516
|
// src/clear-stored-auth-for-relogin.ts
|
|
3654
4517
|
function normalizeServerUrl(serverUrl) {
|
|
3655
4518
|
try {
|
|
@@ -3658,16 +4521,56 @@ function normalizeServerUrl(serverUrl) {
|
|
|
3658
4521
|
return serverUrl;
|
|
3659
4522
|
}
|
|
3660
4523
|
}
|
|
3661
|
-
async function clearStoredAuthForRelogin(serverUrl) {
|
|
3662
|
-
if (!serverUrl?.trim()) return;
|
|
4524
|
+
async function clearStoredAuthForRelogin(serverUrl, options) {
|
|
4525
|
+
if (!serverUrl?.trim()) return void 0;
|
|
3663
4526
|
const raw = serverUrl.trim();
|
|
3664
4527
|
const normalized = normalizeServerUrl(raw);
|
|
3665
4528
|
const storage = new NodeOAuthStorage();
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
4529
|
+
const keys = normalized === raw ? [raw] : [normalized, raw];
|
|
4530
|
+
const plans = [];
|
|
4531
|
+
for (const key of keys) {
|
|
4532
|
+
plans.push(
|
|
4533
|
+
await clearAndPlanRevocation({
|
|
4534
|
+
serverUrl: key,
|
|
4535
|
+
storage,
|
|
4536
|
+
enabled: options?.revoke !== false
|
|
4537
|
+
})
|
|
4538
|
+
);
|
|
3669
4539
|
}
|
|
3670
4540
|
resetNodeOAuthStorageCache();
|
|
4541
|
+
return options?.revoke === false ? void 0 : sendPlans(plans, options?.budgetMs ?? DEFAULT_REVOCATION_TIMEOUT_MS);
|
|
4542
|
+
}
|
|
4543
|
+
async function sendPlans(plans, budgetMs) {
|
|
4544
|
+
const fetchFn = createProxyFetch() ?? fetch;
|
|
4545
|
+
const deadlineAt = Date.now() + budgetMs;
|
|
4546
|
+
let reported;
|
|
4547
|
+
let lastSkip;
|
|
4548
|
+
for (const plan of plans) {
|
|
4549
|
+
const remainingMs = deadlineAt - Date.now();
|
|
4550
|
+
const needsNetwork = plan.outcome === void 0;
|
|
4551
|
+
if (needsNetwork && remainingMs <= 0) {
|
|
4552
|
+
const exhausted = {
|
|
4553
|
+
status: "failed",
|
|
4554
|
+
detail: `the ${budgetMs}ms revocation budget was exhausted before "${plan.serverUrl}" was attempted`
|
|
4555
|
+
};
|
|
4556
|
+
if (reported?.status !== "failed") reported = exhausted;
|
|
4557
|
+
continue;
|
|
4558
|
+
}
|
|
4559
|
+
const outcome = await executeOAuthRevocation(plan, {
|
|
4560
|
+
fetchFn,
|
|
4561
|
+
timeoutMs: needsNetwork ? remainingMs : void 0
|
|
4562
|
+
});
|
|
4563
|
+
if (outcome.status === "skipped" && outcome.reason === "no_tokens") {
|
|
4564
|
+
lastSkip = outcome;
|
|
4565
|
+
continue;
|
|
4566
|
+
}
|
|
4567
|
+
if (outcome.status === "failed") {
|
|
4568
|
+
if (reported?.status !== "failed") reported = outcome;
|
|
4569
|
+
} else {
|
|
4570
|
+
reported ??= outcome;
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
return reported ?? lastSkip;
|
|
3671
4574
|
}
|
|
3672
4575
|
|
|
3673
4576
|
// ../../core/mcp/inspectorClient.ts
|
|
@@ -3830,6 +4733,20 @@ function mcpParamHeadersForTool(tool, args) {
|
|
|
3830
4733
|
return buildMcpParamHeaders(scan.declarations, args);
|
|
3831
4734
|
}
|
|
3832
4735
|
|
|
4736
|
+
// ../../core/mcp/subscriptionAck.ts
|
|
4737
|
+
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
4738
|
+
var NEVER_ACKNOWLEDGED_SDK_MESSAGE = "closed the subscription gracefully before acknowledging";
|
|
4739
|
+
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.";
|
|
4740
|
+
function isNeverAcknowledgedSubscriptionClose(error) {
|
|
4741
|
+
return SdkError.isInstance(error) && error.code === SdkErrorCode.ConnectionClosed && error.message.includes(NEVER_ACKNOWLEDGED_SDK_MESSAGE);
|
|
4742
|
+
}
|
|
4743
|
+
function subscriptionFailureMessage(error) {
|
|
4744
|
+
if (isNeverAcknowledgedSubscriptionClose(error)) {
|
|
4745
|
+
return NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE;
|
|
4746
|
+
}
|
|
4747
|
+
return error instanceof Error ? error.message : String(error);
|
|
4748
|
+
}
|
|
4749
|
+
|
|
3833
4750
|
// ../../core/mcp/messageTrackingTransport.ts
|
|
3834
4751
|
var MessageTrackingTransport = class {
|
|
3835
4752
|
baseTransport;
|
|
@@ -3921,6 +4838,29 @@ var MessageTrackingTransport = class {
|
|
|
3921
4838
|
get sessionId() {
|
|
3922
4839
|
return this.baseTransport.sessionId;
|
|
3923
4840
|
}
|
|
4841
|
+
/**
|
|
4842
|
+
* Forward the base transport's per-request-stream capability (#2140).
|
|
4843
|
+
*
|
|
4844
|
+
* The SDK's `Protocol.request` reads this off the transport it was handed —
|
|
4845
|
+
* which is always this wrapper — to decide how a cancelled request is
|
|
4846
|
+
* signalled: abort that request's own stream (the 2026-07-28 signal for
|
|
4847
|
+
* Streamable HTTP) or POST a `notifications/cancelled` (the stdio one).
|
|
4848
|
+
*
|
|
4849
|
+
* Not forwarding it made the wrapper *answer* the question, `undefined`, for
|
|
4850
|
+
* every client. So the Cancel button POSTed a notification even on a modern
|
|
4851
|
+
* Streamable HTTP connection, where a spec-compliant server acknowledges it
|
|
4852
|
+
* `202` and drops it — the tool kept running and only a disconnect really
|
|
4853
|
+
* cancelled anything. That hit the CLI and TUI as much as the web client:
|
|
4854
|
+
* their real `StreamableHTTPClientTransport` advertises the flag correctly
|
|
4855
|
+
* and this wrapper hid it.
|
|
4856
|
+
*
|
|
4857
|
+
* A base transport that says nothing stays `undefined` rather than becoming
|
|
4858
|
+
* `false`, since the SDK's check is `=== true` either way and inventing a
|
|
4859
|
+
* value here would misreport what the transport claimed.
|
|
4860
|
+
*/
|
|
4861
|
+
get hasPerRequestStream() {
|
|
4862
|
+
return this.baseTransport.hasPerRequestStream;
|
|
4863
|
+
}
|
|
3924
4864
|
// Implemented as a concrete method (rather than delegating the base
|
|
3925
4865
|
// transport's optional `setProtocolVersion`) so the SDK Client always
|
|
3926
4866
|
// invokes it after the initialize handshake — including for stdio, whose
|
|
@@ -4149,7 +5089,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
4149
5089
|
|
|
4150
5090
|
// ../../core/mcp/listSalvage.ts
|
|
4151
5091
|
import { z as z2 } from "zod/v4";
|
|
4152
|
-
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
5092
|
+
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
|
|
4153
5093
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
4154
5094
|
var LIST_MAX_PAGES = 64;
|
|
4155
5095
|
function listPaginationExceeded(method) {
|
|
@@ -4158,10 +5098,10 @@ function listPaginationExceeded(method) {
|
|
|
4158
5098
|
);
|
|
4159
5099
|
}
|
|
4160
5100
|
function isClientDecodeRejection(err) {
|
|
4161
|
-
return
|
|
5101
|
+
return SdkError2.isInstance(err) && (err.code === SdkErrorCode2.InvalidResult || err.code === SdkErrorCode2.UnsupportedResultType);
|
|
4162
5102
|
}
|
|
4163
5103
|
function isSalvageableRejection(err) {
|
|
4164
|
-
return
|
|
5104
|
+
return SdkError2.isInstance(err) && err.code === SdkErrorCode2.InvalidResult;
|
|
4165
5105
|
}
|
|
4166
5106
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
4167
5107
|
return resultSchema.extend({ [itemsKey]: z2.array(z2.unknown()) });
|
|
@@ -4471,6 +5411,52 @@ var TypedEventGeneric = class extends CustomEvent {
|
|
|
4471
5411
|
}
|
|
4472
5412
|
};
|
|
4473
5413
|
var TypedEventTarget = class extends EventTarget {
|
|
5414
|
+
/**
|
|
5415
|
+
* Per-event-type dispatch counter. Read through `getEventRevision` by
|
|
5416
|
+
* `useStoreSnapshot` (core/react) to tell "nothing has happened on this
|
|
5417
|
+
* event since I last looked" from "something has", without comparing the
|
|
5418
|
+
* snapshot's contents (#1955).
|
|
5419
|
+
*/
|
|
5420
|
+
eventRevisions = /* @__PURE__ */ new Map();
|
|
5421
|
+
/**
|
|
5422
|
+
* How many times an event of this type has been dispatched from this
|
|
5423
|
+
* target. Monotonic per type, and per type only: an `errorChange` does not
|
|
5424
|
+
* advance `toolsChange`, so a subscriber to one is not re-read because of
|
|
5425
|
+
* the other.
|
|
5426
|
+
*
|
|
5427
|
+
* The counter — not the list's contents — is what a `useSyncExternalStore`
|
|
5428
|
+
* snapshot is cached against. That distinction is load-bearing rather than
|
|
5429
|
+
* an optimization: these stores hand out a defensive copy, so comparing
|
|
5430
|
+
* contents is the only alternative, and a contents comparison cannot see an
|
|
5431
|
+
* in-place mutation of an entry the list already holds. `MessageLogState`
|
|
5432
|
+
* does exactly that when a response is folded into its request entry
|
|
5433
|
+
* (filling in `response`/`duration`), and it dispatches — so the counter
|
|
5434
|
+
* moves and the fold reaches the UI, where a shallow compare would report
|
|
5435
|
+
* "unchanged" and never re-render it.
|
|
5436
|
+
*/
|
|
5437
|
+
getEventRevision(type) {
|
|
5438
|
+
return this.eventRevisions.get(type) ?? 0;
|
|
5439
|
+
}
|
|
5440
|
+
/**
|
|
5441
|
+
* Overridden solely to advance the counter. It sits on `dispatchEvent`
|
|
5442
|
+
* rather than on `dispatchTypedEvent` because that is the one funnel every
|
|
5443
|
+
* dispatch passes through — the typed helper below delegates to it, and so
|
|
5444
|
+
* does a hand-rolled `dispatchEvent(new CustomEvent(...))`, which the tests
|
|
5445
|
+
* use extensively. A counter bumped only in the typed helper would be
|
|
5446
|
+
* silently stale for those, i.e. exactly where nobody would look for it.
|
|
5447
|
+
*
|
|
5448
|
+
* Bumped BEFORE dispatching: the state mutation always precedes the
|
|
5449
|
+
* dispatch, so by the time a listener runs the counter must already reflect
|
|
5450
|
+
* it, or a snapshot read from inside that listener would return the cached
|
|
5451
|
+
* pre-change value.
|
|
5452
|
+
*/
|
|
5453
|
+
dispatchEvent(event) {
|
|
5454
|
+
this.eventRevisions.set(
|
|
5455
|
+
event.type,
|
|
5456
|
+
(this.eventRevisions.get(event.type) ?? 0) + 1
|
|
5457
|
+
);
|
|
5458
|
+
return super.dispatchEvent(event);
|
|
5459
|
+
}
|
|
4474
5460
|
dispatchTypedEvent(type, ...args) {
|
|
4475
5461
|
const detail = args[0] ?? void 0;
|
|
4476
5462
|
this.dispatchEvent(new TypedEventGeneric(type, detail));
|
|
@@ -4859,6 +5845,115 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
|
|
|
4859
5845
|
};
|
|
4860
5846
|
}
|
|
4861
5847
|
|
|
5848
|
+
// ../../core/auth/oidcDiscoveryCompat.ts
|
|
5849
|
+
import {
|
|
5850
|
+
OAuthMetadataSchema,
|
|
5851
|
+
OpenIdProviderDiscoveryMetadataSchema
|
|
5852
|
+
} from "@modelcontextprotocol/core";
|
|
5853
|
+
var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
5854
|
+
var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
|
|
5855
|
+
var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
|
|
5856
|
+
function oidcDiscoveryCandidates(rfc8414Url) {
|
|
5857
|
+
let url;
|
|
5858
|
+
try {
|
|
5859
|
+
url = new URL(rfc8414Url);
|
|
5860
|
+
} catch {
|
|
5861
|
+
return [];
|
|
5862
|
+
}
|
|
5863
|
+
if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
|
|
5864
|
+
const path5 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
|
|
5865
|
+
if (path5 !== "" && !path5.startsWith("/")) return [];
|
|
5866
|
+
if (path5 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
|
|
5867
|
+
return [
|
|
5868
|
+
new URL(`${OIDC_WELL_KNOWN}${path5}`, url.origin).href,
|
|
5869
|
+
new URL(`${path5}${OIDC_WELL_KNOWN}`, url.origin).href
|
|
5870
|
+
];
|
|
5871
|
+
}
|
|
5872
|
+
function continuesDiscovery(status) {
|
|
5873
|
+
return status >= 400 && status < 500 || status === 502;
|
|
5874
|
+
}
|
|
5875
|
+
async function releaseBody(response) {
|
|
5876
|
+
await response.body?.cancel().catch(() => {
|
|
5877
|
+
});
|
|
5878
|
+
}
|
|
5879
|
+
function isRfc8414OnlyMetadata(value) {
|
|
5880
|
+
if (!OAuthMetadataSchema.safeParse(value).success) return false;
|
|
5881
|
+
return !OpenIdProviderDiscoveryMetadataSchema.safeParse(value).success;
|
|
5882
|
+
}
|
|
5883
|
+
function requestUrlOf2(input) {
|
|
5884
|
+
if (typeof input === "string") return input;
|
|
5885
|
+
if (input instanceof URL) return input.href;
|
|
5886
|
+
return input.url;
|
|
5887
|
+
}
|
|
5888
|
+
function discoveryHeaders(input, init) {
|
|
5889
|
+
if (init?.headers) return new Headers(init.headers);
|
|
5890
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
5891
|
+
return new Headers(input.headers);
|
|
5892
|
+
}
|
|
5893
|
+
return new Headers();
|
|
5894
|
+
}
|
|
5895
|
+
function requestMethodOf(input, init) {
|
|
5896
|
+
if (init?.method) return init.method.toUpperCase();
|
|
5897
|
+
if (typeof input !== "string" && !(input instanceof URL)) {
|
|
5898
|
+
return input.method.toUpperCase();
|
|
5899
|
+
}
|
|
5900
|
+
return "GET";
|
|
5901
|
+
}
|
|
5902
|
+
function withRfc8414OidcCompat(fetchFn) {
|
|
5903
|
+
return async (input, init) => {
|
|
5904
|
+
const response = await fetchFn(input, init);
|
|
5905
|
+
if (response.ok) return response;
|
|
5906
|
+
if (!continuesDiscovery(response.status)) return response;
|
|
5907
|
+
if (requestMethodOf(input, init) !== "GET") return response;
|
|
5908
|
+
const candidates = oidcDiscoveryCandidates(requestUrlOf2(input));
|
|
5909
|
+
if (candidates.length === 0) return response;
|
|
5910
|
+
const headers = discoveryHeaders(input, init);
|
|
5911
|
+
for (const candidate of candidates) {
|
|
5912
|
+
let probe;
|
|
5913
|
+
try {
|
|
5914
|
+
probe = await fetchFn(candidate, { headers });
|
|
5915
|
+
} catch {
|
|
5916
|
+
return response;
|
|
5917
|
+
}
|
|
5918
|
+
if (!probe.ok) {
|
|
5919
|
+
await releaseBody(probe);
|
|
5920
|
+
if (continuesDiscovery(probe.status)) continue;
|
|
5921
|
+
return response;
|
|
5922
|
+
}
|
|
5923
|
+
let parsed;
|
|
5924
|
+
let body;
|
|
5925
|
+
try {
|
|
5926
|
+
body = await probe.text();
|
|
5927
|
+
parsed = JSON.parse(body);
|
|
5928
|
+
} catch {
|
|
5929
|
+
return response;
|
|
5930
|
+
}
|
|
5931
|
+
if (!isRfc8414OnlyMetadata(parsed)) {
|
|
5932
|
+
return response;
|
|
5933
|
+
}
|
|
5934
|
+
const source = probe.url || candidate;
|
|
5935
|
+
await releaseBody(response);
|
|
5936
|
+
console.warn(
|
|
5937
|
+
`[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.`
|
|
5938
|
+
);
|
|
5939
|
+
return new Response(body, {
|
|
5940
|
+
status: 200,
|
|
5941
|
+
statusText: "OK",
|
|
5942
|
+
headers: {
|
|
5943
|
+
"content-type": "application/json",
|
|
5944
|
+
// The substituted response is what the fetch trackers above this
|
|
5945
|
+
// wrapper record, so it says where its body actually came from
|
|
5946
|
+
// rather than letting the Network tab imply the RFC 8414 path
|
|
5947
|
+
// answered. Named on the response, not logged only, so it survives
|
|
5948
|
+
// into the captured entry.
|
|
5949
|
+
[COMPAT_SOURCE_HEADER]: source
|
|
5950
|
+
}
|
|
5951
|
+
});
|
|
5952
|
+
}
|
|
5953
|
+
return response;
|
|
5954
|
+
};
|
|
5955
|
+
}
|
|
5956
|
+
|
|
4862
5957
|
// ../../core/logging/logger.ts
|
|
4863
5958
|
var noop = () => {
|
|
4864
5959
|
};
|
|
@@ -5277,7 +6372,7 @@ import {
|
|
|
5277
6372
|
exchangeAuthorization,
|
|
5278
6373
|
startAuthorization
|
|
5279
6374
|
} from "@modelcontextprotocol/client";
|
|
5280
|
-
import { OAuthMetadataSchema } from "@modelcontextprotocol/core";
|
|
6375
|
+
import { OAuthMetadataSchema as OAuthMetadataSchema2 } from "@modelcontextprotocol/core";
|
|
5281
6376
|
|
|
5282
6377
|
// ../../core/auth/ema/constants.ts
|
|
5283
6378
|
var GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
@@ -5326,7 +6421,7 @@ function idpOAuthStorageKey(issuer) {
|
|
|
5326
6421
|
}
|
|
5327
6422
|
|
|
5328
6423
|
// ../../core/auth/ema/tokenEndpoint.ts
|
|
5329
|
-
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
|
|
6424
|
+
import { selectClientAuthMethod as selectClientAuthMethod2 } from "@modelcontextprotocol/client";
|
|
5330
6425
|
async function parseOAuthTokenErrorResponse(response, step) {
|
|
5331
6426
|
let body;
|
|
5332
6427
|
try {
|
|
@@ -5347,7 +6442,7 @@ async function parseOAuthTokenErrorResponse(response, step) {
|
|
|
5347
6442
|
}
|
|
5348
6443
|
function applyClientAuth(metadata, clientInformation, headers, body) {
|
|
5349
6444
|
const supported = metadata?.token_endpoint_auth_methods_supported ?? [];
|
|
5350
|
-
const method =
|
|
6445
|
+
const method = selectClientAuthMethod2(clientInformation, supported);
|
|
5351
6446
|
if (method === "client_secret_basic" && clientInformation.client_secret) {
|
|
5352
6447
|
const credentials = btoa(
|
|
5353
6448
|
`${clientInformation.client_id}:${clientInformation.client_secret}`
|
|
@@ -5401,7 +6496,7 @@ async function discoverIdpMetadata(issuer, fetchFn) {
|
|
|
5401
6496
|
`Failed to discover OIDC metadata for IdP issuer ${issuer}`
|
|
5402
6497
|
);
|
|
5403
6498
|
}
|
|
5404
|
-
return
|
|
6499
|
+
return OAuthMetadataSchema2.parse(metadata);
|
|
5405
6500
|
}
|
|
5406
6501
|
async function startIdpOidcAuthorization(params) {
|
|
5407
6502
|
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
|
@@ -5657,7 +6752,7 @@ async function discoverResourceAsMetadata(resourceAsUrl, fetchFn) {
|
|
|
5657
6752
|
|
|
5658
6753
|
// ../../core/auth/ema/wire.ts
|
|
5659
6754
|
import { discoverAndRequestJwtAuthGrant } from "@modelcontextprotocol/client";
|
|
5660
|
-
import { OAuthTokensSchema as
|
|
6755
|
+
import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
|
|
5661
6756
|
async function exchangeIdJag(params) {
|
|
5662
6757
|
const issuer = normalizeIdpIssuer(params.idp.issuer);
|
|
5663
6758
|
const resource = params.resource.trim();
|
|
@@ -5736,7 +6831,7 @@ async function redeemIdJagForAccessToken(params) {
|
|
|
5736
6831
|
"EMA leg 3 (resource AS JWT bearer grant)"
|
|
5737
6832
|
);
|
|
5738
6833
|
}
|
|
5739
|
-
return
|
|
6834
|
+
return OAuthTokensSchema3.parse(await response.json());
|
|
5740
6835
|
}
|
|
5741
6836
|
|
|
5742
6837
|
// ../../core/auth/ema/emaFlow.ts
|
|
@@ -6465,14 +7560,41 @@ var OAuthManager = class {
|
|
|
6465
7560
|
return void 0;
|
|
6466
7561
|
}
|
|
6467
7562
|
}
|
|
6468
|
-
|
|
7563
|
+
/**
|
|
7564
|
+
* Drop this server's local OAuth state, and revoke the grant at the
|
|
7565
|
+
* authorization server (RFC 7009).
|
|
7566
|
+
*
|
|
7567
|
+
* The state is taken and deleted in one atomic step, and the requests are
|
|
7568
|
+
* sent afterwards from what was taken. Both halves matter: the requests need
|
|
7569
|
+
* state the clear destroys, and the clear must not wait on the network, or a
|
|
7570
|
+
* fresh authorization completing during a five-second revocation would be
|
|
7571
|
+
* deleted by a clear reasoning about the grant it replaced.
|
|
7572
|
+
*
|
|
7573
|
+
* Best-effort by construction: every failure is reported through the returned
|
|
7574
|
+
* outcome and the clear has already happened regardless, because forgetting
|
|
7575
|
+
* the tokens is what the caller actually asked for (#2144).
|
|
7576
|
+
*
|
|
7577
|
+
* `options.revoke === false` skips the request. That is not only an escape
|
|
7578
|
+
* hatch for an authorization server that mishandles it — disconnecting while
|
|
7579
|
+
* still holding live tokens is a case a user may want to reproduce
|
|
7580
|
+
* deliberately, to watch how a server under test copes with it.
|
|
7581
|
+
*/
|
|
7582
|
+
async clearOAuthTokens(options) {
|
|
6469
7583
|
if (!this.oauthConfig?.storage) {
|
|
6470
|
-
return;
|
|
7584
|
+
return { status: "skipped", reason: "no_tokens" };
|
|
6471
7585
|
}
|
|
6472
7586
|
const serverUrl = this.getServerUrl();
|
|
6473
|
-
await
|
|
7587
|
+
const plan = await clearAndPlanRevocation({
|
|
7588
|
+
serverUrl,
|
|
7589
|
+
storage: this.oauthConfig.storage,
|
|
7590
|
+
enabled: options?.revoke
|
|
7591
|
+
});
|
|
6474
7592
|
this.oauthFlowState = null;
|
|
6475
7593
|
this.pendingAuthorizationScope = void 0;
|
|
7594
|
+
return executeOAuthRevocation(plan, {
|
|
7595
|
+
fetchFn: this.params.effectiveAuthFetch,
|
|
7596
|
+
logger: this.params.logger
|
|
7597
|
+
});
|
|
6476
7598
|
}
|
|
6477
7599
|
async isOAuthAuthorized() {
|
|
6478
7600
|
const tokens = await this.getOAuthTokens();
|
|
@@ -6799,10 +7921,11 @@ var OAuthManager = class {
|
|
|
6799
7921
|
await this.params.onBeforeOAuthRedirect(parsedState.authId);
|
|
6800
7922
|
}
|
|
6801
7923
|
}
|
|
6802
|
-
this.requireNavigation()
|
|
7924
|
+
const navigation = this.requireNavigation();
|
|
6803
7925
|
const provider = await this.createOAuthProvider();
|
|
6804
7926
|
const clientInfo = await provider.clientInformation();
|
|
6805
7927
|
await this.recordAuthorizationCodeFlowState(authorizationUrl, clientInfo);
|
|
7928
|
+
navigation.navigateToAuthorization(authorizationUrl);
|
|
6806
7929
|
this.params.dispatchOAuthAuthorizationRequired({ url: authorizationUrl });
|
|
6807
7930
|
}
|
|
6808
7931
|
requireNavigation() {
|
|
@@ -6959,6 +8082,24 @@ var RemoteClientTransport = class {
|
|
|
6959
8082
|
sseResponseWaits = /* @__PURE__ */ new Map();
|
|
6960
8083
|
options;
|
|
6961
8084
|
config;
|
|
8085
|
+
/**
|
|
8086
|
+
* Whether the upstream connection gives each request its own response stream
|
|
8087
|
+
* (#2140). The SDK's `Protocol.request` reads this: on a 2026-era connection
|
|
8088
|
+
* a transport advertising it has its per-request stream **aborted** as the
|
|
8089
|
+
* spec's cancellation signal, and no `notifications/cancelled` is sent; a
|
|
8090
|
+
* transport that does not gets the stdio mechanism instead.
|
|
8091
|
+
*
|
|
8092
|
+
* The real upstream transport lives on the backend, so this transport has to
|
|
8093
|
+
* answer for it. Streamable HTTP opens one POST — and one SSE response
|
|
8094
|
+
* stream — per request, so it qualifies; stdio and SSE multiplex every
|
|
8095
|
+
* request over one shared channel and must keep sending the notification.
|
|
8096
|
+
*
|
|
8097
|
+
* Advertising it is only half the fix: `requestSend` applies the SDK's
|
|
8098
|
+
* `requestSignal` to the browser-to-backend fetch, and `/api/mcp/send`
|
|
8099
|
+
* forwards that disconnect to the upstream `transport.send`, which is what
|
|
8100
|
+
* actually closes the stream the server is watching.
|
|
8101
|
+
*/
|
|
8102
|
+
hasPerRequestStream;
|
|
6962
8103
|
/**
|
|
6963
8104
|
* Intentionally returns undefined. The MCP Client checks transport.sessionId to detect
|
|
6964
8105
|
* reconnects and skip initialize. Our _sessionId is the remote server's session ID, not
|
|
@@ -7003,6 +8144,7 @@ var RemoteClientTransport = class {
|
|
|
7003
8144
|
constructor(options, config) {
|
|
7004
8145
|
this.options = options;
|
|
7005
8146
|
this.config = config;
|
|
8147
|
+
this.hasPerRequestStream = config.type === "streamable-http";
|
|
7006
8148
|
}
|
|
7007
8149
|
setAuthRecovery(handlers) {
|
|
7008
8150
|
this.options.authRecovery = handlers;
|
|
@@ -7367,7 +8509,8 @@ var RemoteClientTransport = class {
|
|
|
7367
8509
|
const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
|
|
7368
8510
|
method: "POST",
|
|
7369
8511
|
headers: this.headers,
|
|
7370
|
-
body: JSON.stringify(body)
|
|
8512
|
+
body: JSON.stringify(body),
|
|
8513
|
+
...options?.requestSignal && { signal: options.requestSignal }
|
|
7371
8514
|
});
|
|
7372
8515
|
if (!res.ok) {
|
|
7373
8516
|
const text = await res.text();
|
|
@@ -7604,6 +8747,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7604
8747
|
// pending re-listen timer.
|
|
7605
8748
|
modernReconnectAttempts = 0;
|
|
7606
8749
|
modernReconnectTimer;
|
|
8750
|
+
// Set when the last `listen()` was rejected because the server answered it
|
|
8751
|
+
// with a bare graceful-closure result instead of acknowledging (#2097). That
|
|
8752
|
+
// condition is deterministic, so the reconnect machinery must not treat it as
|
|
8753
|
+
// a drop to retry; the flag is what carries the distinction from the rejection
|
|
8754
|
+
// site to the two failure handlers. Cleared by any user-initiated refresh (a
|
|
8755
|
+
// subscribe/unsubscribe is a fresh attempt, and the server may have changed).
|
|
8756
|
+
modernNeverAcknowledged = false;
|
|
7607
8757
|
// Task ids the user explicitly cancelled. A cancel makes the in-flight
|
|
7608
8758
|
// `callToolStream` reject with a generic -32603 error, which the stream's
|
|
7609
8759
|
// error path would otherwise report as a *failed* task — flashing "failed"
|
|
@@ -7625,9 +8775,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7625
8775
|
pendingRawWireRequests = /* @__PURE__ */ new Map();
|
|
7626
8776
|
rawWireRequestCounter = 0;
|
|
7627
8777
|
// Abort controller for the in-flight ordinary (non-task) tool call. Aborting
|
|
7628
|
-
// it
|
|
7629
|
-
//
|
|
7630
|
-
//
|
|
8778
|
+
// it hands the SDK the MCP cancellation flow for that request and rejects the
|
|
8779
|
+
// pending call, which `callTool` surfaces as a `ToolCallCancelledError`. Which
|
|
8780
|
+
// signal reaches the server is the transport's business, not ours: a
|
|
8781
|
+
// per-request-stream transport on a 2026-era connection has that request's
|
|
8782
|
+
// stream aborted, and everything else gets `notifications/cancelled` (#2140).
|
|
8783
|
+
// Undefined when no ordinary call is in flight.
|
|
7631
8784
|
// Task-augmented calls have a server-side task and are cancelled via
|
|
7632
8785
|
// `cancelRequestorTask` instead, so they don't use this (#1458).
|
|
7633
8786
|
activeToolCallAbortController;
|
|
@@ -7734,6 +8887,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7734
8887
|
this.fetchFn ?? fetch,
|
|
7735
8888
|
() => this.oauthManager?.getEndpointOverrides()
|
|
7736
8889
|
);
|
|
8890
|
+
this.fetchFn = withRfc8414OidcCompat(this.fetchFn);
|
|
7737
8891
|
this.effectiveAuthFetch = this.buildEffectiveAuthFetch();
|
|
7738
8892
|
this.sessionId = options.sessionId;
|
|
7739
8893
|
if (options.oauth || options.environment.oauth) {
|
|
@@ -7753,6 +8907,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
7753
8907
|
return Promise.resolve();
|
|
7754
8908
|
},
|
|
7755
8909
|
initialConfig: oauthConfig,
|
|
8910
|
+
logger: this.logger,
|
|
7756
8911
|
enterpriseManagedAuth: options.enterpriseManagedAuth,
|
|
7757
8912
|
installEnterpriseManagedAuth: options.installEnterpriseManagedAuth,
|
|
7758
8913
|
dispatchOAuthComplete: (detail) => this.dispatchTypedEvent("oauthComplete", detail),
|
|
@@ -8443,6 +9598,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
8443
9598
|
this.modernListenGeneration++;
|
|
8444
9599
|
this.clearModernReconnectTimer();
|
|
8445
9600
|
this.modernReconnectAttempts = 0;
|
|
9601
|
+
this.modernNeverAcknowledged = false;
|
|
8446
9602
|
this.modernSubscription = null;
|
|
8447
9603
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
8448
9604
|
this.dispatchSubscriptionsChange();
|
|
@@ -9229,10 +10385,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
9229
10385
|
}
|
|
9230
10386
|
/**
|
|
9231
10387
|
* Cancel the in-flight ordinary (non-task) tool call started by
|
|
9232
|
-
* {@link callTool}. Aborting its request
|
|
9233
|
-
*
|
|
9234
|
-
*
|
|
9235
|
-
*
|
|
10388
|
+
* {@link callTool}. Aborting its request runs the MCP cancellation flow and
|
|
10389
|
+
* rejects the pending call, which `callTool` surfaces as a
|
|
10390
|
+
* {@link ToolCallCancelledError}. The SDK chooses the wire signal from the
|
|
10391
|
+
* transport: on a 2026-era Streamable HTTP connection it aborts that
|
|
10392
|
+
* request's own SSE response stream — the spec's cancellation signal —
|
|
10393
|
+
* rather than sending `notifications/cancelled`, which is the stdio
|
|
10394
|
+
* mechanism and remains in use there (#2140).
|
|
9236
10395
|
*
|
|
9237
10396
|
* Task-augmented calls have a server-side task and are cancelled via
|
|
9238
10397
|
* {@link cancelRequestorTask} instead — this is a no-op for them (and whenever
|
|
@@ -11448,6 +12607,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11448
12607
|
if (!fromReconnect) {
|
|
11449
12608
|
this.clearModernReconnectTimer();
|
|
11450
12609
|
this.modernReconnectAttempts = 0;
|
|
12610
|
+
this.modernNeverAcknowledged = false;
|
|
11451
12611
|
}
|
|
11452
12612
|
const generation = ++this.modernListenGeneration;
|
|
11453
12613
|
const previous = this.modernSubscription;
|
|
@@ -11459,10 +12619,18 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11459
12619
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
11460
12620
|
return;
|
|
11461
12621
|
}
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
this.
|
|
11465
|
-
|
|
12622
|
+
let subscription;
|
|
12623
|
+
try {
|
|
12624
|
+
subscription = await this.client.listen(
|
|
12625
|
+
this.buildSubscriptionFilter(),
|
|
12626
|
+
this.getRequestOptions()
|
|
12627
|
+
);
|
|
12628
|
+
} catch (error) {
|
|
12629
|
+
if (generation === this.modernListenGeneration && isNeverAcknowledgedSubscriptionClose(error)) {
|
|
12630
|
+
this.modernNeverAcknowledged = true;
|
|
12631
|
+
}
|
|
12632
|
+
throw error;
|
|
12633
|
+
}
|
|
11466
12634
|
if (generation !== this.modernListenGeneration) {
|
|
11467
12635
|
await closeSubscriptionBestEffort(subscription);
|
|
11468
12636
|
return;
|
|
@@ -11544,8 +12712,34 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11544
12712
|
this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
|
|
11545
12713
|
return;
|
|
11546
12714
|
}
|
|
12715
|
+
if (this.modernNeverAcknowledged) {
|
|
12716
|
+
this.endModernStreamNeverAcknowledged();
|
|
12717
|
+
return;
|
|
12718
|
+
}
|
|
11547
12719
|
this.scheduleModernReconnect();
|
|
11548
12720
|
}
|
|
12721
|
+
/**
|
|
12722
|
+
* Settle the stream on the never-acknowledged close (#2097): a status of its
|
|
12723
|
+
* own so the UI can say what happened, no reconnect, and a log line carrying
|
|
12724
|
+
* the same sentence the UI shows.
|
|
12725
|
+
*
|
|
12726
|
+
* The state is *not* `"ended"` — that badge covers the two expected closes (a
|
|
12727
|
+
* server shutting an established stream down, and reconnection abandoned after
|
|
12728
|
+
* repeated failures), and reading this case as either of them is exactly the
|
|
12729
|
+
* silence #2063 reported.
|
|
12730
|
+
*/
|
|
12731
|
+
endModernStreamNeverAcknowledged() {
|
|
12732
|
+
this.clearModernReconnectTimer();
|
|
12733
|
+
this.logger.warn(
|
|
12734
|
+
{ message: NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE },
|
|
12735
|
+
"subscriptions/listen closed without an acknowledgement"
|
|
12736
|
+
);
|
|
12737
|
+
this.setModernStreamState({
|
|
12738
|
+
active: this.modernStreamActive(),
|
|
12739
|
+
status: "never-acknowledged",
|
|
12740
|
+
honoredUris: []
|
|
12741
|
+
});
|
|
12742
|
+
}
|
|
11549
12743
|
/**
|
|
11550
12744
|
* Schedule a reconnect re-listen after the current backoff delay (#1630).
|
|
11551
12745
|
* `modernReconnectAttempts` reflects the number of *consecutive failed*
|
|
@@ -11579,6 +12773,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11579
12773
|
* stream ended (re-subscribing resets the run and tries again).
|
|
11580
12774
|
*/
|
|
11581
12775
|
onModernReconnectFailed() {
|
|
12776
|
+
if (this.modernNeverAcknowledged && !isTerminalStatus(this.status) && this.wantsModernStream()) {
|
|
12777
|
+
this.endModernStreamNeverAcknowledged();
|
|
12778
|
+
return;
|
|
12779
|
+
}
|
|
11582
12780
|
this.modernReconnectAttempts += 1;
|
|
11583
12781
|
if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
|
|
11584
12782
|
this.setModernStreamState({
|
|
@@ -11635,7 +12833,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11635
12833
|
this.dispatchSubscriptionsChange();
|
|
11636
12834
|
} catch (error) {
|
|
11637
12835
|
throw new Error(
|
|
11638
|
-
`Failed to subscribe to resource: ${
|
|
12836
|
+
`Failed to subscribe to resource: ${subscriptionFailureMessage(error)}`,
|
|
11639
12837
|
{ cause: error }
|
|
11640
12838
|
);
|
|
11641
12839
|
}
|
|
@@ -11680,7 +12878,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11680
12878
|
}
|
|
11681
12879
|
} catch (error) {
|
|
11682
12880
|
throw new Error(
|
|
11683
|
-
`Failed to unsubscribe from resource: ${
|
|
12881
|
+
`Failed to unsubscribe from resource: ${subscriptionFailureMessage(error)}`,
|
|
11684
12882
|
{ cause: error }
|
|
11685
12883
|
);
|
|
11686
12884
|
}
|
|
@@ -11951,10 +13149,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
11951
13149
|
return this.oauthManager.getOAuthTokens();
|
|
11952
13150
|
}
|
|
11953
13151
|
/**
|
|
11954
|
-
*
|
|
13152
|
+
* Drop this server's stored OAuth state and revoke the grant at the
|
|
13153
|
+
* authorization server (RFC 7009, #2144).
|
|
13154
|
+
*
|
|
13155
|
+
* The request is planned from the stored state, the state is cleared, and
|
|
13156
|
+
* only then is the request sent — so the clear never waits on the network.
|
|
13157
|
+
*
|
|
13158
|
+
* The revocation is best-effort — an authorization server that advertises no
|
|
13159
|
+
* `revocation_endpoint` is left behaving exactly as before, and a network
|
|
13160
|
+
* error, a non-2xx or a timeout is reported in the returned outcome rather
|
|
13161
|
+
* than thrown — so the local clear always completes. Pass
|
|
13162
|
+
* `{ revoke: false }` to skip it.
|
|
11955
13163
|
*/
|
|
11956
|
-
async clearOAuthTokens() {
|
|
11957
|
-
await this.oauthManager?.clearOAuthTokens()
|
|
13164
|
+
async clearOAuthTokens(options) {
|
|
13165
|
+
return await this.oauthManager?.clearOAuthTokens(options) ?? {
|
|
13166
|
+
status: "skipped",
|
|
13167
|
+
reason: "no_tokens"
|
|
13168
|
+
};
|
|
11958
13169
|
}
|
|
11959
13170
|
/**
|
|
11960
13171
|
* Checks if client is currently OAuth authorized
|
|
@@ -14518,14 +15729,22 @@ var validLogLevels = Object.values(
|
|
|
14518
15729
|
LoggingLevelSchema.enum
|
|
14519
15730
|
);
|
|
14520
15731
|
var CLI_CLIENT_NAME = "inspector-cli";
|
|
14521
|
-
async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin) {
|
|
15732
|
+
async function callMethod(serverConfig, serverSettings, args, clientConfig, cliAuthOverrides, callbackUrlConfig, storedAuthOnly, relogin, revoke) {
|
|
14522
15733
|
if (relogin) {
|
|
14523
15734
|
if (!("url" in serverConfig && serverConfig.url)) {
|
|
14524
15735
|
throw new Error(
|
|
14525
15736
|
"--relogin requires an HTTP/SSE server URL (no OAuth store entry for stdio)"
|
|
14526
15737
|
);
|
|
14527
15738
|
}
|
|
14528
|
-
await clearStoredAuthForRelogin(serverConfig.url
|
|
15739
|
+
const revocation = await clearStoredAuthForRelogin(serverConfig.url, {
|
|
15740
|
+
revoke: revoke && serverSettings?.oauthRevokeOnClear !== false
|
|
15741
|
+
});
|
|
15742
|
+
if (revocation?.status === "failed") {
|
|
15743
|
+
process.stderr.write(
|
|
15744
|
+
`Warning: could not revoke the OAuth grant at the authorization server (${revocation.detail}); it may still be valid there.
|
|
15745
|
+
`
|
|
15746
|
+
);
|
|
15747
|
+
}
|
|
14529
15748
|
}
|
|
14530
15749
|
const clientIdentity = {
|
|
14531
15750
|
name: CLI_CLIENT_NAME,
|
|
@@ -14646,7 +15865,13 @@ function findStoredToken(servers, serverUrl) {
|
|
|
14646
15865
|
}
|
|
14647
15866
|
async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
|
|
14648
15867
|
const refresh = deps.refresh ?? refreshAuthorization;
|
|
14649
|
-
const
|
|
15868
|
+
const storedAuthFetch = withRfc8414OidcCompat(createProxyFetch() ?? fetch);
|
|
15869
|
+
const discover = deps.discover ?? ((authorizationServerUrl, options) => discoverAuthorizationServerMetadata4(authorizationServerUrl, {
|
|
15870
|
+
...options,
|
|
15871
|
+
// A caller-supplied fetch is left alone — it is theirs to compose. The
|
|
15872
|
+
// walker below passes no options, so in practice this is ours.
|
|
15873
|
+
fetchFn: options?.fetchFn ?? storedAuthFetch
|
|
15874
|
+
}));
|
|
14650
15875
|
const snapshot = await readOAuthSnapshot(statePath);
|
|
14651
15876
|
const servers = snapshot.servers;
|
|
14652
15877
|
const found = findStoredServerState(servers, serverUrl);
|
|
@@ -14684,7 +15909,8 @@ async function refreshStoredAuthToken(serverUrl, statePath, deps = {}) {
|
|
|
14684
15909
|
metadata,
|
|
14685
15910
|
clientInformation,
|
|
14686
15911
|
refreshToken,
|
|
14687
|
-
resource: new URL(serverUrl)
|
|
15912
|
+
resource: new URL(serverUrl),
|
|
15913
|
+
fetchFn: storedAuthFetch
|
|
14688
15914
|
});
|
|
14689
15915
|
} catch (err) {
|
|
14690
15916
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -14912,6 +16138,9 @@ async function parseArgs(argv) {
|
|
|
14912
16138
|
).option(
|
|
14913
16139
|
"--relogin",
|
|
14914
16140
|
"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)"
|
|
16141
|
+
).option(
|
|
16142
|
+
"--no-revoke",
|
|
16143
|
+
"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
16144
|
).option(
|
|
14916
16145
|
"--wait-for-auth <sec>",
|
|
14917
16146
|
"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 +16182,9 @@ async function parseArgs(argv) {
|
|
|
14953
16182
|
);
|
|
14954
16183
|
}
|
|
14955
16184
|
}
|
|
16185
|
+
if (options.revoke === false && !options.relogin) {
|
|
16186
|
+
throw new Error("--no-revoke requires --relogin (it has no other effect).");
|
|
16187
|
+
}
|
|
14956
16188
|
if (options.strict) {
|
|
14957
16189
|
if (options.method !== "tools/list") {
|
|
14958
16190
|
throw new Error("--strict requires --method tools/list.");
|
|
@@ -15138,7 +16370,10 @@ async function parseArgs(argv) {
|
|
|
15138
16370
|
clientMetadataUrl: options.clientMetadataUrl,
|
|
15139
16371
|
callbackUrl: options.callbackUrl,
|
|
15140
16372
|
storedAuthOnly: options.storedAuthOnly === true,
|
|
15141
|
-
relogin: options.relogin === true
|
|
16373
|
+
relogin: options.relogin === true,
|
|
16374
|
+
// Commander's `--no-revoke` defaults this to true; only an explicit
|
|
16375
|
+
// `--no-revoke` makes it false.
|
|
16376
|
+
revoke: options.revoke !== false
|
|
15142
16377
|
};
|
|
15143
16378
|
}
|
|
15144
16379
|
async function runCli(argv) {
|
|
@@ -15154,7 +16389,8 @@ async function runCli(argv) {
|
|
|
15154
16389
|
clientMetadataUrl,
|
|
15155
16390
|
callbackUrl,
|
|
15156
16391
|
storedAuthOnly,
|
|
15157
|
-
relogin
|
|
16392
|
+
relogin,
|
|
16393
|
+
revoke
|
|
15158
16394
|
} = parsed;
|
|
15159
16395
|
const clientConfig = await loadRunnerClientConfig({ clientConfigPath });
|
|
15160
16396
|
let callbackUrlConfig;
|
|
@@ -15178,7 +16414,8 @@ async function runCli(argv) {
|
|
|
15178
16414
|
},
|
|
15179
16415
|
callbackUrlConfig,
|
|
15180
16416
|
storedAuthOnly === true,
|
|
15181
|
-
relogin === true
|
|
16417
|
+
relogin === true,
|
|
16418
|
+
revoke !== false
|
|
15182
16419
|
);
|
|
15183
16420
|
}
|
|
15184
16421
|
|