@modelcontextprotocol/inspector 2.7.0 → 2.8.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.
@@ -788,6 +788,176 @@ var SECRET_FIELD_OAUTH_CLIENT_SECRET = "oauth-client-secret";
788
788
  var SECRET_FIELD_IDP_CLIENT_SECRET = "idp-client-secret";
789
789
  var envSecretField = (envKey) => `env:${envKey}`;
790
790
 
791
+ // ../../core/json/localRefs.ts
792
+ var SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
793
+ "items",
794
+ "contains",
795
+ "not",
796
+ "propertyNames",
797
+ "if",
798
+ "then",
799
+ "else",
800
+ "additionalProperties",
801
+ "unevaluatedProperties",
802
+ "additionalItems",
803
+ "unevaluatedItems",
804
+ "contentSchema"
805
+ ]);
806
+ var SUBSCHEMA_ARRAY_KEYWORDS = /* @__PURE__ */ new Set([
807
+ "allOf",
808
+ "anyOf",
809
+ "oneOf",
810
+ "prefixItems"
811
+ ]);
812
+ var SUBSCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
813
+ "properties",
814
+ "patternProperties",
815
+ "dependentSchemas",
816
+ // Pre-2019 spelling of `dependentSchemas`; its array values are property
817
+ // name lists, which are not schemas and pass through untouched.
818
+ "dependencies",
819
+ "$defs",
820
+ "definitions"
821
+ ]);
822
+ var NON_CONSTRAINT_SIBLINGS = /* @__PURE__ */ new Set([
823
+ "title",
824
+ "description",
825
+ "default",
826
+ "examples",
827
+ "deprecated",
828
+ "readOnly",
829
+ "writeOnly",
830
+ // Non-standard labels for `enum` values, read by both form builders.
831
+ "enumNames",
832
+ "$comment",
833
+ "$schema",
834
+ "$id",
835
+ "$defs",
836
+ "definitions"
837
+ ]);
838
+ var EXPANSION_BUDGET = 1e4;
839
+ var MAX_DEPTH = 64;
840
+ var Bail = class extends Error {
841
+ };
842
+ function spend(traversal) {
843
+ traversal.remaining -= 1;
844
+ if (traversal.remaining < 0) throw new Bail();
845
+ }
846
+ function mapSubschemas(node, visit) {
847
+ const result = {};
848
+ for (const [key, value] of Object.entries(node)) {
849
+ let next = value;
850
+ if (SUBSCHEMA_KEYWORDS.has(key)) {
851
+ next = Array.isArray(value) ? value.map(visit) : visit(value);
852
+ } else if (SUBSCHEMA_ARRAY_KEYWORDS.has(key) && Array.isArray(value)) {
853
+ next = value.map(visit);
854
+ } else if (SUBSCHEMA_MAP_KEYWORDS.has(key) && isRecord(value)) {
855
+ next = mapNames(value, visit);
856
+ }
857
+ define(result, key, next);
858
+ }
859
+ return result;
860
+ }
861
+ function mapNames(map, visit) {
862
+ const result = {};
863
+ for (const [name, value] of Object.entries(map)) {
864
+ define(result, name, Array.isArray(value) ? value : visit(value));
865
+ }
866
+ return result;
867
+ }
868
+ function define(target, key, value) {
869
+ Object.defineProperty(target, key, {
870
+ value,
871
+ writable: true,
872
+ enumerable: true,
873
+ configurable: true
874
+ });
875
+ }
876
+ function isRecord(value) {
877
+ return typeof value === "object" && value !== null && !Array.isArray(value);
878
+ }
879
+ function resolvePointer(root, ref) {
880
+ if (!ref.startsWith("#")) return void 0;
881
+ let pointer;
882
+ try {
883
+ pointer = decodeURIComponent(ref.slice(1));
884
+ } catch {
885
+ return void 0;
886
+ }
887
+ if (pointer === "") return root;
888
+ if (!pointer.startsWith("/")) return void 0;
889
+ let current = root;
890
+ for (const token of pointer.slice(1).split("/")) {
891
+ if (/~(?![01])/.test(token)) return void 0;
892
+ const segment = token.replace(/~1/g, "/").replace(/~0/g, "~");
893
+ if (Array.isArray(current)) {
894
+ const index = Number(segment);
895
+ if (!/^(0|[1-9]\d*)$/.test(segment) || index >= current.length) {
896
+ return void 0;
897
+ }
898
+ current = current[index];
899
+ } else if (isRecord(current) && Object.hasOwn(current, segment)) {
900
+ current = current[segment];
901
+ } else {
902
+ return void 0;
903
+ }
904
+ }
905
+ return current;
906
+ }
907
+ function containsRef(node, depth) {
908
+ if (depth > MAX_DEPTH) throw new Bail();
909
+ if (!isRecord(node)) return false;
910
+ if (typeof node.$ref === "string") return true;
911
+ let found = false;
912
+ mapSubschemas(node, (child) => {
913
+ found ||= containsRef(child, depth + 1);
914
+ return child;
915
+ });
916
+ return found;
917
+ }
918
+ function inline(node, traversal, depth) {
919
+ if (depth > MAX_DEPTH) throw new Bail();
920
+ if (!isRecord(node)) return node;
921
+ if (node !== traversal.root && typeof node.$id === "string") return node;
922
+ spend(traversal);
923
+ const visit = (child) => inline(child, traversal, depth + 1);
924
+ const ref = node.$ref;
925
+ const siblingsConstrain = Object.keys(node).some(
926
+ (key) => key !== "$ref" && !NON_CONSTRAINT_SIBLINGS.has(key)
927
+ );
928
+ if (typeof ref === "string" && !siblingsConstrain && !traversal.active.has(ref)) {
929
+ const target = resolvePointer(traversal.root, ref);
930
+ if (isRecord(target)) {
931
+ traversal.active.add(ref);
932
+ const resolved2 = visit(target);
933
+ traversal.active.delete(ref);
934
+ const siblings = { ...node };
935
+ delete siblings.$ref;
936
+ return { ...resolved2, ...mapSubschemas(siblings, visit) };
937
+ }
938
+ }
939
+ return mapSubschemas(node, visit);
940
+ }
941
+ var cache = /* @__PURE__ */ new WeakMap();
942
+ function inlineLocalRefs(schema) {
943
+ if (!isRecord(schema)) return schema;
944
+ const cached = cache.get(schema);
945
+ if (cached !== void 0) return cached;
946
+ let resolved2;
947
+ try {
948
+ resolved2 = containsRef(schema, 0) ? inline(
949
+ schema,
950
+ { root: schema, active: /* @__PURE__ */ new Set(), remaining: EXPANSION_BUDGET },
951
+ 0
952
+ ) : schema;
953
+ } catch (error) {
954
+ if (!(error instanceof Bail)) throw error;
955
+ resolved2 = schema;
956
+ }
957
+ cache.set(schema, resolved2);
958
+ return resolved2;
959
+ }
960
+
791
961
  // ../../core/json/nullableUnion.ts
792
962
  var RENDERABLE_TYPES = [
793
963
  "string",
@@ -1386,7 +1556,7 @@ function convertToolParameters(tool, params) {
1386
1556
  function convertParametersForSchema(inputSchema, params) {
1387
1557
  const result = {};
1388
1558
  const { base, branches } = resolveRootUnion(
1389
- inputSchema ?? {}
1559
+ inlineLocalRefs(inputSchema ?? {})
1390
1560
  );
1391
1561
  const properties = coercionProperties(base, branches, params);
1392
1562
  for (const [key, value] of Object.entries(params)) {
@@ -1578,7 +1748,7 @@ function oauthEndpointOverridesFromSettings(settings) {
1578
1748
  };
1579
1749
  }
1580
1750
  function storedFieldsToInspectorSettings(stored) {
1581
- const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
1751
+ const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.suppressNotificationStream !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
1582
1752
  if (!hasAny) return void 0;
1583
1753
  const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
1584
1754
  const settings = {
@@ -1610,6 +1780,9 @@ function storedFieldsToInspectorSettings(stored) {
1610
1780
  if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
1611
1781
  settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
1612
1782
  }
1783
+ if (stored.suppressNotificationStream === true) {
1784
+ settings.suppressNotificationStream = true;
1785
+ }
1613
1786
  if (isProtocolEra(stored.protocolEra)) {
1614
1787
  settings.protocolEra = stored.protocolEra;
1615
1788
  }
@@ -1661,6 +1834,7 @@ var INSPECTOR_FIELD_KEY_MAP = {
1661
1834
  taskTtl: true,
1662
1835
  autoRefreshOnListChanged: true,
1663
1836
  paginatedLists: true,
1837
+ suppressNotificationStream: true,
1664
1838
  advertisedExtensions: true,
1665
1839
  maxFetchRequests: true,
1666
1840
  skillCatalogMaxSkills: true,
@@ -2015,7 +2189,7 @@ function secretStorageCaveat(info) {
2015
2189
  return `The secrets file could not be read (${info.encryptionUnknown}). Saving a secret will fail rather than overwrite it.`;
2016
2190
  }
2017
2191
  if (info.plaintext) {
2018
- return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY to encrypt them.";
2192
+ return info.pendingEncryption ? "Existing secrets in this file are still unencrypted (file mode 0600). They are re-encrypted the next time a secret is saved." : "Secrets are stored unencrypted (file mode 0600). Set MCP_INSPECTOR_SECRET_KEY or MCP_INSPECTOR_SECRET_KEY_FILE to encrypt them.";
2019
2193
  }
2020
2194
  return void 0;
2021
2195
  }
@@ -2030,6 +2204,7 @@ function secretStorageSummary(info) {
2030
2204
 
2031
2205
  // ../../core/auth/node/file-secret-store.ts
2032
2206
  import * as crypto2 from "crypto";
2207
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
2033
2208
  import * as fs3 from "fs/promises";
2034
2209
  import * as path3 from "path";
2035
2210
 
@@ -2460,6 +2635,55 @@ var SCRYPT_N = 16384;
2460
2635
  var SCRYPT_R = 8;
2461
2636
  var SCRYPT_P = 1;
2462
2637
  var SECRET_KEY_ENV = "MCP_INSPECTOR_SECRET_KEY";
2638
+ var SECRET_KEY_FILE_ENV = "MCP_INSPECTOR_SECRET_KEY_FILE";
2639
+ function isSameFile(a, b) {
2640
+ if (path3.resolve(a) === path3.resolve(b)) return true;
2641
+ try {
2642
+ const sa = statSync2(a);
2643
+ const sb = statSync2(b);
2644
+ return sa.dev === sb.dev && sa.ino === sb.ino;
2645
+ } catch {
2646
+ return false;
2647
+ }
2648
+ }
2649
+ function resolveSecretPassphrase(env2 = process.env, secretFilePath) {
2650
+ const direct = env2[SECRET_KEY_ENV];
2651
+ const hasDirect = direct !== void 0 && direct.trim() !== "";
2652
+ const rawKeyFile = env2[SECRET_KEY_FILE_ENV];
2653
+ const wantsKeyFile = rawKeyFile !== void 0;
2654
+ const keyFile = rawKeyFile?.trim();
2655
+ if (hasDirect && wantsKeyFile) {
2656
+ return {
2657
+ problem: `both ${SECRET_KEY_ENV} and ${SECRET_KEY_FILE_ENV} are set; set only one`
2658
+ };
2659
+ }
2660
+ if (hasDirect) return { passphrase: direct };
2661
+ if (!wantsKeyFile) return {};
2662
+ if (!keyFile) {
2663
+ return { problem: `${SECRET_KEY_FILE_ENV} is set but empty` };
2664
+ }
2665
+ const resolved2 = path3.resolve(keyFile);
2666
+ if (secretFilePath !== void 0 && isSameFile(resolved2, secretFilePath)) {
2667
+ return {
2668
+ problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is the secrets file itself; point it at a separate key file`
2669
+ };
2670
+ }
2671
+ let contents;
2672
+ try {
2673
+ contents = readFileSync2(resolved2, "utf-8");
2674
+ } catch (err) {
2675
+ return {
2676
+ // `String(err)` rather than `.message`: `readFileSync` only ever throws
2677
+ // an `Error`, so an `instanceof` guard would be a branch no test can take.
2678
+ problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) could not be read: ${String(err)}`
2679
+ };
2680
+ }
2681
+ const passphrase = contents.replace(/[\r\n]+$/, "");
2682
+ if (passphrase.trim() === "") {
2683
+ return { problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is empty` };
2684
+ }
2685
+ return { passphrase };
2686
+ }
2463
2687
  var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
2464
2688
  function asSecretMap(value, filePath) {
2465
2689
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -2479,7 +2703,7 @@ function asSecretMap(value, filePath) {
2479
2703
  var SecretFileKeyMismatchError = class extends SecretStoreUnavailableError {
2480
2704
  constructor(filePath, hasKey) {
2481
2705
  super(
2482
- hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV}. Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set. Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
2706
+ hasKey ? `The secrets file at ${filePath} could not be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Restore the original passphrase, or delete the file to start over.` : `The secrets file at ${filePath} is encrypted but ${SECRET_KEY_ENV} is not set (nor ${SECRET_KEY_FILE_ENV}). Refusing to write, which would overwrite the existing secrets. Set the passphrase this file was written with, or delete the file to start over.`
2483
2707
  );
2484
2708
  this.name = "SecretFileKeyMismatchError";
2485
2709
  }
@@ -2537,10 +2761,17 @@ function encryptedEnvelopeProblem(parsed) {
2537
2761
  var FileSecretStore = class {
2538
2762
  filePath;
2539
2763
  passphrase;
2764
+ /**
2765
+ * Why a configured key could not be obtained. While set, every read throws
2766
+ * and every write refuses — see {@link SecretPassphrase}.
2767
+ */
2768
+ keyProblem;
2540
2769
  constructor(options) {
2541
2770
  this.filePath = options.filePath;
2542
- const raw = options.passphrase ?? process.env[SECRET_KEY_ENV];
2771
+ const resolved2 = options.passphrase !== void 0 ? { passphrase: options.passphrase } : resolveSecretPassphrase(process.env, this.filePath);
2772
+ const raw = resolved2.passphrase;
2543
2773
  this.passphrase = raw && raw.trim() ? raw : void 0;
2774
+ this.keyProblem = resolved2.problem;
2544
2775
  }
2545
2776
  /**
2546
2777
  * True when the *next write* will encrypt. This is the configured policy,
@@ -2555,7 +2786,8 @@ var FileSecretStore = class {
2555
2786
  * no file yet, or when it cannot be read or parsed.
2556
2787
  *
2557
2788
  * Separate from {@link encrypted} because the two genuinely disagree for a
2558
- * whole session: adding `MCP_INSPECTOR_SECRET_KEY` to an install that
2789
+ * whole session: adding a passphrase (`MCP_INSPECTOR_SECRET_KEY` or
2790
+ * `MCP_INSPECTOR_SECRET_KEY_FILE`) to an install that
2559
2791
  * already has a plaintext file flips `encrypted` to true immediately,
2560
2792
  * while the bytes stay readable until the next `set`. A descriptor built
2561
2793
  * from the policy would tell that user "File (encrypted)" while their
@@ -2579,6 +2811,9 @@ var FileSecretStore = class {
2579
2811
  * `set` is in fact about to refuse.
2580
2812
  */
2581
2813
  async readOnDiskEncryption() {
2814
+ if (this.keyProblem !== void 0) {
2815
+ return { state: "unreadable", detail: this.keyProblem };
2816
+ }
2582
2817
  let raw;
2583
2818
  try {
2584
2819
  raw = await readStoreFile(this.filePath);
@@ -2616,7 +2851,7 @@ var FileSecretStore = class {
2616
2851
  } catch (err) {
2617
2852
  return {
2618
2853
  state: "unreadable",
2619
- detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV}` : err instanceof Error ? err.message : String(err)
2854
+ detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV} (or ${SECRET_KEY_FILE_ENV})` : err instanceof Error ? err.message : String(err)
2620
2855
  };
2621
2856
  }
2622
2857
  return { state: "encrypted" };
@@ -2667,6 +2902,11 @@ var FileSecretStore = class {
2667
2902
  return this.serialize(() => this.readMap());
2668
2903
  }
2669
2904
  async readMap() {
2905
+ if (this.keyProblem !== void 0) {
2906
+ throw new SecretStoreUnavailableError(
2907
+ `The secrets file at ${this.filePath} cannot be used: ${this.keyProblem}. Refusing to read or write it rather than store secrets unencrypted.`
2908
+ );
2909
+ }
2670
2910
  const raw = await readStoreFile(this.filePath);
2671
2911
  if (raw === null) return null;
2672
2912
  let parsed;
@@ -3022,6 +3262,7 @@ async function readSecretFilePermissions(filePath) {
3022
3262
  var SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
3023
3263
  var SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
3024
3264
  var STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
3265
+ var SECRET_STORAGE_DOCS_URL = "https://github.com/modelcontextprotocol/inspector/blob/main/docs/secret-storage.md";
3025
3266
  var KINDS = ["keyring", "file", "memory"];
3026
3267
  function parseSecretStoreEnv(raw) {
3027
3268
  const value = raw?.trim().toLowerCase();
@@ -3164,6 +3405,11 @@ function warnAboutSecretStorage(info) {
3164
3405
  }
3165
3406
  const caveat = secretStorageCaveat(info);
3166
3407
  if (caveat) console.warn(`[mcp-inspector] ${caveat}`);
3408
+ if (info.reason === "fallback" || caveat) {
3409
+ console.warn(
3410
+ `[mcp-inspector] How the secret store is chosen, and how to secure it: ${SECRET_STORAGE_DOCS_URL}`
3411
+ );
3412
+ }
3167
3413
  }
3168
3414
  var resolved;
3169
3415
  function resolveSecretStore() {
@@ -4305,6 +4551,73 @@ function createProxyFetch() {
4305
4551
  };
4306
4552
  }
4307
4553
 
4554
+ // ../../core/mcp/node/notificationHeadersFetch.ts
4555
+ import { PROTOCOL_VERSION_META_KEY } from "@modelcontextprotocol/client";
4556
+ function createNotificationHeadersFetch(baseFetch) {
4557
+ return (input, init) => {
4558
+ const method = modernNotificationMethod(init);
4559
+ if (method === void 0) return baseFetch(input, init);
4560
+ const headers = new Headers(init?.headers);
4561
+ headers.set("mcp-protocol-version", method.version);
4562
+ headers.set("mcp-method", method.method);
4563
+ return baseFetch(input, { ...init, headers });
4564
+ };
4565
+ }
4566
+ function modernNotificationMethod(init) {
4567
+ if (init?.method?.toUpperCase() !== "POST") return void 0;
4568
+ if (typeof init.body !== "string") return void 0;
4569
+ let message;
4570
+ try {
4571
+ message = JSON.parse(init.body);
4572
+ } catch {
4573
+ return void 0;
4574
+ }
4575
+ if (!isRecord2(message) || "id" in message) return void 0;
4576
+ if (typeof message.method !== "string") return void 0;
4577
+ const meta = isRecord2(message.params) ? message.params._meta : void 0;
4578
+ const version = isRecord2(meta) ? meta[PROTOCOL_VERSION_META_KEY] : void 0;
4579
+ if (typeof version !== "string" || version < MODERN_PROTOCOL_VERSION) {
4580
+ return void 0;
4581
+ }
4582
+ return { method: message.method, version };
4583
+ }
4584
+ function isRecord2(value) {
4585
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4586
+ }
4587
+
4588
+ // ../../core/mcp/node/suppressNotificationStreamFetch.ts
4589
+ function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
4590
+ return async (input, init) => {
4591
+ if (isStandaloneStreamRequest(input, init, endpoint)) {
4592
+ return new Response(null, {
4593
+ status: 405,
4594
+ statusText: "Method Not Allowed"
4595
+ });
4596
+ }
4597
+ return baseFetch(input, init);
4598
+ };
4599
+ }
4600
+ function requestUrl(input) {
4601
+ const raw = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
4602
+ try {
4603
+ return new URL(raw);
4604
+ } catch {
4605
+ return void 0;
4606
+ }
4607
+ }
4608
+ function isStandaloneStreamRequest(input, init, endpoint) {
4609
+ const request = input instanceof Request ? input : void 0;
4610
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
4611
+ if (method !== "GET") return false;
4612
+ const url = requestUrl(input);
4613
+ if (!url || url.origin !== endpoint.origin || url.pathname !== endpoint.pathname || url.search !== endpoint.search) {
4614
+ return false;
4615
+ }
4616
+ const headers = new Headers(init?.headers ?? request?.headers);
4617
+ const accept = headers.get("accept")?.toLowerCase() ?? "";
4618
+ return accept.includes("text/event-stream") && !headers.has("last-event-id");
4619
+ }
4620
+
4308
4621
  // ../../core/mcp/node/transport.ts
4309
4622
  function headersFromSettings(settings) {
4310
4623
  if (!settings || settings.headers.length === 0) return void 0;
@@ -4392,10 +4705,14 @@ function createTransportNode(config, options = {}) {
4392
4705
  ...httpConfig.requestInit,
4393
4706
  ...headers && { headers }
4394
4707
  };
4708
+ const stampedFetch = createNotificationHeadersFetch(
4709
+ fetchWithOptionalAuthIntercept
4710
+ );
4711
+ const httpFetch = settings?.suppressNotificationStream === true ? createSuppressNotificationStreamFetch(stampedFetch, url) : stampedFetch;
4395
4712
  const transport = new StreamableHTTPClientTransport(url, {
4396
4713
  authProvider,
4397
4714
  requestInit,
4398
- fetch: fetchWithOptionalAuthIntercept,
4715
+ fetch: httpFetch,
4399
4716
  // SEP-2350: how the transport reacts to a `403 insufficient_scope`
4400
4717
  // challenge. Defaults to the SDK's `reauthorize` when unset.
4401
4718
  ...settings?.oauthOnInsufficientScope && {
@@ -4826,14 +5143,14 @@ var OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
4826
5143
  function pathName(path6) {
4827
5144
  return path6.length === 0 ? "<root>" : path6.join(".");
4828
5145
  }
4829
- function isRecord(node) {
5146
+ function isRecord3(node) {
4830
5147
  return node !== null && typeof node === "object";
4831
5148
  }
4832
5149
  function scanXMcpHeaderDeclarations(inputSchema) {
4833
5150
  const declarations = [];
4834
5151
  const seenLower = /* @__PURE__ */ new Map();
4835
5152
  const visit = (node, path6, reachable) => {
4836
- if (!isRecord(node)) return void 0;
5153
+ if (!isRecord3(node)) return void 0;
4837
5154
  const schema = node;
4838
5155
  if (X_MCP_HEADER_KEY in schema) {
4839
5156
  if (!reachable || path6.length === 0) {
@@ -4859,7 +5176,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
4859
5176
  declarations.push({ path: path6, headerName: raw, type });
4860
5177
  }
4861
5178
  const properties = schema.properties;
4862
- if (isRecord(properties)) {
5179
+ if (isRecord3(properties)) {
4863
5180
  for (const [key, child] of Object.entries(properties)) {
4864
5181
  const fault2 = visit(child, [...path6, key], reachable);
4865
5182
  if (fault2 !== void 0) return fault2;
@@ -4868,7 +5185,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
4868
5185
  for (const k of NON_REACHABLE_SUBSCHEMA_KEYWORDS) {
4869
5186
  const sub = schema[k];
4870
5187
  if (sub === void 0) continue;
4871
- const branches = Array.isArray(sub) ? sub : isRecord(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
5188
+ const branches = Array.isArray(sub) ? sub : isRecord3(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
4872
5189
  for (const branch of branches) {
4873
5190
  const fault2 = visit(branch, [...path6, `<${k}>`], false);
4874
5191
  if (fault2 !== void 0) return fault2;
@@ -5100,7 +5417,7 @@ import {
5100
5417
  LOG_LEVEL_META_KEY,
5101
5418
  CLIENT_CAPABILITIES_META_KEY,
5102
5419
  CLIENT_INFO_META_KEY,
5103
- PROTOCOL_VERSION_META_KEY,
5420
+ PROTOCOL_VERSION_META_KEY as PROTOCOL_VERSION_META_KEY2,
5104
5421
  RELATED_TASK_META_KEY as RELATED_TASK_META_KEY3
5105
5422
  } from "@modelcontextprotocol/client";
5106
5423
 
@@ -6351,7 +6668,7 @@ function withRfc8414OidcCompat(fetchFn) {
6351
6668
  return response;
6352
6669
  }
6353
6670
  if (!probe.ok) {
6354
- await releaseBody(probe);
6671
+ releaseBodyDetached(probe);
6355
6672
  if (callerSignal?.aborted) {
6356
6673
  releaseBodyDetached(response);
6357
6674
  throw callerSignal.reason;
@@ -6375,7 +6692,7 @@ function withRfc8414OidcCompat(fetchFn) {
6375
6692
  return response;
6376
6693
  }
6377
6694
  const source = probe.url || candidate;
6378
- await releaseBody(response);
6695
+ releaseBodyDetached(response);
6379
6696
  console.warn(
6380
6697
  `[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.`
6381
6698
  );
@@ -11237,7 +11554,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11237
11554
  ...params,
11238
11555
  _meta: {
11239
11556
  ...existingMeta,
11240
- [PROTOCOL_VERSION_META_KEY]: protocolVersion,
11557
+ [PROTOCOL_VERSION_META_KEY2]: protocolVersion,
11241
11558
  [CLIENT_INFO_META_KEY]: this.clientInfo,
11242
11559
  [CLIENT_CAPABILITIES_META_KEY]: clientCapabilities
11243
11560
  }
@@ -15275,7 +15592,7 @@ import { useCallback as useCallback2, useEffect, useState } from "react";
15275
15592
  // ../../core/react/useStoreSnapshot.ts
15276
15593
  import { useCallback, useRef, useSyncExternalStore } from "react";
15277
15594
  function useStoreSnapshot(store, event, read, whenAbsent) {
15278
- const cache = useRef(null);
15595
+ const cache2 = useRef(null);
15279
15596
  const subscribe = useCallback(
15280
15597
  (onStoreChange) => {
15281
15598
  if (!store) return () => {
@@ -15289,12 +15606,12 @@ function useStoreSnapshot(store, event, read, whenAbsent) {
15289
15606
  );
15290
15607
  const getSnapshot = useCallback(() => {
15291
15608
  const revision = store ? store.getEventRevision(event) : 0;
15292
- const cached = cache.current;
15609
+ const cached = cache2.current;
15293
15610
  if (cached && Object.is(cached.store, store) && cached.event === event && cached.read === read && Object.is(cached.whenAbsent, whenAbsent) && cached.revision === revision) {
15294
15611
  return cached.value;
15295
15612
  }
15296
15613
  const value = store ? read(store) : whenAbsent;
15297
- cache.current = { store, event, read, whenAbsent, revision, value };
15614
+ cache2.current = { store, event, read, whenAbsent, revision, value };
15298
15615
  return value;
15299
15616
  }, [store, event, read, whenAbsent]);
15300
15617
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
@@ -15699,7 +16016,7 @@ function classifyAuthorizationServerMismatch(err) {
15699
16016
  }
15700
16017
 
15701
16018
  // ../../core/node/version.ts
15702
- import { readFileSync as readFileSync3 } from "fs";
16019
+ import { readFileSync as readFileSync4 } from "fs";
15703
16020
  import { dirname as dirname5, join as join3, parse } from "path";
15704
16021
  import { fileURLToPath } from "url";
15705
16022
  var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
@@ -15711,7 +16028,7 @@ function readInspectorVersion(callerUrl) {
15711
16028
  let manifest;
15712
16029
  try {
15713
16030
  manifest = JSON.parse(
15714
- readFileSync3(join3(dir, "package.json"), "utf-8")
16031
+ readFileSync4(join3(dir, "package.json"), "utf-8")
15715
16032
  );
15716
16033
  } catch {
15717
16034
  }
@@ -16128,7 +16445,7 @@ var OAuthMemoryStore = class {
16128
16445
  };
16129
16446
 
16130
16447
  // ../../core/auth/oauth-persist.ts
16131
- function isRecord2(value) {
16448
+ function isRecord4(value) {
16132
16449
  return typeof value === "object" && value !== null && !Array.isArray(value);
16133
16450
  }
16134
16451
  function snapshotFromPayload(payload) {
@@ -16142,10 +16459,10 @@ function parseOAuthPersistBlob(raw) {
16142
16459
  return null;
16143
16460
  }
16144
16461
  const parsed = typeof raw === "string" ? raw ? parseStore(raw) : null : raw;
16145
- if (!isRecord2(parsed)) {
16462
+ if (!isRecord4(parsed)) {
16146
16463
  return null;
16147
16464
  }
16148
- if (isRecord2(parsed.state) && "version" in parsed) {
16465
+ if (isRecord4(parsed.state) && "version" in parsed) {
16149
16466
  return snapshotFromPayload(parsed.state);
16150
16467
  }
16151
16468
  if ("servers" in parsed || "idpSessions" in parsed) {
@@ -18485,7 +18802,7 @@ var BOOLEAN_VALUED_KEYWORDS = /* @__PURE__ */ new Set([
18485
18802
  "additionalItems",
18486
18803
  "unevaluatedItems"
18487
18804
  ]);
18488
- var SUBSCHEMA_KEYWORDS = [
18805
+ var SUBSCHEMA_KEYWORDS2 = [
18489
18806
  "items",
18490
18807
  "contains",
18491
18808
  "not",
@@ -18502,13 +18819,13 @@ var SUBSCHEMA_KEYWORDS = [
18502
18819
  // real finding.
18503
18820
  "contentSchema"
18504
18821
  ];
18505
- var SUBSCHEMA_ARRAY_KEYWORDS = [
18822
+ var SUBSCHEMA_ARRAY_KEYWORDS2 = [
18506
18823
  "allOf",
18507
18824
  "anyOf",
18508
18825
  "oneOf",
18509
18826
  "prefixItems"
18510
18827
  ];
18511
- var SUBSCHEMA_MAP_KEYWORDS = [
18828
+ var SUBSCHEMA_MAP_KEYWORDS2 = [
18512
18829
  "properties",
18513
18830
  "patternProperties",
18514
18831
  "dependentSchemas",
@@ -18590,8 +18907,8 @@ function isPortableTypeUnion(type) {
18590
18907
  }
18591
18908
  return new Set(type).size === type.length;
18592
18909
  }
18593
- var MAX_DEPTH = 64;
18594
- function isRecord3(value) {
18910
+ var MAX_DEPTH2 = 64;
18911
+ function isRecord5(value) {
18595
18912
  return typeof value === "object" && value !== null && !Array.isArray(value);
18596
18913
  }
18597
18914
  function childPath(parent, key) {
@@ -18607,7 +18924,7 @@ function describeSchemaPath(schema, path6) {
18607
18924
  return path6 === "" ? schema : `${schema}.${path6}`;
18608
18925
  }
18609
18926
  function declaresAnyId(node, depth = 0) {
18610
- if (depth > MAX_DEPTH || !isRecord3(node)) return false;
18927
+ if (depth > MAX_DEPTH2 || !isRecord5(node)) return false;
18611
18928
  if (typeof node.$id === "string" && node.$id !== "") return true;
18612
18929
  let found = false;
18613
18930
  forEachSubschema(node, "", (child) => {
@@ -18626,21 +18943,21 @@ function add(ctx, rule, severity, path6, issue, suggestion) {
18626
18943
  });
18627
18944
  }
18628
18945
  function forEachSubschema(node, path6, visit) {
18629
- for (const key of SUBSCHEMA_MAP_KEYWORDS) {
18946
+ for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
18630
18947
  const map = node[key];
18631
- if (!isRecord3(map)) continue;
18948
+ if (!isRecord5(map)) continue;
18632
18949
  for (const [name, sub] of Object.entries(map)) {
18633
18950
  visit(sub, childPath(childPath(path6, key), name), key);
18634
18951
  }
18635
18952
  }
18636
- for (const key of SUBSCHEMA_ARRAY_KEYWORDS) {
18953
+ for (const key of SUBSCHEMA_ARRAY_KEYWORDS2) {
18637
18954
  const arr = node[key];
18638
18955
  if (!Array.isArray(arr)) continue;
18639
18956
  arr.forEach((sub, i) => {
18640
18957
  visit(sub, indexPath(childPath(path6, key), i), key);
18641
18958
  });
18642
18959
  }
18643
- for (const key of SUBSCHEMA_KEYWORDS) {
18960
+ for (const key of SUBSCHEMA_KEYWORDS2) {
18644
18961
  if (!(key in node)) continue;
18645
18962
  const value = node[key];
18646
18963
  if (key === "items" && Array.isArray(value)) {
@@ -18659,7 +18976,7 @@ function booleanTrueSuggestion(keyword) {
18659
18976
  return 'Declare what the value actually is \u2014 e.g. `{"type": "object", "additionalProperties": true}` for a free-form object. That is a deliberate change of contract, not an equivalent rewrite: `true` accepts any JSON value at all.';
18660
18977
  }
18661
18978
  function walk(node, path6, keyword, depth, ctx) {
18662
- if (depth > MAX_DEPTH) return;
18979
+ if (depth > MAX_DEPTH2) return;
18663
18980
  if (typeof node === "boolean") {
18664
18981
  if (keyword !== void 0 && BOOLEAN_VALUED_KEYWORDS.has(keyword)) return;
18665
18982
  add(
@@ -18678,12 +18995,13 @@ function walk(node, path6, keyword, depth, ctx) {
18678
18995
  );
18679
18996
  return;
18680
18997
  }
18681
- if (!isRecord3(node)) return;
18998
+ if (!isRecord5(node)) return;
18682
18999
  lintNode(node, path6, keyword, ctx);
18683
19000
  forEachSubschema(node, path6, (child, childPathValue, childKeyword) => {
18684
19001
  walk(child, childPathValue, childKeyword, depth + 1, ctx);
18685
19002
  });
18686
19003
  }
19004
+ var NULL_BRANCH_CAVEAT = " A dialect with no `null` type, such as OpenAPI 3.0, still cannot express the `null` branch directly; there nullability is written `nullable: true`, which is not JSON Schema, so no single spelling is portable to both.";
18687
19005
  function lintNode(node, path6, keyword, ctx) {
18688
19006
  const type = node.type;
18689
19007
  if (Array.isArray(type) && isPortableTypeUnion(type)) {
@@ -18692,10 +19010,10 @@ function lintNode(node, path6, keyword, ctx) {
18692
19010
  "type-union",
18693
19011
  "warning",
18694
19012
  path6,
18695
- `\`type\` is an array (${JSON.stringify(type)}). The array form is legal JSON Schema, but several MCP clients read \`type\` as a single string and either reject the tool or drop the constraint.`,
19013
+ `\`type\` is an array (${JSON.stringify(type)}). This is legal JSON Schema, and some model providers recommend it for nullable fields, but it is less portable: a client that maps tool schemas onto a single-\`type\` dialect (such as the OpenAPI subset used for Gemini function declarations) may reject the tool or drop the constraint.`,
18696
19014
  `Split it into \`anyOf\` branches, each with a single \`type\` \u2014 \`{"anyOf": [${type.map((t) => `{"type": "${t}"}`).join(
18697
19015
  ", "
18698
- )}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)`
19016
+ )}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)${type.includes("null") ? NULL_BRANCH_CAVEAT : ""}`
18699
19017
  );
18700
19018
  }
18701
19019
  const ref = node.$ref;
@@ -21289,7 +21607,8 @@ function ToolTestModal({
21289
21607
  process.stdout.off("resize", updateDimensions);
21290
21608
  };
21291
21609
  }, [width, height]);
21292
- const formStructure = tool?.inputSchema ? schemaToForm(tool.inputSchema, tool.name || "Unknown Tool") : {
21610
+ const inputSchema = inlineLocalRefs(tool?.inputSchema);
21611
+ const formStructure = inputSchema ? schemaToForm(inputSchema, tool?.name || "Unknown Tool") : {
21293
21612
  title: `Test Tool: ${tool?.name || "Unknown"}`,
21294
21613
  sections: [{ title: "Parameters", fields: [] }]
21295
21614
  };
@@ -21328,8 +21647,8 @@ function ToolTestModal({
21328
21647
  );
21329
21648
  const handleFormSubmit = async (rawValues) => {
21330
21649
  if (!inspectorClient || !tool) return;
21331
- const values = decodeFormValues(tool.inputSchema, rawValues);
21332
- const missing = missingRequiredFields(tool.inputSchema, values, rawValues);
21650
+ const values = decodeFormValues(inputSchema, rawValues);
21651
+ const missing = missingRequiredFields(inputSchema, values, rawValues);
21333
21652
  if (missing.length > 0) {
21334
21653
  setResult({
21335
21654
  input: values,