@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.
@@ -3177,6 +3177,13 @@ var BaseOAuthClientProvider = class {
3177
3177
  requestRefreshToken;
3178
3178
  /** Server-configured scope, for the #2068 `offline_access` distinction. */
3179
3179
  configuredScope;
3180
+ /** Per-flow verifier store (#2726); absent → the shared storage slot. */
3181
+ flowStore;
3182
+ /**
3183
+ * The authId of the flow this provider is driving: set by {@link state} on the
3184
+ * redirect leg and by {@link resumeAuthorizationFlow} on the callback leg.
3185
+ */
3186
+ flowId;
3180
3187
  constructor(serverUrl, oauthConfig) {
3181
3188
  this.serverUrl = serverUrl;
3182
3189
  this.storage = oauthConfig.storage;
@@ -3186,6 +3193,7 @@ var BaseOAuthClientProvider = class {
3186
3193
  this.authorizationParams = oauthConfig.authorizationParams;
3187
3194
  this.requestRefreshToken = oauthConfig.requestRefreshToken ?? true;
3188
3195
  this.configuredScope = oauthConfig.configuredScope;
3196
+ this.flowStore = oauthConfig.flowStore;
3189
3197
  }
3190
3198
  /**
3191
3199
  * Load persisted scope into {@link cachedScope} before SDK `auth()` (which
@@ -3256,7 +3264,24 @@ var BaseOAuthClientProvider = class {
3256
3264
  return metadata;
3257
3265
  }
3258
3266
  state() {
3259
- return generateOAuthState();
3267
+ const state = generateOAuthState();
3268
+ this.flowId = state;
3269
+ return state;
3270
+ }
3271
+ /**
3272
+ * Bind this provider to the flow a callback's `state` names, so
3273
+ * {@link codeVerifier} reads that flow's verifier and no other (#2726). A
3274
+ * `state` that is not one of ours binds nothing — and, with a flow store, a
3275
+ * flow-less read then fails rather than reaching for the shared slot.
3276
+ */
3277
+ resumeAuthorizationFlow(state) {
3278
+ this.flowId = parseOAuthState(state)?.authId;
3279
+ }
3280
+ /** Drop the bound flow's verifier once its exchange has completed. */
3281
+ async clearAuthorizationFlow() {
3282
+ if (this.flowStore && this.flowId) {
3283
+ await this.flowStore.clearCodeVerifier(this.flowId);
3284
+ }
3260
3285
  }
3261
3286
  async clientInformation(ctx) {
3262
3287
  const preregistered = await this.storage.getClientInformation(
@@ -3401,9 +3426,26 @@ var BaseOAuthClientProvider = class {
3401
3426
  }
3402
3427
  }
3403
3428
  async saveCodeVerifier(codeVerifier) {
3429
+ if (this.flowStore && this.flowId) {
3430
+ await this.flowStore.saveCodeVerifier(
3431
+ this.flowId,
3432
+ this.serverUrl,
3433
+ codeVerifier
3434
+ );
3435
+ return;
3436
+ }
3404
3437
  await this.storage.saveCodeVerifier(this.serverUrl, codeVerifier);
3405
3438
  }
3406
3439
  async codeVerifier() {
3440
+ if (this.flowStore) {
3441
+ const verifier2 = this.flowId ? await this.flowStore.getCodeVerifier(this.flowId, this.serverUrl) : void 0;
3442
+ if (!verifier2) {
3443
+ throw new Error(
3444
+ "No code verifier saved for this authorization flow. It may have been started in another browser tab or already completed; please re-authenticate."
3445
+ );
3446
+ }
3447
+ return verifier2;
3448
+ }
3407
3449
  const verifier = await this.storage.getCodeVerifier(this.serverUrl);
3408
3450
  if (!verifier) {
3409
3451
  throw new Error("No code verifier saved for session");
@@ -3450,6 +3492,10 @@ var BaseOAuthClientProvider = class {
3450
3492
  await this.storage.clearTokens(this.serverUrl);
3451
3493
  return;
3452
3494
  case "verifier":
3495
+ if (this.flowStore) {
3496
+ await this.clearAuthorizationFlow();
3497
+ return;
3498
+ }
3453
3499
  await this.storage.clearCodeVerifier(this.serverUrl);
3454
3500
  return;
3455
3501
  case "discovery":
@@ -4190,7 +4236,7 @@ function serializeOAuthPersistBlob(snapshot) {
4190
4236
  import { resolve as resolve5 } from "path";
4191
4237
 
4192
4238
  // ../../core/auth/node/oauth-secrets.ts
4193
- import { randomUUID as randomUUID2 } from "crypto";
4239
+ import { createHmac, randomUUID as randomUUID2 } from "crypto";
4194
4240
  import { OAuthTokensSchema as OAuthTokensSchema3 } from "@modelcontextprotocol/core";
4195
4241
  var PERSIST_TOKENS_ENV = "MCP_INSPECTOR_PERSIST_TOKENS";
4196
4242
  var warnedPolicyValues = /* @__PURE__ */ new Set();
@@ -4223,6 +4269,29 @@ var LEGACY_REGISTRATION_TOKEN_FIELD = "registration-token";
4223
4269
  var PREREG_CLIENT_SECRET_FIELD = "prereg-client-secret";
4224
4270
  var PREREG_REGISTRATION_TOKEN_FIELD = "prereg-registration-token";
4225
4271
  var IDP_SESSION_FIELD = "idp-session";
4272
+ var clientCredentialStampField = (field) => `${field}#owner`;
4273
+ var credentialMac = (clientId, value) => createHmac("sha256", value).update(clientId).digest("hex");
4274
+ function clientCredentialStamp(clientId, value) {
4275
+ return JSON.stringify({
4276
+ client_id: clientId,
4277
+ mac: credentialMac(clientId, value)
4278
+ });
4279
+ }
4280
+ function stampedOwner(stamp, value) {
4281
+ if (stamp === void 0) return void 0;
4282
+ let parsed;
4283
+ try {
4284
+ parsed = JSON.parse(stamp);
4285
+ } catch {
4286
+ return void 0;
4287
+ }
4288
+ if (typeof parsed !== "object" || parsed === null) return void 0;
4289
+ const { client_id: clientId, mac } = parsed;
4290
+ if (typeof clientId !== "string" || mac !== credentialMac(clientId, value)) {
4291
+ return void 0;
4292
+ }
4293
+ return clientId;
4294
+ }
4226
4295
  function applyTokensPolicy(tokens, policy) {
4227
4296
  if (policy === "none") return void 0;
4228
4297
  if (policy === "access" && tokens.refresh_token !== void 0) {
@@ -4251,12 +4320,18 @@ function splitClientInformation(info, fields, secrets) {
4251
4320
  if (client_secret === void 0 && registration_access_token === void 0) {
4252
4321
  return info;
4253
4322
  }
4254
- if (client_secret !== void 0) {
4255
- secrets[fields.clientSecret] = client_secret;
4256
- }
4257
- if (registration_access_token !== void 0) {
4258
- secrets[fields.registrationToken] = registration_access_token;
4259
- }
4323
+ const put = (field, value) => {
4324
+ if (value === void 0) return;
4325
+ secrets[field] = value;
4326
+ if (fields.stamped && typeof info.client_id === "string") {
4327
+ secrets[clientCredentialStampField(field)] = clientCredentialStamp(
4328
+ info.client_id,
4329
+ value
4330
+ );
4331
+ }
4332
+ };
4333
+ put(fields.clientSecret, client_secret);
4334
+ put(fields.registrationToken, registration_access_token);
4260
4335
  return publicInfo;
4261
4336
  }
4262
4337
  function joinClientInformation(info, fields, secrets) {
@@ -4275,15 +4350,18 @@ function joinClientInformation(info, fields, secrets) {
4275
4350
  }
4276
4351
  var issuerClientInfoFields = (issuer) => ({
4277
4352
  clientSecret: issuerClientSecretField(issuer),
4278
- registrationToken: issuerRegistrationTokenField(issuer)
4353
+ registrationToken: issuerRegistrationTokenField(issuer),
4354
+ stamped: true
4279
4355
  });
4280
4356
  var LEGACY_CLIENT_INFO_FIELDS = {
4281
4357
  clientSecret: LEGACY_CLIENT_SECRET_FIELD,
4282
- registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD
4358
+ registrationToken: LEGACY_REGISTRATION_TOKEN_FIELD,
4359
+ stamped: true
4283
4360
  };
4284
4361
  var PREREG_CLIENT_INFO_FIELDS = {
4285
4362
  clientSecret: PREREG_CLIENT_SECRET_FIELD,
4286
- registrationToken: PREREG_REGISTRATION_TOKEN_FIELD
4363
+ registrationToken: PREREG_REGISTRATION_TOKEN_FIELD,
4364
+ stamped: false
4287
4365
  };
4288
4366
  function splitServerOAuthState(state, policy) {
4289
4367
  const secrets = {};
@@ -4433,20 +4511,54 @@ function joinIdpSession(residue, secrets) {
4433
4511
  return stored ? { ...residue, ...stored } : { ...residue };
4434
4512
  }
4435
4513
  function serverSecretFields(state) {
4514
+ const stamped = (field) => [
4515
+ field,
4516
+ clientCredentialStampField(field)
4517
+ ];
4436
4518
  const fields = /* @__PURE__ */ new Set([
4437
4519
  LEGACY_TOKENS_FIELD,
4438
- LEGACY_CLIENT_SECRET_FIELD,
4439
- LEGACY_REGISTRATION_TOKEN_FIELD,
4520
+ ...stamped(LEGACY_CLIENT_SECRET_FIELD),
4521
+ ...stamped(LEGACY_REGISTRATION_TOKEN_FIELD),
4440
4522
  PREREG_CLIENT_SECRET_FIELD,
4441
4523
  PREREG_REGISTRATION_TOKEN_FIELD
4442
4524
  ]);
4443
4525
  for (const issuer of Object.keys(state?.byIssuer ?? {})) {
4444
4526
  fields.add(issuerTokensField(issuer));
4445
- fields.add(issuerClientSecretField(issuer));
4446
- fields.add(issuerRegistrationTokenField(issuer));
4527
+ for (const field of stamped(issuerClientSecretField(issuer))) {
4528
+ fields.add(field);
4529
+ }
4530
+ for (const field of stamped(issuerRegistrationTokenField(issuer))) {
4531
+ fields.add(field);
4532
+ }
4447
4533
  }
4448
4534
  return [...fields];
4449
4535
  }
4536
+ function clientCredentialOwners(state) {
4537
+ const owners = /* @__PURE__ */ new Map();
4538
+ const add = (info, fields) => {
4539
+ if (typeof info?.client_id !== "string") return;
4540
+ owners.set(fields.clientSecret, info.client_id);
4541
+ owners.set(fields.registrationToken, info.client_id);
4542
+ };
4543
+ add(state.clientInformation, LEGACY_CLIENT_INFO_FIELDS);
4544
+ for (const [issuer, slot] of Object.entries(state.byIssuer ?? {})) {
4545
+ add(slot.clientInformation, issuerClientInfoFields(issuer));
4546
+ }
4547
+ return owners;
4548
+ }
4549
+ function restampClientCredentials(state, secrets) {
4550
+ const out = { ...secrets };
4551
+ for (const [field, clientId] of clientCredentialOwners(state)) {
4552
+ const stampField = clientCredentialStampField(field);
4553
+ const value = Object.hasOwn(out, field) ? out[field] : void 0;
4554
+ if (value === void 0) {
4555
+ delete out[stampField];
4556
+ } else if (stampedOwner(out[stampField], value) === void 0) {
4557
+ setOwnEntry(out, stampField, clientCredentialStamp(clientId, value));
4558
+ }
4559
+ }
4560
+ return out;
4561
+ }
4450
4562
  function clientInfoHasPlaintext(info) {
4451
4563
  return info !== void 0 && (info.client_secret !== void 0 || info.registration_access_token !== void 0);
4452
4564
  }
@@ -4487,6 +4599,44 @@ function coveredLocations(location, byLocation) {
4487
4599
  ([recorded]) => recorded === location || location === KEYRING_LOCATION && recorded.startsWith(FILE_LOCATION_PREFIX)
4488
4600
  ).map(([recorded, keys]) => ({ location: recorded, keys }));
4489
4601
  }
4602
+ var emptyKeys = () => ({
4603
+ servers: /* @__PURE__ */ new Set(),
4604
+ idpSessions: /* @__PURE__ */ new Set(),
4605
+ owners: /* @__PURE__ */ new Map()
4606
+ });
4607
+ function candidateFields(location, byLocation) {
4608
+ const fields = /* @__PURE__ */ new Map();
4609
+ for (const { keys } of coveredLocations(location, byLocation)) {
4610
+ for (const [url, owned] of keys.owners) {
4611
+ const kept = fields.get(url) ?? /* @__PURE__ */ new Set();
4612
+ for (const field of owned.keys()) kept.add(field);
4613
+ fields.set(url, kept);
4614
+ }
4615
+ }
4616
+ return fields;
4617
+ }
4618
+ function addMissingOwners(target, source) {
4619
+ for (const [url, fields] of source) {
4620
+ const kept = target.get(url) ?? /* @__PURE__ */ new Map();
4621
+ for (const [field, clientId] of fields) {
4622
+ if (!kept.has(field)) kept.set(field, clientId);
4623
+ }
4624
+ if (kept.size > 0) target.set(url, kept);
4625
+ }
4626
+ }
4627
+ function mergeOwners(target, source) {
4628
+ let changed = false;
4629
+ for (const [url, fields] of source) {
4630
+ const kept = target.get(url) ?? /* @__PURE__ */ new Map();
4631
+ for (const [field, clientId] of fields) {
4632
+ if (kept.get(field) === clientId) continue;
4633
+ kept.set(field, clientId);
4634
+ changed = true;
4635
+ }
4636
+ if (kept.size > 0) target.set(url, kept);
4637
+ }
4638
+ return changed;
4639
+ }
4490
4640
  var warnedLedgerFailures = /* @__PURE__ */ new Set();
4491
4641
  function warnLedgerFailure(what, error) {
4492
4642
  const reason = error instanceof Error ? error.message : String(error);
@@ -4500,6 +4650,19 @@ function warnLedgerFailure(what, error) {
4500
4650
  function isRecord2(value) {
4501
4651
  return typeof value === "object" && value !== null && !Array.isArray(value);
4502
4652
  }
4653
+ function parseOwners(value) {
4654
+ const owners = /* @__PURE__ */ new Map();
4655
+ if (!isRecord2(value)) return owners;
4656
+ for (const [url, fields] of Object.entries(value)) {
4657
+ if (!isRecord2(fields)) continue;
4658
+ const byField = /* @__PURE__ */ new Map();
4659
+ for (const [field, clientId] of Object.entries(fields)) {
4660
+ if (typeof clientId === "string") byField.set(field, clientId);
4661
+ }
4662
+ if (byField.size > 0) owners.set(url, byField);
4663
+ }
4664
+ return owners;
4665
+ }
4503
4666
  function stringsOf(value) {
4504
4667
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
4505
4668
  }
@@ -4521,7 +4684,9 @@ function parseLedger(raw) {
4521
4684
  if (!isRecord2(keys)) continue;
4522
4685
  byLocation.set(location, {
4523
4686
  servers: new Set(stringsOf(keys.servers)),
4524
- idpSessions: new Set(stringsOf(keys.idpSessions))
4687
+ idpSessions: new Set(stringsOf(keys.idpSessions)),
4688
+ owners: parseOwners(keys.owners),
4689
+ ...isValidSecretsNamespace(keys.owedTo) && { owedTo: keys.owedTo }
4525
4690
  });
4526
4691
  }
4527
4692
  if (byLocation.size > 0) ledger.set(namespace, byLocation);
@@ -4535,7 +4700,19 @@ function serializeLedger(ledger) {
4535
4700
  Object.fromEntries(
4536
4701
  [...byLocation].map(([location, keys]) => [
4537
4702
  location,
4538
- { servers: [...keys.servers], idpSessions: [...keys.idpSessions] }
4703
+ {
4704
+ servers: [...keys.servers],
4705
+ idpSessions: [...keys.idpSessions],
4706
+ ...keys.owners.size > 0 && {
4707
+ owners: Object.fromEntries(
4708
+ [...keys.owners].map(([url, fields]) => [
4709
+ url,
4710
+ Object.fromEntries(fields)
4711
+ ])
4712
+ )
4713
+ },
4714
+ ...keys.owedTo !== void 0 && { owedTo: keys.owedTo }
4715
+ }
4539
4716
  ])
4540
4717
  )
4541
4718
  ])
@@ -4547,7 +4724,7 @@ async function writeLedger(stateFilePath, ledger) {
4547
4724
  if (ledger.size === 0) await deleteStoreFile(path7);
4548
4725
  else await writeStoreFile(path7, serializeLedger(ledger));
4549
4726
  }
4550
- async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions) {
4727
+ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, servers, idpSessions, owners = /* @__PURE__ */ new Map()) {
4551
4728
  try {
4552
4729
  const ledger = parseLedger(
4553
4730
  await readStoreFile(namespaceLedgerPath(stateFilePath))
@@ -4561,7 +4738,7 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
4561
4738
  let keys = byLocation.get(location);
4562
4739
  let changed = false;
4563
4740
  if (!keys) {
4564
- keys = { servers: /* @__PURE__ */ new Set(), idpSessions: /* @__PURE__ */ new Set() };
4741
+ keys = emptyKeys();
4565
4742
  byLocation.set(location, keys);
4566
4743
  changed = true;
4567
4744
  }
@@ -4575,11 +4752,93 @@ async function recordNamespaceKeys(stateFilePath, secretStore, namespace, server
4575
4752
  keys.idpSessions.add(issuer);
4576
4753
  changed = true;
4577
4754
  }
4755
+ if (mergeOwners(keys.owners, owners)) changed = true;
4578
4756
  if (changed) await writeLedger(stateFilePath, ledger);
4579
4757
  } catch (error) {
4580
4758
  warnLedgerFailure("update", error);
4581
4759
  }
4582
4760
  }
4761
+ async function markUncarriedNamespaces(stateFilePath, secretStore, into) {
4762
+ const ledger = parseLedger(
4763
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
4764
+ );
4765
+ const location = await secretStoreLocation(secretStore);
4766
+ let changed = false;
4767
+ for (const [namespace, byLocation] of ledger) {
4768
+ if (namespace === into) continue;
4769
+ for (const [recorded, keys] of byLocation) {
4770
+ if (recorded === location || keys.owners.size === 0) continue;
4771
+ if (keys.owedTo === into) continue;
4772
+ keys.owedTo = into;
4773
+ changed = true;
4774
+ }
4775
+ }
4776
+ if (changed) await writeLedger(stateFilePath, ledger);
4777
+ }
4778
+ async function pendingCarries(stateFilePath, secretStore, current) {
4779
+ const ledger = parseLedger(
4780
+ await readStoreFile(namespaceLedgerPath(stateFilePath))
4781
+ );
4782
+ const location = await secretStoreLocation(secretStore);
4783
+ const carries = [];
4784
+ for (const [namespace, byLocation] of ledger) {
4785
+ const owed = coveredLocations(location, byLocation).some(
4786
+ ({ keys }) => keys.owedTo === current
4787
+ );
4788
+ if (owed) {
4789
+ carries.push({
4790
+ namespace,
4791
+ fields: candidateFields(location, byLocation)
4792
+ });
4793
+ }
4794
+ }
4795
+ return carries.reverse();
4796
+ }
4797
+ async function carryOwed(stateFilePath, ledger, location, secretStore, namespace, current, recorded, now) {
4798
+ let carried = true;
4799
+ for (const [url, fields] of recorded) {
4800
+ for (const field of fields) {
4801
+ const clientId = now.get(url)?.get(field);
4802
+ if (clientId === void 0) continue;
4803
+ try {
4804
+ const from = oauthSecretServerId(url, namespace);
4805
+ const to = oauthSecretServerId(url, current);
4806
+ const stampField = clientCredentialStampField(field);
4807
+ const value = await secretStoreGetStrict(secretStore, from, field);
4808
+ if (value === null) continue;
4809
+ const stamp = await secretStoreGetStrict(secretStore, from, stampField);
4810
+ if (stamp === null || stampedOwner(stamp, value) !== clientId) {
4811
+ continue;
4812
+ }
4813
+ if (await secretStoreGetStrict(secretStore, to, field) !== null) {
4814
+ continue;
4815
+ }
4816
+ let byLocation = ledger.get(current);
4817
+ if (!byLocation) {
4818
+ byLocation = /* @__PURE__ */ new Map();
4819
+ ledger.set(current, byLocation);
4820
+ }
4821
+ const target = byLocation.get(location) ?? emptyKeys();
4822
+ byLocation.set(location, target);
4823
+ const isNew = !target.servers.has(url);
4824
+ target.servers.add(url);
4825
+ const owner = /* @__PURE__ */ new Map([[url, /* @__PURE__ */ new Map([[field, clientId]])]]);
4826
+ if (mergeOwners(target.owners, owner) || isNew) {
4827
+ await writeLedger(stateFilePath, ledger);
4828
+ }
4829
+ await secretStore.set(to, stampField, stamp);
4830
+ await secretStore.set(to, field, value);
4831
+ } catch (error) {
4832
+ carried = false;
4833
+ warnLedgerFailure(
4834
+ "carry over a superseded namespace recorded in",
4835
+ error
4836
+ );
4837
+ }
4838
+ }
4839
+ }
4840
+ return carried;
4841
+ }
4583
4842
  async function recordedNamespaces(stateFilePath) {
4584
4843
  try {
4585
4844
  const ledger = parseLedger(
@@ -4591,7 +4850,7 @@ async function recordedNamespaces(stateFilePath) {
4591
4850
  return [];
4592
4851
  }
4593
4852
  }
4594
- async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
4853
+ async function purgeSupersededNamespaces(stateFilePath, secretStore, current, owners = /* @__PURE__ */ new Map()) {
4595
4854
  let ledger;
4596
4855
  let location;
4597
4856
  try {
@@ -4604,8 +4863,22 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
4604
4863
  return;
4605
4864
  }
4606
4865
  let changed = false;
4607
- for (const [namespace, byLocation] of ledger) {
4866
+ for (const [namespace, byLocation] of [...ledger].reverse()) {
4608
4867
  if (namespace === current) continue;
4868
+ if (current !== void 0 && coveredLocations(location, byLocation).some(
4869
+ ({ keys }) => keys.owedTo === current
4870
+ ) && !await carryOwed(
4871
+ stateFilePath,
4872
+ ledger,
4873
+ location,
4874
+ secretStore,
4875
+ namespace,
4876
+ current,
4877
+ candidateFields(location, byLocation),
4878
+ owners
4879
+ )) {
4880
+ break;
4881
+ }
4609
4882
  for (const { location: recorded, keys } of coveredLocations(
4610
4883
  location,
4611
4884
  byLocation
@@ -4630,12 +4903,11 @@ async function purgeSupersededNamespaces(stateFilePath, secretStore, current) {
4630
4903
  if (!purged || recorded !== location) continue;
4631
4904
  byLocation.delete(recorded);
4632
4905
  if (recorded.startsWith(FILE_LOCATION_PREFIX)) {
4633
- const keyring = byLocation.get(KEYRING_LOCATION) ?? {
4634
- servers: /* @__PURE__ */ new Set(),
4635
- idpSessions: /* @__PURE__ */ new Set()
4636
- };
4906
+ const keyring = byLocation.get(KEYRING_LOCATION) ?? emptyKeys();
4907
+ addMissingOwners(keyring.owners, keys.owners);
4637
4908
  for (const url of keys.servers) keyring.servers.add(url);
4638
4909
  for (const issuer of keys.idpSessions) keyring.idpSessions.add(issuer);
4910
+ keyring.owedTo ??= keys.owedTo;
4639
4911
  byLocation.set(KEYRING_LOCATION, keyring);
4640
4912
  }
4641
4913
  changed = true;
@@ -4746,7 +5018,12 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4746
5018
  const existing = parseSecretsNamespace(raw);
4747
5019
  if (existing !== void 0) {
4748
5020
  if (locked) {
4749
- await purgeSupersededNamespaces(filePath, secretStore, existing);
5021
+ await purgeSupersededNamespaces(
5022
+ filePath,
5023
+ secretStore,
5024
+ existing,
5025
+ snapshotClientOwners(parseOAuthPersistBlob(raw))
5026
+ );
4750
5027
  }
4751
5028
  return existing;
4752
5029
  }
@@ -4766,25 +5043,33 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4766
5043
  `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).`
4767
5044
  );
4768
5045
  }
5046
+ const values = await readEntrySecrets(
5047
+ snapshot,
5048
+ secretStore,
5049
+ void 0,
5050
+ await recordedNamespaces(filePath)
5051
+ );
5052
+ const owners = snapshotClientOwners(snapshot);
4769
5053
  await recordNamespaceKeys(
4770
5054
  filePath,
4771
5055
  secretStore,
4772
5056
  namespace,
4773
5057
  Object.keys(snapshot.servers),
4774
- Object.keys(snapshot.idpSessions)
5058
+ Object.keys(snapshot.idpSessions),
5059
+ writtenClientOwners(snapshot, (url) => values.servers.get(url) ?? {})
4775
5060
  );
5061
+ await markUncarriedNamespaces(filePath, secretStore, namespace);
4776
5062
  const touched = [];
4777
5063
  try {
4778
- const values = await readEntrySecrets(
4779
- snapshot,
4780
- secretStore,
4781
- void 0,
4782
- await recordedNamespaces(filePath)
4783
- );
4784
5064
  const copies = [
5065
+ // Restamped: a value read from the legacy ids predates stamps, and a
5066
+ // fallback's stamp may belong to a value read from elsewhere (#2734).
4785
5067
  ...[...values.servers].map(([url, secrets]) => ({
4786
5068
  scopedId: oauthSecretServerId(url, namespace),
4787
- secrets
5069
+ secrets: restampClientCredentials(
5070
+ getOwnEntry(snapshot.servers, url) ?? {},
5071
+ secrets
5072
+ )
4788
5073
  })),
4789
5074
  ...[...values.idpSessions].map(([issuer, secrets]) => ({
4790
5075
  scopedId: oauthIdpSecretServerId(issuer, namespace),
@@ -4802,7 +5087,7 @@ async function adoptSecretsNamespace(filePath, secretStore, locked) {
4802
5087
  await restoreSecretFields(secretStore, touched, warnRestoreFailure);
4803
5088
  throw error;
4804
5089
  }
4805
- await purgeSupersededNamespaces(filePath, secretStore, namespace);
5090
+ await purgeSupersededNamespaces(filePath, secretStore, namespace, owners);
4806
5091
  const legacyIds = [
4807
5092
  ...Object.keys(snapshot.servers).map((url) => oauthSecretServerId(url)),
4808
5093
  ...Object.keys(snapshot.idpSessions).map(
@@ -4979,7 +5264,22 @@ async function writeOAuthSectionsFrom(filePath, resolveSnapshot, sections, secre
4979
5264
  secretStore,
4980
5265
  namespace,
4981
5266
  Object.keys(merged.servers),
4982
- Object.keys(merged.idpSessions)
5267
+ Object.keys(merged.idpSessions),
5268
+ // Only the entries this save writes: another entry's secret in
5269
+ // this store was written for whatever client it was written for,
5270
+ // and relabeling it from the file would misattribute it (#2672).
5271
+ writtenClientOwners(
5272
+ {
5273
+ servers: Object.fromEntries(
5274
+ (effective.servers ?? []).flatMap((url) => {
5275
+ const entry = getOwnEntry(merged.servers, url);
5276
+ return entry === void 0 ? [] : [[url, entry]];
5277
+ })
5278
+ ),
5279
+ idpSessions: {}
5280
+ },
5281
+ splitSecretsOf
5282
+ )
4983
5283
  );
4984
5284
  for (const url of effective.servers ?? []) {
4985
5285
  const serverId = oauthSecretServerId(url, namespace);
@@ -5167,7 +5467,29 @@ function fillMissingSecrets(target, source) {
5167
5467
  if (!Object.hasOwn(target, field)) setOwnEntry(target, field, value);
5168
5468
  }
5169
5469
  }
5170
- async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
5470
+ function writtenClientOwners(snapshot, secretsOf) {
5471
+ const owners = /* @__PURE__ */ new Map();
5472
+ for (const [url, state] of Object.entries(snapshot.servers)) {
5473
+ const secrets = secretsOf(url, state);
5474
+ const fields = new Map(
5475
+ [...clientCredentialOwners(state)].filter(
5476
+ ([field]) => Object.hasOwn(secrets, field)
5477
+ )
5478
+ );
5479
+ if (fields.size > 0) owners.set(url, fields);
5480
+ }
5481
+ return owners;
5482
+ }
5483
+ var splitSecretsOf = (_url, state) => splitServerOAuthState(state, "all").secrets;
5484
+ function snapshotClientOwners(snapshot) {
5485
+ const owners = /* @__PURE__ */ new Map();
5486
+ for (const [url, state] of Object.entries(snapshot?.servers ?? {})) {
5487
+ const fields = clientCredentialOwners(state);
5488
+ if (fields.size > 0) owners.set(url, fields);
5489
+ }
5490
+ return owners;
5491
+ }
5492
+ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks, carries = []) {
5171
5493
  const servers = /* @__PURE__ */ new Map();
5172
5494
  const idpSessions = /* @__PURE__ */ new Map();
5173
5495
  for (const url of Object.keys(snapshot.servers)) servers.set(url, {});
@@ -5188,14 +5510,49 @@ async function readEntrySecrets(snapshot, secretStore, namespace, fallbacks) {
5188
5510
  );
5189
5511
  }
5190
5512
  }
5513
+ const owners = snapshotClientOwners(snapshot);
5514
+ for (const { namespace: from, fields: recorded } of carries) {
5515
+ const requests = [];
5516
+ for (const [url, fields] of recorded) {
5517
+ const target = servers.get(url);
5518
+ if (!target) continue;
5519
+ const wanted = [...fields].filter(
5520
+ (field) => owners.get(url)?.has(field) === true && !Object.hasOwn(target, field)
5521
+ );
5522
+ if (wanted.length > 0) {
5523
+ requests.push({
5524
+ serverId: oauthSecretServerId(url, from),
5525
+ fields: wanted.flatMap((field) => [
5526
+ field,
5527
+ clientCredentialStampField(field)
5528
+ ])
5529
+ });
5530
+ }
5531
+ }
5532
+ if (requests.length === 0) continue;
5533
+ const values = await secretStoreGetManyStrict(secretStore, requests);
5534
+ for (const [url, target] of servers) {
5535
+ const found = values[oauthSecretServerId(url, from)] ?? {};
5536
+ for (const [field, clientId] of owners.get(url) ?? []) {
5537
+ const value = Object.hasOwn(found, field) ? found[field] : void 0;
5538
+ const stampField = clientCredentialStampField(field);
5539
+ if (value === void 0 || Object.hasOwn(target, field) || stampedOwner(found[stampField], value) !== clientId) {
5540
+ continue;
5541
+ }
5542
+ setOwnEntry(target, field, value);
5543
+ setOwnEntry(target, stampField, found[stampField]);
5544
+ }
5545
+ }
5546
+ }
5191
5547
  return { servers, idpSessions };
5192
5548
  }
5193
- async function joinSnapshot(snapshot, secretStore, namespace, fallbacks) {
5549
+ async function joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries = []) {
5194
5550
  const values = await readEntrySecrets(
5195
5551
  snapshot,
5196
5552
  secretStore,
5197
5553
  namespace,
5198
- fallbacks
5554
+ fallbacks,
5555
+ carries
5199
5556
  );
5200
5557
  return {
5201
5558
  servers: Object.fromEntries(
@@ -5223,7 +5580,8 @@ async function migratePlaintextSecrets(filePath, secretStore) {
5223
5580
  secretStore,
5224
5581
  namespace,
5225
5582
  Object.keys(fresh.servers),
5226
- Object.keys(fresh.idpSessions)
5583
+ Object.keys(fresh.idpSessions),
5584
+ writtenClientOwners(fresh, splitSecretsOf)
5227
5585
  );
5228
5586
  }
5229
5587
  const migrateEntrySecrets = async (serverId, secrets) => {
@@ -5276,7 +5634,8 @@ async function readOAuthStore(filePath, secretStore = defaultSecretStore()) {
5276
5634
  }
5277
5635
  const namespace = parseSecretsNamespace(raw);
5278
5636
  const fallbacks = namespace === void 0 ? await recordedNamespaces(filePath) : [];
5279
- return joinSnapshot(snapshot, secretStore, namespace, fallbacks);
5637
+ const carries = namespace === void 0 ? [] : await pendingCarries(filePath, secretStore, namespace);
5638
+ return joinSnapshot(snapshot, secretStore, namespace, fallbacks, carries);
5280
5639
  });
5281
5640
  }
5282
5641
  async function removeOAuthStore(filePath, secretStore = defaultSecretStore()) {
@@ -6198,6 +6557,16 @@ var MessageLogState = class extends TypedEventTarget {
6198
6557
  return;
6199
6558
  }
6200
6559
  };
6560
+ const onRequestFailed = (event) => {
6561
+ const { id, reason } = event.detail;
6562
+ const requestEntry = this.pendingRequestEntries.get(id);
6563
+ if (!requestEntry) return;
6564
+ this.pendingRequestEntries.delete(id);
6565
+ requestEntry.clientError = reason;
6566
+ requestEntry.duration = Date.now() - requestEntry.timestamp.getTime();
6567
+ this.dispatchTypedEvent("message", requestEntry);
6568
+ this.dispatchTypedEvent("messagesChange", this.getMessages());
6569
+ };
6201
6570
  const onStatusChange = () => {
6202
6571
  if (isTerminalStatus(this.client?.getStatus())) {
6203
6572
  this.messages = [];
@@ -6207,11 +6576,13 @@ var MessageLogState = class extends TypedEventTarget {
6207
6576
  };
6208
6577
  this.client.addEventListener("message", onMessage);
6209
6578
  this.client.addEventListener("responseRejected", onResponseRejected);
6579
+ this.client.addEventListener("requestFailed", onRequestFailed);
6210
6580
  this.client.addEventListener("statusChange", onStatusChange);
6211
6581
  this.unsubscribe = () => {
6212
6582
  if (this.client) {
6213
6583
  this.client.removeEventListener("message", onMessage);
6214
6584
  this.client.removeEventListener("responseRejected", onResponseRejected);
6585
+ this.client.removeEventListener("requestFailed", onRequestFailed);
6215
6586
  this.client.removeEventListener("statusChange", onStatusChange);
6216
6587
  }
6217
6588
  this.client = null;
@@ -6571,6 +6942,12 @@ var DirectoryReadResultSchema = z2.looseObject({
6571
6942
  resources: z2.array(DirectoryChildSchema),
6572
6943
  nextCursor: z2.string().optional()
6573
6944
  });
6945
+ var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
6946
+ {
6947
+ ttlMs: z2.int().min(0),
6948
+ cacheScope: z2.enum(["public", "private"])
6949
+ }
6950
+ );
6574
6951
 
6575
6952
  // ../../core/mcp/state/managedSkillsState.ts
6576
6953
  var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
@@ -9823,6 +10200,23 @@ function subscriptionFailureMessage(error) {
9823
10200
  return error instanceof Error ? error.message : String(error);
9824
10201
  }
9825
10202
 
10203
+ // ../../core/mcp/subscriptionRefusal.ts
10204
+ import { SdkErrorCode as SdkErrorCode3, SdkHttpError } from "@modelcontextprotocol/client";
10205
+ var REFUSAL_STATUSES = /* @__PURE__ */ new Set([400, 403, 404, 405]);
10206
+ function subscriptionListenRefusal(error) {
10207
+ if (!SdkHttpError.isInstance(error) || error.code !== SdkErrorCode3.ClientHttpNotImplemented || !REFUSAL_STATUSES.has(error.status)) {
10208
+ return void 0;
10209
+ }
10210
+ return {
10211
+ status: error.status,
10212
+ ...error.statusText ? { statusText: error.statusText } : {}
10213
+ };
10214
+ }
10215
+ function subscriptionListenRefusalMessage(refusal) {
10216
+ const status = refusal.statusText ? `${refusal.status} ${refusal.statusText}` : String(refusal.status);
10217
+ return `The server refused subscriptions/listen (HTTP ${status}). Not retrying \u2014 the server would answer the same way again.`;
10218
+ }
10219
+
9826
10220
  // ../../core/mcp/messageTrackingTransport.ts
9827
10221
  var MessageTrackingTransport = class {
9828
10222
  baseTransport;
@@ -10052,7 +10446,7 @@ var TaskProgressRouter = class {
10052
10446
  };
10053
10447
 
10054
10448
  // ../../core/extension/tasks/rawWireChannel.ts
10055
- import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode3 } from "@modelcontextprotocol/client";
10449
+ import { SdkError as SdkError3, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
10056
10450
  import { DispatchError as DispatchError2 } from "@modelcontextprotocol/ext-tasks/client";
10057
10451
  var RAW_WIRE_ID_PREFIX = "inspector-ext-";
10058
10452
  var RawWireChannel = class {
@@ -10125,8 +10519,8 @@ var RawWireChannel = class {
10125
10519
  `Raw MCP request "${message.method}" timed out after ${timeoutMs} ms`,
10126
10520
  false,
10127
10521
  {
10128
- cause: this.host.annotateTimeout(
10129
- new SdkError3(SdkErrorCode3.RequestTimeout, "Request timed out", {
10522
+ cause: this.host.annotateError(
10523
+ new SdkError3(SdkErrorCode4.RequestTimeout, "Request timed out", {
10130
10524
  timeout: timeoutMs
10131
10525
  }),
10132
10526
  message.method
@@ -10166,7 +10560,7 @@ var RawWireChannel = class {
10166
10560
  if (!pending) return;
10167
10561
  pending.cleanup();
10168
10562
  reject(
10169
- this.host.annotateTimeout(
10563
+ this.host.annotateError(
10170
10564
  error instanceof Error ? error : new Error(String(error)),
10171
10565
  message.method
10172
10566
  )
@@ -10899,12 +11293,33 @@ var ToolCallCancelledError = class extends Error {
10899
11293
  };
10900
11294
 
10901
11295
  // ../../core/mcp/connectionDiagnostics.ts
10902
- import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode4 } from "@modelcontextprotocol/client";
11296
+ import { SdkError as SdkError4, SdkErrorCode as SdkErrorCode5 } from "@modelcontextprotocol/client";
10903
11297
  function isRequestTimeoutData(value) {
10904
11298
  return typeof value === "object" && value !== null && typeof value.timeout === "number";
10905
11299
  }
10906
11300
  function isRequestTimeoutError(err) {
10907
- return SdkError4.isInstance(err) && err.code === SdkErrorCode4.RequestTimeout && isRequestTimeoutData(err.data);
11301
+ return SdkError4.isInstance(err) && err.code === SdkErrorCode5.RequestTimeout && isRequestTimeoutData(err.data);
11302
+ }
11303
+ function describeSendFailure(err) {
11304
+ const message = err instanceof Error ? err.message : String(err);
11305
+ const status = httpStatusOf(err);
11306
+ if (status === void 0 || message.includes(`HTTP ${status}`)) {
11307
+ return message;
11308
+ }
11309
+ const statusText = httpStatusTextOf(err);
11310
+ return `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ${message}`;
11311
+ }
11312
+ function httpStatusOf(err) {
11313
+ if (typeof err !== "object" || err === null || !("status" in err)) {
11314
+ return void 0;
11315
+ }
11316
+ return typeof err.status === "number" ? err.status : void 0;
11317
+ }
11318
+ function httpStatusTextOf(err) {
11319
+ if (typeof err !== "object" || err === null || !("statusText" in err)) {
11320
+ return void 0;
11321
+ }
11322
+ return typeof err.statusText === "string" ? err.statusText : void 0;
10908
11323
  }
10909
11324
  function formatDuration(ms) {
10910
11325
  const clamped = Math.max(0, ms);
@@ -10966,11 +11381,108 @@ function annotateRequestTimeout(err, method, diagnostics) {
10966
11381
  diagnostics
10967
11382
  };
10968
11383
  const message = `Request timed out after ${formatDuration(err.data.timeout)} (${method}). ${describeConnectionDiagnostics(diagnostics)}`;
10969
- const annotated = new SdkError4(SdkErrorCode4.RequestTimeout, message, data);
11384
+ const annotated = new SdkError4(SdkErrorCode5.RequestTimeout, message, data);
10970
11385
  annotated.cause = err;
10971
11386
  return annotated;
10972
11387
  }
10973
11388
 
11389
+ // ../../core/mcp/rateLimit.ts
11390
+ import {
11391
+ SdkHttpError as SdkHttpError2
11392
+ } from "@modelcontextprotocol/client";
11393
+ var HTTP_TOO_MANY_REQUESTS = 429;
11394
+ function requestIdsOf(body) {
11395
+ if (body === void 0) return [];
11396
+ let parsed;
11397
+ try {
11398
+ parsed = JSON.parse(body);
11399
+ } catch {
11400
+ return [];
11401
+ }
11402
+ const messages = Array.isArray(parsed) ? parsed : [parsed];
11403
+ const ids = [];
11404
+ for (const message of messages) {
11405
+ if (typeof message !== "object" || message === null) continue;
11406
+ if (!("method" in message) || !("id" in message)) continue;
11407
+ const { id } = message;
11408
+ if (typeof id === "string" || typeof id === "number") ids.push(id);
11409
+ }
11410
+ return ids;
11411
+ }
11412
+ var MAX_UNCLAIMED_RATE_LIMITS = 100;
11413
+ var RateLimitLedger = class {
11414
+ // `undefined` is a real value here: a 429 that carried no header.
11415
+ byRequestId = /* @__PURE__ */ new Map();
11416
+ /** File a tracked fetch, if it was a 429. Anything else is ignored. */
11417
+ record(entry) {
11418
+ if (entry.responseStatus !== HTTP_TOO_MANY_REQUESTS) return;
11419
+ const retryAfter = findHeader(entry.responseHeaders, "retry-after");
11420
+ for (const id of requestIdsOf(entry.requestBody)) {
11421
+ this.byRequestId.delete(id);
11422
+ this.byRequestId.set(id, retryAfter);
11423
+ if (this.byRequestId.size > MAX_UNCLAIMED_RATE_LIMITS) {
11424
+ const [oldest] = this.byRequestId.keys();
11425
+ if (oldest !== void 0) this.byRequestId.delete(oldest);
11426
+ }
11427
+ }
11428
+ }
11429
+ /** The `Retry-After` filed for `id`, removed as it is read. */
11430
+ take(id) {
11431
+ const retryAfter = this.byRequestId.get(id);
11432
+ this.byRequestId.delete(id);
11433
+ return retryAfter;
11434
+ }
11435
+ clear() {
11436
+ this.byRequestId.clear();
11437
+ }
11438
+ };
11439
+ var IMF_FIXDATE = /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/;
11440
+ function parseRetryAfter(value, now) {
11441
+ if (value === void 0) return void 0;
11442
+ const trimmed = value.trim();
11443
+ if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
11444
+ if (!IMF_FIXDATE.test(trimmed)) return void 0;
11445
+ const at = Date.parse(trimmed);
11446
+ if (Number.isNaN(at) || new Date(at).toUTCString() !== trimmed) {
11447
+ return void 0;
11448
+ }
11449
+ return Math.max(0, at - now);
11450
+ }
11451
+ function isRateLimitError(err) {
11452
+ return SdkHttpError2.isInstance(err) && err.status === HTTP_TOO_MANY_REQUESTS;
11453
+ }
11454
+ function retryAfterOn(err) {
11455
+ const value = err.data.retryAfter;
11456
+ return typeof value === "string" ? value : void 0;
11457
+ }
11458
+ function attachRetryAfter(err, retryAfter) {
11459
+ if (!isRateLimitError(err) || retryAfter === void 0) return;
11460
+ if (retryAfterOn(err) !== void 0) return;
11461
+ err.data.retryAfter = retryAfter;
11462
+ }
11463
+ function describeRetry(retryAfter, retryAfterMs) {
11464
+ if (retryAfterMs !== void 0) {
11465
+ return `The server asked to retry after ${formatDuration(retryAfterMs)}.`;
11466
+ }
11467
+ if (retryAfter !== void 0) {
11468
+ return `The server sent an unparseable Retry-After (${retryAfter}).`;
11469
+ }
11470
+ return "The server sent no Retry-After header.";
11471
+ }
11472
+ function annotateRateLimit(err, method, now = Date.now()) {
11473
+ if (!isRateLimitError(err)) return err;
11474
+ const header = retryAfterOn(err);
11475
+ const retryAfterMs = parseRetryAfter(header, now);
11476
+ const data = {
11477
+ ...err.data,
11478
+ method,
11479
+ ...header !== void 0 && { retryAfter: header },
11480
+ ...retryAfterMs !== void 0 && { retryAfterMs }
11481
+ };
11482
+ const message = `Rate limited by the server (HTTP 429) on ${method}. ${describeRetry(header, retryAfterMs)} ${err.message}`;
11483
+ return new SdkHttpError2(err.code, message, data, { cause: err });
11484
+ }
11485
+
10974
11486
  // ../../core/auth/ema/clientConfigError.ts
10975
11487
  function emaClientNotConfiguredMessage(reason) {
10976
11488
  if (reason === "disabled") {
@@ -11406,11 +11918,6 @@ async function mintEmaResourceTokens(config, resourceContext) {
11406
11918
  "EMA requires resource authorization server clientId (per-server oauth.clientId)"
11407
11919
  );
11408
11920
  }
11409
- if (!config.resourceClientSecret?.trim()) {
11410
- throw new Error(
11411
- "EMA requires resource authorization server client secret in server OAuth settings (Test Client secret from xaa.dev resource registration)"
11412
- );
11413
- }
11414
11921
  const idToken = await getValidIdToken({
11415
11922
  idp: config.idp,
11416
11923
  storage: config.storage,
@@ -11676,7 +12183,9 @@ var OAuthManager = class {
11676
12183
  requestRefreshToken: this.oauthConfig.requestRefreshToken,
11677
12184
  // #2068: lets the provider tell an `offline_access` the user asked for
11678
12185
  // from one inherited from a previous grant's persisted scope.
11679
- configuredScope: this.oauthConfig.scope
12186
+ configuredScope: this.oauthConfig.scope,
12187
+ // #2726: in-flight PKCE verifiers keyed by flow, not by server URL.
12188
+ flowStore: this.oauthConfig.flowStore
11680
12189
  });
11681
12190
  provider.setEventTarget(this.params.getEventTarget());
11682
12191
  await provider.prepareForAuth();
@@ -11840,7 +12349,12 @@ var OAuthManager = class {
11840
12349
  recordAuthorizationRequestScope(authorizationUrl, requestedScope) {
11841
12350
  this.pendingAuthorizationScope = authorizationUrl.searchParams.get("scope")?.trim() || requestedScope;
11842
12351
  }
11843
- async completeOAuthFlow(authorizationCode, iss) {
12352
+ /**
12353
+ * `state` is the OAuth `state` the callback was redirected back with. It
12354
+ * selects the flow whose PKCE verifier the exchange uses (#2726); without it
12355
+ * a provider with a flow store has no verifier to send.
12356
+ */
12357
+ async completeOAuthFlow(authorizationCode, iss, state) {
11844
12358
  try {
11845
12359
  if (this.isEnterpriseManaged()) {
11846
12360
  const emaConfig = await this.getEmaFlowConfig();
@@ -11864,6 +12378,9 @@ var OAuthManager = class {
11864
12378
  }
11865
12379
  const provider = await this.createOAuthProvider();
11866
12380
  const serverUrl = this.getServerUrl();
12381
+ if (state !== void 0) {
12382
+ provider.resumeAuthorizationFlow(state);
12383
+ }
11867
12384
  const result = await mcpAuth(provider, {
11868
12385
  serverUrl,
11869
12386
  authorizationCode,
@@ -11875,6 +12392,7 @@ var OAuthManager = class {
11875
12392
  `Expected AUTHORIZED after providing authorization code, got: ${result}`
11876
12393
  );
11877
12394
  }
12395
+ await provider.clearAuthorizationFlow();
11878
12396
  const tokens = await provider.tokens();
11879
12397
  if (!tokens) {
11880
12398
  throw new Error("Failed to retrieve tokens after authorization");
@@ -12006,15 +12524,21 @@ var OAuthManager = class {
12006
12524
  * Re-read persisted OAuth state and determine whether `challenge` is already
12007
12525
  * satisfied without an authorization-server round-trip.
12008
12526
  *
12009
- * Returns `true` for `insufficient_scope` when stored + token scope cover the
12010
- * SEP-2350 union. For `token_expired`, returns `true` only when storage holds
12011
- * a *provably* unexpired token — a JWT whose `exp` is still in the future. An
12012
- * opaque token carries no local expiry evidence, so the resource server's
12013
- * verdict stands and re-authorization proceeds. `invalid_token` and
12014
- * `unauthorized` always return `false` — the resource server explicitly
12015
- * rejected the credential.
12527
+ * For `token_expired` and `insufficient_scope`, returns `true` only when
12528
+ * storage holds a *provably* unexpired token — a JWT whose `exp` is still in
12529
+ * the future. An opaque token carries no local expiry evidence, so the
12530
+ * resource server's verdict stands and re-authorization proceeds.
12531
+ * `insufficient_scope` additionally requires stored + token scope to cover
12532
+ * the SEP-2350 union: some resource servers answer an *expired* token with
12533
+ * `insufficient_scope`, and a scope-only check would replay that dead token
12534
+ * instead of refreshing it (#2724, the same gap #2051 closed for
12535
+ * `token_expired`). `invalid_token` and `unauthorized` always return
12536
+ * `false` — the resource server explicitly rejected the credential.
12537
+ *
12538
+ * A caller that has just completed a grant passes `scopeOnly` — see
12539
+ * {@link CheckAuthChallengeSatisfiedOptions}.
12016
12540
  */
12017
- async checkAuthChallengeSatisfied(challenge) {
12541
+ async checkAuthChallengeSatisfied(challenge, options) {
12018
12542
  const storage = this.oauthConfig.storage;
12019
12543
  if (!storage) {
12020
12544
  return false;
@@ -12027,6 +12551,14 @@ var OAuthManager = class {
12027
12551
  if (challenge.reason !== "insufficient_scope") {
12028
12552
  return challenge.reason === "token_expired" && isAccessTokenProvablyUnexpired(tokens);
12029
12553
  }
12554
+ if (!options?.scopeOnly && !isAccessTokenProvablyUnexpired(tokens)) {
12555
+ return false;
12556
+ }
12557
+ return this.isStepUpScopeCovered(challenge, storage, tokens);
12558
+ }
12559
+ /** Stored + token scope cover the challenge's SEP-2350 union. */
12560
+ async isStepUpScopeCovered(challenge, storage, tokens) {
12561
+ const serverUrl = this.getServerUrl();
12030
12562
  const enriched = await this.enrichChallengeWithAuthorizationScopes(
12031
12563
  challenge,
12032
12564
  tokens.scope
@@ -12130,14 +12662,14 @@ var OAuthManager = class {
12130
12662
  }
12131
12663
  async handleEnterpriseManagedAuthChallenge(challenge, options) {
12132
12664
  const enriched = await this.enrichChallengeWithAuthorizationScopes(challenge);
12133
- if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp) {
12665
+ if (enriched.reason === "insufficient_scope" && !options?.confirmedStepUp && !await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
12134
12666
  return { kind: "step_up_confirm", challenge: enriched };
12135
12667
  }
12136
12668
  const config = await this.emaFlowConfigForChallenge(enriched);
12137
12669
  if (enriched.reason === "insufficient_scope") {
12138
12670
  const silent = await trySilentEmaAuth(config);
12139
12671
  if (silent.status === "success") {
12140
- if (await this.checkAuthChallengeSatisfied(enriched)) {
12672
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
12141
12673
  const minted = await this.requireStorage().getTokens(
12142
12674
  this.getServerUrl()
12143
12675
  );
@@ -12215,9 +12747,16 @@ var OAuthManager = class {
12215
12747
  fetchFn: this.params.effectiveAuthFetch,
12216
12748
  resourceMetadataUrl,
12217
12749
  ...enriched.reason === "insufficient_scope" && {
12750
+ // Judge coverage the way checkAuthChallengeSatisfied does: a token
12751
+ // response that omitted `scope` granted what was stored, so a
12752
+ // covered challenge on an expired token refreshes rather than
12753
+ // forcing interactive authorization (#2724).
12218
12754
  forceReauthorization: isStrictScopeSuperset(
12219
12755
  scopeForAuth,
12220
- tokens?.scope
12756
+ resolveEffectiveGrantedScope(
12757
+ await this.oauthConfig.storage?.getScope(serverUrl),
12758
+ tokens?.scope
12759
+ )
12221
12760
  )
12222
12761
  }
12223
12762
  });
@@ -12226,7 +12765,7 @@ var OAuthManager = class {
12226
12765
  }
12227
12766
  if (result === "AUTHORIZED") {
12228
12767
  if (enriched.reason === "insufficient_scope") {
12229
- if (await this.checkAuthChallengeSatisfied(enriched)) {
12768
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
12230
12769
  const freshTokens = await provider.tokens();
12231
12770
  const scopeToPersist = resolvePersistedScopeAfterGrant(
12232
12771
  freshTokens?.scope,
@@ -12328,7 +12867,7 @@ var OAuthManager = class {
12328
12867
  if (result !== "AUTHORIZED") {
12329
12868
  return null;
12330
12869
  }
12331
- if (await this.checkAuthChallengeSatisfied(enriched)) {
12870
+ if (await this.checkAuthChallengeSatisfied(enriched, { scopeOnly: true })) {
12332
12871
  const freshTokens = await provider.tokens();
12333
12872
  const scopeToPersist = resolvePersistedScopeAfterGrant(
12334
12873
  freshTokens?.scope,
@@ -12360,8 +12899,8 @@ var OAuthManager = class {
12360
12899
  // ../../core/mcp/remote/remoteClientTransport.ts
12361
12900
  import {
12362
12901
  SdkError as SdkError5,
12363
- SdkErrorCode as SdkErrorCode5,
12364
- SdkHttpError
12902
+ SdkErrorCode as SdkErrorCode6,
12903
+ SdkHttpError as SdkHttpError3
12365
12904
  } from "@modelcontextprotocol/client";
12366
12905
 
12367
12906
  // ../../core/mcp/remote/types.ts
@@ -12418,14 +12957,15 @@ function isConnectTransportError(json) {
12418
12957
  return typeof json === "object" && json !== null && "ok" in json && json.ok === false && "kind" in json && json.kind === "transport_error";
12419
12958
  }
12420
12959
  var SDK_ERROR_CODES = new Set(
12421
- Object.values(SdkErrorCode5)
12960
+ Object.values(SdkErrorCode6)
12422
12961
  );
12423
12962
  function toSdkHttpError(message, http) {
12424
- const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode5.ClientHttpNotImplemented;
12425
- return new SdkHttpError(code, message, {
12963
+ const code = SDK_ERROR_CODES.has(http.code) ? http.code : SdkErrorCode6.ClientHttpNotImplemented;
12964
+ return new SdkHttpError3(code, message, {
12426
12965
  status: http.status,
12427
12966
  ...http.statusText !== void 0 && { statusText: http.statusText },
12428
- ...http.text !== void 0 && { text: http.text }
12967
+ ...http.text !== void 0 && { text: http.text },
12968
+ ...http.retryAfter !== void 0 && { retryAfter: http.retryAfter }
12429
12969
  });
12430
12970
  }
12431
12971
  function legacySessionId(json) {
@@ -12859,7 +13399,7 @@ var RemoteClientTransport = class {
12859
13399
  timer = setTimeout(() => {
12860
13400
  this.sseResponseWaits.delete(requestId);
12861
13401
  reject(
12862
- new SdkError5(SdkErrorCode5.RequestTimeout, "Request timed out", {
13402
+ new SdkError5(SdkErrorCode6.RequestTimeout, "Request timed out", {
12863
13403
  timeout: this.sseResponseTimeoutMs
12864
13404
  })
12865
13405
  );
@@ -13071,7 +13611,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13071
13611
  transport: () => this.transport,
13072
13612
  defaultTimeoutMs: () => this.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC,
13073
13613
  resetTimeoutOnProgress: () => this.resetTimeoutOnProgress,
13074
- annotateTimeout: (error, method) => annotateRequestTimeout(error, method, this.getConnectionDiagnostics())
13614
+ annotateError: (error, method) => this.annotateRequestError(error, method)
13075
13615
  });
13076
13616
  dispatchTaskRequest = (request, options) => this.rawWire.dispatch(request, options);
13077
13617
  // Every outbound request still awaiting a response, keyed by JSON-RPC id.
@@ -13091,6 +13631,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13091
13631
  // `GET` on Streamable HTTP). Both per-session, reset with the maps above.
13092
13632
  lastResponse;
13093
13633
  notificationStream;
13634
+ // Each tracked `429`'s `Retry-After`, by the JSON-RPC id that drew it, until
13635
+ // that id's failed send claims it (#2442). Per-session, reset with the
13636
+ // diagnostics above.
13637
+ rateLimits = new RateLimitLedger();
13094
13638
  /** True when the cached transport was built with an OAuth authProvider attached. */
13095
13639
  transportHasAuthProvider = false;
13096
13640
  /** Dedupes concurrent ambient auth challenges (reason + scopes). */
@@ -13235,6 +13779,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13235
13779
  // site to the two failure handlers. Cleared by any user-initiated refresh (a
13236
13780
  // subscribe/unsubscribe is a fresh attempt, and the server may have changed).
13237
13781
  modernNeverAcknowledged = false;
13782
+ // Set when the last `listen()` was refused at the HTTP layer with a status a
13783
+ // retry cannot change (#2722) — the sibling of `modernNeverAcknowledged`,
13784
+ // carried and cleared the same way, holding the HTTP answer the UI reports.
13785
+ modernListenRefusal = null;
13238
13786
  /** Which in-flight task calls own each progress token (#2316). */
13239
13787
  taskProgress = new TaskProgressRouter();
13240
13788
  // Abort controller for the in-flight ordinary (non-task) tool call. Aborting
@@ -13492,7 +14040,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13492
14040
  this.clientInfo,
13493
14041
  Object.keys(clientOptions).length > 0 ? clientOptions : void 0
13494
14042
  );
13495
- this.annotateSdkRequestTimeouts(this.client);
14043
+ this.annotateSdkRequestErrors(this.client);
13496
14044
  if (this.tasksCapabilityAdvertised) {
13497
14045
  this.bindReceiverTasks();
13498
14046
  }
@@ -13500,7 +14048,8 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13500
14048
  /**
13501
14049
  * Decorate the SDK Client's `request` so a per-request timeout rejects with
13502
14050
  * the connection's state at that moment instead of a bare `Request timed
13503
- * out` (#2318).
14051
+ * out` (#2318), and a `429` rejects as rate limiting with the server's
14052
+ * `Retry-After` instead of a bare `Error POSTing to endpoint` (#2442).
13504
14053
  *
13505
14054
  * `Protocol.request` is the one funnel every SDK verb goes through —
13506
14055
  * `listTools`, `callTool`, `readResource`, the handshake's `initialize`,
@@ -13518,21 +14067,30 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13518
14067
  * implementation signature serves both at runtime; the single cast on the
13519
14068
  * way back is what the overloads cost.
13520
14069
  */
13521
- annotateSdkRequestTimeouts(client) {
14070
+ annotateSdkRequestErrors(client) {
13522
14071
  const sdkRequest = client.request.bind(client);
13523
14072
  const decorated = async (...args) => {
13524
14073
  try {
13525
14074
  return await sdkRequest(...args);
13526
14075
  } catch (err) {
13527
- throw annotateRequestTimeout(
13528
- err,
13529
- args[0].method,
13530
- this.getConnectionDiagnostics()
13531
- );
14076
+ throw this.annotateRequestError(err, args[0].method);
13532
14077
  }
13533
14078
  };
13534
14079
  client.request = decorated;
13535
14080
  }
14081
+ /**
14082
+ * The one place a failed request's error is decorated, shared by the SDK
14083
+ * funnel above and the raw-wire channel that bypasses it (modern-era tool
14084
+ * calls and task operations), so a caller sees the same error whichever
14085
+ * path its request took: a timeout with the connection's state (#2318), a
14086
+ * `429` named as rate limiting (#2442), anything else untouched.
14087
+ */
14088
+ annotateRequestError(err, method) {
14089
+ return annotateRateLimit(
14090
+ annotateRequestTimeout(err, method, this.getConnectionDiagnostics()),
14091
+ method
14092
+ );
14093
+ }
13536
14094
  /**
13537
14095
  * @param base - the composed OAuth-path fetch, deadline innermost (#2319).
13538
14096
  *
@@ -13576,9 +14134,14 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13576
14134
  },
13577
14135
  trackSendFailure: (message, error) => {
13578
14136
  if (isRequestTimeoutError(error)) return;
14137
+ attachRetryAfter(error, this.rateLimits.take(message.id));
13579
14138
  if (this.outstandingRequests.delete(message.id)) {
13580
14139
  this.dispatchConnectionDiagnosticsChange();
13581
14140
  }
14141
+ this.dispatchTypedEvent("requestFailed", {
14142
+ id: message.id,
14143
+ reason: describeSendFailure(error)
14144
+ });
13582
14145
  },
13583
14146
  trackResponse: (message, origin) => {
13584
14147
  const responseId = message.id;
@@ -13644,6 +14207,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13644
14207
  };
13645
14208
  baseTransport.onerror = (error) => {
13646
14209
  if (this.status === "connecting") return;
14210
+ if (subscriptionListenRefusal(error) !== void 0) return;
13647
14211
  this.status = "error";
13648
14212
  this.dispatchTypedEvent("statusChange", this.status);
13649
14213
  this.dispatchTypedEvent("error", error);
@@ -13889,6 +14453,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13889
14453
  this.clearModernReconnectTimer();
13890
14454
  this.modernReconnectAttempts = 0;
13891
14455
  this.modernNeverAcknowledged = false;
14456
+ this.modernListenRefusal = null;
13892
14457
  this.modernSubscription = null;
13893
14458
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
13894
14459
  this.dispatchSubscriptionsChange();
@@ -13927,6 +14492,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
13927
14492
  this.taskProgress.clear();
13928
14493
  this.lastResponse = void 0;
13929
14494
  this.notificationStream = void 0;
14495
+ this.rateLimits.clear();
13930
14496
  this.dispatchConnectionDiagnosticsChange();
13931
14497
  if (this.malformedListItems.size > 0) {
13932
14498
  this.malformedListItems.clear();
@@ -14029,6 +14595,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
14029
14595
  onFetchRequest: (entry) => {
14030
14596
  this.dispatchFetchRequest({ ...entry, category: "transport" });
14031
14597
  this.noteTransportStream(entry);
14598
+ this.rateLimits.record(entry);
14032
14599
  },
14033
14600
  onFetchResponseBody: (id, body) => {
14034
14601
  this.dispatchFetchRequestBodyUpdate(id, body);
@@ -16368,11 +16935,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16368
16935
  // for page one.
16369
16936
  ...cursor !== void 0 ? { cursor } : {}
16370
16937
  };
16938
+ const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
16371
16939
  try {
16372
16940
  return await this.invokeMcpClient(
16373
16941
  () => this.client.request(
16374
16942
  { method: RESOURCES_DIRECTORY_READ_METHOD, params },
16375
- DirectoryReadResultSchema,
16943
+ resultSchema,
16376
16944
  this.getRequestOptions(this.progressTokenOf(metadata))
16377
16945
  ),
16378
16946
  { method: RESOURCES_DIRECTORY_READ_METHOD }
@@ -16827,6 +17395,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16827
17395
  this.clearModernReconnectTimer();
16828
17396
  this.modernReconnectAttempts = 0;
16829
17397
  this.modernNeverAcknowledged = false;
17398
+ this.modernListenRefusal = null;
16830
17399
  }
16831
17400
  const generation = ++this.modernListenGeneration;
16832
17401
  const previous = this.modernSubscription;
@@ -16845,8 +17414,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16845
17414
  this.getRequestOptions()
16846
17415
  );
16847
17416
  } catch (error) {
16848
- if (generation === this.modernListenGeneration && isNeverAcknowledgedSubscriptionClose(error)) {
16849
- this.modernNeverAcknowledged = true;
17417
+ if (generation === this.modernListenGeneration) {
17418
+ if (isNeverAcknowledgedSubscriptionClose(error)) {
17419
+ this.modernNeverAcknowledged = true;
17420
+ }
17421
+ this.modernListenRefusal = subscriptionListenRefusal(error) ?? null;
16850
17422
  }
16851
17423
  throw error;
16852
17424
  }
@@ -16927,6 +17499,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16927
17499
  * its error either way — the retry is about the subscriptions, not the call.
16928
17500
  */
16929
17501
  reconcileModernStreamStateAfterFailedRefresh() {
17502
+ if (this.modernListenRefusal) {
17503
+ this.endModernStreamRefused(this.modernListenRefusal);
17504
+ return;
17505
+ }
16930
17506
  if (!this.wantsModernStream()) {
16931
17507
  this.setModernStreamState(INACTIVE_SUBSCRIPTION_STREAM_STATE);
16932
17508
  return;
@@ -16959,6 +17535,23 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16959
17535
  honoredUris: []
16960
17536
  });
16961
17537
  }
17538
+ /**
17539
+ * Settle the stream on an HTTP refusal of the listen (#2722): the
17540
+ * `"refused"` status carrying the HTTP answer, no reconnect, and a log line
17541
+ * with the sentence the UI shows. Reported whatever `active` says — see
17542
+ * `ResourceSubscriptionStreamState`.
17543
+ */
17544
+ endModernStreamRefused(refusal) {
17545
+ this.clearModernReconnectTimer();
17546
+ const message = subscriptionListenRefusalMessage(refusal);
17547
+ this.logger.warn({ message }, "subscriptions/listen refused over HTTP");
17548
+ this.setModernStreamState({
17549
+ active: this.modernStreamActive(),
17550
+ status: "refused",
17551
+ honoredUris: [],
17552
+ refusal
17553
+ });
17554
+ }
16962
17555
  /**
16963
17556
  * Schedule a reconnect re-listen after the current backoff delay (#1630).
16964
17557
  * `modernReconnectAttempts` reflects the number of *consecutive failed*
@@ -16996,6 +17589,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
16996
17589
  this.endModernStreamNeverAcknowledged();
16997
17590
  return;
16998
17591
  }
17592
+ if (this.modernListenRefusal && !isTerminalStatus(this.status)) {
17593
+ this.endModernStreamRefused(this.modernListenRefusal);
17594
+ return;
17595
+ }
16999
17596
  this.modernReconnectAttempts += 1;
17000
17597
  if (this.modernReconnectAttempts > MODERN_RECONNECT_MAX_ATTEMPTS || isTerminalStatus(this.status) || !this.wantsModernStream()) {
17001
17598
  this.setModernStreamState({
@@ -17150,8 +17747,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
17150
17747
  * Re-read OAuth storage and test whether a challenge is already satisfied.
17151
17748
  * See {@link OAuthManager.checkAuthChallengeSatisfied}.
17152
17749
  */
17153
- async checkAuthChallengeSatisfied(challenge) {
17154
- return this.ensureOAuthManager().checkAuthChallengeSatisfied(challenge);
17750
+ async checkAuthChallengeSatisfied(challenge, options) {
17751
+ return this.ensureOAuthManager().checkAuthChallengeSatisfied(
17752
+ challenge,
17753
+ options
17754
+ );
17155
17755
  }
17156
17756
  /**
17157
17757
  * Push recovered OAuth auth state to the remote backend (same MCP session).
@@ -17315,8 +17915,12 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
17315
17915
  * Direct transports reconnect after token exchange so the live MCP session
17316
17916
  * picks up the new Bearer token (mirrors silent recovery reconnect).
17317
17917
  */
17318
- async completeOAuthFlow(authorizationCode, iss) {
17319
- await this.ensureOAuthManager().completeOAuthFlow(authorizationCode, iss);
17918
+ async completeOAuthFlow(authorizationCode, iss, state) {
17919
+ await this.ensureOAuthManager().completeOAuthFlow(
17920
+ authorizationCode,
17921
+ iss,
17922
+ state
17923
+ );
17320
17924
  if (this.usesDirectAuthRecovery()) {
17321
17925
  await this.reconnectAfterAuthRecovery();
17322
17926
  }
@@ -17340,7 +17944,11 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
17340
17944
  * Finish OAuth after a full-page redirect and reconnect (or reattach) the MCP session.
17341
17945
  */
17342
17946
  async resumeAfterOAuth(authorizationCode, options) {
17343
- await this.completeOAuthFlow(authorizationCode, options?.iss);
17947
+ await this.completeOAuthFlow(
17948
+ authorizationCode,
17949
+ options?.iss,
17950
+ options?.state
17951
+ );
17344
17952
  const remoteSessionId = options?.remoteSessionId;
17345
17953
  const transport = this.baseTransport;
17346
17954
  if (remoteSessionId && transport instanceof RemoteClientTransport) {
@@ -18895,7 +19503,11 @@ async function runRunnerInteractiveOAuth(options) {
18895
19503
  path: options.callbackListen.pathname,
18896
19504
  onCallback: async (params) => {
18897
19505
  try {
18898
- await options.client.completeOAuthFlow(params.code, params.iss);
19506
+ await options.client.completeOAuthFlow(
19507
+ params.code,
19508
+ params.iss,
19509
+ params.state
19510
+ );
18899
19511
  flowResolve();
18900
19512
  } catch (err) {
18901
19513
  flowReject(toRunnerOAuthError(err));
@@ -18947,7 +19559,10 @@ async function runRunnerInteractiveOAuth(options) {
18947
19559
  }
18948
19560
  if (options.authChallenge) {
18949
19561
  const satisfied = await racingSignals(
18950
- options.client.checkAuthChallengeSatisfied(options.authChallenge)
19562
+ // The token exchange just completed, so only scope is in question.
19563
+ options.client.checkAuthChallengeSatisfied(options.authChallenge, {
19564
+ scopeOnly: true
19565
+ })
18951
19566
  );
18952
19567
  if (!satisfied) {
18953
19568
  return {
@@ -19337,14 +19952,25 @@ import * as path6 from "path";
19337
19952
  // ../../core/node/openUrl.ts
19338
19953
  import open from "open";
19339
19954
  var OPEN_URL_TIMEOUT_MS = 5e3;
19340
- function launched(child) {
19341
- return new Promise((resolve8, reject) => {
19342
- child.on("error", reject);
19343
- child.once("spawn", () => resolve8());
19955
+ var OPENABLE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
19956
+ function toOpenableHref(href) {
19957
+ let parsed;
19958
+ try {
19959
+ parsed = new URL(href);
19960
+ } catch {
19961
+ throw new Error("refusing to open an invalid URL");
19962
+ }
19963
+ if (!OPENABLE_PROTOCOLS.has(parsed.protocol)) {
19964
+ throw new Error(`refusing to open a non-http(s) URL (${parsed.protocol})`);
19965
+ }
19966
+ return parsed.href;
19967
+ }
19968
+ function absorbLateErrors(child) {
19969
+ child.on("error", () => {
19344
19970
  });
19345
19971
  }
19346
19972
  async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
19347
- const href = typeof url === "string" ? url : url.href;
19973
+ const href = toOpenableHref(typeof url === "string" ? url : url.href);
19348
19974
  let timer;
19349
19975
  const timeout = new Promise((_, reject) => {
19350
19976
  timer = setTimeout(
@@ -19353,8 +19979,7 @@ async function openUrl(url, timeoutMs = OPEN_URL_TIMEOUT_MS) {
19353
19979
  );
19354
19980
  });
19355
19981
  try {
19356
- const opened = Promise.resolve().then(() => open(href)).then(launched);
19357
- await Promise.race([opened, timeout]);
19982
+ await Promise.race([open(href).then(absorbLateErrors), timeout]);
19358
19983
  } finally {
19359
19984
  clearTimeout(timer);
19360
19985
  }