toolcraft 0.0.309 → 0.0.311

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.
Files changed (26) hide show
  1. package/composition.json +3 -3
  2. package/dist/composition.json +3 -3
  3. package/node_modules/auth-store/README.md +16 -0
  4. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +3 -0
  5. package/node_modules/auth-store/dist/encrypted-file-store.js +7 -0
  6. package/node_modules/auth-store/dist/index.d.ts +1 -0
  7. package/node_modules/auth-store/dist/keychain-store.d.ts +8 -0
  8. package/node_modules/auth-store/dist/keychain-store.js +13 -0
  9. package/node_modules/auth-store/dist/transaction-lock.d.ts +24 -0
  10. package/node_modules/auth-store/dist/transaction-lock.js +152 -0
  11. package/node_modules/auth-store/dist/types.d.ts +2 -0
  12. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  13. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +33 -0
  14. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +8 -0
  15. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +202 -185
  16. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/session-transaction.d.ts +6 -0
  17. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/session-transaction.js +42 -0
  18. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
  19. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +7 -3
  20. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +14 -1
  21. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  22. package/node_modules/tiny-mcp-client/dist/index.d.ts +39 -1
  23. package/node_modules/tiny-mcp-client/dist/index.js +503 -254
  24. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  25. package/node_modules/toolcraft-schema/package.json +1 -1
  26. package/package.json +2 -2
@@ -279,8 +279,8 @@ function receivedType(value) {
279
279
  }
280
280
  return typeof value;
281
281
  }
282
- function issue(path3, expected, value, message, keyword = keywordFor(expected)) {
283
- return { path: path3, expected, received: receivedType(value), message, keyword };
282
+ function issue(path5, expected, value, message, keyword = keywordFor(expected)) {
283
+ return { path: path5, expected, received: receivedType(value), message, keyword };
284
284
  }
285
285
  function keywordFor(expected) {
286
286
  if (["null", "boolean", "object", "array", "number", "integer", "string"].includes(expected)) {
@@ -997,113 +997,113 @@ function evaluateArrayApplicators(graph, node, schema, value, context) {
997
997
  }
998
998
  return results;
999
999
  }
1000
- function evaluateValidationKeywords(node, schema, value, path3) {
1000
+ function evaluateValidationKeywords(node, schema, value, path5) {
1001
1001
  const results = [];
1002
1002
  const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : [];
1003
1003
  if (types.length > 0 && !types.some((type) => typeof type === "string" && typeMatches(type, value))) {
1004
- results.push(invalidResult(issue(path3, types.join(","), value, `must be ${types.join(",")}`)));
1004
+ results.push(invalidResult(issue(path5, types.join(","), value, `must be ${types.join(",")}`)));
1005
1005
  return results;
1006
1006
  }
1007
1007
  if (schema.const !== void 0 && !deepEqual(schema.const, value)) {
1008
- results.push(invalidResult(issue(path3, "const", value, "must be equal to constant")));
1008
+ results.push(invalidResult(issue(path5, "const", value, "must be equal to constant")));
1009
1009
  }
1010
1010
  if (Array.isArray(schema.enum) && !schema.enum.some((entry) => deepEqual(entry, value))) {
1011
- results.push(invalidResult(issue(path3, "enum", value, "must be equal to one of the allowed values")));
1011
+ results.push(invalidResult(issue(path5, "enum", value, "must be equal to one of the allowed values")));
1012
1012
  }
1013
1013
  if (typeof value === "number" && Number.isFinite(value)) {
1014
- evaluateNumberKeywords(schema, value, path3, results);
1014
+ evaluateNumberKeywords(schema, value, path5, results);
1015
1015
  }
1016
1016
  if (typeof value === "string") {
1017
- evaluateStringKeywords(schema, value, path3, results);
1017
+ evaluateStringKeywords(schema, value, path5, results);
1018
1018
  }
1019
1019
  if (Array.isArray(value)) {
1020
- evaluateArrayKeywords(schema, value, path3, results);
1020
+ evaluateArrayKeywords(schema, value, path5, results);
1021
1021
  }
1022
1022
  if (isObject(value)) {
1023
- evaluateObjectKeywords(node, schema, value, path3, results);
1023
+ evaluateObjectKeywords(node, schema, value, path5, results);
1024
1024
  }
1025
1025
  return results;
1026
1026
  }
1027
- function evaluateNumberKeywords(schema, value, path3, results) {
1027
+ function evaluateNumberKeywords(schema, value, path5, results) {
1028
1028
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
1029
- results.push(invalidResult(issue(path3, `multiple of ${schema.multipleOf}`, value, `must be multiple of ${schema.multipleOf}`)));
1029
+ results.push(invalidResult(issue(path5, `multiple of ${schema.multipleOf}`, value, `must be multiple of ${schema.multipleOf}`)));
1030
1030
  }
1031
1031
  if (typeof schema.maximum === "number" && value > schema.maximum) {
1032
- results.push(invalidResult(issue(path3, `<= ${schema.maximum}`, value, `must be <= ${schema.maximum}`)));
1032
+ results.push(invalidResult(issue(path5, `<= ${schema.maximum}`, value, `must be <= ${schema.maximum}`)));
1033
1033
  }
1034
1034
  if (typeof schema.minimum === "number" && value < schema.minimum) {
1035
- results.push(invalidResult(issue(path3, `>= ${schema.minimum}`, value, `must be >= ${schema.minimum}`)));
1035
+ results.push(invalidResult(issue(path5, `>= ${schema.minimum}`, value, `must be >= ${schema.minimum}`)));
1036
1036
  }
1037
1037
  if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
1038
- results.push(invalidResult(issue(path3, `< ${schema.exclusiveMaximum}`, value, `must be < ${schema.exclusiveMaximum}`)));
1038
+ results.push(invalidResult(issue(path5, `< ${schema.exclusiveMaximum}`, value, `must be < ${schema.exclusiveMaximum}`)));
1039
1039
  }
1040
1040
  if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) {
1041
- results.push(invalidResult(issue(path3, `> ${schema.exclusiveMinimum}`, value, `must be > ${schema.exclusiveMinimum}`)));
1041
+ results.push(invalidResult(issue(path5, `> ${schema.exclusiveMinimum}`, value, `must be > ${schema.exclusiveMinimum}`)));
1042
1042
  }
1043
1043
  }
1044
- function evaluateStringKeywords(schema, value, path3, results) {
1044
+ function evaluateStringKeywords(schema, value, path5, results) {
1045
1045
  const length = unicodeLength(value);
1046
1046
  if (typeof schema.maxLength === "number" && length > schema.maxLength) {
1047
- results.push(invalidResult(issue(path3, `length <= ${schema.maxLength}`, value, `must NOT have more than ${schema.maxLength} characters`)));
1047
+ results.push(invalidResult(issue(path5, `length <= ${schema.maxLength}`, value, `must NOT have more than ${schema.maxLength} characters`)));
1048
1048
  }
1049
1049
  if (typeof schema.minLength === "number" && length < schema.minLength) {
1050
- results.push(invalidResult(issue(path3, `length >= ${schema.minLength}`, value, `must NOT have fewer than ${schema.minLength} characters`)));
1050
+ results.push(invalidResult(issue(path5, `length >= ${schema.minLength}`, value, `must NOT have fewer than ${schema.minLength} characters`)));
1051
1051
  }
1052
1052
  if (typeof schema.pattern === "string" && !new RegExp(schema.pattern, "u").test(value)) {
1053
- results.push(invalidResult(issue(path3, `pattern ${schema.pattern}`, value, `must match pattern ${schema.pattern}`)));
1053
+ results.push(invalidResult(issue(path5, `pattern ${schema.pattern}`, value, `must match pattern ${schema.pattern}`)));
1054
1054
  }
1055
1055
  }
1056
- function evaluateArrayKeywords(schema, value, path3, results) {
1056
+ function evaluateArrayKeywords(schema, value, path5, results) {
1057
1057
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
1058
- results.push(invalidResult(issue(path3, `items <= ${schema.maxItems}`, value, `must NOT have more than ${schema.maxItems} items`)));
1058
+ results.push(invalidResult(issue(path5, `items <= ${schema.maxItems}`, value, `must NOT have more than ${schema.maxItems} items`)));
1059
1059
  }
1060
1060
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
1061
- results.push(invalidResult(issue(path3, `items >= ${schema.minItems}`, value, `must NOT have fewer than ${schema.minItems} items`)));
1061
+ results.push(invalidResult(issue(path5, `items >= ${schema.minItems}`, value, `must NOT have fewer than ${schema.minItems} items`)));
1062
1062
  }
1063
1063
  if (schema.uniqueItems === true) {
1064
1064
  for (let left = 0; left < value.length; left += 1) {
1065
1065
  for (let right = left + 1; right < value.length; right += 1) {
1066
1066
  if (deepEqual(value[left], value[right])) {
1067
- results.push(invalidResult(issue(path3, "unique items", value, "must NOT have duplicate items")));
1067
+ results.push(invalidResult(issue(path5, "unique items", value, "must NOT have duplicate items")));
1068
1068
  return;
1069
1069
  }
1070
1070
  }
1071
1071
  }
1072
1072
  }
1073
1073
  }
1074
- function evaluateObjectKeywords(node, schema, value, path3, results) {
1074
+ function evaluateObjectKeywords(node, schema, value, path5, results) {
1075
1075
  const keys = Object.keys(value);
1076
1076
  if (typeof schema.maxProperties === "number" && keys.length > schema.maxProperties) {
1077
- results.push(invalidResult(issue(path3, `properties <= ${schema.maxProperties}`, value, `must NOT have more than ${schema.maxProperties} properties`)));
1077
+ results.push(invalidResult(issue(path5, `properties <= ${schema.maxProperties}`, value, `must NOT have more than ${schema.maxProperties} properties`)));
1078
1078
  }
1079
1079
  if (typeof schema.minProperties === "number" && keys.length < schema.minProperties) {
1080
- results.push(invalidResult(issue(path3, `properties >= ${schema.minProperties}`, value, `must NOT have fewer than ${schema.minProperties} properties`)));
1080
+ results.push(invalidResult(issue(path5, `properties >= ${schema.minProperties}`, value, `must NOT have fewer than ${schema.minProperties} properties`)));
1081
1081
  }
1082
1082
  if (Array.isArray(schema.required)) {
1083
1083
  for (const key2 of schema.required) {
1084
1084
  if (typeof key2 === "string" && !Object.prototype.hasOwnProperty.call(value, key2)) {
1085
- results.push(invalidResult(issue([...path3, key2], "required", void 0, `must have required property '${key2}'`)));
1085
+ results.push(invalidResult(issue([...path5, key2], "required", void 0, `must have required property '${key2}'`)));
1086
1086
  }
1087
1087
  }
1088
1088
  }
1089
1089
  const dependentRequired = isObject(schema.dependentRequired) ? schema.dependentRequired : {};
1090
1090
  for (const [key2, dependencies] of Object.entries(dependentRequired)) {
1091
1091
  if (Object.prototype.hasOwnProperty.call(value, key2) && Array.isArray(dependencies)) {
1092
- addMissingDependencies(value, dependencies, path3, results);
1092
+ addMissingDependencies(value, dependencies, path5, results);
1093
1093
  }
1094
1094
  }
1095
1095
  if (node.dialect === "draft7" && isObject(schema.dependencies)) {
1096
1096
  for (const [key2, dependencies] of Object.entries(schema.dependencies)) {
1097
1097
  if (Object.prototype.hasOwnProperty.call(value, key2) && Array.isArray(dependencies)) {
1098
- addMissingDependencies(value, dependencies, path3, results);
1098
+ addMissingDependencies(value, dependencies, path5, results);
1099
1099
  }
1100
1100
  }
1101
1101
  }
1102
1102
  }
1103
- function addMissingDependencies(value, dependencies, path3, results) {
1103
+ function addMissingDependencies(value, dependencies, path5, results) {
1104
1104
  for (const dependency of dependencies) {
1105
1105
  if (typeof dependency === "string" && !Object.prototype.hasOwnProperty.call(value, dependency)) {
1106
- results.push(invalidResult(issue([...path3, dependency], "dependency", void 0, `must have property '${dependency}'`)));
1106
+ results.push(invalidResult(issue([...path5, dependency], "dependency", void 0, `must have property '${dependency}'`)));
1107
1107
  }
1108
1108
  }
1109
1109
  }
@@ -3397,19 +3397,168 @@ var SubscriptionManager = class {
3397
3397
 
3398
3398
  // ../mcp-oauth/dist/client/auth-store-session-store.js
3399
3399
  import crypto from "node:crypto";
3400
- import path2 from "node:path";
3400
+ import path4 from "node:path";
3401
3401
 
3402
3402
  // ../auth-store/dist/encrypted-file-store.js
3403
- import { createCipheriv, createDecipheriv, randomBytes, randomUUID, scrypt } from "node:crypto";
3403
+ import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
3404
3404
  import { promises as fs } from "node:fs";
3405
3405
  import { homedir, hostname, userInfo } from "node:os";
3406
- import path from "node:path";
3406
+ import path2 from "node:path";
3407
3407
 
3408
3408
  // ../auth-store/dist/error-codes.js
3409
3409
  function hasOwnErrorCode(error, code) {
3410
3410
  return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
3411
3411
  }
3412
3412
 
3413
+ // ../auth-store/dist/transaction-lock.js
3414
+ import { randomUUID } from "node:crypto";
3415
+ import path from "node:path";
3416
+ async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
3417
+ const timeoutMs = options.timeoutMs ?? 3e4;
3418
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
3419
+ throw new Error("Invalid secret-store transaction lock timeout");
3420
+ options.signal?.throwIfAborted();
3421
+ const deadline = performance.now() + timeoutMs;
3422
+ const directory = path.resolve(lockDirectory);
3423
+ await assertLockDirectoryPath(fs2, directory);
3424
+ await fs2.mkdir(directory, { recursive: true, mode: 448 });
3425
+ await assertLockDirectoryPath(fs2, directory);
3426
+ const name = `${process.pid}-${randomUUID()}.claim`;
3427
+ const claimPath = path.join(directory, name);
3428
+ const temporaryPath = `${claimPath}.tmp`;
3429
+ let claimed = true;
3430
+ let temporaryCreated = false;
3431
+ let outcome;
3432
+ try {
3433
+ try {
3434
+ await fs2.writeFile(claimPath, JSON.stringify({ ticket: null }), { encoding: "utf8", flag: "wx", mode: 384 });
3435
+ } catch (error) {
3436
+ if (hasOwnErrorCode(error, "EEXIST"))
3437
+ claimed = false;
3438
+ throw error;
3439
+ }
3440
+ const existing = await readClaims(fs2, directory, name);
3441
+ const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
3442
+ if (!Number.isSafeInteger(ticket))
3443
+ throw new Error("Secret-store transaction lock ticket overflow");
3444
+ temporaryCreated = true;
3445
+ try {
3446
+ await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
3447
+ } catch (error) {
3448
+ if (hasOwnErrorCode(error, "EEXIST"))
3449
+ temporaryCreated = false;
3450
+ throw error;
3451
+ }
3452
+ await fs2.rename(temporaryPath, claimPath);
3453
+ temporaryCreated = false;
3454
+ for (; ; ) {
3455
+ options.signal?.throwIfAborted();
3456
+ const peers = await readClaims(fs2, directory, name);
3457
+ if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
3458
+ break;
3459
+ const remaining = deadline - performance.now();
3460
+ if (remaining <= 0)
3461
+ throw new Error("Timed out waiting for secret-store transaction lock");
3462
+ await new Promise((resolve, reject) => {
3463
+ const abort = () => {
3464
+ clearTimeout(timer);
3465
+ options.signal?.removeEventListener("abort", abort);
3466
+ reject(options.signal?.reason);
3467
+ };
3468
+ const timer = setTimeout(() => {
3469
+ options.signal?.removeEventListener("abort", abort);
3470
+ resolve();
3471
+ }, Math.min(10, remaining));
3472
+ options.signal?.addEventListener("abort", abort, { once: true });
3473
+ if (options.signal?.aborted)
3474
+ abort();
3475
+ });
3476
+ }
3477
+ options.signal?.throwIfAborted();
3478
+ outcome = { result: await operation() };
3479
+ } catch (error) {
3480
+ outcome = { error };
3481
+ }
3482
+ const cleanup = [];
3483
+ for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
3484
+ try {
3485
+ await fs2.unlink(target);
3486
+ } catch (error) {
3487
+ if (!hasOwnErrorCode(error, "ENOENT"))
3488
+ cleanup.push(error);
3489
+ }
3490
+ }
3491
+ if (cleanup.length)
3492
+ throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
3493
+ if ("error" in outcome)
3494
+ throw outcome.error;
3495
+ return outcome.result;
3496
+ }
3497
+ async function assertNoSymbolicLink(fs2, target) {
3498
+ try {
3499
+ if ((await fs2.lstat(target)).isSymbolicLink())
3500
+ throw new Error("Refusing secret-store transaction lock through symbolic link");
3501
+ } catch (error) {
3502
+ if (!hasOwnErrorCode(error, "ENOENT"))
3503
+ throw error;
3504
+ }
3505
+ }
3506
+ async function assertLockDirectoryPath(fs2, directory) {
3507
+ const root = path.parse(directory).root;
3508
+ const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
3509
+ let current = root;
3510
+ for (const [index, segment] of segments.entries()) {
3511
+ current = path.join(current, segment);
3512
+ if (index > 0 || segments.length === 1)
3513
+ await assertNoSymbolicLink(fs2, current);
3514
+ }
3515
+ }
3516
+ async function readClaims(fs2, directory, ownName) {
3517
+ const claims = [];
3518
+ for (const name of await fs2.readdir(directory)) {
3519
+ if (name === ownName || !name.endsWith(".claim"))
3520
+ continue;
3521
+ const pidText = name.slice(0, name.indexOf("-"));
3522
+ const pid = Number(pidText);
3523
+ if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
3524
+ throw new Error("Malformed secret-store transaction lock owner");
3525
+ const target = path.join(directory, name);
3526
+ await assertNoSymbolicLink(fs2, target);
3527
+ let alive = true;
3528
+ try {
3529
+ process.kill(pid, 0);
3530
+ } catch (error) {
3531
+ alive = !hasOwnErrorCode(error, "ESRCH");
3532
+ }
3533
+ if (!alive) {
3534
+ try {
3535
+ await fs2.unlink(target);
3536
+ } catch (error) {
3537
+ if (!hasOwnErrorCode(error, "ENOENT"))
3538
+ throw error;
3539
+ }
3540
+ continue;
3541
+ }
3542
+ let ticket = null;
3543
+ let raw;
3544
+ try {
3545
+ raw = await fs2.readFile(target, "utf8");
3546
+ } catch (error) {
3547
+ if (hasOwnErrorCode(error, "ENOENT"))
3548
+ continue;
3549
+ throw error;
3550
+ }
3551
+ try {
3552
+ const value = JSON.parse(raw);
3553
+ if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0)
3554
+ ticket = value.ticket;
3555
+ } catch {
3556
+ }
3557
+ claims.push({ name, ticket });
3558
+ }
3559
+ return claims;
3560
+ }
3561
+
3413
3562
  // ../auth-store/dist/encrypted-file-store.js
3414
3563
  var derivedKeyCache = /* @__PURE__ */ new Map();
3415
3564
  var ENCRYPTION_ALGORITHM = "aes-256-gcm";
@@ -3435,7 +3584,7 @@ var EncryptedFileStore = class {
3435
3584
  const defaultFileName = input.defaultFileName ?? "credentials.enc";
3436
3585
  assertSafeDefaultDirectory(defaultDirectory);
3437
3586
  assertSafeDefaultFileName(defaultFileName);
3438
- this.filePath = path.join(homeDirectory, defaultDirectory, defaultFileName);
3587
+ this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
3439
3588
  this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
3440
3589
  } else {
3441
3590
  this.filePath = input.filePath;
@@ -3475,6 +3624,12 @@ var EncryptedFileStore = class {
3475
3624
  return null;
3476
3625
  }
3477
3626
  }
3627
+ async withLock(operation, options = {}) {
3628
+ await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
3629
+ if (this.fs.readdir === void 0)
3630
+ throw new Error("Secret-store transaction locks require filesystem readdir support");
3631
+ return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
3632
+ }
3478
3633
  async set(value) {
3479
3634
  await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3480
3635
  const key2 = await this.getEncryptionKey();
@@ -3491,9 +3646,9 @@ var EncryptedFileStore = class {
3491
3646
  authTag: authTag.toString("base64"),
3492
3647
  ciphertext: ciphertext.toString("base64")
3493
3648
  };
3494
- await this.fs.mkdir(path.dirname(this.filePath), { recursive: true });
3649
+ await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
3495
3650
  await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3496
- const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
3651
+ const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
3497
3652
  let temporaryCreated = false;
3498
3653
  try {
3499
3654
  await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
@@ -3523,7 +3678,7 @@ var EncryptedFileStore = class {
3523
3678
  }
3524
3679
  }
3525
3680
  async assertCredentialPathHasNoSymbolicLinks(targetPath) {
3526
- const resolvedPath = path.resolve(targetPath);
3681
+ const resolvedPath = path2.resolve(targetPath);
3527
3682
  const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
3528
3683
  for (const currentPath of protectedPaths) {
3529
3684
  try {
@@ -3554,26 +3709,26 @@ var EncryptedFileStore = class {
3554
3709
  };
3555
3710
  function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
3556
3711
  const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
3557
- return path.resolve(homeDirectory, firstSegment ?? ".");
3712
+ return path2.resolve(homeDirectory, firstSegment ?? ".");
3558
3713
  }
3559
3714
  function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
3560
3715
  if (symbolicLinkCheckStartPath === null) {
3561
3716
  return getExplicitProtectedCredentialPaths(resolvedPath);
3562
3717
  }
3563
- const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
3718
+ const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
3564
3719
  if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
3565
- return [path.dirname(resolvedPath), resolvedPath];
3720
+ return [path2.dirname(resolvedPath), resolvedPath];
3566
3721
  }
3567
3722
  const protectedPaths = [resolvedStartPath];
3568
3723
  let currentPath = resolvedStartPath;
3569
- for (const segment of path.relative(resolvedStartPath, resolvedPath).split(path.sep).filter(Boolean)) {
3570
- currentPath = path.join(currentPath, segment);
3724
+ for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
3725
+ currentPath = path2.join(currentPath, segment);
3571
3726
  protectedPaths.push(currentPath);
3572
3727
  }
3573
3728
  return protectedPaths;
3574
3729
  }
3575
3730
  function assertSafeDefaultDirectory(defaultDirectory) {
3576
- if (path.isAbsolute(defaultDirectory) || path.win32.isAbsolute(defaultDirectory)) {
3731
+ if (path2.isAbsolute(defaultDirectory) || path2.win32.isAbsolute(defaultDirectory)) {
3577
3732
  throw new Error("defaultDirectory must be a relative path inside the home directory");
3578
3733
  }
3579
3734
  for (const segment of splitPathSegments(defaultDirectory)) {
@@ -3591,15 +3746,15 @@ function splitPathSegments(value) {
3591
3746
  return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
3592
3747
  }
3593
3748
  function getExplicitProtectedCredentialPaths(resolvedPath) {
3594
- const parsed = path.parse(resolvedPath);
3595
- const segments = resolvedPath.slice(parsed.root.length).split(path.sep).filter((segment) => segment.length > 0);
3749
+ const parsed = path2.parse(resolvedPath);
3750
+ const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
3596
3751
  if (segments.length <= 1) {
3597
3752
  return [resolvedPath];
3598
3753
  }
3599
3754
  const protectedPaths = [];
3600
3755
  let currentPath = parsed.root;
3601
3756
  for (const [index, segment] of segments.entries()) {
3602
- currentPath = path.join(currentPath, segment);
3757
+ currentPath = path2.join(currentPath, segment);
3603
3758
  if (index === 0) {
3604
3759
  continue;
3605
3760
  }
@@ -3608,8 +3763,8 @@ function getExplicitProtectedCredentialPaths(resolvedPath) {
3608
3763
  return protectedPaths;
3609
3764
  }
3610
3765
  function isPathInsideOrEqual(childPath, parentPath) {
3611
- const relativePath = path.relative(parentPath, childPath);
3612
- return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
3766
+ const relativePath = path2.relative(parentPath, childPath);
3767
+ return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
3613
3768
  }
3614
3769
  async function removeIfPresent(fileSystem, filePath) {
3615
3770
  try {
@@ -3692,16 +3847,24 @@ function isAlreadyExistsError(error) {
3692
3847
 
3693
3848
  // ../auth-store/dist/keychain-store.js
3694
3849
  import { spawn } from "node:child_process";
3850
+ import { createHash } from "node:crypto";
3851
+ import { promises as nodeFs } from "node:fs";
3852
+ import { homedir as homedir2 } from "node:os";
3853
+ import path3 from "node:path";
3695
3854
  var SECURITY_CLI = "security";
3696
3855
  var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
3697
3856
  var KeychainStore = class {
3698
3857
  runCommand;
3699
3858
  service;
3700
3859
  account;
3860
+ lockFs;
3861
+ lockDirectory;
3701
3862
  constructor(input) {
3702
3863
  this.runCommand = input.runCommand ?? runSecurityCommand;
3703
3864
  this.service = input.service.trim();
3704
3865
  this.account = input.account.trim();
3866
+ this.lockFs = input.lock?.fs ?? nodeFs;
3867
+ this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
3705
3868
  if (this.service.length === 0) {
3706
3869
  throw new Error("Keychain service must not be empty");
3707
3870
  }
@@ -3719,6 +3882,10 @@ var KeychainStore = class {
3719
3882
  }
3720
3883
  throw createSecurityCliFailure("read secret from macOS Keychain", result);
3721
3884
  }
3885
+ async withLock(operation, options = {}) {
3886
+ const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
3887
+ return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
3888
+ }
3722
3889
  async set(value) {
3723
3890
  if (value.includes("\n") || value.includes("\r")) {
3724
3891
  throw new Error("Keychain secrets cannot contain line breaks");
@@ -3904,6 +4071,12 @@ var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
3904
4071
  var MAX_JS_DATE_MS = 864e13;
3905
4072
  function createAuthStoreSessionStore(options = {}) {
3906
4073
  return {
4074
+ async withLock(resource, operation, lockOptions) {
4075
+ const store = createResourceSecretStore(resource, options);
4076
+ if (store.withLock === void 0)
4077
+ throw new Error("OAuth secret-store backend does not support transaction locks");
4078
+ return store.withLock(operation, lockOptions);
4079
+ },
3907
4080
  async load(resource) {
3908
4081
  const store = createResourceSecretStore(resource, options);
3909
4082
  const value = await store.get();
@@ -3958,10 +4131,10 @@ function createAuthStoreClientStore(options) {
3958
4131
  function createNamedSecretStore(key2, options, defaults) {
3959
4132
  const hash = crypto.createHash("sha256").update(key2).digest("hex");
3960
4133
  const configuredFilePath = options.fileStore?.filePath;
3961
- const parsedFilePath = configuredFilePath === void 0 ? null : path2.parse(configuredFilePath);
4134
+ const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
3962
4135
  const fileStore = {
3963
4136
  ...options.fileStore,
3964
- filePath: parsedFilePath === null ? void 0 : path2.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
4137
+ filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
3965
4138
  salt: options.fileStore?.salt ?? defaults.salt,
3966
4139
  defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
3967
4140
  defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
@@ -4003,7 +4176,7 @@ function isStoredOAuthSession(value) {
4003
4176
  if (!isObjectRecord(value)) {
4004
4177
  return false;
4005
4178
  }
4006
- return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4179
+ return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && (getOwnEntry3(value, "refreshState") === void 0 || getOwnEntry3(value, "refreshState") === "pending" && getOwnEntry3(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4007
4180
  }
4008
4181
  function isStoredOAuthClient(value) {
4009
4182
  if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
@@ -4432,7 +4605,9 @@ var OAuthError = class extends Error {
4432
4605
  status;
4433
4606
  retryable;
4434
4607
  terminal;
4435
- constructor(shape, status) {
4608
+ /** True only when a complete OAuth error response establishes rejection. */
4609
+ outcomeKnown;
4610
+ constructor(shape, status, outcomeKnown = true) {
4436
4611
  super(shape.error_description ?? shape.error);
4437
4612
  this.name = "OAuthError";
4438
4613
  this.error = shape.error;
@@ -4443,6 +4618,7 @@ var OAuthError = class extends Error {
4443
4618
  this.status = status;
4444
4619
  this.retryable = isRetryableOAuthError(this);
4445
4620
  this.terminal = !this.retryable;
4621
+ this.outcomeKnown = outcomeKnown;
4446
4622
  }
4447
4623
  };
4448
4624
  function isRetryableOAuthError(error) {
@@ -4557,7 +4733,8 @@ async function readOAuthJsonObjectResponse(response, signal) {
4557
4733
  }
4558
4734
  const record2 = payload;
4559
4735
  if (!response.ok) {
4560
- throw new OAuthError(readOAuthError(record2, fallbackError.error), response.status);
4736
+ const error = getOwnEntry5(record2, "error");
4737
+ throw new OAuthError(readOAuthError(record2, fallbackError.error), response.status, typeof error === "string" && error.trim().length > 0);
4561
4738
  }
4562
4739
  return record2;
4563
4740
  }
@@ -4576,7 +4753,7 @@ function getOwnEntry5(record2, key2) {
4576
4753
  }
4577
4754
  function createFallbackOAuthError(status) {
4578
4755
  const error = status === 503 ? "temporarily_unavailable" : "server_error";
4579
- return new OAuthError({ error }, status);
4756
+ return new OAuthError({ error }, status, false);
4580
4757
  }
4581
4758
  function normalizeBearerTokenType(value) {
4582
4759
  if (typeof value !== "string") {
@@ -4585,6 +4762,55 @@ function normalizeBearerTokenType(value) {
4585
4762
  return value.toLowerCase() === "bearer" ? "Bearer" : null;
4586
4763
  }
4587
4764
 
4765
+ // ../mcp-oauth/dist/client/session-transaction.js
4766
+ var queues = /* @__PURE__ */ new WeakMap();
4767
+ async function withOAuthSessionTransaction(store, resource, operation, options = {}) {
4768
+ const timeoutMs = options.timeoutMs ?? 3e4;
4769
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
4770
+ throw new Error("sessionLockTimeoutMs must be an integer from 1 to 2147483647 milliseconds");
4771
+ options.signal?.throwIfAborted();
4772
+ const started = performance.now();
4773
+ const pending = queues.get(store) ?? /* @__PURE__ */ new Map();
4774
+ queues.set(store, pending);
4775
+ const previous = pending.get(resource) ?? Promise.resolve();
4776
+ let release;
4777
+ const current = new Promise((resolve) => {
4778
+ release = resolve;
4779
+ });
4780
+ const tail = previous.then(() => current);
4781
+ pending.set(resource, tail);
4782
+ try {
4783
+ let timer;
4784
+ let rejectWait;
4785
+ const waiting = new Promise((resolve, reject) => {
4786
+ rejectWait = reject;
4787
+ previous.then(resolve, reject);
4788
+ });
4789
+ const abort = () => rejectWait(options.signal?.reason);
4790
+ try {
4791
+ timer = setTimeout(() => rejectWait(new Error("Timed out waiting for OAuth session transaction lock")), timeoutMs);
4792
+ options.signal?.addEventListener("abort", abort, { once: true });
4793
+ if (options.signal?.aborted)
4794
+ abort();
4795
+ await waiting;
4796
+ } finally {
4797
+ clearTimeout(timer);
4798
+ options.signal?.removeEventListener("abort", abort);
4799
+ }
4800
+ options.signal?.throwIfAborted();
4801
+ return store.withLock === void 0 ? await operation() : await store.withLock(resource, operation, {
4802
+ signal: options.signal,
4803
+ timeoutMs: Math.max(0, timeoutMs - (performance.now() - started))
4804
+ });
4805
+ } finally {
4806
+ release();
4807
+ void tail.then(() => {
4808
+ if (pending.get(resource) === tail)
4809
+ pending.delete(resource);
4810
+ });
4811
+ }
4812
+ }
4813
+
4588
4814
  // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4589
4815
  var MAX_JS_DATE_MS3 = 864e13;
4590
4816
  function createOAuthClientProvider(options) {
@@ -4599,8 +4825,6 @@ function createDefaultOAuthClientProvider(options) {
4599
4825
  const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore);
4600
4826
  const now = options.now ?? Date.now;
4601
4827
  const registeredClients = /* @__PURE__ */ new Map();
4602
- const refreshPromises = /* @__PURE__ */ new Map();
4603
- const authorizationPromises = /* @__PURE__ */ new Map();
4604
4828
  if (options.initialGrant !== void 0) {
4605
4829
  let resource;
4606
4830
  try {
@@ -4634,13 +4858,14 @@ function createDefaultOAuthClientProvider(options) {
4634
4858
  const accessToken = session?.tokens?.accessToken;
4635
4859
  if (session === null && !initialGrantConsumed && initialGrant?.resource === requestUrl && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now)) {
4636
4860
  input.headers.set("Authorization", `Bearer ${initialGrant.tokens.accessToken}`);
4637
- return;
4861
+ return { ...initialGrant.tokens };
4638
4862
  }
4639
4863
  if (session === null || accessToken === void 0 || session.tokens === void 0 || isExpired(session.tokens, now)) {
4640
4864
  return;
4641
4865
  }
4642
4866
  assertRequestMatchesResource(requestUrl, session.resource);
4643
4867
  input.headers.set("Authorization", `Bearer ${accessToken}`);
4868
+ return { ...session.tokens };
4644
4869
  },
4645
4870
  async handleUnauthorized(input) {
4646
4871
  try {
@@ -4648,11 +4873,28 @@ function createDefaultOAuthClientProvider(options) {
4648
4873
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
4649
4874
  const resource = canonicalizeResourceIndicator(input.discovery.resource);
4650
4875
  assertRequestMatchesResource(requestUrl, resource);
4651
- const forceRefresh = (hasCachedAccessToken(await loadSession(resource)) || !initialGrantConsumed && initialGrant?.resource === resource) && input.challenge?.params.error === "invalid_token";
4876
+ const cached = await loadSession(resource);
4877
+ const currentTokens = cached?.tokens ?? (!initialGrantConsumed && initialGrant?.resource === resource ? initialGrant.tokens : void 0);
4878
+ let rejectedCurrentGrant = hasCachedAccessToken(cached) || !initialGrantConsumed && initialGrant?.resource === resource;
4879
+ let presentedTokens = input.presentedTokens;
4880
+ if (input.presentedTokens !== void 0) {
4881
+ rejectedCurrentGrant = false;
4882
+ if (input.presentedTokens !== null) {
4883
+ const presented = normalizeStoredTokens(input.presentedTokens);
4884
+ const header = input.requestHeaders?.get("Authorization") ?? "";
4885
+ const separator = header.indexOf(" ");
4886
+ if (presented === void 0 || header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented.accessToken)
4887
+ throw new Error("OAuth rejected-request provenance does not match its authorization header");
4888
+ presentedTokens = presented;
4889
+ rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens, presented);
4890
+ }
4891
+ }
4892
+ const challengeError = input.challenge?.params.error;
4893
+ const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || input.presentedTokens !== void 0 && challengeError === void 0);
4652
4894
  const session = await ensureAuthorizedSession(resource, {
4653
4895
  ...input.discovery,
4654
4896
  resource
4655
- }, input.fetch, true, forceRefresh, input.signal);
4897
+ }, input.fetch, true, forceRefresh, input.signal, presentedTokens);
4656
4898
  if (session?.tokens?.accessToken === void 0) {
4657
4899
  return { action: "fail" };
4658
4900
  }
@@ -4666,204 +4908,196 @@ function createDefaultOAuthClientProvider(options) {
4666
4908
  }
4667
4909
  }
4668
4910
  };
4669
- async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal) {
4911
+ async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal, rejectedTokens) {
4670
4912
  signal?.throwIfAborted();
4671
4913
  const canonicalResource = canonicalizeResourceIndicator(resource);
4672
- let session = await loadSession(canonicalResource);
4673
- if (session !== null && initialGrant?.resource === canonicalResource)
4674
- initialGrantConsumed = true;
4675
- signal?.throwIfAborted();
4676
- if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
4677
- throw new Error("OAuth discovery authorization-server issuer mismatch");
4678
- }
4679
- if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource || getOwnString2(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer || discovery !== void 0 && discovery.authorizationServer !== session.authorizationServer)) {
4680
- await clearSession(canonicalResource);
4681
- session = null;
4682
- }
4683
- if (session === null && discovery !== void 0 && !initialGrantConsumed && initialGrant?.resource === canonicalResource && initialGrant.tokens !== void 0 && initialGrant.client !== null) {
4684
- assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4685
- session = {
4686
- resource: canonicalResource,
4687
- authorizationServer: discovery.authorizationServer,
4688
- client: initialGrant.client,
4689
- tokens: initialGrant.tokens,
4690
- discovery: toStoredDiscovery(discovery)
4691
- };
4692
- await saveSession(canonicalResource, session);
4693
- initialGrantConsumed = true;
4914
+ return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
4915
+ let session = await loadSession(canonicalResource);
4916
+ if (session !== null && initialGrant?.resource === canonicalResource)
4917
+ initialGrantConsumed = true;
4694
4918
  signal?.throwIfAborted();
4695
- }
4696
- const sessionDiscovery = resolveDiscovery(discovery, session);
4697
- if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
4698
- return session;
4699
- }
4700
- if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
4701
- session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4702
- if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
4919
+ if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
4920
+ throw new Error("OAuth discovery authorization-server issuer mismatch");
4921
+ }
4922
+ if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource || getOwnString2(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer || discovery !== void 0 && discovery.authorizationServer !== session.authorizationServer)) {
4923
+ await clearSession(canonicalResource);
4924
+ session = null;
4925
+ }
4926
+ if (session === null && discovery !== void 0 && !initialGrantConsumed && initialGrant?.resource === canonicalResource && initialGrant.tokens !== void 0 && initialGrant.client !== null) {
4927
+ assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4928
+ session = {
4929
+ resource: canonicalResource,
4930
+ authorizationServer: discovery.authorizationServer,
4931
+ client: initialGrant.client,
4932
+ tokens: initialGrant.tokens,
4933
+ discovery: toStoredDiscovery(discovery)
4934
+ };
4935
+ await saveSession(canonicalResource, session);
4936
+ initialGrantConsumed = true;
4937
+ signal?.throwIfAborted();
4938
+ }
4939
+ if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
4940
+ forceRefresh = false;
4941
+ const sessionDiscovery = resolveDiscovery(discovery, session);
4942
+ if (session?.refreshState === "pending") {
4943
+ if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
4944
+ throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
4945
+ return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
4946
+ }
4947
+ if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
4703
4948
  return session;
4704
4949
  }
4705
- }
4706
- if (forceRefresh && session?.tokens !== void 0) {
4707
- session = clearSessionTokens(session);
4708
- await saveSession(canonicalResource, session);
4709
- }
4710
- if (!allowInteractive || sessionDiscovery === void 0) {
4711
- return session;
4712
- }
4713
- if (options.allowInteractive === false)
4714
- throw new Error("OAuth interactive authorization is disabled");
4715
- return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4950
+ if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
4951
+ session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4952
+ if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
4953
+ return session;
4954
+ }
4955
+ }
4956
+ if (forceRefresh && session?.tokens !== void 0) {
4957
+ session = clearSessionTokens(session);
4958
+ await saveSession(canonicalResource, session);
4959
+ }
4960
+ if (!allowInteractive || sessionDiscovery === void 0) {
4961
+ return session;
4962
+ }
4963
+ if (options.allowInteractive === false)
4964
+ throw new Error("OAuth interactive authorization is disabled");
4965
+ return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
4966
+ }, { signal, timeoutMs: options.sessionLockTimeoutMs });
4716
4967
  }
4717
4968
  async function refreshSession(resource, session, discovery, fetch2, signal) {
4718
4969
  signal?.throwIfAborted();
4719
4970
  assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4720
- const inFlight = refreshPromises.get(resource);
4721
- if (inFlight !== void 0) {
4722
- return inFlight;
4971
+ if (session.tokens?.refreshToken === void 0) {
4972
+ return session;
4723
4973
  }
4724
- const promise = (async () => {
4974
+ const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
4975
+ await saveSession(resource, pendingSession);
4976
+ signal?.throwIfAborted();
4977
+ let refreshAttempted = false;
4978
+ let refreshedTokens;
4979
+ while (true) {
4725
4980
  try {
4726
- if (session.tokens?.refreshToken === void 0) {
4727
- return session;
4728
- }
4729
- let refreshAttempted = false;
4730
- let refreshedTokens;
4731
- while (true) {
4732
- try {
4733
- refreshedTokens = await refreshAccessToken({
4734
- tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
4735
- clientId: session.client.clientId,
4736
- clientSecret: session.client.clientSecret,
4737
- refreshToken: session.tokens.refreshToken,
4738
- resource,
4739
- fetch: fetch2,
4740
- signal,
4741
- now
4742
- });
4743
- break;
4744
- } catch (error) {
4745
- signal?.throwIfAborted();
4746
- if (error instanceof OAuthError && error.error === "invalid_grant") {
4747
- const clearedSession = clearSessionTokens(session);
4748
- await saveSession(resource, clearedSession);
4749
- return clearedSession;
4750
- }
4751
- if (shouldReRegisterStoredDynamicClient(error, await loadRegisteredClient(discovery.authorizationServer), false)) {
4752
- await clearRegisteredClient(discovery.authorizationServer);
4753
- await clearSession(resource);
4754
- return null;
4755
- }
4756
- if (!refreshAttempted && isRetryableOAuthError(error)) {
4757
- refreshAttempted = true;
4758
- continue;
4759
- }
4760
- throw error;
4761
- }
4981
+ refreshedTokens = await refreshAccessToken({
4982
+ tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
4983
+ clientId: session.client.clientId,
4984
+ clientSecret: session.client.clientSecret,
4985
+ refreshToken: session.tokens.refreshToken,
4986
+ resource,
4987
+ fetch: fetch2,
4988
+ signal,
4989
+ now
4990
+ });
4991
+ break;
4992
+ } catch (error) {
4993
+ signal?.throwIfAborted();
4994
+ if (!(error instanceof OAuthError) || !error.outcomeKnown)
4995
+ throw error;
4996
+ if (error.error === "invalid_grant") {
4997
+ const clearedSession = clearSessionTokens(session);
4998
+ await saveSession(resource, clearedSession);
4999
+ return clearedSession;
5000
+ }
5001
+ if (shouldReRegisterStoredDynamicClient(error, await loadRegisteredClient(discovery.authorizationServer), false)) {
5002
+ await clearRegisteredClient(discovery.authorizationServer);
5003
+ await clearSession(resource);
5004
+ return null;
5005
+ }
5006
+ if (!refreshAttempted && isRetryableOAuthError(error)) {
5007
+ refreshAttempted = true;
5008
+ continue;
4762
5009
  }
4763
- const updatedSession = {
4764
- ...session,
4765
- tokens: {
4766
- ...refreshedTokens,
4767
- refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
4768
- },
4769
- discovery: toStoredDiscovery(discovery)
4770
- };
4771
- await saveSession(resource, updatedSession);
4772
- return updatedSession;
4773
- } finally {
4774
- refreshPromises.delete(resource);
5010
+ await saveSession(resource, session);
5011
+ throw error;
4775
5012
  }
4776
- })();
4777
- refreshPromises.set(resource, promise);
4778
- return promise;
5013
+ }
5014
+ const updatedSession = {
5015
+ ...session,
5016
+ tokens: {
5017
+ ...refreshedTokens,
5018
+ refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
5019
+ },
5020
+ discovery: toStoredDiscovery(discovery)
5021
+ };
5022
+ await saveSession(resource, updatedSession);
5023
+ return updatedSession;
4779
5024
  }
4780
5025
  async function authorizeSession(resource, existingSession, discovery, fetch2, signal) {
4781
5026
  signal?.throwIfAborted();
4782
- const inFlight = authorizationPromises.get(resource);
4783
- if (inFlight !== void 0) {
4784
- return inFlight;
4785
- }
4786
- const promise = (async () => {
4787
- assertS256PkceSupport(discovery.authorizationServerMetadata);
4788
- assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4789
- let currentSession = existingSession;
4790
- let transientRetryAttempted = false;
4791
- let reRegistrationAttempted = false;
4792
- while (true) {
4793
- const loopback = await createLoopbackAuthorizationSession({
4794
- openBrowser: options.browser.openBrowser,
4795
- readLine: options.browser.readLine,
4796
- createServer: options.browser.createServer,
4797
- landingPage: options.browser.landingPage,
4798
- redirectUri: options.browser.redirectUri,
4799
- signal: options.browser.signal === void 0 ? signal : signal === void 0 ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
4800
- timeoutMs: options.browser.timeoutMs
5027
+ assertS256PkceSupport(discovery.authorizationServerMetadata);
5028
+ assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
5029
+ let currentSession = existingSession;
5030
+ let transientRetryAttempted = false;
5031
+ let reRegistrationAttempted = false;
5032
+ while (true) {
5033
+ const loopback = await createLoopbackAuthorizationSession({
5034
+ openBrowser: options.browser.openBrowser,
5035
+ readLine: options.browser.readLine,
5036
+ createServer: options.browser.createServer,
5037
+ landingPage: options.browser.landingPage,
5038
+ redirectUri: options.browser.redirectUri,
5039
+ signal: options.browser.signal === void 0 ? signal : signal === void 0 ? options.browser.signal : AbortSignal.any([signal, options.browser.signal]),
5040
+ timeoutMs: options.browser.timeoutMs
5041
+ });
5042
+ let resolvedClient = null;
5043
+ try {
5044
+ resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
5045
+ const sessionWithoutTokens = {
5046
+ resource,
5047
+ authorizationServer: discovery.authorizationServer,
5048
+ client: resolvedClient.client,
5049
+ discovery: toStoredDiscovery(discovery)
5050
+ };
5051
+ await saveSession(resource, sessionWithoutTokens);
5052
+ const verifier = generateCodeVerifier();
5053
+ const challenge = generateCodeChallenge(verifier);
5054
+ const authorizationUrl = buildAuthorizationUrl({
5055
+ metadata: discovery.authorizationServerMetadata,
5056
+ resource,
5057
+ clientId: resolvedClient.client.clientId,
5058
+ redirectUri: loopback.redirectUri,
5059
+ codeChallenge: challenge,
5060
+ clientMetadata: getClientMetadata(options.client)
4801
5061
  });
4802
- let resolvedClient = null;
4803
- try {
4804
- resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
4805
- const sessionWithoutTokens = {
4806
- resource,
4807
- authorizationServer: discovery.authorizationServer,
4808
- client: resolvedClient.client,
4809
- discovery: toStoredDiscovery(discovery)
4810
- };
4811
- await saveSession(resource, sessionWithoutTokens);
4812
- const verifier = generateCodeVerifier();
4813
- const challenge = generateCodeChallenge(verifier);
4814
- const authorizationUrl = buildAuthorizationUrl({
4815
- metadata: discovery.authorizationServerMetadata,
4816
- resource,
4817
- clientId: resolvedClient.client.clientId,
4818
- redirectUri: loopback.redirectUri,
4819
- codeChallenge: challenge,
4820
- clientMetadata: getClientMetadata(options.client)
4821
- });
4822
- const code = await loopback.waitForCode(authorizationUrl);
4823
- const tokens = await exchangeAuthorizationCode({
4824
- tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
4825
- clientId: resolvedClient.client.clientId,
4826
- clientSecret: resolvedClient.client.clientSecret,
4827
- code,
4828
- codeVerifier: verifier,
4829
- redirectUri: loopback.redirectUri,
4830
- resource,
4831
- fetch: fetch2,
4832
- signal,
4833
- now
4834
- });
4835
- const session = {
4836
- ...sessionWithoutTokens,
4837
- tokens
4838
- };
4839
- await saveSession(resource, session);
4840
- return session;
4841
- } catch (error) {
4842
- signal?.throwIfAborted();
4843
- if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
4844
- reRegistrationAttempted = true;
4845
- await clearRegisteredClient(discovery.authorizationServer);
4846
- await clearSession(resource);
4847
- currentSession = null;
4848
- continue;
4849
- }
4850
- if (!transientRetryAttempted && isRetryableOAuthError(error)) {
4851
- transientRetryAttempted = true;
4852
- await clearSession(resource);
4853
- currentSession = null;
4854
- continue;
4855
- }
4856
- throw error;
4857
- } finally {
4858
- loopback.close();
5062
+ const code = await loopback.waitForCode(authorizationUrl);
5063
+ const tokens = await exchangeAuthorizationCode({
5064
+ tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5065
+ clientId: resolvedClient.client.clientId,
5066
+ clientSecret: resolvedClient.client.clientSecret,
5067
+ code,
5068
+ codeVerifier: verifier,
5069
+ redirectUri: loopback.redirectUri,
5070
+ resource,
5071
+ fetch: fetch2,
5072
+ signal,
5073
+ now
5074
+ });
5075
+ const session = {
5076
+ ...sessionWithoutTokens,
5077
+ tokens
5078
+ };
5079
+ await saveSession(resource, session);
5080
+ return session;
5081
+ } catch (error) {
5082
+ signal?.throwIfAborted();
5083
+ if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
5084
+ reRegistrationAttempted = true;
5085
+ await clearRegisteredClient(discovery.authorizationServer);
5086
+ await clearSession(resource);
5087
+ currentSession = null;
5088
+ continue;
5089
+ }
5090
+ if (!transientRetryAttempted && isRetryableOAuthError(error)) {
5091
+ transientRetryAttempted = true;
5092
+ await clearSession(resource);
5093
+ currentSession = null;
5094
+ continue;
4859
5095
  }
5096
+ throw error;
5097
+ } finally {
5098
+ loopback.close();
4860
5099
  }
4861
- })();
4862
- const finalPromise = promise.finally(() => {
4863
- authorizationPromises.delete(resource);
4864
- });
4865
- authorizationPromises.set(resource, finalPromise);
4866
- return finalPromise;
5100
+ }
4867
5101
  }
4868
5102
  async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
4869
5103
  parentSignal?.throwIfAborted();
@@ -5014,9 +5248,13 @@ function resolveDiscovery(discovery, session) {
5014
5248
  authorizationServerMetadata: metadata
5015
5249
  };
5016
5250
  }
5251
+ function sameTokenGrant(left, right) {
5252
+ return left.accessToken === right.accessToken && left.refreshToken === right.refreshToken && left.tokenType === right.tokenType && left.expiresAt === right.expiresAt && left.scope === right.scope;
5253
+ }
5017
5254
  function clearSessionTokens(session) {
5018
5255
  const nextSession = { ...session };
5019
5256
  delete nextSession.tokens;
5257
+ delete nextSession.refreshState;
5020
5258
  return nextSession;
5021
5259
  }
5022
5260
  function hasCachedAccessToken(session) {
@@ -5026,6 +5264,9 @@ function normalizeLoadedSession(session) {
5026
5264
  if (session === null) {
5027
5265
  return null;
5028
5266
  }
5267
+ const refreshState = getOwnEntry6(session, "refreshState");
5268
+ if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
5269
+ throw new Error("Stored OAuth refresh state is invalid");
5029
5270
  const client = normalizeStoredClient(getOwnEntry6(session, "client"));
5030
5271
  if (client === null) {
5031
5272
  return { ...session, client: { clientId: "" }, tokens: void 0 };
@@ -5298,7 +5539,7 @@ function getParameterHeaders(schema) {
5298
5539
  const names = /* @__PURE__ */ new Set();
5299
5540
  const ancestors = /* @__PURE__ */ new Set();
5300
5541
  let nodes = 0;
5301
- const visit = (value, path3, reachable, depth) => {
5542
+ const visit = (value, path5, reachable, depth) => {
5302
5543
  if (++nodes > 1e4 || depth > 64)
5303
5544
  throw new Error("MCP header schema traversal limit exceeded");
5304
5545
  if (typeof value === "boolean")
@@ -5309,7 +5550,7 @@ function getParameterHeaders(schema) {
5309
5550
  try {
5310
5551
  if (Object.prototype.hasOwnProperty.call(value, "x-mcp-header")) {
5311
5552
  const name = value["x-mcp-header"];
5312
- if (!reachable || path3.length === 0 || typeof name !== "string" || name.length === 0 || [...name].some((character) => {
5553
+ if (!reachable || path5.length === 0 || typeof name !== "string" || name.length === 0 || [...name].some((character) => {
5313
5554
  const code = character.charCodeAt(0);
5314
5555
  return !tokenPunctuation.has(character) && !(code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122);
5315
5556
  }))
@@ -5320,25 +5561,25 @@ function getParameterHeaders(schema) {
5320
5561
  if (names.has(key2))
5321
5562
  throw new Error("Duplicate x-mcp-header name");
5322
5563
  names.add(key2);
5323
- headers.push({ name: `Mcp-Param-${name}`, path: [...path3], type: value.type });
5564
+ headers.push({ name: `Mcp-Param-${name}`, path: [...path5], type: value.type });
5324
5565
  }
5325
5566
  for (const [keyword, child] of Object.entries(value)) {
5326
5567
  if (schemaMaps.has(keyword)) {
5327
5568
  if (!isRecord3(child))
5328
5569
  throw new Error("Invalid schema map");
5329
5570
  for (const [key2, nested] of Object.entries(child))
5330
- visit(nested, keyword === "properties" ? [...path3, key2] : path3, reachable && keyword === "properties", depth + 1);
5571
+ visit(nested, keyword === "properties" ? [...path5, key2] : path5, reachable && keyword === "properties", depth + 1);
5331
5572
  } else if (schemaArrays.has(keyword)) {
5332
5573
  if (!Array.isArray(child))
5333
5574
  throw new Error("Invalid schema array");
5334
5575
  for (const nested of child)
5335
- visit(nested, path3, false, depth + 1);
5576
+ visit(nested, path5, false, depth + 1);
5336
5577
  } else if (schemaChildren.has(keyword)) {
5337
5578
  if (Array.isArray(child))
5338
5579
  for (const nested of child)
5339
- visit(nested, path3, false, depth + 1);
5580
+ visit(nested, path5, false, depth + 1);
5340
5581
  else
5341
- visit(child, path3, false, depth + 1);
5582
+ visit(child, path5, false, depth + 1);
5342
5583
  }
5343
5584
  }
5344
5585
  } finally {
@@ -5536,8 +5777,8 @@ function authorizationServerMetadataLocations(issuer) {
5536
5777
  ];
5537
5778
  const issuerUrl = new URL(issuer);
5538
5779
  if (issuerUrl.pathname !== "/") {
5539
- const path3 = issuerUrl.pathname.endsWith("/") ? issuerUrl.pathname.slice(0, -1) : issuerUrl.pathname;
5540
- issuerUrl.pathname = `${path3}/.well-known/openid-configuration`;
5780
+ const path5 = issuerUrl.pathname.endsWith("/") ? issuerUrl.pathname.slice(0, -1) : issuerUrl.pathname;
5781
+ issuerUrl.pathname = `${path5}/.well-known/openid-configuration`;
5541
5782
  locations.push(issuerUrl.toString());
5542
5783
  }
5543
5784
  return locations;
@@ -6789,6 +7030,7 @@ var HttpTransport = class {
6789
7030
  oauthMetadataDiscovery;
6790
7031
  inFlightFetchAbortControllers = /* @__PURE__ */ new Set();
6791
7032
  inFlightOAuthAbortControllers = /* @__PURE__ */ new Set();
7033
+ oauthRequestTokens = /* @__PURE__ */ new WeakMap();
6792
7034
  openResponseReaders = /* @__PURE__ */ new Set();
6793
7035
  modernRequests = /* @__PURE__ */ new Map();
6794
7036
  modernMode = false;
@@ -7059,7 +7301,7 @@ var HttpTransport = class {
7059
7301
  }
7060
7302
  async authorizeRequestHeaders(headers, signal) {
7061
7303
  signal?.throwIfAborted();
7062
- await this.oauthProvider?.authorizeRequest?.({
7304
+ const tokens = await this.oauthProvider?.authorizeRequest?.({
7063
7305
  requestUrl: new URL(this.url),
7064
7306
  headers,
7065
7307
  signal,
@@ -7068,6 +7310,7 @@ var HttpTransport = class {
7068
7310
  signal: signal === void 0 ? init?.signal : init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
7069
7311
  })
7070
7312
  });
7313
+ if (tokens !== void 0) this.oauthRequestTokens.set(headers, { ...tokens });
7071
7314
  signal?.throwIfAborted();
7072
7315
  return headers;
7073
7316
  }
@@ -7211,7 +7454,7 @@ var HttpTransport = class {
7211
7454
  const message = responseBody.length === 0 ? `HTTP transport POST failed (${statusDescriptor})` : `HTTP transport POST failed (${statusDescriptor}): ${responseBody}`;
7212
7455
  throw new HttpTransportError(message, response.status, "POST");
7213
7456
  }
7214
- async maybeHandleUnauthorizedResponse(response, signal) {
7457
+ async maybeHandleUnauthorizedResponse(response, signal, requestHeaders, presentedTokens) {
7215
7458
  if (response.status !== 401 || this.oauthProvider === void 0) {
7216
7459
  return false;
7217
7460
  }
@@ -7232,6 +7475,8 @@ var HttpTransport = class {
7232
7475
  challenge,
7233
7476
  discovery,
7234
7477
  signal,
7478
+ requestHeaders: new Headers(requestHeaders),
7479
+ presentedTokens,
7235
7480
  fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
7236
7481
  ...init,
7237
7482
  signal: init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
@@ -7372,16 +7617,20 @@ var HttpTransport = class {
7372
7617
  const request = async () => {
7373
7618
  controller.signal.throwIfAborted();
7374
7619
  const headers = await input.createHeaders(controller.signal);
7620
+ const headerSnapshot = new Headers(headers);
7621
+ const tokens = this.oauthRequestTokens.get(headers) ?? null;
7375
7622
  controller.signal.throwIfAborted();
7376
- return this.fetchWithAbort(input.url ?? this.url, {
7623
+ const response = await this.fetchWithAbort(input.url ?? this.url, {
7377
7624
  method: input.method,
7378
7625
  headers,
7379
7626
  body: input.body
7380
7627
  }, controller);
7628
+ return { response, headers: headerSnapshot, tokens };
7381
7629
  };
7382
7630
  try {
7383
- let response = await request();
7384
- if (await this.maybeHandleUnauthorizedResponse(response, controller.signal)) response = await request();
7631
+ let attempt = await request();
7632
+ if (await this.maybeHandleUnauthorizedResponse(attempt.response, controller.signal, attempt.headers, attempt.tokens)) attempt = await request();
7633
+ const response = attempt.response;
7385
7634
  const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
7386
7635
  if (oauthError !== null) {
7387
7636
  void response.body?.cancel().catch(() => void 0);