@modelcontextprotocol/inspector 2.10.0 → 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.
@@ -2707,6 +2707,84 @@ function isJsonDocumentResponse(contentType) {
2707
2707
  const mediaType = contentType.split(";")[0].trim().toLowerCase();
2708
2708
  return mediaType === "application/json" || mediaType.endsWith("+json");
2709
2709
  }
2710
+ var SYNTHESIZED_METADATA_HEADER = "x-inspector-oauth-metadata-synthesized";
2711
+ var synthesizedResponses = /* @__PURE__ */ new WeakSet();
2712
+ function isSynthesizedMetadataResponse(response) {
2713
+ return synthesizedResponses.has(response);
2714
+ }
2715
+ var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
2716
+ var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
2717
+ function continuesDiscovery(status) {
2718
+ return status < 500 || status === 502;
2719
+ }
2720
+ function authorizationServerForFinalCandidate(url) {
2721
+ let parsed;
2722
+ try {
2723
+ parsed = new URL(url);
2724
+ } catch {
2725
+ return void 0;
2726
+ }
2727
+ const { pathname } = parsed;
2728
+ if (pathname === OIDC_WELL_KNOWN) return parsed.origin;
2729
+ if (!pathname.endsWith(OIDC_WELL_KNOWN)) return void 0;
2730
+ if (pathname.startsWith(`${RFC8414_WELL_KNOWN}/`) || pathname.startsWith(`${OIDC_WELL_KNOWN}/`)) {
2731
+ return void 0;
2732
+ }
2733
+ return `${parsed.origin}${pathname.slice(0, -OIDC_WELL_KNOWN.length)}`;
2734
+ }
2735
+ function synthesizeAuthorizationServerMetadata(issuer, overrides) {
2736
+ return {
2737
+ issuer,
2738
+ authorization_endpoint: overrides.authorizationUrl ?? new URL("/authorize", issuer).href,
2739
+ token_endpoint: overrides.tokenUrl ?? new URL("/token", issuer).href,
2740
+ registration_endpoint: new URL("/register", issuer).href,
2741
+ response_types_supported: ["code"],
2742
+ code_challenge_methods_supported: ["S256"]
2743
+ };
2744
+ }
2745
+ function requestMethodOf(input, init) {
2746
+ if (init?.method) return init.method.toUpperCase();
2747
+ if (typeof input !== "string" && !(input instanceof URL)) {
2748
+ return input.method.toUpperCase();
2749
+ }
2750
+ return "GET";
2751
+ }
2752
+ function acceptsJson(input, init) {
2753
+ let accept = null;
2754
+ if (init?.headers) {
2755
+ accept = new Headers(init.headers).get("accept");
2756
+ } else if (typeof input !== "string" && !(input instanceof URL)) {
2757
+ accept = input.headers.get("accept");
2758
+ }
2759
+ if (!accept) return false;
2760
+ const mediaTypes = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
2761
+ return mediaTypes.includes("application/json") && !mediaTypes.includes("text/event-stream");
2762
+ }
2763
+ function synthesizedMetadataResponse(input, init, response, overrides) {
2764
+ if (!continuesDiscovery(response.status)) return void 0;
2765
+ if (requestMethodOf(input, init) !== "GET") return void 0;
2766
+ if (!acceptsJson(input, init)) return void 0;
2767
+ const issuer = authorizationServerForFinalCandidate(requestUrlOf(input));
2768
+ if (!issuer) return void 0;
2769
+ void response.body?.cancel().catch(() => {
2770
+ });
2771
+ console.warn(
2772
+ `[oauth] ${issuer} publishes no authorization server metadata; using the configured endpoint override(s) in place of the SDK's /authorize and /token fallbacks.`
2773
+ );
2774
+ const synthesized = new Response(
2775
+ JSON.stringify(synthesizeAuthorizationServerMetadata(issuer, overrides)),
2776
+ {
2777
+ status: 200,
2778
+ statusText: "OK",
2779
+ headers: {
2780
+ "content-type": "application/json",
2781
+ [SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
2782
+ }
2783
+ }
2784
+ );
2785
+ synthesizedResponses.add(synthesized);
2786
+ return synthesized;
2787
+ }
2710
2788
  function responseWithBody(response, body) {
2711
2789
  const headers = new Headers(response.headers);
2712
2790
  headers.delete("content-length");
@@ -2734,7 +2812,9 @@ function withOAuthEndpointOverrides(fetchFn, getOverrides) {
2734
2812
  const overrides = resolveOverrides();
2735
2813
  if (!overrides) return response;
2736
2814
  if (!isAuthorizationServerMetadataRequest(input)) return response;
2737
- if (!response.ok) return response;
2815
+ if (!response.ok) {
2816
+ return synthesizedMetadataResponse(input, init, response, overrides) ?? response;
2817
+ }
2738
2818
  if (!isJsonDocumentResponse(response.headers.get("content-type")))
2739
2819
  return response;
2740
2820
  let parsed;
@@ -2910,8 +2990,8 @@ import {
2910
2990
  OAuthMetadataSchema,
2911
2991
  OpenIdProviderDiscoveryMetadataSchema
2912
2992
  } from "@modelcontextprotocol/core";
2913
- var RFC8414_WELL_KNOWN = "/.well-known/oauth-authorization-server";
2914
- var OIDC_WELL_KNOWN = "/.well-known/openid-configuration";
2993
+ var RFC8414_WELL_KNOWN2 = "/.well-known/oauth-authorization-server";
2994
+ var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
2915
2995
  var COMPAT_SOURCE_HEADER = "x-inspector-oauth-metadata-source";
2916
2996
  function oidcDiscoveryCandidates(rfc8414Url) {
2917
2997
  let url;
@@ -2920,16 +3000,16 @@ function oidcDiscoveryCandidates(rfc8414Url) {
2920
3000
  } catch {
2921
3001
  return [];
2922
3002
  }
2923
- if (!url.pathname.startsWith(RFC8414_WELL_KNOWN)) return [];
2924
- const path7 = url.pathname.slice(RFC8414_WELL_KNOWN.length);
3003
+ if (!url.pathname.startsWith(RFC8414_WELL_KNOWN2)) return [];
3004
+ const path7 = url.pathname.slice(RFC8414_WELL_KNOWN2.length);
2925
3005
  if (path7 !== "" && !path7.startsWith("/")) return [];
2926
- if (path7 === "") return [new URL(OIDC_WELL_KNOWN, url.origin).href];
3006
+ if (path7 === "") return [new URL(OIDC_WELL_KNOWN2, url.origin).href];
2927
3007
  return [
2928
- new URL(`${OIDC_WELL_KNOWN}${path7}`, url.origin).href,
2929
- new URL(`${path7}${OIDC_WELL_KNOWN}`, url.origin).href
3008
+ new URL(`${OIDC_WELL_KNOWN2}${path7}`, url.origin).href,
3009
+ new URL(`${path7}${OIDC_WELL_KNOWN2}`, url.origin).href
2930
3010
  ];
2931
3011
  }
2932
- function continuesDiscovery(status) {
3012
+ function continuesDiscovery2(status) {
2933
3013
  return status >= 400 && status < 500 || status === 502;
2934
3014
  }
2935
3015
  async function releaseBody(response) {
@@ -2955,7 +3035,7 @@ function discoveryHeaders(input, init) {
2955
3035
  }
2956
3036
  return new Headers();
2957
3037
  }
2958
- function requestMethodOf(input, init) {
3038
+ function requestMethodOf2(input, init) {
2959
3039
  if (init?.method) return init.method.toUpperCase();
2960
3040
  if (typeof input !== "string" && !(input instanceof URL)) {
2961
3041
  return input.method.toUpperCase();
@@ -2966,8 +3046,8 @@ function withRfc8414OidcCompat(fetchFn) {
2966
3046
  return async (input, init) => {
2967
3047
  const response = await fetchFn(input, init);
2968
3048
  if (response.ok) return response;
2969
- if (!continuesDiscovery(response.status)) return response;
2970
- if (requestMethodOf(input, init) !== "GET") return response;
3049
+ if (!continuesDiscovery2(response.status)) return response;
3050
+ if (requestMethodOf2(input, init) !== "GET") return response;
2971
3051
  const candidates = oidcDiscoveryCandidates(requestUrlOf3(input));
2972
3052
  if (candidates.length === 0) return response;
2973
3053
  const headers = discoveryHeaders(input, init);
@@ -2997,7 +3077,7 @@ function withRfc8414OidcCompat(fetchFn) {
2997
3077
  releaseBodyDetached(response);
2998
3078
  throw callerSignal.reason;
2999
3079
  }
3000
- if (continuesDiscovery(probe.status)) continue;
3080
+ if (continuesDiscovery2(probe.status)) continue;
3001
3081
  return response;
3002
3082
  }
3003
3083
  let parsed;
@@ -3017,6 +3097,16 @@ function withRfc8414OidcCompat(fetchFn) {
3017
3097
  }
3018
3098
  const source = probe.url || candidate;
3019
3099
  releaseBodyDetached(response);
3100
+ if (isSynthesizedMetadataResponse(probe)) {
3101
+ return new Response(body, {
3102
+ status: 200,
3103
+ statusText: "OK",
3104
+ headers: {
3105
+ "content-type": "application/json",
3106
+ [SYNTHESIZED_METADATA_HEADER]: "endpoint-overrides"
3107
+ }
3108
+ });
3109
+ }
3020
3110
  console.warn(
3021
3111
  `[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.`
3022
3112
  );
@@ -3497,7 +3587,10 @@ function parseAuthChallengeFromResponse(response, context) {
3497
3587
  if (status !== 401 && status !== 403) {
3498
3588
  return void 0;
3499
3589
  }
3500
- const wwwAuthenticate = response.headers.get("WWW-Authenticate") ?? void 0;
3590
+ const wwwAuthenticate = response.headers.get("WWW-Authenticate") || void 0;
3591
+ if (status === 403 && !wwwAuthenticate) {
3592
+ return void 0;
3593
+ }
3501
3594
  const bearer = wwwAuthenticate ? parseWwwAuthenticateBearer(wwwAuthenticate) : {};
3502
3595
  const requiredScopes = parseScopeString(bearer.scope);
3503
3596
  return {
@@ -4487,6 +4580,17 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
4487
4580
  warnLedgerFailure("update", error);
4488
4581
  }
4489
4582
  }
4583
+ async function recordedNamespaces(stateFilePath) {
4584
+ try {
4585
+ const ledger = parseLedger(
4586
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
4587
+ );
4588
+ return [...ledger.keys()].reverse();
4589
+ } catch (error) {
4590
+ warnLedgerFailure("read", error);
4591
+ return [];
4592
+ }
4593
+ }
4490
4594
  async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
4491
4595
  let ledger;
4492
4596
  let location;
@@ -4650,24 +4754,13 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4650
4754
  if (raw !== null && snapshot === null) {
4651
4755
  throw new OAuthStateFileUnrecognizedError(filePath, "save");
4652
4756
  }
4653
- if (locked) {
4654
- await purgeSupersededNamespaces(filePath, secretStore, void 0);
4655
- }
4656
4757
  const namespace = newSecretsNamespace();
4657
- if (snapshot === null) return namespace;
4658
- const moves = [
4659
- ...Object.entries(snapshot.servers).map(([url, state]) => ({
4660
- legacyId: oauthSecretServerId(url),
4661
- scopedId: oauthSecretServerId(url, namespace),
4662
- fields: serverSecretFields(state)
4663
- })),
4664
- ...Object.keys(snapshot.idpSessions).map((issuer) => ({
4665
- legacyId: oauthIdpSecretServerId(issuer),
4666
- scopedId: oauthIdpSecretServerId(issuer, namespace),
4667
- fields: [IDP_SESSION_FIELD]
4668
- }))
4669
- ];
4670
- if (moves.length === 0) return namespace;
4758
+ if (snapshot === null || Object.keys(snapshot.servers).length === 0 && Object.keys(snapshot.idpSessions).length === 0) {
4759
+ if (locked) {
4760
+ await purgeSupersededNamespaces(filePath, secretStore, void 0);
4761
+ }
4762
+ return namespace;
4763
+ }
4671
4764
  if (!locked) {
4672
4765
  throw new SecretStoreUnavailableError(
4673
4766
  `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).`
@@ -4682,10 +4775,24 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4682
4775
  );
4683
4776
  const touched = [];
4684
4777
  try {
4685
- for (const { legacyId, scopedId, fields } of moves) {
4686
- for (const field of fields) {
4687
- const value = await secretStoreGetStrict(secretStore, legacyId, field);
4688
- if (value === null) continue;
4778
+ const values = await readEntrySecrets(
4779
+ snapshot,
4780
+ secretStore,
4781
+ void 0,
4782
+ await recordedNamespaces(filePath)
4783
+ );
4784
+ const copies = [
4785
+ ...[...values.servers].map(([url, secrets]) => ({
4786
+ scopedId: oauthSecretServerId(url, namespace),
4787
+ secrets
4788
+ })),
4789
+ ...[...values.idpSessions].map(([issuer, secrets]) => ({
4790
+ scopedId: oauthIdpSecretServerId(issuer, namespace),
4791
+ secrets
4792
+ }))
4793
+ ];
4794
+ for (const { scopedId, secrets } of copies) {
4795
+ for (const [field, value] of Object.entries(secrets)) {
4689
4796
  touched.push({ serverId: scopedId, field, value: null });
4690
4797
  await secretStore.set(scopedId, field, value);
4691
4798
  }
@@ -4695,7 +4802,14 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4695
4802
  await restoreSecretFields(secretStore, touched, warnRestoreFailure);
4696
4803
  throw error;
4697
4804
  }
4698
- for (const { legacyId } of moves) {
4805
+ await purgeSupersededNamespaces(filePath, secretStore, namespace);
4806
+ const legacyIds = [
4807
+ ...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
4808
+ ...Object.keys(snapshot.idpSessions).map(
4809
+ (issuer) => oauthIdpSecretServerId(issuer)
4810
+ )
4811
+ ];
4812
+ for (const legacyId of legacyIds) {
4699
4813
  try {
4700
4814
  await secretStore.deleteAllForServer(legacyId);
4701
4815
  } catch (error) {
@@ -4748,6 +4862,14 @@ function preserveNonDurableSecrets(diskSecrets, secrets) {
4748
4862
  return keep;
4749
4863
  }
4750
4864
  async function writeOAuthSections(filePath, snapshot, sections, secretStore = defaultSecretStore()) {
4865
+ await writeOAuthSectionsFrom(
4866
+ filePath,
4867
+ async () => snapshot,
4868
+ sections,
4869
+ secretStore
4870
+ );
4871
+ }
4872
+ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secretStore) {
4751
4873
  const policy = getPersistTokensPolicy();
4752
4874
  const durable = await secretStoreIsDurable(secretStore);
4753
4875
  await withOAuthStateLock(filePath, "save", async (locked) => {
@@ -4831,6 +4953,7 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
4831
4953
  unconfirmed = null;
4832
4954
  namespace = diskNamespace;
4833
4955
  }
4956
+ const snapshot = await resolveSnapshot(disk, namespace);
4834
4957
  const effective = sections ? {
4835
4958
  servers: sections.servers && [...new Set(sections.servers)],
4836
4959
  idpSessions: sections.idpSessions && [
@@ -4864,7 +4987,8 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
4864
4987
  const candidates = [
4865
4988
  .../* @__PURE__ */ new Set([
4866
4989
  ...serverSecretFields(getOwnEntry(disk?.servers, url)),
4867
- ...serverSecretFields(next)
4990
+ ...serverSecretFields(next),
4991
+ ...[...restoreBaseline.values()].filter((prior) => prior.serverId === serverId).map(({ field }) => field)
4868
4992
  ])
4869
4993
  ];
4870
4994
  const entryPrior = await snapshotSecretFields(
@@ -4873,8 +4997,12 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
4873
4997
  candidates
4874
4998
  );
4875
4999
  priorSecrets.push(...entryPrior);
5000
+ const entryPriorValue = new Map(
5001
+ entryPrior.map(({ field, value }) => [field, value])
5002
+ );
4876
5003
  if (next === void 0) {
4877
5004
  for (const field of candidates) {
5005
+ if (entryPriorValue.get(field) === null) continue;
4878
5006
  ourWrites.set(priorKey({ serverId, field }), null);
4879
5007
  }
4880
5008
  await secretStore.deleteAllForServer(serverId);
@@ -4889,10 +5017,9 @@ async function writeOAuthSections(filePath, snapshot, sections, secretStore = de
4889
5017
  }
4890
5018
  const { residue, secrets } = splitServerOAuthState(next, policy);
4891
5019
  for (const field of candidates) {
4892
- ourWrites.set(
4893
- priorKey({ serverId, field }),
4894
- secrets[field] ?? null
4895
- );
5020
+ const value = secrets[field] ?? null;
5021
+ if (value === entryPriorValue.get(field)) continue;
5022
+ ourWrites.set(priorKey({ serverId, field }), value);
4896
5023
  }
4897
5024
  setOwnEntry(merged.servers, url, residue);
4898
5025
  if (!durable) {
@@ -5035,26 +5162,52 @@ function secretRequestsFor(snapshot, namespace) {
5035
5162
  }
5036
5163
  return requests;
5037
5164
  }
5038
- async function joinSnapshot(snapshot, secretStore, namespace) {
5039
- const requests = secretRequestsFor(snapshot, namespace);
5040
- const values = requests.length > 0 ? await secretStoreGetManyStrict(secretStore, requests) : {};
5165
+ function fillMissingSecrets(target, source) {
5166
+ for (const [field, value] of Object.entries(source ?? {})) {
5167
+ if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
5168
+ }
5169
+ }
5170
+ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
5171
+ const servers = /* @__PURE__ */ new Map();
5172
+ const idpSessions = /* @__PURE__ */ new Map();
5173
+ for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
5174
+ for (const issuer of Object.keys(snapshot.idpSessions)) {
5175
+ idpSessions.set(issuer, {});
5176
+ }
5177
+ for (const source of [namespace, ...fallbacks]) {
5178
+ const requests = secretRequestsFor(snapshot, source);
5179
+ if (requests.length === 0) break;
5180
+ const values = await secretStoreGetManyStrict(secretStore, requests);
5181
+ for (const [url, target] of servers) {
5182
+ fillMissingSecrets(target, values[oauthSecretServerId(url, source)]);
5183
+ }
5184
+ for (const [issuer, target] of idpSessions) {
5185
+ fillMissingSecrets(
5186
+ target,
5187
+ values[oauthIdpSecretServerId(issuer, source)]
5188
+ );
5189
+ }
5190
+ }
5191
+ return { servers, idpSessions };
5192
+ }
5193
+ async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
5194
+ const values = await readEntrySecrets(
5195
+ snapshot,
5196
+ secretStore,
5197
+ namespace,
5198
+ fallbacks
5199
+ );
5041
5200
  return {
5042
5201
  servers: Object.fromEntries(
5043
5202
  Object.entries(snapshot.servers).map(([url, state]) => [
5044
5203
  url,
5045
- joinServerOAuthState(
5046
- state,
5047
- values[oauthSecretServerId(url, namespace)] ?? {}
5048
- )
5204
+ joinServerOAuthState(state, values.servers.get(url) ?? {})
5049
5205
  ])
5050
5206
  ),
5051
5207
  idpSessions: Object.fromEntries(
5052
5208
  Object.entries(snapshot.idpSessions).map(([issuer, session]) => [
5053
5209
  issuer,
5054
- joinIdpSession(
5055
- session,
5056
- values[oauthIdpSecretServerId(issuer, namespace)] ?? {}
5057
- )
5210
+ joinIdpSession(session, values.idpSessions.get(issuer) ?? {})
5058
5211
  ])
5059
5212
  )
5060
5213
  };
@@ -5121,7 +5274,9 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
5121
5274
  warnMigrationFailure(error);
5122
5275
  }
5123
5276
  }
5124
- return joinSnapshot(snapshot, secretStore, parseSecretsNamespace(raw));
5277
+ const namespace = parseSecretsNamespace(raw);
5278
+ const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
5279
+ return joinSnapshot(snapshot, secretStore, namespace, fallbacks);
5125
5280
  });
5126
5281
  }
5127
5282
  async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
@@ -5748,6 +5903,11 @@ var ManagedListState = class extends TypedEventTarget {
5748
5903
  // rendered by the list panel instead of vanishing into an unhandled
5749
5904
  // rejection — which read as "this server has no tools" (#1953).
5750
5905
  error = null;
5906
+ // Whether a fetch has committed a list this session. Until it has, the empty
5907
+ // `items` mean "not loaded yet", not "the server has none" — a distinction a
5908
+ // count of the list cannot make from `items` alone (#2607). Set and cleared
5909
+ // just before the `*Change` dispatch, so a reader keyed on that event sees it.
5910
+ loaded = false;
5751
5911
  config;
5752
5912
  // Debounce a burst of `list_changed` notifications into a single
5753
5913
  // refresh (or one indicator light) once it settles.
@@ -5783,6 +5943,7 @@ var ManagedListState = class extends TypedEventTarget {
5783
5943
  this.listChangedTimer = null;
5784
5944
  }
5785
5945
  this.items = [];
5946
+ this.loaded = false;
5786
5947
  this.dispatchChange();
5787
5948
  this.setListChanged(false);
5788
5949
  this.setError(null);
@@ -5841,6 +6002,14 @@ var ManagedListState = class extends TypedEventTarget {
5841
6002
  getItems() {
5842
6003
  return [...this.items];
5843
6004
  }
6005
+ /**
6006
+ * Whether a fetch has committed a list since the last connect. False while
6007
+ * the connect-time load is in flight, when it was deferred (paginated mode),
6008
+ * and after a disconnect. Changes only alongside the `*Change` event (#2607).
6009
+ */
6010
+ isLoaded() {
6011
+ return this.loaded;
6012
+ }
5844
6013
  /** Whether a `list_changed` arrived since the last refresh (indicator on). */
5845
6014
  getListChanged() {
5846
6015
  return this.listChanged;
@@ -5934,6 +6103,7 @@ var ManagedListState = class extends TypedEventTarget {
5934
6103
  /** Commit a fetched list as the current one and notify subscribers. */
5935
6104
  applyItems(items) {
5936
6105
  this.items = items;
6106
+ this.loaded = true;
5937
6107
  this.dispatchChange();
5938
6108
  }
5939
6109
  dispatchChange() {
@@ -5944,6 +6114,7 @@ var ManagedListState = class extends TypedEventTarget {
5944
6114
  this.unsubscribe?.();
5945
6115
  this.unsubscribe = null;
5946
6116
  this.items = [];
6117
+ this.loaded = false;
5947
6118
  }
5948
6119
  };
5949
6120
 
@@ -7212,6 +7383,10 @@ function checkSkillNameCollisions(entries) {
7212
7383
  }
7213
7384
 
7214
7385
  // ../../core/mcp/skillsVerification.ts
7386
+ var DEFAULT_SKILL_BUDGET_HINT = {
7387
+ raise: "with the server's skillCatalogMaxSkills / skillCatalogMaxBytes settings",
7388
+ verifyOne: "by verifying this skill alone"
7389
+ };
7215
7390
  function reasonOf(err) {
7216
7391
  return err instanceof Error ? err.message : String(err);
7217
7392
  }
@@ -7273,7 +7448,7 @@ function responseBytes(result) {
7273
7448
  }
7274
7449
  return total;
7275
7450
  }
7276
- async function verifySkills(client, entries, metadata) {
7451
+ async function verifySkills(client, entries, metadata, budgetHint = DEFAULT_SKILL_BUDGET_HINT) {
7277
7452
  const collisions = checkSkillNameCollisions(entries);
7278
7453
  const reports = [];
7279
7454
  const budget = resolveSkillCatalogBudget(client.getServerSettings?.());
@@ -7285,7 +7460,7 @@ async function verifySkills(client, entries, metadata) {
7285
7460
  const files = [];
7286
7461
  const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
7287
7462
  const manifest = withinBudget ? boundedManifest(declared) : [];
7288
- 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;
7463
+ 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;
7289
7464
  let receivedBytes = 0;
7290
7465
  const entryIdentity = skillUriIdentity(entry.uri);
7291
7466
  let selfAttempted = false;
@@ -7423,6 +7598,14 @@ function anySkillUnverifiable(reports) {
7423
7598
  }
7424
7599
 
7425
7600
  // ../../core/cli/handlers/skills-verify.ts
7601
+ var CLI_SKILL_BUDGET_HINT = {
7602
+ raise: "with --skill-catalog-max-skills / --skill-catalog-max-bytes, or the server's skillCatalogMaxSkills / skillCatalogMaxBytes",
7603
+ verifyOne: "with `--method skills/get --uri <uri> --verify`, where <uri> is this report's uri,"
7604
+ };
7605
+ var MCPDO_SKILL_BUDGET_HINT = {
7606
+ raise: "with skillCatalogMaxSkills / skillCatalogMaxBytes in the connection's catalog entry",
7607
+ verifyOne: "with `mcpdo @<conn> skills/get <uri> --verify`, where <uri> is this report's uri,"
7608
+ };
7426
7609
  function skillVerificationExitCode(reports, requireDigests) {
7427
7610
  if (allSkillsVerified(reports)) return void 0;
7428
7611
  if (anySkillFailed(reports)) return EXIT_CODES.SKILL_NONCONFORMANT;
@@ -7474,7 +7657,8 @@ function assertSkillsSupported(inspectorClient, method) {
7474
7657
  }
7475
7658
  }
7476
7659
  var resourceStreamRefs = /* @__PURE__ */ new WeakMap();
7477
- async function runMethod(inspectorClient, args) {
7660
+ async function runMethod(inspectorClient, args, options = {}) {
7661
+ const skillBudgetHint = options.skillBudgetHint ?? CLI_SKILL_BUDGET_HINT;
7478
7662
  let managedToolsState = null;
7479
7663
  let managedResourcesState = null;
7480
7664
  let managedResourceTemplatesState = null;
@@ -7539,7 +7723,8 @@ async function runMethod(inspectorClient, args) {
7539
7723
  if (args.appInfo) {
7540
7724
  result = { ...appInfo };
7541
7725
  } else {
7542
- const invocation = args.task ? await inspectorClient.callToolStream(
7726
+ const useTask = args.task === true || tool.execution?.taskSupport === "required";
7727
+ const invocation = useTask ? await inspectorClient.callToolStream(
7543
7728
  tool,
7544
7729
  args.toolArg || {},
7545
7730
  args.metadata,
@@ -7770,7 +7955,8 @@ async function runMethod(inspectorClient, args) {
7770
7955
  const reports = await verifySkills(
7771
7956
  inspectorClient,
7772
7957
  skills,
7773
- args.metadata
7958
+ args.metadata,
7959
+ skillBudgetHint
7774
7960
  );
7775
7961
  return {
7776
7962
  kind: "ndjson",
@@ -7799,7 +7985,8 @@ async function runMethod(inspectorClient, args) {
7799
7985
  const reports = await verifySkills(
7800
7986
  inspectorClient,
7801
7987
  [skill],
7802
- args.metadata
7988
+ args.metadata,
7989
+ skillBudgetHint
7803
7990
  );
7804
7991
  return {
7805
7992
  kind: "ndjson",
@@ -10881,10 +11068,10 @@ async function resolveIdpMetadata(issuer, storage, fetchFn) {
10881
11068
  }
10882
11069
  return discoverIdpMetadata(issuer, fetchFn);
10883
11070
  }
10884
- var OIDC_WELL_KNOWN2 = "/.well-known/openid-configuration";
11071
+ var OIDC_WELL_KNOWN3 = "/.well-known/openid-configuration";
10885
11072
  async function fetchOpenIdConfiguration(issuer, issuerUrl, fetchFn) {
10886
11073
  const url = new URL(
10887
- `${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN2}`,
11074
+ `${issuerUrl.pathname.replace(/\/$/, "")}${OIDC_WELL_KNOWN3}`,
10888
11075
  issuerUrl.origin
10889
11076
  );
10890
11077
  try {
@@ -12171,7 +12358,11 @@ var OAuthManager = class {
12171
12358
  };
12172
12359
 
12173
12360
  // ../../core/mcp/remote/remoteClientTransport.ts
12174
- import { SdkError as SdkError5, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
12361
+ import {
12362
+ SdkError as SdkError5,
12363
+ SdkErrorCode as SdkErrorCode5,
12364
+ SdkHttpError
12365
+ } from "@modelcontextprotocol/client";
12175
12366
 
12176
12367
  // ../../core/mcp/remote/types.ts
12177
12368
  function oauthTokensToRemoteAuthState(tokens) {
@@ -12226,6 +12417,17 @@ function isConnectAuthChallenge(json) {
12226
12417
  function isConnectTransportError(json) {
12227
12418
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
12228
12419
  }
12420
+ var SDK_ERROR_CODES = new Set(
12421
+ Object.values(SdkErrorCode5)
12422
+ );
12423
+ function toSdkHttpError(message, http) {
12424
+ const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode5.ClientHttpNotImplemented;
12425
+ return new SdkHttpError(code, message, {
12426
+ status: http.status,
12427
+ ...http.statusText !== void 0 && { statusText: http.statusText },
12428
+ ...http.text !== void 0 && { text: http.text }
12429
+ });
12430
+ }
12229
12431
  function legacySessionId(json) {
12230
12432
  if (typeof json === "object" && json !== null && "sessionId" in json && typeof json.sessionId === "string" && !("ok" in json)) {
12231
12433
  return json.sessionId;
@@ -12726,6 +12928,9 @@ var RemoteClientTransport = class {
12726
12928
  });
12727
12929
  return;
12728
12930
  }
12931
+ if (json.http) {
12932
+ throw toSdkHttpError(json.error, json.http);
12933
+ }
12729
12934
  throw new Error(json.error);
12730
12935
  }
12731
12936
  async requestSend(message, options) {
@@ -12798,8 +13003,9 @@ var corePackageJson = {
12798
13003
  version: "0.0.0"
12799
13004
  };
12800
13005
  var MAX_URL_ELICITATION_RETRIES = 5;
12801
- function connectionTimeoutMessage(timeoutMs) {
12802
- return `Connection timed out after ${timeoutMs} ms. To accommodate a slower server, you may increase the timeout value in Server Settings.`;
13006
+ var WEB_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, you may increase the timeout value in Server Settings.";
13007
+ function connectionTimeoutMessage(timeoutMs, remedy = WEB_CONNECTION_TIMEOUT_REMEDY) {
13008
+ return `Connection timed out after ${timeoutMs} ms. ${remedy}`;
12803
13009
  }
12804
13010
  function createPendingAbortError() {
12805
13011
  return new Error("Pending request aborted");
@@ -12890,6 +13096,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
12890
13096
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
12891
13097
  ambientAuthChallengeInFlight = /* @__PURE__ */ new Map();
12892
13098
  pipeStderr;
13099
+ connectionTimeoutRemedy;
12893
13100
  initialLoggingLevel;
12894
13101
  // Modern-era per-request log level (#1629). On 2026-07-28 servers
12895
13102
  // `logging/setLevel` is gone; the client opts into logs per request via the
@@ -13127,6 +13334,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13127
13334
  this.fetchFn = options.environment.fetch;
13128
13335
  this.logger = options.environment.logger ?? silentLogger;
13129
13336
  this.pipeStderr = options.pipeStderr ?? false;
13337
+ this.connectionTimeoutRemedy = options.connectionTimeoutRemedy ?? WEB_CONNECTION_TIMEOUT_REMEDY;
13130
13338
  this.initialLoggingLevel = options.initialLoggingLevel;
13131
13339
  this.sample = options.sample ?? true;
13132
13340
  this.elicit = options.elicit ?? true;
@@ -13868,9 +14076,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13868
14076
  // for that upstream gap and should be deleted with it.
13869
14077
  //
13870
14078
  // Known, accepted side effect of turning intercept on with no stored
13871
- // tokens: `parseAuthChallengeFromResponse` treats 403 as a challenge
13872
- // too, so a probe answered 403 for a *non-auth* reason (a gateway
13873
- // rejecting the unknown `server/discover` method, say) now starts OAuth
14079
+ // tokens: `parseAuthChallengeFromResponse` treats a 403 carrying
14080
+ // `WWW-Authenticate` as a challenge too, so a probe answered that way
14081
+ // for a *non-auth* reason (a gateway rejecting the unknown
14082
+ // `server/discover` method, say) now starts OAuth
13874
14083
  // discovery instead of letting "auto" fall back to the legacy
13875
14084
  // `initialize`. The outcome is a surfaced `oauthError`, not a hang, and
13876
14085
  // it goes away with this clause.
@@ -13937,7 +14146,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13937
14146
  const timeoutPromise = new Promise((_, reject) => {
13938
14147
  timer = setTimeout(() => {
13939
14148
  connectTimedOut = true;
13940
- reject(new Error(connectionTimeoutMessage(connectTimeoutMs)));
14149
+ reject(
14150
+ new Error(
14151
+ connectionTimeoutMessage(
14152
+ connectTimeoutMs,
14153
+ this.connectionTimeoutRemedy
14154
+ )
14155
+ )
14156
+ );
13941
14157
  }, connectTimeoutMs);
13942
14158
  });
13943
14159
  try {
@@ -17287,16 +17503,30 @@ function buildConfigFromOptions(options) {
17287
17503
  const target = options.target ?? [];
17288
17504
  const first = target[0];
17289
17505
  const rest = target.slice(1);
17506
+ if (first && options.serverUrl) {
17507
+ throw new Error(
17508
+ `Cannot combine a positional target (${first}) with --server-url (${options.serverUrl}). Pass one or the other.`
17509
+ );
17510
+ }
17290
17511
  const urlFromTarget = first && (first.startsWith("http://") || first.startsWith("https://")) ? first : null;
17291
17512
  const url = urlFromTarget ?? options.serverUrl ?? null;
17292
17513
  if (url) {
17293
- if (rest.length > 0 && urlFromTarget) {
17514
+ if (rest.length > 0) {
17294
17515
  throw new Error("Arguments cannot be passed to a URL-based MCP server.");
17295
17516
  }
17517
+ if (options.transport === "stdio") {
17518
+ throw new Error(
17519
+ `stdio transport cannot be used with a URL (${url}). Use --transport http or --transport sse, or pass a local command instead.`
17520
+ );
17521
+ }
17296
17522
  let transportType;
17297
17523
  const t = options.transport === "http" ? "streamable-http" : options.transport;
17298
17524
  if (t === "sse" || t === "streamable-http") {
17299
17525
  transportType = t;
17526
+ } else if (t) {
17527
+ throw new Error(
17528
+ `Invalid transport type: ${t}. Valid types are: sse, http, stdio`
17529
+ );
17300
17530
  } else {
17301
17531
  const u = new URL(url);
17302
17532
  if (u.pathname.endsWith("/mcp")) {
@@ -19078,6 +19308,26 @@ function readInspectorVersion(callerUrl) {
19078
19308
  );
19079
19309
  }
19080
19310
 
19311
+ // ../../core/cli/handlers/connect-timeout.ts
19312
+ var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
19313
+ var CLI_CONNECTION_TIMEOUT_REMEDY = "To accommodate a slower server, pass a larger --connect-timeout <ms>, or set connectionTimeout on the server's catalog or config entry.";
19314
+ function withConnectTimeout(settings, connectionTimeout) {
19315
+ if (connectionTimeout === void 0) return settings;
19316
+ if (settings) return { ...settings, connectionTimeout };
19317
+ return {
19318
+ headers: [],
19319
+ metadata: {},
19320
+ env: [],
19321
+ connectionTimeout,
19322
+ requestTimeout: 0,
19323
+ taskTtl: DEFAULT_TASK_TTL_MS,
19324
+ maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
19325
+ autoRefreshOnListChanged: false,
19326
+ paginatedLists: false,
19327
+ roots: []
19328
+ };
19329
+ }
19330
+
19081
19331
  // src/connection/auth-helper.ts
19082
19332
  import { spawn } from "child_process";
19083
19333
  import { createHash as createHash2 } from "crypto";
@@ -19471,13 +19721,15 @@ async function runEmaIdpInteractiveFlow(active, storage, navigation) {
19471
19721
  );
19472
19722
  const redirectUrlProvider = new MutableRedirectUrlProvider();
19473
19723
  redirectUrlProvider.redirectUrl = formatRunnerOAuthRedirectUrl(callbackUrlConfig);
19724
+ const fetchFn = createProxyFetch();
19474
19725
  await runRunnerInteractiveOAuth({
19475
19726
  client: {
19476
19727
  authenticate: async () => {
19477
19728
  const { authorizationUrl } = await startIdpOidcAuthorization({
19478
19729
  idp: active,
19479
19730
  redirectUrl: redirectUrlProvider.redirectUrl,
19480
- storage
19731
+ storage,
19732
+ fetchFn
19481
19733
  });
19482
19734
  navigation.navigateToAuthorization(authorizationUrl);
19483
19735
  return authorizationUrl;
@@ -19491,7 +19743,8 @@ async function runEmaIdpInteractiveFlow(active, storage, navigation) {
19491
19743
  authorizationCode,
19492
19744
  iss,
19493
19745
  redirectUrl: redirectUrlProvider.redirectUrl,
19494
- storage
19746
+ storage,
19747
+ fetchFn
19495
19748
  });
19496
19749
  },
19497
19750
  /* v8 ignore next 2 -- only reached when options.authChallenge is set, which this flow never does */
@@ -19511,7 +19764,13 @@ async function authorizeInFrontend(serverConfig, serverSettings, options) {
19511
19764
  return;
19512
19765
  }
19513
19766
  const environment = {
19514
- transport: createTransportNode
19767
+ transport: createTransportNode,
19768
+ // HTTPS_PROXY / HTTP_PROXY / NO_PROXY, as in the one-shot CLI (#2642):
19769
+ // `InspectorClient` always hands the transport a defined fetch, so the
19770
+ // transport's own proxy fallback never fires — the proxy has to sit here,
19771
+ // at the bottom of the stack, for OAuth discovery and token requests to
19772
+ // reach it. Undefined when no proxy variable is set.
19773
+ fetch: createProxyFetch()
19515
19774
  };
19516
19775
  const redirectUrlProvider = new MutableRedirectUrlProvider();
19517
19776
  const callbackUrlConfig = parseRunnerOAuthCallbackUrl(
@@ -19545,6 +19804,8 @@ async function authorizeInFrontend(serverConfig, serverSettings, options) {
19545
19804
  name: "inspector-cli",
19546
19805
  version: readInspectorVersion(import.meta.url)
19547
19806
  },
19807
+ // Same knobs as the daemon's connect: --connect-timeout or mcp.json (#2615).
19808
+ connectionTimeoutRemedy: CLI_CONNECTION_TIMEOUT_REMEDY,
19548
19809
  initialLoggingLevel: "debug",
19549
19810
  progress: false,
19550
19811
  sample: false,
@@ -19754,9 +20015,15 @@ function tryReserveAuthFlow(lockPath) {
19754
20015
  if (!isStale(fs8.statSync(lockPath).mtimeMs)) return false;
19755
20016
  const claimPath = `${lockPath}.claim-${process.pid}`;
19756
20017
  fs8.renameSync(lockPath, claimPath);
19757
- const claimedFresh = !isStale(fs8.statSync(claimPath).mtimeMs);
20018
+ if (!isStale(fs8.statSync(claimPath).mtimeMs)) {
20019
+ try {
20020
+ fs8.linkSync(claimPath, lockPath);
20021
+ } finally {
20022
+ fs8.rmSync(claimPath, { force: true });
20023
+ }
20024
+ return false;
20025
+ }
19758
20026
  fs8.rmSync(claimPath, { force: true });
19759
- if (claimedFresh) return false;
19760
20027
  create();
19761
20028
  return true;
19762
20029
  } catch {
@@ -20411,7 +20678,13 @@ async function getLiveConnectionAuthInfo(connection) {
20411
20678
  }
20412
20679
  async function createConnectionClient(serverConfig, serverSettings) {
20413
20680
  const environment = {
20414
- transport: createTransportNode
20681
+ transport: createTransportNode,
20682
+ // HTTPS_PROXY / HTTP_PROXY / NO_PROXY, as in the one-shot CLI (#2642).
20683
+ // `InspectorClient` builds its fetch chain on `environment.fetch ?? fetch`
20684
+ // and always hands the transport the result, so the transport's own
20685
+ // `createProxyFetch()` fallback is never reached — leave this out and the
20686
+ // daemon connects direct. Undefined when no proxy variable is set.
20687
+ fetch: createProxyFetch()
20415
20688
  };
20416
20689
  const redirectUrlProvider = new MutableRedirectUrlProvider();
20417
20690
  if (isOAuthCapableServerConfig(serverConfig)) {
@@ -20437,6 +20710,9 @@ async function createConnectionClient(serverConfig, serverSettings) {
20437
20710
  name: CONNECTION_CLIENT_NAME,
20438
20711
  version: readInspectorVersion(import.meta.url)
20439
20712
  },
20713
+ // `mcpdo connect` takes the same --connect-timeout as the one-shot CLI, so
20714
+ // a timeout names that rather than the web's Server Settings (#2615).
20715
+ connectionTimeoutRemedy: CLI_CONNECTION_TIMEOUT_REMEDY,
20440
20716
  initialLoggingLevel: "debug",
20441
20717
  progress: false,
20442
20718
  sample: false,
@@ -20493,6 +20769,9 @@ function connectCancelledError() {
20493
20769
  }
20494
20770
 
20495
20771
  // src/daemon/server.ts
20772
+ var MCPDO_RUN_OPTIONS = {
20773
+ skillBudgetHint: MCPDO_SKILL_BUDGET_HINT
20774
+ };
20496
20775
  var autoCancelElicitationChannel = {
20497
20776
  request(frame) {
20498
20777
  return Promise.resolve({
@@ -20924,7 +21203,7 @@ var DaemonServer = class _DaemonServer {
20924
21203
  const clearAmbientSignal = client.setAmbientRequestSignal(signal);
20925
21204
  let outcome;
20926
21205
  try {
20927
- outcome = await runMethod(client, methodArgs);
21206
+ outcome = await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS);
20928
21207
  } finally {
20929
21208
  clearAmbientSignal();
20930
21209
  signal?.removeEventListener("abort", onAbort);
@@ -20968,7 +21247,10 @@ var DaemonServer = class _DaemonServer {
20968
21247
  const unwire = wireElicitationBridge(client, channel, requestId);
20969
21248
  const outcome = (async () => {
20970
21249
  try {
20971
- return toRpcResult(await runMethod(client, methodArgs), params.method);
21250
+ return toRpcResult(
21251
+ await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS),
21252
+ params.method
21253
+ );
20972
21254
  } finally {
20973
21255
  unwire();
20974
21256
  }
@@ -21087,7 +21369,7 @@ var DaemonServer = class _DaemonServer {
21087
21369
  params.interactive
21088
21370
  );
21089
21371
  const methodArgs = stripConnectionFields(params);
21090
- const outcome = await runMethod(client, methodArgs);
21372
+ const outcome = await runMethod(client, methodArgs, MCPDO_RUN_OPTIONS);
21091
21373
  if (outcome.kind !== "stream") {
21092
21374
  throw new CliExitCodeError(
21093
21375
  EXIT_CODES.USAGE,
@@ -21336,6 +21618,8 @@ export {
21336
21618
  resetNodeOAuthStorageCache,
21337
21619
  clearAllOAuthClientState,
21338
21620
  NodeOAuthStorage,
21621
+ DEFAULT_CONNECT_TIMEOUT_MS,
21622
+ withConnectTimeout,
21339
21623
  PLAIN,
21340
21624
  styleFromOpts,
21341
21625
  isCliAutoOpenForced,