@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.
@@ -130,6 +130,491 @@ var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
130
130
  var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
131
131
  var envSecretField = (envKey) => `env:${envKey}`;
132
132
 
133
+ // ../../core/json/nullableUnion.ts
134
+ var RENDERABLE_TYPES = [
135
+ "string",
136
+ "number",
137
+ "integer",
138
+ "boolean",
139
+ "array",
140
+ "object"
141
+ ];
142
+ function isRenderableType(type) {
143
+ return RENDERABLE_TYPES.includes(type);
144
+ }
145
+ function toBranch(value) {
146
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
147
+ return null;
148
+ }
149
+ return value;
150
+ }
151
+ function isStringEnum(value) {
152
+ return Array.isArray(value) && value.length > 0 && value.every((member) => typeof member === "string");
153
+ }
154
+ function nullExcludedBySiblings(schema) {
155
+ if (Array.isArray(schema.enum) && !schema.enum.includes(null)) {
156
+ return true;
157
+ }
158
+ return schema.const !== void 0 && schema.const !== null;
159
+ }
160
+ var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
161
+ "title",
162
+ "description",
163
+ "default",
164
+ "enumNames",
165
+ "examples",
166
+ "deprecated",
167
+ "readOnly",
168
+ "writeOnly",
169
+ "$comment",
170
+ "$schema",
171
+ "$id"
172
+ ]);
173
+ function wrapperCarriesConstraints(schema) {
174
+ return Object.keys(schema).some(
175
+ (key) => key !== "anyOf" && !ANNOTATION_KEYWORDS.has(key)
176
+ );
177
+ }
178
+ function typeNamesNull(type) {
179
+ return Array.isArray(type) ? type.includes("null") : type === "null";
180
+ }
181
+ function hasOpaqueApplicator(schema) {
182
+ 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
183
+ // *alongside* its siblings — `{ type: "string", $ref: "#/$defs/intOnly" }`
184
+ // can be unsatisfiable. The wrapper path already declines on it (it is not
185
+ // an annotation); this is the same rule for every other position.
186
+ schema.$ref !== void 0;
187
+ }
188
+ function hasUnevaluatedComposition(schema) {
189
+ return schema.anyOf !== void 0 || hasOpaqueApplicator(schema);
190
+ }
191
+ function stripNullEnumMembers(members, names) {
192
+ if (!Array.isArray(members)) {
193
+ return { kind: "unchanged" };
194
+ }
195
+ const kept = members.map((member, index) => ({ member, index })).filter((entry) => entry.member !== null);
196
+ if (kept.length === 0) {
197
+ return { kind: "unselectable" };
198
+ }
199
+ if (kept.length === members.length) {
200
+ return { kind: "unchanged" };
201
+ }
202
+ const wasParallel = Array.isArray(names) && names.length === members.length;
203
+ const aligned = wasParallel ? kept.map((entry) => names[entry.index]) : void 0;
204
+ return {
205
+ kind: "filtered",
206
+ members: kept.map((entry) => entry.member),
207
+ names: aligned
208
+ };
209
+ }
210
+ function collapsed(schema, branch, type) {
211
+ const merged = { ...schema, ...branch };
212
+ const strip = stripNullEnumMembers(
213
+ merged.enum,
214
+ merged.enumNames
215
+ );
216
+ if (strip.kind === "unselectable") {
217
+ return null;
218
+ }
219
+ return {
220
+ ...merged,
221
+ type,
222
+ anyOf: void 0,
223
+ // Read off the ORIGINAL schema, not the merge: a hoisted branch `enum` is
224
+ // not a sibling constraint, and treating it as one would mark the very
225
+ // shape #1928 is about as non-nullable. See `nullExcludedBySiblings`.
226
+ nullable: admitsNull(schema),
227
+ ...strip.kind === "filtered" ? { enum: strip.members, enumNames: strip.names } : {}
228
+ };
229
+ }
230
+ function typeAdmitsNull(type) {
231
+ return type === void 0 || typeNamesNull(type);
232
+ }
233
+ function admitsNull(schema) {
234
+ if (nullExcludedBySiblings(schema)) {
235
+ return false;
236
+ }
237
+ if (hasOpaqueApplicator(schema)) {
238
+ return false;
239
+ }
240
+ if (schema.const === null) {
241
+ if (schema.nullable !== true && !typeAdmitsNull(schema.type)) return false;
242
+ return schema.anyOf === void 0 || anyOfAdmitsNull(schema);
243
+ }
244
+ if (Array.isArray(schema.enum) && schema.enum.includes(null)) {
245
+ return (schema.nullable === true || typeAdmitsNull(schema.type)) && (schema.anyOf === void 0 || anyOfAdmitsNull(schema));
246
+ }
247
+ if (schema.nullable === true) {
248
+ return true;
249
+ }
250
+ if (schema.type !== void 0) {
251
+ if (schema.anyOf !== void 0) {
252
+ return false;
253
+ }
254
+ return typeNamesNull(schema.type);
255
+ }
256
+ return anyOfAdmitsNull(schema);
257
+ }
258
+ var ANNOTATION_ONLY_KEYWORDS = /* @__PURE__ */ new Set([
259
+ "title",
260
+ "description",
261
+ "examples",
262
+ "default",
263
+ "deprecated",
264
+ "readOnly",
265
+ "writeOnly",
266
+ "$comment",
267
+ // Core keywords that identify or declare rather than assert. `$ref` is
268
+ // deliberately absent: it applies whatever it points at, which is exactly an
269
+ // assertion this module cannot see.
270
+ "$defs",
271
+ "$id",
272
+ "$schema",
273
+ "$anchor",
274
+ "$dynamicAnchor",
275
+ "$vocabulary"
276
+ ]);
277
+ function constrainsNothing(schema) {
278
+ return Object.keys(schema).every(
279
+ (keyword) => ANNOTATION_ONLY_KEYWORDS.has(keyword)
280
+ );
281
+ }
282
+ function anyOfAdmitsNull(schema) {
283
+ const branches = schema.anyOf;
284
+ if (!Array.isArray(branches)) return false;
285
+ return branches.some((entry) => {
286
+ if (typeof entry === "boolean") return entry;
287
+ const branch = toBranch(entry);
288
+ if (branch === null) return false;
289
+ const branchSchema = branch;
290
+ if (hasUnevaluatedComposition(branchSchema)) return false;
291
+ if (constrainsNothing(branchSchema)) return true;
292
+ return admitsNull(branchSchema);
293
+ });
294
+ }
295
+ function normalizeNullableUnion(schema) {
296
+ if (schema.anyOf?.length === 2) {
297
+ const branches = schema.anyOf.map(toBranch);
298
+ const nullBranch = branches.find((entry) => entry?.type === "null");
299
+ const branch = branches.find(
300
+ (entry) => entry !== null && entry.type !== "null"
301
+ );
302
+ if (nullBranch && branch && !wrapperCarriesConstraints(schema) && !hasUnevaluatedComposition(branch)) {
303
+ const type = branch.type ?? (isStringEnum(branch.enum) ? "string" : void 0);
304
+ if (isRenderableType(type)) {
305
+ const result = collapsed(schema, branch, type);
306
+ if (result !== null) {
307
+ return result;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ 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`
313
+ // and the renderers ignore the other applicators — so a compound schema
314
+ // like `{ type: ["string", "null"], anyOf: [{ const: "a" }] }` would be
315
+ // widened into an unconstrained string field. Leave it for the JSON editor.
316
+ !hasUnevaluatedComposition(schema)) {
317
+ const type = schema.type.find((member) => member !== "null");
318
+ if (isRenderableType(type)) {
319
+ const result = collapsed(schema, void 0, type);
320
+ if (result !== null) {
321
+ return result;
322
+ }
323
+ }
324
+ }
325
+ return schema;
326
+ }
327
+
328
+ // ../../core/json/rootUnion.ts
329
+ function membersOf(value) {
330
+ return Array.isArray(value) ? value : [];
331
+ }
332
+ function requiredOf(schema) {
333
+ const { required } = schema;
334
+ return Array.isArray(required) ? required.filter((name) => typeof name === "string") : [];
335
+ }
336
+ function toBranch2(value) {
337
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
338
+ return null;
339
+ }
340
+ return value;
341
+ }
342
+ function admitsObject(schema) {
343
+ const { type } = schema;
344
+ if (type === void 0) return true;
345
+ return Array.isArray(type) ? type.includes("object") : type === "object";
346
+ }
347
+ function hasReadableProperties(schema) {
348
+ const { properties } = schema;
349
+ return typeof properties === "object" && properties !== null && !Array.isArray(properties);
350
+ }
351
+ function propertiesOf(schema) {
352
+ return hasReadableProperties(schema) ? schema.properties : {};
353
+ }
354
+ function isOfferable(branch, merged) {
355
+ if (branch.properties !== void 0 && !hasReadableProperties(branch)) {
356
+ return false;
357
+ }
358
+ if (!admitsObject(branch)) return false;
359
+ const properties = Object.values(propertiesOf(merged));
360
+ return properties.length > 0 && // Every value has to be something a renderer can read AND something a
361
+ // caller can satisfy. A `null` or an array is not a schema at all, and the
362
+ // web form dereferences one on the way to choosing a widget. JSON Schema's
363
+ // boolean form is legal, but only `true` is harmless — it constrains
364
+ // nothing and answers every keyword lookup with `undefined`, while `false`
365
+ // admits no value whatsoever, so a field declared with it can never be
366
+ // filled and a required one makes the whole branch unsatisfiable. Either
367
+ // way the branch is declined rather than offered as a callable shape.
368
+ properties.every(
369
+ (property) => property === true || typeof property === "object" && property !== null && !Array.isArray(property)
370
+ );
371
+ }
372
+ var ANNOTATION_KEYWORDS2 = /* @__PURE__ */ new Set([
373
+ "title",
374
+ "description",
375
+ "examples",
376
+ "deprecated",
377
+ "readOnly",
378
+ "writeOnly",
379
+ "$comment",
380
+ // An annotation too: a suggested initial value constrains nothing, so two
381
+ // declarations suggesting different ones still accept the same values.
382
+ "default"
383
+ ]);
384
+ function canonicalJson(value) {
385
+ if (Array.isArray(value)) {
386
+ return `[${value.map(canonicalJson).join(",")}]`;
387
+ }
388
+ if (typeof value === "object" && value !== null) {
389
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(
390
+ ([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`
391
+ );
392
+ return `{${entries.join(",")}}`;
393
+ }
394
+ return JSON.stringify(value) ?? "undefined";
395
+ }
396
+ function sameJsonValue(a, b) {
397
+ return sameValue(a, b);
398
+ }
399
+ function sameValue(a, b) {
400
+ return a === b || canonicalJson(a) === canonicalJson(b);
401
+ }
402
+ function conflicts(baseProperty, branchProperty) {
403
+ const a = toBranch2(baseProperty);
404
+ const b = toBranch2(branchProperty);
405
+ if (a === null || b === null) {
406
+ return !sameValue(baseProperty, branchProperty);
407
+ }
408
+ const left = a;
409
+ const right = b;
410
+ if (Object.keys(right).some(
411
+ (keyword) => !ANNOTATION_KEYWORDS2.has(keyword) && keyword in left && !sameValue(left[keyword], right[keyword])
412
+ )) {
413
+ return true;
414
+ }
415
+ return !constSatisfiesSiblings({ ...left, ...right });
416
+ }
417
+ function jsonTypeOf(value) {
418
+ if (value === null) return "null";
419
+ if (Array.isArray(value)) return "array";
420
+ if (typeof value === "number") {
421
+ return Number.isInteger(value) ? "integer" : "number";
422
+ }
423
+ return typeof value;
424
+ }
425
+ var CONST_CHECKABLE = /* @__PURE__ */ new Set(["const", "type", "enum"]);
426
+ function constSatisfiesSiblings(schema) {
427
+ if (!("const" in schema)) return true;
428
+ const value = schema.const;
429
+ const { type, enum: allowed } = schema;
430
+ if (Object.keys(schema).some(
431
+ (keyword) => !CONST_CHECKABLE.has(keyword) && !ANNOTATION_KEYWORDS2.has(keyword)
432
+ )) {
433
+ return false;
434
+ }
435
+ const actual = jsonTypeOf(value);
436
+ const admits = (name) => name === actual || name === "number" && actual === "integer";
437
+ if (typeof type === "string" && !admits(type)) return false;
438
+ if (Array.isArray(type) && !type.some(admits)) return false;
439
+ if (Array.isArray(allowed) && !allowed.some((member) => sameValue(member, value))) {
440
+ return false;
441
+ }
442
+ return true;
443
+ }
444
+ function mergeProperty(baseProperty, branchProperty) {
445
+ const a = toBranch2(baseProperty);
446
+ const b = toBranch2(branchProperty);
447
+ if (a === null || b === null) {
448
+ return branchProperty;
449
+ }
450
+ return { ...a, ...b };
451
+ }
452
+ var MERGEABLE_KEYWORDS = /* @__PURE__ */ new Set(["type", "properties", "required"]);
453
+ function isFlattenable(member) {
454
+ const branch = toBranch2(member);
455
+ if (branch === null || !admitsObject(branch)) return false;
456
+ return Object.keys(branch).every(
457
+ (keyword) => MERGEABLE_KEYWORDS.has(keyword) || ANNOTATION_KEYWORDS2.has(keyword)
458
+ );
459
+ }
460
+ function conflictsWithBase(base, branch) {
461
+ const baseProperties = propertiesOf(base);
462
+ const branchProperties = propertiesOf(branch);
463
+ return Object.entries(branchProperties).some(
464
+ ([name, branchProperty]) => (
465
+ // `hasOwn`, not `in`: `properties` is a JSON record, so `constructor` and
466
+ // `toString` are legal argument names that `in` would find on
467
+ // `Object.prototype` and report as collisions the root never declared.
468
+ Object.hasOwn(baseProperties, name) && conflicts(baseProperties[name], branchProperty)
469
+ )
470
+ );
471
+ }
472
+ function hasDiscriminator(members, named, rootRequired) {
473
+ const first = propertiesOf(members[0] ?? {});
474
+ const candidates = named !== void 0 ? [named] : Object.keys(first);
475
+ return candidates.some((name) => {
476
+ const constants = members.map((member) => {
477
+ if (!rootRequired.includes(name) && !requiredOf(member).includes(name)) {
478
+ return void 0;
479
+ }
480
+ const property = toBranch2(propertiesOf(member)[name]);
481
+ return property?.const;
482
+ });
483
+ if (constants.some((value) => value === void 0)) return false;
484
+ const seen = new Set(constants.map(canonicalJson));
485
+ return seen.size === constants.length;
486
+ });
487
+ }
488
+ function restrictsAdditional(schema) {
489
+ const additional = schema.additionalProperties;
490
+ return additional === false || typeof additional === "object" && additional !== null && Object.keys(additional).some(
491
+ (keyword) => !ANNOTATION_KEYWORDS2.has(keyword)
492
+ );
493
+ }
494
+ function addsForbiddenNames(base, member) {
495
+ if (!restrictsAdditional(base)) return false;
496
+ const baseNames = propertiesOf(base);
497
+ return Object.keys(propertiesOf(member)).some(
498
+ (name) => !Object.hasOwn(baseNames, name)
499
+ );
500
+ }
501
+ function mergeBranch(base, branch) {
502
+ const baseProperties = propertiesOf(base);
503
+ const branchProperties = propertiesOf(branch);
504
+ const properties = Object.fromEntries([
505
+ ...Object.entries(baseProperties),
506
+ ...Object.entries(branchProperties).map(([name, branchProperty]) => [
507
+ name,
508
+ Object.hasOwn(baseProperties, name) ? mergeProperty(baseProperties[name], branchProperty) : branchProperty
509
+ ])
510
+ ]);
511
+ const baseRequired = requiredOf(base);
512
+ const required = [
513
+ ...baseRequired,
514
+ ...requiredOf(branch).filter((name) => !baseRequired.includes(name))
515
+ ];
516
+ return {
517
+ ...base,
518
+ ...Object.keys(properties).length > 0 ? { properties } : {},
519
+ ...required.length > 0 ? { required } : {}
520
+ };
521
+ }
522
+ function withoutComposition(schema) {
523
+ const { allOf: _allOf, anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
524
+ return rest;
525
+ }
526
+ function branchLabel(branch, index, discriminatorProperty) {
527
+ if (typeof branch.title === "string" && branch.title.trim() !== "") {
528
+ return branch.title;
529
+ }
530
+ const properties = propertiesOf(branch);
531
+ const constOf2 = (name) => {
532
+ const property = toBranch2(properties[name]);
533
+ if (property === null || !("const" in property)) return void 0;
534
+ const value = property.const;
535
+ return value === null || typeof value !== "object" ? String(value) : void 0;
536
+ };
537
+ if (discriminatorProperty !== void 0) {
538
+ const value = constOf2(discriminatorProperty);
539
+ if (value !== void 0) return value;
540
+ }
541
+ const constants = Object.keys(properties).map((name) => constOf2(name)).filter((value) => value !== void 0);
542
+ if (constants.length === 1) return constants[0];
543
+ return `Option ${index + 1}`;
544
+ }
545
+ function resolveRootUnion(schema) {
546
+ const allOfMembers = membersOf(schema.allOf);
547
+ let merged = schema;
548
+ for (const member of allOfMembers) {
549
+ const branch = toBranch2(member);
550
+ if (branch === null || !isFlattenable(member) || conflictsWithBase(merged, branch) || addsForbiddenNames(merged, branch)) {
551
+ return { base: schema, branches: [] };
552
+ }
553
+ merged = mergeBranch(merged, branch);
554
+ }
555
+ const base = withoutComposition(merged);
556
+ if (schema.oneOf !== void 0 && schema.anyOf !== void 0) {
557
+ return { base, branches: [] };
558
+ }
559
+ const isExclusiveUnion = schema.oneOf !== void 0;
560
+ const members = membersOf(schema.oneOf ?? schema.anyOf);
561
+ const branches = members.map(toBranch2);
562
+ if (branches.length === 0 || branches.some(
563
+ (branch) => branch === null || !isOfferable(branch, mergeBranch(base, branch)) || // The same faithfulness test the `allOf` fold applies: a member
564
+ // carrying a constraint the merge does not copy — a nested `allOf`, a
565
+ // `not`, a `$ref` — would have it erased along with the union keyword,
566
+ // which can present an unsatisfiable branch as a callable one.
567
+ !isFlattenable(branch) || conflictsWithBase(base, branch)
568
+ )) {
569
+ return { base, branches: [] };
570
+ }
571
+ if (branches.some(
572
+ (branch) => addsForbiddenNames(base, branch)
573
+ )) {
574
+ return { base, branches: [] };
575
+ }
576
+ if (isExclusiveUnion && !hasDiscriminator(
577
+ branches,
578
+ schema.discriminator?.propertyName,
579
+ requiredOf(base)
580
+ )) {
581
+ return { base, branches: [] };
582
+ }
583
+ const discriminatorProperty = schema.discriminator?.propertyName;
584
+ return {
585
+ base,
586
+ branches: branches.filter((branch) => branch !== null).map((branch, index) => ({
587
+ // `base` is already composition-free and `mergeBranch` copies only
588
+ // `properties`/`required` off the branch, so the merge stays that way.
589
+ schema: mergeBranch(base, branch),
590
+ label: branchLabel(branch, index, discriminatorProperty),
591
+ declaredFields: Object.keys(propertiesOf(branch))
592
+ }))
593
+ };
594
+ }
595
+ function narrowBySuppliedNames(branches, candidates, suppliedNames) {
596
+ if (candidates.length === 1) return candidates[0];
597
+ if (candidates.length === 0 || suppliedNames.length === 0) return null;
598
+ const supplied = new Set(suppliedNames);
599
+ const satisfied = candidates.filter((index) => {
600
+ const branch = branches[index];
601
+ const required = requiredOf(branch.schema);
602
+ const own = required.filter((name) => branch.declaredFields.includes(name));
603
+ return own.length > 0 && own.every((name) => supplied.has(name));
604
+ });
605
+ if (satisfied.length === 1) return satisfied[0];
606
+ const exclusiveTo = /* @__PURE__ */ new Map();
607
+ for (const index of candidates) {
608
+ for (const name of branches[index].declaredFields) {
609
+ exclusiveTo.set(name, exclusiveTo.has(name) ? -1 : index);
610
+ }
611
+ }
612
+ const named = new Set(
613
+ suppliedNames.map((name) => exclusiveTo.get(name)).filter((index) => index !== void 0 && index >= 0)
614
+ );
615
+ return named.size === 1 ? [...named][0] : null;
616
+ }
617
+
133
618
  // ../../core/json/jsonUtils.ts
134
619
  function isSerializableJson(value) {
135
620
  if (value === null) return true;
@@ -169,16 +654,90 @@ function convertParameterValue(value, schema) {
169
654
  }
170
655
  return value;
171
656
  }
657
+ function coercionProperties(base, branches, params) {
658
+ if (branches.length === 0) {
659
+ return { ...base.properties };
660
+ }
661
+ const candidates = branches.map((branch, index) => ({ branch, index })).filter(
662
+ ({ branch }) => matchesConstants(branch.schema.properties ?? {}, params)
663
+ ).map(({ index }) => index);
664
+ const selected = narrowBySuppliedNames(
665
+ branches,
666
+ candidates,
667
+ Object.keys(params)
668
+ );
669
+ if (selected !== null) {
670
+ return { ...branches[selected].schema.properties };
671
+ }
672
+ const properties = Object.fromEntries(
673
+ Object.entries(base.properties ?? {})
674
+ );
675
+ const pool = candidates.length > 0 ? candidates.map((i) => branches[i]) : branches;
676
+ for (const name of new Set(pool.flatMap((b) => b.declaredFields))) {
677
+ 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));
678
+ const types = new Set(declarations.map((schema) => typeNameOf(schema)));
679
+ const pinnedOf = (schema) => typeof schema === "object" && schema !== null && "const" in schema ? schema.const : void 0;
680
+ const constsAgree = declarations.every(
681
+ (schema) => sameJsonValue(pinnedOf(schema), pinnedOf(declarations[0]))
682
+ );
683
+ if (types.size === 1 && constsAgree && declarations.length > 0) {
684
+ Object.defineProperty(properties, name, {
685
+ value: declarations[0],
686
+ writable: true,
687
+ enumerable: true,
688
+ configurable: true
689
+ });
690
+ } else {
691
+ delete properties[name];
692
+ }
693
+ }
694
+ return properties;
695
+ }
696
+ function typeNameOf(schema) {
697
+ if (typeof schema !== "object" || schema === null) return "";
698
+ const { type } = schema;
699
+ return Array.isArray(type) ? [...type].map(String).sort().join(",") : typeof type === "string" ? type : "";
700
+ }
701
+ function matchesConstants(properties, params) {
702
+ return Object.entries(properties).every(([name, schema]) => {
703
+ if (typeof schema !== "object" || schema === null) return true;
704
+ const constValue = schema.const;
705
+ if (constValue === void 0) return true;
706
+ if (!Object.hasOwn(params, name)) return true;
707
+ const supplied = params[name];
708
+ return supplied === void 0 || suppliedMatchesConst(supplied, constValue);
709
+ });
710
+ }
711
+ function suppliedMatchesConst(value, constValue) {
712
+ if (constValue === null || typeof constValue !== "object") {
713
+ return value === String(constValue);
714
+ }
715
+ try {
716
+ return sameJsonValue(JSON.parse(value), constValue);
717
+ } catch {
718
+ return false;
719
+ }
720
+ }
172
721
  function convertToolParameters(tool, params) {
722
+ return convertParametersForSchema(tool.inputSchema, params);
723
+ }
724
+ function convertParametersForSchema(inputSchema, params) {
173
725
  const result = {};
174
- const properties = tool.inputSchema?.properties || {};
726
+ const { base, branches } = resolveRootUnion(
727
+ inputSchema ?? {}
728
+ );
729
+ const properties = coercionProperties(base, branches, params);
175
730
  for (const [key, value] of Object.entries(params)) {
176
- const paramSchema = properties[key];
177
- if (paramSchema) {
178
- result[key] = convertParameterValue(value, paramSchema);
179
- } else {
180
- result[key] = value;
181
- }
731
+ const declared = properties[key];
732
+ const paramSchema = typeof declared === "object" && declared !== null ? normalizeNullableUnion(declared) : declared;
733
+ const pinned = paramSchema?.const;
734
+ const converted = pinned !== void 0 && suppliedMatchesConst(value, pinned) ? pinned : paramSchema ? convertParameterValue(value, paramSchema) : value;
735
+ Object.defineProperty(result, key, {
736
+ value: converted,
737
+ writable: true,
738
+ enumerable: true,
739
+ configurable: true
740
+ });
182
741
  }
183
742
  return result;
184
743
  }
@@ -415,6 +974,9 @@ function storedFieldsToInspectorSettings(stored) {
415
974
  if (stored.oauth?.requestRefreshToken === false) {
416
975
  settings.oauthRequestRefreshToken = false;
417
976
  }
977
+ if (stored.oauth?.revokeOnClear === false) {
978
+ settings.oauthRevokeOnClear = false;
979
+ }
418
980
  if (stored.cwd) settings.cwd = stored.cwd;
419
981
  return settings;
420
982
  }
@@ -3360,11 +3922,11 @@ function formatRunnerOAuthRedirectUrl(config) {
3360
3922
 
3361
3923
  // src/App.tsx
3362
3924
  import {
3363
- useState as useState23,
3925
+ useState as useState16,
3364
3926
  useMemo as useMemo4,
3365
- useEffect as useEffect20,
3366
- useCallback as useCallback11,
3367
- useRef as useRef13
3927
+ useEffect as useEffect13,
3928
+ useCallback as useCallback12,
3929
+ useRef as useRef14
3368
3930
  } from "react";
3369
3931
  import { Box as Box27, Text as Text24, useInput as useInput21, useApp } from "ink";
3370
3932
 
@@ -3528,6 +4090,20 @@ function mcpParamHeadersForTool(tool, args) {
3528
4090
  return buildMcpParamHeaders(scan.declarations, args);
3529
4091
  }
3530
4092
 
4093
+ // ../../core/mcp/subscriptionAck.ts
4094
+ import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
4095
+ var NEVER_ACKNOWLEDGED_SDK_MESSAGE = "closed the subscription gracefully before acknowledging";
4096
+ 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.";
4097
+ function isNeverAcknowledgedSubscriptionClose(error) {
4098
+ return SdkError.isInstance(error) && error.code === SdkErrorCode.ConnectionClosed && error.message.includes(NEVER_ACKNOWLEDGED_SDK_MESSAGE);
4099
+ }
4100
+ function subscriptionFailureMessage(error) {
4101
+ if (isNeverAcknowledgedSubscriptionClose(error)) {
4102
+ return NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE;
4103
+ }
4104
+ return error instanceof Error ? error.message : String(error);
4105
+ }
4106
+
3531
4107
  // ../../core/mcp/messageTrackingTransport.ts
3532
4108
  var MessageTrackingTransport = class {
3533
4109
  baseTransport;
@@ -3619,6 +4195,29 @@ var MessageTrackingTransport = class {
3619
4195
  get sessionId() {
3620
4196
  return this.baseTransport.sessionId;
3621
4197
  }
4198
+ /**
4199
+ * Forward the base transport's per-request-stream capability (#2140).
4200
+ *
4201
+ * The SDK's `Protocol.request` reads this off the transport it was handed —
4202
+ * which is always this wrapper — to decide how a cancelled request is
4203
+ * signalled: abort that request's own stream (the 2026-07-28 signal for
4204
+ * Streamable HTTP) or POST a `notifications/cancelled` (the stdio one).
4205
+ *
4206
+ * Not forwarding it made the wrapper *answer* the question, `undefined`, for
4207
+ * every client. So the Cancel button POSTed a notification even on a modern
4208
+ * Streamable HTTP connection, where a spec-compliant server acknowledges it
4209
+ * `202` and drops it — the tool kept running and only a disconnect really
4210
+ * cancelled anything. That hit the CLI and TUI as much as the web client:
4211
+ * their real `StreamableHTTPClientTransport` advertises the flag correctly
4212
+ * and this wrapper hid it.
4213
+ *
4214
+ * A base transport that says nothing stays `undefined` rather than becoming
4215
+ * `false`, since the SDK's check is `=== true` either way and inventing a
4216
+ * value here would misreport what the transport claimed.
4217
+ */
4218
+ get hasPerRequestStream() {
4219
+ return this.baseTransport.hasPerRequestStream;
4220
+ }
3622
4221
  // Implemented as a concrete method (rather than delegating the base
3623
4222
  // transport's optional `setProtocolVersion`) so the SDK Client always
3624
4223
  // invokes it after the initialize handshake — including for stdio, whose
@@ -3847,7 +4446,7 @@ function validateToolOutput(provider, tool, result) {
3847
4446
 
3848
4447
  // ../../core/mcp/listSalvage.ts
3849
4448
  import { z as z3 } from "zod/v4";
3850
- import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
4449
+ import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
3851
4450
  import { ToolSchema } from "@modelcontextprotocol/core";
3852
4451
  var LIST_MAX_PAGES = 64;
3853
4452
  function listPaginationExceeded(method) {
@@ -3856,10 +4455,10 @@ function listPaginationExceeded(method) {
3856
4455
  );
3857
4456
  }
3858
4457
  function isClientDecodeRejection(err) {
3859
- return SdkError.isInstance(err) && (err.code === SdkErrorCode.InvalidResult || err.code === SdkErrorCode.UnsupportedResultType);
4458
+ return SdkError2.isInstance(err) && (err.code === SdkErrorCode2.InvalidResult || err.code === SdkErrorCode2.UnsupportedResultType);
3860
4459
  }
3861
4460
  function isSalvageableRejection(err) {
3862
- return SdkError.isInstance(err) && err.code === SdkErrorCode.InvalidResult;
4461
+ return SdkError2.isInstance(err) && err.code === SdkErrorCode2.InvalidResult;
3863
4462
  }
3864
4463
  function lenientListPageSchema(resultSchema, itemsKey) {
3865
4464
  return resultSchema.extend({ [itemsKey]: z3.array(z3.unknown()) });
@@ -4214,6 +4813,52 @@ var TypedEventGeneric = class extends CustomEvent {
4214
4813
  }
4215
4814
  };
4216
4815
  var TypedEventTarget = class extends EventTarget {
4816
+ /**
4817
+ * Per-event-type dispatch counter. Read through `getEventRevision` by
4818
+ * `useStoreSnapshot` (core/react) to tell "nothing has happened on this
4819
+ * event since I last looked" from "something has", without comparing the
4820
+ * snapshot's contents (#1955).
4821
+ */
4822
+ eventRevisions = /* @__PURE__ */ new Map();
4823
+ /**
4824
+ * How many times an event of this type has been dispatched from this
4825
+ * target. Monotonic per type, and per type only: an `errorChange` does not
4826
+ * advance `toolsChange`, so a subscriber to one is not re-read because of
4827
+ * the other.
4828
+ *
4829
+ * The counter — not the list's contents — is what a `useSyncExternalStore`
4830
+ * snapshot is cached against. That distinction is load-bearing rather than
4831
+ * an optimization: these stores hand out a defensive copy, so comparing
4832
+ * contents is the only alternative, and a contents comparison cannot see an
4833
+ * in-place mutation of an entry the list already holds. `MessageLogState`
4834
+ * does exactly that when a response is folded into its request entry
4835
+ * (filling in `response`/`duration`), and it dispatches — so the counter
4836
+ * moves and the fold reaches the UI, where a shallow compare would report
4837
+ * "unchanged" and never re-render it.
4838
+ */
4839
+ getEventRevision(type) {
4840
+ return this.eventRevisions.get(type) ?? 0;
4841
+ }
4842
+ /**
4843
+ * Overridden solely to advance the counter. It sits on `dispatchEvent`
4844
+ * rather than on `dispatchTypedEvent` because that is the one funnel every
4845
+ * dispatch passes through — the typed helper below delegates to it, and so
4846
+ * does a hand-rolled `dispatchEvent(new CustomEvent(...))`, which the tests
4847
+ * use extensively. A counter bumped only in the typed helper would be
4848
+ * silently stale for those, i.e. exactly where nobody would look for it.
4849
+ *
4850
+ * Bumped BEFORE dispatching: the state mutation always precedes the
4851
+ * dispatch, so by the time a listener runs the counter must already reflect
4852
+ * it, or a snapshot read from inside that listener would return the cached
4853
+ * pre-change value.
4854
+ */
4855
+ dispatchEvent(event) {
4856
+ this.eventRevisions.set(
4857
+ event.type,
4858
+ (this.eventRevisions.get(event.type) ?? 0) + 1
4859
+ );
4860
+ return super.dispatchEvent(event);
4861
+ }
4217
4862
  dispatchTypedEvent(type, ...args) {
4218
4863
  const detail = args[0] ?? void 0;
4219
4864
  this.dispatchEvent(new TypedEventGeneric(type, detail));
@@ -4602,6 +5247,115 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
4602
5247
  };
4603
5248
  }
4604
5249
 
5250
+ // ../../core/auth/oidcDiscoveryCompat.ts
5251
+ import {
5252
+ OAuthMetadataSchema,
5253
+ OpenIdProviderDiscoveryMetadataSchema
5254
+ } from "@modelcontextprotocol/core";
5255
+ var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
5256
+ var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
5257
+ var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
5258
+ function oidcDiscoveryCandidates(rfc8414Url) {
5259
+ let url;
5260
+ try {
5261
+ url = new URL(rfc8414Url);
5262
+ } catch {
5263
+ return [];
5264
+ }
5265
+ if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
5266
+ const path6 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
5267
+ if (path6 !== "" && !path6.startsWith("/")) return [];
5268
+ if (path6 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
5269
+ return [
5270
+ new URL(`${OIDC_WELL_KNOWN}${path6}`, url.origin).href,
5271
+ new URL(`${path6}${OIDC_WELL_KNOWN}`, url.origin).href
5272
+ ];
5273
+ }
5274
+ function continuesDiscovery(status) {
5275
+ return status >= 400 && status < 500 || status === 502;
5276
+ }
5277
+ async function releaseBody(response) {
5278
+ await response.body?.cancel().catch(() => {
5279
+ });
5280
+ }
5281
+ function isRfc8414OnlyMetadata(value) {
5282
+ if (!OAuthMetadataSchema.safeParse(value).success) return false;
5283
+ return !OpenIdProviderDiscoveryMetadataSchema.safeParse(value).success;
5284
+ }
5285
+ function requestUrlOf2(input) {
5286
+ if (typeof input === "string") return input;
5287
+ if (input instanceof URL) return input.href;
5288
+ return input.url;
5289
+ }
5290
+ function discoveryHeaders(input, init) {
5291
+ if (init?.headers) return new Headers(init.headers);
5292
+ if (typeof input !== "string" && !(input instanceof URL)) {
5293
+ return new Headers(input.headers);
5294
+ }
5295
+ return new Headers();
5296
+ }
5297
+ function requestMethodOf(input, init) {
5298
+ if (init?.method) return init.method.toUpperCase();
5299
+ if (typeof input !== "string" && !(input instanceof URL)) {
5300
+ return input.method.toUpperCase();
5301
+ }
5302
+ return "GET";
5303
+ }
5304
+ function withRfc8414OidcCompat(fetchFn) {
5305
+ return async (input, init) => {
5306
+ const response = await fetchFn(input, init);
5307
+ if (response.ok) return response;
5308
+ if (!continuesDiscovery(response.status)) return response;
5309
+ if (requestMethodOf(input, init) !== "GET") return response;
5310
+ const candidates = oidcDiscoveryCandidates(requestUrlOf2(input));
5311
+ if (candidates.length === 0) return response;
5312
+ const headers = discoveryHeaders(input, init);
5313
+ for (const candidate of candidates) {
5314
+ let probe;
5315
+ try {
5316
+ probe = await fetchFn(candidate, { headers });
5317
+ } catch {
5318
+ return response;
5319
+ }
5320
+ if (!probe.ok) {
5321
+ await releaseBody(probe);
5322
+ if (continuesDiscovery(probe.status)) continue;
5323
+ return response;
5324
+ }
5325
+ let parsed;
5326
+ let body;
5327
+ try {
5328
+ body = await probe.text();
5329
+ parsed = JSON.parse(body);
5330
+ } catch {
5331
+ return response;
5332
+ }
5333
+ if (!isRfc8414OnlyMetadata(parsed)) {
5334
+ return response;
5335
+ }
5336
+ const source = probe.url || candidate;
5337
+ await releaseBody(response);
5338
+ console.warn(
5339
+ `[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.`
5340
+ );
5341
+ return new Response(body, {
5342
+ status: 200,
5343
+ statusText: "OK",
5344
+ headers: {
5345
+ "content-type": "application/json",
5346
+ // The substituted response is what the fetch trackers above this
5347
+ // wrapper record, so it says where its body actually came from
5348
+ // rather than letting the Network tab imply the RFC 8414 path
5349
+ // answered. Named on the response, not logged only, so it survives
5350
+ // into the captured entry.
5351
+ [COMPAT_SOURCE_HEADER]: source
5352
+ }
5353
+ });
5354
+ }
5355
+ return response;
5356
+ };
5357
+ }
5358
+
4605
5359
  // ../../core/logging/logger.ts
4606
5360
  var noop = () => {
4607
5361
  };
@@ -4953,49 +5707,331 @@ var BaseOAuthClientProvider = class {
4953
5707
  await this.storage.clearDiscoveryState(this.serverUrl);
4954
5708
  return;
4955
5709
  }
4956
- }
4957
- };
4958
-
4959
- // ../../core/auth/types.ts
4960
- function authProtocolFromEnterpriseManaged(enterpriseManaged) {
4961
- return enterpriseManaged ? "ema" : "standard";
4962
- }
4963
- var EMPTY_OAUTH_FLOW_STATE = {
4964
- completedAt: null,
4965
- isInitiatingAuth: false,
4966
- oauthTokens: null,
4967
- oauthStep: "authorization_code",
4968
- oauthMetadata: null,
4969
- resourceMetadata: null,
4970
- resourceMetadataError: null,
4971
- resource: null,
4972
- authServerUrl: null,
4973
- oauthClientInfo: null,
4974
- authorizationUrl: null,
4975
- authorizationCode: "",
4976
- latestError: null,
4977
- statusMessage: null,
4978
- validationError: null
4979
- };
4980
-
4981
- // ../../core/auth/mcpAuth.ts
4982
- import { auth as sdkAuth } from "@modelcontextprotocol/client";
4983
- async function mcpAuth(provider, options) {
4984
- if (options.forceReauthorization && options.authorizationCode !== void 0) {
4985
- throw new Error(
4986
- "forceReauthorization cannot be combined with authorizationCode"
5710
+ }
5711
+ };
5712
+
5713
+ // ../../core/auth/types.ts
5714
+ function authProtocolFromEnterpriseManaged(enterpriseManaged) {
5715
+ return enterpriseManaged ? "ema" : "standard";
5716
+ }
5717
+ var EMPTY_OAUTH_FLOW_STATE = {
5718
+ completedAt: null,
5719
+ isInitiatingAuth: false,
5720
+ oauthTokens: null,
5721
+ oauthStep: "authorization_code",
5722
+ oauthMetadata: null,
5723
+ resourceMetadata: null,
5724
+ resourceMetadataError: null,
5725
+ resource: null,
5726
+ authServerUrl: null,
5727
+ oauthClientInfo: null,
5728
+ authorizationUrl: null,
5729
+ authorizationCode: "",
5730
+ latestError: null,
5731
+ statusMessage: null,
5732
+ validationError: null
5733
+ };
5734
+
5735
+ // ../../core/auth/mcpAuth.ts
5736
+ import { auth as sdkAuth } from "@modelcontextprotocol/client";
5737
+ async function mcpAuth(provider, options) {
5738
+ if (options.forceReauthorization && options.authorizationCode !== void 0) {
5739
+ throw new Error(
5740
+ "forceReauthorization cannot be combined with authorizationCode"
5741
+ );
5742
+ }
5743
+ return sdkAuth(provider, {
5744
+ serverUrl: options.serverUrl,
5745
+ authorizationCode: options.authorizationCode,
5746
+ iss: options.iss,
5747
+ scope: options.scope,
5748
+ resourceMetadataUrl: options.resourceMetadataUrl,
5749
+ fetchFn: options.fetchFn,
5750
+ skipIssuerMetadataValidation: options.skipIssuerMetadataValidation,
5751
+ forceReauthorization: options.forceReauthorization
5752
+ });
5753
+ }
5754
+
5755
+ // ../../core/auth/revocation.ts
5756
+ import { selectClientAuthMethod } from "@modelcontextprotocol/client";
5757
+ import {
5758
+ OAuthClientInformationSchema,
5759
+ OAuthTokensSchema
5760
+ } from "@modelcontextprotocol/core";
5761
+ var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
5762
+ function selectRevocableToken(tokens) {
5763
+ if (!tokens) return null;
5764
+ if (tokens.refresh_token) {
5765
+ return { token: tokens.refresh_token, tokenTypeHint: "refresh_token" };
5766
+ }
5767
+ if (tokens.access_token) {
5768
+ return { token: tokens.access_token, tokenTypeHint: "access_token" };
5769
+ }
5770
+ return null;
5771
+ }
5772
+ function revocationAuthMethods(metadata) {
5773
+ return metadata.revocation_endpoint_auth_methods_supported ?? [
5774
+ "client_secret_basic"
5775
+ ];
5776
+ }
5777
+ function buildRevocationRequest(params) {
5778
+ const body = new URLSearchParams({
5779
+ token: params.token,
5780
+ token_type_hint: params.tokenTypeHint
5781
+ });
5782
+ const headers = {
5783
+ "Content-Type": "application/x-www-form-urlencoded",
5784
+ Accept: "application/json"
5785
+ };
5786
+ const client = params.clientInformation;
5787
+ if (client) {
5788
+ const method = selectClientAuthMethod(client, params.supportedAuthMethods);
5789
+ if (method === "client_secret_basic" && client.client_secret) {
5790
+ const credentials = `${encodeURIComponent(client.client_id)}:${encodeURIComponent(client.client_secret)}`;
5791
+ headers.Authorization = `Basic ${base64Encode(credentials)}`;
5792
+ } else if (method === "client_secret_post" && client.client_secret) {
5793
+ body.set("client_id", client.client_id);
5794
+ body.set("client_secret", client.client_secret);
5795
+ } else {
5796
+ body.set("client_id", client.client_id);
5797
+ }
5798
+ }
5799
+ return {
5800
+ url: params.endpoint,
5801
+ init: { method: "POST", headers, body: body.toString() }
5802
+ };
5803
+ }
5804
+ function base64Encode(value) {
5805
+ if (typeof btoa === "function") return btoa(value);
5806
+ return Buffer.from(value, "utf8").toString("base64");
5807
+ }
5808
+ async function revokeToken(params) {
5809
+ const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
5810
+ try {
5811
+ const { url, init } = buildRevocationRequest(params);
5812
+ const response = await withDeadline(
5813
+ params.fetchFn(url, { ...init, signal: AbortSignal.timeout(timeoutMs) }),
5814
+ timeoutMs
5815
+ );
5816
+ if (!response.ok) {
5817
+ return {
5818
+ status: "failed",
5819
+ endpoint: url,
5820
+ detail: `revocation endpoint responded ${response.status} ${response.statusText}`.trim()
5821
+ };
5822
+ }
5823
+ return {
5824
+ status: "revoked",
5825
+ tokenTypeHint: params.tokenTypeHint,
5826
+ endpoint: url
5827
+ };
5828
+ } catch (err) {
5829
+ return {
5830
+ // `params.endpoint`, not the built `url`: construction is inside the try
5831
+ // now, so `url` may not exist on this path.
5832
+ status: "failed",
5833
+ endpoint: params.endpoint,
5834
+ detail: err instanceof Error ? err.message : String(err)
5835
+ };
5836
+ }
5837
+ }
5838
+ async function withDeadline(promise, timeoutMs) {
5839
+ let timer;
5840
+ const deadline = new Promise((_resolve, reject) => {
5841
+ timer = setTimeout(
5842
+ () => reject(new Error(`revocation request timed out after ${timeoutMs}ms`)),
5843
+ timeoutMs
5844
+ );
5845
+ });
5846
+ try {
5847
+ return await Promise.race([promise, deadline]);
5848
+ } finally {
5849
+ if (timer !== void 0) clearTimeout(timer);
5850
+ }
5851
+ }
5852
+ function emptyPlan(serverUrl, outcome, failures = []) {
5853
+ return {
5854
+ serverUrl,
5855
+ grants: [],
5856
+ failures,
5857
+ supportedAuthMethods: [],
5858
+ outcome: failures.length > 0 ? aggregateOutcomes([...failures, outcome]) : outcome
5859
+ };
5860
+ }
5861
+ async function clearAndPlanRevocation(params) {
5862
+ const { serverUrl, storage } = params;
5863
+ let snapshot;
5864
+ try {
5865
+ snapshot = await storage.takeRevocationSnapshot(serverUrl);
5866
+ } catch (err) {
5867
+ throw err instanceof Error ? err : new Error(String(err));
5868
+ }
5869
+ if (params.enabled === false) {
5870
+ return emptyPlan(serverUrl, { status: "skipped", reason: "disabled" });
5871
+ }
5872
+ try {
5873
+ const { grants, failures } = await collectGrants(snapshot);
5874
+ if (grants.length === 0) {
5875
+ return emptyPlan(
5876
+ serverUrl,
5877
+ { status: "skipped", reason: "no_tokens" },
5878
+ failures
5879
+ );
5880
+ }
5881
+ const metadata = parseServerMetadata(snapshot.serverMetadata);
5882
+ if (!metadata) {
5883
+ return emptyPlan(
5884
+ serverUrl,
5885
+ { status: "skipped", reason: "no_metadata" },
5886
+ failures
5887
+ );
5888
+ }
5889
+ if (!metadata.revocation_endpoint) {
5890
+ return emptyPlan(
5891
+ serverUrl,
5892
+ { status: "skipped", reason: "no_endpoint" },
5893
+ failures
5894
+ );
5895
+ }
5896
+ return {
5897
+ serverUrl,
5898
+ grants,
5899
+ failures,
5900
+ endpoint: metadata.revocation_endpoint,
5901
+ supportedAuthMethods: revocationAuthMethods(metadata),
5902
+ metadataIssuer: metadata.issuer
5903
+ };
5904
+ } catch (err) {
5905
+ return emptyPlan(serverUrl, {
5906
+ status: "failed",
5907
+ detail: err instanceof Error ? err.message : String(err)
5908
+ });
5909
+ }
5910
+ }
5911
+ function parseServerMetadata(raw) {
5912
+ if (raw === null || typeof raw !== "object") return void 0;
5913
+ return raw;
5914
+ }
5915
+ async function executeOAuthRevocation(plan, params) {
5916
+ const outcome = await runPlan(plan, params);
5917
+ const { logger } = params;
5918
+ if (outcome.status === "failed") {
5919
+ logger?.warn(
5920
+ {
5921
+ serverUrl: plan.serverUrl,
5922
+ endpoint: outcome.endpoint,
5923
+ detail: outcome.detail
5924
+ },
5925
+ "Token revocation failed; local OAuth state was cleared anyway"
5926
+ );
5927
+ } else if (outcome.status === "skipped" && outcome.reason === "no_endpoint") {
5928
+ logger?.debug(
5929
+ { serverUrl: plan.serverUrl },
5930
+ "Skipping token revocation: authorization server metadata has no revocation_endpoint"
5931
+ );
5932
+ }
5933
+ return outcome;
5934
+ }
5935
+ async function collectGrants(snapshot) {
5936
+ const grants = [];
5937
+ const failures = [];
5938
+ const seenKeys = /* @__PURE__ */ new Set();
5939
+ let preregistered;
5940
+ try {
5941
+ preregistered = await parseClient(snapshot.preregisteredClientInformation);
5942
+ } catch (err) {
5943
+ failures.push({
5944
+ status: "failed",
5945
+ detail: `could not read the preconfigured client registration: ${err instanceof Error ? err.message : String(err)}`
5946
+ });
5947
+ }
5948
+ const add2 = async (issuer, rawTokens, rawClient) => {
5949
+ const revocable = selectRevocableToken(await parseTokens(rawTokens));
5950
+ if (!revocable) return;
5951
+ const key = `${issuer ?? "\0legacy"}\0${revocable.token}`;
5952
+ if (seenKeys.has(key)) return;
5953
+ seenKeys.add(key);
5954
+ const bound = await parseClient(rawClient);
5955
+ grants.push({
5956
+ issuer,
5957
+ ...revocable,
5958
+ clientInformation: bound ?? preregistered
5959
+ });
5960
+ };
5961
+ const addSafely = async (issuer, rawTokens, rawClient) => {
5962
+ try {
5963
+ await add2(issuer, rawTokens, rawClient);
5964
+ } catch (err) {
5965
+ const where = issuer === void 0 ? "the unkeyed slot" : `issuer ${issuer}`;
5966
+ failures.push({
5967
+ status: "failed",
5968
+ detail: `could not read the stored grant for ${where}: ${err instanceof Error ? err.message : String(err)}`
5969
+ });
5970
+ }
5971
+ };
5972
+ for (const [issuer, slot] of Object.entries(snapshot.byIssuer)) {
5973
+ await addSafely(issuer, slot?.tokens, slot?.clientInformation);
5974
+ }
5975
+ await addSafely(
5976
+ void 0,
5977
+ snapshot.legacyTokens,
5978
+ snapshot.legacyClientInformation
5979
+ );
5980
+ return { grants, failures };
5981
+ }
5982
+ async function parseTokens(raw) {
5983
+ return raw === void 0 || raw === null ? void 0 : await OAuthTokensSchema.parseAsync(raw);
5984
+ }
5985
+ async function parseClient(raw) {
5986
+ return raw === void 0 || raw === null ? void 0 : await OAuthClientInformationSchema.parseAsync(raw);
5987
+ }
5988
+ function aggregateOutcomes(outcomes) {
5989
+ return outcomes.find((o) => o.status === "failed") ?? outcomes.find((o) => o.status === "revoked") ?? outcomes[0] ?? { status: "skipped", reason: "no_tokens" };
5990
+ }
5991
+ async function runPlan(plan, params) {
5992
+ if (plan.outcome) return plan.outcome;
5993
+ const endpoint = plan.endpoint;
5994
+ if (!endpoint) return { status: "skipped", reason: "no_endpoint" };
5995
+ const outcomes = [...plan.failures];
5996
+ const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
5997
+ const deadlineAt = Date.now() + timeoutMs;
5998
+ for (const grant of plan.grants) {
5999
+ if (grant.issuer === void 0) {
6000
+ outcomes.push({
6001
+ status: "failed",
6002
+ 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"
6003
+ });
6004
+ continue;
6005
+ }
6006
+ if (plan.metadataIssuer !== grant.issuer) {
6007
+ outcomes.push({
6008
+ status: "failed",
6009
+ 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`
6010
+ });
6011
+ continue;
6012
+ }
6013
+ const remainingMs = deadlineAt - Date.now();
6014
+ if (remainingMs <= 0) {
6015
+ outcomes.push({
6016
+ status: "failed",
6017
+ endpoint,
6018
+ detail: `the ${timeoutMs}ms revocation budget was exhausted before this grant was attempted${grant.issuer === void 0 ? "" : ` (issuer ${grant.issuer})`}`
6019
+ });
6020
+ continue;
6021
+ }
6022
+ outcomes.push(
6023
+ await revokeToken({
6024
+ endpoint,
6025
+ token: grant.token,
6026
+ tokenTypeHint: grant.tokenTypeHint,
6027
+ clientInformation: grant.clientInformation,
6028
+ supportedAuthMethods: plan.supportedAuthMethods,
6029
+ fetchFn: params.fetchFn,
6030
+ timeoutMs: remainingMs
6031
+ })
4987
6032
  );
4988
6033
  }
4989
- return sdkAuth(provider, {
4990
- serverUrl: options.serverUrl,
4991
- authorizationCode: options.authorizationCode,
4992
- iss: options.iss,
4993
- scope: options.scope,
4994
- resourceMetadataUrl: options.resourceMetadataUrl,
4995
- fetchFn: options.fetchFn,
4996
- skipIssuerMetadataValidation: options.skipIssuerMetadataValidation,
4997
- forceReauthorization: options.forceReauthorization
4998
- });
6034
+ return aggregateOutcomes(outcomes);
4999
6035
  }
5000
6036
 
5001
6037
  // ../../core/auth/ema/clientConfigError.ts
@@ -5023,7 +6059,7 @@ import {
5023
6059
  exchangeAuthorization,
5024
6060
  startAuthorization
5025
6061
  } from "@modelcontextprotocol/client";
5026
- import { OAuthMetadataSchema } from "@modelcontextprotocol/core";
6062
+ import { OAuthMetadataSchema as OAuthMetadataSchema2 } from "@modelcontextprotocol/core";
5027
6063
 
5028
6064
  // ../../core/auth/ema/constants.ts
5029
6065
  var GRANT_TYPE_JWT_BEARER = "urn:ietf:params:oauth:grant-type:jwt-bearer";
@@ -5072,7 +6108,7 @@ function idpOAuthStorageKey(issuer) {
5072
6108
  }
5073
6109
 
5074
6110
  // ../../core/auth/ema/tokenEndpoint.ts
5075
- import { selectClientAuthMethod } from "@modelcontextprotocol/client";
6111
+ import { selectClientAuthMethod as selectClientAuthMethod2 } from "@modelcontextprotocol/client";
5076
6112
  async function parseOAuthTokenErrorResponse(response, step) {
5077
6113
  let body;
5078
6114
  try {
@@ -5093,7 +6129,7 @@ async function parseOAuthTokenErrorResponse(response, step) {
5093
6129
  }
5094
6130
  function applyClientAuth(metadata, clientInformation, headers, body) {
5095
6131
  const supported = metadata?.token_endpoint_auth_methods_supported ?? [];
5096
- const method = selectClientAuthMethod(clientInformation, supported);
6132
+ const method = selectClientAuthMethod2(clientInformation, supported);
5097
6133
  if (method === "client_secret_basic" && clientInformation.client_secret) {
5098
6134
  const credentials = btoa(
5099
6135
  `${clientInformation.client_id}:${clientInformation.client_secret}`
@@ -5147,7 +6183,7 @@ async function discoverIdpMetadata(issuer, fetchFn) {
5147
6183
  `Failed to discover OIDC metadata for IdP issuer ${issuer}`
5148
6184
  );
5149
6185
  }
5150
- return OAuthMetadataSchema.parse(metadata);
6186
+ return OAuthMetadataSchema2.parse(metadata);
5151
6187
  }
5152
6188
  async function startIdpOidcAuthorization(params) {
5153
6189
  const issuer = normalizeIdpIssuer(params.idp.issuer);
@@ -5403,7 +6439,7 @@ async function discoverResourceAsMetadata(resourceAsUrl, fetchFn) {
5403
6439
 
5404
6440
  // ../../core/auth/ema/wire.ts
5405
6441
  import { discoverAndRequestJwtAuthGrant } from "@modelcontextprotocol/client";
5406
- import { OAuthTokensSchema } from "@modelcontextprotocol/core";
6442
+ import { OAuthTokensSchema as OAuthTokensSchema2 } from "@modelcontextprotocol/core";
5407
6443
  async function exchangeIdJag(params) {
5408
6444
  const issuer = normalizeIdpIssuer(params.idp.issuer);
5409
6445
  const resource = params.resource.trim();
@@ -5482,7 +6518,7 @@ async function redeemIdJagForAccessToken(params) {
5482
6518
  "EMA leg 3 (resource AS JWT bearer grant)"
5483
6519
  );
5484
6520
  }
5485
- return OAuthTokensSchema.parse(await response.json());
6521
+ return OAuthTokensSchema2.parse(await response.json());
5486
6522
  }
5487
6523
 
5488
6524
  // ../../core/auth/ema/emaFlow.ts
@@ -6229,14 +7265,41 @@ var OAuthManager = class {
6229
7265
  return void 0;
6230
7266
  }
6231
7267
  }
6232
- async clearOAuthTokens() {
7268
+ /**
7269
+ * Drop this server's local OAuth state, and revoke the grant at the
7270
+ * authorization server (RFC 7009).
7271
+ *
7272
+ * The state is taken and deleted in one atomic step, and the requests are
7273
+ * sent afterwards from what was taken. Both halves matter: the requests need
7274
+ * state the clear destroys, and the clear must not wait on the network, or a
7275
+ * fresh authorization completing during a five-second revocation would be
7276
+ * deleted by a clear reasoning about the grant it replaced.
7277
+ *
7278
+ * Best-effort by construction: every failure is reported through the returned
7279
+ * outcome and the clear has already happened regardless, because forgetting
7280
+ * the tokens is what the caller actually asked for (#2144).
7281
+ *
7282
+ * `options.revoke === false` skips the request. That is not only an escape
7283
+ * hatch for an authorization server that mishandles it — disconnecting while
7284
+ * still holding live tokens is a case a user may want to reproduce
7285
+ * deliberately, to watch how a server under test copes with it.
7286
+ */
7287
+ async clearOAuthTokens(options) {
6233
7288
  if (!this.oauthConfig?.storage) {
6234
- return;
7289
+ return { status: "skipped", reason: "no_tokens" };
6235
7290
  }
6236
7291
  const serverUrl = this.getServerUrl();
6237
- await this.oauthConfig.storage.clear(serverUrl);
7292
+ const plan = await clearAndPlanRevocation({
7293
+ serverUrl,
7294
+ storage: this.oauthConfig.storage,
7295
+ enabled: options?.revoke
7296
+ });
6238
7297
  this.oauthFlowState = null;
6239
7298
  this.pendingAuthorizationScope = void 0;
7299
+ return executeOAuthRevocation(plan, {
7300
+ fetchFn: this.params.effectiveAuthFetch,
7301
+ logger: this.params.logger
7302
+ });
6240
7303
  }
6241
7304
  async isOAuthAuthorized() {
6242
7305
  const tokens = await this.getOAuthTokens();
@@ -6563,10 +7626,11 @@ var OAuthManager = class {
6563
7626
  await this.params.onBeforeOAuthRedirect(parsedState.authId);
6564
7627
  }
6565
7628
  }
6566
- this.requireNavigation().navigateToAuthorization(authorizationUrl);
7629
+ const navigation = this.requireNavigation();
6567
7630
  const provider = await this.createOAuthProvider();
6568
7631
  const clientInfo = await provider.clientInformation();
6569
7632
  await this.recordAuthorizationCodeFlowState(authorizationUrl, clientInfo);
7633
+ navigation.navigateToAuthorization(authorizationUrl);
6570
7634
  this.params.dispatchOAuthAuthorizationRequired({ url: authorizationUrl });
6571
7635
  }
6572
7636
  requireNavigation() {
@@ -6723,6 +7787,24 @@ var RemoteClientTransport = class {
6723
7787
  sseResponseWaits = /* @__PURE__ */ new Map();
6724
7788
  options;
6725
7789
  config;
7790
+ /**
7791
+ * Whether the upstream connection gives each request its own response stream
7792
+ * (#2140). The SDK's `Protocol.request` reads this: on a 2026-era connection
7793
+ * a transport advertising it has its per-request stream **aborted** as the
7794
+ * spec's cancellation signal, and no `notifications/cancelled` is sent; a
7795
+ * transport that does not gets the stdio mechanism instead.
7796
+ *
7797
+ * The real upstream transport lives on the backend, so this transport has to
7798
+ * answer for it. Streamable HTTP opens one POST — and one SSE response
7799
+ * stream — per request, so it qualifies; stdio and SSE multiplex every
7800
+ * request over one shared channel and must keep sending the notification.
7801
+ *
7802
+ * Advertising it is only half the fix: `requestSend` applies the SDK's
7803
+ * `requestSignal` to the browser-to-backend fetch, and `/api/mcp/send`
7804
+ * forwards that disconnect to the upstream `transport.send`, which is what
7805
+ * actually closes the stream the server is watching.
7806
+ */
7807
+ hasPerRequestStream;
6726
7808
  /**
6727
7809
  * Intentionally returns undefined. The MCP Client checks transport.sessionId to detect
6728
7810
  * reconnects and skip initialize. Our _sessionId is the remote server's session ID, not
@@ -6767,6 +7849,7 @@ var RemoteClientTransport = class {
6767
7849
  constructor(options, config) {
6768
7850
  this.options = options;
6769
7851
  this.config = config;
7852
+ this.hasPerRequestStream = config.type === "streamable-http";
6770
7853
  }
6771
7854
  setAuthRecovery(handlers) {
6772
7855
  this.options.authRecovery = handlers;
@@ -7131,7 +8214,8 @@ var RemoteClientTransport = class {
7131
8214
  const res = await this.fetchFn(`${this.baseUrl}/api/mcp/send`, {
7132
8215
  method: "POST",
7133
8216
  headers: this.headers,
7134
- body: JSON.stringify(body)
8217
+ body: JSON.stringify(body),
8218
+ ...options?.requestSignal && { signal: options.requestSignal }
7135
8219
  });
7136
8220
  if (!res.ok) {
7137
8221
  const text = await res.text();
@@ -7368,6 +8452,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7368
8452
  // pending re-listen timer.
7369
8453
  modernReconnectAttempts = 0;
7370
8454
  modernReconnectTimer;
8455
+ // Set when the last `listen()` was rejected because the server answered it
8456
+ // with a bare graceful-closure result instead of acknowledging (#2097). That
8457
+ // condition is deterministic, so the reconnect machinery must not treat it as
8458
+ // a drop to retry; the flag is what carries the distinction from the rejection
8459
+ // site to the two failure handlers. Cleared by any user-initiated refresh (a
8460
+ // subscribe/unsubscribe is a fresh attempt, and the server may have changed).
8461
+ modernNeverAcknowledged = false;
7371
8462
  // Task ids the user explicitly cancelled. A cancel makes the in-flight
7372
8463
  // `callToolStream` reject with a generic -32603 error, which the stream's
7373
8464
  // error path would otherwise report as a *failed* task — flashing "failed"
@@ -7389,9 +8480,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7389
8480
  pendingRawWireRequests = /* @__PURE__ */ new Map();
7390
8481
  rawWireRequestCounter = 0;
7391
8482
  // Abort controller for the in-flight ordinary (non-task) tool call. Aborting
7392
- // it makes the SDK send a `notifications/cancelled` for that request (the MCP
7393
- // cancellation flow) and reject the pending call, which `callTool` surfaces as
7394
- // a `ToolCallCancelledError`. Undefined when no ordinary call is in flight.
8483
+ // it hands the SDK the MCP cancellation flow for that request and rejects the
8484
+ // pending call, which `callTool` surfaces as a `ToolCallCancelledError`. Which
8485
+ // signal reaches the server is the transport's business, not ours: a
8486
+ // per-request-stream transport on a 2026-era connection has that request's
8487
+ // stream aborted, and everything else gets `notifications/cancelled` (#2140).
8488
+ // Undefined when no ordinary call is in flight.
7395
8489
  // Task-augmented calls have a server-side task and are cancelled via
7396
8490
  // `cancelRequestorTask` instead, so they don't use this (#1458).
7397
8491
  activeToolCallAbortController;
@@ -7498,6 +8592,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7498
8592
  this.fetchFn ?? fetch,
7499
8593
  () => this.oauthManager?.getEndpointOverrides()
7500
8594
  );
8595
+ this.fetchFn = withRfc8414OidcCompat(this.fetchFn);
7501
8596
  this.effectiveAuthFetch = this.buildEffectiveAuthFetch();
7502
8597
  this.sessionId = options.sessionId;
7503
8598
  if (options.oauth || options.environment.oauth) {
@@ -7517,6 +8612,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
7517
8612
  return Promise.resolve();
7518
8613
  },
7519
8614
  initialConfig: oauthConfig,
8615
+ logger: this.logger,
7520
8616
  enterpriseManagedAuth: options.enterpriseManagedAuth,
7521
8617
  installEnterpriseManagedAuth: options.installEnterpriseManagedAuth,
7522
8618
  dispatchOAuthComplete: (detail) => this.dispatchTypedEvent("oauthComplete", detail),
@@ -8207,6 +9303,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8207
9303
  this.modernListenGeneration++;
8208
9304
  this.clearModernReconnectTimer();
8209
9305
  this.modernReconnectAttempts = 0;
9306
+ this.modernNeverAcknowledged = false;
8210
9307
  this.modernSubscription = null;
8211
9308
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
8212
9309
  this.dispatchSubscriptionsChange();
@@ -8993,10 +10090,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
8993
10090
  }
8994
10091
  /**
8995
10092
  * Cancel the in-flight ordinary (non-task) tool call started by
8996
- * {@link callTool}. Aborting its request makes the SDK send a
8997
- * `notifications/cancelled` to the server (the MCP cancellation flow) and
8998
- * reject the pending call, which `callTool` surfaces as a
8999
- * {@link ToolCallCancelledError}.
10093
+ * {@link callTool}. Aborting its request runs the MCP cancellation flow and
10094
+ * rejects the pending call, which `callTool` surfaces as a
10095
+ * {@link ToolCallCancelledError}. The SDK chooses the wire signal from the
10096
+ * transport: on a 2026-era Streamable HTTP connection it aborts that
10097
+ * request's own SSE response stream — the spec's cancellation signal —
10098
+ * rather than sending `notifications/cancelled`, which is the stdio
10099
+ * mechanism and remains in use there (#2140).
9000
10100
  *
9001
10101
  * Task-augmented calls have a server-side task and are cancelled via
9002
10102
  * {@link cancelRequestorTask} instead — this is a no-op for them (and whenever
@@ -11212,6 +12312,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11212
12312
  if (!fromReconnect) {
11213
12313
  this.clearModernReconnectTimer();
11214
12314
  this.modernReconnectAttempts = 0;
12315
+ this.modernNeverAcknowledged = false;
11215
12316
  }
11216
12317
  const generation = ++this.modernListenGeneration;
11217
12318
  const previous = this.modernSubscription;
@@ -11223,10 +12324,18 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11223
12324
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
11224
12325
  return;
11225
12326
  }
11226
- const subscription = await this.client.listen(
11227
- this.buildSubscriptionFilter(),
11228
- this.getRequestOptions()
11229
- );
12327
+ let subscription;
12328
+ try {
12329
+ subscription = await this.client.listen(
12330
+ this.buildSubscriptionFilter(),
12331
+ this.getRequestOptions()
12332
+ );
12333
+ } catch (error) {
12334
+ if (generation === this.modernListenGeneration && isNeverAcknowledgedSubscriptionClose(error)) {
12335
+ this.modernNeverAcknowledged = true;
12336
+ }
12337
+ throw error;
12338
+ }
11230
12339
  if (generation !== this.modernListenGeneration) {
11231
12340
  await closeSubscriptionBestEffort(subscription);
11232
12341
  return;
@@ -11308,8 +12417,34 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11308
12417
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
11309
12418
  return;
11310
12419
  }
12420
+ if (this.modernNeverAcknowledged) {
12421
+ this.endModernStreamNeverAcknowledged();
12422
+ return;
12423
+ }
11311
12424
  this.scheduleModernReconnect();
11312
12425
  }
12426
+ /**
12427
+ * Settle the stream on the never-acknowledged close (#2097): a status of its
12428
+ * own so the UI can say what happened, no reconnect, and a log line carrying
12429
+ * the same sentence the UI shows.
12430
+ *
12431
+ * The state is *not* `"ended"` — that badge covers the two expected closes (a
12432
+ * server shutting an established stream down, and reconnection abandoned after
12433
+ * repeated failures), and reading this case as either of them is exactly the
12434
+ * silence #2063 reported.
12435
+ */
12436
+ endModernStreamNeverAcknowledged() {
12437
+ this.clearModernReconnectTimer();
12438
+ this.logger.warn(
12439
+ { message: NEVER_ACKNOWLEDGED_SUBSCRIPTION_MESSAGE },
12440
+ "subscriptions/listen closed without an acknowledgement"
12441
+ );
12442
+ this.setModernStreamState({
12443
+ active: this.modernStreamActive(),
12444
+ status: "never-acknowledged",
12445
+ honoredUris: []
12446
+ });
12447
+ }
11313
12448
  /**
11314
12449
  * Schedule a reconnect re-listen after the current backoff delay (#1630).
11315
12450
  * `modernReconnectAttempts` reflects the number of *consecutive failed*
@@ -11343,6 +12478,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11343
12478
  * stream ended (re-subscribing resets the run and tries again).
11344
12479
  */
11345
12480
  onModernReconnectFailed() {
12481
+ if (this.modernNeverAcknowledged && !isTerminalStatus(this.status) && this.wantsModernStream()) {
12482
+ this.endModernStreamNeverAcknowledged();
12483
+ return;
12484
+ }
11346
12485
  this.modernReconnectAttempts += 1;
11347
12486
  if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
11348
12487
  this.setModernStreamState({
@@ -11399,7 +12538,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11399
12538
  this.dispatchSubscriptionsChange();
11400
12539
  } catch (error) {
11401
12540
  throw new Error(
11402
- `Failed to subscribe to resource: ${error instanceof Error ? error.message : String(error)}`,
12541
+ `Failed to subscribe to resource: ${subscriptionFailureMessage(error)}`,
11403
12542
  { cause: error }
11404
12543
  );
11405
12544
  }
@@ -11444,7 +12583,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11444
12583
  }
11445
12584
  } catch (error) {
11446
12585
  throw new Error(
11447
- `Failed to unsubscribe from resource: ${error instanceof Error ? error.message : String(error)}`,
12586
+ `Failed to unsubscribe from resource: ${subscriptionFailureMessage(error)}`,
11448
12587
  { cause: error }
11449
12588
  );
11450
12589
  }
@@ -11715,10 +12854,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11715
12854
  return this.oauthManager.getOAuthTokens();
11716
12855
  }
11717
12856
  /**
11718
- * Clears OAuth tokens and client information
12857
+ * Drop this server's stored OAuth state and revoke the grant at the
12858
+ * authorization server (RFC 7009, #2144).
12859
+ *
12860
+ * The request is planned from the stored state, the state is cleared, and
12861
+ * only then is the request sent — so the clear never waits on the network.
12862
+ *
12863
+ * The revocation is best-effort — an authorization server that advertises no
12864
+ * `revocation_endpoint` is left behaving exactly as before, and a network
12865
+ * error, a non-2xx or a timeout is reported in the returned outcome rather
12866
+ * than thrown — so the local clear always completes. Pass
12867
+ * `{ revoke: false }` to skip it.
11719
12868
  */
11720
- async clearOAuthTokens() {
11721
- await this.oauthManager?.clearOAuthTokens();
12869
+ async clearOAuthTokens(options) {
12870
+ return await this.oauthManager?.clearOAuthTokens(options) ?? {
12871
+ status: "skipped",
12872
+ reason: "no_tokens"
12873
+ };
11722
12874
  }
11723
12875
  /**
11724
12876
  * Checks if client is currently OAuth authorized
@@ -12361,158 +13513,131 @@ var ManagedPromptsState = class extends ManagedListState {
12361
13513
  };
12362
13514
 
12363
13515
  // ../../core/react/useInspectorClient.ts
12364
- import { useState, useEffect, useCallback } from "react";
13516
+ import { useCallback as useCallback2, useEffect, useState } from "react";
13517
+
13518
+ // ../../core/react/useStoreSnapshot.ts
13519
+ import { useCallback, useRef, useSyncExternalStore } from "react";
13520
+ function useStoreSnapshot(store, event, read, whenAbsent) {
13521
+ const cache = useRef(null);
13522
+ const subscribe = useCallback(
13523
+ (onStoreChange) => {
13524
+ if (!store) return () => {
13525
+ };
13526
+ store.addEventListener(event, onStoreChange);
13527
+ return () => {
13528
+ store.removeEventListener(event, onStoreChange);
13529
+ };
13530
+ },
13531
+ [store, event]
13532
+ );
13533
+ const getSnapshot = useCallback(() => {
13534
+ const revision = store ? store.getEventRevision(event) : 0;
13535
+ const cached = cache.current;
13536
+ if (cached && Object.is(cached.store, store) && cached.event === event && cached.read === read && Object.is(cached.whenAbsent, whenAbsent) && cached.revision === revision) {
13537
+ return cached.value;
13538
+ }
13539
+ const value = store ? read(store) : whenAbsent;
13540
+ cache.current = { store, event, read, whenAbsent, revision, value };
13541
+ return value;
13542
+ }, [store, event, read, whenAbsent]);
13543
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
13544
+ }
13545
+
13546
+ // ../../core/react/useInspectorClient.ts
12365
13547
  var EMPTY_CLIENT_CAPABILITIES = Object.freeze({});
13548
+ var NO_EXCLUDED_TOOLS = [];
13549
+ var NO_MALFORMED_LIST_ITEMS = [];
13550
+ var readStatus = (client) => client.getStatus();
13551
+ var readCapabilities = (client) => client.getCapabilities();
13552
+ var readServerInfo = (client) => client.getServerInfo();
13553
+ var readInstructions = (client) => client.getInstructions();
13554
+ var readProtocolVersion = (client) => client.getProtocolVersion();
13555
+ var readProtocolEra = (client) => client.getProtocolEra();
13556
+ var readDiscoverResult = (client) => client.getDiscoverResult();
13557
+ var readExcludedTools = (client) => client.getExcludedTools();
13558
+ var readMalformedListItems = (client) => client.getMalformedListItems();
12366
13559
  function useInspectorClient(inspectorClient) {
12367
- const [status, setStatus] = useState(
12368
- inspectorClient?.getStatus() ?? "disconnected"
13560
+ const status = useStoreSnapshot(
13561
+ inspectorClient,
13562
+ "statusChange",
13563
+ readStatus,
13564
+ "disconnected"
13565
+ );
13566
+ const capabilities = useStoreSnapshot(
13567
+ inspectorClient,
13568
+ "capabilitiesChange",
13569
+ readCapabilities,
13570
+ void 0
13571
+ );
13572
+ const serverInfo = useStoreSnapshot(
13573
+ inspectorClient,
13574
+ "serverInfoChange",
13575
+ readServerInfo,
13576
+ void 0
12369
13577
  );
12370
- const [capabilities, setCapabilities] = useState(inspectorClient?.getCapabilities());
12371
- const [serverInfo, setServerInfo] = useState(
12372
- inspectorClient?.getServerInfo()
13578
+ const instructions = useStoreSnapshot(
13579
+ inspectorClient,
13580
+ "instructionsChange",
13581
+ readInstructions,
13582
+ void 0
12373
13583
  );
12374
- const [instructions, setInstructions] = useState(
12375
- inspectorClient?.getInstructions()
13584
+ const protocolVersion = useStoreSnapshot(
13585
+ inspectorClient,
13586
+ "protocolVersionChange",
13587
+ readProtocolVersion,
13588
+ void 0
12376
13589
  );
12377
- const [protocolVersion, setProtocolVersion] = useState(
12378
- inspectorClient?.getProtocolVersion()
13590
+ const protocolEra = useStoreSnapshot(
13591
+ inspectorClient,
13592
+ "protocolEraChange",
13593
+ readProtocolEra,
13594
+ void 0
12379
13595
  );
12380
- const [protocolEra, setProtocolEra] = useState(
12381
- inspectorClient?.getProtocolEra()
13596
+ const discoverResult = useStoreSnapshot(
13597
+ inspectorClient,
13598
+ "discoverResultChange",
13599
+ readDiscoverResult,
13600
+ void 0
12382
13601
  );
12383
- const [discoverResult, setDiscoverResult] = useState(inspectorClient?.getDiscoverResult());
12384
- const [excludedTools, setExcludedTools] = useState(
12385
- inspectorClient?.getExcludedTools() ?? []
13602
+ const excludedTools = useStoreSnapshot(
13603
+ inspectorClient,
13604
+ "excludedToolsChange",
13605
+ readExcludedTools,
13606
+ NO_EXCLUDED_TOOLS
12386
13607
  );
12387
- const [malformedListItems, setMalformedListItems] = useState(inspectorClient?.getMalformedListItems() ?? []);
12388
- const [lastError, setLastError] = useState(void 0);
13608
+ const malformedListItems = useStoreSnapshot(
13609
+ inspectorClient,
13610
+ "malformedListItemsChange",
13611
+ readMalformedListItems,
13612
+ NO_MALFORMED_LIST_ITEMS
13613
+ );
13614
+ const [errorState, setErrorState] = useState({ client: inspectorClient });
13615
+ if (errorState.client !== inspectorClient) {
13616
+ setErrorState({ client: inspectorClient });
13617
+ }
13618
+ const lastError = errorState.client === inspectorClient ? errorState.message : void 0;
12389
13619
  useEffect(() => {
12390
- if (!inspectorClient) {
12391
- setStatus("disconnected");
12392
- setCapabilities(void 0);
12393
- setServerInfo(void 0);
12394
- setInstructions(void 0);
12395
- setProtocolVersion(void 0);
12396
- setProtocolEra(void 0);
12397
- setDiscoverResult(void 0);
12398
- setExcludedTools([]);
12399
- setMalformedListItems([]);
12400
- setLastError(void 0);
12401
- return;
12402
- }
12403
- setStatus(inspectorClient.getStatus());
12404
- setCapabilities(inspectorClient.getCapabilities());
12405
- setServerInfo(inspectorClient.getServerInfo());
12406
- setInstructions(inspectorClient.getInstructions());
12407
- setProtocolVersion(inspectorClient.getProtocolVersion());
12408
- setProtocolEra(inspectorClient.getProtocolEra());
12409
- setDiscoverResult(inspectorClient.getDiscoverResult());
12410
- setExcludedTools(inspectorClient.getExcludedTools());
12411
- setMalformedListItems(inspectorClient.getMalformedListItems());
12412
- setLastError(void 0);
13620
+ if (!inspectorClient) return;
12413
13621
  const onStatusChange = (event) => {
12414
- setStatus(event.detail);
12415
13622
  if (event.detail === "connecting") {
12416
- setLastError(void 0);
13623
+ setErrorState({ client: inspectorClient });
12417
13624
  }
12418
13625
  };
12419
13626
  const onError = (event) => {
12420
- setLastError(event.detail.message);
12421
- };
12422
- const onCapabilitiesChange = (event) => {
12423
- setCapabilities(event.detail);
12424
- };
12425
- const onServerInfoChange = (event) => {
12426
- setServerInfo(event.detail);
12427
- };
12428
- const onInstructionsChange = (event) => {
12429
- setInstructions(event.detail);
12430
- };
12431
- const onProtocolVersionChange = (event) => {
12432
- setProtocolVersion(event.detail);
12433
- };
12434
- const onProtocolEraChange = (event) => {
12435
- setProtocolEra(event.detail);
12436
- };
12437
- const onDiscoverResultChange = (event) => {
12438
- setDiscoverResult(event.detail);
12439
- };
12440
- const onMalformedListItemsChange = (event) => {
12441
- setMalformedListItems(event.detail);
12442
- };
12443
- const onExcludedToolsChange = (event) => {
12444
- setExcludedTools(event.detail);
13627
+ setErrorState({ client: inspectorClient, message: event.detail.message });
12445
13628
  };
12446
13629
  inspectorClient.addEventListener("statusChange", onStatusChange);
12447
13630
  inspectorClient.addEventListener("error", onError);
12448
- inspectorClient.addEventListener(
12449
- "capabilitiesChange",
12450
- onCapabilitiesChange
12451
- );
12452
- inspectorClient.addEventListener("serverInfoChange", onServerInfoChange);
12453
- inspectorClient.addEventListener(
12454
- "instructionsChange",
12455
- onInstructionsChange
12456
- );
12457
- inspectorClient.addEventListener(
12458
- "protocolVersionChange",
12459
- onProtocolVersionChange
12460
- );
12461
- inspectorClient.addEventListener("protocolEraChange", onProtocolEraChange);
12462
- inspectorClient.addEventListener(
12463
- "discoverResultChange",
12464
- onDiscoverResultChange
12465
- );
12466
- inspectorClient.addEventListener(
12467
- "excludedToolsChange",
12468
- onExcludedToolsChange
12469
- );
12470
- inspectorClient.addEventListener(
12471
- "malformedListItemsChange",
12472
- onMalformedListItemsChange
12473
- );
12474
13631
  return () => {
12475
13632
  inspectorClient.removeEventListener("statusChange", onStatusChange);
12476
13633
  inspectorClient.removeEventListener("error", onError);
12477
- inspectorClient.removeEventListener(
12478
- "capabilitiesChange",
12479
- onCapabilitiesChange
12480
- );
12481
- inspectorClient.removeEventListener(
12482
- "serverInfoChange",
12483
- onServerInfoChange
12484
- );
12485
- inspectorClient.removeEventListener(
12486
- "instructionsChange",
12487
- onInstructionsChange
12488
- );
12489
- inspectorClient.removeEventListener(
12490
- "protocolVersionChange",
12491
- onProtocolVersionChange
12492
- );
12493
- inspectorClient.removeEventListener(
12494
- "protocolEraChange",
12495
- onProtocolEraChange
12496
- );
12497
- inspectorClient.removeEventListener(
12498
- "discoverResultChange",
12499
- onDiscoverResultChange
12500
- );
12501
- inspectorClient.removeEventListener(
12502
- "excludedToolsChange",
12503
- onExcludedToolsChange
12504
- );
12505
- inspectorClient.removeEventListener(
12506
- "malformedListItemsChange",
12507
- onMalformedListItemsChange
12508
- );
12509
13634
  };
12510
13635
  }, [inspectorClient]);
12511
- const connect = useCallback(async () => {
13636
+ const connect = useCallback2(async () => {
12512
13637
  if (!inspectorClient) return;
12513
13638
  await inspectorClient.connect();
12514
13639
  }, [inspectorClient]);
12515
- const disconnect = useCallback(async () => {
13640
+ const disconnect = useCallback2(async () => {
12516
13641
  if (!inspectorClient) return;
12517
13642
  await inspectorClient.disconnect();
12518
13643
  }, [inspectorClient]);
@@ -12540,12 +13665,12 @@ function useInspectorClient(inspectorClient) {
12540
13665
  }
12541
13666
 
12542
13667
  // ../../core/react/useManagedTools.ts
12543
- import { useState as useState2, useEffect as useEffect2, useCallback as useCallback3 } from "react";
13668
+ import { useCallback as useCallback4 } from "react";
12544
13669
 
12545
13670
  // ../../core/react/useListError.ts
12546
- import { useCallback as useCallback2, useSyncExternalStore } from "react";
13671
+ import { useCallback as useCallback3, useSyncExternalStore as useSyncExternalStore2 } from "react";
12547
13672
  function useListError(state) {
12548
- const subscribe = useCallback2(
13673
+ const subscribe = useCallback3(
12549
13674
  (onStoreChange) => {
12550
13675
  if (!state) return () => {
12551
13676
  };
@@ -12557,277 +13682,156 @@ function useListError(state) {
12557
13682
  },
12558
13683
  [state]
12559
13684
  );
12560
- const getSnapshot = useCallback2(() => state?.getError() ?? null, [state]);
12561
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
13685
+ const getSnapshot = useCallback3(() => state?.getError() ?? null, [state]);
13686
+ return useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
12562
13687
  }
12563
13688
 
12564
13689
  // ../../core/react/useManagedTools.ts
13690
+ var NO_TOOLS = [];
13691
+ var readTools = (state) => state.getTools();
13692
+ var readListChanged = (state) => state.getListChanged();
12565
13693
  function useManagedTools(client, managedToolsState) {
12566
- const [tools, setTools] = useState2(
12567
- managedToolsState?.getTools() ?? []
13694
+ const tools = useStoreSnapshot(
13695
+ managedToolsState,
13696
+ "toolsChange",
13697
+ readTools,
13698
+ NO_TOOLS
12568
13699
  );
12569
- const [listChanged, setListChanged] = useState2(
12570
- managedToolsState?.getListChanged() ?? false
13700
+ const listChanged = useStoreSnapshot(
13701
+ managedToolsState,
13702
+ "listChangedChange",
13703
+ readListChanged,
13704
+ false
12571
13705
  );
12572
- useEffect2(() => {
12573
- if (!managedToolsState) {
12574
- setTools([]);
12575
- setListChanged(false);
12576
- return;
12577
- }
12578
- setTools(managedToolsState.getTools());
12579
- setListChanged(managedToolsState.getListChanged());
12580
- const onToolsChange = (event) => {
12581
- setTools(event.detail);
12582
- };
12583
- const onListChangedChange = (event) => {
12584
- setListChanged(event.detail);
12585
- };
12586
- managedToolsState.addEventListener("toolsChange", onToolsChange);
12587
- managedToolsState.addEventListener(
12588
- "listChangedChange",
12589
- onListChangedChange
12590
- );
12591
- return () => {
12592
- managedToolsState.removeEventListener("toolsChange", onToolsChange);
12593
- managedToolsState.removeEventListener(
12594
- "listChangedChange",
12595
- onListChangedChange
12596
- );
12597
- };
12598
- }, [managedToolsState]);
12599
13706
  const error = useListError(managedToolsState);
12600
- const refresh = useCallback3(async () => {
12601
- if (!managedToolsState || !client) return [];
13707
+ const refresh = useCallback4(async () => {
13708
+ if (!managedToolsState || !client) return NO_TOOLS;
12602
13709
  managedToolsState.clearListChanged();
12603
- const next = await managedToolsState.refresh(void 0, "refresh");
12604
- setTools(next);
12605
- return next;
13710
+ return managedToolsState.refresh(void 0, "refresh");
12606
13711
  }, [client, managedToolsState]);
12607
- const clearListChanged = useCallback3(() => {
13712
+ const clearListChanged = useCallback4(() => {
12608
13713
  managedToolsState?.clearListChanged();
12609
13714
  }, [managedToolsState]);
12610
13715
  return { tools, error, listChanged, refresh, clearListChanged };
12611
13716
  }
12612
13717
 
12613
13718
  // ../../core/react/useManagedResources.ts
12614
- import { useState as useState3, useEffect as useEffect3, useCallback as useCallback4 } from "react";
13719
+ import { useCallback as useCallback5 } from "react";
13720
+ var NO_RESOURCES = [];
13721
+ var readResources = (state) => state.getResources();
13722
+ var readListChanged2 = (state) => state.getListChanged();
12615
13723
  function useManagedResources(client, managedResourcesState) {
12616
- const [resources, setResources] = useState3(
12617
- managedResourcesState?.getResources() ?? []
13724
+ const resources = useStoreSnapshot(
13725
+ managedResourcesState,
13726
+ "resourcesChange",
13727
+ readResources,
13728
+ NO_RESOURCES
12618
13729
  );
12619
- const [listChanged, setListChanged] = useState3(
12620
- managedResourcesState?.getListChanged() ?? false
13730
+ const listChanged = useStoreSnapshot(
13731
+ managedResourcesState,
13732
+ "listChangedChange",
13733
+ readListChanged2,
13734
+ false
12621
13735
  );
12622
- useEffect3(() => {
12623
- if (!managedResourcesState) {
12624
- setResources([]);
12625
- setListChanged(false);
12626
- return;
12627
- }
12628
- setResources(managedResourcesState.getResources());
12629
- setListChanged(managedResourcesState.getListChanged());
12630
- const onResourcesChange = (event) => {
12631
- setResources(event.detail);
12632
- };
12633
- const onListChangedChange = (event) => {
12634
- setListChanged(event.detail);
12635
- };
12636
- managedResourcesState.addEventListener(
12637
- "resourcesChange",
12638
- onResourcesChange
12639
- );
12640
- managedResourcesState.addEventListener(
12641
- "listChangedChange",
12642
- onListChangedChange
12643
- );
12644
- return () => {
12645
- managedResourcesState.removeEventListener(
12646
- "resourcesChange",
12647
- onResourcesChange
12648
- );
12649
- managedResourcesState.removeEventListener(
12650
- "listChangedChange",
12651
- onListChangedChange
12652
- );
12653
- };
12654
- }, [managedResourcesState]);
12655
13736
  const error = useListError(managedResourcesState);
12656
- const refresh = useCallback4(async () => {
12657
- if (!managedResourcesState || !client) return [];
13737
+ const refresh = useCallback5(async () => {
13738
+ if (!managedResourcesState || !client) return NO_RESOURCES;
12658
13739
  managedResourcesState.clearListChanged();
12659
- const next = await managedResourcesState.refresh(void 0, "refresh");
12660
- setResources(next);
12661
- return next;
13740
+ return managedResourcesState.refresh(void 0, "refresh");
12662
13741
  }, [client, managedResourcesState]);
12663
- const clearListChanged = useCallback4(() => {
13742
+ const clearListChanged = useCallback5(() => {
12664
13743
  managedResourcesState?.clearListChanged();
12665
13744
  }, [managedResourcesState]);
12666
13745
  return { resources, error, listChanged, refresh, clearListChanged };
12667
13746
  }
12668
13747
 
12669
13748
  // ../../core/react/useManagedResourceTemplates.ts
12670
- import { useState as useState4, useEffect as useEffect4, useCallback as useCallback5 } from "react";
13749
+ import { useCallback as useCallback6 } from "react";
13750
+ var NO_RESOURCE_TEMPLATES = [];
13751
+ var readResourceTemplates = (state) => state.getResourceTemplates();
12671
13752
  function useManagedResourceTemplates(client, managedResourceTemplatesState) {
12672
- const [resourceTemplates, setResourceTemplates] = useState4(managedResourceTemplatesState?.getResourceTemplates() ?? []);
12673
- useEffect4(() => {
12674
- if (!managedResourceTemplatesState) {
12675
- setResourceTemplates([]);
12676
- return;
12677
- }
12678
- setResourceTemplates(managedResourceTemplatesState.getResourceTemplates());
12679
- const onResourceTemplatesChange = (event) => {
12680
- setResourceTemplates(event.detail);
12681
- };
12682
- managedResourceTemplatesState.addEventListener(
12683
- "resourceTemplatesChange",
12684
- onResourceTemplatesChange
12685
- );
12686
- return () => {
12687
- managedResourceTemplatesState.removeEventListener(
12688
- "resourceTemplatesChange",
12689
- onResourceTemplatesChange
12690
- );
12691
- };
12692
- }, [managedResourceTemplatesState]);
13753
+ const resourceTemplates = useStoreSnapshot(
13754
+ managedResourceTemplatesState,
13755
+ "resourceTemplatesChange",
13756
+ readResourceTemplates,
13757
+ NO_RESOURCE_TEMPLATES
13758
+ );
12693
13759
  const error = useListError(managedResourceTemplatesState);
12694
- const refresh = useCallback5(async () => {
12695
- if (!managedResourceTemplatesState || !client) return [];
12696
- const next = await managedResourceTemplatesState.refresh(
12697
- void 0,
12698
- "refresh"
12699
- );
12700
- setResourceTemplates(next);
12701
- return next;
13760
+ const refresh = useCallback6(async () => {
13761
+ if (!managedResourceTemplatesState || !client) {
13762
+ return NO_RESOURCE_TEMPLATES;
13763
+ }
13764
+ return managedResourceTemplatesState.refresh(void 0, "refresh");
12702
13765
  }, [client, managedResourceTemplatesState]);
12703
13766
  return { resourceTemplates, error, refresh };
12704
13767
  }
12705
13768
 
12706
13769
  // ../../core/react/useManagedPrompts.ts
12707
- import { useState as useState5, useEffect as useEffect5, useCallback as useCallback6 } from "react";
13770
+ import { useCallback as useCallback7 } from "react";
13771
+ var NO_PROMPTS = [];
13772
+ var readPrompts = (state) => state.getPrompts();
13773
+ var readListChanged3 = (state) => state.getListChanged();
12708
13774
  function useManagedPrompts(client, managedPromptsState) {
12709
- const [prompts, setPrompts] = useState5(
12710
- managedPromptsState?.getPrompts() ?? []
13775
+ const prompts = useStoreSnapshot(
13776
+ managedPromptsState,
13777
+ "promptsChange",
13778
+ readPrompts,
13779
+ NO_PROMPTS
12711
13780
  );
12712
- const [listChanged, setListChanged] = useState5(
12713
- managedPromptsState?.getListChanged() ?? false
13781
+ const listChanged = useStoreSnapshot(
13782
+ managedPromptsState,
13783
+ "listChangedChange",
13784
+ readListChanged3,
13785
+ false
12714
13786
  );
12715
- useEffect5(() => {
12716
- if (!managedPromptsState) {
12717
- setPrompts([]);
12718
- setListChanged(false);
12719
- return;
12720
- }
12721
- setPrompts(managedPromptsState.getPrompts());
12722
- setListChanged(managedPromptsState.getListChanged());
12723
- const onPromptsChange = (event) => {
12724
- setPrompts(event.detail);
12725
- };
12726
- const onListChangedChange = (event) => {
12727
- setListChanged(event.detail);
12728
- };
12729
- managedPromptsState.addEventListener("promptsChange", onPromptsChange);
12730
- managedPromptsState.addEventListener(
12731
- "listChangedChange",
12732
- onListChangedChange
12733
- );
12734
- return () => {
12735
- managedPromptsState.removeEventListener("promptsChange", onPromptsChange);
12736
- managedPromptsState.removeEventListener(
12737
- "listChangedChange",
12738
- onListChangedChange
12739
- );
12740
- };
12741
- }, [managedPromptsState]);
12742
13787
  const error = useListError(managedPromptsState);
12743
- const refresh = useCallback6(async () => {
12744
- if (!managedPromptsState || !client) return [];
13788
+ const refresh = useCallback7(async () => {
13789
+ if (!managedPromptsState || !client) return NO_PROMPTS;
12745
13790
  managedPromptsState.clearListChanged();
12746
- const next = await managedPromptsState.refresh(void 0, "refresh");
12747
- setPrompts(next);
12748
- return next;
13791
+ return managedPromptsState.refresh(void 0, "refresh");
12749
13792
  }, [client, managedPromptsState]);
12750
- const clearListChanged = useCallback6(() => {
13793
+ const clearListChanged = useCallback7(() => {
12751
13794
  managedPromptsState?.clearListChanged();
12752
13795
  }, [managedPromptsState]);
12753
13796
  return { prompts, error, listChanged, refresh, clearListChanged };
12754
13797
  }
12755
13798
 
12756
13799
  // ../../core/react/useMessageLog.ts
12757
- import { useState as useState6, useEffect as useEffect6 } from "react";
13800
+ var NO_MESSAGES = [];
13801
+ var readMessages = (state) => state.getMessages();
12758
13802
  function useMessageLog(messageLogState) {
12759
- const [messages, setMessages] = useState6(
12760
- messageLogState?.getMessages() ?? []
13803
+ const messages = useStoreSnapshot(
13804
+ messageLogState,
13805
+ "messagesChange",
13806
+ readMessages,
13807
+ NO_MESSAGES
12761
13808
  );
12762
- useEffect6(() => {
12763
- if (!messageLogState) {
12764
- setMessages([]);
12765
- return;
12766
- }
12767
- setMessages(messageLogState.getMessages());
12768
- const onMessagesChange = (event) => {
12769
- setMessages(event.detail);
12770
- };
12771
- messageLogState.addEventListener("messagesChange", onMessagesChange);
12772
- return () => {
12773
- messageLogState.removeEventListener("messagesChange", onMessagesChange);
12774
- };
12775
- }, [messageLogState]);
12776
13809
  return { messages };
12777
13810
  }
12778
13811
 
12779
13812
  // ../../core/react/useFetchRequestLog.ts
12780
- import { useState as useState7, useEffect as useEffect7 } from "react";
13813
+ var NO_FETCH_REQUESTS = [];
13814
+ var readFetchRequests = (state) => state.getFetchRequests();
12781
13815
  function useFetchRequestLog(fetchRequestLogState) {
12782
- const [fetchRequests, setFetchRequests] = useState7(
12783
- fetchRequestLogState?.getFetchRequests() ?? []
13816
+ const fetchRequests = useStoreSnapshot(
13817
+ fetchRequestLogState,
13818
+ "fetchRequestsChange",
13819
+ readFetchRequests,
13820
+ NO_FETCH_REQUESTS
12784
13821
  );
12785
- useEffect7(() => {
12786
- if (!fetchRequestLogState) {
12787
- setFetchRequests([]);
12788
- return;
12789
- }
12790
- setFetchRequests(fetchRequestLogState.getFetchRequests());
12791
- const onFetchRequestsChange = (event) => {
12792
- setFetchRequests(event.detail);
12793
- };
12794
- fetchRequestLogState.addEventListener(
12795
- "fetchRequestsChange",
12796
- onFetchRequestsChange
12797
- );
12798
- return () => {
12799
- fetchRequestLogState.removeEventListener(
12800
- "fetchRequestsChange",
12801
- onFetchRequestsChange
12802
- );
12803
- };
12804
- }, [fetchRequestLogState]);
12805
13822
  return { fetchRequests };
12806
13823
  }
12807
13824
 
12808
13825
  // ../../core/react/useStderrLog.ts
12809
- import { useState as useState8, useEffect as useEffect8 } from "react";
13826
+ var NO_STDERR_LOGS = [];
13827
+ var readStderrLogs = (state) => state.getStderrLogs();
12810
13828
  function useStderrLog(stderrLogState) {
12811
- const [stderrLogs, setStderrLogs] = useState8(
12812
- stderrLogState?.getStderrLogs() ?? []
13829
+ const stderrLogs = useStoreSnapshot(
13830
+ stderrLogState,
13831
+ "stderrLogsChange",
13832
+ readStderrLogs,
13833
+ NO_STDERR_LOGS
12813
13834
  );
12814
- useEffect8(() => {
12815
- if (!stderrLogState) {
12816
- setStderrLogs([]);
12817
- return;
12818
- }
12819
- setStderrLogs(stderrLogState.getStderrLogs());
12820
- const onStderrLogsChange = (event) => {
12821
- setStderrLogs(event.detail);
12822
- };
12823
- stderrLogState.addEventListener("stderrLogsChange", onStderrLogsChange);
12824
- return () => {
12825
- stderrLogState.removeEventListener(
12826
- "stderrLogsChange",
12827
- onStderrLogsChange
12828
- );
12829
- };
12830
- }, [stderrLogState]);
12831
13835
  return { stderrLogs };
12832
13836
  }
12833
13837
 
@@ -12926,8 +13930,8 @@ function readInspectorVersion(callerUrl) {
12926
13930
 
12927
13931
  // ../../core/auth/oauth-storage.ts
12928
13932
  import {
12929
- OAuthClientInformationSchema,
12930
- OAuthTokensSchema as OAuthTokensSchema2
13933
+ OAuthClientInformationSchema as OAuthClientInformationSchema2,
13934
+ OAuthTokensSchema as OAuthTokensSchema3
12931
13935
  } from "@modelcontextprotocol/core";
12932
13936
  function withIssuer(value, issuer) {
12933
13937
  return issuer === void 0 ? value : { ...value, issuer };
@@ -13013,7 +14017,7 @@ var OAuthStorageBase = class {
13013
14017
  const state = this.memory.getState().getServerState(serverUrl);
13014
14018
  if (isPreregistered) {
13015
14019
  const prereg = state.preregisteredClientInformation;
13016
- return prereg ? await OAuthClientInformationSchema.parseAsync(prereg) : void 0;
14020
+ return prereg ? await OAuthClientInformationSchema2.parseAsync(prereg) : void 0;
13017
14021
  }
13018
14022
  const slot = this.issuerSlot(state, issuer);
13019
14023
  const fromSlot = slot?.clientInformation;
@@ -13021,7 +14025,7 @@ var OAuthStorageBase = class {
13021
14025
  if (!clientInfo) {
13022
14026
  return void 0;
13023
14027
  }
13024
- const parsed = await OAuthClientInformationSchema.parseAsync(clientInfo);
14028
+ const parsed = await OAuthClientInformationSchema2.parseAsync(clientInfo);
13025
14029
  return withIssuer(
13026
14030
  parsed,
13027
14031
  fromSlot ? this.resolveReadIssuer(state, issuer) : void 0
@@ -13100,7 +14104,7 @@ var OAuthStorageBase = class {
13100
14104
  if (!tokens) {
13101
14105
  return void 0;
13102
14106
  }
13103
- const parsed = await OAuthTokensSchema2.parseAsync(tokens);
14107
+ const parsed = await OAuthTokensSchema3.parseAsync(tokens);
13104
14108
  return withIssuer(
13105
14109
  parsed,
13106
14110
  fromSlot ? this.resolveReadIssuer(state, issuer) : void 0
@@ -13206,6 +14210,25 @@ var OAuthStorageBase = class {
13206
14210
  this.memory.getState().setServerState(serverUrl, { discoveryState: void 0 });
13207
14211
  await this.persist();
13208
14212
  }
14213
+ async takeRevocationSnapshot(serverUrl) {
14214
+ await this.ensureLoaded();
14215
+ const state = this.memory.getState().getServerState(serverUrl);
14216
+ const snapshot = {
14217
+ byIssuer: Object.fromEntries(
14218
+ Object.entries(state.byIssuer ?? {}).map(([issuer, slot]) => [
14219
+ issuer,
14220
+ { tokens: slot?.tokens, clientInformation: slot?.clientInformation }
14221
+ ])
14222
+ ),
14223
+ legacyTokens: state.tokens,
14224
+ legacyClientInformation: state.clientInformation,
14225
+ preregisteredClientInformation: state.preregisteredClientInformation,
14226
+ serverMetadata: state.serverMetadata
14227
+ };
14228
+ this.memory.getState().clearServerState(serverUrl);
14229
+ await this.persist();
14230
+ return snapshot;
14231
+ }
13209
14232
  async clear(serverUrl) {
13210
14233
  await this.ensureLoaded();
13211
14234
  this.memory.getState().clearServerState(serverUrl);
@@ -13634,6 +14657,11 @@ async function runRunnerInteractiveOAuth(options) {
13634
14657
  }
13635
14658
  }
13636
14659
 
14660
+ // src/oauthMessageTone.ts
14661
+ function oauthMessageToneFor(message, warningText) {
14662
+ return message !== null && message === warningText ? "warning" : "info";
14663
+ }
14664
+
13637
14665
  // src/logger.ts
13638
14666
  import path5 from "path";
13639
14667
  import pino from "pino";
@@ -13759,24 +14787,24 @@ function Tabs({
13759
14787
  }
13760
14788
 
13761
14789
  // src/components/InfoTab.tsx
13762
- import { useRef as useRef3 } from "react";
14790
+ import { useRef as useRef4 } from "react";
13763
14791
  import { Box as Box3, Text as Text2, useInput } from "ink";
13764
14792
 
13765
14793
  // node_modules/ink-scroll-view/dist/index.js
13766
14794
  import {
13767
- useState as useState22,
14795
+ useState as useState2,
13768
14796
  useRef as useRef2,
13769
14797
  forwardRef as forwardRef2,
13770
14798
  useImperativeHandle as useImperativeHandle2,
13771
14799
  useCallback as useCallback22
13772
14800
  } from "react";
13773
14801
  import {
13774
- useState as useState9,
13775
- useRef,
14802
+ useState as useState3,
14803
+ useRef as useRef3,
13776
14804
  forwardRef,
13777
14805
  useImperativeHandle,
13778
14806
  useLayoutEffect,
13779
- useCallback as useCallback7,
14807
+ useCallback as useCallback8,
13780
14808
  Children,
13781
14809
  isValidElement
13782
14810
  } from "react";
@@ -13790,7 +14818,7 @@ var MeasurableItem = ({
13790
14818
  width,
13791
14819
  measureKey
13792
14820
  }) => {
13793
- const ref = useRef(null);
14821
+ const ref = useRef3(null);
13794
14822
  useLayoutEffect(() => {
13795
14823
  if (ref.current) {
13796
14824
  const { height } = measureElement(ref.current);
@@ -13800,14 +14828,14 @@ var MeasurableItem = ({
13800
14828
  return /* @__PURE__ */ jsx2(Box2, { ref, flexShrink: 0, width: "100%", flexDirection: "column", children });
13801
14829
  };
13802
14830
  function useStateRef(initialValue) {
13803
- const [state, setStateInternal] = useState9(initialValue);
13804
- const ref = useRef(initialValue);
13805
- const setState = useCallback7((update) => {
14831
+ const [state, setStateInternal] = useState3(initialValue);
14832
+ const ref = useRef3(initialValue);
14833
+ const setState = useCallback8((update) => {
13806
14834
  const nextValue = typeof update === "function" ? update(ref.current) : update;
13807
14835
  ref.current = nextValue;
13808
14836
  setStateInternal(nextValue);
13809
14837
  }, []);
13810
- const getState = useCallback7(() => ref.current, []);
14838
+ const getState = useCallback8(() => ref.current, []);
13811
14839
  return [state, setState, getState];
13812
14840
  }
13813
14841
  var ControlledScrollView = forwardRef(
@@ -13825,21 +14853,21 @@ var ControlledScrollView = forwardRef(
13825
14853
  width: 0
13826
14854
  });
13827
14855
  const [contentHeight, setContentHeight, getContentHeight] = useStateRef(0);
13828
- const [itemMeasureKeys, setItemMeasureKeys] = useState9({});
13829
- const viewportRef = useRef(null);
13830
- const contentRef = useRef(null);
13831
- const prevContentHeightRef = useRef(0);
14856
+ const [itemMeasureKeys, setItemMeasureKeys] = useState3({});
14857
+ const viewportRef = useRef3(null);
14858
+ const contentRef = useRef3(null);
14859
+ const prevContentHeightRef = useRef3(0);
13832
14860
  useLayoutEffect(() => {
13833
14861
  if (contentHeight !== prevContentHeightRef.current) {
13834
14862
  onContentHeightChange?.(contentHeight, prevContentHeightRef.current);
13835
14863
  prevContentHeightRef.current = contentHeight;
13836
14864
  }
13837
14865
  }, [contentHeight, onContentHeightChange]);
13838
- const itemHeightsRef = useRef({});
13839
- const itemKeysRef = useRef([]);
13840
- const itemOffsetsRef = useRef([]);
13841
- const firstInvalidOffsetIndexRef = useRef(0);
13842
- const handleItemMeasure = useCallback7(
14866
+ const itemHeightsRef = useRef3({});
14867
+ const itemKeysRef = useRef3([]);
14868
+ const itemOffsetsRef = useRef3([]);
14869
+ const firstInvalidOffsetIndexRef = useRef3(0);
14870
+ const handleItemMeasure = useCallback8(
13843
14871
  (index, height) => {
13844
14872
  const key = itemKeysRef.current[index] || index;
13845
14873
  if (itemHeightsRef.current[key] !== height) {
@@ -13870,7 +14898,7 @@ var ControlledScrollView = forwardRef(
13870
14898
  setContentHeight
13871
14899
  ]
13872
14900
  );
13873
- const measureViewport = useCallback7(() => {
14901
+ const measureViewport = useCallback8(() => {
13874
14902
  if (viewportRef.current) {
13875
14903
  const { width, height } = measureElement(viewportRef.current);
13876
14904
  const currentSize = getViewportSize();
@@ -13883,7 +14911,7 @@ var ControlledScrollView = forwardRef(
13883
14911
  useLayoutEffect(() => {
13884
14912
  measureViewport();
13885
14913
  });
13886
- const prevChildrenRef = useRef(null);
14914
+ const prevChildrenRef = useRef3(null);
13887
14915
  if (prevChildrenRef.current !== children) {
13888
14916
  prevChildrenRef.current = children;
13889
14917
  const newItemKeys = [];
@@ -13980,7 +15008,7 @@ var ControlledScrollView = forwardRef(
13980
15008
  }
13981
15009
  );
13982
15010
  function useStateRef2(initialValue) {
13983
- const [state, setStateInternal] = useState22(initialValue);
15011
+ const [state, setStateInternal] = useState2(initialValue);
13984
15012
  const ref = useRef2(initialValue);
13985
15013
  const setState = useCallback22((update) => {
13986
15014
  const nextValue = typeof update === "function" ? update(ref.current) : update;
@@ -14109,7 +15137,7 @@ function InfoTab({
14109
15137
  " ",
14110
15138
  headerPairs.map(({ key, value }) => `${key}=${value}`).join(", ")
14111
15139
  ] }) }) : null;
14112
- const scrollViewRef = useRef3(null);
15140
+ const scrollViewRef = useRef4(null);
14113
15141
  useInput(
14114
15142
  (input, key) => {
14115
15143
  if (focused) {
@@ -14231,7 +15259,7 @@ function InfoTab({
14231
15259
  }
14232
15260
 
14233
15261
  // src/components/AuthTab.tsx
14234
- import { useState as useState10, useEffect as useEffect9, useRef as useRef4, useCallback as useCallback8 } from "react";
15262
+ import { useState as useState4, useEffect as useEffect2, useRef as useRef5, useCallback as useCallback9 } from "react";
14235
15263
  import { Box as Box5, Text as Text4, useInput as useInput2 } from "ink";
14236
15264
 
14237
15265
  // src/components/SelectableItem.tsx
@@ -14294,6 +15322,7 @@ function AuthTab({
14294
15322
  inspectorClient,
14295
15323
  oauthStatus,
14296
15324
  oauthMessage,
15325
+ oauthMessageTone = "info",
14297
15326
  oauthRevision,
14298
15327
  pendingStepUp,
14299
15328
  onAuthorizeStepUp,
@@ -14305,12 +15334,28 @@ function AuthTab({
14305
15334
  connectionStatus
14306
15335
  }) {
14307
15336
  const isLiveConnection = connectionStatus === "connected" || connectionStatus === "connecting";
14308
- const scrollViewRef = useRef4(null);
14309
- const [oauthState, setOauthState] = useState10(void 0);
14310
- const [clearedConfirmation, setClearedConfirmation] = useState10(false);
14311
- const [lastClearDisconnected, setLastClearDisconnected] = useState10(false);
14312
- const [stepUpChoiceIndex, setStepUpChoiceIndex] = useState10(0);
14313
- const refreshOAuthState = useCallback8(async () => {
15337
+ const scrollViewRef = useRef5(null);
15338
+ const [oauthState, setOauthState] = useState4(void 0);
15339
+ const [clearState, setClearState] = useState4("idle");
15340
+ const [clearFailure, setClearFailure] = useState4(null);
15341
+ const clearAttemptRef = useRef5(0);
15342
+ const clearInFlightRef = useRef5(false);
15343
+ const ownClearRef = useRef5(false);
15344
+ const serverNameRef = useRef5(serverName);
15345
+ useEffect2(() => {
15346
+ serverNameRef.current = serverName;
15347
+ }, [serverName]);
15348
+ useEffect2(() => {
15349
+ clearAttemptRef.current++;
15350
+ clearInFlightRef.current = false;
15351
+ ownClearRef.current = false;
15352
+ setClearState("idle");
15353
+ setClearFailure(null);
15354
+ setLastClearDisconnected(false);
15355
+ }, [serverName]);
15356
+ const [lastClearDisconnected, setLastClearDisconnected] = useState4(false);
15357
+ const [stepUpChoiceIndex, setStepUpChoiceIndex] = useState4(0);
15358
+ const refreshOAuthState = useCallback9(async () => {
14314
15359
  if (!inspectorClient) {
14315
15360
  setOauthState(void 0);
14316
15361
  return;
@@ -14318,17 +15363,22 @@ function AuthTab({
14318
15363
  const state = await inspectorClient.getOAuthState();
14319
15364
  setOauthState(state);
14320
15365
  }, [inspectorClient]);
14321
- useEffect9(() => {
15366
+ useEffect2(() => {
14322
15367
  void refreshOAuthState();
14323
15368
  }, [refreshOAuthState, oauthRevision, connectionStatus]);
14324
- useEffect9(() => {
14325
- setClearedConfirmation(false);
15369
+ useEffect2(() => {
15370
+ if (ownClearRef.current) {
15371
+ ownClearRef.current = false;
15372
+ return;
15373
+ }
15374
+ setClearState("idle");
15375
+ setClearFailure(null);
14326
15376
  setLastClearDisconnected(false);
14327
15377
  }, [oauthRevision]);
14328
- useEffect9(() => {
15378
+ useEffect2(() => {
14329
15379
  setStepUpChoiceIndex(0);
14330
15380
  }, [pendingStepUp]);
14331
- useEffect9(() => {
15381
+ useEffect2(() => {
14332
15382
  if (!inspectorClient) return;
14333
15383
  const update = () => {
14334
15384
  void refreshOAuthState();
@@ -14379,9 +15429,29 @@ function AuthTab({
14379
15429
  const h = scrollViewRef.current.getViewportHeight() || 1;
14380
15430
  scrollViewRef.current.scrollBy(h);
14381
15431
  } else if (input.toLowerCase() === "s") {
15432
+ if (clearInFlightRef.current) return;
15433
+ clearInFlightRef.current = true;
15434
+ const attempt = ++clearAttemptRef.current;
15435
+ const attemptServer = serverName;
14382
15436
  setLastClearDisconnected(isLiveConnection);
14383
- onClearOAuth();
14384
- setClearedConfirmation(true);
15437
+ setClearFailure(null);
15438
+ ownClearRef.current = true;
15439
+ setClearState("clearing");
15440
+ const current = () => clearAttemptRef.current === attempt && serverNameRef.current === attemptServer;
15441
+ void Promise.resolve(onClearOAuth()).then(
15442
+ () => {
15443
+ if (!current()) return;
15444
+ clearInFlightRef.current = false;
15445
+ setClearState("cleared");
15446
+ },
15447
+ (err) => {
15448
+ if (!current()) return;
15449
+ clearInFlightRef.current = false;
15450
+ ownClearRef.current = false;
15451
+ setClearFailure(err instanceof Error ? err.message : String(err));
15452
+ setClearState("failed");
15453
+ }
15454
+ );
14385
15455
  }
14386
15456
  },
14387
15457
  { isActive: focused }
@@ -14396,7 +15466,7 @@ function AuthTab({
14396
15466
  /* @__PURE__ */ jsx5(ScrollView, { ref: scrollViewRef, height: height - 8, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 0, children: [
14397
15467
  oauthStatus === "authenticating" && /* @__PURE__ */ jsx5(Text4, { color: "yellow", children: "Authenticating\u2026" }),
14398
15468
  oauthStatus === "error" && oauthMessage && /* @__PURE__ */ jsx5(Text4, { color: "red", children: oauthMessage }),
14399
- oauthStatus === "idle" && oauthMessage && /* @__PURE__ */ jsx5(Text4, { color: "cyan", children: oauthMessage }),
15469
+ oauthStatus === "idle" && oauthMessage && /* @__PURE__ */ jsx5(Text4, { color: oauthMessageTone === "warning" ? "yellow" : "cyan", children: oauthMessage }),
14400
15470
  pendingStepUp ? /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", gap: 0, children: [
14401
15471
  /* @__PURE__ */ jsx5(Text4, { bold: true, color: "yellow", children: stepUpModalTitle({
14402
15472
  enterpriseManaged: pendingStepUp.enterpriseManaged
@@ -14505,7 +15575,12 @@ function AuthTab({
14505
15575
  "tate",
14506
15576
  isLiveConnection && " and disconnect"
14507
15577
  ] }),
14508
- clearedConfirmation && /* @__PURE__ */ jsx5(Text4, { color: "green", children: lastClearDisconnected ? "OAuth state cleared. Disconnected." : "OAuth state cleared." })
15578
+ clearState === "clearing" && /* @__PURE__ */ jsx5(Text4, { color: "yellow", children: "Clearing OAuth state\u2026" }),
15579
+ clearState === "cleared" && /* @__PURE__ */ jsx5(Text4, { color: "green", children: lastClearDisconnected ? "OAuth state cleared. Disconnected." : "OAuth state cleared." }),
15580
+ clearState === "failed" && /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
15581
+ "Could not clear OAuth state",
15582
+ clearFailure ? `: ${clearFailure}` : "."
15583
+ ] })
14509
15584
  ] })
14510
15585
  ] }) }),
14511
15586
  focused && /* @__PURE__ */ jsx5(
@@ -14522,20 +15597,20 @@ function AuthTab({
14522
15597
  }
14523
15598
 
14524
15599
  // src/components/ResourcesTab.tsx
14525
- import { useState as useState12, useEffect as useEffect11, useRef as useRef5, useMemo } from "react";
15600
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef6, useMemo } from "react";
14526
15601
  import { Box as Box6, Text as Text5, useInput as useInput3 } from "ink";
14527
15602
 
14528
15603
  // src/hooks/useSelectableList.ts
14529
- import { useState as useState11, useEffect as useEffect10, useCallback as useCallback9 } from "react";
15604
+ import { useState as useState5, useEffect as useEffect3, useCallback as useCallback10 } from "react";
14530
15605
  function clampFirstVisible(first, selected, visibleCount) {
14531
15606
  if (selected < first) return selected;
14532
15607
  if (selected >= first + visibleCount) return selected - visibleCount + 1;
14533
15608
  return first;
14534
15609
  }
14535
15610
  function useSelectableList(itemCount, visibleCount, options) {
14536
- const [selectedIndex, setSelectedIndex] = useState11(0);
14537
- const [firstVisible, setFirstVisible] = useState11(0);
14538
- const setSelection = useCallback9(
15611
+ const [selectedIndex, setSelectedIndex] = useState5(0);
15612
+ const [firstVisible, setFirstVisible] = useState5(0);
15613
+ const setSelection = useCallback10(
14539
15614
  (newIndex) => {
14540
15615
  setSelectedIndex(newIndex);
14541
15616
  setFirstVisible(
@@ -14544,13 +15619,13 @@ function useSelectableList(itemCount, visibleCount, options) {
14544
15619
  },
14545
15620
  [visibleCount]
14546
15621
  );
14547
- useEffect10(() => {
15622
+ useEffect3(() => {
14548
15623
  if (options?.resetWhen !== void 0) {
14549
15624
  setSelectedIndex(0);
14550
15625
  setFirstVisible(0);
14551
15626
  }
14552
15627
  }, [options?.resetWhen]);
14553
- useEffect10(() => {
15628
+ useEffect3(() => {
14554
15629
  if (itemCount > 0 && selectedIndex >= itemCount) {
14555
15630
  const newIndex = itemCount - 1;
14556
15631
  setSelectedIndex(newIndex);
@@ -14578,13 +15653,13 @@ function ResourcesTab({
14578
15653
  onAuthRecoveryRequired,
14579
15654
  modalOpen = false
14580
15655
  }) {
14581
- const [error, setError] = useState12(null);
14582
- const [resourceContent, setResourceContent] = useState12(null);
14583
- const [loading, setLoading] = useState12(false);
14584
- const [shouldFetchResource, setShouldFetchResource] = useState12(
15656
+ const [error, setError] = useState6(null);
15657
+ const [resourceContent, setResourceContent] = useState6(null);
15658
+ const [loading, setLoading] = useState6(false);
15659
+ const [shouldFetchResource, setShouldFetchResource] = useState6(
14585
15660
  null
14586
15661
  );
14587
- const scrollViewRef = useRef5(null);
15662
+ const scrollViewRef = useRef6(null);
14588
15663
  const allItems = useMemo(
14589
15664
  () => [
14590
15665
  ...resources.map((r) => ({ type: "resource", data: r })),
@@ -14649,11 +15724,11 @@ function ResourcesTab({
14649
15724
  isActive: !modalOpen && (focusedPane === "list" || focusedPane === "details")
14650
15725
  }
14651
15726
  );
14652
- useEffect11(() => {
15727
+ useEffect4(() => {
14653
15728
  scrollViewRef.current?.scrollTo(0);
14654
15729
  }, [selectedIndex]);
14655
- const prevResourcesRef = useRef5(resources);
14656
- useEffect11(() => {
15730
+ const prevResourcesRef = useRef6(resources);
15731
+ useEffect4(() => {
14657
15732
  if (prevResourcesRef.current !== resources) {
14658
15733
  setResourceContent(null);
14659
15734
  setShouldFetchResource(null);
@@ -14664,7 +15739,7 @@ function ResourcesTab({
14664
15739
  const isTemplate = selectedItem?.type === "template";
14665
15740
  const selectedResource = isResource ? selectedItem.data : null;
14666
15741
  const selectedTemplate = isTemplate ? selectedItem.data : null;
14667
- useEffect11(() => {
15742
+ useEffect4(() => {
14668
15743
  if (!shouldFetchResource || !inspectorClient) return;
14669
15744
  const fetchContent = async () => {
14670
15745
  setLoading(true);
@@ -14690,8 +15765,8 @@ function ResourcesTab({
14690
15765
  }, [shouldFetchResource, inspectorClient, onAuthRecoveryRequired]);
14691
15766
  const listWidth = Math.floor(width * 0.4);
14692
15767
  const detailWidth = width - listWidth;
14693
- const prevCountRef = useRef5(totalCount);
14694
- useEffect11(() => {
15768
+ const prevCountRef = useRef6(totalCount);
15769
+ useEffect4(() => {
14695
15770
  if (prevCountRef.current !== totalCount) {
14696
15771
  prevCountRef.current = totalCount;
14697
15772
  onCountChange?.(totalCount);
@@ -14841,7 +15916,7 @@ function ResourcesTab({
14841
15916
  }
14842
15917
 
14843
15918
  // src/components/PromptsTab.tsx
14844
- import { useState as useState13, useEffect as useEffect12, useRef as useRef6 } from "react";
15919
+ import { useState as useState7, useEffect as useEffect5, useRef as useRef7 } from "react";
14845
15920
  import { Box as Box7, Text as Text6, useInput as useInput4 } from "ink";
14846
15921
  import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
14847
15922
  function PromptsTab({
@@ -14861,8 +15936,8 @@ function PromptsTab({
14861
15936
  visibleCount,
14862
15937
  { resetWhen: prompts }
14863
15938
  );
14864
- const [error, setError] = useState13(null);
14865
- const scrollViewRef = useRef6(null);
15939
+ const [error, setError] = useState7(null);
15940
+ const scrollViewRef = useRef7(null);
14866
15941
  useInput4(
14867
15942
  (input, key) => {
14868
15943
  if (key.return && selectedPrompt && inspectorClient && onFetchPrompt) {
@@ -14923,7 +15998,7 @@ function PromptsTab({
14923
15998
  isActive: !modalOpen && (focusedPane === "list" || focusedPane === "details")
14924
15999
  }
14925
16000
  );
14926
- useEffect12(() => {
16001
+ useEffect5(() => {
14927
16002
  scrollViewRef.current?.scrollTo(0);
14928
16003
  }, [selectedIndex]);
14929
16004
  const selectedPrompt = prompts[selectedIndex] || null;
@@ -15043,7 +16118,7 @@ function PromptsTab({
15043
16118
  }
15044
16119
 
15045
16120
  // src/components/ToolsTab.tsx
15046
- import { useState as useState14, useEffect as useEffect13, useMemo as useMemo2, useRef as useRef7 } from "react";
16121
+ import { useState as useState8, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef8 } from "react";
15047
16122
  import { Box as Box8, Text as Text7, useInput as useInput5 } from "ink";
15048
16123
 
15049
16124
  // ../../core/json/schemaLint.ts
@@ -15336,12 +16411,12 @@ function ToolsTab({
15336
16411
  visibleCount,
15337
16412
  { resetWhen: tools }
15338
16413
  );
15339
- const [error] = useState14(null);
16414
+ const [error] = useState8(null);
15340
16415
  const findingsByTool = useMemo2(
15341
16416
  () => tools.map((tool) => lintToolSchemas(tool)),
15342
16417
  [tools]
15343
16418
  );
15344
- const scrollViewRef = useRef7(null);
16419
+ const scrollViewRef = useRef8(null);
15345
16420
  const listWidth = Math.floor(width * 0.4);
15346
16421
  const detailWidth = width - listWidth;
15347
16422
  useInput5(
@@ -15380,7 +16455,7 @@ function ToolsTab({
15380
16455
  isActive: !modalOpen && (focusedPane === "list" || focusedPane === "details")
15381
16456
  }
15382
16457
  );
15383
- useEffect13(() => {
16458
+ useEffect6(() => {
15384
16459
  scrollViewRef.current?.scrollTo(0);
15385
16460
  }, [selectedIndex]);
15386
16461
  const selectedTool = tools[selectedIndex] || null;
@@ -15539,7 +16614,7 @@ function ToolsTab({
15539
16614
  }
15540
16615
 
15541
16616
  // src/components/NotificationsTab.tsx
15542
- import { useEffect as useEffect14, useRef as useRef8 } from "react";
16617
+ import { useEffect as useEffect7, useRef as useRef9 } from "react";
15543
16618
  import { Box as Box9, Text as Text8, useInput as useInput6 } from "ink";
15544
16619
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
15545
16620
  function NotificationsTab({
@@ -15549,12 +16624,12 @@ function NotificationsTab({
15549
16624
  onCountChange,
15550
16625
  focused = false
15551
16626
  }) {
15552
- const scrollViewRef = useRef8(null);
15553
- const onCountChangeRef = useRef8(onCountChange);
15554
- useEffect14(() => {
16627
+ const scrollViewRef = useRef9(null);
16628
+ const onCountChangeRef = useRef9(onCountChange);
16629
+ useEffect7(() => {
15555
16630
  onCountChangeRef.current = onCountChange;
15556
16631
  }, [onCountChange]);
15557
- useEffect14(() => {
16632
+ useEffect7(() => {
15558
16633
  onCountChangeRef.current?.(stderrLogs.length);
15559
16634
  }, [stderrLogs.length]);
15560
16635
  useInput6(
@@ -15602,7 +16677,7 @@ function NotificationsTab({
15602
16677
  }
15603
16678
 
15604
16679
  // src/components/HistoryTab.tsx
15605
- import React7, { useEffect as useEffect15, useRef as useRef9 } from "react";
16680
+ import React7, { useEffect as useEffect8, useRef as useRef10 } from "react";
15606
16681
  import { Box as Box10, Text as Text9, useInput as useInput7 } from "ink";
15607
16682
  import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
15608
16683
  function HistoryTab({
@@ -15619,7 +16694,7 @@ function HistoryTab({
15619
16694
  messages.length,
15620
16695
  visibleCount
15621
16696
  );
15622
- const scrollViewRef = useRef9(null);
16697
+ const scrollViewRef = useRef10(null);
15623
16698
  const selectedMessage = messages[selectedIndex] || null;
15624
16699
  useInput7(
15625
16700
  (input, key) => {
@@ -15660,7 +16735,7 @@ function HistoryTab({
15660
16735
  React7.useEffect(() => {
15661
16736
  onCountChange?.(messages.length);
15662
16737
  }, [messages.length]);
15663
- useEffect15(() => {
16738
+ useEffect8(() => {
15664
16739
  scrollViewRef.current?.scrollTo(0);
15665
16740
  }, [selectedIndex]);
15666
16741
  const listWidth = Math.floor(width * 0.4);
@@ -15822,7 +16897,7 @@ function HistoryTab({
15822
16897
  }
15823
16898
 
15824
16899
  // src/components/RequestsTab.tsx
15825
- import React8, { useEffect as useEffect16, useRef as useRef10 } from "react";
16900
+ import React8, { useEffect as useEffect9, useRef as useRef11 } from "react";
15826
16901
  import { Box as Box11, Text as Text10, useInput as useInput8 } from "ink";
15827
16902
  import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
15828
16903
  function RequestsTab({
@@ -15839,7 +16914,7 @@ function RequestsTab({
15839
16914
  requests.length,
15840
16915
  visibleCount
15841
16916
  );
15842
- const scrollViewRef = useRef10(null);
16917
+ const scrollViewRef = useRef11(null);
15843
16918
  const selectedRequest = requests[selectedIndex] || null;
15844
16919
  useInput8(
15845
16920
  (input, key) => {
@@ -15880,7 +16955,7 @@ function RequestsTab({
15880
16955
  React8.useEffect(() => {
15881
16956
  onCountChange?.(requests.length);
15882
16957
  }, [requests.length]);
15883
- useEffect16(() => {
16958
+ useEffect9(() => {
15884
16959
  scrollViewRef.current?.scrollTo(0);
15885
16960
  }, [selectedIndex]);
15886
16961
  const listWidth = Math.floor(width * 0.4);
@@ -16093,12 +17168,12 @@ function RequestsTab({
16093
17168
  }
16094
17169
 
16095
17170
  // src/components/ToolTestModal.tsx
16096
- import React25, { useState as useState19 } from "react";
17171
+ import React25, { useState as useState13 } from "react";
16097
17172
  import { Box as Box23, Text as Text20, useInput as useInput17 } from "ink";
16098
17173
 
16099
17174
  // node_modules/ink-form/lib/Form.js
16100
17175
  import { Box as Box22, useFocusManager, useInput as useInput16 } from "ink";
16101
- import { useEffect as useEffect19, useMemo as useMemo3, useState as useState18 } from "react";
17176
+ import { useEffect as useEffect12, useMemo as useMemo3, useState as useState12 } from "react";
16102
17177
  import * as React23 from "react";
16103
17178
 
16104
17179
  // node_modules/ink-form/lib/FormHeader.js
@@ -16166,7 +17241,7 @@ var FormHeader = (props) => {
16166
17241
  };
16167
17242
 
16168
17243
  // node_modules/ink-form/lib/FormFieldRenderer.js
16169
- import React21, { useState as useState17 } from "react";
17244
+ import React21, { useState as useState11 } from "react";
16170
17245
  import { Box as Box20, useFocus, Text as Text18, useInput as useInput14 } from "ink";
16171
17246
 
16172
17247
  // node_modules/ink-form/lib/managers/FloatFormFieldManager.js
@@ -16177,7 +17252,7 @@ import React11 from "react";
16177
17252
  import { Box as Box13, Text as Text13, useInput as useInput11 } from "ink";
16178
17253
 
16179
17254
  // node_modules/ink-text-input/build/index.js
16180
- import React10, { useState as useState15, useEffect as useEffect17 } from "react";
17255
+ import React10, { useState as useState9, useEffect as useEffect10 } from "react";
16181
17256
  import { Text as Text12, useInput as useInput10 } from "ink";
16182
17257
 
16183
17258
  // node_modules/chalk/source/vendor/ansi-styles/index.js
@@ -16677,12 +17752,12 @@ var source_default = chalk;
16677
17752
 
16678
17753
  // node_modules/ink-text-input/build/index.js
16679
17754
  function TextInput({ value: originalValue, placeholder = "", focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit }) {
16680
- const [state, setState] = useState15({
17755
+ const [state, setState] = useState9({
16681
17756
  cursorOffset: (originalValue || "").length,
16682
17757
  cursorWidth: 0
16683
17758
  });
16684
17759
  const { cursorOffset, cursorWidth } = state;
16685
- useEffect17(() => {
17760
+ useEffect10(() => {
16686
17761
  setState((previousState) => {
16687
17762
  if (!focus || !showCursor) {
16688
17763
  return previousState;
@@ -17153,7 +18228,7 @@ var Item_default = Item;
17153
18228
 
17154
18229
  // node_modules/ink-select-input/build/SelectInput.js
17155
18230
  import { isDeepStrictEqual } from "util";
17156
- import React16, { useState as useState16, useEffect as useEffect18, useRef as useRef11, useCallback as useCallback10 } from "react";
18231
+ import React16, { useState as useState10, useEffect as useEffect11, useRef as useRef12, useCallback as useCallback11 } from "react";
17157
18232
 
17158
18233
  // node_modules/to-rotated/index.js
17159
18234
  function toRotated(array, steps) {
@@ -17183,17 +18258,17 @@ function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicator
17183
18258
  const hasLimit = typeof customLimit === "number" && items.length > customLimit;
17184
18259
  const limit = hasLimit ? Math.min(customLimit, items.length) : items.length;
17185
18260
  const lastIndex = limit - 1;
17186
- const [rotateIndex, setRotateIndex] = useState16(initialIndex > lastIndex ? lastIndex - initialIndex : 0);
17187
- const [selectedIndex, setSelectedIndex] = useState16(initialIndex ? initialIndex > lastIndex ? lastIndex : initialIndex : 0);
17188
- const previousItems = useRef11(items);
17189
- useEffect18(() => {
18261
+ const [rotateIndex, setRotateIndex] = useState10(initialIndex > lastIndex ? lastIndex - initialIndex : 0);
18262
+ const [selectedIndex, setSelectedIndex] = useState10(initialIndex ? initialIndex > lastIndex ? lastIndex : initialIndex : 0);
18263
+ const previousItems = useRef12(items);
18264
+ useEffect11(() => {
17190
18265
  if (!isDeepStrictEqual(previousItems.current.map((item) => item.value), items.map((item) => item.value))) {
17191
18266
  setRotateIndex(0);
17192
18267
  setSelectedIndex(0);
17193
18268
  }
17194
18269
  previousItems.current = items;
17195
18270
  }, [items]);
17196
- useInput12(useCallback10((input, key) => {
18271
+ useInput12(useCallback11((input, key) => {
17197
18272
  if (input === "k" || key.upArrow) {
17198
18273
  const lastIndex2 = (hasLimit ? limit : items.length) - 1;
17199
18274
  const atFirstIndex = selectedIndex === 0;
@@ -17354,8 +18429,8 @@ var DescriptionRenderer = (props) => {
17354
18429
  // node_modules/ink-form/lib/FormFieldRenderer.js
17355
18430
  var FormFieldRenderer = (props) => {
17356
18431
  const manager = getManager(props.field.type, props.customManagers);
17357
- const [error, setError] = useState17();
17358
- const [currentValue, setCurrentValue] = useState17(props.value ?? props.field.initialValue);
18432
+ const [error, setError] = useState11();
18433
+ const [currentValue, setCurrentValue] = useState11(props.value ?? props.field.initialValue);
17359
18434
  const isEditing = !!props.editingField && props.editingField === (props.field.label ?? props.field.name);
17360
18435
  const hide = !isEditing && !!props.editingField;
17361
18436
  const save = (newValue) => {
@@ -17513,20 +18588,20 @@ var SubmitButton = (props) => {
17513
18588
  // node_modules/ink-form/lib/Form.js
17514
18589
  var Form = (props) => {
17515
18590
  const isControlled = props.value !== void 0;
17516
- const [currentTab, setCurrentTab] = useState18(0);
17517
- const [value, setValue] = useState18(props.value ?? {});
17518
- const [editingField, setEditingField] = useState18();
18591
+ const [currentTab, setCurrentTab] = useState12(0);
18592
+ const [value, setValue] = useState12(props.value ?? {});
18593
+ const [editingField, setEditingField] = useState12();
17519
18594
  const canSubmitForm = useMemo3(() => canSubmit(props.form, value), [value, props.form]);
17520
18595
  const focusManager = useFocusManager();
17521
- useEffect19(() => {
18596
+ useEffect12(() => {
17522
18597
  focusManager.enableFocus();
17523
18598
  }, []);
17524
- useEffect19(() => {
18599
+ useEffect12(() => {
17525
18600
  if (props.value) {
17526
18601
  setValue(props.value);
17527
18602
  }
17528
18603
  }, [props.value]);
17529
- useEffect19(() => {
18604
+ useEffect12(() => {
17530
18605
  if (!isControlled) {
17531
18606
  setValueAndPropagate({
17532
18607
  ...value,
@@ -17567,179 +18642,159 @@ var Form = (props) => {
17567
18642
  import { render } from "ink";
17568
18643
  import React24 from "react";
17569
18644
 
17570
- // ../../core/json/nullableUnion.ts
17571
- var RENDERABLE_TYPES = [
17572
- "string",
17573
- "number",
17574
- "integer",
17575
- "boolean",
17576
- "array",
17577
- "object"
17578
- ];
17579
- function isRenderableType(type) {
17580
- return RENDERABLE_TYPES.includes(type);
18645
+ // src/utils/schemaToForm.ts
18646
+ function toSelectOptions(values, names) {
18647
+ const useNames = names !== void 0 && names.length === values.length;
18648
+ return values.map((val, index) => ({
18649
+ label: useNames ? names[index] : String(val),
18650
+ value: String(val)
18651
+ }));
17581
18652
  }
17582
- function toBranch(value) {
17583
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
17584
- return null;
17585
- }
17586
- return value;
18653
+ function generatedNames(base, branches) {
18654
+ const declared = [
18655
+ ...Object.keys(base.properties ?? {}),
18656
+ ...branches.flatMap((branch) => branch.declaredFields)
18657
+ ];
18658
+ let variant = "__variant";
18659
+ while (declared.includes(variant)) variant += "_";
18660
+ let prefix = "__b";
18661
+ while (declared.some((name) => name.startsWith(prefix))) prefix += "_";
18662
+ return { variant, prefix };
18663
+ }
18664
+ function branchFieldName(prefix, branchIndex, name) {
18665
+ return `${prefix}${branchIndex}__${name}`;
18666
+ }
18667
+ function sharedFieldNames(base, branches) {
18668
+ const declared = new Set(branches.flatMap((branch) => branch.declaredFields));
18669
+ return Object.keys(base.properties ?? {}).filter(
18670
+ (name) => declared.has(name)
18671
+ );
17587
18672
  }
17588
- function isStringEnum(value) {
17589
- return Array.isArray(value) && value.length > 0 && value.every((member) => typeof member === "string");
18673
+ function branchFields(base, branches, index) {
18674
+ const own = branches[index].declaredFields;
18675
+ return [.../* @__PURE__ */ new Set([...own, ...sharedFieldNames(base, branches)])];
17590
18676
  }
17591
- function nullExcludedBySiblings(schema) {
17592
- if (Array.isArray(schema.enum) && !schema.enum.includes(null)) {
17593
- return true;
18677
+ function labelled(property, name) {
18678
+ if (typeof property !== "object" || property === null) {
18679
+ return { title: name };
17594
18680
  }
17595
- return schema.const !== void 0 && schema.const !== null;
17596
- }
17597
- var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
17598
- "title",
17599
- "description",
17600
- "default",
17601
- "enumNames",
17602
- "examples",
17603
- "deprecated",
17604
- "readOnly",
17605
- "writeOnly",
17606
- "$comment",
17607
- "$schema",
17608
- "$id"
17609
- ]);
17610
- function wrapperCarriesConstraints(schema) {
17611
- return Object.keys(schema).some(
17612
- (key) => key !== "anyOf" && !ANNOTATION_KEYWORDS.has(key)
17613
- );
17614
- }
17615
- function typeNamesNull(type) {
17616
- return Array.isArray(type) ? type.includes("null") : type === "null";
18681
+ return { title: name, ...property };
17617
18682
  }
17618
- function hasOpaqueApplicator(schema) {
17619
- 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
17620
- // *alongside* its siblings — `{ type: "string", $ref: "#/$defs/intOnly" }`
17621
- // can be unsatisfiable. The wrapper path already declines on it (it is not
17622
- // an annotation); this is the same rule for every other position.
17623
- schema.$ref !== void 0;
18683
+ function requiredOf2(schema) {
18684
+ return Array.isArray(schema.required) ? schema.required.filter((name) => typeof name === "string") : [];
17624
18685
  }
17625
- function hasUnevaluatedComposition(schema) {
17626
- return schema.anyOf !== void 0 || hasOpaqueApplicator(schema);
18686
+ function constOf(schema) {
18687
+ if (typeof schema !== "object" || schema === null) return void 0;
18688
+ return schema.const;
17627
18689
  }
17628
- function stripNullEnumMembers(members, names) {
17629
- if (!Array.isArray(members)) {
17630
- return { kind: "unchanged" };
17631
- }
17632
- const kept = members.map((member, index) => ({ member, index })).filter((entry) => entry.member !== null);
17633
- if (kept.length === 0) {
17634
- return { kind: "unselectable" };
18690
+ function schemaToForm(schema, toolName) {
18691
+ const title = `Test Tool: ${toolName}`;
18692
+ if (!schema) {
18693
+ return { title, sections: [{ title: "Parameters", fields: [] }] };
17635
18694
  }
17636
- if (kept.length === members.length) {
17637
- return { kind: "unchanged" };
18695
+ const { base, branches } = resolveRootUnion(schema);
18696
+ if (branches.length === 0) {
18697
+ return {
18698
+ title,
18699
+ sections: [{ title: "Parameters", fields: buildFields(base) }]
18700
+ };
17638
18701
  }
17639
- const wasParallel = Array.isArray(names) && names.length === members.length;
17640
- const aligned = wasParallel ? kept.map((entry) => names[entry.index]) : void 0;
17641
- return {
17642
- kind: "filtered",
17643
- members: kept.map((entry) => entry.member),
17644
- names: aligned
17645
- };
17646
- }
17647
- function collapsed(schema, branch, type) {
17648
- const merged = { ...schema, ...branch };
17649
- const strip = stripNullEnumMembers(
17650
- merged.enum,
17651
- merged.enumNames
18702
+ const { variant, prefix } = generatedNames(base, branches);
18703
+ const shared = sharedFieldNames(base, branches);
18704
+ const baseProperties = Object.fromEntries(
18705
+ Object.entries(base.properties ?? {}).filter(
18706
+ ([name]) => !shared.includes(name)
18707
+ )
17652
18708
  );
17653
- if (strip.kind === "unselectable") {
17654
- return null;
17655
- }
17656
- return {
17657
- ...merged,
17658
- type,
17659
- anyOf: void 0,
17660
- // Read off the ORIGINAL schema, not the merge: a hoisted branch `enum` is
17661
- // not a sibling constraint, and treating it as one would mark the very
17662
- // shape #1928 is about as non-nullable. See `nullExcludedBySiblings`.
17663
- nullable: admitsNull(schema),
17664
- ...strip.kind === "filtered" ? { enum: strip.members, enumNames: strip.names } : {}
17665
- };
18709
+ const parameters = [
18710
+ {
18711
+ type: "select",
18712
+ name: variant,
18713
+ label: "Variant",
18714
+ required: true,
18715
+ initialValue: "0",
18716
+ options: branches.map((branch, index) => ({
18717
+ label: branch.label,
18718
+ value: String(index)
18719
+ }))
18720
+ },
18721
+ ...buildFields({ properties: baseProperties, required: base.required })
18722
+ ];
18723
+ const sections = [{ title: "Parameters", fields: parameters }];
18724
+ branches.forEach((branch, index) => {
18725
+ const byRealName = Object.fromEntries(
18726
+ branchFields(base, branches, index).map((name) => [name, true])
18727
+ );
18728
+ const properties = Object.fromEntries(
18729
+ branchFields(base, branches, index).map((name) => [
18730
+ branchFieldName(prefix, index, name),
18731
+ // The prefix is an internal field NAME, never a label: `buildFields`
18732
+ // falls back to its map key when a declaration carries no `title`, so
18733
+ // without this the form would show `__b0__address` where the schema
18734
+ // says `address`.
18735
+ labelled(branch.schema.properties?.[name], name)
18736
+ ])
18737
+ );
18738
+ sections.push({
18739
+ title: branch.label,
18740
+ fields: buildFields(
18741
+ {
18742
+ properties,
18743
+ // Under their prefixed names, so the constant seeding below can tell
18744
+ // which of this branch's fields the schema actually requires.
18745
+ required: requiredOf2(branch.schema).filter((name) => Object.hasOwn(byRealName, name)).map((name) => branchFieldName(prefix, index, name))
18746
+ },
18747
+ { optional: true }
18748
+ )
18749
+ });
18750
+ });
18751
+ return { title, sections };
17666
18752
  }
17667
- function admitsNull(schema) {
17668
- if (nullExcludedBySiblings(schema)) {
17669
- return false;
17670
- }
17671
- if (hasOpaqueApplicator(schema)) {
17672
- return false;
17673
- }
17674
- if (schema.nullable === true) {
17675
- return true;
17676
- }
17677
- if (schema.type !== void 0) {
17678
- if (schema.anyOf !== void 0) {
17679
- return false;
17680
- }
17681
- return typeNamesNull(schema.type);
18753
+ function decodeFormValues(schema, values) {
18754
+ const { base, branches } = resolveRootUnion(schema ?? {});
18755
+ if (branches.length === 0) {
18756
+ return applyConstants(base.properties ?? {}, values, requiredOf2(base));
17682
18757
  }
17683
- return schema.anyOf?.some((entry) => {
17684
- const branch = toBranch(entry);
17685
- if (branch === null || !typeNamesNull(branch.type)) {
17686
- return false;
18758
+ const { variant, prefix } = generatedNames(base, branches);
18759
+ const branchIndex = selectedBranchIndex(base, branches, values);
18760
+ const branch = branches[branchIndex];
18761
+ const generated = /* @__PURE__ */ new Set([variant]);
18762
+ branches.forEach((_each, index) => {
18763
+ for (const name of branchFields(base, branches, index)) {
18764
+ generated.add(branchFieldName(prefix, index, name));
17687
18765
  }
17688
- const branchSchema = branch;
17689
- return !nullExcludedBySiblings(branchSchema) && !hasUnevaluatedComposition(branchSchema);
17690
- }) ?? false;
18766
+ });
18767
+ const decoded = Object.fromEntries([
18768
+ ...Object.entries(values).filter(([name]) => !generated.has(name)),
18769
+ ...branchFields(base, branches, branchIndex).map(
18770
+ (name) => [name, values[branchFieldName(prefix, branchIndex, name)]]
18771
+ ).filter(([, value]) => value !== void 0)
18772
+ ]);
18773
+ return applyConstants(
18774
+ branch.schema.properties ?? {},
18775
+ decoded,
18776
+ requiredOf2(branch.schema)
18777
+ );
17691
18778
  }
17692
- function normalizeNullableUnion(schema) {
17693
- if (schema.anyOf?.length === 2) {
17694
- const branches = schema.anyOf.map(toBranch);
17695
- const nullBranch = branches.find((entry) => entry?.type === "null");
17696
- const branch = branches.find(
17697
- (entry) => entry !== null && entry.type !== "null"
17698
- );
17699
- if (nullBranch && branch && !wrapperCarriesConstraints(schema) && !hasUnevaluatedComposition(branch)) {
17700
- const type = branch.type ?? (isStringEnum(branch.enum) ? "string" : void 0);
17701
- if (isRenderableType(type)) {
17702
- const result = collapsed(schema, branch, type);
17703
- if (result !== null) {
17704
- return result;
17705
- }
17706
- }
17707
- }
17708
- }
17709
- 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`
17710
- // and the renderers ignore the other applicators — so a compound schema
17711
- // like `{ type: ["string", "null"], anyOf: [{ const: "a" }] }` would be
17712
- // widened into an unconstrained string field. Leave it for the JSON editor.
17713
- !hasUnevaluatedComposition(schema)) {
17714
- const type = schema.type.find((member) => member !== "null");
17715
- if (isRenderableType(type)) {
17716
- const result = collapsed(schema, void 0, type);
17717
- if (result !== null) {
17718
- return result;
17719
- }
17720
- }
17721
- }
17722
- return schema;
18779
+ function selectedBranchIndex(base, branches, values) {
18780
+ const { variant } = generatedNames(base, branches);
18781
+ const selected = Number(values[variant]);
18782
+ return Number.isInteger(selected) && selected >= 0 && selected < branches.length ? selected : 0;
17723
18783
  }
17724
-
17725
- // src/utils/schemaToForm.ts
17726
- function toSelectOptions(values, names) {
17727
- const useNames = names !== void 0 && names.length === values.length;
17728
- return values.map((val, index) => ({
17729
- label: useNames ? names[index] : String(val),
17730
- value: String(val)
17731
- }));
18784
+ function applyConstants(properties, values, required) {
18785
+ const pinned = Object.entries(properties).filter(
18786
+ ([name, schema]) => constOf(schema) !== void 0 && (required.includes(name) || Object.hasOwn(values, name) && values[name] !== void 0)
18787
+ );
18788
+ if (pinned.length === 0) return values;
18789
+ return Object.fromEntries([
18790
+ ...Object.entries(values),
18791
+ ...pinned.map(([name, schema]) => [name, constOf(schema)])
18792
+ ]);
17732
18793
  }
17733
- function schemaToForm(schema, toolName) {
18794
+ function buildFields(schema, { optional = false } = {}) {
17734
18795
  const fields = [];
17735
- if (!schema || !schema.properties) {
17736
- return {
17737
- title: `Test Tool: ${toolName}`,
17738
- sections: [{ title: "Parameters", fields: [] }]
17739
- };
17740
- }
17741
18796
  const properties = schema.properties || {};
17742
- const required = schema.required || [];
18797
+ const required = Array.isArray(schema.required) ? schema.required : [];
17743
18798
  for (const [key, prop] of Object.entries(properties)) {
17744
18799
  const property = normalizeNullableUnion(
17745
18800
  typeof prop === "object" && prop !== null ? prop : {}
@@ -17747,9 +18802,30 @@ function schemaToForm(schema, toolName) {
17747
18802
  const baseField = {
17748
18803
  name: key,
17749
18804
  label: property.title || key,
17750
- required: required.includes(key)
18805
+ required: optional ? false : required.includes(key)
17751
18806
  };
17752
18807
  let field;
18808
+ const pinned = property.const;
18809
+ if (pinned !== void 0) {
18810
+ fields.push({
18811
+ type: "select",
18812
+ ...baseField,
18813
+ // Only a REQUIRED constant is pre-filled. `const` constrains a present
18814
+ // value; it neither requires the property nor acts as a default, so an
18815
+ // optional `dryRun: { const: true }` must stay omittable rather than
18816
+ // being sent on every call. The single option is still there for a user
18817
+ // who wants it.
18818
+ ...required.includes(key) ? { initialValue: String(pinned) } : {},
18819
+ // Never required: the one option may legitimately be the empty string,
18820
+ // which ink-form's required gate can never accept — submission would
18821
+ // not even reach `decodeFormValues`, which reapplies the constant. The
18822
+ // value is fixed by the schema, and `missingRequiredFields` still
18823
+ // validates the decoded call.
18824
+ required: false,
18825
+ options: [{ label: String(pinned), value: String(pinned) }]
18826
+ });
18827
+ continue;
18828
+ }
17753
18829
  if (property.type === "array" && property.items?.enum) {
17754
18830
  field = {
17755
18831
  type: "select",
@@ -17760,6 +18836,7 @@ function schemaToForm(schema, toolName) {
17760
18836
  field = {
17761
18837
  type: "select",
17762
18838
  ...baseField,
18839
+ /* v8 ignore next -- guarded by `isStringEnum(property.enum)` above */
17763
18840
  options: toSelectOptions(property.enum ?? [], property.enumNames)
17764
18841
  };
17765
18842
  } else {
@@ -17804,16 +18881,25 @@ function schemaToForm(schema, toolName) {
17804
18881
  }
17805
18882
  fields.push(field);
17806
18883
  }
17807
- const sections = [
17808
- {
17809
- title: "Parameters",
17810
- fields
17811
- }
17812
- ];
17813
- return {
17814
- title: `Test Tool: ${toolName}`,
17815
- sections
17816
- };
18884
+ return fields;
18885
+ }
18886
+ function missingRequiredFields(schema, decoded, rawValues = {}) {
18887
+ const { base, branches } = resolveRootUnion(schema ?? {});
18888
+ const effective = branches.length === 0 ? base : branches[selectedBranchIndex(base, branches, rawValues)].schema;
18889
+ const properties = effective.properties ?? {};
18890
+ const required = Array.isArray(effective.required) ? effective.required : [];
18891
+ return required.filter((name) => {
18892
+ if (!Object.hasOwn(decoded, name)) return true;
18893
+ const value = decoded[name];
18894
+ if (value === null) {
18895
+ const property = properties[name];
18896
+ return typeof property !== "object" || property === null || !admitsNull(property);
18897
+ }
18898
+ if (value === "") {
18899
+ return constOf(properties[name]) !== "";
18900
+ }
18901
+ return value === void 0;
18902
+ });
17817
18903
  }
17818
18904
 
17819
18905
  // src/components/ToolTestModal.tsx
@@ -17826,8 +18912,8 @@ function ToolTestModal({
17826
18912
  onClose,
17827
18913
  onAuthRecoveryRequired
17828
18914
  }) {
17829
- const [state, setState] = useState19("form");
17830
- const [result, setResult] = useState19(null);
18915
+ const [state, setState] = useState13("form");
18916
+ const [result, setResult] = useState13(null);
17831
18917
  const scrollViewRef = React25.useRef(null);
17832
18918
  const [terminalDimensions, setTerminalDimensions] = React25.useState({
17833
18919
  width: process.stdout.columns || width,
@@ -17883,8 +18969,20 @@ function ToolTestModal({
17883
18969
  },
17884
18970
  { isActive: true }
17885
18971
  );
17886
- const handleFormSubmit = async (values) => {
18972
+ const handleFormSubmit = async (rawValues) => {
17887
18973
  if (!inspectorClient || !tool) return;
18974
+ const values = decodeFormValues(tool.inputSchema, rawValues);
18975
+ const missing = missingRequiredFields(tool.inputSchema, values, rawValues);
18976
+ if (missing.length > 0) {
18977
+ setResult({
18978
+ input: values,
18979
+ output: null,
18980
+ error: `Missing required argument${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
18981
+ duration: 0
18982
+ });
18983
+ setState("results");
18984
+ return;
18985
+ }
17888
18986
  setState("loading");
17889
18987
  const startTime = Date.now();
17890
18988
  try {
@@ -17996,7 +19094,7 @@ function ToolTestModal({
17996
19094
  }
17997
19095
 
17998
19096
  // src/components/ResourceTestModal.tsx
17999
- import React26, { useState as useState20 } from "react";
19097
+ import React26, { useState as useState14 } from "react";
18000
19098
  import { Box as Box24, Text as Text21, useInput as useInput18 } from "ink";
18001
19099
 
18002
19100
  // src/utils/uriTemplateToForm.ts
@@ -18051,8 +19149,8 @@ function ResourceTestModal({
18051
19149
  onClose,
18052
19150
  onAuthRecoveryRequired
18053
19151
  }) {
18054
- const [state, setState] = useState20("form");
18055
- const [result, setResult] = useState20(null);
19152
+ const [state, setState] = useState14("form");
19153
+ const [result, setResult] = useState14(null);
18056
19154
  const scrollViewRef = React26.useRef(null);
18057
19155
  const [terminalDimensions, setTerminalDimensions] = React26.useState({
18058
19156
  width: process.stdout.columns || width,
@@ -18255,7 +19353,7 @@ function ResourceTestModal({
18255
19353
  }
18256
19354
 
18257
19355
  // src/components/PromptTestModal.tsx
18258
- import React27, { useState as useState21 } from "react";
19356
+ import React27, { useState as useState15 } from "react";
18259
19357
  import { Box as Box25, Text as Text22, useInput as useInput19 } from "ink";
18260
19358
 
18261
19359
  // src/utils/promptArgsToForm.ts
@@ -18313,8 +19411,8 @@ function PromptTestModal({
18313
19411
  onClose,
18314
19412
  onAuthRecoveryRequired
18315
19413
  }) {
18316
- const [state, setState] = useState21("form");
18317
- const [result, setResult] = useState21(null);
19414
+ const [state, setState] = useState15("form");
19415
+ const [result, setResult] = useState15(null);
18318
19416
  const scrollViewRef = React27.useRef(null);
18319
19417
  const [terminalDimensions, setTerminalDimensions] = React27.useState({
18320
19418
  width: process.stdout.columns || width,
@@ -18483,7 +19581,7 @@ function PromptTestModal({
18483
19581
  }
18484
19582
 
18485
19583
  // src/components/DetailsModal.tsx
18486
- import React28, { useRef as useRef12 } from "react";
19584
+ import React28, { useRef as useRef13 } from "react";
18487
19585
  import { Box as Box26, Text as Text23, useInput as useInput20 } from "ink";
18488
19586
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
18489
19587
  function DetailsModal({
@@ -18493,7 +19591,7 @@ function DetailsModal({
18493
19591
  height,
18494
19592
  onClose
18495
19593
  }) {
18496
- const scrollViewRef = useRef12(null);
19594
+ const scrollViewRef = useRef13(null);
18497
19595
  const [terminalDimensions, setTerminalDimensions] = React28.useState({
18498
19596
  width: process.stdout.columns || width,
18499
19597
  height: process.stdout.rows || height
@@ -18580,49 +19678,52 @@ function App({
18580
19678
  callbackUrlConfig
18581
19679
  }) {
18582
19680
  const { exit } = useApp();
18583
- useEffect20(() => {
19681
+ useEffect13(() => {
18584
19682
  getTuiLogger().info(
18585
19683
  { serverNames: Object.keys(mcpServers) },
18586
19684
  "TUI started"
18587
19685
  );
18588
19686
  }, [mcpServers]);
18589
- const [selectedServer, setSelectedServer] = useState23(null);
18590
- const [activeTab, setActiveTab] = useState23("info");
18591
- const [focus, setFocus] = useState23("serverList");
18592
- const [tabCounts, setTabCounts] = useState23({});
18593
- const [oauthStatus, setOauthStatus] = useState23("idle");
18594
- const [oauthMessage, setOauthMessage] = useState23(null);
18595
- const [oauthRevision, setOauthRevision] = useState23(0);
18596
- const [pendingStepUp, setPendingStepUp] = useState23(null);
18597
- const pendingStepUpRef = useRef13(pendingStepUp);
18598
- useEffect20(() => {
19687
+ const [selectedServer, setSelectedServer] = useState16(null);
19688
+ const [activeTab, setActiveTab] = useState16("info");
19689
+ const [focus, setFocus] = useState16("serverList");
19690
+ const [tabCounts, setTabCounts] = useState16({});
19691
+ const [oauthStatus, setOauthStatus] = useState16("idle");
19692
+ const [oauthMessage, setOauthMessage] = useState16(null);
19693
+ const [oauthWarningText, setOauthWarningText] = useState16(null);
19694
+ const oauthMessageTone = oauthMessageToneFor(oauthMessage, oauthWarningText);
19695
+ const [oauthRevision, setOauthRevision] = useState16(0);
19696
+ const [pendingStepUp, setPendingStepUp] = useState16(null);
19697
+ const pendingStepUpRef = useRef14(pendingStepUp);
19698
+ useEffect13(() => {
18599
19699
  pendingStepUpRef.current = pendingStepUp;
18600
19700
  }, [pendingStepUp]);
18601
- const [connectError, setConnectError] = useState23(null);
18602
- const disconnectAttemptRef = useRef13(0);
18603
- const [disconnectError, setDisconnectError] = useState23(null);
18604
- const oauthInProgressRef = useRef13(false);
18605
- const callbackServerRef = useRef13(null);
18606
- const selectedServerRef = useRef13(null);
18607
- const mcpServersRef = useRef13(mcpServers);
18608
- const inspectorClientsRef = useRef13({});
18609
- const [toolTestModal, setToolTestModal] = useState23(null);
18610
- const [resourceTestModal, setResourceTestModal] = useState23(null);
18611
- const [promptTestModal, setPromptTestModal] = useState23(null);
18612
- const [detailsModal, setDetailsModal] = useState23(null);
18613
- const [inspectorClients, setInspectorClients] = useState23({});
18614
- const [managedToolsStates, setManagedToolsStates] = useState23({});
18615
- const [managedResourcesStates, setManagedResourcesStates] = useState23({});
18616
- const [managedResourceTemplatesStates, setManagedResourceTemplatesStates] = useState23({});
18617
- const [managedPromptsStates, setManagedPromptsStates] = useState23({});
18618
- const [messageLogStates, setMessageLogStates] = useState23({});
18619
- const [fetchRequestLogStates, setFetchRequestLogStates] = useState23({});
18620
- const [stderrLogStates, setStderrLogStates] = useState23({});
18621
- const [dimensions, setDimensions] = useState23({
19701
+ const [connectError, setConnectError] = useState16(null);
19702
+ const disconnectAttemptRef = useRef14(0);
19703
+ const clearOAuthAttemptRef = useRef14(0);
19704
+ const [disconnectError, setDisconnectError] = useState16(null);
19705
+ const oauthInProgressRef = useRef14(false);
19706
+ const callbackServerRef = useRef14(null);
19707
+ const selectedServerRef = useRef14(null);
19708
+ const mcpServersRef = useRef14(mcpServers);
19709
+ const inspectorClientsRef = useRef14({});
19710
+ const [toolTestModal, setToolTestModal] = useState16(null);
19711
+ const [resourceTestModal, setResourceTestModal] = useState16(null);
19712
+ const [promptTestModal, setPromptTestModal] = useState16(null);
19713
+ const [detailsModal, setDetailsModal] = useState16(null);
19714
+ const [inspectorClients, setInspectorClients] = useState16({});
19715
+ const [managedToolsStates, setManagedToolsStates] = useState16({});
19716
+ const [managedResourcesStates, setManagedResourcesStates] = useState16({});
19717
+ const [managedResourceTemplatesStates, setManagedResourceTemplatesStates] = useState16({});
19718
+ const [managedPromptsStates, setManagedPromptsStates] = useState16({});
19719
+ const [messageLogStates, setMessageLogStates] = useState16({});
19720
+ const [fetchRequestLogStates, setFetchRequestLogStates] = useState16({});
19721
+ const [stderrLogStates, setStderrLogStates] = useState16({});
19722
+ const [dimensions, setDimensions] = useState16({
18622
19723
  width: process.stdout.columns || 80,
18623
19724
  height: process.stdout.rows || 24
18624
19725
  });
18625
- useEffect20(() => {
19726
+ useEffect13(() => {
18626
19727
  const updateDimensions = () => {
18627
19728
  setDimensions({
18628
19729
  width: process.stdout.columns || 80,
@@ -18637,8 +19738,8 @@ function App({
18637
19738
  const serverNames = Object.keys(mcpServers);
18638
19739
  const selectedServerEntry = selectedServer ? mcpServers[selectedServer] : null;
18639
19740
  const selectedServerConfig = selectedServerEntry?.config ?? null;
18640
- const redirectUrlProvidersRef = useRef13({});
18641
- useEffect20(() => {
19741
+ const redirectUrlProvidersRef = useRef14({});
19742
+ useEffect13(() => {
18642
19743
  const newClients = {};
18643
19744
  const newManagers = {};
18644
19745
  const newManagedResourcesStates = {};
@@ -18742,7 +19843,7 @@ function App({
18742
19843
  clientMetadataUrl,
18743
19844
  callbackUrlConfig
18744
19845
  ]);
18745
- useEffect20(() => {
19846
+ useEffect13(() => {
18746
19847
  return () => {
18747
19848
  void callbackServerRef.current?.stop();
18748
19849
  callbackServerRef.current = null;
@@ -18782,27 +19883,28 @@ function App({
18782
19883
  fetchRequestLogStates,
18783
19884
  stderrLogStates
18784
19885
  ]);
18785
- useEffect20(() => {
19886
+ useEffect13(() => {
18786
19887
  if (serverNames.length > 0 && selectedServer === null) {
18787
19888
  setSelectedServer(serverNames[0]);
18788
19889
  }
18789
19890
  }, []);
18790
- useEffect20(() => {
19891
+ useEffect13(() => {
18791
19892
  setOauthStatus("idle");
18792
19893
  setOauthMessage(null);
18793
19894
  disconnectAttemptRef.current++;
19895
+ clearOAuthAttemptRef.current++;
18794
19896
  setDisconnectError(null);
18795
19897
  const stepUp = pendingStepUpRef.current;
18796
19898
  if (stepUp && selectedServer && stepUp.serverName !== selectedServer) {
18797
19899
  setPendingStepUp(null);
18798
19900
  }
18799
19901
  }, [selectedServer]);
18800
- useEffect20(() => {
19902
+ useEffect13(() => {
18801
19903
  selectedServerRef.current = selectedServer;
18802
19904
  mcpServersRef.current = mcpServers;
18803
19905
  inspectorClientsRef.current = inspectorClients;
18804
19906
  }, [selectedServer, mcpServers, inspectorClients]);
18805
- useEffect20(() => {
19907
+ useEffect13(() => {
18806
19908
  if (activeTab === "auth" && selectedServerConfig && !isOAuthCapableServerConfig(selectedServerConfig)) {
18807
19909
  setActiveTab("info");
18808
19910
  }
@@ -18873,7 +19975,7 @@ function App({
18873
19975
  selectedInspectorClient,
18874
19976
  selectedManagedPromptsState
18875
19977
  );
18876
- const runOAuthAuthentication = useCallback11(
19978
+ const runOAuthAuthentication = useCallback12(
18877
19979
  async (options) => {
18878
19980
  const serverName = options?.serverName ?? selectedServer;
18879
19981
  if (!serverName) {
@@ -18932,7 +20034,7 @@ function App({
18932
20034
  },
18933
20035
  [selectedServer, callbackUrlConfig]
18934
20036
  );
18935
- const presentStepUpForServer = useCallback11(
20037
+ const presentStepUpForServer = useCallback12(
18936
20038
  (serverName, challenge, authorizationUrl, enterpriseManaged) => {
18937
20039
  const pending = pendingStepUpRef.current;
18938
20040
  if (pending && pending.serverName !== serverName) {
@@ -18961,7 +20063,7 @@ function App({
18961
20063
  },
18962
20064
  []
18963
20065
  );
18964
- const handleAuthRecoveryRequired = useCallback11(
20066
+ const handleAuthRecoveryRequired = useCallback12(
18965
20067
  (serverName, error) => {
18966
20068
  const serverEntry = mcpServersRef.current[serverName];
18967
20069
  const settings = serverEntry?.settings;
@@ -19024,7 +20126,7 @@ function App({
19024
20126
  },
19025
20127
  [presentStepUpForServer, runOAuthAuthentication]
19026
20128
  );
19027
- const onAuthRecoveryRequired = useCallback11(
20129
+ const onAuthRecoveryRequired = useCallback12(
19028
20130
  (error) => {
19029
20131
  if (selectedServer) {
19030
20132
  handleAuthRecoveryRequired(selectedServer, error);
@@ -19032,11 +20134,12 @@ function App({
19032
20134
  },
19033
20135
  [selectedServer, handleAuthRecoveryRequired]
19034
20136
  );
19035
- const handleConnect = useCallback11(async () => {
20137
+ const handleConnect = useCallback12(async () => {
19036
20138
  if (!selectedServer || !selectedInspectorClient || !selectedServerConfig) {
19037
20139
  return;
19038
20140
  }
19039
20141
  disconnectAttemptRef.current++;
20142
+ clearOAuthAttemptRef.current++;
19040
20143
  setDisconnectError(null);
19041
20144
  const finishConnect = async () => {
19042
20145
  await connectInspector();
@@ -19098,7 +20201,7 @@ function App({
19098
20201
  runOAuthAuthentication,
19099
20202
  handleAuthRecoveryRequired
19100
20203
  ]);
19101
- useEffect20(() => {
20204
+ useEffect13(() => {
19102
20205
  const cleanups = [];
19103
20206
  for (const [serverName, client] of Object.entries(inspectorClients)) {
19104
20207
  const onAmbient = () => {
@@ -19143,7 +20246,7 @@ function App({
19143
20246
  }
19144
20247
  };
19145
20248
  }, [inspectorClients, handleAuthRecoveryRequired]);
19146
- const handleDisconnect = useCallback11(async () => {
20249
+ const handleDisconnect = useCallback12(async () => {
19147
20250
  if (!selectedServer) return;
19148
20251
  const attempt = ++disconnectAttemptRef.current;
19149
20252
  const attemptServer = selectedServer;
@@ -19157,17 +20260,43 @@ function App({
19157
20260
  setDisconnectError(err instanceof Error ? err.message : String(err));
19158
20261
  }
19159
20262
  }, [selectedServer, disconnectInspector]);
19160
- const handleClearOAuth = useCallback11(async () => {
20263
+ const handleClearOAuth = useCallback12(async () => {
19161
20264
  if (!selectedInspectorClient) return;
19162
- await selectedInspectorClient.clearOAuthTokens();
20265
+ const attempt = ++clearOAuthAttemptRef.current;
20266
+ const attemptServer = selectedServer;
20267
+ const revocation = await selectedInspectorClient.clearOAuthTokens({
20268
+ revoke: selectedServerEntry?.settings?.oauthRevokeOnClear !== false
20269
+ });
20270
+ if (clearOAuthAttemptRef.current !== attempt || selectedServerRef.current !== attemptServer) {
20271
+ return;
20272
+ }
19163
20273
  setOauthStatus("idle");
19164
- setOauthMessage(null);
20274
+ if (revocation.status === "failed") {
20275
+ const warning = `Cleared locally, but revoking the grant at the authorization server failed: ${revocation.detail}. It may still be valid there.`;
20276
+ setOauthWarningText(warning);
20277
+ setOauthMessage(warning);
20278
+ } else {
20279
+ setOauthMessage(null);
20280
+ }
19165
20281
  setConnectError(null);
19166
20282
  if (inspectorStatus === "connected" || inspectorStatus === "connecting") {
19167
- await disconnectInspector();
20283
+ try {
20284
+ await disconnectInspector();
20285
+ } catch (err) {
20286
+ setDisconnectError(err instanceof Error ? err.message : String(err));
20287
+ }
20288
+ if (clearOAuthAttemptRef.current !== attempt || selectedServerRef.current !== attemptServer) {
20289
+ return;
20290
+ }
19168
20291
  }
19169
20292
  setOauthRevision((n) => n + 1);
19170
- }, [selectedInspectorClient, inspectorStatus, disconnectInspector]);
20293
+ }, [
20294
+ selectedInspectorClient,
20295
+ selectedServer,
20296
+ selectedServerEntry,
20297
+ inspectorStatus,
20298
+ disconnectInspector
20299
+ ]);
19171
20300
  const currentServerState = useMemo4(() => {
19172
20301
  if (!selectedServer) return null;
19173
20302
  return {
@@ -19379,7 +20508,7 @@ function App({
19379
20508
  /* @__PURE__ */ jsx16(Box27, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Text24, { dimColor: true, children: JSON.stringify(message.message, null, 2) }) })
19380
20509
  ] })
19381
20510
  ] });
19382
- useEffect20(() => {
20511
+ useEffect13(() => {
19383
20512
  if (!selectedServer) {
19384
20513
  return;
19385
20514
  }
@@ -19400,7 +20529,7 @@ function App({
19400
20529
  inspectorFetchRequests,
19401
20530
  inspectorStderrLogs
19402
20531
  ]);
19403
- useEffect20(() => {
20532
+ useEffect13(() => {
19404
20533
  if (activeTab === "messages") {
19405
20534
  if (focus === "tabContentList" || focus === "tabContentDetails") {
19406
20535
  setFocus("messagesList");
@@ -19415,7 +20544,7 @@ function App({
19415
20544
  }
19416
20545
  }
19417
20546
  }, [activeTab]);
19418
- useEffect20(() => {
20547
+ useEffect13(() => {
19419
20548
  if (activeTab === "logging" && selectedServer) {
19420
20549
  const client = inspectorClients[selectedServer];
19421
20550
  if (client && client.getServerType() !== "stdio") {
@@ -19744,6 +20873,7 @@ function App({
19744
20873
  inspectorClient: selectedInspectorClient,
19745
20874
  oauthStatus,
19746
20875
  oauthMessage,
20876
+ oauthMessageTone,
19747
20877
  oauthRevision,
19748
20878
  pendingStepUp: pendingStepUp?.serverName === selectedServer ? {
19749
20879
  challenge: pendingStepUp.challenge,
@@ -19829,9 +20959,7 @@ function App({
19829
20959
  width: contentWidth,
19830
20960
  height: contentHeight,
19831
20961
  focused: focus === "tabContentList" || focus === "tabContentDetails",
19832
- onClearOAuth: () => {
19833
- void handleClearOAuth();
19834
- },
20962
+ onClearOAuth: handleClearOAuth,
19835
20963
  connectionStatus: inspectorStatus
19836
20964
  }
19837
20965
  ) : null,