@modelcontextprotocol/inspector 2.10.1 → 2.11.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.
@@ -2044,16 +2044,30 @@ function buildConfigFromOptions(options) {
2044
2044
  const target = options.target ?? [];
2045
2045
  const first = target[0];
2046
2046
  const rest = target.slice(1);
2047
+ if (first && options.serverUrl) {
2048
+ throw new Error(
2049
+ `Cannot combine a positional target (${first}) with --server-url (${options.serverUrl}). Pass one or the other.`
2050
+ );
2051
+ }
2047
2052
  const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
2048
2053
  const url = urlFromTarget ?? options.serverUrl ?? null;
2049
2054
  if (url) {
2050
- if (rest.length > 0 && urlFromTarget) {
2055
+ if (rest.length > 0) {
2051
2056
  throw new Error("Arguments cannot be passed to a URL-based MCP server.");
2052
2057
  }
2058
+ if (options.transport === "stdio") {
2059
+ throw new Error(
2060
+ `stdio transport cannot be used with a URL (${url}). Use --transport http or --transport sse, or pass a local command instead.`
2061
+ );
2062
+ }
2053
2063
  let transportType;
2054
2064
  const t = options.transport === "http" ? "streamable-http" : options.transport;
2055
2065
  if (t === "sse" || t === "streamable-http") {
2056
2066
  transportType = t;
2067
+ } else if (t) {
2068
+ throw new Error(
2069
+ `Invalid transport type: ${t}. Valid types are: sse, http, stdio`
2070
+ );
2057
2071
  } else {
2058
2072
  const u = new URL(url);
2059
2073
  if (u.pathname.endsWith("/mcp")) {
@@ -4552,7 +4566,10 @@ function parseAuthChallengeFromResponse(response, context) {
4552
4566
  if (status !== 401 && status !== 403) {
4553
4567
  return void 0;
4554
4568
  }
4555
- const wwwAuthenticate = response.headers.get("WWW-Authenticate") ?? void 0;
4569
+ const wwwAuthenticate = response.headers.get("WWW-Authenticate") || void 0;
4570
+ if (status === 403 && !wwwAuthenticate) {
4571
+ return void 0;
4572
+ }
4556
4573
  const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
4557
4574
  const requiredScopes = parseScopeString(bearer.scope);
4558
4575
  return {
@@ -5249,6 +5266,34 @@ function buildRunnerClientAuthOptions(clientConfig, savedSettings, cliOverrides)
5249
5266
  };
5250
5267
  }
5251
5268
 
5269
+ // ../../core/node/version.ts
5270
+ import { readFileSync as readFileSync4 } from "fs";
5271
+ import { dirname as dirname5, join as join3, parse } from "path";
5272
+ import { fileURLToPath } from "url";
5273
+ var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
5274
+ function readInspectorVersion(callerUrl) {
5275
+ const startDir = dirname5(fileURLToPath(callerUrl));
5276
+ const { root } = parse(startDir);
5277
+ let dir = startDir;
5278
+ for (; ; ) {
5279
+ let manifest;
5280
+ try {
5281
+ manifest = JSON.parse(
5282
+ readFileSync4(join3(dir, "package.json"), "utf-8")
5283
+ );
5284
+ } catch {
5285
+ }
5286
+ if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
5287
+ return manifest.version;
5288
+ }
5289
+ if (dir === root) break;
5290
+ dir = dirname5(dir);
5291
+ }
5292
+ throw new Error(
5293
+ `Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
5294
+ );
5295
+ }
5296
+
5252
5297
  // ../../core/node/hostUrl.ts
5253
5298
  import { isIPv6 } from "net";
5254
5299
  function stripBrackets(host) {
@@ -6923,6 +6968,84 @@ function isJsonDocumentResponse(contentType) {
6923
6968
  const mediaType = contentType.split(";")[0].trim().toLowerCase();
6924
6969
  return mediaType === "application/json" || mediaType.endsWith("+json");
6925
6970
  }
6971
+ var SYNTHESIZED_METADATA_HEADER = "x-inspector-oauth-metadata-synthesized";
6972
+ var synthesizedResponses = /* @__PURE__ */ new WeakSet();
6973
+ function isSynthesizedMetadataResponse(response) {
6974
+ return synthesizedResponses.has(response);
6975
+ }
6976
+ var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
6977
+ var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
6978
+ function continuesDiscovery(status) {
6979
+ return status < 500 || status === 502;
6980
+ }
6981
+ function authorizationServerForFinalCandidate(url) {
6982
+ let parsed;
6983
+ try {
6984
+ parsed = new URL(url);
6985
+ } catch {
6986
+ return void 0;
6987
+ }
6988
+ const { pathname } = parsed;
6989
+ if (pathname === OIDC_WELL_KNOWN) return parsed.origin;
6990
+ if (!pathname.endsWith(OIDC_WELL_KNOWN)) return void 0;
6991
+ if (pathname.startsWith(`${RFC8414_WELL_KNOWN}/`) || pathname.startsWith(`${OIDC_WELL_KNOWN}/`)) {
6992
+ return void 0;
6993
+ }
6994
+ return `${parsed.origin}${pathname.slice(0, -OIDC_WELL_KNOWN.length)}`;
6995
+ }
6996
+ function synthesizeAuthorizationServerMetadata(issuer, overrides) {
6997
+ return {
6998
+ issuer,
6999
+ authorization_endpoint: overrides.authorizationUrl ?? new URL("/authorize", issuer).href,
7000
+ token_endpoint: overrides.tokenUrl ?? new URL("/token", issuer).href,
7001
+ registration_endpoint: new URL("/register", issuer).href,
7002
+ response_types_supported: ["code"],
7003
+ code_challenge_methods_supported: ["S256"]
7004
+ };
7005
+ }
7006
+ function requestMethodOf(input, init) {
7007
+ if (init?.method) return init.method.toUpperCase();
7008
+ if (typeof input !== "string" && !(input instanceof URL)) {
7009
+ return input.method.toUpperCase();
7010
+ }
7011
+ return "GET";
7012
+ }
7013
+ function acceptsJson(input, init) {
7014
+ let accept = null;
7015
+ if (init?.headers) {
7016
+ accept = new Headers(init.headers).get("accept");
7017
+ } else if (typeof input !== "string" && !(input instanceof URL)) {
7018
+ accept = input.headers.get("accept");
7019
+ }
7020
+ if (!accept) return false;
7021
+ const mediaTypes = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
7022
+ return mediaTypes.includes("application/json") && !mediaTypes.includes("text/event-stream");
7023
+ }
7024
+ function synthesizedMetadataResponse(input, init, response, overrides) {
7025
+ if (!continuesDiscovery(response.status)) return void 0;
7026
+ if (requestMethodOf(input, init) !== "GET") return void 0;
7027
+ if (!acceptsJson(input, init)) return void 0;
7028
+ const issuer = authorizationServerForFinalCandidate(requestUrlOf(input));
7029
+ if (!issuer) return void 0;
7030
+ void response.body?.cancel().catch(() => {
7031
+ });
7032
+ console.warn(
7033
+ `[oauth] ${issuer} publishes no authorization server metadata; using the configured endpoint override(s) in place of the SDK's /authorize and /token fallbacks.`
7034
+ );
7035
+ const synthesized = new Response(
7036
+ JSON.stringify(synthesizeAuthorizationServerMetadata(issuer, overrides)),
7037
+ {
7038
+ status: 200,
7039
+ statusText: "OK",
7040
+ headers: {
7041
+ "content-type": "application/json",
7042
+ [SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
7043
+ }
7044
+ }
7045
+ );
7046
+ synthesizedResponses.add(synthesized);
7047
+ return synthesized;
7048
+ }
6926
7049
  function responseWithBody(response, body) {
6927
7050
  const headers = new Headers(response.headers);
6928
7051
  headers.delete("content-length");
@@ -6950,7 +7073,9 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
6950
7073
  const overrides = resolveOverrides();
6951
7074
  if (!overrides) return response;
6952
7075
  if (!isAuthorizationServerMetadataRequest(input)) return response;
6953
- if (!response.ok) return response;
7076
+ if (!response.ok) {
7077
+ return synthesizedMetadataResponse(input, init, response, overrides) ?? response;
7078
+ }
6954
7079
  if (!isJsonDocumentResponse(response.headers.get("content-type")))
6955
7080
  return response;
6956
7081
  let parsed;
@@ -7126,8 +7251,8 @@ import {
7126
7251
  OAuthMetadataSchema,
7127
7252
  OpenIdProviderDiscoveryMetadataSchema
7128
7253
  } from "@modelcontextprotocol/core";
7129
- var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
7130
- var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
7254
+ var RFC8414_WELL_KNOWN2 = "/.well-known/oauth-authorization-server";
7255
+ var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
7131
7256
  var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
7132
7257
  function oidcDiscoveryCandidates(rfc8414Url) {
7133
7258
  let url;
@@ -7136,16 +7261,16 @@ function oidcDiscoveryCandidates(rfc8414Url) {
7136
7261
  } catch {
7137
7262
  return [];
7138
7263
  }
7139
- if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
7140
- const path7 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
7264
+ if (!url.pathname.startsWith(RFC8414_WELL_KNOWN2)) return [];
7265
+ const path7 = url.pathname.slice(RFC8414_WELL_KNOWN2.length);
7141
7266
  if (path7 !== "" && !path7.startsWith("/")) return [];
7142
- if (path7 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
7267
+ if (path7 === "") return [new URL(OIDC_WELL_KNOWN2, url.origin).href];
7143
7268
  return [
7144
- new URL(`${OIDC_WELL_KNOWN}${path7}`, url.origin).href,
7145
- new URL(`${path7}${OIDC_WELL_KNOWN}`, url.origin).href
7269
+ new URL(`${OIDC_WELL_KNOWN2}${path7}`, url.origin).href,
7270
+ new URL(`${path7}${OIDC_WELL_KNOWN2}`, url.origin).href
7146
7271
  ];
7147
7272
  }
7148
- function continuesDiscovery(status) {
7273
+ function continuesDiscovery2(status) {
7149
7274
  return status >= 400 && status < 500 || status === 502;
7150
7275
  }
7151
7276
  async function releaseBody(response) {
@@ -7171,7 +7296,7 @@ function discoveryHeaders(input, init) {
7171
7296
  }
7172
7297
  return new Headers();
7173
7298
  }
7174
- function requestMethodOf(input, init) {
7299
+ function requestMethodOf2(input, init) {
7175
7300
  if (init?.method) return init.method.toUpperCase();
7176
7301
  if (typeof input !== "string" && !(input instanceof URL)) {
7177
7302
  return input.method.toUpperCase();
@@ -7182,8 +7307,8 @@ function withRfc8414OidcCompat(fetchFn) {
7182
7307
  return async (input, init) => {
7183
7308
  const response = await fetchFn(input, init);
7184
7309
  if (response.ok) return response;
7185
- if (!continuesDiscovery(response.status)) return response;
7186
- if (requestMethodOf(input, init) !== "GET") return response;
7310
+ if (!continuesDiscovery2(response.status)) return response;
7311
+ if (requestMethodOf2(input, init) !== "GET") return response;
7187
7312
  const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
7188
7313
  if (candidates.length === 0) return response;
7189
7314
  const headers = discoveryHeaders(input, init);
@@ -7213,7 +7338,7 @@ function withRfc8414OidcCompat(fetchFn) {
7213
7338
  releaseBodyDetached(response);
7214
7339
  throw callerSignal.reason;
7215
7340
  }
7216
- if (continuesDiscovery(probe.status)) continue;
7341
+ if (continuesDiscovery2(probe.status)) continue;
7217
7342
  return response;
7218
7343
  }
7219
7344
  let parsed;
@@ -7233,6 +7358,16 @@ function withRfc8414OidcCompat(fetchFn) {
7233
7358
  }
7234
7359
  const source = probe.url || candidate;
7235
7360
  releaseBodyDetached(response);
7361
+ if (isSynthesizedMetadataResponse(probe)) {
7362
+ return new Response(body, {
7363
+ status: 200,
7364
+ statusText: "OK",
7365
+ headers: {
7366
+ "content-type": "application/json",
7367
+ [SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
7368
+ }
7369
+ });
7370
+ }
7236
7371
  console.warn(
7237
7372
  `[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.`
7238
7373
  );
@@ -8213,10 +8348,10 @@ async function resolveIdpMetadata(issuer, storage, fetchFn) {
8213
8348
  }
8214
8349
  return discoverIdpMetadata(issuer, fetchFn);
8215
8350
  }
8216
- var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
8351
+ var OIDC_WELL_KNOWN3 = "/.well-known/openid-configuration";
8217
8352
  async function fetchOpenIdConfiguration(issuer, issuerUrl, fetchFn) {
8218
8353
  const url = new URL(
8219
- `${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN2}`,
8354
+ `${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN3}`,
8220
8355
  issuerUrl.origin
8221
8356
  );
8222
8357
  try {
@@ -9799,7 +9934,11 @@ var OAuthManager = class {
9799
9934
  };
9800
9935
 
9801
9936
  // ../../core/mcp/remote/remoteClientTransport.ts
9802
- import { SdkError as SdkError5, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
9937
+ import {
9938
+ SdkError as SdkError5,
9939
+ SdkErrorCode as SdkErrorCode5,
9940
+ SdkHttpError
9941
+ } from "@modelcontextprotocol/client";
9803
9942
 
9804
9943
  // ../../core/mcp/remote/types.ts
9805
9944
  function oauthTokensToRemoteAuthState(tokens) {
@@ -9854,6 +9993,17 @@ function isConnectAuthChallenge(json) {
9854
9993
  function isConnectTransportError(json) {
9855
9994
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
9856
9995
  }
9996
+ var SDK_ERROR_CODES = new Set(
9997
+ Object.values(SdkErrorCode5)
9998
+ );
9999
+ function toSdkHttpError(message, http) {
10000
+ const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode5.ClientHttpNotImplemented;
10001
+ return new SdkHttpError(code, message, {
10002
+ status: http.status,
10003
+ ...http.statusText !== void 0 && { statusText: http.statusText },
10004
+ ...http.text !== void 0 && { text: http.text }
10005
+ });
10006
+ }
9857
10007
  function legacySessionId(json) {
9858
10008
  if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
9859
10009
  return json.sessionId;
@@ -10354,6 +10504,9 @@ var RemoteClientTransport = class {
10354
10504
  });
10355
10505
  return;
10356
10506
  }
10507
+ if (json.http) {
10508
+ throw toSdkHttpError(json.error, json.http);
10509
+ }
10357
10510
  throw new Error(json.error);
10358
10511
  }
10359
10512
  async requestSend(message, options) {
@@ -10426,8 +10579,9 @@ var corePackageJson = {
10426
10579
  version: "0.0.0"
10427
10580
  };
10428
10581
  var MAX_URL_ELICITATION_RETRIES = 5;
10429
- function connectionTimeoutMessage(timeoutMs) {
10430
- return `Connection timed out after ${timeoutMs} ms. To accommodate a slower server, you may increase the timeout value in Server Settings.`;
10582
+ var WEB_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, you may increase the timeout value in Server Settings.";
10583
+ function connectionTimeoutMessage(timeoutMs, remedy = WEB_CONNECTION_TIMEOUT_REMEDY) {
10584
+ return `Connection timed out after ${timeoutMs} ms. ${remedy}`;
10431
10585
  }
10432
10586
  function createPendingAbortError() {
10433
10587
  return new Error("Pending request aborted");
@@ -10518,6 +10672,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10518
10672
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
10519
10673
  ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
10520
10674
  pipeStderr;
10675
+ connectionTimeoutRemedy;
10521
10676
  initialLoggingLevel;
10522
10677
  // Modern-era per-request log level (#1629). On 2026-07-28 servers
10523
10678
  // `logging/setLevel` is gone; the client opts into logs per request via the
@@ -10755,6 +10910,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10755
10910
  this.fetchFn = options.environment.fetch;
10756
10911
  this.logger = options.environment.logger ?? silentLogger;
10757
10912
  this.pipeStderr = options.pipeStderr ?? false;
10913
+ this.connectionTimeoutRemedy = options.connectionTimeoutRemedy ?? WEB_CONNECTION_TIMEOUT_REMEDY;
10758
10914
  this.initialLoggingLevel = options.initialLoggingLevel;
10759
10915
  this.sample = options.sample ?? true;
10760
10916
  this.elicit = options.elicit ?? true;
@@ -11496,9 +11652,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11496
11652
  // for that upstream gap and should be deleted with it.
11497
11653
  //
11498
11654
  // Known, accepted side effect of turning intercept on with no stored
11499
- // tokens: `parseAuthChallengeFromResponse` treats 403 as a challenge
11500
- // too, so a probe answered 403 for a *non-auth* reason (a gateway
11501
- // rejecting the unknown `server/discover` method, say) now starts OAuth
11655
+ // tokens: `parseAuthChallengeFromResponse` treats a 403 carrying
11656
+ // `WWW-Authenticate` as a challenge too, so a probe answered that way
11657
+ // for a *non-auth* reason (a gateway rejecting the unknown
11658
+ // `server/discover` method, say) now starts OAuth
11502
11659
  // discovery instead of letting "auto" fall back to the legacy
11503
11660
  // `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
11504
11661
  // it goes away with this clause.
@@ -11565,7 +11722,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11565
11722
  const timeoutPromise = new Promise((_, reject) => {
11566
11723
  timer = setTimeout(() => {
11567
11724
  connectTimedOut = true;
11568
- reject(new Error(connectionTimeoutMessage(connectTimeoutMs)));
11725
+ reject(
11726
+ new Error(
11727
+ connectionTimeoutMessage(
11728
+ connectTimeoutMs,
11729
+ this.connectionTimeoutRemedy
11730
+ )
11731
+ )
11732
+ );
11569
11733
  }, connectTimeoutMs);
11570
11734
  });
11571
11735
  try {
@@ -14843,6 +15007,11 @@ var ManagedListState = class extends TypedEventTarget {
14843
15007
  // rendered by the list panel instead of vanishing into an unhandled
14844
15008
  // rejection — which read as "this server has no tools" (#1953).
14845
15009
  error = null;
15010
+ // Whether a fetch has committed a list this session. Until it has, the empty
15011
+ // `items` mean "not loaded yet", not "the server has none" — a distinction a
15012
+ // count of the list cannot make from `items` alone (#2607). Set and cleared
15013
+ // just before the `*Change` dispatch, so a reader keyed on that event sees it.
15014
+ loaded = false;
14846
15015
  config;
14847
15016
  // Debounce a burst of `list_changed` notifications into a single
14848
15017
  // refresh (or one indicator light) once it settles.
@@ -14878,6 +15047,7 @@ var ManagedListState = class extends TypedEventTarget {
14878
15047
  this.listChangedTimer = null;
14879
15048
  }
14880
15049
  this.items = [];
15050
+ this.loaded = false;
14881
15051
  this.dispatchChange();
14882
15052
  this.setListChanged(false);
14883
15053
  this.setError(null);
@@ -14936,6 +15106,14 @@ var ManagedListState = class extends TypedEventTarget {
14936
15106
  getItems() {
14937
15107
  return [...this.items];
14938
15108
  }
15109
+ /**
15110
+ * Whether a fetch has committed a list since the last connect. False while
15111
+ * the connect-time load is in flight, when it was deferred (paginated mode),
15112
+ * and after a disconnect. Changes only alongside the `*Change` event (#2607).
15113
+ */
15114
+ isLoaded() {
15115
+ return this.loaded;
15116
+ }
14939
15117
  /** Whether a `list_changed` arrived since the last refresh (indicator on). */
14940
15118
  getListChanged() {
14941
15119
  return this.listChanged;
@@ -15029,6 +15207,7 @@ var ManagedListState = class extends TypedEventTarget {
15029
15207
  /** Commit a fetched list as the current one and notify subscribers. */
15030
15208
  applyItems(items) {
15031
15209
  this.items = items;
15210
+ this.loaded = true;
15032
15211
  this.dispatchChange();
15033
15212
  }
15034
15213
  dispatchChange() {
@@ -15039,6 +15218,7 @@ var ManagedListState = class extends TypedEventTarget {
15039
15218
  this.unsubscribe?.();
15040
15219
  this.unsubscribe = null;
15041
15220
  this.items = [];
15221
+ this.loaded = false;
15042
15222
  }
15043
15223
  };
15044
15224
 
@@ -16087,6 +16267,7 @@ function useListError(state) {
16087
16267
  var NO_TOOLS = [];
16088
16268
  var readTools = (state) => state.getTools();
16089
16269
  var readListChanged = (state) => state.getListChanged();
16270
+ var readLoaded = (state) => state.isLoaded();
16090
16271
  function useManagedTools(client, managedToolsState) {
16091
16272
  const tools = useStoreSnapshot(
16092
16273
  managedToolsState,
@@ -16100,6 +16281,12 @@ function useManagedTools(client, managedToolsState) {
16100
16281
  readListChanged,
16101
16282
  false
16102
16283
  );
16284
+ const loaded = useStoreSnapshot(
16285
+ managedToolsState,
16286
+ "toolsChange",
16287
+ readLoaded,
16288
+ false
16289
+ );
16103
16290
  const error = useListError(managedToolsState);
16104
16291
  const refresh = useCallback4(async () => {
16105
16292
  if (!managedToolsState || !client) return NO_TOOLS;
@@ -16109,7 +16296,7 @@ function useManagedTools(client, managedToolsState) {
16109
16296
  const clearListChanged = useCallback4(() => {
16110
16297
  managedToolsState?.clearListChanged();
16111
16298
  }, [managedToolsState]);
16112
- return { tools, error, listChanged, refresh, clearListChanged };
16299
+ return { tools, error, listChanged, loaded, refresh, clearListChanged };
16113
16300
  }
16114
16301
 
16115
16302
  // ../../core/react/useManagedResources.ts
@@ -16117,6 +16304,7 @@ import { useCallback as useCallback5 } from "react";
16117
16304
  var NO_RESOURCES = [];
16118
16305
  var readResources = (state) => state.getResources();
16119
16306
  var readListChanged2 = (state) => state.getListChanged();
16307
+ var readLoaded2 = (state) => state.isLoaded();
16120
16308
  function useManagedResources(client, managedResourcesState) {
16121
16309
  const resources = useStoreSnapshot(
16122
16310
  managedResourcesState,
@@ -16130,6 +16318,12 @@ function useManagedResources(client, managedResourcesState) {
16130
16318
  readListChanged2,
16131
16319
  false
16132
16320
  );
16321
+ const loaded = useStoreSnapshot(
16322
+ managedResourcesState,
16323
+ "resourcesChange",
16324
+ readLoaded2,
16325
+ false
16326
+ );
16133
16327
  const error = useListError(managedResourcesState);
16134
16328
  const refresh = useCallback5(async () => {
16135
16329
  if (!managedResourcesState || !client) return NO_RESOURCES;
@@ -16139,7 +16333,7 @@ function useManagedResources(client, managedResourcesState) {
16139
16333
  const clearListChanged = useCallback5(() => {
16140
16334
  managedResourcesState?.clearListChanged();
16141
16335
  }, [managedResourcesState]);
16142
- return { resources, error, listChanged, refresh, clearListChanged };
16336
+ return { resources, error, listChanged, loaded, refresh, clearListChanged };
16143
16337
  }
16144
16338
 
16145
16339
  // ../../core/react/useManagedResourceTemplates.ts
@@ -16168,6 +16362,7 @@ import { useCallback as useCallback7 } from "react";
16168
16362
  var NO_PROMPTS = [];
16169
16363
  var readPrompts = (state) => state.getPrompts();
16170
16364
  var readListChanged3 = (state) => state.getListChanged();
16365
+ var readLoaded3 = (state) => state.isLoaded();
16171
16366
  function useManagedPrompts(client, managedPromptsState) {
16172
16367
  const prompts = useStoreSnapshot(
16173
16368
  managedPromptsState,
@@ -16181,6 +16376,12 @@ function useManagedPrompts(client, managedPromptsState) {
16181
16376
  readListChanged3,
16182
16377
  false
16183
16378
  );
16379
+ const loaded = useStoreSnapshot(
16380
+ managedPromptsState,
16381
+ "promptsChange",
16382
+ readLoaded3,
16383
+ false
16384
+ );
16184
16385
  const error = useListError(managedPromptsState);
16185
16386
  const refresh = useCallback7(async () => {
16186
16387
  if (!managedPromptsState || !client) return NO_PROMPTS;
@@ -16190,7 +16391,7 @@ function useManagedPrompts(client, managedPromptsState) {
16190
16391
  const clearListChanged = useCallback7(() => {
16191
16392
  managedPromptsState?.clearListChanged();
16192
16393
  }, [managedPromptsState]);
16193
- return { prompts, error, listChanged, refresh, clearListChanged };
16394
+ return { prompts, error, listChanged, loaded, refresh, clearListChanged };
16194
16395
  }
16195
16396
 
16196
16397
  // ../../core/react/useManagedSkills.ts
@@ -16367,34 +16568,6 @@ function classifyAuthorizationServerMismatch(err) {
16367
16568
  };
16368
16569
  }
16369
16570
 
16370
- // ../../core/node/version.ts
16371
- import { readFileSync as readFileSync4 } from "fs";
16372
- import { dirname as dirname5, join as join3, parse } from "path";
16373
- import { fileURLToPath } from "url";
16374
- var ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
16375
- function readInspectorVersion(callerUrl) {
16376
- const startDir = dirname5(fileURLToPath(callerUrl));
16377
- const { root } = parse(startDir);
16378
- let dir = startDir;
16379
- for (; ; ) {
16380
- let manifest;
16381
- try {
16382
- manifest = JSON.parse(
16383
- readFileSync4(join3(dir, "package.json"), "utf-8")
16384
- );
16385
- } catch {
16386
- }
16387
- if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
16388
- return manifest.version;
16389
- }
16390
- if (dir === root) break;
16391
- dir = dirname5(dir);
16392
- }
16393
- throw new Error(
16394
- `Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`
16395
- );
16396
- }
16397
-
16398
16571
  // ../../core/auth/oauth-storage.ts
16399
16572
  import {
16400
16573
  OAuthClientInformationSchema as OAuthClientInformationSchema2,
@@ -17300,6 +17473,17 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
17300
17473
  warnLedgerFailure("update", error);
17301
17474
  }
17302
17475
  }
17476
+ async function recordedNamespaces(stateFilePath) {
17477
+ try {
17478
+ const ledger = parseLedger(
17479
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
17480
+ );
17481
+ return [...ledger.keys()].reverse();
17482
+ } catch (error) {
17483
+ warnLedgerFailure("read", error);
17484
+ return [];
17485
+ }
17486
+ }
17303
17487
  async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
17304
17488
  let ledger;
17305
17489
  let location;
@@ -17463,24 +17647,13 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17463
17647
  if (raw !== null && snapshot === null) {
17464
17648
  throw new OAuthStateFileUnrecognizedError(filePath, "save");
17465
17649
  }
17466
- if (locked) {
17467
- await purgeSupersededNamespaces(filePath, secretStore, void 0);
17468
- }
17469
17650
  const namespace = newSecretsNamespace();
17470
- if (snapshot === null) return namespace;
17471
- const moves = [
17472
- ...Object.entries(snapshot.servers).map(([url, state]) => ({
17473
- legacyId: oauthSecretServerId(url),
17474
- scopedId: oauthSecretServerId(url, namespace),
17475
- fields: serverSecretFields(state)
17476
- })),
17477
- ...Object.keys(snapshot.idpSessions).map((issuer) => ({
17478
- legacyId: oauthIdpSecretServerId(issuer),
17479
- scopedId: oauthIdpSecretServerId(issuer, namespace),
17480
- fields: [IDP_SESSION_FIELD]
17481
- }))
17482
- ];
17483
- if (moves.length === 0) return namespace;
17651
+ if (snapshot === null || Object.keys(snapshot.servers).length === 0 && Object.keys(snapshot.idpSessions).length === 0) {
17652
+ if (locked) {
17653
+ await purgeSupersededNamespaces(filePath, secretStore, void 0);
17654
+ }
17655
+ return namespace;
17656
+ }
17484
17657
  if (!locked) {
17485
17658
  throw new SecretStoreUnavailableError(
17486
17659
  `Could not save OAuth state: ${filePath} predates per-state-file secret namespaces, and migrating its secret-store entries needs the file lock, which is unavailable here (see the lock warning above). Migrating without it could lose credentials if two processes migrate at once. Nothing was changed; make the lock directory writable and retry \u2014 or, if you cannot, clear this file's stored OAuth state and re-authorize (clearing does not migrate, and the fresh state file mints its namespace without the lock).`
@@ -17495,10 +17668,24 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17495
17668
  );
17496
17669
  const touched = [];
17497
17670
  try {
17498
- for (const { legacyId, scopedId, fields } of moves) {
17499
- for (const field of fields) {
17500
- const value = await secretStoreGetStrict(secretStore, legacyId, field);
17501
- if (value === null) continue;
17671
+ const values = await readEntrySecrets(
17672
+ snapshot,
17673
+ secretStore,
17674
+ void 0,
17675
+ await recordedNamespaces(filePath)
17676
+ );
17677
+ const copies = [
17678
+ ...[...values.servers].map(([url, secrets]) => ({
17679
+ scopedId: oauthSecretServerId(url, namespace),
17680
+ secrets
17681
+ })),
17682
+ ...[...values.idpSessions].map(([issuer, secrets]) => ({
17683
+ scopedId: oauthIdpSecretServerId(issuer, namespace),
17684
+ secrets
17685
+ }))
17686
+ ];
17687
+ for (const { scopedId, secrets } of copies) {
17688
+ for (const [field, value] of Object.entries(secrets)) {
17502
17689
  touched.push({ serverId: scopedId, field, value: null });
17503
17690
  await secretStore.set(scopedId, field, value);
17504
17691
  }
@@ -17508,7 +17695,14 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17508
17695
  await restoreSecretFields(secretStore, touched, warnRestoreFailure);
17509
17696
  throw error;
17510
17697
  }
17511
- for (const { legacyId } of moves) {
17698
+ await purgeSupersededNamespaces(filePath, secretStore, namespace);
17699
+ const legacyIds = [
17700
+ ...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
17701
+ ...Object.keys(snapshot.idpSessions).map(
17702
+ (issuer) => oauthIdpSecretServerId(issuer)
17703
+ )
17704
+ ];
17705
+ for (const legacyId of legacyIds) {
17512
17706
  try {
17513
17707
  await secretStore.deleteAllForServer(legacyId);
17514
17708
  } catch (error) {
@@ -17561,6 +17755,14 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
17561
17755
  return keep;
17562
17756
  }
17563
17757
  async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
17758
+ await writeOAuthSectionsFrom(
17759
+ filePath,
17760
+ async () => snapshot,
17761
+ sections,
17762
+ secretStore
17763
+ );
17764
+ }
17765
+ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secretStore) {
17564
17766
  const policy = getPersistTokensPolicy();
17565
17767
  const durable = await secretStoreIsDurable(secretStore);
17566
17768
  await withOAuthStateLock(filePath, "save", async (locked) => {
@@ -17644,6 +17846,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
17644
17846
  unconfirmed = null;
17645
17847
  namespace = diskNamespace;
17646
17848
  }
17849
+ const snapshot = await resolveSnapshot(disk, namespace);
17647
17850
  const effective = sections ? {
17648
17851
  servers: sections.servers && [...new Set(sections.servers)],
17649
17852
  idpSessions: sections.idpSessions && [
@@ -17677,7 +17880,8 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
17677
17880
  const candidates = [
17678
17881
  .../* @__PURE__ */ new Set([
17679
17882
  ...serverSecretFields(getOwnEntry(disk?.servers, url)),
17680
- ...serverSecretFields(next)
17883
+ ...serverSecretFields(next),
17884
+ ...[...restoreBaseline.values()].filter((prior) => prior.serverId === serverId).map(({ field }) => field)
17681
17885
  ])
17682
17886
  ];
17683
17887
  const entryPrior = await snapshotSecretFields(
@@ -17686,8 +17890,12 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
17686
17890
  candidates
17687
17891
  );
17688
17892
  priorSecrets.push(...entryPrior);
17893
+ const entryPriorValue = new Map(
17894
+ entryPrior.map(({ field, value }) => [field, value])
17895
+ );
17689
17896
  if (next === void 0) {
17690
17897
  for (const field of candidates) {
17898
+ if (entryPriorValue.get(field) === null) continue;
17691
17899
  ourWrites.set(priorKey({ serverId, field }), null);
17692
17900
  }
17693
17901
  await secretStore.deleteAllForServer(serverId);
@@ -17702,10 +17910,9 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
17702
17910
  }
17703
17911
  const { residue, secrets } = splitServerOAuthState(next, policy);
17704
17912
  for (const field of candidates) {
17705
- ourWrites.set(
17706
- priorKey({ serverId, field }),
17707
- secrets[field] ?? null
17708
- );
17913
+ const value = secrets[field] ?? null;
17914
+ if (value === entryPriorValue.get(field)) continue;
17915
+ ourWrites.set(priorKey({ serverId, field }), value);
17709
17916
  }
17710
17917
  setOwnEntry(merged.servers, url, residue);
17711
17918
  if (!durable) {
@@ -17848,26 +18055,52 @@ function secretRequestsFor(snapshot, namespace) {
17848
18055
  }
17849
18056
  return requests;
17850
18057
  }
17851
- async function joinSnapshot(snapshot, secretStore, namespace) {
17852
- const requests = secretRequestsFor(snapshot, namespace);
17853
- const values = requests.length > 0 ? await secretStoreGetManyStrict(secretStore, requests) : {};
18058
+ function fillMissingSecrets(target, source) {
18059
+ for (const [field, value] of Object.entries(source ?? {})) {
18060
+ if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
18061
+ }
18062
+ }
18063
+ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
18064
+ const servers = /* @__PURE__ */ new Map();
18065
+ const idpSessions = /* @__PURE__ */ new Map();
18066
+ for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
18067
+ for (const issuer of Object.keys(snapshot.idpSessions)) {
18068
+ idpSessions.set(issuer, {});
18069
+ }
18070
+ for (const source of [namespace, ...fallbacks]) {
18071
+ const requests = secretRequestsFor(snapshot, source);
18072
+ if (requests.length === 0) break;
18073
+ const values = await secretStoreGetManyStrict(secretStore, requests);
18074
+ for (const [url, target] of servers) {
18075
+ fillMissingSecrets(target, values[oauthSecretServerId(url, source)]);
18076
+ }
18077
+ for (const [issuer, target] of idpSessions) {
18078
+ fillMissingSecrets(
18079
+ target,
18080
+ values[oauthIdpSecretServerId(issuer, source)]
18081
+ );
18082
+ }
18083
+ }
18084
+ return { servers, idpSessions };
18085
+ }
18086
+ async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
18087
+ const values = await readEntrySecrets(
18088
+ snapshot,
18089
+ secretStore,
18090
+ namespace,
18091
+ fallbacks
18092
+ );
17854
18093
  return {
17855
18094
  servers: Object.fromEntries(
17856
18095
  Object.entries(snapshot.servers).map(([url, state]) => [
17857
18096
  url,
17858
- joinServerOAuthState(
17859
- state,
17860
- values[oauthSecretServerId(url, namespace)] ?? {}
17861
- )
18097
+ joinServerOAuthState(state, values.servers.get(url) ?? {})
17862
18098
  ])
17863
18099
  ),
17864
18100
  idpSessions: Object.fromEntries(
17865
18101
  Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
17866
18102
  issuer,
17867
- joinIdpSession(
17868
- session,
17869
- values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
17870
- )
18103
+ joinIdpSession(session, values.idpSessions.get(issuer) ?? {})
17871
18104
  ])
17872
18105
  )
17873
18106
  };
@@ -17934,7 +18167,9 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
17934
18167
  warnMigrationFailure(error);
17935
18168
  }
17936
18169
  }
17937
- return joinSnapshot(snapshot, secretStore, parseSecretsNamespace(raw));
18170
+ const namespace = parseSecretsNamespace(raw);
18171
+ const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
18172
+ return joinSnapshot(snapshot, secretStore, namespace, fallbacks);
17938
18173
  });
17939
18174
  }
17940
18175
  async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
@@ -19069,8 +19304,24 @@ function copyViaOsc52(text, sink) {
19069
19304
  large: payloadBytes > OSC52_LARGE_PAYLOAD_BYTES
19070
19305
  };
19071
19306
  }
19307
+ var copyDirs = /* @__PURE__ */ new Set();
19308
+ var exitCleanupRegistered = false;
19309
+ function removeCopyFiles() {
19310
+ for (const dir of copyDirs) {
19311
+ try {
19312
+ fs5.rmSync(dir, { recursive: true, force: true });
19313
+ } catch {
19314
+ }
19315
+ copyDirs.delete(dir);
19316
+ }
19317
+ }
19072
19318
  function writeCopyFile(text, baseDir = os.tmpdir()) {
19073
19319
  const dir = fs5.mkdtempSync(path6.join(baseDir, "mcp-inspector-copy-"));
19320
+ copyDirs.add(dir);
19321
+ if (!exitCleanupRegistered) {
19322
+ process.on("exit", removeCopyFiles);
19323
+ exitCleanupRegistered = true;
19324
+ }
19074
19325
  const file = path6.join(dir, "value.txt");
19075
19326
  fs5.writeFileSync(file, text, { encoding: "utf8", mode: 384 });
19076
19327
  return file;
@@ -19107,7 +19358,10 @@ function useCopyKeys(value, label) {
19107
19358
  if (key === SAVE_KEY) {
19108
19359
  try {
19109
19360
  const file = writeCopyFile(value);
19110
- setStatus({ tone: "success", message: `Saved ${label} to ${file}` });
19361
+ setStatus({
19362
+ tone: "success",
19363
+ message: `Saved ${label} to ${file} (deleted when the TUI exits)`
19364
+ });
19111
19365
  } catch (err) {
19112
19366
  setStatus({
19113
19367
  tone: "error",
@@ -19516,7 +19770,7 @@ import { useCallback as useCallback14, useEffect as useEffect4, useMemo, useStat
19516
19770
  function printable(input) {
19517
19771
  return input.replace(/[\u0000-\u001f\u007f]/g, "");
19518
19772
  }
19519
- function useListFilter(items, fields, { enabled, onEditingChange }) {
19773
+ function useListFilter(items, fields, { enabled, onEditingChange, onEscapeClaimChange }) {
19520
19774
  const [query, setQuery] = useState7("");
19521
19775
  const [editingRequested, setEditingRequested] = useState7(false);
19522
19776
  const editing = editingRequested && enabled;
@@ -19525,6 +19779,13 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
19525
19779
  onEditingChange?.(true);
19526
19780
  return () => onEditingChange?.(false);
19527
19781
  }, [editing, onEditingChange]);
19782
+ const active = query.trim() !== "";
19783
+ const claimsEscape = enabled && active && !editingRequested;
19784
+ useEffect4(() => {
19785
+ if (!claimsEscape) return;
19786
+ onEscapeClaimChange?.(true);
19787
+ return () => onEscapeClaimChange?.(false);
19788
+ }, [claimsEscape, onEscapeClaimChange]);
19528
19789
  const { filtered, indices } = useMemo(() => {
19529
19790
  const needle = query.trim().toLowerCase();
19530
19791
  const filtered2 = [];
@@ -19545,6 +19806,10 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
19545
19806
  setEditingRequested(true);
19546
19807
  return true;
19547
19808
  }
19809
+ if (key.escape && query.trim() !== "") {
19810
+ setQuery("");
19811
+ return true;
19812
+ }
19548
19813
  return false;
19549
19814
  }
19550
19815
  if (key.escape) {
@@ -19568,12 +19833,12 @@ function useListFilter(items, fields, { enabled, onEditingChange }) {
19568
19833
  if (text) setQuery((prev) => prev + text);
19569
19834
  return true;
19570
19835
  },
19571
- [enabled, editingRequested]
19836
+ [enabled, editingRequested, query]
19572
19837
  );
19573
19838
  return {
19574
19839
  query,
19575
19840
  editing,
19576
- active: query.trim() !== "",
19841
+ active,
19577
19842
  items: filtered,
19578
19843
  indices,
19579
19844
  handleInput
@@ -19593,7 +19858,7 @@ function ListFilterBar({ query, editing, focused }) {
19593
19858
  ] }) : query.trim() !== "" ? /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate-end", children: [
19594
19859
  /* @__PURE__ */ jsx6(Text5, { color: "cyan", children: "/" }),
19595
19860
  query,
19596
- /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: " (/ to edit)" })
19861
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: focused ? " (/ edit \xB7 Esc clear)" : " (/ to edit)" })
19597
19862
  ] }) : focused ? /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "/ to filter" }) : /* @__PURE__ */ jsx6(Text5, { children: " " }) });
19598
19863
  }
19599
19864
  function filterCount(active, shown, total) {
@@ -19618,7 +19883,8 @@ function ResourcesTab({
19618
19883
  onFetchTemplate,
19619
19884
  onAuthRecoveryRequired,
19620
19885
  modalOpen = false,
19621
- onFilterEditingChange
19886
+ onFilterEditingChange,
19887
+ onFilterEscapeClaimChange
19622
19888
  }) {
19623
19889
  const [error, setError] = useState8(null);
19624
19890
  const [resourceContent, setResourceContent] = useState8(null);
@@ -19641,7 +19907,8 @@ function ResourcesTab({
19641
19907
  const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
19642
19908
  const filter = useListFilter(allItems, resourceFilterFields, {
19643
19909
  enabled: !modalOpen && focusedPane === "list",
19644
- onEditingChange: onFilterEditingChange
19910
+ onEditingChange: onFilterEditingChange,
19911
+ onEscapeClaimChange: onFilterEscapeClaimChange
19645
19912
  });
19646
19913
  const shownItems = filter.items;
19647
19914
  const { selectedIndex, firstVisible, setSelection } = useSelectableList(
@@ -19914,12 +20181,14 @@ function PromptsTab({
19914
20181
  onFetchPrompt,
19915
20182
  onAuthRecoveryRequired,
19916
20183
  modalOpen = false,
19917
- onFilterEditingChange
20184
+ onFilterEditingChange,
20185
+ onFilterEscapeClaimChange
19918
20186
  }) {
19919
20187
  const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
19920
20188
  const filter = useListFilter(prompts, promptFilterFields, {
19921
20189
  enabled: !modalOpen && focusedPane === "list",
19922
- onEditingChange: onFilterEditingChange
20190
+ onEditingChange: onFilterEditingChange,
20191
+ onEscapeClaimChange: onFilterEscapeClaimChange
19923
20192
  });
19924
20193
  const shownPrompts = filter.items;
19925
20194
  const { selectedIndex, firstVisible, setSelection } = useSelectableList(
@@ -20137,6 +20406,10 @@ import {
20137
20406
  import { Box as Box9, Text as Text8, useInput as useInput5 } from "ink";
20138
20407
 
20139
20408
  // ../../core/mcp/skillsVerification.ts
20409
+ var DEFAULT_SKILL_BUDGET_HINT = {
20410
+ raise: "with the server's skillCatalogMaxSkills / skillCatalogMaxBytes settings",
20411
+ verifyOne: "by verifying this skill alone"
20412
+ };
20140
20413
  function reasonOf(err) {
20141
20414
  return err instanceof Error ? err.message : String(err);
20142
20415
  }
@@ -20198,7 +20471,7 @@ function responseBytes(result) {
20198
20471
  }
20199
20472
  return total;
20200
20473
  }
20201
- async function verifySkills(client, entries, metadata) {
20474
+ async function verifySkills(client, entries, metadata, budgetHint = DEFAULT_SKILL_BUDGET_HINT) {
20202
20475
  const collisions = checkSkillNameCollisions(entries);
20203
20476
  const reports = [];
20204
20477
  const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
@@ -20210,7 +20483,7 @@ async function verifySkills(client, entries, metadata) {
20210
20483
  const files = [];
20211
20484
  const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
20212
20485
  const manifest = withinBudget ? boundedManifest(declared) : [];
20213
- let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it in the server's Skills settings). Nothing about this skill's files has been checked \u2014 verify it on its own with \`--method skills/get --uri <uri> --verify\`, where <uri> is this report's uri, to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
20486
+ let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${budget.maxSkills} skills / ${budget.maxBytes} bytes (raise it ${budgetHint.raise}). Nothing about this skill's files has been checked \u2014 verify it on its own ${budgetHint.verifyOne} to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
20214
20487
  let receivedBytes = 0;
20215
20488
  const entryIdentity = skillUriIdentity(entry.uri);
20216
20489
  let selfAttempted = false;
@@ -20365,6 +20638,10 @@ var FILE_COLOR = {
20365
20638
  error: "red",
20366
20639
  "read-error": "red"
20367
20640
  };
20641
+ var TUI_SKILL_BUDGET_HINT = {
20642
+ raise: "with --skill-catalog-max-skills / --skill-catalog-max-bytes at launch",
20643
+ verifyOne: "by selecting it and pressing Enter"
20644
+ };
20368
20645
  var VERIFY_STATUS = {
20369
20646
  verified: "[Verified \u2014 Enter to re-verify]",
20370
20647
  unverifiable: "[UNVERIFIABLE \u2014 Enter to re-verify]",
@@ -20425,12 +20702,14 @@ function SkillsTab({
20425
20702
  focusedPane = null,
20426
20703
  onAuthRecoveryRequired,
20427
20704
  modalOpen = false,
20428
- onFilterEditingChange
20705
+ onFilterEditingChange,
20706
+ onFilterEscapeClaimChange
20429
20707
  }) {
20430
20708
  const visibleCount = Math.max(1, height - 8 - LIST_FILTER_ROWS);
20431
20709
  const filter = useListFilter(skills, skillFilterFields, {
20432
20710
  enabled: !modalOpen && focusedPane === "list",
20433
- onEditingChange: onFilterEditingChange
20711
+ onEditingChange: onFilterEditingChange,
20712
+ onEscapeClaimChange: onFilterEscapeClaimChange
20434
20713
  });
20435
20714
  const shownSkills = filter.items;
20436
20715
  const { selectedIndex, firstVisible, setSelection } = useSelectableList(
@@ -20454,7 +20733,12 @@ function SkillsTab({
20454
20733
  setError(null);
20455
20734
  void (async () => {
20456
20735
  try {
20457
- const results = await verifySkills(inspectorClient, entries);
20736
+ const results = await verifySkills(
20737
+ inspectorClient,
20738
+ entries,
20739
+ void 0,
20740
+ TUI_SKILL_BUDGET_HINT
20741
+ );
20458
20742
  const live = new Set(skillsRef.current.map(skillEntryKey));
20459
20743
  setReports(
20460
20744
  (previous) => mergeReports(previous, entries, results, live)
@@ -21022,12 +21306,14 @@ function ToolsTab({
21022
21306
  onTestTool,
21023
21307
  onViewDetails,
21024
21308
  modalOpen = false,
21025
- onFilterEditingChange
21309
+ onFilterEditingChange,
21310
+ onFilterEscapeClaimChange
21026
21311
  }) {
21027
21312
  const visibleCount = Math.max(1, height - 7 - LIST_FILTER_ROWS);
21028
21313
  const filter = useListFilter(tools, toolFilterFields, {
21029
21314
  enabled: !modalOpen && focusedPane === "list",
21030
- onEditingChange: onFilterEditingChange
21315
+ onEditingChange: onFilterEditingChange,
21316
+ onEscapeClaimChange: onFilterEscapeClaimChange
21031
21317
  });
21032
21318
  const shownTools = filter.items;
21033
21319
  const { selectedIndex, firstVisible, setSelection } = useSelectableList(
@@ -24574,7 +24860,10 @@ function tabAcceleratorBinding(visible) {
24574
24860
  function globalBindings(visible = tabs) {
24575
24861
  return [
24576
24862
  { keys: "?", action: "Show or hide this help" },
24577
- { keys: "Esc / Ctrl+C", action: "Exit (Esc closes a dialog first)" },
24863
+ {
24864
+ keys: "Esc / Ctrl+C",
24865
+ action: "Exit (Esc closes a dialog or clears a kept filter first)"
24866
+ },
24578
24867
  {
24579
24868
  keys: "Tab / Shift+Tab",
24580
24869
  action: "Move focus: servers \u2192 tabs \u2192 list \u2192 details"
@@ -25451,6 +25740,7 @@ var APP_NAME = "MCP Inspector TUI";
25451
25740
  var APP_DESCRIPTION = "Terminal User Interface for the Model Context Protocol Inspector";
25452
25741
  var APP_VERSION = readInspectorVersion(import.meta.url);
25453
25742
  var TUI_CLIENT_NAME = "inspector-tui";
25743
+ var TUI_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, set connectionTimeout on the server's entry in a catalog (--catalog) or config (--config) file.";
25454
25744
  var NO_ROOTS = [];
25455
25745
  var readRoots = (client) => client.getRoots();
25456
25746
  function App({
@@ -25474,6 +25764,7 @@ function App({
25474
25764
  const [helpOpen, setHelpOpen] = useState22(false);
25475
25765
  const focus = helpOpen ? "help" : paneFocus;
25476
25766
  const [listFilterEditing, setListFilterEditing] = useState22(false);
25767
+ const [listFilterClaimsEscape, setListFilterClaimsEscape] = useState22(false);
25477
25768
  const [tabCounts, setTabCounts] = useState22({});
25478
25769
  const [oauthStatus, setOauthStatus] = useState22("idle");
25479
25770
  const [oauthMessage, setOauthMessage] = useState22(null);
@@ -25569,6 +25860,7 @@ function App({
25569
25860
  environment,
25570
25861
  clientIdentity: { name: TUI_CLIENT_NAME, version: APP_VERSION },
25571
25862
  pipeStderr: true,
25863
+ connectionTimeoutRemedy: TUI_CONNECTION_TIMEOUT_REMEDY,
25572
25864
  ...savedSettings && savedSettings.requestTimeout > 0 && {
25573
25865
  timeout: savedSettings.requestTimeout
25574
25866
  },
@@ -26452,6 +26744,7 @@ function App({
26452
26744
  return;
26453
26745
  }
26454
26746
  if (key.escape) {
26747
+ if (listFilterClaimsEscape) return;
26455
26748
  exit();
26456
26749
  }
26457
26750
  const showAuthTab = !!selectedServer && !!selectedServerConfig && isOAuthCapableServerConfig(selectedServerConfig);
@@ -26553,7 +26846,7 @@ function App({
26553
26846
  }
26554
26847
  }
26555
26848
  });
26556
- const headerHeight = 1;
26849
+ const headerHeight = 2;
26557
26850
  const serverListWidth = Math.floor(dimensions.width * 0.3);
26558
26851
  const contentWidth = dimensions.width - serverListWidth;
26559
26852
  const shownTabs = visibleTabs({
@@ -26612,17 +26905,18 @@ function App({
26612
26905
  justifyContent: "space-between",
26613
26906
  alignItems: "center",
26614
26907
  children: [
26615
- /* @__PURE__ */ jsxs23(Box35, { children: [
26616
- /* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }),
26617
- /* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
26618
- " - ",
26908
+ /* @__PURE__ */ jsxs23(Box35, { flexShrink: 1, overflow: "hidden", children: [
26909
+ /* @__PURE__ */ jsx25(Box35, { flexShrink: 0, children: /* @__PURE__ */ jsx25(Text32, { bold: true, color: "cyan", children: APP_NAME }) }),
26910
+ /* @__PURE__ */ jsxs23(Text32, { dimColor: true, wrap: "truncate-end", children: [
26911
+ " ",
26912
+ "- ",
26619
26913
  APP_DESCRIPTION
26620
26914
  ] })
26621
26915
  ] }),
26622
- /* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
26916
+ /* @__PURE__ */ jsx25(Box35, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsxs23(Text32, { dimColor: true, children: [
26623
26917
  "v",
26624
26918
  APP_VERSION
26625
- ] })
26919
+ ] }) })
26626
26920
  ]
26627
26921
  }
26628
26922
  ),
@@ -26884,6 +27178,7 @@ function App({
26884
27178
  ResourcesTab,
26885
27179
  {
26886
27180
  onFilterEditingChange: setListFilterEditing,
27181
+ onFilterEscapeClaimChange: setListFilterClaimsEscape,
26887
27182
  resources: currentServerState.resources,
26888
27183
  resourceTemplates: currentServerState.resourceTemplates,
26889
27184
  inspectorClient: selectedInspectorClient,
@@ -26948,6 +27243,7 @@ function App({
26948
27243
  SkillsTab,
26949
27244
  {
26950
27245
  onFilterEditingChange: setListFilterEditing,
27246
+ onFilterEscapeClaimChange: setListFilterClaimsEscape,
26951
27247
  skills: managedSkills,
26952
27248
  pageCount: managedSkillsPageCount,
26953
27249
  loadError: managedSkillsError,
@@ -26963,6 +27259,7 @@ function App({
26963
27259
  PromptsTab,
26964
27260
  {
26965
27261
  onFilterEditingChange: setListFilterEditing,
27262
+ onFilterEscapeClaimChange: setListFilterClaimsEscape,
26966
27263
  prompts: currentServerState.prompts,
26967
27264
  inspectorClient: selectedInspectorClient,
26968
27265
  width: contentWidth,
@@ -26988,6 +27285,7 @@ function App({
26988
27285
  ToolsTab,
26989
27286
  {
26990
27287
  onFilterEditingChange: setListFilterEditing,
27288
+ onFilterEscapeClaimChange: setListFilterClaimsEscape,
26991
27289
  tools: currentServerState.tools,
26992
27290
  isConnected: inspectorStatus === "connected",
26993
27291
  width: contentWidth,
@@ -27145,7 +27443,11 @@ var App_default = App;
27145
27443
  import { jsx as jsx26 } from "react/jsx-runtime";
27146
27444
  async function runTui(args) {
27147
27445
  const program = new Command();
27148
- program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").option(
27446
+ program.name("mcp-inspector-tui").description("Terminal UI for MCP Inspector").version(
27447
+ readInspectorVersion(import.meta.url),
27448
+ "-V, --version",
27449
+ "Print the Inspector version"
27450
+ ).option(
27149
27451
  "--catalog <path>",
27150
27452
  "Writable catalog file (created if missing; default: ~/.mcp-inspector/mcp.json, or MCP_CATALOG_PATH)"
27151
27453
  ).option(