@modelcontextprotocol/inspector 2.11.0 → 2.12.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.
@@ -177,6 +177,12 @@ var DirectoryReadResultSchema = z.looseObject({
177
177
  resources: z.array(DirectoryChildSchema),
178
178
  nextCursor: z.string().optional()
179
179
  });
180
+ var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
181
+ {
182
+ ttlMs: z.int().min(0),
183
+ cacheScope: z.enum(["public", "private"])
184
+ }
185
+ );
180
186
 
181
187
  // ../../core/mcp/sha256.ts
182
188
  var K = new Uint32Array([
@@ -5576,6 +5582,23 @@ function subscriptionFailureMessage(error) {
5576
5582
  return error instanceof Error ? error.message : String(error);
5577
5583
  }
5578
5584
 
5585
+ // ../../core/mcp/subscriptionRefusal.ts
5586
+ import { SdkErrorCode as SdkErrorCode2, SdkHttpError } from "@modelcontextprotocol/client";
5587
+ var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
5588
+ function subscriptionListenRefusal(error) {
5589
+ if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode2.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
5590
+ return void 0;
5591
+ }
5592
+ return {
5593
+ status: error.status,
5594
+ ...error.statusText ? { statusText: error.statusText } : {}
5595
+ };
5596
+ }
5597
+ function subscriptionListenRefusalMessage(refusal) {
5598
+ const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
5599
+ return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
5600
+ }
5601
+
5579
5602
  // ../../core/mcp/messageTrackingTransport.ts
5580
5603
  var MessageTrackingTransport = class {
5581
5604
  baseTransport;
@@ -5805,7 +5828,7 @@ var TaskProgressRouter = class {
5805
5828
  };
5806
5829
 
5807
5830
  // ../../core/extension/tasks/rawWireChannel.ts
5808
- import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
5831
+ import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
5809
5832
  import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
5810
5833
  var RAW_WIRE_ID_PREFIX = "inspector-ext-";
5811
5834
  var RawWireChannel = class {
@@ -5878,8 +5901,8 @@ var RawWireChannel = class {
5878
5901
  `Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
5879
5902
  false,
5880
5903
  {
5881
- cause: this.host.annotateTimeout(
5882
- new SdkError2(SdkErrorCode2.RequestTimeout, "Request timed out", {
5904
+ cause: this.host.annotateError(
5905
+ new SdkError2(SdkErrorCode3.RequestTimeout, "Request timed out", {
5883
5906
  timeout: timeoutMs
5884
5907
  }),
5885
5908
  message.method
@@ -5919,7 +5942,7 @@ var RawWireChannel = class {
5919
5942
  if (!pending) return;
5920
5943
  pending.cleanup();
5921
5944
  reject(
5922
- this.host.annotateTimeout(
5945
+ this.host.annotateError(
5923
5946
  error instanceof Error ? error : new Error(String(error)),
5924
5947
  message.method
5925
5948
  )
@@ -6219,7 +6242,7 @@ function validateToolOutput(provider, tool, result) {
6219
6242
 
6220
6243
  // ../../core/mcp/listSalvage.ts
6221
6244
  import { z as z4 } from "zod/v4";
6222
- import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
6245
+ import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
6223
6246
  import { ToolSchema } from "@modelcontextprotocol/core";
6224
6247
  var LIST_MAX_PAGES = 64;
6225
6248
  function listPaginationExceeded(method) {
@@ -6228,10 +6251,10 @@ function listPaginationExceeded(method) {
6228
6251
  );
6229
6252
  }
6230
6253
  function isClientDecodeRejection(err) {
6231
- return SdkError3.isInstance(err) && (err.code === SdkErrorCode3.InvalidResult || err.code === SdkErrorCode3.UnsupportedResultType);
6254
+ return SdkError3.isInstance(err) && (err.code === SdkErrorCode4.InvalidResult || err.code === SdkErrorCode4.UnsupportedResultType);
6232
6255
  }
6233
6256
  function isSalvageableRejection(err) {
6234
- return SdkError3.isInstance(err) && err.code === SdkErrorCode3.InvalidResult;
6257
+ return SdkError3.isInstance(err) && err.code === SdkErrorCode4.InvalidResult;
6235
6258
  }
6236
6259
  function lenientListPageSchema(resultSchema, itemsKey) {
6237
6260
  return resultSchema.extend({ [itemsKey]: z4.array(z4.unknown()) });
@@ -7409,12 +7432,33 @@ function createSilentLogger() {
7409
7432
  var silentLogger = createSilentLogger();
7410
7433
 
7411
7434
  // ../../core/mcp/connectionDiagnostics.ts
7412
- import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
7435
+ import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
7413
7436
  function isRequestTimeoutData(value) {
7414
7437
  return typeof value === "object" && value !== null && typeof value.timeout === "number";
7415
7438
  }
7416
7439
  function isRequestTimeoutError(err) {
7417
- return SdkError4.isInstance(err) && err.code === SdkErrorCode4.RequestTimeout && isRequestTimeoutData(err.data);
7440
+ return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
7441
+ }
7442
+ function describeSendFailure(err) {
7443
+ const message = err instanceof Error ? err.message : String(err);
7444
+ const status = httpStatusOf(err);
7445
+ if (status === void 0 || message.includes(`HTTP ${status}`)) {
7446
+ return message;
7447
+ }
7448
+ const statusText = httpStatusTextOf(err);
7449
+ return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
7450
+ }
7451
+ function httpStatusOf(err) {
7452
+ if (typeof err !== "object" || err === null || !("status" in err)) {
7453
+ return void 0;
7454
+ }
7455
+ return typeof err.status === "number" ? err.status : void 0;
7456
+ }
7457
+ function httpStatusTextOf(err) {
7458
+ if (typeof err !== "object" || err === null || !("statusText" in err)) {
7459
+ return void 0;
7460
+ }
7461
+ return typeof err.statusText === "string" ? err.statusText : void 0;
7418
7462
  }
7419
7463
  function formatDuration(ms) {
7420
7464
  const clamped = Math.max(0, ms);
@@ -7476,11 +7520,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
7476
7520
  diagnostics
7477
7521
  };
7478
7522
  const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
7479
- const annotated = new SdkError4(SdkErrorCode4.RequestTimeout, message, data);
7523
+ const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
7480
7524
  annotated.cause = err;
7481
7525
  return annotated;
7482
7526
  }
7483
7527
 
7528
+ // ../../core/mcp/rateLimit.ts
7529
+ import {
7530
+ SdkHttpError as SdkHttpError2
7531
+ } from "@modelcontextprotocol/client";
7532
+ var HTTP_TOO_MANY_REQUESTS = 429;
7533
+ function requestIdsOf(body) {
7534
+ if (body === void 0) return [];
7535
+ let parsed;
7536
+ try {
7537
+ parsed = JSON.parse(body);
7538
+ } catch {
7539
+ return [];
7540
+ }
7541
+ const messages = Array.isArray(parsed) ? parsed : [parsed];
7542
+ const ids = [];
7543
+ for (const message of messages) {
7544
+ if (typeof message !== "object" || message === null) continue;
7545
+ if (!("method" in message) || !("id" in message)) continue;
7546
+ const { id } = message;
7547
+ if (typeof id === "string" || typeof id === "number") ids.push(id);
7548
+ }
7549
+ return ids;
7550
+ }
7551
+ var MAX_UNCLAIMED_RATE_LIMITS = 100;
7552
+ var RateLimitLedger = class {
7553
+ // `undefined` is a real value here: a 429 that carried no header.
7554
+ byRequestId = /* @__PURE__ */ new Map();
7555
+ /** File a tracked fetch, if it was a 429. Anything else is ignored. */
7556
+ record(entry) {
7557
+ if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
7558
+ const retryAfter = findHeader(entry.responseHeaders, "retry-after");
7559
+ for (const id of requestIdsOf(entry.requestBody)) {
7560
+ this.byRequestId.delete(id);
7561
+ this.byRequestId.set(id, retryAfter);
7562
+ if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
7563
+ const [oldest] = this.byRequestId.keys();
7564
+ if (oldest !== void 0) this.byRequestId.delete(oldest);
7565
+ }
7566
+ }
7567
+ }
7568
+ /** The `Retry-After` filed for `id`, removed as it is read. */
7569
+ take(id) {
7570
+ const retryAfter = this.byRequestId.get(id);
7571
+ this.byRequestId.delete(id);
7572
+ return retryAfter;
7573
+ }
7574
+ clear() {
7575
+ this.byRequestId.clear();
7576
+ }
7577
+ };
7578
+ var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
7579
+ function parseRetryAfter(value, now) {
7580
+ if (value === void 0) return void 0;
7581
+ const trimmed = value.trim();
7582
+ if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
7583
+ if (!IMF_FIXDATE.test(trimmed)) return void 0;
7584
+ const at = Date.parse(trimmed);
7585
+ if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
7586
+ return void 0;
7587
+ }
7588
+ return Math.max(0, at - now);
7589
+ }
7590
+ function isRateLimitError(err) {
7591
+ return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
7592
+ }
7593
+ function retryAfterOn(err) {
7594
+ const value = err.data.retryAfter;
7595
+ return typeof value === "string" ? value : void 0;
7596
+ }
7597
+ function attachRetryAfter(err, retryAfter) {
7598
+ if (!isRateLimitError(err) || retryAfter === void 0) return;
7599
+ if (retryAfterOn(err) !== void 0) return;
7600
+ err.data.retryAfter = retryAfter;
7601
+ }
7602
+ function describeRetry(retryAfter, retryAfterMs) {
7603
+ if (retryAfterMs !== void 0) {
7604
+ return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
7605
+ }
7606
+ if (retryAfter !== void 0) {
7607
+ return `The server sent an unparseable Retry-After (${retryAfter}).`;
7608
+ }
7609
+ return "The server sent no Retry-After header.";
7610
+ }
7611
+ function annotateRateLimit(err, method, now = Date.now()) {
7612
+ if (!isRateLimitError(err)) return err;
7613
+ const header = retryAfterOn(err);
7614
+ const retryAfterMs = parseRetryAfter(header, now);
7615
+ const data = {
7616
+ ...err.data,
7617
+ method,
7618
+ ...header !== void 0 && { retryAfter: header },
7619
+ ...retryAfterMs !== void 0 && { retryAfterMs }
7620
+ };
7621
+ const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
7622
+ return new SdkHttpError2(err.code, message, data, { cause: err });
7623
+ }
7624
+
7484
7625
  // ../../core/auth/authorizationParams.ts
7485
7626
  var RESERVED_AUTHORIZATION_PARAMS = [
7486
7627
  "client_id",
@@ -7598,6 +7739,13 @@ var BaseOAuthClientProvider = class {
7598
7739
  requestRefreshToken;
7599
7740
  /** Server-configured scope, for the #2068 `offline_access` distinction. */
7600
7741
  configuredScope;
7742
+ /** Per-flow verifier store (#2726); absent → the shared storage slot. */
7743
+ flowStore;
7744
+ /**
7745
+ * The authId of the flow this provider is driving: set by {@link state} on the
7746
+ * redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
7747
+ */
7748
+ flowId;
7601
7749
  constructor(serverUrl, oauthConfig) {
7602
7750
  this.serverUrl = serverUrl;
7603
7751
  this.storage = oauthConfig.storage;
@@ -7607,6 +7755,7 @@ var BaseOAuthClientProvider = class {
7607
7755
  this.authorizationParams = oauthConfig.authorizationParams;
7608
7756
  this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
7609
7757
  this.configuredScope = oauthConfig.configuredScope;
7758
+ this.flowStore = oauthConfig.flowStore;
7610
7759
  }
7611
7760
  /**
7612
7761
  * Load persisted scope into {@link cachedScope} before SDK `auth()` (which
@@ -7677,7 +7826,24 @@ var BaseOAuthClientProvider = class {
7677
7826
  return metadata;
7678
7827
  }
7679
7828
  state() {
7680
- return generateOAuthState();
7829
+ const state = generateOAuthState();
7830
+ this.flowId = state;
7831
+ return state;
7832
+ }
7833
+ /**
7834
+ * Bind this provider to the flow a callback's `state` names, so
7835
+ * {@link codeVerifier} reads that flow's verifier and no other (#2726). A
7836
+ * `state` that is not one of ours binds nothing — and, with a flow store, a
7837
+ * flow-less read then fails rather than reaching for the shared slot.
7838
+ */
7839
+ resumeAuthorizationFlow(state) {
7840
+ this.flowId = parseOAuthState(state)?.authId;
7841
+ }
7842
+ /** Drop the bound flow's verifier once its exchange has completed. */
7843
+ async clearAuthorizationFlow() {
7844
+ if (this.flowStore && this.flowId) {
7845
+ await this.flowStore.clearCodeVerifier(this.flowId);
7846
+ }
7681
7847
  }
7682
7848
  async clientInformation(ctx) {
7683
7849
  const preregistered = await this.storage.getClientInformation(
@@ -7822,9 +7988,26 @@ var BaseOAuthClientProvider = class {
7822
7988
  }
7823
7989
  }
7824
7990
  async saveCodeVerifier(codeVerifier) {
7991
+ if (this.flowStore && this.flowId) {
7992
+ await this.flowStore.saveCodeVerifier(
7993
+ this.flowId,
7994
+ this.serverUrl,
7995
+ codeVerifier
7996
+ );
7997
+ return;
7998
+ }
7825
7999
  await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
7826
8000
  }
7827
8001
  async codeVerifier() {
8002
+ if (this.flowStore) {
8003
+ const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
8004
+ if (!verifier2) {
8005
+ throw new Error(
8006
+ "No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
8007
+ );
8008
+ }
8009
+ return verifier2;
8010
+ }
7828
8011
  const verifier = await this.storage.getCodeVerifier(this.serverUrl);
7829
8012
  if (!verifier) {
7830
8013
  throw new Error("No code verifier saved for session");
@@ -7871,6 +8054,10 @@ var BaseOAuthClientProvider = class {
7871
8054
  await this.storage.clearTokens(this.serverUrl);
7872
8055
  return;
7873
8056
  case "verifier":
8057
+ if (this.flowStore) {
8058
+ await this.clearAuthorizationFlow();
8059
+ return;
8060
+ }
7874
8061
  await this.storage.clearCodeVerifier(this.serverUrl);
7875
8062
  return;
7876
8063
  case "discovery":
@@ -8741,11 +8928,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
8741
8928
  "EMA requires resource authorization server clientId (per-server oauth.clientId)"
8742
8929
  );
8743
8930
  }
8744
- if (!config.resourceClientSecret?.trim()) {
8745
- throw new Error(
8746
- "EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
8747
- );
8748
- }
8749
8931
  const idToken = await getValidIdToken({
8750
8932
  idp: config.idp,
8751
8933
  storage: config.storage,
@@ -9252,7 +9434,9 @@ var OAuthManager = class {
9252
9434
  requestRefreshToken: this.oauthConfig.requestRefreshToken,
9253
9435
  // #2068: lets the provider tell an `offline_access` the user asked for
9254
9436
  // from one inherited from a previous grant's persisted scope.
9255
- configuredScope: this.oauthConfig.scope
9437
+ configuredScope: this.oauthConfig.scope,
9438
+ // #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
9439
+ flowStore: this.oauthConfig.flowStore
9256
9440
  });
9257
9441
  provider.setEventTarget(this.params.getEventTarget());
9258
9442
  await provider.prepareForAuth();
@@ -9416,7 +9600,12 @@ var OAuthManager = class {
9416
9600
  recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
9417
9601
  this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
9418
9602
  }
9419
- async completeOAuthFlow(authorizationCode, iss) {
9603
+ /**
9604
+ * `state` is the OAuth `state` the callback was redirected back with. It
9605
+ * selects the flow whose PKCE verifier the exchange uses (#2726); without it
9606
+ * a provider with a flow store has no verifier to send.
9607
+ */
9608
+ async completeOAuthFlow(authorizationCode, iss, state) {
9420
9609
  try {
9421
9610
  if (this.isEnterpriseManaged()) {
9422
9611
  const emaConfig = await this.getEmaFlowConfig();
@@ -9440,6 +9629,9 @@ var OAuthManager = class {
9440
9629
  }
9441
9630
  const provider = await this.createOAuthProvider();
9442
9631
  const serverUrl = this.getServerUrl();
9632
+ if (state !== void 0) {
9633
+ provider.resumeAuthorizationFlow(state);
9634
+ }
9443
9635
  const result = await mcpAuth(provider, {
9444
9636
  serverUrl,
9445
9637
  authorizationCode,
@@ -9451,6 +9643,7 @@ var OAuthManager = class {
9451
9643
  `Expected AUTHORIZED after providing authorization code, got: ${result}`
9452
9644
  );
9453
9645
  }
9646
+ await provider.clearAuthorizationFlow();
9454
9647
  const tokens = await provider.tokens();
9455
9648
  if (!tokens) {
9456
9649
  throw new Error("Failed to retrieve tokens after authorization");
@@ -9582,15 +9775,21 @@ var OAuthManager = class {
9582
9775
  * Re-read persisted OAuth state and determine whether `challenge` is already
9583
9776
  * satisfied without an authorization-server round-trip.
9584
9777
  *
9585
- * Returns `true` for `insufficient_scope` when stored + token scope cover the
9586
- * SEP-2350 union. For `token_expired`, returns `true` only when storage holds
9587
- * a *provably* unexpired token — a JWT whose `exp` is still in the future. An
9588
- * opaque token carries no local expiry evidence, so the resource server's
9589
- * verdict stands and re-authorization proceeds. `invalid_token` and
9590
- * `unauthorized` always return `false` — the resource server explicitly
9591
- * rejected the credential.
9778
+ * For `token_expired` and `insufficient_scope`, returns `true` only when
9779
+ * storage holds a *provably* unexpired token — a JWT whose `exp` is still in
9780
+ * the future. An opaque token carries no local expiry evidence, so the
9781
+ * resource server's verdict stands and re-authorization proceeds.
9782
+ * `insufficient_scope` additionally requires stored + token scope to cover
9783
+ * the SEP-2350 union: some resource servers answer an *expired* token with
9784
+ * `insufficient_scope`, and a scope-only check would replay that dead token
9785
+ * instead of refreshing it (#2724, the same gap #2051 closed for
9786
+ * `token_expired`). `invalid_token` and `unauthorized` always return
9787
+ * `false` — the resource server explicitly rejected the credential.
9788
+ *
9789
+ * A caller that has just completed a grant passes `scopeOnly` — see
9790
+ * {@link CheckAuthChallengeSatisfiedOptions}.
9592
9791
  */
9593
- async checkAuthChallengeSatisfied(challenge) {
9792
+ async checkAuthChallengeSatisfied(challenge, options) {
9594
9793
  const storage = this.oauthConfig.storage;
9595
9794
  if (!storage) {
9596
9795
  return false;
@@ -9603,6 +9802,14 @@ var OAuthManager = class {
9603
9802
  if (challenge.reason !== "insufficient_scope") {
9604
9803
  return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
9605
9804
  }
9805
+ if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
9806
+ return false;
9807
+ }
9808
+ return this.isStepUpScopeCovered(challenge, storage, tokens);
9809
+ }
9810
+ /** Stored + token scope cover the challenge's SEP-2350 union. */
9811
+ async isStepUpScopeCovered(challenge, storage, tokens) {
9812
+ const serverUrl = this.getServerUrl();
9606
9813
  const enriched = await this.enrichChallengeWithAuthorizationScopes(
9607
9814
  challenge,
9608
9815
  tokens.scope
@@ -9706,14 +9913,14 @@ var OAuthManager = class {
9706
9913
  }
9707
9914
  async handleEnterpriseManagedAuthChallenge(challenge, options) {
9708
9915
  const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
9709
- if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
9916
+ if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
9710
9917
  return { kind: "step_up_confirm", challenge: enriched };
9711
9918
  }
9712
9919
  const config = await this.emaFlowConfigForChallenge(enriched);
9713
9920
  if (enriched.reason === "insufficient_scope") {
9714
9921
  const silent = await trySilentEmaAuth(config);
9715
9922
  if (silent.status === "success") {
9716
- if (await this.checkAuthChallengeSatisfied(enriched)) {
9923
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
9717
9924
  const minted = await this.requireStorage().getTokens(
9718
9925
  this.getServerUrl()
9719
9926
  );
@@ -9791,9 +9998,16 @@ var OAuthManager = class {
9791
9998
  fetchFn: this.params.effectiveAuthFetch,
9792
9999
  resourceMetadataUrl,
9793
10000
  ...enriched.reason === "insufficient_scope" && {
10001
+ // Judge coverage the way checkAuthChallengeSatisfied does: a token
10002
+ // response that omitted `scope` granted what was stored, so a
10003
+ // covered challenge on an expired token refreshes rather than
10004
+ // forcing interactive authorization (#2724).
9794
10005
  forceReauthorization: isStrictScopeSuperset(
9795
10006
  scopeForAuth,
9796
- tokens?.scope
10007
+ resolveEffectiveGrantedScope(
10008
+ await this.oauthConfig.storage?.getScope(serverUrl),
10009
+ tokens?.scope
10010
+ )
9797
10011
  )
9798
10012
  }
9799
10013
  });
@@ -9802,7 +10016,7 @@ var OAuthManager = class {
9802
10016
  }
9803
10017
  if (result === "AUTHORIZED") {
9804
10018
  if (enriched.reason === "insufficient_scope") {
9805
- if (await this.checkAuthChallengeSatisfied(enriched)) {
10019
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
9806
10020
  const freshTokens = await provider.tokens();
9807
10021
  const scopeToPersist = resolvePersistedScopeAfterGrant(
9808
10022
  freshTokens?.scope,
@@ -9904,7 +10118,7 @@ var OAuthManager = class {
9904
10118
  if (result !== "AUTHORIZED") {
9905
10119
  return null;
9906
10120
  }
9907
- if (await this.checkAuthChallengeSatisfied(enriched)) {
10121
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
9908
10122
  const freshTokens = await provider.tokens();
9909
10123
  const scopeToPersist = resolvePersistedScopeAfterGrant(
9910
10124
  freshTokens?.scope,
@@ -9936,8 +10150,8 @@ var OAuthManager = class {
9936
10150
  // ../../core/mcp/remote/remoteClientTransport.ts
9937
10151
  import {
9938
10152
  SdkError as SdkError5,
9939
- SdkErrorCode as SdkErrorCode5,
9940
- SdkHttpError
10153
+ SdkErrorCode as SdkErrorCode6,
10154
+ SdkHttpError as SdkHttpError3
9941
10155
  } from "@modelcontextprotocol/client";
9942
10156
 
9943
10157
  // ../../core/mcp/remote/types.ts
@@ -9994,14 +10208,15 @@ function isConnectTransportError(json) {
9994
10208
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
9995
10209
  }
9996
10210
  var SDK_ERROR_CODES = new Set(
9997
- Object.values(SdkErrorCode5)
10211
+ Object.values(SdkErrorCode6)
9998
10212
  );
9999
10213
  function toSdkHttpError(message, http) {
10000
- const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode5.ClientHttpNotImplemented;
10001
- return new SdkHttpError(code, message, {
10214
+ const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
10215
+ return new SdkHttpError3(code, message, {
10002
10216
  status: http.status,
10003
10217
  ...http.statusText !== void 0 && { statusText: http.statusText },
10004
- ...http.text !== void 0 && { text: http.text }
10218
+ ...http.text !== void 0 && { text: http.text },
10219
+ ...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
10005
10220
  });
10006
10221
  }
10007
10222
  function legacySessionId(json) {
@@ -10435,7 +10650,7 @@ var RemoteClientTransport = class {
10435
10650
  timer = setTimeout(() => {
10436
10651
  this.sseResponseWaits.delete(requestId);
10437
10652
  reject(
10438
- new SdkError5(SdkErrorCode5.RequestTimeout, "Request timed out", {
10653
+ new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
10439
10654
  timeout: this.sseResponseTimeoutMs
10440
10655
  })
10441
10656
  );
@@ -10647,7 +10862,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10647
10862
  transport: () => this.transport,
10648
10863
  defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
10649
10864
  resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
10650
- annotateTimeout: (error, method) => annotateRequestTimeout(error, method, this.getConnectionDiagnostics())
10865
+ annotateError: (error, method) => this.annotateRequestError(error, method)
10651
10866
  });
10652
10867
  dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
10653
10868
  // Every outbound request still awaiting a response, keyed by JSON-RPC id.
@@ -10667,6 +10882,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10667
10882
  // `GET` on Streamable HTTP). Both per-session, reset with the maps above.
10668
10883
  lastResponse;
10669
10884
  notificationStream;
10885
+ // Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
10886
+ // that id's failed send claims it (#2442). Per-session, reset with the
10887
+ // diagnostics above.
10888
+ rateLimits = new RateLimitLedger();
10670
10889
  /** True when the cached transport was built with an OAuth authProvider attached. */
10671
10890
  transportHasAuthProvider = false;
10672
10891
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
@@ -10811,6 +11030,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
10811
11030
  // site to the two failure handlers. Cleared by any user-initiated refresh (a
10812
11031
  // subscribe/unsubscribe is a fresh attempt, and the server may have changed).
10813
11032
  modernNeverAcknowledged = false;
11033
+ // Set when the last `listen()` was refused at the HTTP layer with a status a
11034
+ // retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
11035
+ // carried and cleared the same way, holding the HTTP answer the UI reports.
11036
+ modernListenRefusal = null;
10814
11037
  /** Which in-flight task calls own each progress token (#2316). */
10815
11038
  taskProgress = new TaskProgressRouter();
10816
11039
  // Abort controller for the in-flight ordinary (non-task) tool call. Aborting
@@ -11068,7 +11291,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11068
11291
  this.clientInfo,
11069
11292
  Object.keys(clientOptions).length > 0 ? clientOptions : void 0
11070
11293
  );
11071
- this.annotateSdkRequestTimeouts(this.client);
11294
+ this.annotateSdkRequestErrors(this.client);
11072
11295
  if (this.tasksCapabilityAdvertised) {
11073
11296
  this.bindReceiverTasks();
11074
11297
  }
@@ -11076,7 +11299,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11076
11299
  /**
11077
11300
  * Decorate the SDK Client's `request` so a per-request timeout rejects with
11078
11301
  * the connection's state at that moment instead of a bare `Request timed
11079
- * out` (#2318).
11302
+ * out` (#2318), and a `429` rejects as rate limiting with the server's
11303
+ * `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
11080
11304
  *
11081
11305
  * `Protocol.request` is the one funnel every SDK verb goes through —
11082
11306
  * `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
@@ -11094,21 +11318,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11094
11318
  * implementation signature serves both at runtime; the single cast on the
11095
11319
  * way back is what the overloads cost.
11096
11320
  */
11097
- annotateSdkRequestTimeouts(client) {
11321
+ annotateSdkRequestErrors(client) {
11098
11322
  const sdkRequest = client.request.bind(client);
11099
11323
  const decorated = async (...args) => {
11100
11324
  try {
11101
11325
  return await sdkRequest(...args);
11102
11326
  } catch (err) {
11103
- throw annotateRequestTimeout(
11104
- err,
11105
- args[0].method,
11106
- this.getConnectionDiagnostics()
11107
- );
11327
+ throw this.annotateRequestError(err, args[0].method);
11108
11328
  }
11109
11329
  };
11110
11330
  client.request = decorated;
11111
11331
  }
11332
+ /**
11333
+ * The one place a failed request's error is decorated, shared by the SDK
11334
+ * funnel above and the raw-wire channel that bypasses it (modern-era tool
11335
+ * calls and task operations), so a caller sees the same error whichever
11336
+ * path its request took: a timeout with the connection's state (#2318), a
11337
+ * `429` named as rate limiting (#2442), anything else untouched.
11338
+ */
11339
+ annotateRequestError(err, method) {
11340
+ return annotateRateLimit(
11341
+ annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
11342
+ method
11343
+ );
11344
+ }
11112
11345
  /**
11113
11346
  * @param base - the composed OAuth-path fetch, deadline innermost (#2319).
11114
11347
  *
@@ -11152,9 +11385,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11152
11385
  },
11153
11386
  trackSendFailure: (message, error) => {
11154
11387
  if (isRequestTimeoutError(error)) return;
11388
+ attachRetryAfter(error, this.rateLimits.take(message.id));
11155
11389
  if (this.outstandingRequests.delete(message.id)) {
11156
11390
  this.dispatchConnectionDiagnosticsChange();
11157
11391
  }
11392
+ this.dispatchTypedEvent("requestFailed", {
11393
+ id: message.id,
11394
+ reason: describeSendFailure(error)
11395
+ });
11158
11396
  },
11159
11397
  trackResponse: (message, origin) => {
11160
11398
  const responseId = message.id;
@@ -11220,6 +11458,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11220
11458
  };
11221
11459
  baseTransport.onerror = (error) => {
11222
11460
  if (this.status === "connecting") return;
11461
+ if (subscriptionListenRefusal(error) !== void 0) return;
11223
11462
  this.status = "error";
11224
11463
  this.dispatchTypedEvent("statusChange", this.status);
11225
11464
  this.dispatchTypedEvent("error", error);
@@ -11465,6 +11704,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11465
11704
  this.clearModernReconnectTimer();
11466
11705
  this.modernReconnectAttempts = 0;
11467
11706
  this.modernNeverAcknowledged = false;
11707
+ this.modernListenRefusal = null;
11468
11708
  this.modernSubscription = null;
11469
11709
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
11470
11710
  this.dispatchSubscriptionsChange();
@@ -11503,6 +11743,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11503
11743
  this.taskProgress.clear();
11504
11744
  this.lastResponse = void 0;
11505
11745
  this.notificationStream = void 0;
11746
+ this.rateLimits.clear();
11506
11747
  this.dispatchConnectionDiagnosticsChange();
11507
11748
  if (this.malformedListItems.size > 0) {
11508
11749
  this.malformedListItems.clear();
@@ -11605,6 +11846,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
11605
11846
  onFetchRequest: (entry) => {
11606
11847
  this.dispatchFetchRequest({ ...entry, category: "transport" });
11607
11848
  this.noteTransportStream(entry);
11849
+ this.rateLimits.record(entry);
11608
11850
  },
11609
11851
  onFetchResponseBody: (id, body) => {
11610
11852
  this.dispatchFetchRequestBodyUpdate(id, body);
@@ -13944,11 +14186,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13944
14186
  // for page one.
13945
14187
  ...cursor !== void 0 ? { cursor } : {}
13946
14188
  };
14189
+ const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
13947
14190
  try {
13948
14191
  return await this.invokeMcpClient(
13949
14192
  () => this.client.request(
13950
14193
  { method: RESOURCES_DIRECTORY_READ_METHOD, params },
13951
- DirectoryReadResultSchema,
14194
+ resultSchema,
13952
14195
  this.getRequestOptions(this.progressTokenOf(metadata))
13953
14196
  ),
13954
14197
  { method: RESOURCES_DIRECTORY_READ_METHOD }
@@ -14403,6 +14646,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14403
14646
  this.clearModernReconnectTimer();
14404
14647
  this.modernReconnectAttempts = 0;
14405
14648
  this.modernNeverAcknowledged = false;
14649
+ this.modernListenRefusal = null;
14406
14650
  }
14407
14651
  const generation = ++this.modernListenGeneration;
14408
14652
  const previous = this.modernSubscription;
@@ -14421,8 +14665,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14421
14665
  this.getRequestOptions()
14422
14666
  );
14423
14667
  } catch (error) {
14424
- if (generation === this.modernListenGeneration && isNeverAcknowledgedSubscriptionClose(error)) {
14425
- this.modernNeverAcknowledged = true;
14668
+ if (generation === this.modernListenGeneration) {
14669
+ if (isNeverAcknowledgedSubscriptionClose(error)) {
14670
+ this.modernNeverAcknowledged = true;
14671
+ }
14672
+ this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
14426
14673
  }
14427
14674
  throw error;
14428
14675
  }
@@ -14503,6 +14750,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14503
14750
  * its error either way — the retry is about the subscriptions, not the call.
14504
14751
  */
14505
14752
  reconcileModernStreamStateAfterFailedRefresh() {
14753
+ if (this.modernListenRefusal) {
14754
+ this.endModernStreamRefused(this.modernListenRefusal);
14755
+ return;
14756
+ }
14506
14757
  if (!this.wantsModernStream()) {
14507
14758
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
14508
14759
  return;
@@ -14535,6 +14786,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14535
14786
  honoredUris: []
14536
14787
  });
14537
14788
  }
14789
+ /**
14790
+ * Settle the stream on an HTTP refusal of the listen (#2722): the
14791
+ * `"refused"` status carrying the HTTP answer, no reconnect, and a log line
14792
+ * with the sentence the UI shows. Reported whatever `active` says — see
14793
+ * `ResourceSubscriptionStreamState`.
14794
+ */
14795
+ endModernStreamRefused(refusal) {
14796
+ this.clearModernReconnectTimer();
14797
+ const message = subscriptionListenRefusalMessage(refusal);
14798
+ this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
14799
+ this.setModernStreamState({
14800
+ active: this.modernStreamActive(),
14801
+ status: "refused",
14802
+ honoredUris: [],
14803
+ refusal
14804
+ });
14805
+ }
14538
14806
  /**
14539
14807
  * Schedule a reconnect re-listen after the current backoff delay (#1630).
14540
14808
  * `modernReconnectAttempts` reflects the number of *consecutive failed*
@@ -14572,6 +14840,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14572
14840
  this.endModernStreamNeverAcknowledged();
14573
14841
  return;
14574
14842
  }
14843
+ if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
14844
+ this.endModernStreamRefused(this.modernListenRefusal);
14845
+ return;
14846
+ }
14575
14847
  this.modernReconnectAttempts += 1;
14576
14848
  if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
14577
14849
  this.setModernStreamState({
@@ -14726,8 +14998,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14726
14998
  * Re-read OAuth storage and test whether a challenge is already satisfied.
14727
14999
  * See {@link OAuthManager.checkAuthChallengeSatisfied}.
14728
15000
  */
14729
- async checkAuthChallengeSatisfied(challenge) {
14730
- return this.ensureOAuthManager().checkAuthChallengeSatisfied(challenge);
15001
+ async checkAuthChallengeSatisfied(challenge, options) {
15002
+ return this.ensureOAuthManager().checkAuthChallengeSatisfied(
15003
+ challenge,
15004
+ options
15005
+ );
14731
15006
  }
14732
15007
  /**
14733
15008
  * Push recovered OAuth auth state to the remote backend (same MCP session).
@@ -14891,8 +15166,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14891
15166
  * Direct transports reconnect after token exchange so the live MCP session
14892
15167
  * picks up the new Bearer token (mirrors silent recovery reconnect).
14893
15168
  */
14894
- async completeOAuthFlow(authorizationCode, iss) {
14895
- await this.ensureOAuthManager().completeOAuthFlow(authorizationCode, iss);
15169
+ async completeOAuthFlow(authorizationCode, iss, state) {
15170
+ await this.ensureOAuthManager().completeOAuthFlow(
15171
+ authorizationCode,
15172
+ iss,
15173
+ state
15174
+ );
14896
15175
  if (this.usesDirectAuthRecovery()) {
14897
15176
  await this.reconnectAfterAuthRecovery();
14898
15177
  }
@@ -14916,7 +15195,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14916
15195
  * Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
14917
15196
  */
14918
15197
  async resumeAfterOAuth(authorizationCode, options) {
14919
- await this.completeOAuthFlow(authorizationCode, options?.iss);
15198
+ await this.completeOAuthFlow(
15199
+ authorizationCode,
15200
+ options?.iss,
15201
+ options?.state
15202
+ );
14920
15203
  const remoteSessionId = options?.remoteSessionId;
14921
15204
  const transport = this.baseTransport;
14922
15205
  if (remoteSessionId && transport instanceof RemoteClientTransport) {
@@ -15302,6 +15585,16 @@ var MessageLogState = class extends TypedEventTarget {
15302
15585
  return;
15303
15586
  }
15304
15587
  };
15588
+ const onRequestFailed = (event) => {
15589
+ const { id, reason } = event.detail;
15590
+ const requestEntry = this.pendingRequestEntries.get(id);
15591
+ if (!requestEntry) return;
15592
+ this.pendingRequestEntries.delete(id);
15593
+ requestEntry.clientError = reason;
15594
+ requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
15595
+ this.dispatchTypedEvent("message", requestEntry);
15596
+ this.dispatchTypedEvent("messagesChange", this.getMessages());
15597
+ };
15305
15598
  const onStatusChange = () => {
15306
15599
  if (isTerminalStatus(this.client?.getStatus())) {
15307
15600
  this.messages = [];
@@ -15311,11 +15604,13 @@ var MessageLogState = class extends TypedEventTarget {
15311
15604
  };
15312
15605
  this.client.addEventListener("message", onMessage);
15313
15606
  this.client.addEventListener("responseRejected", onResponseRejected);
15607
+ this.client.addEventListener("requestFailed", onRequestFailed);
15314
15608
  this.client.addEventListener("statusChange", onStatusChange);
15315
15609
  this.unsubscribe = () => {
15316
15610
  if (this.client) {
15317
15611
  this.client.removeEventListener("message", onMessage);
15318
15612
  this.client.removeEventListener("responseRejected", onResponseRejected);
15613
+ this.client.removeEventListener("requestFailed", onRequestFailed);
15319
15614
  this.client.removeEventListener("statusChange", onStatusChange);
15320
15615
  }
15321
15616
  this.client = null;
@@ -17083,7 +17378,7 @@ function serializeOAuthPersistBlob(snapshot) {
17083
17378
  import { resolve as resolve6 } from "path";
17084
17379
 
17085
17380
  // ../../core/auth/node/oauth-secrets.ts
17086
- import { randomUUID as randomUUID2 } from "crypto";
17381
+ import { createHmac, randomUUID as randomUUID2 } from "crypto";
17087
17382
  import { OAuthTokensSchema as OAuthTokensSchema5 } from "@modelcontextprotocol/core";
17088
17383
  var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
17089
17384
  var warnedPolicyValues = /* @__PURE__ */ new Set();
@@ -17116,6 +17411,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
17116
17411
  var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
17117
17412
  var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
17118
17413
  var IDP_SESSION_FIELD = "idp-session";
17414
+ var clientCredentialStampField = (field) => `${field}#owner`;
17415
+ var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
17416
+ function clientCredentialStamp(clientId, value) {
17417
+ return JSON.stringify({
17418
+ client_id: clientId,
17419
+ mac: credentialMac(clientId, value)
17420
+ });
17421
+ }
17422
+ function stampedOwner(stamp, value) {
17423
+ if (stamp === void 0) return void 0;
17424
+ let parsed;
17425
+ try {
17426
+ parsed = JSON.parse(stamp);
17427
+ } catch {
17428
+ return void 0;
17429
+ }
17430
+ if (typeof parsed !== "object" || parsed === null) return void 0;
17431
+ const { client_id: clientId, mac } = parsed;
17432
+ if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
17433
+ return void 0;
17434
+ }
17435
+ return clientId;
17436
+ }
17119
17437
  function applyTokensPolicy(tokens, policy) {
17120
17438
  if (policy === "none") return void 0;
17121
17439
  if (policy === "access" && tokens.refresh_token !== void 0) {
@@ -17144,12 +17462,18 @@ function splitClientInformation(info, fields, secrets) {
17144
17462
  if (client_secret === void 0 && registration_access_token === void 0) {
17145
17463
  return info;
17146
17464
  }
17147
- if (client_secret !== void 0) {
17148
- secrets[fields.clientSecret] = client_secret;
17149
- }
17150
- if (registration_access_token !== void 0) {
17151
- secrets[fields.registrationToken] = registration_access_token;
17152
- }
17465
+ const put = (field, value) => {
17466
+ if (value === void 0) return;
17467
+ secrets[field] = value;
17468
+ if (fields.stamped && typeof info.client_id === "string") {
17469
+ secrets[clientCredentialStampField(field)] = clientCredentialStamp(
17470
+ info.client_id,
17471
+ value
17472
+ );
17473
+ }
17474
+ };
17475
+ put(fields.clientSecret, client_secret);
17476
+ put(fields.registrationToken, registration_access_token);
17153
17477
  return publicInfo;
17154
17478
  }
17155
17479
  function joinClientInformation(info, fields, secrets) {
@@ -17168,15 +17492,18 @@ function joinClientInformation(info, fields, secrets) {
17168
17492
  }
17169
17493
  var issuerClientInfoFields = (issuer) => ({
17170
17494
  clientSecret: issuerClientSecretField(issuer),
17171
- registrationToken: issuerRegistrationTokenField(issuer)
17495
+ registrationToken: issuerRegistrationTokenField(issuer),
17496
+ stamped: true
17172
17497
  });
17173
17498
  var LEGACY_CLIENT_INFO_FIELDS = {
17174
17499
  clientSecret: LEGACY_CLIENT_SECRET_FIELD,
17175
- registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
17500
+ registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
17501
+ stamped: true
17176
17502
  };
17177
17503
  var PREREG_CLIENT_INFO_FIELDS = {
17178
17504
  clientSecret: PREREG_CLIENT_SECRET_FIELD,
17179
- registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
17505
+ registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
17506
+ stamped: false
17180
17507
  };
17181
17508
  function splitServerOAuthState(state, policy) {
17182
17509
  const secrets = {};
@@ -17326,20 +17653,54 @@ function joinIdpSession(residue, secrets) {
17326
17653
  return stored ? { ...residue, ...stored } : { ...residue };
17327
17654
  }
17328
17655
  function serverSecretFields(state) {
17656
+ const stamped = (field) => [
17657
+ field,
17658
+ clientCredentialStampField(field)
17659
+ ];
17329
17660
  const fields = /* @__PURE__ */ new Set([
17330
17661
  LEGACY_TOKENS_FIELD,
17331
- LEGACY_CLIENT_SECRET_FIELD,
17332
- LEGACY_REGISTRATION_TOKEN_FIELD,
17662
+ ...stamped(LEGACY_CLIENT_SECRET_FIELD),
17663
+ ...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
17333
17664
  PREREG_CLIENT_SECRET_FIELD,
17334
17665
  PREREG_REGISTRATION_TOKEN_FIELD
17335
17666
  ]);
17336
17667
  for (const issuer of Object.keys(state?.byIssuer ?? {})) {
17337
17668
  fields.add(issuerTokensField(issuer));
17338
- fields.add(issuerClientSecretField(issuer));
17339
- fields.add(issuerRegistrationTokenField(issuer));
17669
+ for (const field of stamped(issuerClientSecretField(issuer))) {
17670
+ fields.add(field);
17671
+ }
17672
+ for (const field of stamped(issuerRegistrationTokenField(issuer))) {
17673
+ fields.add(field);
17674
+ }
17340
17675
  }
17341
17676
  return [...fields];
17342
17677
  }
17678
+ function clientCredentialOwners(state) {
17679
+ const owners = /* @__PURE__ */ new Map();
17680
+ const add2 = (info, fields) => {
17681
+ if (typeof info?.client_id !== "string") return;
17682
+ owners.set(fields.clientSecret, info.client_id);
17683
+ owners.set(fields.registrationToken, info.client_id);
17684
+ };
17685
+ add2(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
17686
+ for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
17687
+ add2(slot.clientInformation, issuerClientInfoFields(issuer));
17688
+ }
17689
+ return owners;
17690
+ }
17691
+ function restampClientCredentials(state, secrets) {
17692
+ const out = { ...secrets };
17693
+ for (const [field, clientId] of clientCredentialOwners(state)) {
17694
+ const stampField = clientCredentialStampField(field);
17695
+ const value = Object.hasOwn(out, field) ? out[field] : void 0;
17696
+ if (value === void 0) {
17697
+ delete out[stampField];
17698
+ } else if (stampedOwner(out[stampField], value) === void 0) {
17699
+ setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
17700
+ }
17701
+ }
17702
+ return out;
17703
+ }
17343
17704
  function clientInfoHasPlaintext(info) {
17344
17705
  return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
17345
17706
  }
@@ -17380,6 +17741,44 @@ function coveredLocations(location, byLocation) {
17380
17741
  ([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
17381
17742
  ).map(([recorded, keys]) => ({ location: recorded, keys }));
17382
17743
  }
17744
+ var emptyKeys = () => ({
17745
+ servers: /* @__PURE__ */ new Set(),
17746
+ idpSessions: /* @__PURE__ */ new Set(),
17747
+ owners: /* @__PURE__ */ new Map()
17748
+ });
17749
+ function candidateFields(location, byLocation) {
17750
+ const fields = /* @__PURE__ */ new Map();
17751
+ for (const { keys } of coveredLocations(location, byLocation)) {
17752
+ for (const [url, owned] of keys.owners) {
17753
+ const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
17754
+ for (const field of owned.keys()) kept.add(field);
17755
+ fields.set(url, kept);
17756
+ }
17757
+ }
17758
+ return fields;
17759
+ }
17760
+ function addMissingOwners(target, source) {
17761
+ for (const [url, fields] of source) {
17762
+ const kept = target.get(url) ?? /* @__PURE__ */ new Map();
17763
+ for (const [field, clientId] of fields) {
17764
+ if (!kept.has(field)) kept.set(field, clientId);
17765
+ }
17766
+ if (kept.size > 0) target.set(url, kept);
17767
+ }
17768
+ }
17769
+ function mergeOwners(target, source) {
17770
+ let changed = false;
17771
+ for (const [url, fields] of source) {
17772
+ const kept = target.get(url) ?? /* @__PURE__ */ new Map();
17773
+ for (const [field, clientId] of fields) {
17774
+ if (kept.get(field) === clientId) continue;
17775
+ kept.set(field, clientId);
17776
+ changed = true;
17777
+ }
17778
+ if (kept.size > 0) target.set(url, kept);
17779
+ }
17780
+ return changed;
17781
+ }
17383
17782
  var warnedLedgerFailures = /* @__PURE__ */ new Set();
17384
17783
  function warnLedgerFailure(what, error) {
17385
17784
  const reason = error instanceof Error ? error.message : String(error);
@@ -17393,6 +17792,19 @@ function warnLedgerFailure(what, error) {
17393
17792
  function isRecord5(value) {
17394
17793
  return typeof value === "object" && value !== null && !Array.isArray(value);
17395
17794
  }
17795
+ function parseOwners(value) {
17796
+ const owners = /* @__PURE__ */ new Map();
17797
+ if (!isRecord5(value)) return owners;
17798
+ for (const [url, fields] of Object.entries(value)) {
17799
+ if (!isRecord5(fields)) continue;
17800
+ const byField = /* @__PURE__ */ new Map();
17801
+ for (const [field, clientId] of Object.entries(fields)) {
17802
+ if (typeof clientId === "string") byField.set(field, clientId);
17803
+ }
17804
+ if (byField.size > 0) owners.set(url, byField);
17805
+ }
17806
+ return owners;
17807
+ }
17396
17808
  function stringsOf(value) {
17397
17809
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17398
17810
  }
@@ -17414,7 +17826,9 @@ function parseLedger(raw) {
17414
17826
  if (!isRecord5(keys)) continue;
17415
17827
  byLocation.set(location, {
17416
17828
  servers: new Set(stringsOf(keys.servers)),
17417
- idpSessions: new Set(stringsOf(keys.idpSessions))
17829
+ idpSessions: new Set(stringsOf(keys.idpSessions)),
17830
+ owners: parseOwners(keys.owners),
17831
+ ...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
17418
17832
  });
17419
17833
  }
17420
17834
  if (byLocation.size > 0) ledger.set(namespace, byLocation);
@@ -17428,7 +17842,19 @@ function serializeLedger(ledger) {
17428
17842
  Object.fromEntries(
17429
17843
  [...byLocation].map(([location, keys]) => [
17430
17844
  location,
17431
- { servers: [...keys.servers], idpSessions: [...keys.idpSessions] }
17845
+ {
17846
+ servers: [...keys.servers],
17847
+ idpSessions: [...keys.idpSessions],
17848
+ ...keys.owners.size > 0 && {
17849
+ owners: Object.fromEntries(
17850
+ [...keys.owners].map(([url, fields]) => [
17851
+ url,
17852
+ Object.fromEntries(fields)
17853
+ ])
17854
+ )
17855
+ },
17856
+ ...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
17857
+ }
17432
17858
  ])
17433
17859
  )
17434
17860
  ])
@@ -17440,7 +17866,7 @@ async function writeLedger(stateFilePath, ledger) {
17440
17866
  if (ledger.size === 0) await deleteStoreFile(path7);
17441
17867
  else await writeStoreFile(path7, serializeLedger(ledger));
17442
17868
  }
17443
- async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
17869
+ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
17444
17870
  try {
17445
17871
  const ledger = parseLedger(
17446
17872
  await readStoreFile(namespaceLedgerPath(stateFilePath))
@@ -17454,7 +17880,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
17454
17880
  let keys = byLocation.get(location);
17455
17881
  let changed = false;
17456
17882
  if (!keys) {
17457
- keys = { servers: /* @__PURE__ */ new Set(), idpSessions: /* @__PURE__ */ new Set() };
17883
+ keys = emptyKeys();
17458
17884
  byLocation.set(location, keys);
17459
17885
  changed = true;
17460
17886
  }
@@ -17468,11 +17894,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
17468
17894
  keys.idpSessions.add(issuer);
17469
17895
  changed = true;
17470
17896
  }
17897
+ if (mergeOwners(keys.owners, owners)) changed = true;
17471
17898
  if (changed) await writeLedger(stateFilePath, ledger);
17472
17899
  } catch (error) {
17473
17900
  warnLedgerFailure("update", error);
17474
17901
  }
17475
17902
  }
17903
+ async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
17904
+ const ledger = parseLedger(
17905
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
17906
+ );
17907
+ const location = await secretStoreLocation(secretStore);
17908
+ let changed = false;
17909
+ for (const [namespace, byLocation] of ledger) {
17910
+ if (namespace === into) continue;
17911
+ for (const [recorded, keys] of byLocation) {
17912
+ if (recorded === location || keys.owners.size === 0) continue;
17913
+ if (keys.owedTo === into) continue;
17914
+ keys.owedTo = into;
17915
+ changed = true;
17916
+ }
17917
+ }
17918
+ if (changed) await writeLedger(stateFilePath, ledger);
17919
+ }
17920
+ async function pendingCarries(stateFilePath, secretStore, current) {
17921
+ const ledger = parseLedger(
17922
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
17923
+ );
17924
+ const location = await secretStoreLocation(secretStore);
17925
+ const carries = [];
17926
+ for (const [namespace, byLocation] of ledger) {
17927
+ const owed = coveredLocations(location, byLocation).some(
17928
+ ({ keys }) => keys.owedTo === current
17929
+ );
17930
+ if (owed) {
17931
+ carries.push({
17932
+ namespace,
17933
+ fields: candidateFields(location, byLocation)
17934
+ });
17935
+ }
17936
+ }
17937
+ return carries.reverse();
17938
+ }
17939
+ async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
17940
+ let carried = true;
17941
+ for (const [url, fields] of recorded) {
17942
+ for (const field of fields) {
17943
+ const clientId = now.get(url)?.get(field);
17944
+ if (clientId === void 0) continue;
17945
+ try {
17946
+ const from = oauthSecretServerId(url, namespace);
17947
+ const to = oauthSecretServerId(url, current);
17948
+ const stampField = clientCredentialStampField(field);
17949
+ const value = await secretStoreGetStrict(secretStore, from, field);
17950
+ if (value === null) continue;
17951
+ const stamp = await secretStoreGetStrict(secretStore, from, stampField);
17952
+ if (stamp === null || stampedOwner(stamp, value) !== clientId) {
17953
+ continue;
17954
+ }
17955
+ if (await secretStoreGetStrict(secretStore, to, field) !== null) {
17956
+ continue;
17957
+ }
17958
+ let byLocation = ledger.get(current);
17959
+ if (!byLocation) {
17960
+ byLocation = /* @__PURE__ */ new Map();
17961
+ ledger.set(current, byLocation);
17962
+ }
17963
+ const target = byLocation.get(location) ?? emptyKeys();
17964
+ byLocation.set(location, target);
17965
+ const isNew = !target.servers.has(url);
17966
+ target.servers.add(url);
17967
+ const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
17968
+ if (mergeOwners(target.owners, owner) || isNew) {
17969
+ await writeLedger(stateFilePath, ledger);
17970
+ }
17971
+ await secretStore.set(to, stampField, stamp);
17972
+ await secretStore.set(to, field, value);
17973
+ } catch (error) {
17974
+ carried = false;
17975
+ warnLedgerFailure(
17976
+ "carry over a superseded namespace recorded in",
17977
+ error
17978
+ );
17979
+ }
17980
+ }
17981
+ }
17982
+ return carried;
17983
+ }
17476
17984
  async function recordedNamespaces(stateFilePath) {
17477
17985
  try {
17478
17986
  const ledger = parseLedger(
@@ -17484,7 +17992,7 @@ async function recordedNamespaces(stateFilePath) {
17484
17992
  return [];
17485
17993
  }
17486
17994
  }
17487
- async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
17995
+ async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
17488
17996
  let ledger;
17489
17997
  let location;
17490
17998
  try {
@@ -17497,8 +18005,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
17497
18005
  return;
17498
18006
  }
17499
18007
  let changed = false;
17500
- for (const [namespace, byLocation] of ledger) {
18008
+ for (const [namespace, byLocation] of [...ledger].reverse()) {
17501
18009
  if (namespace === current) continue;
18010
+ if (current !== void 0 && coveredLocations(location, byLocation).some(
18011
+ ({ keys }) => keys.owedTo === current
18012
+ ) && !await carryOwed(
18013
+ stateFilePath,
18014
+ ledger,
18015
+ location,
18016
+ secretStore,
18017
+ namespace,
18018
+ current,
18019
+ candidateFields(location, byLocation),
18020
+ owners
18021
+ )) {
18022
+ break;
18023
+ }
17502
18024
  for (const { location: recorded, keys } of coveredLocations(
17503
18025
  location,
17504
18026
  byLocation
@@ -17523,12 +18045,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
17523
18045
  if (!purged || recorded !== location) continue;
17524
18046
  byLocation.delete(recorded);
17525
18047
  if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
17526
- const keyring = byLocation.get(KEYRING_LOCATION) ?? {
17527
- servers: /* @__PURE__ */ new Set(),
17528
- idpSessions: /* @__PURE__ */ new Set()
17529
- };
18048
+ const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
18049
+ addMissingOwners(keyring.owners, keys.owners);
17530
18050
  for (const url of keys.servers) keyring.servers.add(url);
17531
18051
  for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
18052
+ keyring.owedTo ??= keys.owedTo;
17532
18053
  byLocation.set(KEYRING_LOCATION, keyring);
17533
18054
  }
17534
18055
  changed = true;
@@ -17639,7 +18160,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17639
18160
  const existing = parseSecretsNamespace(raw);
17640
18161
  if (existing !== void 0) {
17641
18162
  if (locked) {
17642
- await purgeSupersededNamespaces(filePath, secretStore, existing);
18163
+ await purgeSupersededNamespaces(
18164
+ filePath,
18165
+ secretStore,
18166
+ existing,
18167
+ snapshotClientOwners(parseOAuthPersistBlob(raw))
18168
+ );
17643
18169
  }
17644
18170
  return existing;
17645
18171
  }
@@ -17659,25 +18185,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17659
18185
  `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).`
17660
18186
  );
17661
18187
  }
18188
+ const values = await readEntrySecrets(
18189
+ snapshot,
18190
+ secretStore,
18191
+ void 0,
18192
+ await recordedNamespaces(filePath)
18193
+ );
18194
+ const owners = snapshotClientOwners(snapshot);
17662
18195
  await recordNamespaceKeys(
17663
18196
  filePath,
17664
18197
  secretStore,
17665
18198
  namespace,
17666
18199
  Object.keys(snapshot.servers),
17667
- Object.keys(snapshot.idpSessions)
18200
+ Object.keys(snapshot.idpSessions),
18201
+ writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
17668
18202
  );
18203
+ await markUncarriedNamespaces(filePath, secretStore, namespace);
17669
18204
  const touched = [];
17670
18205
  try {
17671
- const values = await readEntrySecrets(
17672
- snapshot,
17673
- secretStore,
17674
- void 0,
17675
- await recordedNamespaces(filePath)
17676
- );
17677
18206
  const copies = [
18207
+ // Restamped: a value read from the legacy ids predates stamps, and a
18208
+ // fallback's stamp may belong to a value read from elsewhere (#2734).
17678
18209
  ...[...values.servers].map(([url, secrets]) => ({
17679
18210
  scopedId: oauthSecretServerId(url, namespace),
17680
- secrets
18211
+ secrets: restampClientCredentials(
18212
+ getOwnEntry(snapshot.servers, url) ?? {},
18213
+ secrets
18214
+ )
17681
18215
  })),
17682
18216
  ...[...values.idpSessions].map(([issuer, secrets]) => ({
17683
18217
  scopedId: oauthIdpSecretServerId(issuer, namespace),
@@ -17695,7 +18229,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
17695
18229
  await restoreSecretFields(secretStore, touched, warnRestoreFailure);
17696
18230
  throw error;
17697
18231
  }
17698
- await purgeSupersededNamespaces(filePath, secretStore, namespace);
18232
+ await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
17699
18233
  const legacyIds = [
17700
18234
  ...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
17701
18235
  ...Object.keys(snapshot.idpSessions).map(
@@ -17872,7 +18406,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
17872
18406
  secretStore,
17873
18407
  namespace,
17874
18408
  Object.keys(merged.servers),
17875
- Object.keys(merged.idpSessions)
18409
+ Object.keys(merged.idpSessions),
18410
+ // Only the entries this save writes: another entry's secret in
18411
+ // this store was written for whatever client it was written for,
18412
+ // and relabeling it from the file would misattribute it (#2672).
18413
+ writtenClientOwners(
18414
+ {
18415
+ servers: Object.fromEntries(
18416
+ (effective.servers ?? []).flatMap((url) => {
18417
+ const entry = getOwnEntry(merged.servers, url);
18418
+ return entry === void 0 ? [] : [[url, entry]];
18419
+ })
18420
+ ),
18421
+ idpSessions: {}
18422
+ },
18423
+ splitSecretsOf
18424
+ )
17876
18425
  );
17877
18426
  for (const url of effective.servers ?? []) {
17878
18427
  const serverId = oauthSecretServerId(url, namespace);
@@ -18060,7 +18609,29 @@ function fillMissingSecrets(target, source) {
18060
18609
  if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
18061
18610
  }
18062
18611
  }
18063
- async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
18612
+ function writtenClientOwners(snapshot, secretsOf) {
18613
+ const owners = /* @__PURE__ */ new Map();
18614
+ for (const [url, state] of Object.entries(snapshot.servers)) {
18615
+ const secrets = secretsOf(url, state);
18616
+ const fields = new Map(
18617
+ [...clientCredentialOwners(state)].filter(
18618
+ ([field]) => Object.hasOwn(secrets, field)
18619
+ )
18620
+ );
18621
+ if (fields.size > 0) owners.set(url, fields);
18622
+ }
18623
+ return owners;
18624
+ }
18625
+ var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
18626
+ function snapshotClientOwners(snapshot) {
18627
+ const owners = /* @__PURE__ */ new Map();
18628
+ for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
18629
+ const fields = clientCredentialOwners(state);
18630
+ if (fields.size > 0) owners.set(url, fields);
18631
+ }
18632
+ return owners;
18633
+ }
18634
+ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
18064
18635
  const servers = /* @__PURE__ */ new Map();
18065
18636
  const idpSessions = /* @__PURE__ */ new Map();
18066
18637
  for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
@@ -18081,14 +18652,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
18081
18652
  );
18082
18653
  }
18083
18654
  }
18655
+ const owners = snapshotClientOwners(snapshot);
18656
+ for (const { namespace: from, fields: recorded } of carries) {
18657
+ const requests = [];
18658
+ for (const [url, fields] of recorded) {
18659
+ const target = servers.get(url);
18660
+ if (!target) continue;
18661
+ const wanted = [...fields].filter(
18662
+ (field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
18663
+ );
18664
+ if (wanted.length > 0) {
18665
+ requests.push({
18666
+ serverId: oauthSecretServerId(url, from),
18667
+ fields: wanted.flatMap((field) => [
18668
+ field,
18669
+ clientCredentialStampField(field)
18670
+ ])
18671
+ });
18672
+ }
18673
+ }
18674
+ if (requests.length === 0) continue;
18675
+ const values = await secretStoreGetManyStrict(secretStore, requests);
18676
+ for (const [url, target] of servers) {
18677
+ const found = values[oauthSecretServerId(url, from)] ?? {};
18678
+ for (const [field, clientId] of owners.get(url) ?? []) {
18679
+ const value = Object.hasOwn(found, field) ? found[field] : void 0;
18680
+ const stampField = clientCredentialStampField(field);
18681
+ if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
18682
+ continue;
18683
+ }
18684
+ setOwnEntry(target, field, value);
18685
+ setOwnEntry(target, stampField, found[stampField]);
18686
+ }
18687
+ }
18688
+ }
18084
18689
  return { servers, idpSessions };
18085
18690
  }
18086
- async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
18691
+ async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
18087
18692
  const values = await readEntrySecrets(
18088
18693
  snapshot,
18089
18694
  secretStore,
18090
18695
  namespace,
18091
- fallbacks
18696
+ fallbacks,
18697
+ carries
18092
18698
  );
18093
18699
  return {
18094
18700
  servers: Object.fromEntries(
@@ -18116,7 +18722,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
18116
18722
  secretStore,
18117
18723
  namespace,
18118
18724
  Object.keys(fresh.servers),
18119
- Object.keys(fresh.idpSessions)
18725
+ Object.keys(fresh.idpSessions),
18726
+ writtenClientOwners(fresh, splitSecretsOf)
18120
18727
  );
18121
18728
  }
18122
18729
  const migrateEntrySecrets = async (serverId, secrets) => {
@@ -18169,7 +18776,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
18169
18776
  }
18170
18777
  const namespace = parseSecretsNamespace(raw);
18171
18778
  const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
18172
- return joinSnapshot(snapshot, secretStore, namespace, fallbacks);
18779
+ const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
18780
+ return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
18173
18781
  });
18174
18782
  }
18175
18783
  async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
@@ -18479,7 +19087,11 @@ async function runRunnerInteractiveOAuth(options) {
18479
19087
  path: options.callbackListen.pathname,
18480
19088
  onCallback: async (params) => {
18481
19089
  try {
18482
- await options.client.completeOAuthFlow(params.code, params.iss);
19090
+ await options.client.completeOAuthFlow(
19091
+ params.code,
19092
+ params.iss,
19093
+ params.state
19094
+ );
18483
19095
  flowResolve();
18484
19096
  } catch (err) {
18485
19097
  flowReject(toRunnerOAuthError(err));
@@ -18531,7 +19143,10 @@ async function runRunnerInteractiveOAuth(options) {
18531
19143
  }
18532
19144
  if (options.authChallenge) {
18533
19145
  const satisfied = await racingSignals(
18534
- options.client.checkAuthChallengeSatisfied(options.authChallenge)
19146
+ // The token exchange just completed, so only scope is in question.
19147
+ options.client.checkAuthChallengeSatisfied(options.authChallenge, {
19148
+ scopeOnly: true
19149
+ })
18535
19150
  );
18536
19151
  if (!satisfied) {
18537
19152
  return {
@@ -18584,14 +19199,25 @@ function getTuiLogger() {
18584
19199
  // ../../core/node/openUrl.ts
18585
19200
  import open from "open";
18586
19201
  var OPEN_URL_TIMEOUT_MS = 5e3;
18587
- function launched(child) {
18588
- return new Promise((resolve9, reject) => {
18589
- child.on("error", reject);
18590
- child.once("spawn", () => resolve9());
19202
+ var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
19203
+ function toOpenableHref(href) {
19204
+ let parsed;
19205
+ try {
19206
+ parsed = new URL(href);
19207
+ } catch {
19208
+ throw new Error("refusing to open an invalid URL");
19209
+ }
19210
+ if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
19211
+ throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
19212
+ }
19213
+ return parsed.href;
19214
+ }
19215
+ function absorbLateErrors(child) {
19216
+ child.on("error", () => {
18591
19217
  });
18592
19218
  }
18593
19219
  async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
18594
- const href = typeof url === "string" ? url : url.href;
19220
+ const href = toOpenableHref(typeof url === "string" ? url : url.href);
18595
19221
  let timer;
18596
19222
  const timeout = new Promise((_, reject) => {
18597
19223
  timer = setTimeout(
@@ -18600,8 +19226,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
18600
19226
  );
18601
19227
  });
18602
19228
  try {
18603
- const opened = Promise.resolve().then(() => open(href)).then(launched);
18604
- await Promise.race([opened, timeout]);
19229
+ await Promise.race([open(href).then(absorbLateErrors), timeout]);
18605
19230
  } finally {
18606
19231
  clearTimeout(timer);
18607
19232
  }
@@ -25352,7 +25977,8 @@ var STREAM_STATUS_TEXT = {
25352
25977
  "never-acknowledged": {
25353
25978
  text: "closed without acknowledging (server answered listen with a result)",
25354
25979
  color: "red"
25355
- }
25980
+ },
25981
+ refused: { text: "refused by the server", color: "red" }
25356
25982
  };
25357
25983
  function SubscriptionsTab({
25358
25984
  resources,
@@ -25441,6 +26067,7 @@ function SubscriptionsTab({
25441
26067
  scrollViewRef.current?.scrollTo(0);
25442
26068
  }, [selectedIndex]);
25443
26069
  const streamText = STREAM_STATUS_TEXT[streamState.status];
26070
+ const streamRefusal = streamState.status === "refused" ? streamState.refusal : void 0;
25444
26071
  const notHonored = !!selected && selected.subscribed && streamState.active && streamState.status === "acknowledged" && !streamState.honoredUris.includes(selected.resource.uri);
25445
26072
  return /* @__PURE__ */ jsxs21(Box33, { flexDirection: "row", width, height, children: [
25446
26073
  /* @__PURE__ */ jsxs21(
@@ -25527,11 +26154,12 @@ function SubscriptionsTab({
25527
26154
  ] }) }),
25528
26155
  notHonored && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "yellow", children: "Server did not honor this URI in its listen filter" }) })
25529
26156
  ] }),
25530
- streamState.active && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
26157
+ (streamState.active || streamRefusal) && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { children: [
25531
26158
  "Listen stream:",
25532
26159
  " ",
25533
26160
  /* @__PURE__ */ jsx23(Text30, { color: streamText.color, children: streamText.text })
25534
26161
  ] }) }),
26162
+ streamRefusal && /* @__PURE__ */ jsx23(Box33, { flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: subscriptionListenRefusalMessage(streamRefusal) }) }),
25535
26163
  error && /* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsx23(Text30, { color: "red", children: error }) }),
25536
26164
  /* @__PURE__ */ jsx23(Box33, { marginTop: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs21(Text30, { bold: true, children: [
25537
26165
  "Updates (",