@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.
@@ -1041,6 +1041,176 @@ function checkSkillNameCollisions(entries) {
1041
1041
  return issues;
1042
1042
  }
1043
1043
 
1044
+ // ../../core/json/localRefs.ts
1045
+ var SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
1046
+ "items",
1047
+ "contains",
1048
+ "not",
1049
+ "propertyNames",
1050
+ "if",
1051
+ "then",
1052
+ "else",
1053
+ "additionalProperties",
1054
+ "unevaluatedProperties",
1055
+ "additionalItems",
1056
+ "unevaluatedItems",
1057
+ "contentSchema"
1058
+ ]);
1059
+ var SUBSCHEMA_ARRAY_KEYWORDS = /* @__PURE__ */ new Set([
1060
+ "allOf",
1061
+ "anyOf",
1062
+ "oneOf",
1063
+ "prefixItems"
1064
+ ]);
1065
+ var SUBSCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
1066
+ "properties",
1067
+ "patternProperties",
1068
+ "dependentSchemas",
1069
+ // Pre-2019 spelling of `dependentSchemas`; its array values are property
1070
+ // name lists, which are not schemas and pass through untouched.
1071
+ "dependencies",
1072
+ "$defs",
1073
+ "definitions"
1074
+ ]);
1075
+ var NON_CONSTRAINT_SIBLINGS = /* @__PURE__ */ new Set([
1076
+ "title",
1077
+ "description",
1078
+ "default",
1079
+ "examples",
1080
+ "deprecated",
1081
+ "readOnly",
1082
+ "writeOnly",
1083
+ // Non-standard labels for `enum` values, read by both form builders.
1084
+ "enumNames",
1085
+ "$comment",
1086
+ "$schema",
1087
+ "$id",
1088
+ "$defs",
1089
+ "definitions"
1090
+ ]);
1091
+ var EXPANSION_BUDGET = 1e4;
1092
+ var MAX_DEPTH = 64;
1093
+ var Bail = class extends Error {
1094
+ };
1095
+ function spend(traversal) {
1096
+ traversal.remaining -= 1;
1097
+ if (traversal.remaining < 0) throw new Bail();
1098
+ }
1099
+ function mapSubschemas(node, visit) {
1100
+ const result = {};
1101
+ for (const [key, value] of Object.entries(node)) {
1102
+ let next = value;
1103
+ if (SUBSCHEMA_KEYWORDS.has(key)) {
1104
+ next = Array.isArray(value) ? value.map(visit) : visit(value);
1105
+ } else if (SUBSCHEMA_ARRAY_KEYWORDS.has(key) && Array.isArray(value)) {
1106
+ next = value.map(visit);
1107
+ } else if (SUBSCHEMA_MAP_KEYWORDS.has(key) && isRecord(value)) {
1108
+ next = mapNames(value, visit);
1109
+ }
1110
+ define(result, key, next);
1111
+ }
1112
+ return result;
1113
+ }
1114
+ function mapNames(map, visit) {
1115
+ const result = {};
1116
+ for (const [name, value] of Object.entries(map)) {
1117
+ define(result, name, Array.isArray(value) ? value : visit(value));
1118
+ }
1119
+ return result;
1120
+ }
1121
+ function define(target, key, value) {
1122
+ Object.defineProperty(target, key, {
1123
+ value,
1124
+ writable: true,
1125
+ enumerable: true,
1126
+ configurable: true
1127
+ });
1128
+ }
1129
+ function isRecord(value) {
1130
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1131
+ }
1132
+ function resolvePointer(root, ref) {
1133
+ if (!ref.startsWith("#")) return void 0;
1134
+ let pointer;
1135
+ try {
1136
+ pointer = decodeURIComponent(ref.slice(1));
1137
+ } catch {
1138
+ return void 0;
1139
+ }
1140
+ if (pointer === "") return root;
1141
+ if (!pointer.startsWith("/")) return void 0;
1142
+ let current = root;
1143
+ for (const token of pointer.slice(1).split("/")) {
1144
+ if (/~(?![01])/.test(token)) return void 0;
1145
+ const segment = token.replace(/~1/g, "/").replace(/~0/g, "~");
1146
+ if (Array.isArray(current)) {
1147
+ const index = Number(segment);
1148
+ if (!/^(0|[1-9]\d*)$/.test(segment) || index >= current.length) {
1149
+ return void 0;
1150
+ }
1151
+ current = current[index];
1152
+ } else if (isRecord(current) && Object.hasOwn(current, segment)) {
1153
+ current = current[segment];
1154
+ } else {
1155
+ return void 0;
1156
+ }
1157
+ }
1158
+ return current;
1159
+ }
1160
+ function containsRef(node, depth) {
1161
+ if (depth > MAX_DEPTH) throw new Bail();
1162
+ if (!isRecord(node)) return false;
1163
+ if (typeof node.$ref === "string") return true;
1164
+ let found = false;
1165
+ mapSubschemas(node, (child) => {
1166
+ found ||= containsRef(child, depth + 1);
1167
+ return child;
1168
+ });
1169
+ return found;
1170
+ }
1171
+ function inline(node, traversal, depth) {
1172
+ if (depth > MAX_DEPTH) throw new Bail();
1173
+ if (!isRecord(node)) return node;
1174
+ if (node !== traversal.root && typeof node.$id === "string") return node;
1175
+ spend(traversal);
1176
+ const visit = (child) => inline(child, traversal, depth + 1);
1177
+ const ref = node.$ref;
1178
+ const siblingsConstrain = Object.keys(node).some(
1179
+ (key) => key !== "$ref" && !NON_CONSTRAINT_SIBLINGS.has(key)
1180
+ );
1181
+ if (typeof ref === "string" && !siblingsConstrain && !traversal.active.has(ref)) {
1182
+ const target = resolvePointer(traversal.root, ref);
1183
+ if (isRecord(target)) {
1184
+ traversal.active.add(ref);
1185
+ const resolved2 = visit(target);
1186
+ traversal.active.delete(ref);
1187
+ const siblings = { ...node };
1188
+ delete siblings.$ref;
1189
+ return { ...resolved2, ...mapSubschemas(siblings, visit) };
1190
+ }
1191
+ }
1192
+ return mapSubschemas(node, visit);
1193
+ }
1194
+ var cache = /* @__PURE__ */ new WeakMap();
1195
+ function inlineLocalRefs(schema) {
1196
+ if (!isRecord(schema)) return schema;
1197
+ const cached = cache.get(schema);
1198
+ if (cached !== void 0) return cached;
1199
+ let resolved2;
1200
+ try {
1201
+ resolved2 = containsRef(schema, 0) ? inline(
1202
+ schema,
1203
+ { root: schema, active: /* @__PURE__ */ new Set(), remaining: EXPANSION_BUDGET },
1204
+ 0
1205
+ ) : schema;
1206
+ } catch (error) {
1207
+ if (!(error instanceof Bail)) throw error;
1208
+ resolved2 = schema;
1209
+ }
1210
+ cache.set(schema, resolved2);
1211
+ return resolved2;
1212
+ }
1213
+
1044
1214
  // ../../core/json/nullableUnion.ts
1045
1215
  var RENDERABLE_TYPES = [
1046
1216
  "string",
@@ -1639,7 +1809,7 @@ function convertToolParameters(tool, params) {
1639
1809
  function convertParametersForSchema(inputSchema, params) {
1640
1810
  const result = {};
1641
1811
  const { base, branches } = resolveRootUnion(
1642
- inputSchema ?? {}
1812
+ inlineLocalRefs(inputSchema ?? {})
1643
1813
  );
1644
1814
  const properties = coercionProperties(base, branches, params);
1645
1815
  for (const [key, value] of Object.entries(params)) {
@@ -1831,7 +2001,7 @@ function oauthEndpointOverridesFromSettings(settings) {
1831
2001
  };
1832
2002
  }
1833
2003
  function storedFieldsToInspectorSettings(stored) {
1834
- const hasAny = stored.headers !== void 0 || stored.metadata !== void 0 || stored.connectionTimeout !== void 0 || stored.requestTimeout !== void 0 || stored.taskTtl !== void 0 || stored.autoRefreshOnListChanged !== void 0 || stored.paginatedLists !== void 0 || stored.advertisedExtensions !== void 0 || stored.maxFetchRequests !== void 0 || stored.skillCatalogMaxSkills !== void 0 || stored.skillCatalogMaxBytes !== void 0 || stored.oauth !== void 0 || stored.roots !== void 0 || stored.protocolEra !== void 0 || stored.modernLogLevel !== void 0 || stored.env !== void 0 || stored.cwd !== void 0;
2004
+ 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;
1835
2005
  if (!hasAny) return void 0;
1836
2006
  const headersPairs = stored.headers ? Object.entries(stored.headers).map(([key, value]) => ({ key, value })) : [];
1837
2007
  const settings = {
@@ -1863,6 +2033,9 @@ function storedFieldsToInspectorSettings(stored) {
1863
2033
  if (isSkillCatalogLimit(stored.skillCatalogMaxBytes)) {
1864
2034
  settings.skillCatalogMaxBytes = stored.skillCatalogMaxBytes;
1865
2035
  }
2036
+ if (stored.suppressNotificationStream === true) {
2037
+ settings.suppressNotificationStream = true;
2038
+ }
1866
2039
  if (isProtocolEra(stored.protocolEra)) {
1867
2040
  settings.protocolEra = stored.protocolEra;
1868
2041
  }
@@ -1914,6 +2087,7 @@ var INSPECTOR_FIELD_KEY_MAP = {
1914
2087
  taskTtl: true,
1915
2088
  autoRefreshOnListChanged: true,
1916
2089
  paginatedLists: true,
2090
+ suppressNotificationStream: true,
1917
2091
  advertisedExtensions: true,
1918
2092
  maxFetchRequests: true,
1919
2093
  skillCatalogMaxSkills: true,
@@ -2268,7 +2442,7 @@ function secretStorageCaveat(info) {
2268
2442
  return `The secrets file could not be read (${info.encryptionUnknown}). Saving a secret will fail rather than overwrite it.`;
2269
2443
  }
2270
2444
  if (info.plaintext) {
2271
- 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.";
2445
+ 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.";
2272
2446
  }
2273
2447
  return void 0;
2274
2448
  }
@@ -2283,6 +2457,7 @@ function secretStorageSummary(info) {
2283
2457
 
2284
2458
  // ../../core/auth/node/file-secret-store.ts
2285
2459
  import * as crypto2 from "crypto";
2460
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
2286
2461
  import * as fs3 from "fs/promises";
2287
2462
  import * as path3 from "path";
2288
2463
 
@@ -2485,6 +2660,55 @@ var SCRYPT_N = 16384;
2485
2660
  var SCRYPT_R = 8;
2486
2661
  var SCRYPT_P = 1;
2487
2662
  var SECRET_KEY_ENV = "MCP_INSPECTOR_SECRET_KEY";
2663
+ var SECRET_KEY_FILE_ENV = "MCP_INSPECTOR_SECRET_KEY_FILE";
2664
+ function isSameFile(a, b) {
2665
+ if (path3.resolve(a) === path3.resolve(b)) return true;
2666
+ try {
2667
+ const sa = statSync2(a);
2668
+ const sb = statSync2(b);
2669
+ return sa.dev === sb.dev && sa.ino === sb.ino;
2670
+ } catch {
2671
+ return false;
2672
+ }
2673
+ }
2674
+ function resolveSecretPassphrase(env = process.env, secretFilePath) {
2675
+ const direct = env[SECRET_KEY_ENV];
2676
+ const hasDirect = direct !== void 0 && direct.trim() !== "";
2677
+ const rawKeyFile = env[SECRET_KEY_FILE_ENV];
2678
+ const wantsKeyFile = rawKeyFile !== void 0;
2679
+ const keyFile = rawKeyFile?.trim();
2680
+ if (hasDirect && wantsKeyFile) {
2681
+ return {
2682
+ problem: `both ${SECRET_KEY_ENV} and ${SECRET_KEY_FILE_ENV} are set; set only one`
2683
+ };
2684
+ }
2685
+ if (hasDirect) return { passphrase: direct };
2686
+ if (!wantsKeyFile) return {};
2687
+ if (!keyFile) {
2688
+ return { problem: `${SECRET_KEY_FILE_ENV} is set but empty` };
2689
+ }
2690
+ const resolved2 = path3.resolve(keyFile);
2691
+ if (secretFilePath !== void 0 && isSameFile(resolved2, secretFilePath)) {
2692
+ return {
2693
+ problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is the secrets file itself; point it at a separate key file`
2694
+ };
2695
+ }
2696
+ let contents;
2697
+ try {
2698
+ contents = readFileSync2(resolved2, "utf-8");
2699
+ } catch (err) {
2700
+ return {
2701
+ // `String(err)` rather than `.message`: `readFileSync` only ever throws
2702
+ // an `Error`, so an `instanceof` guard would be a branch no test can take.
2703
+ problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) could not be read: ${String(err)}`
2704
+ };
2705
+ }
2706
+ const passphrase = contents.replace(/[\r\n]+$/, "");
2707
+ if (passphrase.trim() === "") {
2708
+ return { problem: `${SECRET_KEY_FILE_ENV} (${resolved2}) is empty` };
2709
+ }
2710
+ return { passphrase };
2711
+ }
2488
2712
  var buildAccount2 = (serverId, field) => `${serverId}:${field}`;
2489
2713
  function asSecretMap(value, filePath) {
2490
2714
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -2504,7 +2728,7 @@ function asSecretMap(value, filePath) {
2504
2728
  var SecretFileKeyMismatchError = class extends SecretStoreUnavailableError {
2505
2729
  constructor(filePath, hasKey) {
2506
2730
  super(
2507
- 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.`
2731
+ 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.`
2508
2732
  );
2509
2733
  this.name = "SecretFileKeyMismatchError";
2510
2734
  }
@@ -2562,10 +2786,17 @@ function encryptedEnvelopeProblem(parsed) {
2562
2786
  var FileSecretStore = class {
2563
2787
  filePath;
2564
2788
  passphrase;
2789
+ /**
2790
+ * Why a configured key could not be obtained. While set, every read throws
2791
+ * and every write refuses — see {@link SecretPassphrase}.
2792
+ */
2793
+ keyProblem;
2565
2794
  constructor(options) {
2566
2795
  this.filePath = options.filePath;
2567
- const raw = options.passphrase ?? process.env[SECRET_KEY_ENV];
2796
+ const resolved2 = options.passphrase !== void 0 ? { passphrase: options.passphrase } : resolveSecretPassphrase(process.env, this.filePath);
2797
+ const raw = resolved2.passphrase;
2568
2798
  this.passphrase = raw && raw.trim() ? raw : void 0;
2799
+ this.keyProblem = resolved2.problem;
2569
2800
  }
2570
2801
  /**
2571
2802
  * True when the *next write* will encrypt. This is the configured policy,
@@ -2580,7 +2811,8 @@ var FileSecretStore = class {
2580
2811
  * no file yet, or when it cannot be read or parsed.
2581
2812
  *
2582
2813
  * Separate from {@link encrypted} because the two genuinely disagree for a
2583
- * whole session: adding `MCP_INSPECTOR_SECRET_KEY` to an install that
2814
+ * whole session: adding a passphrase (`MCP_INSPECTOR_SECRET_KEY` or
2815
+ * `MCP_INSPECTOR_SECRET_KEY_FILE`) to an install that
2584
2816
  * already has a plaintext file flips `encrypted` to true immediately,
2585
2817
  * while the bytes stay readable until the next `set`. A descriptor built
2586
2818
  * from the policy would tell that user "File (encrypted)" while their
@@ -2604,6 +2836,9 @@ var FileSecretStore = class {
2604
2836
  * `set` is in fact about to refuse.
2605
2837
  */
2606
2838
  async readOnDiskEncryption() {
2839
+ if (this.keyProblem !== void 0) {
2840
+ return { state: "unreadable", detail: this.keyProblem };
2841
+ }
2607
2842
  let raw;
2608
2843
  try {
2609
2844
  raw = await readStoreFile(this.filePath);
@@ -2641,7 +2876,7 @@ var FileSecretStore = class {
2641
2876
  } catch (err) {
2642
2877
  return {
2643
2878
  state: "unreadable",
2644
- detail: err instanceof SecretFileKeyMismatchError ? `it cannot be decrypted with the current ${SECRET_KEY_ENV}` : err instanceof Error ? err.message : String(err)
2879
+ 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)
2645
2880
  };
2646
2881
  }
2647
2882
  return { state: "encrypted" };
@@ -2692,6 +2927,11 @@ var FileSecretStore = class {
2692
2927
  return this.serialize(() => this.readMap());
2693
2928
  }
2694
2929
  async readMap() {
2930
+ if (this.keyProblem !== void 0) {
2931
+ throw new SecretStoreUnavailableError(
2932
+ `The secrets file at ${this.filePath} cannot be used: ${this.keyProblem}. Refusing to read or write it rather than store secrets unencrypted.`
2933
+ );
2934
+ }
2695
2935
  const raw = await readStoreFile(this.filePath);
2696
2936
  if (raw === null) return null;
2697
2937
  let parsed;
@@ -3047,6 +3287,7 @@ async function readSecretFilePermissions(filePath) {
3047
3287
  var SECRET_STORE_ENV = "MCP_INSPECTOR_SECRET_STORE";
3048
3288
  var SECRET_FILE_ENV = "MCP_INSPECTOR_SECRET_FILE";
3049
3289
  var STORAGE_DIR_ENV = "MCP_STORAGE_DIR";
3290
+ var SECRET_STORAGE_DOCS_URL = "https://github.com/modelcontextprotocol/inspector/blob/main/docs/secret-storage.md";
3050
3291
  var KINDS = ["keyring", "file", "memory"];
3051
3292
  function parseSecretStoreEnv(raw) {
3052
3293
  const value = raw?.trim().toLowerCase();
@@ -3189,6 +3430,11 @@ function warnAboutSecretStorage(info) {
3189
3430
  }
3190
3431
  const caveat = secretStorageCaveat(info);
3191
3432
  if (caveat) console.warn(`[mcp-inspector] ${caveat}`);
3433
+ if (info.reason === "fallback" || caveat) {
3434
+ console.warn(
3435
+ `[mcp-inspector] How the secret store is chosen, and how to secure it: ${SECRET_STORAGE_DOCS_URL}`
3436
+ );
3437
+ }
3192
3438
  }
3193
3439
  var resolved;
3194
3440
  function resolveSecretStore() {
@@ -4351,6 +4597,73 @@ function createProxyFetch() {
4351
4597
  };
4352
4598
  }
4353
4599
 
4600
+ // ../../core/mcp/node/notificationHeadersFetch.ts
4601
+ import { PROTOCOL_VERSION_META_KEY } from "@modelcontextprotocol/client";
4602
+ function createNotificationHeadersFetch(baseFetch) {
4603
+ return (input, init) => {
4604
+ const method = modernNotificationMethod(init);
4605
+ if (method === void 0) return baseFetch(input, init);
4606
+ const headers = new Headers(init?.headers);
4607
+ headers.set("mcp-protocol-version", method.version);
4608
+ headers.set("mcp-method", method.method);
4609
+ return baseFetch(input, { ...init, headers });
4610
+ };
4611
+ }
4612
+ function modernNotificationMethod(init) {
4613
+ if (init?.method?.toUpperCase() !== "POST") return void 0;
4614
+ if (typeof init.body !== "string") return void 0;
4615
+ let message;
4616
+ try {
4617
+ message = JSON.parse(init.body);
4618
+ } catch {
4619
+ return void 0;
4620
+ }
4621
+ if (!isRecord2(message) || "id" in message) return void 0;
4622
+ if (typeof message.method !== "string") return void 0;
4623
+ const meta = isRecord2(message.params) ? message.params._meta : void 0;
4624
+ const version = isRecord2(meta) ? meta[PROTOCOL_VERSION_META_KEY] : void 0;
4625
+ if (typeof version !== "string" || version < MODERN_PROTOCOL_VERSION) {
4626
+ return void 0;
4627
+ }
4628
+ return { method: message.method, version };
4629
+ }
4630
+ function isRecord2(value) {
4631
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4632
+ }
4633
+
4634
+ // ../../core/mcp/node/suppressNotificationStreamFetch.ts
4635
+ function createSuppressNotificationStreamFetch(baseFetch, endpoint) {
4636
+ return async (input, init) => {
4637
+ if (isStandaloneStreamRequest(input, init, endpoint)) {
4638
+ return new Response(null, {
4639
+ status: 405,
4640
+ statusText: "Method Not Allowed"
4641
+ });
4642
+ }
4643
+ return baseFetch(input, init);
4644
+ };
4645
+ }
4646
+ function requestUrl(input) {
4647
+ const raw = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
4648
+ try {
4649
+ return new URL(raw);
4650
+ } catch {
4651
+ return void 0;
4652
+ }
4653
+ }
4654
+ function isStandaloneStreamRequest(input, init, endpoint) {
4655
+ const request = input instanceof Request ? input : void 0;
4656
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
4657
+ if (method !== "GET") return false;
4658
+ const url = requestUrl(input);
4659
+ if (!url || url.origin !== endpoint.origin || url.pathname !== endpoint.pathname || url.search !== endpoint.search) {
4660
+ return false;
4661
+ }
4662
+ const headers = new Headers(init?.headers ?? request?.headers);
4663
+ const accept = headers.get("accept")?.toLowerCase() ?? "";
4664
+ return accept.includes("text/event-stream") && !headers.has("last-event-id");
4665
+ }
4666
+
4354
4667
  // ../../core/mcp/node/transport.ts
4355
4668
  function headersFromSettings(settings) {
4356
4669
  if (!settings || settings.headers.length === 0) return void 0;
@@ -4438,10 +4751,14 @@ function createTransportNode(config, options = {}) {
4438
4751
  ...httpConfig.requestInit,
4439
4752
  ...headers && { headers }
4440
4753
  };
4754
+ const stampedFetch = createNotificationHeadersFetch(
4755
+ fetchWithOptionalAuthIntercept
4756
+ );
4757
+ const httpFetch = settings?.suppressNotificationStream === true ? createSuppressNotificationStreamFetch(stampedFetch, url) : stampedFetch;
4441
4758
  const transport = new StreamableHTTPClientTransport(url, {
4442
4759
  authProvider,
4443
4760
  requestInit,
4444
- fetch: fetchWithOptionalAuthIntercept,
4761
+ fetch: httpFetch,
4445
4762
  // SEP-2350: how the transport reacts to a `403 insufficient_scope`
4446
4763
  // challenge. Defaults to the SDK's `reauthorize` when unset.
4447
4764
  ...settings?.oauthOnInsufficientScope && {
@@ -4981,7 +5298,7 @@ var OAuthMemoryStore = class {
4981
5298
  };
4982
5299
 
4983
5300
  // ../../core/auth/oauth-persist.ts
4984
- function isRecord(value) {
5301
+ function isRecord3(value) {
4985
5302
  return typeof value === "object" && value !== null && !Array.isArray(value);
4986
5303
  }
4987
5304
  function snapshotFromPayload(payload) {
@@ -4995,10 +5312,10 @@ function parseOAuthPersistBlob(raw) {
4995
5312
  return null;
4996
5313
  }
4997
5314
  const parsed = typeof raw === "string" ? raw ? parseStore(raw) : null : raw;
4998
- if (!isRecord(parsed)) {
5315
+ if (!isRecord3(parsed)) {
4999
5316
  return null;
5000
5317
  }
5001
- if (isRecord(parsed.state) && "version" in parsed) {
5318
+ if (isRecord3(parsed.state) && "version" in parsed) {
5002
5319
  return snapshotFromPayload(parsed.state);
5003
5320
  }
5004
5321
  if ("servers" in parsed || "idpSessions" in parsed) {
@@ -5462,14 +5779,14 @@ var OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
5462
5779
  function pathName(path5) {
5463
5780
  return path5.length === 0 ? "<root>" : path5.join(".");
5464
5781
  }
5465
- function isRecord2(node) {
5782
+ function isRecord4(node) {
5466
5783
  return node !== null && typeof node === "object";
5467
5784
  }
5468
5785
  function scanXMcpHeaderDeclarations(inputSchema) {
5469
5786
  const declarations = [];
5470
5787
  const seenLower = /* @__PURE__ */ new Map();
5471
5788
  const visit = (node, path5, reachable) => {
5472
- if (!isRecord2(node)) return void 0;
5789
+ if (!isRecord4(node)) return void 0;
5473
5790
  const schema = node;
5474
5791
  if (X_MCP_HEADER_KEY in schema) {
5475
5792
  if (!reachable || path5.length === 0) {
@@ -5495,7 +5812,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
5495
5812
  declarations.push({ path: path5, headerName: raw, type });
5496
5813
  }
5497
5814
  const properties = schema.properties;
5498
- if (isRecord2(properties)) {
5815
+ if (isRecord4(properties)) {
5499
5816
  for (const [key, child] of Object.entries(properties)) {
5500
5817
  const fault2 = visit(child, [...path5, key], reachable);
5501
5818
  if (fault2 !== void 0) return fault2;
@@ -5504,7 +5821,7 @@ function scanXMcpHeaderDeclarations(inputSchema) {
5504
5821
  for (const k of NON_REACHABLE_SUBSCHEMA_KEYWORDS) {
5505
5822
  const sub = schema[k];
5506
5823
  if (sub === void 0) continue;
5507
- const branches = Array.isArray(sub) ? sub : isRecord2(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
5824
+ const branches = Array.isArray(sub) ? sub : isRecord4(sub) && OBJECT_VALUED_SUBSCHEMA_KEYWORDS.has(k) ? Object.values(sub) : [sub];
5508
5825
  for (const branch of branches) {
5509
5826
  const fault2 = visit(branch, [...path5, `<${k}>`], false);
5510
5827
  if (fault2 !== void 0) return fault2;
@@ -5736,7 +6053,7 @@ import {
5736
6053
  LOG_LEVEL_META_KEY,
5737
6054
  CLIENT_CAPABILITIES_META_KEY,
5738
6055
  CLIENT_INFO_META_KEY,
5739
- PROTOCOL_VERSION_META_KEY,
6056
+ PROTOCOL_VERSION_META_KEY as PROTOCOL_VERSION_META_KEY2,
5740
6057
  RELATED_TASK_META_KEY as RELATED_TASK_META_KEY3
5741
6058
  } from "@modelcontextprotocol/client";
5742
6059
 
@@ -6942,7 +7259,7 @@ function withRfc8414OidcCompat(fetchFn) {
6942
7259
  return response;
6943
7260
  }
6944
7261
  if (!probe.ok) {
6945
- await releaseBody(probe);
7262
+ releaseBodyDetached(probe);
6946
7263
  if (callerSignal?.aborted) {
6947
7264
  releaseBodyDetached(response);
6948
7265
  throw callerSignal.reason;
@@ -6966,7 +7283,7 @@ function withRfc8414OidcCompat(fetchFn) {
6966
7283
  return response;
6967
7284
  }
6968
7285
  const source = probe.url || candidate;
6969
- await releaseBody(response);
7286
+ releaseBodyDetached(response);
6970
7287
  console.warn(
6971
7288
  `[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.`
6972
7289
  );
@@ -11521,7 +11838,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11521
11838
  ...params,
11522
11839
  _meta: {
11523
11840
  ...existingMeta,
11524
- [PROTOCOL_VERSION_META_KEY]: protocolVersion,
11841
+ [PROTOCOL_VERSION_META_KEY2]: protocolVersion,
11525
11842
  [CLIENT_INFO_META_KEY]: this.clientInfo,
11526
11843
  [CLIENT_CAPABILITIES_META_KEY]: clientCapabilities
11527
11844
  }
@@ -14998,7 +15315,7 @@ var BOOLEAN_VALUED_KEYWORDS = /* @__PURE__ */ new Set([
14998
15315
  "additionalItems",
14999
15316
  "unevaluatedItems"
15000
15317
  ]);
15001
- var SUBSCHEMA_KEYWORDS = [
15318
+ var SUBSCHEMA_KEYWORDS2 = [
15002
15319
  "items",
15003
15320
  "contains",
15004
15321
  "not",
@@ -15015,13 +15332,13 @@ var SUBSCHEMA_KEYWORDS = [
15015
15332
  // real finding.
15016
15333
  "contentSchema"
15017
15334
  ];
15018
- var SUBSCHEMA_ARRAY_KEYWORDS = [
15335
+ var SUBSCHEMA_ARRAY_KEYWORDS2 = [
15019
15336
  "allOf",
15020
15337
  "anyOf",
15021
15338
  "oneOf",
15022
15339
  "prefixItems"
15023
15340
  ];
15024
- var SUBSCHEMA_MAP_KEYWORDS = [
15341
+ var SUBSCHEMA_MAP_KEYWORDS2 = [
15025
15342
  "properties",
15026
15343
  "patternProperties",
15027
15344
  "dependentSchemas",
@@ -15103,8 +15420,8 @@ function isPortableTypeUnion(type) {
15103
15420
  }
15104
15421
  return new Set(type).size === type.length;
15105
15422
  }
15106
- var MAX_DEPTH = 64;
15107
- function isRecord3(value) {
15423
+ var MAX_DEPTH2 = 64;
15424
+ function isRecord5(value) {
15108
15425
  return typeof value === "object" && value !== null && !Array.isArray(value);
15109
15426
  }
15110
15427
  function childPath(parent, key) {
@@ -15120,7 +15437,7 @@ function describeSchemaPath(schema, path5) {
15120
15437
  return path5 === "" ? schema : `${schema}.${path5}`;
15121
15438
  }
15122
15439
  function declaresAnyId(node, depth = 0) {
15123
- if (depth > MAX_DEPTH || !isRecord3(node)) return false;
15440
+ if (depth > MAX_DEPTH2 || !isRecord5(node)) return false;
15124
15441
  if (typeof node.$id === "string" && node.$id !== "") return true;
15125
15442
  let found = false;
15126
15443
  forEachSubschema(node, "", (child) => {
@@ -15139,21 +15456,21 @@ function add(ctx, rule, severity, path5, issue, suggestion) {
15139
15456
  });
15140
15457
  }
15141
15458
  function forEachSubschema(node, path5, visit) {
15142
- for (const key of SUBSCHEMA_MAP_KEYWORDS) {
15459
+ for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
15143
15460
  const map = node[key];
15144
- if (!isRecord3(map)) continue;
15461
+ if (!isRecord5(map)) continue;
15145
15462
  for (const [name, sub] of Object.entries(map)) {
15146
15463
  visit(sub, childPath(childPath(path5, key), name), key);
15147
15464
  }
15148
15465
  }
15149
- for (const key of SUBSCHEMA_ARRAY_KEYWORDS) {
15466
+ for (const key of SUBSCHEMA_ARRAY_KEYWORDS2) {
15150
15467
  const arr = node[key];
15151
15468
  if (!Array.isArray(arr)) continue;
15152
15469
  arr.forEach((sub, i) => {
15153
15470
  visit(sub, indexPath(childPath(path5, key), i), key);
15154
15471
  });
15155
15472
  }
15156
- for (const key of SUBSCHEMA_KEYWORDS) {
15473
+ for (const key of SUBSCHEMA_KEYWORDS2) {
15157
15474
  if (!(key in node)) continue;
15158
15475
  const value = node[key];
15159
15476
  if (key === "items" && Array.isArray(value)) {
@@ -15172,7 +15489,7 @@ function booleanTrueSuggestion(keyword) {
15172
15489
  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.';
15173
15490
  }
15174
15491
  function walk(node, path5, keyword, depth, ctx) {
15175
- if (depth > MAX_DEPTH) return;
15492
+ if (depth > MAX_DEPTH2) return;
15176
15493
  if (typeof node === "boolean") {
15177
15494
  if (keyword !== void 0 && BOOLEAN_VALUED_KEYWORDS.has(keyword)) return;
15178
15495
  add(
@@ -15191,12 +15508,13 @@ function walk(node, path5, keyword, depth, ctx) {
15191
15508
  );
15192
15509
  return;
15193
15510
  }
15194
- if (!isRecord3(node)) return;
15511
+ if (!isRecord5(node)) return;
15195
15512
  lintNode(node, path5, keyword, ctx);
15196
15513
  forEachSubschema(node, path5, (child, childPathValue, childKeyword) => {
15197
15514
  walk(child, childPathValue, childKeyword, depth + 1, ctx);
15198
15515
  });
15199
15516
  }
15517
+ 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.";
15200
15518
  function lintNode(node, path5, keyword, ctx) {
15201
15519
  const type = node.type;
15202
15520
  if (Array.isArray(type) && isPortableTypeUnion(type)) {
@@ -15205,10 +15523,10 @@ function lintNode(node, path5, keyword, ctx) {
15205
15523
  "type-union",
15206
15524
  "warning",
15207
15525
  path5,
15208
- `\`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.`,
15526
+ `\`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.`,
15209
15527
  `Split it into \`anyOf\` branches, each with a single \`type\` \u2014 \`{"anyOf": [${type.map((t) => `{"type": "${t}"}`).join(
15210
15528
  ", "
15211
- )}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)`
15529
+ )}]}\`. (Making the property optional instead is a different contract: absent is not the same as \`null\`.)${type.includes("null") ? NULL_BRANCH_CAVEAT : ""}`
15212
15530
  );
15213
15531
  }
15214
15532
  const ref = node.$ref;
@@ -17764,7 +18082,7 @@ async function withCliAuthRecoveryRetry(inspectorClient, serverConfig, redirectU
17764
18082
  import { LoggingLevelSchema } from "@modelcontextprotocol/core";
17765
18083
 
17766
18084
  // ../../core/node/version.ts
17767
- import { readFileSync as readFileSync3 } from "fs";
18085
+ import { readFileSync as readFileSync4 } from "fs";
17768
18086
  import { dirname as dirname5, join as join3, parse } from "path";
17769
18087
  import { fileURLToPath } from "url";
17770
18088
  var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
@@ -17776,7 +18094,7 @@ function readInspectorVersion(callerUrl) {
17776
18094
  let manifest;
17777
18095
  try {
17778
18096
  manifest = JSON.parse(
17779
- readFileSync3(join3(dir, "package.json"), "utf-8")
18097
+ readFileSync4(join3(dir, "package.json"), "utf-8")
17780
18098
  );
17781
18099
  } catch {
17782
18100
  }