@poe-platform/safe-js 0.1.713 → 0.1.715

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.
@@ -85,11 +85,15 @@ var HttpResponseMessages = class {
85
85
  };
86
86
 
87
87
  // packages/toolcraft-schema/src/json.ts
88
- function isJsonValue(value) {
88
+ function isJsonValue(value, options = {}) {
89
+ const maxNodes = options.maxNodes ?? 1e4;
90
+ const maxDepth = options.maxDepth ?? 64;
91
+ if (!Number.isSafeInteger(maxNodes) || maxNodes < 1) throw new Error("maxNodes must be a positive safe integer");
92
+ if (!Number.isSafeInteger(maxDepth) || maxDepth < 0 || maxDepth > 256) throw new Error("maxDepth must be an integer between 0 and 256");
89
93
  const ancestors = /* @__PURE__ */ new Set();
90
94
  let nodes = 0;
91
95
  const visit = (item, depth) => {
92
- if (++nodes > 1e4 || depth > 64) return false;
96
+ if (++nodes > maxNodes || depth > maxDepth) return false;
93
97
  if (item === null || typeof item === "string" || typeof item === "boolean") return true;
94
98
  if (typeof item === "number") return Number.isFinite(item);
95
99
  if (typeof item !== "object" || ancestors.has(item)) return false;
@@ -108,7 +112,7 @@ function isJsonValue(value) {
108
112
  ancestors.add(item);
109
113
  let valid = true;
110
114
  if (Array.isArray(item)) {
111
- if (item.length > 1e4) valid = false;
115
+ if (item.length > maxNodes) valid = false;
112
116
  else for (let index = 0; index < item.length; index++) {
113
117
  const property = Object.getOwnPropertyDescriptor(item, String(index));
114
118
  if (property === void 0 || !("value" in property) || !visit(property.value, depth + 1)) {
@@ -198,8 +202,8 @@ function receivedType(value) {
198
202
  }
199
203
  return typeof value;
200
204
  }
201
- function issue(path4, expected, value, message, keyword = keywordFor(expected)) {
202
- return { path: path4, expected, received: receivedType(value), message, keyword };
205
+ function issue(path6, expected, value, message, keyword = keywordFor(expected)) {
206
+ return { path: path6, expected, received: receivedType(value), message, keyword };
203
207
  }
204
208
  function keywordFor(expected) {
205
209
  if (["null", "boolean", "object", "array", "number", "integer", "string"].includes(expected)) {
@@ -1030,41 +1034,41 @@ function evaluateArrayApplicators(graph, node, schema, value, context) {
1030
1034
  }
1031
1035
  return results;
1032
1036
  }
1033
- function evaluateValidationKeywords(node, schema, value, path4) {
1037
+ function evaluateValidationKeywords(node, schema, value, path6) {
1034
1038
  const results = [];
1035
1039
  const types = typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : [];
1036
1040
  if (types.length > 0 && !types.some((type) => typeof type === "string" && typeMatches(type, value))) {
1037
- results.push(invalidResult(issue(path4, types.join(","), value, `must be ${types.join(",")}`)));
1041
+ results.push(invalidResult(issue(path6, types.join(","), value, `must be ${types.join(",")}`)));
1038
1042
  return results;
1039
1043
  }
1040
1044
  if (schema.const !== void 0 && !deepEqual(schema.const, value)) {
1041
- results.push(invalidResult(issue(path4, "const", value, "must be equal to constant")));
1045
+ results.push(invalidResult(issue(path6, "const", value, "must be equal to constant")));
1042
1046
  }
1043
1047
  if (Array.isArray(schema.enum) && !schema.enum.some((entry) => deepEqual(entry, value))) {
1044
1048
  results.push(
1045
- invalidResult(issue(path4, "enum", value, "must be equal to one of the allowed values"))
1049
+ invalidResult(issue(path6, "enum", value, "must be equal to one of the allowed values"))
1046
1050
  );
1047
1051
  }
1048
1052
  if (typeof value === "number" && Number.isFinite(value)) {
1049
- evaluateNumberKeywords(schema, value, path4, results);
1053
+ evaluateNumberKeywords(schema, value, path6, results);
1050
1054
  }
1051
1055
  if (typeof value === "string") {
1052
- evaluateStringKeywords(schema, value, path4, results);
1056
+ evaluateStringKeywords(schema, value, path6, results);
1053
1057
  }
1054
1058
  if (Array.isArray(value)) {
1055
- evaluateArrayKeywords(schema, value, path4, results);
1059
+ evaluateArrayKeywords(schema, value, path6, results);
1056
1060
  }
1057
1061
  if (isObject(value)) {
1058
- evaluateObjectKeywords(node, schema, value, path4, results);
1062
+ evaluateObjectKeywords(node, schema, value, path6, results);
1059
1063
  }
1060
1064
  return results;
1061
1065
  }
1062
- function evaluateNumberKeywords(schema, value, path4, results) {
1066
+ function evaluateNumberKeywords(schema, value, path6, results) {
1063
1067
  if (typeof schema.multipleOf === "number" && !isMultipleOf(value, schema.multipleOf)) {
1064
1068
  results.push(
1065
1069
  invalidResult(
1066
1070
  issue(
1067
- path4,
1071
+ path6,
1068
1072
  `multiple of ${schema.multipleOf}`,
1069
1073
  value,
1070
1074
  `must be multiple of ${schema.multipleOf}`
@@ -1074,36 +1078,36 @@ function evaluateNumberKeywords(schema, value, path4, results) {
1074
1078
  }
1075
1079
  if (typeof schema.maximum === "number" && value > schema.maximum) {
1076
1080
  results.push(
1077
- invalidResult(issue(path4, `<= ${schema.maximum}`, value, `must be <= ${schema.maximum}`))
1081
+ invalidResult(issue(path6, `<= ${schema.maximum}`, value, `must be <= ${schema.maximum}`))
1078
1082
  );
1079
1083
  }
1080
1084
  if (typeof schema.minimum === "number" && value < schema.minimum) {
1081
1085
  results.push(
1082
- invalidResult(issue(path4, `>= ${schema.minimum}`, value, `must be >= ${schema.minimum}`))
1086
+ invalidResult(issue(path6, `>= ${schema.minimum}`, value, `must be >= ${schema.minimum}`))
1083
1087
  );
1084
1088
  }
1085
1089
  if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
1086
1090
  results.push(
1087
1091
  invalidResult(
1088
- issue(path4, `< ${schema.exclusiveMaximum}`, value, `must be < ${schema.exclusiveMaximum}`)
1092
+ issue(path6, `< ${schema.exclusiveMaximum}`, value, `must be < ${schema.exclusiveMaximum}`)
1089
1093
  )
1090
1094
  );
1091
1095
  }
1092
1096
  if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) {
1093
1097
  results.push(
1094
1098
  invalidResult(
1095
- issue(path4, `> ${schema.exclusiveMinimum}`, value, `must be > ${schema.exclusiveMinimum}`)
1099
+ issue(path6, `> ${schema.exclusiveMinimum}`, value, `must be > ${schema.exclusiveMinimum}`)
1096
1100
  )
1097
1101
  );
1098
1102
  }
1099
1103
  }
1100
- function evaluateStringKeywords(schema, value, path4, results) {
1104
+ function evaluateStringKeywords(schema, value, path6, results) {
1101
1105
  const length = unicodeLength(value);
1102
1106
  if (typeof schema.maxLength === "number" && length > schema.maxLength) {
1103
1107
  results.push(
1104
1108
  invalidResult(
1105
1109
  issue(
1106
- path4,
1110
+ path6,
1107
1111
  `length <= ${schema.maxLength}`,
1108
1112
  value,
1109
1113
  `must NOT have more than ${schema.maxLength} characters`
@@ -1115,7 +1119,7 @@ function evaluateStringKeywords(schema, value, path4, results) {
1115
1119
  results.push(
1116
1120
  invalidResult(
1117
1121
  issue(
1118
- path4,
1122
+ path6,
1119
1123
  `length >= ${schema.minLength}`,
1120
1124
  value,
1121
1125
  `must NOT have fewer than ${schema.minLength} characters`
@@ -1126,17 +1130,17 @@ function evaluateStringKeywords(schema, value, path4, results) {
1126
1130
  if (typeof schema.pattern === "string" && !new RegExp(schema.pattern, "u").test(value)) {
1127
1131
  results.push(
1128
1132
  invalidResult(
1129
- issue(path4, `pattern ${schema.pattern}`, value, `must match pattern ${schema.pattern}`)
1133
+ issue(path6, `pattern ${schema.pattern}`, value, `must match pattern ${schema.pattern}`)
1130
1134
  )
1131
1135
  );
1132
1136
  }
1133
1137
  }
1134
- function evaluateArrayKeywords(schema, value, path4, results) {
1138
+ function evaluateArrayKeywords(schema, value, path6, results) {
1135
1139
  if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
1136
1140
  results.push(
1137
1141
  invalidResult(
1138
1142
  issue(
1139
- path4,
1143
+ path6,
1140
1144
  `items <= ${schema.maxItems}`,
1141
1145
  value,
1142
1146
  `must NOT have more than ${schema.maxItems} items`
@@ -1148,7 +1152,7 @@ function evaluateArrayKeywords(schema, value, path4, results) {
1148
1152
  results.push(
1149
1153
  invalidResult(
1150
1154
  issue(
1151
- path4,
1155
+ path6,
1152
1156
  `items >= ${schema.minItems}`,
1153
1157
  value,
1154
1158
  `must NOT have fewer than ${schema.minItems} items`
@@ -1161,7 +1165,7 @@ function evaluateArrayKeywords(schema, value, path4, results) {
1161
1165
  for (let right = left + 1; right < value.length; right += 1) {
1162
1166
  if (deepEqual(value[left], value[right])) {
1163
1167
  results.push(
1164
- invalidResult(issue(path4, "unique items", value, "must NOT have duplicate items"))
1168
+ invalidResult(issue(path6, "unique items", value, "must NOT have duplicate items"))
1165
1169
  );
1166
1170
  return;
1167
1171
  }
@@ -1169,13 +1173,13 @@ function evaluateArrayKeywords(schema, value, path4, results) {
1169
1173
  }
1170
1174
  }
1171
1175
  }
1172
- function evaluateObjectKeywords(node, schema, value, path4, results) {
1176
+ function evaluateObjectKeywords(node, schema, value, path6, results) {
1173
1177
  const keys = Object.keys(value);
1174
1178
  if (typeof schema.maxProperties === "number" && keys.length > schema.maxProperties) {
1175
1179
  results.push(
1176
1180
  invalidResult(
1177
1181
  issue(
1178
- path4,
1182
+ path6,
1179
1183
  `properties <= ${schema.maxProperties}`,
1180
1184
  value,
1181
1185
  `must NOT have more than ${schema.maxProperties} properties`
@@ -1187,7 +1191,7 @@ function evaluateObjectKeywords(node, schema, value, path4, results) {
1187
1191
  results.push(
1188
1192
  invalidResult(
1189
1193
  issue(
1190
- path4,
1194
+ path6,
1191
1195
  `properties >= ${schema.minProperties}`,
1192
1196
  value,
1193
1197
  `must NOT have fewer than ${schema.minProperties} properties`
@@ -1200,7 +1204,7 @@ function evaluateObjectKeywords(node, schema, value, path4, results) {
1200
1204
  if (typeof key2 === "string" && !Object.prototype.hasOwnProperty.call(value, key2)) {
1201
1205
  results.push(
1202
1206
  invalidResult(
1203
- issue([...path4, key2], "required", void 0, `must have required property '${key2}'`)
1207
+ issue([...path6, key2], "required", void 0, `must have required property '${key2}'`)
1204
1208
  )
1205
1209
  );
1206
1210
  }
@@ -1209,24 +1213,24 @@ function evaluateObjectKeywords(node, schema, value, path4, results) {
1209
1213
  const dependentRequired = isObject(schema.dependentRequired) ? schema.dependentRequired : {};
1210
1214
  for (const [key2, dependencies] of Object.entries(dependentRequired)) {
1211
1215
  if (Object.prototype.hasOwnProperty.call(value, key2) && Array.isArray(dependencies)) {
1212
- addMissingDependencies(value, dependencies, path4, results);
1216
+ addMissingDependencies(value, dependencies, path6, results);
1213
1217
  }
1214
1218
  }
1215
1219
  if (node.dialect === "draft7" && isObject(schema.dependencies)) {
1216
1220
  for (const [key2, dependencies] of Object.entries(schema.dependencies)) {
1217
1221
  if (Object.prototype.hasOwnProperty.call(value, key2) && Array.isArray(dependencies)) {
1218
- addMissingDependencies(value, dependencies, path4, results);
1222
+ addMissingDependencies(value, dependencies, path6, results);
1219
1223
  }
1220
1224
  }
1221
1225
  }
1222
1226
  }
1223
- function addMissingDependencies(value, dependencies, path4, results) {
1227
+ function addMissingDependencies(value, dependencies, path6, results) {
1224
1228
  for (const dependency of dependencies) {
1225
1229
  if (typeof dependency === "string" && !Object.prototype.hasOwnProperty.call(value, dependency)) {
1226
1230
  results.push(
1227
1231
  invalidResult(
1228
1232
  issue(
1229
- [...path4, dependency],
1233
+ [...path6, dependency],
1230
1234
  "dependency",
1231
1235
  void 0,
1232
1236
  `must have property '${dependency}'`
@@ -3515,21 +3519,690 @@ var SubscriptionManager = class {
3515
3519
  }
3516
3520
  };
3517
3521
 
3518
- // packages/mcp-oauth/src/client/auth-store-session-store.ts
3522
+ // packages/mcp-oauth/src/client/persistence-options.ts
3523
+ function snapshotOAuthPersistenceOptions(options) {
3524
+ const file = options.fileStore, keychain = options.keychainStore, lock = keychain?.lock;
3525
+ return {
3526
+ ...options,
3527
+ backend: options.backend,
3528
+ env: options.env,
3529
+ platform: options.platform,
3530
+ backendEnvVar: options.backendEnvVar,
3531
+ ...file === void 0 ? {} : { fileStore: {
3532
+ ...file,
3533
+ fs: file.fs,
3534
+ filePath: file.filePath,
3535
+ salt: file.salt,
3536
+ defaultDirectory: file.defaultDirectory,
3537
+ defaultFileName: file.defaultFileName,
3538
+ throwOnInvalidDocument: file.throwOnInvalidDocument,
3539
+ getHomeDirectory: file.getHomeDirectory?.bind(file),
3540
+ getMachineIdentity: file.getMachineIdentity?.bind(file),
3541
+ getRandomBytes: file.getRandomBytes?.bind(file)
3542
+ } },
3543
+ ...keychain === void 0 ? {} : { keychainStore: {
3544
+ ...keychain,
3545
+ service: keychain.service,
3546
+ account: keychain.account,
3547
+ runCommand: keychain.runCommand?.bind(keychain),
3548
+ ...lock === void 0 ? {} : { lock: { ...lock, fs: lock.fs, directory: lock.directory } }
3549
+ } }
3550
+ };
3551
+ }
3552
+
3553
+ // packages/mcp-oauth/src/client/scope.ts
3554
+ function normalizeOAuthScope(scope) {
3555
+ if (scope === void 0) return void 0;
3556
+ if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
3557
+ throw new Error("Invalid OAuth scope syntax");
3558
+ const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
3559
+ return normalized || void 0;
3560
+ }
3561
+
3562
+ // packages/mcp-oauth/src/client/cancellable-operation.ts
3563
+ async function waitForOAuthOperation(operation, signal) {
3564
+ if (signal === void 0) return operation;
3565
+ let abort;
3566
+ try {
3567
+ return await new Promise((resolve, reject) => {
3568
+ abort = () => reject(signal.reason);
3569
+ signal.addEventListener("abort", abort, { once: true });
3570
+ operation.then(resolve, reject);
3571
+ if (signal.aborted) abort();
3572
+ });
3573
+ } finally {
3574
+ signal.removeEventListener("abort", abort);
3575
+ }
3576
+ }
3577
+
3578
+ // packages/mcp-oauth/src/client/bounded-json.ts
3579
+ function copyBoundedOAuthJson(value, message) {
3580
+ const invalid = () => new Error(message);
3581
+ let nodes = 0;
3582
+ function copy(input, depth) {
3583
+ if (++nodes > 2e4 || depth > 64) throw invalid();
3584
+ if (input === null || typeof input === "boolean" || typeof input === "string") return input;
3585
+ if (typeof input === "number" && Number.isFinite(input)) return input;
3586
+ if (typeof input !== "object" || input === null) throw invalid();
3587
+ const descriptors = Object.getOwnPropertyDescriptors(input);
3588
+ if (Array.isArray(input)) {
3589
+ const length = descriptors.length?.value;
3590
+ if (length > 2e4) throw invalid();
3591
+ const result2 = [];
3592
+ for (let index = 0; index < length; index++) {
3593
+ const descriptor = descriptors[String(index)];
3594
+ if (descriptor === void 0 || !Object.hasOwn(descriptor, "value")) throw invalid();
3595
+ result2.push(copy(descriptor.value, depth + 1));
3596
+ }
3597
+ return result2;
3598
+ }
3599
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) throw invalid();
3600
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
3601
+ if (!Object.hasOwn(descriptor, "value")) throw invalid();
3602
+ return [key2, copy(descriptor.value, depth + 1)];
3603
+ }));
3604
+ }
3605
+ let result;
3606
+ try {
3607
+ result = copy(value, 0);
3608
+ } catch {
3609
+ throw invalid();
3610
+ }
3611
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > 64 * 1024) throw invalid();
3612
+ return result;
3613
+ }
3614
+
3615
+ // packages/mcp-oauth/src/client/loopback-authorization.ts
3616
+ import http from "node:http";
3617
+
3618
+ // packages/mcp-oauth/src/client/authorization-state.ts
3519
3619
  import crypto from "node:crypto";
3520
- import path2 from "node:path";
3620
+ function createAuthorizationState(input) {
3621
+ const payload = {
3622
+ v: 1,
3623
+ n: crypto.randomBytes(16).toString("base64url"),
3624
+ i: input.issuer,
3625
+ r: input.requireIssuer
3626
+ };
3627
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
3628
+ }
3629
+ function parseAuthorizationState(value) {
3630
+ if (value === null || value.length === 0) {
3631
+ return null;
3632
+ }
3633
+ try {
3634
+ const decoded = Buffer.from(value, "base64url").toString("utf8");
3635
+ const parsed = JSON.parse(decoded);
3636
+ if (!isObjectRecord(parsed)) {
3637
+ return null;
3638
+ }
3639
+ const version = getOwnEntry(parsed, "v");
3640
+ const nonce = getOwnEntry(parsed, "n");
3641
+ const issuer = getOwnEntry(parsed, "i");
3642
+ const requireIssuer = getOwnEntry(parsed, "r");
3643
+ if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
3644
+ return null;
3645
+ }
3646
+ return {
3647
+ issuer,
3648
+ requireIssuer
3649
+ };
3650
+ } catch {
3651
+ return null;
3652
+ }
3653
+ }
3654
+ function isObjectRecord(value) {
3655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3656
+ }
3657
+ function getOwnEntry(record2, key2) {
3658
+ return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3659
+ }
3660
+
3661
+ // packages/mcp-oauth/src/client/loopback-authorization.ts
3662
+ var authorizationErrorBrand = /* @__PURE__ */ Symbol.for("poe-platform.mcp-oauth.OAuthAuthorizationError");
3663
+ var oauthCallbackParameters = ["code", "state", "iss", "error", "error_description", "error_uri"];
3664
+ function snapshotLoopbackAuthorizationOptions(options) {
3665
+ return {
3666
+ ...options,
3667
+ openBrowser: options.openBrowser?.bind(options),
3668
+ readLine: options.readLine?.bind(options),
3669
+ createServer: options.createServer?.bind(options),
3670
+ callbackPath: options.callbackPath,
3671
+ redirectUri: options.redirectUri,
3672
+ signal: options.signal,
3673
+ timeoutMs: options.timeoutMs,
3674
+ landingPage: options.landingPage === void 0 ? void 0 : { ...options.landingPage, title: options.landingPage.title, body: options.landingPage.body }
3675
+ };
3676
+ }
3677
+ var OAuthAuthorizationError = class extends Error {
3678
+ constructor(error, errorDescription) {
3679
+ super(`OAuth authorization failed: ${error} \u2014 ${errorDescription}`);
3680
+ this.error = error;
3681
+ this.errorDescription = errorDescription;
3682
+ this.name = "OAuthAuthorizationError";
3683
+ Object.defineProperty(this, authorizationErrorBrand, { value: true });
3684
+ }
3685
+ error;
3686
+ errorDescription;
3687
+ /** Recognize errors from separately bundled copies of this package. */
3688
+ static is(value) {
3689
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, authorizationErrorBrand)?.value === true;
3690
+ }
3691
+ };
3692
+ async function createLoopbackAuthorizationSession(options = {}) {
3693
+ options = snapshotLoopbackAuthorizationOptions(options);
3694
+ options.signal?.throwIfAborted();
3695
+ const timeoutMs = options.timeoutMs ?? 12e4;
3696
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
3697
+ throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
3698
+ const target = loopbackTarget(options);
3699
+ const server = options.createServer ? options.createServer() : http.createServer();
3700
+ const controller = new AbortController();
3701
+ let closed = false;
3702
+ let used = false;
3703
+ const callerAbort = () => controller.abort(options.signal?.reason);
3704
+ const teardown = () => {
3705
+ if (closed) return;
3706
+ closed = true;
3707
+ clearTimeout(timer);
3708
+ options.signal?.removeEventListener("abort", callerAbort);
3709
+ server.closeAllConnections?.();
3710
+ server.close();
3711
+ };
3712
+ const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
3713
+ timer.unref?.();
3714
+ controller.signal.addEventListener("abort", teardown, { once: true });
3715
+ options.signal?.addEventListener("abort", callerAbort, { once: true });
3716
+ if (options.signal?.aborted) callerAbort();
3717
+ let port;
3718
+ try {
3719
+ port = await startServer(server, target.port, target.host, controller.signal);
3720
+ } catch (error) {
3721
+ controller.abort(error);
3722
+ throw error;
3723
+ }
3724
+ const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
3725
+ return {
3726
+ redirectUri,
3727
+ async waitForCode(authorizationUrl) {
3728
+ controller.signal.throwIfAborted();
3729
+ if (used) throw new Error("OAuth authorization session has already been used");
3730
+ used = true;
3731
+ try {
3732
+ return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
3733
+ } finally {
3734
+ clearTimeout(timer);
3735
+ }
3736
+ },
3737
+ close() {
3738
+ controller.abort(new Error("OAuth authorization session closed"));
3739
+ }
3740
+ };
3741
+ }
3742
+ function loopbackTarget(options) {
3743
+ if (options.redirectUri !== void 0) {
3744
+ let url;
3745
+ try {
3746
+ url = new URL(options.redirectUri);
3747
+ } catch (cause) {
3748
+ throw new Error("Invalid OAuth loopback redirect URI", { cause });
3749
+ }
3750
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.href.includes("#") || url.port === "0" || oauthCallbackParameters.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
3751
+ throw new Error("Invalid OAuth loopback redirect URI");
3752
+ return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
3753
+ }
3754
+ const callbackPath = options.callbackPath ?? "/callback";
3755
+ const parsed = new URL(callbackPath, "http://127.0.0.1");
3756
+ if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
3757
+ throw new Error("Invalid OAuth loopback callback path");
3758
+ return { port: 0, host: "127.0.0.1", callbackPath };
3759
+ }
3760
+ async function startServer(server, port, host, signal) {
3761
+ signal.throwIfAborted();
3762
+ return new Promise((resolve, reject) => {
3763
+ const cleanup = () => {
3764
+ server.off("error", handleError);
3765
+ signal.removeEventListener("abort", aborted);
3766
+ };
3767
+ const handleError = (error) => {
3768
+ cleanup();
3769
+ reject(error);
3770
+ };
3771
+ const aborted = () => {
3772
+ cleanup();
3773
+ reject(signal.reason);
3774
+ };
3775
+ server.once("error", handleError);
3776
+ signal.addEventListener("abort", aborted, { once: true });
3777
+ try {
3778
+ server.listen(port, host, () => {
3779
+ cleanup();
3780
+ if (signal.aborted) {
3781
+ server.close();
3782
+ reject(signal.reason);
3783
+ return;
3784
+ }
3785
+ const address = server.address();
3786
+ if (address === null || typeof address === "string") {
3787
+ reject(new Error("OAuth listener has no TCP address"));
3788
+ return;
3789
+ }
3790
+ resolve(address.port);
3791
+ });
3792
+ } catch (error) {
3793
+ cleanup();
3794
+ reject(error);
3795
+ }
3796
+ });
3797
+ }
3798
+ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
3799
+ signal.throwIfAborted();
3800
+ const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
3801
+ return new Promise((resolve, reject) => {
3802
+ let settled = false;
3803
+ const settle = (fn) => {
3804
+ if (settled) return;
3805
+ settled = true;
3806
+ server.off("request", request);
3807
+ signal.removeEventListener("abort", aborted);
3808
+ fn();
3809
+ };
3810
+ const aborted = () => settle(() => reject(signal.reason));
3811
+ const request = (req, res) => {
3812
+ let url;
3813
+ try {
3814
+ url = new URL(req.url ?? "/", "http://127.0.0.1");
3815
+ } catch {
3816
+ res.writeHead(400);
3817
+ res.end("Invalid callback URL");
3818
+ return;
3819
+ }
3820
+ if (url.pathname !== callbackPath) {
3821
+ res.writeHead(404);
3822
+ res.end("Not found");
3823
+ return;
3824
+ }
3825
+ try {
3826
+ const callbackParameters = readAuthorizationCallbackParameters(url);
3827
+ validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3828
+ if (callbackParameters.error !== null)
3829
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3830
+ const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3831
+ res.writeHead(200, { "Content-Type": "text/html" });
3832
+ res.end(buildSuccessPage(options.landingPage));
3833
+ settle(() => resolve(code));
3834
+ } catch (error) {
3835
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8", "X-Content-Type-Options": "nosniff" });
3836
+ res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
3837
+ settle(() => reject(error));
3838
+ }
3839
+ };
3840
+ server.on("request", request);
3841
+ signal.addEventListener("abort", aborted, { once: true });
3842
+ if (options.readLine !== void 0) {
3843
+ void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
3844
+ if (settled) return;
3845
+ const callbackParameters = extractCallbackParametersFromInput(input);
3846
+ if (callbackParameters === null) throw new Error("OAuth callback missing authorization code");
3847
+ validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3848
+ if (callbackParameters.error !== null)
3849
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3850
+ const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3851
+ settle(() => resolve(code));
3852
+ }).catch((error) => settle(() => reject(error)));
3853
+ }
3854
+ if (options.openBrowser !== void 0) {
3855
+ void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
3856
+ }
3857
+ });
3858
+ }
3859
+ function extractCallbackParametersFromInput(input) {
3860
+ const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
3861
+ if (trimmed.length === 0) {
3862
+ return null;
3863
+ }
3864
+ let url;
3865
+ try {
3866
+ url = new URL(trimmed);
3867
+ } catch {
3868
+ return {
3869
+ code: trimmed,
3870
+ error: null,
3871
+ errorDescription: null,
3872
+ state: null,
3873
+ iss: null
3874
+ };
3875
+ }
3876
+ return readAuthorizationCallbackParameters(url);
3877
+ }
3878
+ function readAuthorizationCallbackParameters(url) {
3879
+ for (const parameter of oauthCallbackParameters) {
3880
+ if (url.searchParams.getAll(parameter).length > 1)
3881
+ throw new Error(`OAuth callback parameter '${parameter}' must occur only once`);
3882
+ }
3883
+ return {
3884
+ code: url.searchParams.get("code"),
3885
+ error: url.searchParams.get("error"),
3886
+ errorDescription: url.searchParams.get("error_description"),
3887
+ state: url.searchParams.get("state"),
3888
+ iss: url.searchParams.get("iss")
3889
+ };
3890
+ }
3891
+ function readExpectedAuthorizationCallback(authorizationUrl) {
3892
+ const url = new URL(authorizationUrl);
3893
+ const state = url.searchParams.get("state");
3894
+ const parsedState = parseAuthorizationState(state);
3895
+ return {
3896
+ state,
3897
+ issuer: parsedState?.issuer ?? null,
3898
+ requireIssuer: parsedState?.requireIssuer ?? false
3899
+ };
3900
+ }
3901
+ function validateAuthorizationCallbackParameters(callback, expected) {
3902
+ validateAuthorizationCallbackBinding(callback, expected);
3903
+ if (callback.code === null || callback.code.length === 0) {
3904
+ throw new Error("OAuth callback missing authorization code");
3905
+ }
3906
+ return callback.code;
3907
+ }
3908
+ function validateAuthorizationCallbackBinding(callback, expected) {
3909
+ if (expected.state !== null) {
3910
+ if (callback.state === null || callback.state.length === 0) {
3911
+ throw new Error("OAuth callback missing state");
3912
+ }
3913
+ if (callback.state !== expected.state) {
3914
+ throw new Error("OAuth callback state mismatch");
3915
+ }
3916
+ }
3917
+ if (expected.requireIssuer) {
3918
+ if (callback.iss === null || callback.iss.length === 0) {
3919
+ throw new Error("OAuth callback missing issuer");
3920
+ }
3921
+ }
3922
+ if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
3923
+ throw new Error("OAuth callback issuer mismatch");
3924
+ }
3925
+ }
3926
+ function escapeHtml(text) {
3927
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3928
+ }
3929
+ function buildSuccessPage(landingPage) {
3930
+ const title = landingPage?.title ?? "Connected";
3931
+ const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
3932
+ return [
3933
+ "<!DOCTYPE html>",
3934
+ `<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
3935
+ '<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
3936
+ '<div style="text-align:center">',
3937
+ `<h1>${escapeHtml(title)}</h1>`,
3938
+ `<p style="color:#666">${escapeHtml(body)}</p>`,
3939
+ "</div></body></html>"
3940
+ ].join("");
3941
+ }
3942
+
3943
+ // packages/mcp-oauth/src/client/token-auth-method.ts
3944
+ function normalizeOAuthTokenEndpointAuthMethod(value) {
3945
+ if (value === void 0 || value === null) return void 0;
3946
+ if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
3947
+ throw new Error("Unsupported OAuth token endpoint authentication method");
3948
+ return value;
3949
+ }
3950
+
3951
+ // packages/mcp-oauth/src/client/client-registration.ts
3952
+ function parseOAuthClientRegistration(value) {
3953
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
3954
+ const result = copyBoundedOAuthJson(value, "Invalid OAuth client registration metadata");
3955
+ if (typeof result !== "object" || result === null || Array.isArray(result)) throw invalid();
3956
+ const record2 = result;
3957
+ if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
3958
+ throw new Error("OAuth client registration response missing client_id");
3959
+ for (const key2 of [
3960
+ "client_id",
3961
+ "client_secret",
3962
+ "token_endpoint_auth_method",
3963
+ "application_type",
3964
+ "client_name",
3965
+ "client_uri",
3966
+ "logo_uri",
3967
+ "scope",
3968
+ "tos_uri",
3969
+ "policy_uri",
3970
+ "jwks_uri",
3971
+ "software_id",
3972
+ "software_version",
3973
+ "software_statement",
3974
+ "registration_access_token",
3975
+ "registration_client_uri",
3976
+ "issuer"
3977
+ ]) {
3978
+ if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string") throw invalid();
3979
+ }
3980
+ if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "") throw invalid();
3981
+ for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
3982
+ const entry = record2[key2];
3983
+ if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string"))) throw invalid();
3984
+ }
3985
+ for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
3986
+ const entry = record2[key2];
3987
+ if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0)) throw invalid();
3988
+ }
3989
+ try {
3990
+ normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
3991
+ } catch {
3992
+ throw invalid();
3993
+ }
3994
+ return record2;
3995
+ }
3996
+ function normalizeStoredOAuthClient(value) {
3997
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
3998
+ const record2 = value;
3999
+ const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
4000
+ const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
4001
+ if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === "")) return null;
4002
+ const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
4003
+ const requestedRedirectUri = Object.hasOwn(record2, "requestedRedirectUri") ? record2.requestedRedirectUri : void 0;
4004
+ if (requestedRedirectUri !== void 0) {
4005
+ try {
4006
+ if (typeof requestedRedirectUri !== "string") throw new Error("Invalid redirect identity");
4007
+ loopbackTarget({ redirectUri: requestedRedirectUri });
4008
+ } catch {
4009
+ throw new Error("Invalid stored OAuth registration redirect identity");
4010
+ }
4011
+ client.requestedRedirectUri = requestedRedirectUri;
4012
+ }
4013
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
4014
+ if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
4015
+ const registration = parseOAuthClientRegistration(record2.registration);
4016
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
4017
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
4018
+ throw new Error("OAuth client registration does not match the client identity");
4019
+ client.registration = registration;
4020
+ const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
4021
+ if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
4022
+ throw new Error("OAuth token endpoint authentication conflicts with the client registration");
4023
+ if (registrationMethod !== void 0) client.tokenEndpointAuthMethod = registrationMethod;
4024
+ }
4025
+ if (method !== void 0) client.tokenEndpointAuthMethod = method;
4026
+ const ownership = Object.hasOwn(record2, "registrationOwnership") ? record2.registrationOwnership : void 0;
4027
+ if (ownership !== void 0) {
4028
+ if (ownership !== "caller" || client.registration === void 0)
4029
+ throw new Error("Invalid stored OAuth registration ownership");
4030
+ client.registrationOwnership = "caller";
4031
+ }
4032
+ return client;
4033
+ }
4034
+ function registrationMatchesRedirect(client, requestedUri, fresh = false) {
4035
+ if (client.requestedRedirectUri !== void 0) return client.requestedRedirectUri === requestedUri;
4036
+ const redirects = client.registration?.redirect_uris;
4037
+ if (redirects === void 0 || redirects === null || redirects.length === 0) return true;
4038
+ return redirects.some((returnedUri) => {
4039
+ if (returnedUri === requestedUri) return true;
4040
+ if (!fresh) return false;
4041
+ let returned, requested;
4042
+ try {
4043
+ returned = new URL(returnedUri);
4044
+ requested = new URL(requestedUri);
4045
+ } catch {
4046
+ return false;
4047
+ }
4048
+ if (requested.protocol !== "http:" || returned.protocol !== "http:" || !["127.0.0.1", "[::1]", "localhost"].includes(requested.hostname)) return false;
4049
+ const sameHost = returned.hostname === requested.hostname;
4050
+ const normalizedIpv4 = requested.hostname === "127.0.0.1" && returned.hostname === "localhost" && returned.port === "";
4051
+ if (!sameHost && !normalizedIpv4) return false;
4052
+ returned.hostname = requested.hostname;
4053
+ returned.port = requested.port;
4054
+ return returned.href === requested.href;
4055
+ });
4056
+ }
4057
+
4058
+ // packages/mcp-oauth/src/client/auth-store-session-store.ts
4059
+ import crypto2 from "node:crypto";
4060
+ import path4 from "node:path";
3521
4061
 
3522
4062
  // packages/auth-store/src/encrypted-file-store.ts
3523
- import { createCipheriv, createDecipheriv, randomBytes, randomUUID, scrypt } from "node:crypto";
4063
+ import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
3524
4064
  import { promises as fs } from "node:fs";
3525
4065
  import { homedir, hostname, userInfo } from "node:os";
3526
- import path from "node:path";
4066
+ import path2 from "node:path";
3527
4067
 
3528
4068
  // packages/auth-store/src/error-codes.ts
3529
4069
  function hasOwnErrorCode(error, code) {
3530
4070
  return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
3531
4071
  }
3532
4072
 
4073
+ // packages/auth-store/src/transaction-lock.ts
4074
+ import { randomUUID } from "node:crypto";
4075
+ import path from "node:path";
4076
+ async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
4077
+ options = { ...options, signal: options.signal, timeoutMs: options.timeoutMs };
4078
+ const timeoutMs = options.timeoutMs ?? 3e4;
4079
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
4080
+ throw new Error("Invalid secret-store transaction lock timeout");
4081
+ options.signal?.throwIfAborted();
4082
+ const deadline = performance.now() + timeoutMs;
4083
+ const directory = path.resolve(lockDirectory);
4084
+ await assertLockDirectoryPath(fs2, directory);
4085
+ await fs2.mkdir(directory, { recursive: true, mode: 448 });
4086
+ await assertLockDirectoryPath(fs2, directory);
4087
+ const name = `${process.pid}-${randomUUID()}.claim`;
4088
+ const claimPath = path.join(directory, name);
4089
+ const temporaryPath = `${claimPath}.tmp`;
4090
+ let claimed = true;
4091
+ let temporaryCreated = false;
4092
+ let outcome;
4093
+ try {
4094
+ try {
4095
+ await fs2.writeFile(claimPath, JSON.stringify({ ticket: null }), { encoding: "utf8", flag: "wx", mode: 384 });
4096
+ } catch (error) {
4097
+ if (hasOwnErrorCode(error, "EEXIST")) claimed = false;
4098
+ throw error;
4099
+ }
4100
+ const existing = await readClaims(fs2, directory, name);
4101
+ const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
4102
+ if (!Number.isSafeInteger(ticket)) throw new Error("Secret-store transaction lock ticket overflow");
4103
+ temporaryCreated = true;
4104
+ try {
4105
+ await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
4106
+ } catch (error) {
4107
+ if (hasOwnErrorCode(error, "EEXIST")) temporaryCreated = false;
4108
+ throw error;
4109
+ }
4110
+ await fs2.rename(temporaryPath, claimPath);
4111
+ temporaryCreated = false;
4112
+ for (; ; ) {
4113
+ options.signal?.throwIfAborted();
4114
+ const peers = await readClaims(fs2, directory, name);
4115
+ if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name)) break;
4116
+ const remaining = deadline - performance.now();
4117
+ if (remaining <= 0) throw new Error("Timed out waiting for secret-store transaction lock");
4118
+ await new Promise((resolve, reject) => {
4119
+ const abort = () => {
4120
+ clearTimeout(timer);
4121
+ options.signal?.removeEventListener("abort", abort);
4122
+ reject(options.signal?.reason);
4123
+ };
4124
+ const timer = setTimeout(() => {
4125
+ options.signal?.removeEventListener("abort", abort);
4126
+ resolve();
4127
+ }, Math.min(10, remaining));
4128
+ options.signal?.addEventListener("abort", abort, { once: true });
4129
+ if (options.signal?.aborted) abort();
4130
+ });
4131
+ }
4132
+ options.signal?.throwIfAborted();
4133
+ outcome = { result: await operation() };
4134
+ } catch (error) {
4135
+ outcome = { error };
4136
+ }
4137
+ const cleanup = [];
4138
+ for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
4139
+ try {
4140
+ await fs2.unlink(target);
4141
+ } catch (error) {
4142
+ if (!hasOwnErrorCode(error, "ENOENT")) cleanup.push(error);
4143
+ }
4144
+ }
4145
+ if (cleanup.length) throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
4146
+ if ("error" in outcome) throw outcome.error;
4147
+ return outcome.result;
4148
+ }
4149
+ async function assertNoSymbolicLink(fs2, target) {
4150
+ try {
4151
+ if ((await fs2.lstat(target)).isSymbolicLink()) throw new Error("Refusing secret-store transaction lock through symbolic link");
4152
+ } catch (error) {
4153
+ if (!hasOwnErrorCode(error, "ENOENT")) throw error;
4154
+ }
4155
+ }
4156
+ async function assertLockDirectoryPath(fs2, directory) {
4157
+ const root = path.parse(directory).root;
4158
+ const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
4159
+ let current = root;
4160
+ for (const [index, segment] of segments.entries()) {
4161
+ current = path.join(current, segment);
4162
+ if (index > 0 || segments.length === 1) await assertNoSymbolicLink(fs2, current);
4163
+ }
4164
+ }
4165
+ async function readClaims(fs2, directory, ownName) {
4166
+ const claims = [];
4167
+ for (const name of await fs2.readdir(directory)) {
4168
+ if (name === ownName || !name.endsWith(".claim")) continue;
4169
+ const pidText = name.slice(0, name.indexOf("-"));
4170
+ const pid = Number(pidText);
4171
+ if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText) throw new Error("Malformed secret-store transaction lock owner");
4172
+ const target = path.join(directory, name);
4173
+ await assertNoSymbolicLink(fs2, target);
4174
+ let alive = true;
4175
+ try {
4176
+ process.kill(pid, 0);
4177
+ } catch (error) {
4178
+ alive = !hasOwnErrorCode(error, "ESRCH");
4179
+ }
4180
+ if (!alive) {
4181
+ try {
4182
+ await fs2.unlink(target);
4183
+ } catch (error) {
4184
+ if (!hasOwnErrorCode(error, "ENOENT")) throw error;
4185
+ }
4186
+ continue;
4187
+ }
4188
+ let ticket = null;
4189
+ let raw;
4190
+ try {
4191
+ raw = await fs2.readFile(target, "utf8");
4192
+ } catch (error) {
4193
+ if (hasOwnErrorCode(error, "ENOENT")) continue;
4194
+ throw error;
4195
+ }
4196
+ try {
4197
+ const value = JSON.parse(raw);
4198
+ if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0) ticket = value.ticket;
4199
+ } catch {
4200
+ }
4201
+ claims.push({ name, ticket });
4202
+ }
4203
+ return claims;
4204
+ }
4205
+
3533
4206
  // packages/auth-store/src/encrypted-file-store.ts
3534
4207
  var derivedKeyCache = /* @__PURE__ */ new Map();
3535
4208
  var ENCRYPTION_ALGORITHM = "aes-256-gcm";
@@ -3546,6 +4219,7 @@ var EncryptedFileStore = class {
3546
4219
  getMachineIdentity;
3547
4220
  getRandomBytes;
3548
4221
  keyPromise = null;
4222
+ throwOnInvalidDocument;
3549
4223
  constructor(input) {
3550
4224
  this.fs = input.fs ?? fs;
3551
4225
  this.salt = input.salt;
@@ -3555,7 +4229,7 @@ var EncryptedFileStore = class {
3555
4229
  const defaultFileName = input.defaultFileName ?? "credentials.enc";
3556
4230
  assertSafeDefaultDirectory(defaultDirectory);
3557
4231
  assertSafeDefaultFileName(defaultFileName);
3558
- this.filePath = path.join(
4232
+ this.filePath = path2.join(
3559
4233
  homeDirectory,
3560
4234
  defaultDirectory,
3561
4235
  defaultFileName
@@ -3570,6 +4244,7 @@ var EncryptedFileStore = class {
3570
4244
  }
3571
4245
  this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
3572
4246
  this.getRandomBytes = input.getRandomBytes ?? randomBytes;
4247
+ this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
3573
4248
  }
3574
4249
  async get() {
3575
4250
  await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
@@ -3584,6 +4259,7 @@ var EncryptedFileStore = class {
3584
4259
  }
3585
4260
  const document = parseEncryptedDocument(rawDocument);
3586
4261
  if (!document) {
4262
+ if (this.throwOnInvalidDocument) throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3587
4263
  return null;
3588
4264
  }
3589
4265
  const key2 = await this.getEncryptionKey();
@@ -3592,6 +4268,7 @@ var EncryptedFileStore = class {
3592
4268
  const authTag = Buffer.from(document.authTag, "base64");
3593
4269
  const ciphertext = Buffer.from(document.ciphertext, "base64");
3594
4270
  if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
4271
+ if (this.throwOnInvalidDocument) throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3595
4272
  return null;
3596
4273
  }
3597
4274
  const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
@@ -3599,9 +4276,16 @@ var EncryptedFileStore = class {
3599
4276
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
3600
4277
  return plaintext.toString("utf8");
3601
4278
  } catch {
4279
+ if (this.throwOnInvalidDocument) throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3602
4280
  return null;
3603
4281
  }
3604
4282
  }
4283
+ async withLock(operation, options = {}) {
4284
+ options = { ...options, signal: options.signal, timeoutMs: options.timeoutMs };
4285
+ await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
4286
+ if (this.fs.readdir === void 0) throw new Error("Secret-store transaction locks require filesystem readdir support");
4287
+ return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
4288
+ }
3605
4289
  async set(value) {
3606
4290
  await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3607
4291
  const key2 = await this.getEncryptionKey();
@@ -3618,9 +4302,9 @@ var EncryptedFileStore = class {
3618
4302
  authTag: authTag.toString("base64"),
3619
4303
  ciphertext: ciphertext.toString("base64")
3620
4304
  };
3621
- await this.fs.mkdir(path.dirname(this.filePath), { recursive: true });
4305
+ await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
3622
4306
  await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3623
- const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
4307
+ const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
3624
4308
  let temporaryCreated = false;
3625
4309
  try {
3626
4310
  await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
@@ -3650,7 +4334,7 @@ var EncryptedFileStore = class {
3650
4334
  }
3651
4335
  }
3652
4336
  async assertCredentialPathHasNoSymbolicLinks(targetPath) {
3653
- const resolvedPath = path.resolve(targetPath);
4337
+ const resolvedPath = path2.resolve(targetPath);
3654
4338
  const protectedPaths = getProtectedCredentialPaths(
3655
4339
  resolvedPath,
3656
4340
  this.symbolicLinkCheckStartPath
@@ -3684,26 +4368,26 @@ var EncryptedFileStore = class {
3684
4368
  };
3685
4369
  function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
3686
4370
  const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
3687
- return path.resolve(homeDirectory, firstSegment ?? ".");
4371
+ return path2.resolve(homeDirectory, firstSegment ?? ".");
3688
4372
  }
3689
4373
  function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
3690
4374
  if (symbolicLinkCheckStartPath === null) {
3691
4375
  return getExplicitProtectedCredentialPaths(resolvedPath);
3692
4376
  }
3693
- const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
4377
+ const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
3694
4378
  if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
3695
- return [path.dirname(resolvedPath), resolvedPath];
4379
+ return [path2.dirname(resolvedPath), resolvedPath];
3696
4380
  }
3697
4381
  const protectedPaths = [resolvedStartPath];
3698
4382
  let currentPath = resolvedStartPath;
3699
- for (const segment of path.relative(resolvedStartPath, resolvedPath).split(path.sep).filter(Boolean)) {
3700
- currentPath = path.join(currentPath, segment);
4383
+ for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
4384
+ currentPath = path2.join(currentPath, segment);
3701
4385
  protectedPaths.push(currentPath);
3702
4386
  }
3703
4387
  return protectedPaths;
3704
4388
  }
3705
4389
  function assertSafeDefaultDirectory(defaultDirectory) {
3706
- if (path.isAbsolute(defaultDirectory) || path.win32.isAbsolute(defaultDirectory)) {
4390
+ if (path2.isAbsolute(defaultDirectory) || path2.win32.isAbsolute(defaultDirectory)) {
3707
4391
  throw new Error("defaultDirectory must be a relative path inside the home directory");
3708
4392
  }
3709
4393
  for (const segment of splitPathSegments(defaultDirectory)) {
@@ -3721,15 +4405,15 @@ function splitPathSegments(value) {
3721
4405
  return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
3722
4406
  }
3723
4407
  function getExplicitProtectedCredentialPaths(resolvedPath) {
3724
- const parsed = path.parse(resolvedPath);
3725
- const segments = resolvedPath.slice(parsed.root.length).split(path.sep).filter((segment) => segment.length > 0);
4408
+ const parsed = path2.parse(resolvedPath);
4409
+ const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
3726
4410
  if (segments.length <= 1) {
3727
4411
  return [resolvedPath];
3728
4412
  }
3729
4413
  const protectedPaths = [];
3730
4414
  let currentPath = parsed.root;
3731
4415
  for (const [index, segment] of segments.entries()) {
3732
- currentPath = path.join(currentPath, segment);
4416
+ currentPath = path2.join(currentPath, segment);
3733
4417
  if (index === 0) {
3734
4418
  continue;
3735
4419
  }
@@ -3738,8 +4422,8 @@ function getExplicitProtectedCredentialPaths(resolvedPath) {
3738
4422
  return protectedPaths;
3739
4423
  }
3740
4424
  function isPathInsideOrEqual(childPath, parentPath) {
3741
- const relativePath = path.relative(parentPath, childPath);
3742
- return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
4425
+ const relativePath = path2.relative(parentPath, childPath);
4426
+ return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
3743
4427
  }
3744
4428
  async function removeIfPresent(fileSystem, filePath) {
3745
4429
  try {
@@ -3787,10 +4471,10 @@ function parseEncryptedDocument(raw) {
3787
4471
  if (!isRecord2(parsed)) {
3788
4472
  return null;
3789
4473
  }
3790
- const version = getOwnEntry(parsed, "version");
3791
- const iv = getOwnEntry(parsed, "iv");
3792
- const authTag = getOwnEntry(parsed, "authTag");
3793
- const ciphertext = getOwnEntry(parsed, "ciphertext");
4474
+ const version = getOwnEntry2(parsed, "version");
4475
+ const iv = getOwnEntry2(parsed, "iv");
4476
+ const authTag = getOwnEntry2(parsed, "authTag");
4477
+ const ciphertext = getOwnEntry2(parsed, "ciphertext");
3794
4478
  if (version !== ENCRYPTION_VERSION) {
3795
4479
  return null;
3796
4480
  }
@@ -3810,7 +4494,7 @@ function parseEncryptedDocument(raw) {
3810
4494
  function isRecord2(value) {
3811
4495
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
3812
4496
  }
3813
- function getOwnEntry(record2, key2) {
4497
+ function getOwnEntry2(record2, key2) {
3814
4498
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3815
4499
  }
3816
4500
  function isNotFoundError(error) {
@@ -3822,16 +4506,24 @@ function isAlreadyExistsError(error) {
3822
4506
 
3823
4507
  // packages/auth-store/src/keychain-store.ts
3824
4508
  import { spawn } from "node:child_process";
4509
+ import { createHash } from "node:crypto";
4510
+ import { promises as nodeFs } from "node:fs";
4511
+ import { homedir as homedir2 } from "node:os";
4512
+ import path3 from "node:path";
3825
4513
  var SECURITY_CLI = "security";
3826
4514
  var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
3827
4515
  var KeychainStore = class {
3828
4516
  runCommand;
3829
4517
  service;
3830
4518
  account;
4519
+ lockFs;
4520
+ lockDirectory;
3831
4521
  constructor(input) {
3832
4522
  this.runCommand = input.runCommand ?? runSecurityCommand;
3833
4523
  this.service = input.service.trim();
3834
4524
  this.account = input.account.trim();
4525
+ this.lockFs = input.lock?.fs ?? nodeFs;
4526
+ this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
3835
4527
  if (this.service.length === 0) {
3836
4528
  throw new Error("Keychain service must not be empty");
3837
4529
  }
@@ -3852,6 +4544,10 @@ var KeychainStore = class {
3852
4544
  }
3853
4545
  throw createSecurityCliFailure("read secret from macOS Keychain", result);
3854
4546
  }
4547
+ async withLock(operation, options = {}) {
4548
+ const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
4549
+ return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
4550
+ }
3855
4551
  async set(value) {
3856
4552
  if (value.includes("\n") || value.includes("\r")) {
3857
4553
  throw new Error("Keychain secrets cannot contain line breaks");
@@ -3970,14 +4666,14 @@ function createSecurityCliFailure(operation, result) {
3970
4666
  return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
3971
4667
  }
3972
4668
  function getCommandExitCode(result) {
3973
- const value = getOwnEntry2(result, "exitCode");
4669
+ const value = getOwnEntry3(result, "exitCode");
3974
4670
  return typeof value === "number" && Number.isInteger(value) ? value : 1;
3975
4671
  }
3976
4672
  function getCommandOutput(result, key2) {
3977
- const value = getOwnEntry2(result, key2);
4673
+ const value = getOwnEntry3(result, key2);
3978
4674
  return typeof value === "string" ? value : "";
3979
4675
  }
3980
- function getOwnEntry2(record2, key2) {
4676
+ function getOwnEntry3(record2, key2) {
3981
4677
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3982
4678
  }
3983
4679
 
@@ -3999,7 +4695,7 @@ var storeFactories = {
3999
4695
  }
4000
4696
  };
4001
4697
  function createSecretStore(input) {
4002
- const backend = resolveBackend(input);
4698
+ const backend = resolveSecretStoreBackend(input);
4003
4699
  const platform = input.platform ?? process.platform;
4004
4700
  if (backend === "keychain" && platform !== MACOS_PLATFORM) {
4005
4701
  throw new Error(
@@ -4009,7 +4705,7 @@ function createSecretStore(input) {
4009
4705
  const store = storeFactories[backend](input);
4010
4706
  return { backend, store };
4011
4707
  }
4012
- function resolveBackend(input) {
4708
+ function resolveSecretStoreBackend(input) {
4013
4709
  const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
4014
4710
  const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
4015
4711
  const backend = configuredBackend?.trim();
@@ -4045,66 +4741,86 @@ var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
4045
4741
  var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
4046
4742
  var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
4047
4743
  var MAX_JS_DATE_MS = 864e13;
4048
- function createAuthStoreSessionStore(options = {}) {
4744
+ function createAuthStoreSessionStore(options = {}, namespace) {
4745
+ assertPersistenceNamespace(namespace);
4746
+ options = snapshotPersistenceOptions(options);
4049
4747
  return {
4748
+ async withLock(resource, operation, lockOptions) {
4749
+ const store = createResourceSecretStore(resource, options, namespace);
4750
+ if (store.withLock === void 0) throw new Error("OAuth secret-store backend does not support transaction locks");
4751
+ return store.withLock(operation, lockOptions);
4752
+ },
4050
4753
  async load(resource) {
4051
- const store = createResourceSecretStore(resource, options);
4754
+ const store = createResourceSecretStore(resource, options, namespace);
4052
4755
  const value = await store.get();
4053
4756
  if (value === null) {
4054
4757
  return null;
4055
4758
  }
4056
- const parsed = JSON.parse(value);
4759
+ let parsed;
4760
+ try {
4761
+ parsed = JSON.parse(value);
4762
+ } catch {
4763
+ throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
4764
+ }
4057
4765
  if (isStoredOAuthSession(parsed)) {
4058
4766
  return parsed;
4059
4767
  }
4060
4768
  throw new Error("Stored OAuth session must match the expected shape");
4061
4769
  },
4062
4770
  async save(resource, session) {
4063
- const store = createResourceSecretStore(resource, options);
4771
+ const store = createResourceSecretStore(resource, options, namespace);
4064
4772
  await store.set(JSON.stringify(session));
4065
4773
  },
4066
4774
  async clear(resource) {
4067
- const store = createResourceSecretStore(resource, options);
4775
+ const store = createResourceSecretStore(resource, options, namespace);
4068
4776
  await store.delete();
4069
4777
  }
4070
4778
  };
4071
4779
  }
4072
- function createAuthStoreClientStore(options) {
4780
+ function createAuthStoreClientStore(options, namespace) {
4781
+ assertPersistenceNamespace(namespace);
4782
+ options = snapshotPersistenceOptions(options);
4073
4783
  return {
4074
4784
  async load(issuer) {
4075
- const store = createIssuerSecretStore(issuer, options);
4785
+ const store = createIssuerSecretStore(issuer, options, namespace);
4076
4786
  const value = await store.get();
4077
4787
  if (value === null) {
4078
4788
  return null;
4079
4789
  }
4080
- const parsed = JSON.parse(value);
4081
- const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : void 0;
4082
- if (clientId !== void 0) {
4083
- const client = { clientId };
4084
- if (isObjectRecord(parsed) && Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
4085
- client.clientSecret = getOwnEntry3(parsed, "clientSecret");
4086
- }
4087
- return client;
4790
+ let parsed;
4791
+ try {
4792
+ parsed = JSON.parse(value);
4793
+ } catch {
4794
+ throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4088
4795
  }
4796
+ if (isObjectRecord2(parsed) && typeof getOwnEntry4(parsed, "clientId") === "string")
4797
+ return parsed;
4089
4798
  throw new Error("Stored OAuth client must be a JSON object with clientId");
4090
4799
  },
4091
4800
  async save(issuer, client) {
4092
- const store = createIssuerSecretStore(issuer, options);
4801
+ const store = createIssuerSecretStore(issuer, options, namespace);
4093
4802
  await store.set(JSON.stringify(client));
4094
4803
  },
4095
4804
  async clear(issuer) {
4096
- const store = createIssuerSecretStore(issuer, options);
4805
+ const store = createIssuerSecretStore(issuer, options, namespace);
4097
4806
  await store.delete();
4098
4807
  }
4099
4808
  };
4100
4809
  }
4101
- function createNamedSecretStore(key2, options, defaults) {
4102
- const hash = crypto.createHash("sha256").update(key2).digest("hex");
4810
+ function snapshotPersistenceOptions(options) {
4811
+ const snapshot = snapshotOAuthPersistenceOptions(options);
4812
+ snapshot.backend = resolveSecretStoreBackend(snapshot);
4813
+ return snapshot;
4814
+ }
4815
+ function createNamedSecretStore(key2, options, defaults, namespace) {
4816
+ options = snapshotOAuthPersistenceOptions(options);
4817
+ const hash = crypto2.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
4103
4818
  const configuredFilePath = options.fileStore?.filePath;
4104
- const parsedFilePath = configuredFilePath === void 0 ? null : path2.parse(configuredFilePath);
4819
+ const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
4105
4820
  const fileStore = {
4106
4821
  ...options.fileStore,
4107
- filePath: parsedFilePath === null ? void 0 : path2.join(
4822
+ throwOnInvalidDocument: true,
4823
+ filePath: parsedFilePath === null ? void 0 : path4.join(
4108
4824
  parsedFilePath.dir,
4109
4825
  `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
4110
4826
  ),
@@ -4119,70 +4835,67 @@ function createNamedSecretStore(key2, options, defaults) {
4119
4835
  };
4120
4836
  return createSecretStore({ ...options, fileStore, keychainStore }).store;
4121
4837
  }
4122
- function createResourceSecretStore(resource, options) {
4838
+ function createResourceSecretStore(resource, options, namespace) {
4123
4839
  return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
4124
4840
  salt: DEFAULT_FILE_SALT,
4125
4841
  directory: DEFAULT_FILE_DIRECTORY,
4126
4842
  service: DEFAULT_KEYCHAIN_SERVICE,
4127
4843
  accountPrefix: "provider"
4128
- });
4844
+ }, namespace);
4129
4845
  }
4130
- function createIssuerSecretStore(issuer, options) {
4846
+ function createIssuerSecretStore(issuer, options, namespace) {
4131
4847
  return createNamedSecretStore(issuer, options, {
4132
4848
  salt: DEFAULT_CLIENT_FILE_SALT,
4133
4849
  directory: DEFAULT_CLIENT_FILE_DIRECTORY,
4134
4850
  service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
4135
4851
  accountPrefix: "issuer"
4136
- });
4852
+ }, namespace);
4137
4853
  }
4138
- function isObjectRecord(value) {
4854
+ function assertPersistenceNamespace(namespace) {
4855
+ if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
4856
+ throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
4857
+ }
4858
+ function isObjectRecord2(value) {
4139
4859
  return typeof value === "object" && value !== null && !Array.isArray(value);
4140
4860
  }
4141
- function getOwnEntry3(record2, key2) {
4861
+ function getOwnEntry4(record2, key2) {
4142
4862
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4143
4863
  }
4144
4864
  function getOwnString(record2, key2) {
4145
- const value = getOwnEntry3(record2, key2);
4865
+ const value = getOwnEntry4(record2, key2);
4146
4866
  return typeof value === "string" ? value : void 0;
4147
4867
  }
4148
4868
  function isStoredOAuthSession(value) {
4149
- if (!isObjectRecord(value)) {
4150
- return false;
4151
- }
4152
- return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4153
- }
4154
- function isStoredOAuthClient(value) {
4155
- if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
4869
+ if (!isObjectRecord2(value)) {
4156
4870
  return false;
4157
4871
  }
4158
- const clientSecret = getOwnEntry3(value, "clientSecret");
4159
- return clientSecret === void 0 || typeof clientSecret === "string" && clientSecret.trim().length > 0;
4872
+ return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && normalizeStoredOAuthClient(getOwnEntry4(value, "client")) !== null && isStoredOAuthDiscovery(getOwnEntry4(value, "discovery")) && (getOwnEntry4(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry4(value, "refreshState") === void 0 || getOwnEntry4(value, "refreshState") === "pending" && getOwnEntry4(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry4(value, "tokens"));
4160
4873
  }
4161
4874
  function isStoredOAuthDiscovery(value) {
4162
- if (!isObjectRecord(value)) {
4875
+ if (!isObjectRecord2(value)) {
4163
4876
  return false;
4164
4877
  }
4165
- return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord(getOwnEntry3(value, "resourceMetadata")) && isObjectRecord(getOwnEntry3(value, "authorizationServerMetadata"));
4878
+ return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord2(getOwnEntry4(value, "resourceMetadata")) && isObjectRecord2(getOwnEntry4(value, "authorizationServerMetadata"));
4166
4879
  }
4167
4880
  function isStoredOAuthTokensOrMissing(value) {
4168
4881
  if (value === void 0) {
4169
4882
  return true;
4170
4883
  }
4171
- if (!isObjectRecord(value)) {
4884
+ if (!isObjectRecord2(value)) {
4172
4885
  return false;
4173
4886
  }
4174
4887
  if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
4175
4888
  return false;
4176
4889
  }
4177
- const expiresAt = getOwnEntry3(value, "expiresAt");
4890
+ const expiresAt = getOwnEntry4(value, "expiresAt");
4178
4891
  if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
4179
4892
  return false;
4180
4893
  }
4181
- const refreshToken = getOwnEntry3(value, "refreshToken");
4894
+ const refreshToken = getOwnEntry4(value, "refreshToken");
4182
4895
  if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
4183
4896
  return false;
4184
4897
  }
4185
- const scope = getOwnEntry3(value, "scope");
4898
+ const scope = getOwnEntry4(value, "scope");
4186
4899
  return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
4187
4900
  }
4188
4901
  function isNonBlankOwnString(record2, key2) {
@@ -4190,263 +4903,215 @@ function isNonBlankOwnString(record2, key2) {
4190
4903
  return value !== void 0 && value.trim().length > 0;
4191
4904
  }
4192
4905
 
4193
- // packages/mcp-oauth/src/client/default-oauth-client-provider.ts
4194
- import { isIP } from "node:net";
4195
-
4196
- // packages/mcp-oauth/src/http-fetch.ts
4197
- async function fetchMcpResponse(fetchImplementation, input, init = {}) {
4198
- const response = await fetchImplementation(input, { ...init, redirect: "error" });
4199
- if (response.redirected || response.type === "opaqueredirect") {
4200
- void response.body?.cancel().catch(() => void 0);
4201
- throw new Error("MCP HTTP redirects are not allowed");
4202
- }
4203
- return response;
4204
- }
4205
-
4206
- // packages/mcp-oauth/src/client/default-oauth-client-provider.ts
4207
- import { URL as URL2 } from "node:url";
4208
-
4209
- // packages/mcp-oauth/src/client/loopback-authorization.ts
4210
- import http from "node:http";
4211
-
4212
- // packages/mcp-oauth/src/client/authorization-state.ts
4213
- import crypto2 from "node:crypto";
4214
- function createAuthorizationState(input) {
4215
- const payload = {
4216
- v: 1,
4217
- n: crypto2.randomBytes(16).toString("base64url"),
4218
- i: input.issuer,
4219
- r: input.requireIssuer
4906
+ // packages/mcp-oauth/src/client/resource-bound-store.ts
4907
+ function createResourceBoundOAuthStores(options, namespace, identity) {
4908
+ assertPersistenceNamespace(identity);
4909
+ assertPersistenceNamespace(namespace);
4910
+ const store = createNamedSecretStore(identity, options, {
4911
+ salt: "poe-code:mcp-oauth:resources:v1",
4912
+ directory: ".poe-code/mcp-oauth/resources",
4913
+ service: "poe-code-mcp-oauth-resources",
4914
+ accountPrefix: "resource"
4915
+ }, namespace);
4916
+ const result = {
4917
+ initialGrantAllowed: true,
4918
+ sessionStore: {},
4919
+ clientStore: {},
4920
+ async reset(resource, options2 = {}) {
4921
+ options2.signal?.throwIfAborted();
4922
+ let url;
4923
+ try {
4924
+ url = new URL(resource);
4925
+ } catch {
4926
+ throw new Error("OAuth reset resource must be an absolute HTTP URL");
4927
+ }
4928
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#"))
4929
+ throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
4930
+ await replace({ version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} }, options2);
4931
+ },
4932
+ async importSession(value, options2 = {}) {
4933
+ options2.signal?.throwIfAborted();
4934
+ const session = copyBoundedOAuthJson(value, "Invalid OAuth import session");
4935
+ try {
4936
+ if (!isStoredOAuthSession(session) || session.tokens === void 0 || session.refreshState !== void 0)
4937
+ throw new Error("Invalid session");
4938
+ if (typeof session.discovery.resourceMetadata.resource !== "string") throw new Error("Invalid resource metadata");
4939
+ const resource = new URL(session.resource), issuer = new URL(session.authorizationServer), advertisedResource = new URL(session.discovery.resourceMetadata.resource);
4940
+ if ([resource, issuer, advertisedResource].some((url) => !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#")) || issuer.href.includes("?") || session.discovery.authorizationServerMetadata.issuer !== session.authorizationServer || canonicalizeResourceIndicator(advertisedResource) !== canonicalizeResourceIndicator(resource))
4941
+ throw new Error("Invalid binding");
4942
+ session.client = normalizeStoredOAuthClient(session.client);
4943
+ if (session.client.registration !== void 0) session.client.registrationOwnership = "caller";
4944
+ if (session.client.registration?.issuer !== void 0 && session.client.registration.issuer !== null && session.client.registration.issuer !== session.authorizationServer) throw new Error("Invalid registration issuer");
4945
+ new Headers({ Authorization: `Bearer ${session.tokens.accessToken}` });
4946
+ session.resource = canonicalizeResourceIndicator(resource);
4947
+ } catch {
4948
+ throw new Error("Invalid OAuth import session or resource binding");
4949
+ }
4950
+ await replace({
4951
+ version: 1,
4952
+ resource: session.resource,
4953
+ generation: 1,
4954
+ session,
4955
+ clients: { [session.authorizationServer]: session.client }
4956
+ }, options2);
4957
+ }
4220
4958
  };
4221
- return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
4222
- }
4223
- function parseAuthorizationState(value) {
4224
- if (value === null || value.length === 0) {
4225
- return null;
4226
- }
4227
- try {
4228
- const decoded = Buffer.from(value, "base64url").toString("utf8");
4229
- const parsed = JSON.parse(decoded);
4230
- if (!isObjectRecord2(parsed)) {
4231
- return null;
4959
+ async function replace(record2, options2) {
4960
+ options2 = { ...options2, signal: options2.signal, timeoutMs: options2.timeoutMs };
4961
+ options2.signal?.throwIfAborted();
4962
+ const timeoutMs = options2.timeoutMs ?? 3e4;
4963
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
4964
+ throw new Error("OAuth replacement timeoutMs must be a positive supported timer interval");
4965
+ if (store.withLock === void 0) throw new Error("OAuth resource identity backend must support transaction locks");
4966
+ await store.withLock(async () => {
4967
+ options2.signal?.throwIfAborted();
4968
+ await store.set(JSON.stringify(record2));
4969
+ result.initialGrantAllowed = false;
4970
+ }, { signal: options2.signal, timeoutMs });
4971
+ }
4972
+ async function read() {
4973
+ const raw = await store.get();
4974
+ if (raw === null) return null;
4975
+ let value;
4976
+ try {
4977
+ value = JSON.parse(raw);
4978
+ } catch {
4979
+ throw new Error("Stored OAuth resource identity must be valid JSON; reset explicitly to recover");
4232
4980
  }
4233
- const version = getOwnEntry4(parsed, "v");
4234
- const nonce = getOwnEntry4(parsed, "n");
4235
- const issuer = getOwnEntry4(parsed, "i");
4236
- const requireIssuer = getOwnEntry4(parsed, "r");
4237
- if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
4238
- return null;
4981
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Invalid stored OAuth resource identity");
4982
+ const record2 = value;
4983
+ if (!["version", "resource", "generation", "session", "clients"].every((key2) => Object.hasOwn(record2, key2)) || record2.version !== 1 || typeof record2.resource !== "string" || record2.resource !== canonicalizeResourceIndicator(record2.resource) || !Number.isSafeInteger(record2.generation) || record2.generation < 0 || record2.session !== null && (!isStoredOAuthSession(record2.session) || record2.session.resource !== record2.resource) || typeof record2.clients !== "object" || record2.clients === null || Array.isArray(record2.clients))
4984
+ throw new Error("Invalid stored OAuth resource identity; reset explicitly to recover");
4985
+ let resourceUrl;
4986
+ try {
4987
+ resourceUrl = new URL(record2.resource);
4988
+ } catch {
4989
+ throw new Error("Invalid stored OAuth resource identity URL");
4239
4990
  }
4240
- return {
4241
- issuer,
4242
- requireIssuer
4243
- };
4244
- } catch {
4245
- return null;
4246
- }
4247
- }
4248
- function isObjectRecord2(value) {
4249
- return typeof value === "object" && value !== null && !Array.isArray(value);
4250
- }
4251
- function getOwnEntry4(record2, key2) {
4252
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4253
- }
4254
-
4255
- // packages/mcp-oauth/src/client/loopback-authorization.ts
4256
- async function createLoopbackAuthorizationSession(options = {}) {
4257
- const callbackPath = options.callbackPath ?? "/callback";
4258
- const server = options.createServer ? options.createServer() : http.createServer();
4259
- const port = await startServer(server);
4260
- const redirectUri = `http://127.0.0.1:${port}${callbackPath}`;
4261
- return {
4262
- redirectUri,
4263
- async waitForCode(authorizationUrl) {
4264
- return waitForAuthorizationCode(server, authorizationUrl, options, callbackPath);
4991
+ if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.href.includes("#"))
4992
+ throw new Error("Invalid stored OAuth resource identity URL");
4993
+ for (const [issuer, client] of Object.entries(record2.clients)) {
4994
+ const normalized = normalizeStoredOAuthClient(client);
4995
+ let url;
4996
+ try {
4997
+ url = new URL(issuer);
4998
+ } catch {
4999
+ throw new Error("Invalid stored OAuth resource client issuer");
5000
+ }
5001
+ if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#"))
5002
+ throw new Error("Invalid stored OAuth resource client");
5003
+ Object.defineProperty(record2.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
5004
+ }
5005
+ return record2;
5006
+ }
5007
+ async function reconcile(resource) {
5008
+ resource = canonicalizeResourceIndicator(resource);
5009
+ const existing = await read();
5010
+ if (existing?.resource === resource) {
5011
+ result.initialGrantAllowed = existing.generation === 0;
5012
+ return existing;
5013
+ }
5014
+ const record2 = { version: 1, resource, generation: existing === null ? 0 : existing.generation + 1, session: null, clients: {} };
5015
+ if (!Number.isSafeInteger(record2.generation)) throw new Error("OAuth resource identity generation limit exceeded");
5016
+ await store.set(JSON.stringify(record2));
5017
+ result.initialGrantAllowed = record2.generation === 0;
5018
+ return record2;
5019
+ }
5020
+ result.sessionStore = {
5021
+ async withLock(resource, operation, options2) {
5022
+ options2 = { ...options2, signal: options2?.signal, timeoutMs: options2?.timeoutMs };
5023
+ if (store.withLock === void 0) throw new Error("OAuth resource identity backend must support transaction locks");
5024
+ return store.withLock(async () => {
5025
+ options2.signal?.throwIfAborted();
5026
+ await reconcile(resource);
5027
+ options2.signal?.throwIfAborted();
5028
+ return operation();
5029
+ }, options2);
4265
5030
  },
4266
- close() {
4267
- server.closeAllConnections?.();
4268
- server.close();
5031
+ async load(resource) {
5032
+ const record2 = await read();
5033
+ return record2?.resource === canonicalizeResourceIndicator(resource) ? record2.session : null;
5034
+ },
5035
+ async save(resource, session) {
5036
+ const record2 = await reconcile(resource);
5037
+ if (canonicalizeResourceIndicator(session.resource) !== record2.resource) throw new Error("OAuth session does not match its resource identity");
5038
+ record2.session = session;
5039
+ await store.set(JSON.stringify(record2));
5040
+ },
5041
+ async clear(resource) {
5042
+ const record2 = await reconcile(resource);
5043
+ record2.session = null;
5044
+ record2.generation = Math.max(1, record2.generation);
5045
+ result.initialGrantAllowed = false;
5046
+ await store.set(JSON.stringify(record2));
4269
5047
  }
4270
5048
  };
5049
+ result.clientStore = {
5050
+ async load(issuer) {
5051
+ const record2 = await read();
5052
+ return record2 !== null && Object.hasOwn(record2.clients, issuer) ? record2.clients[issuer] : null;
5053
+ },
5054
+ async save(issuer, client) {
5055
+ const record2 = await read();
5056
+ if (record2 === null) throw new Error("OAuth resource identity must be bound before registering a client");
5057
+ Object.defineProperty(record2.clients, issuer, { value: client, enumerable: true, configurable: true, writable: true });
5058
+ await store.set(JSON.stringify(record2));
5059
+ },
5060
+ async clear(issuer) {
5061
+ const record2 = await read();
5062
+ if (record2 === null || !Object.hasOwn(record2.clients, issuer)) return;
5063
+ delete record2.clients[issuer];
5064
+ await store.set(JSON.stringify(record2));
5065
+ }
5066
+ };
5067
+ return result;
4271
5068
  }
4272
- async function startServer(server) {
4273
- return new Promise((resolve, reject) => {
4274
- const handleError = (error) => {
4275
- server.off("error", handleError);
4276
- reject(error);
4277
- };
4278
- server.once("error", handleError);
4279
- server.listen(0, "127.0.0.1", () => {
4280
- server.off("error", handleError);
4281
- const address = server.address();
4282
- resolve(address.port);
4283
- });
4284
- });
4285
- }
4286
- function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath) {
4287
- const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
5069
+
5070
+ // packages/mcp-oauth/src/client/default-oauth-client-provider.ts
5071
+ import { isIP } from "node:net";
5072
+
5073
+ // packages/mcp-oauth/src/http-fetch.ts
5074
+ async function fetchMcpResponse(fetchImplementation, input, init = {}) {
5075
+ const signal = init.signal;
5076
+ signal?.throwIfAborted();
4288
5077
  return new Promise((resolve, reject) => {
4289
5078
  let settled = false;
4290
- const settle = (fn) => {
4291
- if (!settled) {
4292
- settled = true;
4293
- fn();
4294
- }
5079
+ const finish = (error, response) => {
5080
+ if (settled) return;
5081
+ settled = true;
5082
+ signal?.removeEventListener("abort", abort);
5083
+ if (response === void 0) reject(error);
5084
+ else resolve(response);
4295
5085
  };
4296
- server.on("request", (req, res) => {
4297
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
4298
- if (url.pathname !== callbackPath) {
4299
- res.writeHead(404);
4300
- res.end("Not found");
4301
- return;
4302
- }
4303
- const callbackParameters = {
4304
- code: url.searchParams.get("code"),
4305
- error: url.searchParams.get("error"),
4306
- errorDescription: url.searchParams.get("error_description"),
4307
- state: url.searchParams.get("state"),
4308
- iss: url.searchParams.get("iss")
4309
- };
4310
- try {
4311
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4312
- } catch (error) {
4313
- res.writeHead(400);
4314
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4315
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4316
- return;
4317
- }
4318
- const authorizationError = callbackParameters.error;
4319
- if (authorizationError !== null) {
4320
- const description = callbackParameters.errorDescription ?? authorizationError;
4321
- res.writeHead(400);
4322
- res.end(`Authorization failed: ${description}`);
4323
- settle(() => reject(createAuthorizationError(authorizationError, description)));
4324
- return;
4325
- }
4326
- try {
4327
- const code = validateAuthorizationCallbackParameters(
4328
- callbackParameters,
4329
- expectedAuthorization
4330
- );
4331
- res.writeHead(200, { "Content-Type": "text/html" });
4332
- res.end(buildSuccessPage(options.landingPage));
4333
- settle(() => resolve(code));
4334
- } catch (error) {
4335
- res.writeHead(400);
4336
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4337
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4338
- }
4339
- });
4340
- if (options.readLine !== void 0) {
4341
- options.readLine().then((input) => {
4342
- const callbackParameters = extractCallbackParametersFromInput(input);
4343
- if (callbackParameters === null) {
4344
- settle(() => reject(new Error("OAuth callback missing authorization code")));
4345
- return;
4346
- }
4347
- try {
4348
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4349
- if (callbackParameters.error !== null) {
4350
- const description = callbackParameters.errorDescription ?? callbackParameters.error;
4351
- throw createAuthorizationError(callbackParameters.error, description);
4352
- }
4353
- const code = validateAuthorizationCallbackParameters(
4354
- callbackParameters,
4355
- expectedAuthorization
4356
- );
4357
- settle(() => resolve(code));
4358
- } catch (error) {
4359
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4360
- }
4361
- }).catch((error) => {
4362
- settle(() => reject(error instanceof Error ? error : new Error(String(error))));
4363
- });
5086
+ const abort = () => finish(signal?.reason);
5087
+ signal?.addEventListener("abort", abort, { once: true });
5088
+ if (signal?.aborted) {
5089
+ abort();
5090
+ return;
4364
5091
  }
4365
- if (options.openBrowser !== void 0) {
4366
- options.openBrowser(authorizationUrl).catch((error) => {
4367
- settle(() => reject(error));
4368
- });
5092
+ let pending;
5093
+ try {
5094
+ pending = fetchImplementation(input, { ...init, redirect: "error" });
5095
+ } catch (error) {
5096
+ finish(error);
5097
+ return;
4369
5098
  }
5099
+ void pending.then((response) => {
5100
+ if (settled || signal?.aborted) {
5101
+ void response.body?.cancel().catch(() => void 0);
5102
+ if (!settled) abort();
5103
+ return;
5104
+ }
5105
+ if (response.redirected || response.type === "opaqueredirect") {
5106
+ void response.body?.cancel().catch(() => void 0);
5107
+ finish(new Error("MCP HTTP redirects are not allowed"));
5108
+ } else finish(void 0, response);
5109
+ }, (error) => finish(error));
4370
5110
  });
4371
5111
  }
4372
- function extractCallbackParametersFromInput(input) {
4373
- const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
4374
- if (trimmed.length === 0) {
4375
- return null;
4376
- }
4377
- try {
4378
- const url = new URL(trimmed);
4379
- return {
4380
- code: url.searchParams.get("code"),
4381
- error: url.searchParams.get("error"),
4382
- errorDescription: url.searchParams.get("error_description"),
4383
- state: url.searchParams.get("state"),
4384
- iss: url.searchParams.get("iss")
4385
- };
4386
- } catch {
4387
- return {
4388
- code: trimmed,
4389
- error: null,
4390
- errorDescription: null,
4391
- state: null,
4392
- iss: null
4393
- };
4394
- }
4395
- }
4396
- function readExpectedAuthorizationCallback(authorizationUrl) {
4397
- const url = new URL(authorizationUrl);
4398
- const state = url.searchParams.get("state");
4399
- const parsedState = parseAuthorizationState(state);
4400
- return {
4401
- state,
4402
- issuer: parsedState?.issuer ?? null,
4403
- requireIssuer: parsedState?.requireIssuer ?? false
4404
- };
4405
- }
4406
- function validateAuthorizationCallbackParameters(callback, expected) {
4407
- validateAuthorizationCallbackBinding(callback, expected);
4408
- if (callback.code === null || callback.code.length === 0) {
4409
- throw new Error("OAuth callback missing authorization code");
4410
- }
4411
- return callback.code;
4412
- }
4413
- function validateAuthorizationCallbackBinding(callback, expected) {
4414
- if (expected.state !== null) {
4415
- if (callback.state === null || callback.state.length === 0) {
4416
- throw new Error("OAuth callback missing state");
4417
- }
4418
- if (callback.state !== expected.state) {
4419
- throw new Error("OAuth callback state mismatch");
4420
- }
4421
- }
4422
- if (expected.requireIssuer) {
4423
- if (callback.iss === null || callback.iss.length === 0) {
4424
- throw new Error("OAuth callback missing issuer");
4425
- }
4426
- }
4427
- if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
4428
- throw new Error("OAuth callback issuer mismatch");
4429
- }
4430
- }
4431
- function createAuthorizationError(error, description) {
4432
- return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
4433
- }
4434
- function escapeHtml(text) {
4435
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
4436
- }
4437
- function buildSuccessPage(landingPage) {
4438
- const title = landingPage?.title ?? "Connected";
4439
- const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
4440
- return [
4441
- "<!DOCTYPE html>",
4442
- `<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
4443
- '<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
4444
- '<div style="text-align:center">',
4445
- `<h1>${escapeHtml(title)}</h1>`,
4446
- `<p style="color:#666">${escapeHtml(body)}</p>`,
4447
- "</div></body></html>"
4448
- ].join("");
4449
- }
5112
+
5113
+ // packages/mcp-oauth/src/client/default-oauth-client-provider.ts
5114
+ import { URL as URL2 } from "node:url";
4450
5115
 
4451
5116
  // packages/mcp-oauth/src/client/pkce.ts
4452
5117
  import crypto3 from "node:crypto";
@@ -4502,7 +5167,12 @@ async function readBoundedResponseText(response, maxBytes, readers, signal) {
4502
5167
 
4503
5168
  // packages/mcp-oauth/src/client/token-endpoint.ts
4504
5169
  var MAX_JS_DATE_MS2 = 864e13;
5170
+ var oauthErrorBrand = /* @__PURE__ */ Symbol.for("poe-platform.mcp-oauth.OAuthError");
4505
5171
  var OAuthError = class extends Error {
5172
+ /** Recognize errors from separately bundled copies of this package. */
5173
+ static is(value) {
5174
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, oauthErrorBrand)?.value === true;
5175
+ }
4506
5176
  error;
4507
5177
  errorDescription;
4508
5178
  errorUri;
@@ -4511,17 +5181,23 @@ var OAuthError = class extends Error {
4511
5181
  status;
4512
5182
  retryable;
4513
5183
  terminal;
4514
- constructor(shape, status) {
4515
- super(shape.error_description ?? shape.error);
5184
+ /** True only when a complete OAuth error response establishes rejection. */
5185
+ outcomeKnown;
5186
+ constructor(shape, status, outcomeKnown = true) {
5187
+ const description = Object.hasOwn(shape, "error_description") ? shape.error_description : void 0;
5188
+ const uri = Object.hasOwn(shape, "error_uri") ? shape.error_uri : void 0;
5189
+ super(description ?? (outcomeKnown ? shape.error : `OAuth HTTP response did not contain a valid error (HTTP ${status})`));
4516
5190
  this.name = "OAuthError";
5191
+ Object.defineProperty(this, oauthErrorBrand, { value: true });
4517
5192
  this.error = shape.error;
4518
- this.errorDescription = shape.error_description;
4519
- this.errorUri = shape.error_uri;
4520
- this.error_description = shape.error_description;
4521
- this.error_uri = shape.error_uri;
5193
+ this.errorDescription = description;
5194
+ this.errorUri = uri;
5195
+ this.error_description = description;
5196
+ this.error_uri = uri;
4522
5197
  this.status = status;
4523
5198
  this.retryable = isRetryableOAuthError(this);
4524
5199
  this.terminal = !this.retryable;
5200
+ this.outcomeKnown = outcomeKnown;
4525
5201
  }
4526
5202
  };
4527
5203
  function isRetryableOAuthError(error) {
@@ -4533,6 +5209,7 @@ async function exchangeAuthorizationCode(input) {
4533
5209
  tokenEndpoint: input.tokenEndpoint,
4534
5210
  clientId: input.clientId,
4535
5211
  clientSecret: input.clientSecret,
5212
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4536
5213
  params: {
4537
5214
  grant_type: "authorization_code",
4538
5215
  code: input.code,
@@ -4541,6 +5218,7 @@ async function exchangeAuthorizationCode(input) {
4541
5218
  resource
4542
5219
  },
4543
5220
  fetch: input.fetch,
5221
+ signal: input.signal,
4544
5222
  now: input.now
4545
5223
  });
4546
5224
  }
@@ -4550,29 +5228,37 @@ async function refreshAccessToken(input) {
4550
5228
  tokenEndpoint: input.tokenEndpoint,
4551
5229
  clientId: input.clientId,
4552
5230
  clientSecret: input.clientSecret,
5231
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4553
5232
  params: {
4554
5233
  grant_type: "refresh_token",
4555
5234
  refresh_token: input.refreshToken,
4556
5235
  resource
4557
5236
  },
4558
5237
  fetch: input.fetch,
5238
+ signal: input.signal,
4559
5239
  now: input.now
4560
5240
  });
4561
5241
  }
4562
5242
  async function requestTokens(input) {
4563
- const body = new URLSearchParams({
4564
- client_id: input.clientId,
4565
- ...input.params
4566
- });
4567
- if (input.clientSecret !== void 0) {
4568
- body.set("client_secret", input.clientSecret);
4569
- }
4570
- const signal = AbortSignal.timeout(3e4);
5243
+ const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ?? (input.clientSecret === void 0 ? "none" : "client_secret_post");
5244
+ if (method !== "none" && (input.clientSecret === void 0 || input.clientSecret.trim() === ""))
5245
+ throw new Error("OAuth token endpoint authentication requires a client secret");
5246
+ const body = new URLSearchParams(input.params);
5247
+ const headers = new Headers({ Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" });
5248
+ if (method === "client_secret_basic") {
5249
+ const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
5250
+ const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
5251
+ headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
5252
+ } else {
5253
+ body.set("client_id", input.clientId);
5254
+ if (method === "client_secret_post") body.set("client_secret", input.clientSecret);
5255
+ }
5256
+ input.signal?.throwIfAborted();
5257
+ const deadline = AbortSignal.timeout(3e4);
5258
+ const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
4571
5259
  const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
4572
5260
  method: "POST",
4573
- headers: {
4574
- "Content-Type": "application/x-www-form-urlencoded"
4575
- },
5261
+ headers,
4576
5262
  body: body.toString(),
4577
5263
  signal
4578
5264
  });
@@ -4582,6 +5268,11 @@ async function requestTokens(input) {
4582
5268
  throw new Error("OAuth token response missing access_token");
4583
5269
  }
4584
5270
  const normalizedAccessToken = accessToken.trim();
5271
+ try {
5272
+ new Headers({ Authorization: `Bearer ${normalizedAccessToken}` });
5273
+ } catch {
5274
+ throw new Error("OAuth token response access_token is not a valid HTTP header value");
5275
+ }
4585
5276
  const tokenType = normalizeBearerTokenType(getOwnEntry5(payload, "token_type"));
4586
5277
  if (tokenType === null) {
4587
5278
  throw new Error("OAuth token response missing token_type=Bearer");
@@ -4592,15 +5283,22 @@ async function requestTokens(input) {
4592
5283
  if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || !Number.isInteger(expiresIn) || expiresIn < 0) {
4593
5284
  throw new Error("OAuth token response has invalid expires_in");
4594
5285
  }
4595
- expiresAt = input.now() + expiresIn * 1e3;
5286
+ const issuedAt = input.now();
5287
+ if (!Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS2)
5288
+ throw new Error("OAuth token response has invalid expires_in");
5289
+ expiresAt = issuedAt + expiresIn * 1e3;
4596
5290
  if (!Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS2 || !Number.isFinite(new Date(expiresAt).getTime())) {
4597
5291
  throw new Error("OAuth token response has invalid expires_in");
4598
5292
  }
4599
5293
  }
4600
5294
  const refreshToken = getOwnEntry5(payload, "refresh_token");
5295
+ if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0))
5296
+ throw new Error("OAuth token response has invalid refresh_token");
4601
5297
  const scope = getOwnEntry5(payload, "scope");
4602
5298
  const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : void 0;
4603
- const normalizedScope = typeof scope === "string" && scope.trim().length > 0 ? scope.trim() : void 0;
5299
+ const normalizedScope = normalizeOAuthScope(scope);
5300
+ if (scope !== void 0 && normalizedScope === void 0)
5301
+ throw new Error("Invalid OAuth scope syntax in token response");
4604
5302
  return {
4605
5303
  accessToken: normalizedAccessToken,
4606
5304
  refreshToken: normalizedRefreshToken === void 0 ? void 0 : normalizedRefreshToken,
@@ -4630,7 +5328,9 @@ async function readOAuthJsonObjectResponse(response, signal) {
4630
5328
  }
4631
5329
  const record2 = payload;
4632
5330
  if (!response.ok) {
4633
- throw new OAuthError(readOAuthError(record2, fallbackError.error), response.status);
5331
+ const error = getOwnEntry5(record2, "error");
5332
+ if (typeof error !== "string" || error.trim() === "") throw fallbackError;
5333
+ throw new OAuthError(readOAuthError(record2), response.status);
4634
5334
  }
4635
5335
  return record2;
4636
5336
  }
@@ -4648,8 +5348,8 @@ function getOwnEntry5(record2, key2) {
4648
5348
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4649
5349
  }
4650
5350
  function createFallbackOAuthError(status) {
4651
- const error = status === 503 ? "temporarily_unavailable" : "server_error";
4652
- return new OAuthError({ error }, status);
5351
+ const error = status === 503 ? "temporarily_unavailable" : status >= 500 ? "server_error" : "invalid_response";
5352
+ return new OAuthError({ error }, status, false);
4653
5353
  }
4654
5354
  function normalizeBearerTokenType(value) {
4655
5355
  if (typeof value !== "string") {
@@ -4658,6 +5358,56 @@ function normalizeBearerTokenType(value) {
4658
5358
  return value.toLowerCase() === "bearer" ? "Bearer" : null;
4659
5359
  }
4660
5360
 
5361
+ // packages/mcp-oauth/src/client/session-transaction.ts
5362
+ var queues = /* @__PURE__ */ new WeakMap();
5363
+ async function withOAuthSessionTransaction(store, resource, operation, options = {}) {
5364
+ const timeoutMs = options.timeoutMs ?? 3e4;
5365
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
5366
+ throw new Error("sessionLockTimeoutMs must be an integer from 1 to 2147483647 milliseconds");
5367
+ options.signal?.throwIfAborted();
5368
+ const started = performance.now();
5369
+ const pending = queues.get(store) ?? /* @__PURE__ */ new Map();
5370
+ queues.set(store, pending);
5371
+ const previous = pending.get(resource) ?? Promise.resolve();
5372
+ let release;
5373
+ const current = new Promise((resolve) => {
5374
+ release = resolve;
5375
+ });
5376
+ const tail = previous.then(() => current);
5377
+ pending.set(resource, tail);
5378
+ let running;
5379
+ try {
5380
+ let timer;
5381
+ let rejectWait;
5382
+ const waiting = new Promise((resolve, reject) => {
5383
+ rejectWait = reject;
5384
+ previous.then(resolve, reject);
5385
+ });
5386
+ const abort = () => rejectWait(options.signal?.reason);
5387
+ try {
5388
+ timer = setTimeout(() => rejectWait(new Error("Timed out waiting for OAuth session transaction lock")), timeoutMs);
5389
+ options.signal?.addEventListener("abort", abort, { once: true });
5390
+ if (options.signal?.aborted) abort();
5391
+ await waiting;
5392
+ } finally {
5393
+ clearTimeout(timer);
5394
+ options.signal?.removeEventListener("abort", abort);
5395
+ }
5396
+ options.signal?.throwIfAborted();
5397
+ running = (async () => store.withLock === void 0 ? operation() : store.withLock(resource, operation, {
5398
+ signal: options.signal,
5399
+ timeoutMs: Math.max(0, timeoutMs - (performance.now() - started))
5400
+ }))();
5401
+ return await waitForOAuthOperation(running, options.signal);
5402
+ } finally {
5403
+ if (running === void 0) release();
5404
+ else void running.then(release, release);
5405
+ void tail.then(() => {
5406
+ if (pending.get(resource) === tail) pending.delete(resource);
5407
+ });
5408
+ }
5409
+ }
5410
+
4661
5411
  // packages/mcp-oauth/src/client/default-oauth-client-provider.ts
4662
5412
  var MAX_JS_DATE_MS3 = 864e13;
4663
5413
  function createOAuthClientProvider(options) {
@@ -4667,31 +5417,130 @@ function createOAuthClientProvider(options) {
4667
5417
  return createDefaultOAuthClientProvider(options);
4668
5418
  }
4669
5419
  function createDefaultOAuthClientProvider(options) {
4670
- const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore);
4671
- const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore);
4672
- const now = options.now ?? Date.now;
5420
+ const clock = options.now?.bind(options) ?? Date.now;
5421
+ const browser = snapshotLoopbackAuthorizationOptions(options.browser);
5422
+ const clientMode = options.client.mode;
5423
+ const interactiveEnabled = options.allowInteractive !== false;
5424
+ const sessionLockTimeoutMs = options.sessionLockTimeoutMs;
5425
+ loopbackTarget(browser);
5426
+ assertPersistenceNamespace(options.persistenceNamespace);
5427
+ const clientMetadata = getClientMetadata(options.client);
5428
+ const requestedScope = clientMetadata?.scope;
5429
+ const configuredClient = normalizeConfiguredClient(options.client);
5430
+ const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
5431
+ const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
5432
+ const resourceIdentity = options.resourceIdentity, persistenceNamespace = options.persistenceNamespace, selectedSessionStore = options.sessionStore;
5433
+ const suppliedGrant = options.initialGrant;
5434
+ const capturedGrant = suppliedGrant === void 0 ? void 0 : { resource: suppliedGrant.resource, tokens: snapshotInitialGrantTokens(suppliedGrant.tokens) };
5435
+ const authStore = options.authStore === void 0 ? void 0 : snapshotOAuthPersistenceOptions(options.authStore);
5436
+ if (resourceIdentity !== void 0 && selectedSessionStore !== void 0)
5437
+ throw new Error("OAuth resourceIdentity requires native-owned persistence; custom stores own their resource trust policy");
5438
+ const now = () => {
5439
+ const timestamp = clock();
5440
+ if (!Number.isSafeInteger(timestamp) || Math.abs(timestamp) > MAX_JS_DATE_MS3)
5441
+ throw new Error("OAuth clock must return valid epoch milliseconds");
5442
+ return timestamp;
5443
+ };
4673
5444
  const registeredClients = /* @__PURE__ */ new Map();
4674
- const refreshPromises = /* @__PURE__ */ new Map();
4675
- const authorizationPromises = /* @__PURE__ */ new Map();
5445
+ const resourceStores = resourceIdentity === void 0 ? void 0 : createResourceBoundOAuthStores(authStore ?? {}, persistenceNamespace, resourceIdentity);
5446
+ const sessionStore = resourceStores?.sessionStore ?? selectedSessionStore ?? createAuthStoreSessionStore(authStore, persistenceNamespace);
5447
+ const clientStore = resourceStores?.clientStore ?? (authStore === void 0 ? null : createAuthStoreClientStore(authStore, persistenceNamespace));
5448
+ if (capturedGrant !== void 0) {
5449
+ let resource;
5450
+ try {
5451
+ resource = new URL2(capturedGrant.resource);
5452
+ } catch {
5453
+ throw new Error("OAuth initial grant resource must be an absolute HTTP URL");
5454
+ }
5455
+ if (resource.protocol !== "http:" && resource.protocol !== "https:" || resource.username || resource.password || resource.href.includes("#"))
5456
+ throw new Error("OAuth initial grant resource must be an HTTP URL without credentials or fragments");
5457
+ }
5458
+ const initialGrant = capturedGrant === void 0 ? void 0 : {
5459
+ resource: canonicalizeResourceIndicator(capturedGrant.resource),
5460
+ tokens: normalizeImportedTokens(capturedGrant.tokens, now),
5461
+ client: configuredClient
5462
+ };
5463
+ if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
5464
+ throw new Error("OAuth initial grant requires valid tokens and the original client ID");
5465
+ if (initialGrant?.tokens !== void 0) {
5466
+ if (requestedScope !== void 0 && initialGrant.tokens.scope !== requestedScope)
5467
+ throw new Error("OAuth initial grant does not match the requested OAuth scope");
5468
+ try {
5469
+ new Headers({ Authorization: `Bearer ${initialGrant.tokens.accessToken}` });
5470
+ } catch {
5471
+ throw new Error("OAuth initial grant access token is not a valid HTTP header value");
5472
+ }
5473
+ }
5474
+ let initialGrantConsumed = false;
4676
5475
  return {
5476
+ async authenticate(input) {
5477
+ input = { ...input, ...input.discover === void 0 ? {} : { discover: input.discover.bind(input) } };
5478
+ input.signal?.throwIfAborted();
5479
+ assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
5480
+ const resource = canonicalizeResourceIndicator(input.requestUrl);
5481
+ let session = await ensureAuthorizedSession(resource, void 0, input.fetch, true, false, input.signal);
5482
+ if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) return { ...session.tokens };
5483
+ if (session === null && !initialGrantConsumed && initialGrant?.resource === resource && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now)) return { ...initialGrant.tokens };
5484
+ if (input.discover === void 0) return;
5485
+ const discovery = structuredClone(await waitForOAuthOperation(input.discover(), input.signal));
5486
+ input.signal?.throwIfAborted();
5487
+ assertRequestMatchesResource(resource, canonicalizeResourceIndicator(discovery.resource));
5488
+ session = await ensureAuthorizedSession(resource, discovery, input.fetch, true, false, input.signal);
5489
+ if (session?.tokens === void 0 || isExpired(session.tokens, now)) throw new Error("OAuth authentication did not establish a usable grant");
5490
+ return { ...session.tokens };
5491
+ },
4677
5492
  async authorizeRequest(input) {
5493
+ input = { ...input };
4678
5494
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
4679
5495
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
4680
- const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false);
5496
+ const session = await ensureAuthorizedSession(requestUrl, void 0, input.fetch, false, false, input.signal);
4681
5497
  const accessToken = session?.tokens?.accessToken;
5498
+ if (session === null && !initialGrantConsumed && initialGrant?.resource === requestUrl && initialGrant.tokens !== void 0 && !isExpired(initialGrant.tokens, now)) {
5499
+ input.headers.set("Authorization", `Bearer ${initialGrant.tokens.accessToken}`);
5500
+ return { ...initialGrant.tokens };
5501
+ }
4682
5502
  if (session === null || accessToken === void 0 || session.tokens === void 0 || isExpired(session.tokens, now)) {
4683
5503
  return;
4684
5504
  }
4685
5505
  assertRequestMatchesResource(requestUrl, session.resource);
4686
5506
  input.headers.set("Authorization", `Bearer ${accessToken}`);
5507
+ return { ...session.tokens };
4687
5508
  },
4688
5509
  async handleUnauthorized(input) {
5510
+ input = { ...input };
4689
5511
  try {
5512
+ input.signal?.throwIfAborted();
5513
+ const presented = input.presentedTokens == null ? input.presentedTokens : normalizeStoredTokens(input.presentedTokens);
5514
+ if (input.presentedTokens != null && presented === void 0)
5515
+ throw new Error("OAuth rejected-request provenance does not match its authorization header");
5516
+ const challengeError = input.challenge?.params.error;
5517
+ input = {
5518
+ ...input,
5519
+ discovery: structuredClone(input.discovery),
5520
+ ...input.requestHeaders === void 0 ? {} : { requestHeaders: new Headers(input.requestHeaders) },
5521
+ ...presented === void 0 ? {} : { presentedTokens: presented }
5522
+ };
4690
5523
  assertNoAccessTokenInUrl(input.requestUrl, "Protected resource request URL");
4691
5524
  const requestUrl = canonicalizeResourceIndicator(input.requestUrl);
4692
5525
  const resource = canonicalizeResourceIndicator(input.discovery.resource);
4693
5526
  assertRequestMatchesResource(requestUrl, resource);
4694
- const forceRefresh = hasCachedAccessToken(await loadSession(resource)) && input.challenge?.params.error === "invalid_token";
5527
+ const cached = await waitForOAuthOperation(loadSession(resource), input.signal);
5528
+ const currentTokens = cached?.tokens ?? (!initialGrantConsumed && initialGrant?.resource === resource ? initialGrant.tokens : void 0);
5529
+ let rejectedCurrentGrant = hasCachedAccessToken(cached) || !initialGrantConsumed && initialGrant?.resource === resource;
5530
+ let presentedTokens = input.presentedTokens;
5531
+ if (input.presentedTokens !== void 0) {
5532
+ rejectedCurrentGrant = false;
5533
+ if (input.presentedTokens !== null) {
5534
+ const presented2 = input.presentedTokens;
5535
+ const header = input.requestHeaders?.get("Authorization") ?? "";
5536
+ const separator = header.indexOf(" ");
5537
+ if (header.slice(0, separator).toLowerCase() !== "bearer" || header.slice(separator + 1).trim() !== presented2.accessToken)
5538
+ throw new Error("OAuth rejected-request provenance does not match its authorization header");
5539
+ presentedTokens = presented2;
5540
+ rejectedCurrentGrant = currentTokens !== void 0 && sameTokenGrant(currentTokens, presented2);
5541
+ }
5542
+ }
5543
+ const forceRefresh = rejectedCurrentGrant && (challengeError === "invalid_token" || input.presentedTokens !== void 0 && challengeError === void 0);
4695
5544
  const session = await ensureAuthorizedSession(
4696
5545
  resource,
4697
5546
  {
@@ -4700,13 +5549,16 @@ function createDefaultOAuthClientProvider(options) {
4700
5549
  },
4701
5550
  input.fetch,
4702
5551
  true,
4703
- forceRefresh
5552
+ forceRefresh,
5553
+ input.signal,
5554
+ presentedTokens
4704
5555
  );
4705
5556
  if (session?.tokens?.accessToken === void 0) {
4706
5557
  return { action: "fail" };
4707
5558
  }
4708
5559
  return { action: "retry" };
4709
5560
  } catch (error) {
5561
+ input.signal?.throwIfAborted();
4710
5562
  return {
4711
5563
  action: "fail",
4712
5564
  error: error instanceof Error ? error : new Error(String(error))
@@ -4714,203 +5566,248 @@ function createDefaultOAuthClientProvider(options) {
4714
5566
  }
4715
5567
  }
4716
5568
  };
4717
- async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false) {
4718
- const canonicalResource = canonicalizeResourceIndicator(resource);
4719
- let session = await loadSession(canonicalResource);
4720
- if (discovery !== void 0 && getOwnString2(
4721
- discovery.authorizationServerMetadata,
4722
- "issuer"
4723
- ) !== discovery.authorizationServer) {
4724
- throw new Error("OAuth discovery authorization-server issuer mismatch");
4725
- }
4726
- if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource || getOwnString2(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer || discovery !== void 0 && discovery.authorizationServer !== session.authorizationServer)) {
4727
- await clearSession(canonicalResource);
4728
- session = null;
4729
- }
4730
- const sessionDiscovery = resolveDiscovery(discovery, session);
4731
- if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
4732
- return session;
5569
+ async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal, rejectedTokens) {
5570
+ signal?.throwIfAborted();
5571
+ if (discovery !== void 0) {
5572
+ if (getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer)
5573
+ throw new Error("OAuth discovery authorization-server issuer mismatch");
5574
+ assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4733
5575
  }
4734
- if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
4735
- session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2);
4736
- if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
5576
+ const canonicalResource = canonicalizeResourceIndicator(resource);
5577
+ return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
5578
+ let session = await loadSession(canonicalResource);
5579
+ if (resourceStores !== void 0) {
5580
+ registeredClients.clear();
5581
+ if (!resourceStores.initialGrantAllowed) initialGrantConsumed = true;
5582
+ }
5583
+ if (session !== null) assertRegistrationIssuer(session.client, session.authorizationServer);
5584
+ if (configuredClient !== null && discovery !== void 0) assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
5585
+ if (session !== null && initialGrant?.resource === canonicalResource) initialGrantConsumed = true;
5586
+ signal?.throwIfAborted();
5587
+ if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource || getOwnString2(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer || discovery !== void 0 && discovery.authorizationServer !== session.authorizationServer)) {
5588
+ await clearSession(canonicalResource);
5589
+ session = null;
5590
+ }
5591
+ if (session === null && discovery !== void 0 && !initialGrantConsumed && initialGrant?.resource === canonicalResource && initialGrant.tokens !== void 0 && initialGrant.client !== null) {
5592
+ session = {
5593
+ resource: canonicalResource,
5594
+ authorizationServer: discovery.authorizationServer,
5595
+ client: initialGrant.client,
5596
+ tokens: initialGrant.tokens,
5597
+ ...requestedScope === void 0 ? {} : { requestedScope },
5598
+ discovery: toStoredDiscovery(discovery)
5599
+ };
5600
+ await saveSession(canonicalResource, session);
5601
+ initialGrantConsumed = true;
5602
+ signal?.throwIfAborted();
5603
+ }
5604
+ if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
5605
+ forceRefresh = false;
5606
+ const sessionDiscovery = resolveDiscovery(discovery, session);
5607
+ if ((clientMode === "static" || configuredClient !== null || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
5608
+ const configured = configuredClient;
5609
+ if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
5610
+ throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
5611
+ }
5612
+ if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
5613
+ throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
5614
+ if (configuredTokenMethod !== void 0 && session !== null && (session.tokens !== void 0 || session.refreshState === "pending") && (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === void 0 ? "none" : "client_secret_post")) !== configuredTokenMethod)
5615
+ throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
5616
+ if (session?.refreshState === "pending") {
5617
+ if (!allowInteractive || !interactiveEnabled || sessionDiscovery === void 0)
5618
+ throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
5619
+ return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
5620
+ }
5621
+ if (session?.tokens !== void 0 && !forceRefresh && !isExpired(session.tokens, now)) {
4737
5622
  return session;
4738
5623
  }
4739
- }
4740
- if (forceRefresh && session?.tokens !== void 0) {
4741
- session = clearSessionTokens(session);
4742
- await saveSession(canonicalResource, session);
4743
- }
4744
- if (!allowInteractive || sessionDiscovery === void 0) {
4745
- return session;
4746
- }
4747
- return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2);
5624
+ if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
5625
+ if (hasExpiredClientSecret(session.client, now)) {
5626
+ if (!allowInteractive || !interactiveEnabled || clientMode === "static" || configuredClient?.registration !== void 0)
5627
+ throw new Error("OAuth client secret has expired; authorize again or update the imported registration");
5628
+ return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
5629
+ }
5630
+ session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
5631
+ if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
5632
+ return session;
5633
+ }
5634
+ }
5635
+ if (forceRefresh && session?.tokens !== void 0) {
5636
+ session = clearSessionTokens(session);
5637
+ await saveSession(canonicalResource, session);
5638
+ }
5639
+ if (!allowInteractive || sessionDiscovery === void 0) {
5640
+ return session;
5641
+ }
5642
+ if (!interactiveEnabled) throw new Error("OAuth interactive authorization is disabled");
5643
+ return authorizeSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
5644
+ }, { signal, timeoutMs: sessionLockTimeoutMs });
4748
5645
  }
4749
- async function refreshSession(resource, session, discovery, fetch2) {
5646
+ async function refreshSession(resource, session, discovery, fetch2, signal) {
5647
+ signal?.throwIfAborted();
4750
5648
  assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4751
- const inFlight = refreshPromises.get(resource);
4752
- if (inFlight !== void 0) {
4753
- return inFlight;
5649
+ if (session.tokens?.refreshToken === void 0) {
5650
+ return session;
4754
5651
  }
4755
- const promise = (async () => {
5652
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
5653
+ const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
5654
+ await saveSession(resource, pendingSession);
5655
+ signal?.throwIfAborted();
5656
+ let refreshAttempted = false;
5657
+ let refreshedTokens;
5658
+ while (true) {
4756
5659
  try {
4757
- if (session.tokens?.refreshToken === void 0) {
4758
- return session;
5660
+ refreshedTokens = await refreshAccessToken({
5661
+ tokenEndpoint: requireOwnString(
5662
+ discovery.authorizationServerMetadata,
5663
+ "token_endpoint",
5664
+ "Authorization server metadata"
5665
+ ),
5666
+ clientId: session.client.clientId,
5667
+ clientSecret: session.client.clientSecret,
5668
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
5669
+ refreshToken: session.tokens.refreshToken,
5670
+ resource,
5671
+ fetch: fetch2,
5672
+ signal,
5673
+ now
5674
+ });
5675
+ break;
5676
+ } catch (error) {
5677
+ signal?.throwIfAborted();
5678
+ if (!(error instanceof OAuthError) || !error.outcomeKnown) throw error;
5679
+ if (error.error === "invalid_grant") {
5680
+ const clearedSession = clearSessionTokens(session);
5681
+ await saveSession(resource, clearedSession);
5682
+ return clearedSession;
5683
+ }
5684
+ if (shouldReRegisterStoredDynamicClient(
5685
+ error,
5686
+ await loadRegisteredClient(discovery.authorizationServer),
5687
+ false
5688
+ )) {
5689
+ await clearRegisteredClient(discovery.authorizationServer);
5690
+ await clearSession(resource);
5691
+ return null;
4759
5692
  }
4760
- let refreshAttempted = false;
4761
- let refreshedTokens;
4762
- while (true) {
4763
- try {
4764
- refreshedTokens = await refreshAccessToken({
4765
- tokenEndpoint: requireOwnString(
4766
- discovery.authorizationServerMetadata,
4767
- "token_endpoint",
4768
- "Authorization server metadata"
4769
- ),
4770
- clientId: session.client.clientId,
4771
- clientSecret: session.client.clientSecret,
4772
- refreshToken: session.tokens.refreshToken,
4773
- resource,
4774
- fetch: fetch2,
4775
- now
4776
- });
4777
- break;
4778
- } catch (error) {
4779
- if (error instanceof OAuthError && error.error === "invalid_grant") {
4780
- const clearedSession = clearSessionTokens(session);
4781
- await saveSession(resource, clearedSession);
4782
- return clearedSession;
4783
- }
4784
- if (shouldReRegisterStoredDynamicClient(
4785
- error,
4786
- await loadRegisteredClient(discovery.authorizationServer),
4787
- false
4788
- )) {
4789
- await clearRegisteredClient(discovery.authorizationServer);
4790
- await clearSession(resource);
4791
- return null;
4792
- }
4793
- if (!refreshAttempted && isRetryableOAuthError(error)) {
4794
- refreshAttempted = true;
4795
- continue;
4796
- }
4797
- throw error;
4798
- }
5693
+ if (!refreshAttempted && isRetryableOAuthError(error)) {
5694
+ refreshAttempted = true;
5695
+ continue;
4799
5696
  }
4800
- const updatedSession = {
4801
- ...session,
4802
- tokens: {
4803
- ...refreshedTokens,
4804
- refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken
4805
- },
4806
- discovery: toStoredDiscovery(discovery)
4807
- };
4808
- await saveSession(resource, updatedSession);
4809
- return updatedSession;
4810
- } finally {
4811
- refreshPromises.delete(resource);
5697
+ await saveSession(resource, session);
5698
+ throw error;
4812
5699
  }
4813
- })();
4814
- refreshPromises.set(resource, promise);
4815
- return promise;
4816
- }
4817
- async function authorizeSession(resource, existingSession, discovery, fetch2) {
4818
- const inFlight = authorizationPromises.get(resource);
4819
- if (inFlight !== void 0) {
4820
- return inFlight;
4821
5700
  }
4822
- const promise = (async () => {
4823
- assertS256PkceSupport(discovery.authorizationServerMetadata);
4824
- assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
4825
- let currentSession = existingSession;
4826
- let transientRetryAttempted = false;
4827
- let reRegistrationAttempted = false;
4828
- while (true) {
4829
- const loopback = await createLoopbackAuthorizationSession({
4830
- openBrowser: options.browser.openBrowser,
4831
- readLine: options.browser.readLine,
4832
- createServer: options.browser.createServer,
4833
- landingPage: options.browser.landingPage
5701
+ const updatedSession = {
5702
+ ...session,
5703
+ tokens: {
5704
+ ...refreshedTokens,
5705
+ refreshToken: refreshedTokens.refreshToken ?? session.tokens.refreshToken,
5706
+ scope: refreshedTokens.scope ?? session.tokens.scope
5707
+ },
5708
+ discovery: toStoredDiscovery(discovery)
5709
+ };
5710
+ if (requestedScope !== void 0 && normalizeOAuthScope(updatedSession.tokens?.scope ?? session.requestedScope) !== requestedScope)
5711
+ throw new Error("OAuth refresh response does not match the requested OAuth scope; authorize again");
5712
+ await saveSession(resource, updatedSession);
5713
+ return updatedSession;
5714
+ }
5715
+ async function authorizeSession(resource, existingSession, discovery, fetch2, signal) {
5716
+ signal?.throwIfAborted();
5717
+ assertS256PkceSupport(discovery.authorizationServerMetadata);
5718
+ assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
5719
+ let currentSession = existingSession;
5720
+ let transientRetryAttempted = false;
5721
+ let reRegistrationAttempted = false;
5722
+ while (true) {
5723
+ const loopback = await createLoopbackAuthorizationSession({
5724
+ ...browser,
5725
+ signal: browser.signal === void 0 ? signal : signal === void 0 ? browser.signal : AbortSignal.any([signal, browser.signal])
5726
+ });
5727
+ let resolvedClient = null;
5728
+ try {
5729
+ resolvedClient = await resolveClient(
5730
+ currentSession,
5731
+ discovery,
5732
+ loopback.redirectUri,
5733
+ fetch2,
5734
+ signal
5735
+ );
5736
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
5737
+ const sessionWithoutTokens = {
5738
+ resource,
5739
+ authorizationServer: discovery.authorizationServer,
5740
+ client: resolvedClient.client,
5741
+ ...requestedScope === void 0 ? {} : { requestedScope },
5742
+ discovery: toStoredDiscovery(discovery)
5743
+ };
5744
+ await saveSession(resource, sessionWithoutTokens);
5745
+ const verifier = generateCodeVerifier();
5746
+ const challenge = generateCodeChallenge(verifier);
5747
+ const authorizationUrl = buildAuthorizationUrl({
5748
+ metadata: discovery.authorizationServerMetadata,
5749
+ resource,
5750
+ clientId: resolvedClient.client.clientId,
5751
+ redirectUri: loopback.redirectUri,
5752
+ codeChallenge: challenge,
5753
+ clientMetadata
4834
5754
  });
4835
- let resolvedClient = null;
4836
- try {
4837
- resolvedClient = await resolveClient(
4838
- currentSession,
4839
- discovery,
4840
- loopback.redirectUri,
4841
- fetch2
4842
- );
4843
- const sessionWithoutTokens = {
4844
- resource,
4845
- authorizationServer: discovery.authorizationServer,
4846
- client: resolvedClient.client,
4847
- discovery: toStoredDiscovery(discovery)
4848
- };
4849
- await saveSession(resource, sessionWithoutTokens);
4850
- const verifier = generateCodeVerifier();
4851
- const challenge = generateCodeChallenge(verifier);
4852
- const authorizationUrl = buildAuthorizationUrl({
4853
- metadata: discovery.authorizationServerMetadata,
4854
- resource,
4855
- clientId: resolvedClient.client.clientId,
4856
- redirectUri: loopback.redirectUri,
4857
- codeChallenge: challenge,
4858
- clientMetadata: getClientMetadata(options.client)
4859
- });
4860
- const code = await loopback.waitForCode(authorizationUrl);
4861
- const tokens = await exchangeAuthorizationCode({
4862
- tokenEndpoint: requireOwnString(
4863
- discovery.authorizationServerMetadata,
4864
- "token_endpoint",
4865
- "Authorization server metadata"
4866
- ),
4867
- clientId: resolvedClient.client.clientId,
4868
- clientSecret: resolvedClient.client.clientSecret,
4869
- code,
4870
- codeVerifier: verifier,
4871
- redirectUri: loopback.redirectUri,
4872
- resource,
4873
- fetch: fetch2,
4874
- now
4875
- });
4876
- const session = {
4877
- ...sessionWithoutTokens,
4878
- tokens
4879
- };
4880
- await saveSession(resource, session);
4881
- return session;
4882
- } catch (error) {
4883
- if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
4884
- reRegistrationAttempted = true;
4885
- await clearRegisteredClient(discovery.authorizationServer);
4886
- await clearSession(resource);
4887
- currentSession = null;
4888
- continue;
4889
- }
4890
- if (!transientRetryAttempted && isRetryableOAuthError(error)) {
4891
- transientRetryAttempted = true;
4892
- await clearSession(resource);
4893
- currentSession = null;
4894
- continue;
4895
- }
4896
- throw error;
4897
- } finally {
4898
- loopback.close();
5755
+ const code = await loopback.waitForCode(authorizationUrl);
5756
+ const tokens = await exchangeAuthorizationCode({
5757
+ tokenEndpoint: requireOwnString(
5758
+ discovery.authorizationServerMetadata,
5759
+ "token_endpoint",
5760
+ "Authorization server metadata"
5761
+ ),
5762
+ clientId: resolvedClient.client.clientId,
5763
+ clientSecret: resolvedClient.client.clientSecret,
5764
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
5765
+ code,
5766
+ codeVerifier: verifier,
5767
+ redirectUri: loopback.redirectUri,
5768
+ resource,
5769
+ fetch: fetch2,
5770
+ signal,
5771
+ now
5772
+ });
5773
+ if (requestedScope !== void 0 && tokens.scope !== void 0 && normalizeOAuthScope(tokens.scope) !== requestedScope)
5774
+ throw new Error("OAuth authorization response does not match the requested OAuth scope");
5775
+ const session = {
5776
+ ...sessionWithoutTokens,
5777
+ tokens
5778
+ };
5779
+ await saveSession(resource, session);
5780
+ return session;
5781
+ } catch (error) {
5782
+ signal?.throwIfAborted();
5783
+ if (shouldReRegisterStoredDynamicClient(error, resolvedClient, reRegistrationAttempted)) {
5784
+ reRegistrationAttempted = true;
5785
+ await clearRegisteredClient(discovery.authorizationServer);
5786
+ await clearSession(resource);
5787
+ currentSession = null;
5788
+ continue;
4899
5789
  }
5790
+ if (!transientRetryAttempted && isRetryableOAuthError(error)) {
5791
+ transientRetryAttempted = true;
5792
+ await clearSession(resource);
5793
+ currentSession = null;
5794
+ continue;
5795
+ }
5796
+ throw error;
5797
+ } finally {
5798
+ loopback.close();
4900
5799
  }
4901
- })();
4902
- const finalPromise = promise.finally(() => {
4903
- authorizationPromises.delete(resource);
4904
- });
4905
- authorizationPromises.set(resource, finalPromise);
4906
- return finalPromise;
5800
+ }
4907
5801
  }
4908
- async function resolveClient(existingSession, discovery, redirectUri, fetch2) {
4909
- const configuredClient = normalizeConfiguredClient(options.client);
4910
- if (options.client.mode === "static") {
5802
+ async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
5803
+ parentSignal?.throwIfAborted();
5804
+ if (clientMode === "static" || configuredClient !== null) {
4911
5805
  if (configuredClient === null) {
4912
5806
  throw new Error("OAuth client_id must not be blank");
4913
5807
  }
5808
+ assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
5809
+ if (hasExpiredClientSecret(configuredClient, now))
5810
+ throw new Error("OAuth client secret has expired; update the imported registration");
4914
5811
  return {
4915
5812
  kind: "static",
4916
5813
  fromStoredRegistration: false,
@@ -4921,14 +5818,22 @@ function createDefaultOAuthClientProvider(options) {
4921
5818
  discovery.authorizationServerMetadata,
4922
5819
  "registration_endpoint"
4923
5820
  );
4924
- if (registrationEndpoint === void 0 && configuredClient !== null) {
4925
- return {
4926
- kind: "static",
4927
- fromStoredRegistration: false,
4928
- client: configuredClient
4929
- };
5821
+ let storedClient = await loadRegisteredClient(discovery.authorizationServer);
5822
+ const importedClient = existingSession?.client.registrationOwnership === "caller" ? existingSession.client : storedClient?.registrationOwnership === "caller" ? storedClient : void 0;
5823
+ if (importedClient !== void 0) {
5824
+ assertRegistrationIssuer(importedClient, discovery.authorizationServer);
5825
+ if (hasExpiredClientSecret(importedClient, now)) throw new Error("OAuth client secret has expired; update the imported registration");
5826
+ if (!registrationMatchesRedirect(importedClient, redirectUri))
5827
+ throw new Error("OAuth imported registration does not match the configured redirect URI; update its callback configuration");
5828
+ return { kind: "static", fromStoredRegistration: false, client: importedClient };
5829
+ }
5830
+ if (storedClient !== null) {
5831
+ assertRegistrationIssuer(storedClient, discovery.authorizationServer);
5832
+ if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
5833
+ await clearRegisteredClient(discovery.authorizationServer);
5834
+ storedClient = null;
5835
+ }
4930
5836
  }
4931
- const storedClient = await loadRegisteredClient(discovery.authorizationServer);
4932
5837
  if (storedClient !== null) {
4933
5838
  return {
4934
5839
  kind: "dynamic",
@@ -4937,7 +5842,7 @@ function createDefaultOAuthClientProvider(options) {
4937
5842
  };
4938
5843
  }
4939
5844
  if (registrationEndpoint === void 0) {
4940
- if (existingSession !== null && existingSession.client.clientId.length > 0) {
5845
+ if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
4941
5846
  return {
4942
5847
  kind: "dynamic",
4943
5848
  fromStoredRegistration: true,
@@ -4946,52 +5851,62 @@ function createDefaultOAuthClientProvider(options) {
4946
5851
  }
4947
5852
  throw new Error("Authorization server metadata is missing registration_endpoint");
4948
5853
  }
4949
- if (existingSession !== null && existingSession.client.clientId.length > 0) {
4950
- const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
4951
- if (!isConfiguredStaticFallback) {
4952
- await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
4953
- return {
4954
- kind: "dynamic",
4955
- fromStoredRegistration: true,
4956
- client: existingSession.client
4957
- };
4958
- }
5854
+ if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
5855
+ await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
5856
+ return {
5857
+ kind: "dynamic",
5858
+ fromStoredRegistration: true,
5859
+ client: existingSession.client
5860
+ };
4959
5861
  }
5862
+ const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
5863
+ const registrationMethod = requestedTokenMethod ?? (supported === void 0 ? "none" : ["none", "client_secret_basic", "client_secret_post"].find((method) => supported.includes(method)));
5864
+ if (registrationMethod === void 0 || supported !== void 0 && !supported.includes(registrationMethod))
5865
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
4960
5866
  const registrationBody = buildClientRegistrationBody(
4961
- getClientMetadata(options.client),
4962
- redirectUri
5867
+ clientMetadata,
5868
+ redirectUri,
5869
+ registrationMethod
4963
5870
  );
4964
- const signal = AbortSignal.timeout(3e4);
5871
+ const deadline = AbortSignal.timeout(3e4);
5872
+ const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
4965
5873
  const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
4966
5874
  method: "POST",
4967
5875
  headers: {
5876
+ Accept: "application/json",
4968
5877
  "Content-Type": "application/json"
4969
5878
  },
4970
5879
  body: JSON.stringify(registrationBody),
4971
5880
  signal
4972
5881
  });
4973
5882
  const payload = await readOAuthJsonObjectResponse(response, signal);
4974
- const clientId = getOwnString2(payload, "client_id");
4975
- if (clientId === void 0 || clientId.trim().length === 0) {
4976
- throw new Error("OAuth client registration response missing client_id");
4977
- }
4978
- const clientSecret = getOwnString2(payload, "client_secret");
5883
+ const registration = parseOAuthClientRegistration(payload);
5884
+ const registeredSecret = getOwnString2(registration, "client_secret");
5885
+ const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry6(registration, "token_endpoint_auth_method")) ?? requestedTokenMethod ?? (supported === void 0 ? void 0 : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
4979
5886
  const registeredClient = {
4980
- clientId: clientId.trim(),
4981
- clientSecret: clientSecret !== void 0 && clientSecret.trim().length > 0 ? clientSecret.trim() : void 0
5887
+ clientId: registration.client_id.trim(),
5888
+ ...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
5889
+ ...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
5890
+ registration
4982
5891
  };
4983
- await saveRegisteredClient(discovery.authorizationServer, registeredClient);
5892
+ assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
5893
+ if (hasExpiredClientSecret(registeredClient, now))
5894
+ throw new Error("OAuth client secret has expired in the registration response");
5895
+ if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
5896
+ throw new Error("OAuth registration response does not match the requested redirect URI");
5897
+ const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
5898
+ await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
4984
5899
  return {
4985
5900
  kind: "dynamic",
4986
5901
  fromStoredRegistration: false,
4987
- client: registeredClient
5902
+ client: clientWithRedirect
4988
5903
  };
4989
5904
  }
4990
5905
  async function loadSession(resource) {
4991
5906
  return normalizeLoadedSession(await sessionStore.load(resource));
4992
5907
  }
4993
5908
  async function saveSession(resource, session) {
4994
- await sessionStore.save(resource, session);
5909
+ await sessionStore.save(resource, structuredClone(session));
4995
5910
  }
4996
5911
  async function clearSession(resource) {
4997
5912
  await sessionStore.clear(resource);
@@ -5004,7 +5919,7 @@ function createDefaultOAuthClientProvider(options) {
5004
5919
  return null;
5005
5920
  }
5006
5921
  const client = await clientStore.load(issuer);
5007
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
5922
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
5008
5923
  if (client !== null && normalizedClient === null) {
5009
5924
  await clientStore.clear(issuer);
5010
5925
  return null;
@@ -5061,9 +5976,13 @@ function resolveDiscovery(discovery, session) {
5061
5976
  authorizationServerMetadata: metadata
5062
5977
  };
5063
5978
  }
5979
+ function sameTokenGrant(left, right) {
5980
+ return left.accessToken === right.accessToken && left.refreshToken === right.refreshToken && left.tokenType === right.tokenType && left.expiresAt === right.expiresAt && left.scope === right.scope;
5981
+ }
5064
5982
  function clearSessionTokens(session) {
5065
5983
  const nextSession = { ...session };
5066
5984
  delete nextSession.tokens;
5985
+ delete nextSession.refreshState;
5067
5986
  return nextSession;
5068
5987
  }
5069
5988
  function hasCachedAccessToken(session) {
@@ -5073,34 +5992,41 @@ function normalizeLoadedSession(session) {
5073
5992
  if (session === null) {
5074
5993
  return null;
5075
5994
  }
5076
- const client = normalizeStoredClient(getOwnEntry6(session, "client"));
5995
+ const discovery = structuredClone(session.discovery);
5996
+ const refreshState = getOwnEntry6(session, "refreshState");
5997
+ if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
5998
+ throw new Error("Stored OAuth refresh state is invalid");
5999
+ const client = normalizeStoredOAuthClient(getOwnEntry6(session, "client"));
5077
6000
  if (client === null) {
5078
- return { ...session, client: { clientId: "" }, tokens: void 0 };
5079
- }
5080
- return {
5081
- ...session,
5082
- client,
5083
- tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
5084
- };
5085
- }
5086
- function normalizeStoredClient(value) {
5087
- if (!isObjectRecord3(value)) {
5088
- return null;
6001
+ return { ...session, discovery, client: { clientId: "" }, tokens: void 0 };
5089
6002
  }
5090
- const clientId = getOwnString2(value, "clientId");
5091
- if (clientId === void 0 || clientId.trim().length === 0) {
5092
- return null;
5093
- }
5094
- const normalizedClientId = clientId.trim();
5095
- const clientSecret = getOwnEntry6(value, "clientSecret");
5096
- if (clientSecret === void 0) {
5097
- return { clientId: normalizedClientId };
5098
- }
5099
- if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
5100
- return null;
6003
+ const tokens = normalizeStoredTokens(getOwnEntry6(session, "tokens"));
6004
+ if (tokens !== void 0) {
6005
+ try {
6006
+ new Headers({ Authorization: `Bearer ${tokens.accessToken}` });
6007
+ } catch {
6008
+ throw new Error("Stored OAuth access token is not a valid HTTP header value");
6009
+ }
5101
6010
  }
5102
- const normalizedClientSecret = clientSecret.trim();
5103
- return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
6011
+ return { ...session, discovery, client, tokens };
6012
+ }
6013
+ function snapshotInitialGrantTokens(value) {
6014
+ if (!isObjectRecord3(value)) return value;
6015
+ const expiresAt = getOwnEntry6(value, "expiresAt"), expiresIn = getOwnEntry6(value, "expiresIn"), issuedAt = getOwnEntry6(value, "issuedAt");
6016
+ return { ...value, expiresAt, expiresIn, issuedAt };
6017
+ }
6018
+ function normalizeImportedTokens(value, now) {
6019
+ if (!isObjectRecord3(value)) return void 0;
6020
+ const absolute = getOwnEntry6(value, "expiresAt");
6021
+ const lifetime = getOwnEntry6(value, "expiresIn");
6022
+ const issuedAt = getOwnEntry6(value, "issuedAt");
6023
+ const snapshot = { ...value };
6024
+ if (lifetime !== void 0 && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
6025
+ throw new Error("OAuth initial grant has invalid relative expiry");
6026
+ if (issuedAt !== void 0 && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS3))
6027
+ throw new Error("OAuth initial grant has invalid issuance time");
6028
+ const expiresAt = absolute !== void 0 && absolute !== null ? absolute : lifetime === void 0 ? null : (issuedAt === void 0 ? now() : issuedAt) + lifetime * 1e3;
6029
+ return normalizeStoredTokens({ ...snapshot, expiresAt });
5104
6030
  }
5105
6031
  function normalizeStoredTokens(value) {
5106
6032
  if (value === void 0 || !isObjectRecord3(value)) {
@@ -5110,10 +6036,10 @@ function normalizeStoredTokens(value) {
5110
6036
  const tokenType = getOwnString2(value, "tokenType");
5111
6037
  const expiresAt = getOwnEntry6(value, "expiresAt");
5112
6038
  const refreshToken = getOwnEntry6(value, "refreshToken");
5113
- const scope = getOwnString2(value, "scope");
6039
+ const scope = getOwnEntry6(value, "scope");
5114
6040
  const normalizedAccessToken = accessToken?.trim();
5115
6041
  const normalizedRefreshToken = typeof refreshToken === "string" ? refreshToken.trim() : void 0;
5116
- const normalizedScope = scope?.trim();
6042
+ const normalizedScope = normalizeOAuthScope(scope);
5117
6043
  if (accessToken === void 0 || normalizedAccessToken === void 0 || normalizedAccessToken.length === 0 || tokenType !== "Bearer" || !(expiresAt === null || typeof expiresAt === "number" && Number.isSafeInteger(expiresAt) && expiresAt <= MAX_JS_DATE_MS3 && Number.isFinite(new Date(expiresAt).getTime())) || refreshToken !== void 0 && (typeof refreshToken !== "string" || normalizedRefreshToken === void 0 || normalizedRefreshToken.length === 0)) {
5118
6044
  return void 0;
5119
6045
  }
@@ -5131,18 +6057,23 @@ function getClientMetadata(client) {
5131
6057
  }
5132
6058
  return {
5133
6059
  clientName: normalizeOptionalOAuthString(client.metadata.clientName),
5134
- scope: normalizeOptionalOAuthString(client.metadata.scope),
6060
+ scope: normalizeOAuthScope(client.metadata.scope),
5135
6061
  softwareId: normalizeOptionalOAuthString(client.metadata.softwareId),
5136
6062
  softwareVersion: normalizeOptionalOAuthString(client.metadata.softwareVersion)
5137
6063
  };
5138
6064
  }
5139
6065
  function normalizeConfiguredClient(client) {
5140
- const clientId = normalizeOptionalOAuthString(client.clientId);
5141
- if (clientId === void 0) {
5142
- return null;
5143
- }
5144
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
5145
- return clientSecret === void 0 ? { clientId } : { clientId, clientSecret };
6066
+ const registration = client.registration === void 0 ? void 0 : parseOAuthClientRegistration(client.registration);
6067
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
6068
+ if (clientId === void 0) return null;
6069
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
6070
+ return normalizeStoredOAuthClient({
6071
+ clientId,
6072
+ clientSecret,
6073
+ registration,
6074
+ ...registration === void 0 ? {} : { registrationOwnership: "caller" },
6075
+ tokenEndpointAuthMethod: client.tokenEndpointAuthMethod
6076
+ });
5146
6077
  }
5147
6078
  function normalizeOptionalOAuthString(value) {
5148
6079
  if (value === void 0) {
@@ -5213,18 +6144,20 @@ function isLoopbackHostname(hostname2) {
5213
6144
  }
5214
6145
  function assertSecureUrl(value, label) {
5215
6146
  const url = new URL2(value);
5216
- if (url.username !== "" || url.password !== "" || url.hash !== "") {
6147
+ if (url.username !== "" || url.password !== "" || url.href.includes("#")) {
5217
6148
  throw new Error(`${label} must not include credentials or fragment`);
5218
6149
  }
5219
6150
  if (url.protocol === "https:") {
5220
- return;
6151
+ return url;
5221
6152
  }
5222
6153
  if (url.protocol === "http:" && isLoopbackHostname(url.hostname)) {
5223
- return;
6154
+ return url;
5224
6155
  }
5225
6156
  throw new Error(`${label} must use https unless it targets a loopback host`);
5226
6157
  }
5227
6158
  function assertSecureOAuthFlowEndpoints(metadata) {
6159
+ const issuer = assertSecureUrl(requireOwnString(metadata, "issuer", "Authorization server metadata"), "Authorization server issuer");
6160
+ if (issuer.href.includes("?")) throw new Error("Authorization server issuer must not include query or fragment");
5228
6161
  const authorizationEndpoint = requireOwnString(
5229
6162
  metadata,
5230
6163
  "authorization_endpoint",
@@ -5258,12 +6191,37 @@ function assertRequestMatchesResource(requestUrl, resource) {
5258
6191
  );
5259
6192
  }
5260
6193
  }
5261
- function buildClientRegistrationBody(metadata, redirectUri) {
6194
+ function assertRegistrationIssuer(client, issuer) {
6195
+ const registrationIssuer = client.registration === void 0 ? void 0 : getOwnString2(client.registration, "issuer");
6196
+ if (registrationIssuer !== void 0 && registrationIssuer !== issuer)
6197
+ throw new Error("OAuth client registration issuer does not match the authorization server");
6198
+ }
6199
+ function hasExpiredClientSecret(client, now) {
6200
+ if (client.clientSecret === void 0 || client.tokenEndpointAuthMethod === "none" || client.registration === void 0) return false;
6201
+ const expiry = getOwnEntry6(client.registration, "client_secret_expires_at");
6202
+ return typeof expiry === "number" && expiry !== 0 && expiry <= now() / 1e3;
6203
+ }
6204
+ function getSupportedTokenAuthMethods(metadata) {
6205
+ const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
6206
+ if (value === void 0) return void 0;
6207
+ if (!Array.isArray(value) || value.length > 128 || value.some((method) => typeof method !== "string"))
6208
+ throw new Error("Invalid OAuth token endpoint authentication metadata");
6209
+ return value;
6210
+ }
6211
+ function assertTokenEndpointAuthentication(client, metadata) {
6212
+ const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === void 0 ? "none" : "client_secret_post");
6213
+ if (method !== "none" && client.clientSecret === void 0)
6214
+ throw new Error("OAuth token endpoint authentication requires a client secret");
6215
+ const supported = getSupportedTokenAuthMethods(metadata);
6216
+ if (supported !== void 0 && !supported.includes(method))
6217
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
6218
+ }
6219
+ function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
5262
6220
  const body = {
5263
6221
  redirect_uris: [redirectUri],
5264
6222
  grant_types: ["authorization_code", "refresh_token"],
5265
6223
  response_types: ["code"],
5266
- token_endpoint_auth_method: "none"
6224
+ token_endpoint_auth_method: tokenEndpointAuthMethod
5267
6225
  };
5268
6226
  const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
5269
6227
  const scope = metadata === void 0 ? void 0 : getOwnString2(metadata, "scope");
@@ -5300,7 +6258,7 @@ function shouldReRegisterStoredDynamicClient(error, client, alreadyAttempted) {
5300
6258
  if ("kind" in client) {
5301
6259
  return client.kind === "dynamic" && client.fromStoredRegistration;
5302
6260
  }
5303
- return true;
6261
+ return client.registrationOwnership !== "caller";
5304
6262
  }
5305
6263
 
5306
6264
  // packages/mcp-oauth/src/server/jwks-token-verifier.ts
@@ -5369,7 +6327,7 @@ function getParameterHeaders(schema) {
5369
6327
  const names = /* @__PURE__ */ new Set();
5370
6328
  const ancestors = /* @__PURE__ */ new Set();
5371
6329
  let nodes = 0;
5372
- const visit = (value, path4, reachable, depth) => {
6330
+ const visit = (value, path6, reachable, depth) => {
5373
6331
  if (++nodes > 1e4 || depth > 64)
5374
6332
  throw new Error("MCP header schema traversal limit exceeded");
5375
6333
  if (typeof value === "boolean") return;
@@ -5378,7 +6336,7 @@ function getParameterHeaders(schema) {
5378
6336
  try {
5379
6337
  if (Object.prototype.hasOwnProperty.call(value, "x-mcp-header")) {
5380
6338
  const name = value["x-mcp-header"];
5381
- if (!reachable || path4.length === 0 || typeof name !== "string" || name.length === 0 || [...name].some((character) => {
6339
+ if (!reachable || path6.length === 0 || typeof name !== "string" || name.length === 0 || [...name].some((character) => {
5382
6340
  const code = character.charCodeAt(0);
5383
6341
  return !tokenPunctuation.has(character) && !(code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122);
5384
6342
  }))
@@ -5388,7 +6346,7 @@ function getParameterHeaders(schema) {
5388
6346
  const key2 = name.toLowerCase();
5389
6347
  if (names.has(key2)) throw new Error("Duplicate x-mcp-header name");
5390
6348
  names.add(key2);
5391
- headers.push({ name: `Mcp-Param-${name}`, path: [...path4], type: value.type });
6349
+ headers.push({ name: `Mcp-Param-${name}`, path: [...path6], type: value.type });
5392
6350
  }
5393
6351
  for (const [keyword, child] of Object.entries(value)) {
5394
6352
  if (schemaMaps.has(keyword)) {
@@ -5396,17 +6354,17 @@ function getParameterHeaders(schema) {
5396
6354
  for (const [key2, nested] of Object.entries(child))
5397
6355
  visit(
5398
6356
  nested,
5399
- keyword === "properties" ? [...path4, key2] : path4,
6357
+ keyword === "properties" ? [...path6, key2] : path6,
5400
6358
  reachable && keyword === "properties",
5401
6359
  depth + 1
5402
6360
  );
5403
6361
  } else if (schemaArrays.has(keyword)) {
5404
6362
  if (!Array.isArray(child)) throw new Error("Invalid schema array");
5405
- for (const nested of child) visit(nested, path4, false, depth + 1);
6363
+ for (const nested of child) visit(nested, path6, false, depth + 1);
5406
6364
  } else if (schemaChildren.has(keyword)) {
5407
6365
  if (Array.isArray(child))
5408
- for (const nested of child) visit(nested, path4, false, depth + 1);
5409
- else visit(child, path4, false, depth + 1);
6366
+ for (const nested of child) visit(nested, path6, false, depth + 1);
6367
+ else visit(child, path6, false, depth + 1);
5410
6368
  }
5411
6369
  }
5412
6370
  } finally {
@@ -5433,6 +6391,21 @@ function createParameterHeaders(definitions, argumentsValue) {
5433
6391
 
5434
6392
  // packages/tiny-mcp-client/src/oauth-discovery.ts
5435
6393
  import { isIP as isIP2 } from "node:net";
6394
+ var metadataErrorBrand = /* @__PURE__ */ Symbol.for("poe-platform.tiny-mcp-client.OAuthMetadataError");
6395
+ var OAuthMetadataError = class extends Error {
6396
+ constructor(phase, message, status) {
6397
+ super(message);
6398
+ this.phase = phase;
6399
+ this.status = status;
6400
+ this.name = "OAuthMetadataError";
6401
+ Object.defineProperty(this, metadataErrorBrand, { value: true });
6402
+ }
6403
+ phase;
6404
+ status;
6405
+ static is(value) {
6406
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, metadataErrorBrand)?.value === true;
6407
+ }
6408
+ };
5436
6409
  function defaultOAuthMetadataFetch(input, init) {
5437
6410
  return fetch(input, init);
5438
6411
  }
@@ -5450,7 +6423,7 @@ function isLoopbackHostname2(hostname2) {
5450
6423
  return normalizedHostname === "localhost" || normalizedHostname === "::1" || normalizedHostname === "[::1]" || isIP2(normalizedHostname) === 4 && normalizedHostname.startsWith("127.");
5451
6424
  }
5452
6425
  function assertSecureUrl2(url, label) {
5453
- if (url.username !== "" || url.password !== "" || url.hash !== "") {
6426
+ if (url.username !== "" || url.password !== "" || url.href.includes("#")) {
5454
6427
  throw new Error(`${label} must not include credentials or fragment`);
5455
6428
  }
5456
6429
  if (url.protocol === "https:") {
@@ -5468,9 +6441,13 @@ function validateProtectedResourceMetadata(value, resourceUrl) {
5468
6441
  if (typeof value.resource !== "string" || value.resource.length === 0) {
5469
6442
  throw new Error("Protected resource metadata must include a resource string");
5470
6443
  }
6444
+ if (value.resource.includes("#")) {
6445
+ throw new OAuthMetadataError("protected-resource", "Protected resource metadata resource must not include fragment");
6446
+ }
5471
6447
  const normalizedResource = canonicalizeResourceIndicator(value.resource);
5472
6448
  if (normalizedResource !== resourceUrl) {
5473
- throw new Error(
6449
+ throw new OAuthMetadataError(
6450
+ "protected-resource",
5474
6451
  `Protected resource metadata resource mismatch: expected ${resourceUrl}, received ${value.resource}`
5475
6452
  );
5476
6453
  }
@@ -5492,7 +6469,8 @@ function validateAuthorizationServerMetadata(value, issuer) {
5492
6469
  throw new Error("Authorization server metadata must include issuer");
5493
6470
  }
5494
6471
  if (value.issuer !== issuer) {
5495
- throw new Error(
6472
+ throw new OAuthMetadataError(
6473
+ "authorization-server",
5496
6474
  `Authorization server metadata issuer mismatch: expected ${issuer}, received ${value.issuer}`
5497
6475
  );
5498
6476
  }
@@ -5515,11 +6493,6 @@ function validateAuthorizationServerMetadata(value, issuer) {
5515
6493
  } catch {
5516
6494
  throw new Error(`Authorization server metadata ${field} must be an absolute URL`);
5517
6495
  }
5518
- if (endpoint.username !== "" || endpoint.password !== "" || endpoint.hash !== "") {
5519
- throw new Error(
5520
- `Authorization server metadata ${field} must not include credentials or fragment`
5521
- );
5522
- }
5523
6496
  assertSecureUrl2(endpoint, `Authorization server metadata ${field}`);
5524
6497
  }
5525
6498
  if (!isStringArray(value.response_types_supported) || !value.response_types_supported.includes("code")) {
@@ -5534,11 +6507,12 @@ function validateAuthorizationServerMetadata(value, issuer) {
5534
6507
  }
5535
6508
  return value;
5536
6509
  }
5537
- async function readJsonResponse(response, label, signal) {
6510
+ async function readJsonResponse(response, phase, signal) {
6511
+ const label = phase === "protected-resource" ? "Protected resource metadata" : "Authorization server metadata";
5538
6512
  if (!response.ok) {
5539
- await response.body?.cancel().catch(() => void 0);
6513
+ void response.body?.cancel().catch(() => void 0);
5540
6514
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
5541
- throw new Error(`${label} request failed (${statusDescriptor})`);
6515
+ throw new OAuthMetadataError(phase, `${label} request failed (${statusDescriptor})`, response.status);
5542
6516
  }
5543
6517
  const text = await readBoundedResponseText(response, 1024 * 1024, void 0, signal);
5544
6518
  try {
@@ -5547,14 +6521,16 @@ async function readJsonResponse(response, label, signal) {
5547
6521
  throw new Error(`${label} response must be valid JSON`);
5548
6522
  }
5549
6523
  }
5550
- async function fetchMetadata(fetch2, location, label) {
5551
- const signal = AbortSignal.timeout(1e4);
6524
+ async function fetchMetadata(fetch2, location, phase, parentSignal) {
6525
+ parentSignal?.throwIfAborted();
6526
+ const deadline = AbortSignal.timeout(1e4);
6527
+ const signal = parentSignal === void 0 ? deadline : AbortSignal.any([deadline, parentSignal]);
5552
6528
  const response = await fetchMcpResponse(fetch2, location, {
5553
6529
  method: "GET",
5554
6530
  headers: { Accept: "application/json" },
5555
6531
  signal
5556
6532
  });
5557
- return readJsonResponse(response, label, signal);
6533
+ return readJsonResponse(response, phase, signal);
5558
6534
  }
5559
6535
  function resolveWellKnownMetadataUrl(inputUrl, suffix) {
5560
6536
  const url = new URL(typeof inputUrl === "string" ? inputUrl : inputUrl.toString());
@@ -5563,7 +6539,7 @@ function resolveWellKnownMetadataUrl(inputUrl, suffix) {
5563
6539
  return url.toString();
5564
6540
  }
5565
6541
  function resolveProtectedResourceMetadataUrl(resourceUrl, resourceMetadataUrl) {
5566
- const resource = new URL(canonicalizeResourceIndicator(resourceUrl));
6542
+ const resource = new URL(typeof resourceUrl === "string" ? resourceUrl : resourceUrl.toString());
5567
6543
  assertSecureUrl2(resource, "Protected resource URL");
5568
6544
  if (resourceMetadataUrl !== void 0) {
5569
6545
  const resolvedResourceMetadataUrl2 = new URL(
@@ -5582,7 +6558,7 @@ function resolveProtectedResourceMetadataUrl(resourceUrl, resourceMetadataUrl) {
5582
6558
  function validateAuthorizationServerIssuer(issuer) {
5583
6559
  const input = typeof issuer === "string" ? issuer : issuer.toString();
5584
6560
  const url = new URL(input);
5585
- if (url.search.length > 0 || url.hash.length > 0) {
6561
+ if (url.href.includes("?") || url.href.includes("#")) {
5586
6562
  throw new Error("Authorization server issuer must not include query or fragment");
5587
6563
  }
5588
6564
  assertSecureUrl2(url, "Authorization server issuer");
@@ -5601,8 +6577,8 @@ function authorizationServerMetadataLocations(issuer) {
5601
6577
  ];
5602
6578
  const issuerUrl = new URL(issuer);
5603
6579
  if (issuerUrl.pathname !== "/") {
5604
- const path4 = issuerUrl.pathname.endsWith("/") ? issuerUrl.pathname.slice(0, -1) : issuerUrl.pathname;
5605
- issuerUrl.pathname = `${path4}/.well-known/openid-configuration`;
6580
+ const path6 = issuerUrl.pathname.endsWith("/") ? issuerUrl.pathname.slice(0, -1) : issuerUrl.pathname;
6581
+ issuerUrl.pathname = `${path6}/.well-known/openid-configuration`;
5606
6582
  locations.push(issuerUrl.toString());
5607
6583
  }
5608
6584
  return locations;
@@ -5616,11 +6592,6 @@ function validateCachedDiscovery(value, resource) {
5616
6592
  }
5617
6593
  const resourceMetadata = validateProtectedResourceMetadata(value.resourceMetadata, resource);
5618
6594
  const resourceMetadataLocation = new URL(value.resourceMetadataUrl);
5619
- if (resourceMetadataLocation.username !== "" || resourceMetadataLocation.password !== "" || resourceMetadataLocation.hash !== "") {
5620
- throw new Error(
5621
- "Cached OAuth discovery metadata location must not include credentials or fragment"
5622
- );
5623
- }
5624
6595
  assertSecureUrl2(resourceMetadataLocation, "Cached OAuth discovery metadata location");
5625
6596
  const issuer = validateAuthorizationServerIssuer(value.authorizationServer);
5626
6597
  if (!resourceMetadata.authorization_servers.includes(issuer)) {
@@ -5642,6 +6613,20 @@ function validateCachedDiscovery(value, resource) {
5642
6613
  authorizationServerMetadata
5643
6614
  });
5644
6615
  }
6616
+ async function waitForCache(operation, signal) {
6617
+ if (signal === void 0) return operation;
6618
+ let abort;
6619
+ try {
6620
+ return await new Promise((resolve, reject) => {
6621
+ abort = () => reject(signal.reason);
6622
+ signal.addEventListener("abort", abort, { once: true });
6623
+ Promise.resolve(operation).then(resolve, reject);
6624
+ if (signal.aborted) abort();
6625
+ });
6626
+ } finally {
6627
+ signal.removeEventListener("abort", abort);
6628
+ }
6629
+ }
5645
6630
  var OAuthMetadataDiscovery = class {
5646
6631
  fetchImpl;
5647
6632
  cache;
@@ -5650,7 +6635,7 @@ var OAuthMetadataDiscovery = class {
5650
6635
  this.fetchImpl = fetch2;
5651
6636
  this.cache = cache;
5652
6637
  }
5653
- async discoverProtectedResource(resource, resourceMetadataUrl) {
6638
+ async discoverProtectedResource(resource, resourceMetadataUrl, signal) {
5654
6639
  const locations = /* @__PURE__ */ new Set([resolveProtectedResourceMetadataUrl(resource, resourceMetadataUrl)]);
5655
6640
  if (resourceMetadataUrl === void 0) {
5656
6641
  locations.add(new URL("/.well-known/oauth-protected-resource", resource).toString());
@@ -5659,34 +6644,37 @@ var OAuthMetadataDiscovery = class {
5659
6644
  for (const location of locations) {
5660
6645
  try {
5661
6646
  const metadata = validateProtectedResourceMetadata(
5662
- await fetchMetadata(this.fetchImpl, location, "Protected resource metadata"),
6647
+ await fetchMetadata(this.fetchImpl, location, "protected-resource", signal),
5663
6648
  resource
5664
6649
  );
5665
6650
  return { location, metadata };
5666
6651
  } catch (error) {
6652
+ signal?.throwIfAborted();
5667
6653
  lastError = error;
5668
6654
  }
5669
6655
  }
5670
6656
  throw lastError;
5671
6657
  }
5672
- async discover(resourceUrl, { resourceMetadataUrl } = {}) {
6658
+ async discover(resourceUrl, { resourceMetadataUrl, signal } = {}) {
6659
+ signal?.throwIfAborted();
5673
6660
  const cacheKey = canonicalizeResourceIndicator(resourceUrl);
5674
- resolveProtectedResourceMetadataUrl(cacheKey, resourceMetadataUrl);
6661
+ resolveProtectedResourceMetadataUrl(resourceUrl, resourceMetadataUrl);
5675
6662
  const memoryCachedResult = this.memoryCache.get(cacheKey);
5676
6663
  if (memoryCachedResult !== void 0 && resourceMetadataUrl === void 0) {
5677
6664
  return structuredClone(memoryCachedResult);
5678
6665
  }
5679
- const sharedCachedResult = await this.cache?.get(cacheKey);
6666
+ const sharedCachedResult = resourceMetadataUrl === void 0 ? await waitForCache(this.cache?.get(cacheKey), signal) : void 0;
6667
+ signal?.throwIfAborted();
5680
6668
  if (sharedCachedResult !== null && sharedCachedResult !== void 0 && resourceMetadataUrl === void 0) {
5681
6669
  try {
5682
6670
  const result = validateCachedDiscovery(sharedCachedResult, cacheKey);
5683
6671
  this.memoryCache.set(cacheKey, structuredClone(result));
5684
6672
  return result;
5685
6673
  } catch {
5686
- await this.cache?.delete?.(cacheKey);
6674
+ await waitForCache(this.cache?.delete?.(cacheKey), signal);
5687
6675
  }
5688
6676
  }
5689
- const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl);
6677
+ const { location: resourceMetadataLocation, metadata: resourceMetadata } = await this.discoverProtectedResource(cacheKey, resourceMetadataUrl, signal);
5690
6678
  const authorizationServerErrors = [];
5691
6679
  for (const authorizationServer of resourceMetadata.authorization_servers) {
5692
6680
  const normalizedAuthorizationServer = validateAuthorizationServerIssuer(authorizationServer);
@@ -5694,7 +6682,7 @@ var OAuthMetadataDiscovery = class {
5694
6682
  for (const authorizationServerMetadataUrl of metadataLocations) {
5695
6683
  try {
5696
6684
  const authorizationServerMetadata = validateAuthorizationServerMetadata(
5697
- await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "Authorization server metadata"),
6685
+ await fetchMetadata(this.fetchImpl, authorizationServerMetadataUrl, "authorization-server", signal),
5698
6686
  normalizedAuthorizationServer
5699
6687
  );
5700
6688
  const result = {
@@ -5706,16 +6694,18 @@ var OAuthMetadataDiscovery = class {
5706
6694
  authorizationServerMetadata
5707
6695
  };
5708
6696
  this.memoryCache.set(cacheKey, structuredClone(result));
5709
- await this.cache?.set(cacheKey, structuredClone(result));
6697
+ await waitForCache(this.cache?.set(cacheKey, structuredClone(result)), signal);
5710
6698
  return result;
5711
6699
  } catch (error) {
6700
+ signal?.throwIfAborted();
5712
6701
  authorizationServerErrors.push(
5713
6702
  `${authorizationServerMetadataUrl}: ${error instanceof Error ? error.message : String(error)}`
5714
6703
  );
5715
6704
  }
5716
6705
  }
5717
6706
  }
5718
- throw new Error(
6707
+ throw new OAuthMetadataError(
6708
+ "authorization-server",
5719
6709
  `Unable to load authorization server metadata for ${cacheKey}: ${authorizationServerErrors.join(
5720
6710
  "; "
5721
6711
  )}`
@@ -5877,7 +6867,11 @@ function parseBearerWwwAuthenticateHeader(headerValue) {
5877
6867
  if (parsedParam === null) {
5878
6868
  break;
5879
6869
  }
5880
- params[parsedParam.name.toLowerCase()] = parsedParam.value;
6870
+ const parameterName = parsedParam.name.toLowerCase();
6871
+ if (scheme.token.toLowerCase() === "bearer" && Object.hasOwn(params, parameterName)) {
6872
+ throw new Error("Bearer challenge must not repeat authentication parameters");
6873
+ }
6874
+ params[parameterName] = parsedParam.value;
5881
6875
  index = skipOptionalWhitespace(headerValue, parsedParam.nextIndex);
5882
6876
  if (headerValue[index] !== ",") {
5883
6877
  break;
@@ -5919,6 +6913,7 @@ var inputResponseTypes = {
5919
6913
  "sampling/createMessage": "CreateMessageResult",
5920
6914
  "elicitation/create": "ElicitResult"
5921
6915
  };
6916
+ var MCP_PROTOCOL_VERSIONS = Object.freeze(["2025-03-26", "2025-06-18", "2025-11-25", "2026-07-28"]);
5922
6917
  var MCP_PROTOCOL_VERSION = "2025-03-26";
5923
6918
  var McpClient = class {
5924
6919
  currentState = "disconnected";
@@ -5962,6 +6957,8 @@ var McpClient = class {
5962
6957
  }
5963
6958
  async connect(transport, options = {}) {
5964
6959
  options.signal?.throwIfAborted();
6960
+ if (this.options.protocolVersion !== void 0 && !MCP_PROTOCOL_VERSIONS.includes(this.options.protocolVersion))
6961
+ throw new Error(`Unsupported protocolVersion; use ${MCP_PROTOCOL_VERSIONS.join(", ")}`);
5965
6962
  if (this.currentState !== "disconnected" && this.currentState !== "closed") {
5966
6963
  throw new Error("MCP client is already connected");
5967
6964
  }
@@ -5971,9 +6968,13 @@ var McpClient = class {
5971
6968
  this.currentInstructions = void 0;
5972
6969
  this.subscribedResourceUris.clear();
5973
6970
  this.activeProgressTokens.clear();
5974
- const transportClosedReason = transport.closed.then((closedEvent) => closedEvent.reason).catch(
6971
+ const transportClosedReason = (transport.closeReason ?? transport.closed.then((closedEvent) => closedEvent.reason)).catch(
5975
6972
  (error) => error instanceof Error ? error : new Error(String(error))
5976
6973
  );
6974
+ let primaryCloseReason;
6975
+ void transportClosedReason.then((reason) => {
6976
+ primaryCloseReason = reason;
6977
+ });
5977
6978
  const messageLayer = new JsonRpcMessageLayer(
5978
6979
  transport.readable,
5979
6980
  transport.writable,
@@ -6090,7 +7091,7 @@ var McpClient = class {
6090
7091
  if (this.transport !== transport) {
6091
7092
  return;
6092
7093
  }
6093
- this.messageLayer?.dispose(closedEvent.reason);
7094
+ this.messageLayer?.dispose(primaryCloseReason ?? closedEvent.reason);
6094
7095
  this.messageLayer = null;
6095
7096
  this.transport = null;
6096
7097
  this.currentState = "closed";
@@ -6130,7 +7131,7 @@ var McpClient = class {
6130
7131
  }
6131
7132
  let discovery;
6132
7133
  try {
6133
- if (this.options.protocolVersion !== "2025-03-26") discovery = await messageLayer.sendRequest("server/discover", {
7134
+ if (this.options.protocolVersion === void 0 || this.options.protocolVersion === "2026-07-28") discovery = await messageLayer.sendRequest("server/discover", {
6134
7135
  _meta: {
6135
7136
  "io.modelcontextprotocol/protocolVersion": "2026-07-28",
6136
7137
  "io.modelcontextprotocol/clientCapabilities": capabilities
@@ -6138,6 +7139,7 @@ var McpClient = class {
6138
7139
  }, { signal: options.signal, timeoutMs: this.options.protocolVersion === "2026-07-28" ? this.options.requestTimeoutMs ?? 3e4 : Math.min(this.options.requestTimeoutMs ?? 3e4, 1e3) });
6139
7140
  } catch (error) {
6140
7141
  options.signal?.throwIfAborted();
7142
+ if (this.options.protocolVersion === "2026-07-28") throw error;
6141
7143
  if (error instanceof McpError && [-32020, -32021, -32022].includes(error.code)) throw error;
6142
7144
  }
6143
7145
  if (discovery !== void 0) {
@@ -6181,7 +7183,7 @@ var McpClient = class {
6181
7183
  };
6182
7184
  }
6183
7185
  const initializeResultValue = await messageLayer.sendRequest("initialize", {
6184
- protocolVersion: MCP_PROTOCOL_VERSION,
7186
+ protocolVersion: this.options.protocolVersion ?? MCP_PROTOCOL_VERSION,
6185
7187
  clientInfo: this.options.clientInfo,
6186
7188
  capabilities
6187
7189
  }, { signal: options.signal });
@@ -6189,12 +7191,14 @@ var McpClient = class {
6189
7191
  throw new McpError(ERROR_INVALID_REQUEST, "Invalid initialize result");
6190
7192
  }
6191
7193
  const initializeResult = initializeResultValue;
6192
- if (initializeResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
7194
+ if (initializeResult.protocolVersion === "2026-07-28" || !MCP_PROTOCOL_VERSIONS.includes(initializeResult.protocolVersion)) {
6193
7195
  throw new McpError(
6194
7196
  ERROR_INVALID_REQUEST,
6195
7197
  `Unsupported protocol version: ${initializeResult.protocolVersion}`
6196
7198
  );
6197
7199
  }
7200
+ if (this.options.protocolVersion !== void 0 && initializeResult.protocolVersion !== this.options.protocolVersion)
7201
+ throw new McpError(ERROR_INVALID_REQUEST, `Pinned protocol version ${this.options.protocolVersion} rejected: server selected ${initializeResult.protocolVersion}`);
6198
7202
  this.currentServerCapabilities = structuredClone(initializeResult.capabilities);
6199
7203
  this.currentServerInfo = structuredClone(initializeResult.serverInfo);
6200
7204
  this.currentInstructions = initializeResult.instructions;
@@ -6209,7 +7213,8 @@ var McpClient = class {
6209
7213
  async (params, context) => this.options.onElicitationRequest(params, context)
6210
7214
  );
6211
7215
  }
6212
- messageLayer.sendNotification("notifications/initialized");
7216
+ if (transport.completeInitialization === void 0) messageLayer.sendNotification("notifications/initialized");
7217
+ else await transport.completeInitialization({ signal: options.signal, timeoutMs: this.options.requestTimeoutMs ?? 3e4, protocolVersion: initializeResult.protocolVersion });
6213
7218
  this.currentState = "ready";
6214
7219
  return initializeResult;
6215
7220
  } catch (error) {
@@ -6247,6 +7252,7 @@ var McpClient = class {
6247
7252
  };
6248
7253
  }
6249
7254
  async callTool(params, options = {}) {
7255
+ options = { ...options };
6250
7256
  const messageLayer = this.getMessageLayerOrThrow();
6251
7257
  const serverCapabilities = this.getServerCapabilitiesOrThrow();
6252
7258
  if (serverCapabilities.tools === void 0) {
@@ -6544,6 +7550,18 @@ var ERROR_INVALID_REQUEST = -32600;
6544
7550
  var ERROR_METHOD_NOT_FOUND = -32601;
6545
7551
  var ERROR_INVALID_PARAMS = -32602;
6546
7552
  var ERROR_INTERNAL = -32603;
7553
+ var HttpTransportError = class extends Error {
7554
+ constructor(message, status, method, rpcMethod) {
7555
+ super(message);
7556
+ this.status = status;
7557
+ this.method = method;
7558
+ this.rpcMethod = rpcMethod;
7559
+ this.name = "HttpTransportError";
7560
+ }
7561
+ status;
7562
+ method;
7563
+ rpcMethod;
7564
+ };
6547
7565
  function defaultStdioSpawn(command, args, options) {
6548
7566
  return spawn2(command, args, options);
6549
7567
  }
@@ -6649,23 +7667,41 @@ var StdioTransport = class _StdioTransport {
6649
7667
  }
6650
7668
  }
6651
7669
  };
7670
+ function snapshotHttpTransportHeaders(headers) {
7671
+ try {
7672
+ return new Headers(headers);
7673
+ } catch {
7674
+ throw new Error("Invalid HTTP transport headers");
7675
+ }
7676
+ }
6652
7677
  var HttpTransport = class {
6653
7678
  readable;
6654
7679
  writable;
6655
7680
  closed;
7681
+ closeReason;
7682
+ resolveCloseReason;
6656
7683
  url;
7684
+ mode;
7685
+ legacyEndpoint;
7686
+ legacyEndpointReady;
7687
+ resolveLegacyEndpoint;
7688
+ rejectLegacyEndpoint;
6657
7689
  headers;
6658
7690
  fetchImpl;
6659
7691
  readStream = new PassThrough();
6660
7692
  writeStream = new PassThrough();
6661
7693
  resolveClosed;
6662
7694
  sessionId;
7695
+ legacyProtocolVersion;
7696
+ initializationRequestId;
6663
7697
  lastEventId;
6664
7698
  getSseStreamStarted = false;
6665
7699
  disposed = false;
6666
7700
  oauthProvider;
6667
7701
  oauthMetadataDiscovery;
6668
7702
  inFlightFetchAbortControllers = /* @__PURE__ */ new Set();
7703
+ inFlightOAuthAbortControllers = /* @__PURE__ */ new Set();
7704
+ oauthRequestTokens = /* @__PURE__ */ new WeakMap();
6669
7705
  openResponseReaders = /* @__PURE__ */ new Set();
6670
7706
  modernRequests = /* @__PURE__ */ new Map();
6671
7707
  modernMode = false;
@@ -6674,6 +7710,7 @@ var HttpTransport = class {
6674
7710
  onWarning;
6675
7711
  constructor({
6676
7712
  url,
7713
+ mode = "streamable-http",
6677
7714
  headers = {},
6678
7715
  fetch: fetchImpl = defaultHttpTransportFetch,
6679
7716
  oauth,
@@ -6683,9 +7720,17 @@ var HttpTransport = class {
6683
7720
  }) {
6684
7721
  if (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes < 1)
6685
7722
  throw new Error("HTTP response byte limit must be a positive safe integer");
7723
+ try {
7724
+ const target = new URL(url);
7725
+ if (!["http:", "https:"].includes(target.protocol) || target.username || target.password || target.href.includes("#"))
7726
+ throw new Error("Invalid HTTP transport target");
7727
+ } catch {
7728
+ throw new Error("HTTP transport URL must be an absolute HTTP URL without credentials or fragment");
7729
+ }
6686
7730
  this.maxResponseBytes = maxResponseBytes;
6687
7731
  this.url = url;
6688
- this.headers = headers;
7732
+ this.mode = mode;
7733
+ this.headers = snapshotHttpTransportHeaders(headers);
6689
7734
  this.fetchImpl = fetchImpl;
6690
7735
  this.onWarning = onWarning;
6691
7736
  this.oauthProvider = oauth === void 0 ? void 0 : createOAuthClientProvider(oauth);
@@ -6695,6 +7740,9 @@ var HttpTransport = class {
6695
7740
  });
6696
7741
  this.readable = this.readStream;
6697
7742
  this.writable = this.writeStream;
7743
+ this.closeReason = new Promise((resolve) => {
7744
+ this.resolveCloseReason = resolve;
7745
+ });
6698
7746
  this.closed = new Promise((resolve) => {
6699
7747
  this.resolveClosed = resolve;
6700
7748
  });
@@ -6708,6 +7756,24 @@ var HttpTransport = class {
6708
7756
  this.dispose(error instanceof Error ? error : new Error(String(error)));
6709
7757
  });
6710
7758
  }
7759
+ async completeInitialization(options) {
7760
+ validateRequestTimer(options.timeoutMs, "timeoutMs");
7761
+ const deadline = options.timeoutMs > 0 ? AbortSignal.timeout(Math.ceil(options.timeoutMs)) : void 0;
7762
+ const signals = [options.signal, deadline].filter((signal2) => signal2 !== void 0);
7763
+ const signal = signals.length === 0 ? new AbortController().signal : AbortSignal.any(signals);
7764
+ signal.throwIfAborted();
7765
+ if (options.protocolVersion !== void 0) this.legacyProtocolVersion = options.protocolVersion;
7766
+ this.maybeOpenGetSseStream();
7767
+ try {
7768
+ await this.sendPost(serializeJsonRpcMessage({ jsonrpc: "2.0", method: "notifications/initialized" }), signal);
7769
+ signal.throwIfAborted();
7770
+ if (this.disposed) throw await this.closeReason;
7771
+ } catch (error) {
7772
+ if (error instanceof HttpTransportError)
7773
+ throw new HttpTransportError(error.message, error.status, error.method, "notifications/initialized");
7774
+ throw error;
7775
+ }
7776
+ }
6711
7777
  filterTools(tools, reset = true) {
6712
7778
  if (reset) this.toolParameterHeaders.clear();
6713
7779
  const accepted = [];
@@ -6729,8 +7795,13 @@ var HttpTransport = class {
6729
7795
  return;
6730
7796
  }
6731
7797
  this.disposed = true;
7798
+ this.rejectLegacyEndpoint?.(reason);
7799
+ this.rejectLegacyEndpoint = void 0;
7800
+ this.resolveLegacyEndpoint = void 0;
7801
+ this.resolveCloseReason?.(reason);
7802
+ this.resolveCloseReason = void 0;
6732
7803
  this.toolParameterHeaders.clear();
6733
- this.abortInFlightFetches();
7804
+ this.abortInFlightFetches(reason);
6734
7805
  this.cancelOpenResponseReaders();
6735
7806
  if (!this.writeStream.destroyed && !this.writeStream.writableEnded) {
6736
7807
  this.writeStream.end();
@@ -6766,12 +7837,14 @@ var HttpTransport = class {
6766
7837
  this.resolveClosed = void 0;
6767
7838
  resolveClosed?.({ reason: closeReason });
6768
7839
  }
6769
- abortInFlightFetches() {
6770
- for (const controller of this.modernRequests.values()) controller.abort();
7840
+ abortInFlightFetches(reason) {
7841
+ for (const controller of this.modernRequests.values()) controller.abort(reason);
6771
7842
  this.modernRequests.clear();
6772
7843
  for (const abortController of this.inFlightFetchAbortControllers) {
6773
- abortController.abort();
7844
+ abortController.abort(reason);
6774
7845
  }
7846
+ for (const controller of this.inFlightOAuthAbortControllers) controller.abort(reason);
7847
+ this.inFlightOAuthAbortControllers.clear();
6775
7848
  this.inFlightFetchAbortControllers.clear();
6776
7849
  }
6777
7850
  cancelOpenResponseReaders() {
@@ -6806,7 +7879,11 @@ var HttpTransport = class {
6806
7879
  const parsed = parseJsonRpcMessage(line);
6807
7880
  const post = this.sendPost(line);
6808
7881
  if (parsed.type === "request" && parsed.message.method === "initialize" || parsed.type === "notification" && parsed.message.method === "notifications/initialized") {
6809
- await post;
7882
+ try {
7883
+ await post;
7884
+ } catch (error) {
7885
+ this.dispose(error instanceof Error ? error : new Error(String(error)));
7886
+ }
6810
7887
  } else {
6811
7888
  void post.catch((error) => {
6812
7889
  this.dispose(error instanceof Error ? error : new Error(String(error)));
@@ -6814,28 +7891,39 @@ var HttpTransport = class {
6814
7891
  }
6815
7892
  }
6816
7893
  }
6817
- async sendPost(line) {
7894
+ async sendPost(line, signal) {
7895
+ signal?.throwIfAborted();
6818
7896
  const parsed = parseJsonRpcMessage(line);
6819
7897
  const message = parsed.type === "request" || parsed.type === "notification" ? parsed.message : void 0;
6820
7898
  const metadata = isObjectRecord5(message?.params) ? message.params._meta : void 0;
6821
7899
  const modern = isObjectRecord5(metadata) && typeof metadata["io.modelcontextprotocol/protocolVersion"] === "string";
6822
7900
  if (modern) this.modernMode = true;
6823
- if (parsed.type === "request" && parsed.message.method === "initialize")
7901
+ if (parsed.type === "request" && parsed.message.method === "initialize") {
6824
7902
  this.modernMode = false;
7903
+ this.initializationRequestId = parsed.message.id;
7904
+ this.legacyProtocolVersion = MCP_PROTOCOL_VERSION;
7905
+ if (isObjectRecord5(parsed.message.params) && typeof parsed.message.params.protocolVersion === "string")
7906
+ this.legacyProtocolVersion = parsed.message.params.protocolVersion;
7907
+ }
6825
7908
  if (this.modernMode && parsed.type === "notification" && parsed.message.method === "notifications/cancelled") {
6826
7909
  const requestId = isObjectRecord5(parsed.message.params) ? parsed.message.params.requestId : void 0;
6827
7910
  if (typeof requestId === "string" || typeof requestId === "number")
6828
7911
  this.modernRequests.get(requestId)?.abort();
6829
7912
  return;
6830
7913
  }
6831
- const controller = modern && parsed.type === "request" ? new AbortController() : void 0;
7914
+ const controller = modern && parsed.type === "request" || signal !== void 0 ? new AbortController() : void 0;
7915
+ const aborted = () => controller?.abort(signal?.reason);
7916
+ signal?.addEventListener("abort", aborted, { once: true });
7917
+ if (signal?.aborted) aborted();
6832
7918
  const id = parsed.type === "request" ? parsed.message.id : void 0;
6833
7919
  if (controller !== void 0 && id !== void 0) this.modernRequests.set(id, controller);
6834
7920
  try {
7921
+ const postUrl = this.mode === "sse" ? await this.ensureLegacyEndpoint() : this.url;
6835
7922
  const hasSessionId = !modern && this.sessionId !== void 0;
6836
7923
  const response = await this.fetchWithOAuthRetry({
7924
+ url: postUrl,
6837
7925
  method: "POST",
6838
- createHeaders: () => this.createPostHeaders(message, modern),
7926
+ createHeaders: (signal2) => this.createPostHeaders(message, modern, signal2),
6839
7927
  body: line,
6840
7928
  controller
6841
7929
  });
@@ -6846,7 +7934,7 @@ var HttpTransport = class {
6846
7934
  if (hasSessionId && response.status === 404) {
6847
7935
  void response.body?.cancel().catch(() => void 0);
6848
7936
  this.sessionId = void 0;
6849
- this.dispose(new Error("HTTP transport session expired (404 response)"));
7937
+ this.dispose(new HttpTransportError("HTTP transport session expired (404 response)", 404, "POST"));
6850
7938
  return;
6851
7939
  }
6852
7940
  if (await this.throwForPostHttpError(
@@ -6859,23 +7947,26 @@ var HttpTransport = class {
6859
7947
  void response.body?.cancel().catch(() => void 0);
6860
7948
  return;
6861
7949
  }
6862
- if (!modern) {
7950
+ if (!modern && this.mode !== "sse") {
6863
7951
  this.captureSessionId(response);
6864
- this.maybeOpenGetSseStream();
7952
+ if (message?.method !== "initialize") this.maybeOpenGetSseStream();
6865
7953
  }
6866
- if (controller !== void 0) await this.forwardResponseMessages(response, controller.signal, parsed.type === "request" ? new HttpResponseMessages(parsed.message) : void 0);
6867
- else
7954
+ if (controller !== void 0 || message?.method === "initialize") {
7955
+ await this.forwardResponseMessages(response, controller?.signal, controller === void 0 || parsed.type !== "request" ? void 0 : new HttpResponseMessages(parsed.message), message?.method === "initialize");
7956
+ if (message?.method === "initialize") this.maybeOpenGetSseStream();
7957
+ } else
6868
7958
  void this.forwardResponseMessages(response).catch((error) => {
6869
7959
  this.dispose(error instanceof Error ? error : new Error(String(error)));
6870
7960
  });
6871
7961
  } catch (error) {
6872
7962
  if (!controller?.signal.aborted) throw error;
6873
7963
  } finally {
7964
+ signal?.removeEventListener("abort", aborted);
6874
7965
  if (id !== void 0 && this.modernRequests.get(id) === controller)
6875
7966
  this.modernRequests.delete(id);
6876
7967
  }
6877
7968
  }
6878
- async createPostHeaders(message, modern = false) {
7969
+ async createPostHeaders(message, modern = false, signal) {
6879
7970
  const headers = new Headers(this.headers);
6880
7971
  headers.set("Accept", "application/json, text/event-stream");
6881
7972
  headers.set("Content-Type", "application/json");
@@ -6903,34 +7994,43 @@ var HttpTransport = class {
6903
7994
  }
6904
7995
  } else if (this.sessionId !== void 0) {
6905
7996
  headers.set("Mcp-Session-Id", this.sessionId);
6906
- headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
6907
7997
  }
6908
- return this.authorizeRequestHeaders(headers);
7998
+ if (!modern && message?.method !== "initialize" && this.legacyProtocolVersion !== void 0)
7999
+ headers.set("MCP-Protocol-Version", this.legacyProtocolVersion);
8000
+ return this.authorizeRequestHeaders(headers, signal);
6909
8001
  }
6910
- async createGetHeaders() {
8002
+ async createGetHeaders(signal) {
6911
8003
  const headers = new Headers(this.headers);
6912
8004
  headers.set("Accept", "text/event-stream");
6913
8005
  if (this.sessionId !== void 0) {
6914
8006
  headers.set("Mcp-Session-Id", this.sessionId);
6915
- headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
6916
8007
  }
8008
+ if (this.legacyProtocolVersion !== void 0) headers.set("MCP-Protocol-Version", this.legacyProtocolVersion);
6917
8009
  if (this.lastEventId !== void 0) {
6918
- headers.set("Last-Event-ID", this.lastEventId);
8010
+ if (this.lastEventId === "") headers.delete("Last-Event-ID");
8011
+ else headers.set("Last-Event-ID", Buffer.from(this.lastEventId, "utf8").toString("latin1"));
6919
8012
  }
6920
- return this.authorizeRequestHeaders(headers);
8013
+ return this.authorizeRequestHeaders(headers, signal);
6921
8014
  }
6922
- async createDeleteHeaders(sessionId) {
8015
+ async createDeleteHeaders(sessionId, signal) {
6923
8016
  const headers = new Headers(this.headers);
6924
8017
  headers.set("Mcp-Session-Id", sessionId);
6925
- headers.set("MCP-Protocol-Version", MCP_PROTOCOL_VERSION);
6926
- return this.authorizeRequestHeaders(headers);
8018
+ headers.set("MCP-Protocol-Version", this.legacyProtocolVersion ?? MCP_PROTOCOL_VERSION);
8019
+ return this.authorizeRequestHeaders(headers, signal);
6927
8020
  }
6928
- async authorizeRequestHeaders(headers) {
6929
- await this.oauthProvider?.authorizeRequest?.({
8021
+ async authorizeRequestHeaders(headers, signal) {
8022
+ signal?.throwIfAborted();
8023
+ const tokens = await this.oauthProvider?.authorizeRequest?.({
6930
8024
  requestUrl: new URL(this.url),
6931
8025
  headers,
6932
- fetch: this.fetchImpl
8026
+ signal,
8027
+ fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
8028
+ ...init,
8029
+ signal: signal === void 0 ? init?.signal : init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
8030
+ })
6933
8031
  });
8032
+ if (tokens !== void 0) this.oauthRequestTokens.set(headers, { ...tokens });
8033
+ signal?.throwIfAborted();
6934
8034
  return headers;
6935
8035
  }
6936
8036
  captureSessionId(response) {
@@ -6955,8 +8055,19 @@ var HttpTransport = class {
6955
8055
  this.dispose(error instanceof Error ? error : new Error(String(error)));
6956
8056
  });
6957
8057
  }
8058
+ ensureLegacyEndpoint() {
8059
+ if (this.legacyEndpointReady !== void 0) return this.legacyEndpointReady;
8060
+ this.legacyEndpointReady = new Promise((resolve, reject) => {
8061
+ this.resolveLegacyEndpoint = resolve;
8062
+ this.rejectLegacyEndpoint = reject;
8063
+ });
8064
+ void this.consumeGetSseStream().catch((error) => {
8065
+ this.dispose(error instanceof Error ? error : new Error(String(error)));
8066
+ });
8067
+ return this.legacyEndpointReady;
8068
+ }
6958
8069
  async sendSessionTerminationRequest(sessionId, signal) {
6959
- const headers = await this.createDeleteHeaders(sessionId);
8070
+ const headers = await this.createDeleteHeaders(sessionId, signal);
6960
8071
  signal.throwIfAborted();
6961
8072
  const response = await fetchMcpResponse(this.fetchImpl, this.url, {
6962
8073
  method: "DELETE",
@@ -6974,12 +8085,12 @@ var HttpTransport = class {
6974
8085
  const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders, signal)).trim();
6975
8086
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
6976
8087
  const message = responseBody.length === 0 ? `HTTP transport DELETE failed (${statusDescriptor})` : `HTTP transport DELETE failed (${statusDescriptor}): ${responseBody}`;
6977
- throw new Error(message);
8088
+ throw new HttpTransportError(message, response.status, "DELETE");
6978
8089
  }
6979
8090
  async consumeGetSseStream() {
6980
8091
  const response = await this.fetchWithOAuthRetry({
6981
8092
  method: "GET",
6982
- createHeaders: () => this.createGetHeaders()
8093
+ createHeaders: (signal) => this.createGetHeaders(signal)
6983
8094
  });
6984
8095
  if (this.disposed) {
6985
8096
  void response.body?.cancel().catch(() => void 0);
@@ -6987,26 +8098,32 @@ var HttpTransport = class {
6987
8098
  }
6988
8099
  if (response.status === 405) {
6989
8100
  void response.body?.cancel().catch(() => void 0);
8101
+ if (this.mode === "sse") throw new HttpTransportError("Legacy SSE GET failed (405)", 405, "GET");
6990
8102
  throw new HttpTransportGetSseNotSupportedError();
6991
8103
  }
6992
8104
  if (response.status === 404) {
6993
8105
  void response.body?.cancel().catch(() => void 0);
6994
8106
  this.sessionId = void 0;
6995
- throw new Error("HTTP transport session expired (GET 404 response)");
8107
+ throw new HttpTransportError("HTTP transport session expired (GET 404 response)", 404, "GET");
6996
8108
  }
6997
8109
  if (!response.ok) {
6998
8110
  const responseBody = (await readBoundedResponseText(response, this.maxResponseBytes, this.openResponseReaders)).trim();
6999
8111
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
7000
8112
  const message = responseBody.length === 0 ? `HTTP transport GET failed (${statusDescriptor})` : `HTTP transport GET failed (${statusDescriptor}): ${responseBody}`;
7001
- throw new Error(message);
8113
+ throw new HttpTransportError(message, response.status, "GET");
7002
8114
  }
7003
8115
  const contentType = response.headers.get("Content-Type");
7004
8116
  if (contentType === null) {
7005
8117
  void response.body?.cancel().catch(() => void 0);
8118
+ if (this.mode === "sse") throw new Error("Legacy SSE GET returned an unsupported content type");
7006
8119
  return;
7007
8120
  }
7008
8121
  if (contentType.split(";")[0]?.trim().toLowerCase() === "text/event-stream") {
7009
- await this.forwardSseResponseMessages(response);
8122
+ await this.forwardSseResponseMessages(response, void 0, void 0, { acceptEndpoint: this.mode === "sse" });
8123
+ if (this.mode === "sse") {
8124
+ if (!this.disposed) throw new Error("Legacy SSE stream ended");
8125
+ return;
8126
+ }
7010
8127
  this.getSseStreamStarted = false;
7011
8128
  if (!this.disposed && this.sessionId !== void 0 && this.lastEventId !== void 0) {
7012
8129
  this.maybeOpenGetSseStream();
@@ -7014,6 +8131,7 @@ var HttpTransport = class {
7014
8131
  return;
7015
8132
  }
7016
8133
  void response.body?.cancel().catch(() => void 0);
8134
+ if (this.mode === "sse") throw new Error("Legacy SSE GET returned an unsupported content type");
7017
8135
  }
7018
8136
  async throwForPostHttpError(response, request, signal) {
7019
8137
  if (response.status < 400) {
@@ -7053,9 +8171,9 @@ var HttpTransport = class {
7053
8171
  }
7054
8172
  const statusDescriptor = `${response.status} ${response.statusText}`.trim();
7055
8173
  const message = responseBody.length === 0 ? `HTTP transport POST failed (${statusDescriptor})` : `HTTP transport POST failed (${statusDescriptor}): ${responseBody}`;
7056
- throw new Error(message);
8174
+ throw new HttpTransportError(message, response.status, "POST");
7057
8175
  }
7058
- async maybeHandleUnauthorizedResponse(response) {
8176
+ async maybeHandleUnauthorizedResponse(response, signal, requestHeaders, presentedTokens) {
7059
8177
  if (response.status !== 401 || this.oauthProvider === void 0) {
7060
8178
  return false;
7061
8179
  }
@@ -7063,10 +8181,10 @@ var HttpTransport = class {
7063
8181
  if (discoveryClient === void 0) {
7064
8182
  return false;
7065
8183
  }
7066
- const challenge = parseBearerWwwAuthenticateHeader(response.headers.get("WWW-Authenticate"));
7067
- const resourceMetadataUrl = challenge?.params.resource_metadata;
7068
8184
  try {
7069
- const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl });
8185
+ const challenge = parseBearerWwwAuthenticateHeader(response.headers.get("WWW-Authenticate"));
8186
+ const resourceMetadataUrl = challenge?.params.resource_metadata;
8187
+ const discovery = await discoveryClient.discover(this.url, { resourceMetadataUrl, signal });
7070
8188
  const providerResponse = response.clone();
7071
8189
  let result;
7072
8190
  try {
@@ -7075,8 +8193,15 @@ var HttpTransport = class {
7075
8193
  response: providerResponse,
7076
8194
  challenge,
7077
8195
  discovery,
7078
- fetch: this.fetchImpl
8196
+ signal,
8197
+ requestHeaders: new Headers(requestHeaders),
8198
+ presentedTokens,
8199
+ fetch: (url, init) => fetchMcpResponse(this.fetchImpl, url, {
8200
+ ...init,
8201
+ signal: init?.signal == null ? signal : AbortSignal.any([signal, init.signal])
8202
+ })
7079
8203
  });
8204
+ signal.throwIfAborted();
7080
8205
  } finally {
7081
8206
  void providerResponse.body?.cancel().catch(() => void 0);
7082
8207
  }
@@ -7091,7 +8216,7 @@ var HttpTransport = class {
7091
8216
  throw error;
7092
8217
  }
7093
8218
  }
7094
- async forwardResponseMessages(response, signal, context) {
8219
+ async forwardResponseMessages(response, signal, context, initialization = false) {
7095
8220
  if (response.status === 202) {
7096
8221
  void response.body?.cancel().catch(() => void 0);
7097
8222
  return;
@@ -7103,7 +8228,7 @@ var HttpTransport = class {
7103
8228
  }
7104
8229
  const normalizedContentType = contentType.split(";")[0]?.trim().toLowerCase();
7105
8230
  if (normalizedContentType === "text/event-stream") {
7106
- await this.forwardSseResponseMessages(response, signal, context);
8231
+ await this.forwardSseResponseMessages(response, signal, context, { stopAfterInitialization: initialization });
7107
8232
  return;
7108
8233
  }
7109
8234
  if (normalizedContentType === "application/json") {
@@ -7113,11 +8238,11 @@ var HttpTransport = class {
7113
8238
  void response.body?.cancel().catch(() => void 0);
7114
8239
  throw new Error("HTTP transport POST returned an unsupported response content type");
7115
8240
  }
7116
- async forwardSseResponseMessages(response, signal, context) {
8241
+ async forwardSseResponseMessages(response, signal, context, options = {}) {
7117
8242
  if (response.body === null) {
7118
8243
  return;
7119
8244
  }
7120
- const parser = new SseParser(this.maxResponseBytes);
8245
+ const parser = new SseParser(this.maxResponseBytes, options.acceptEndpoint);
7121
8246
  const decoder = new TextDecoder("utf-8", { fatal: true });
7122
8247
  const reader = response.body.getReader();
7123
8248
  this.openResponseReaders.add(reader);
@@ -7138,19 +8263,19 @@ var HttpTransport = class {
7138
8263
  }
7139
8264
  const messages = parser.push(decoder.decode(value, { stream: true }));
7140
8265
  this.writeSseMessages(messages, context);
7141
- if (context?.completed) {
8266
+ if (context?.completed || options.stopAfterInitialization && this.initializationRequestId === void 0) {
7142
8267
  void reader.cancel().catch(() => void 0);
7143
8268
  return;
7144
8269
  }
7145
- this.lastEventId = parser.lastEventId;
8270
+ this.lastEventId = parser.lastEventId ?? this.lastEventId;
7146
8271
  }
7147
8272
  const trailingChunk = decoder.decode();
7148
8273
  if (trailingChunk.length > 0) {
7149
8274
  this.writeSseMessages(parser.push(trailingChunk), context);
7150
- this.lastEventId = parser.lastEventId;
8275
+ this.lastEventId = parser.lastEventId ?? this.lastEventId;
7151
8276
  }
7152
8277
  this.writeSseMessages(parser.flush(), context);
7153
- this.lastEventId = parser.lastEventId;
8278
+ this.lastEventId = parser.lastEventId ?? this.lastEventId;
7154
8279
  if (context !== void 0 && !context.completed) throw new Error("MCP HTTP stream ended before its final response");
7155
8280
  } catch (error) {
7156
8281
  void reader.cancel().catch(() => void 0);
@@ -7181,6 +8306,19 @@ var HttpTransport = class {
7181
8306
  }
7182
8307
  writeSseMessages(messages, context) {
7183
8308
  for (const message of messages) {
8309
+ if (message.event === "endpoint") {
8310
+ const endpoint = new URL(message.data, this.url);
8311
+ const resource = new URL(this.url);
8312
+ if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:" || endpoint.origin !== resource.origin || endpoint.username || endpoint.password || endpoint.href.includes("#"))
8313
+ throw new Error("Unsafe legacy SSE endpoint");
8314
+ if (this.legacyEndpoint !== void 0 && this.legacyEndpoint !== endpoint.href)
8315
+ throw new Error("Legacy SSE endpoint changed during the active connection");
8316
+ this.legacyEndpoint = endpoint.href;
8317
+ this.resolveLegacyEndpoint?.(endpoint.href);
8318
+ this.resolveLegacyEndpoint = void 0;
8319
+ this.rejectLegacyEndpoint = void 0;
8320
+ continue;
8321
+ }
7184
8322
  this.writeReadableLine(context?.validate(message.data, true) ?? message.data);
7185
8323
  if (context?.completed) return;
7186
8324
  }
@@ -7189,29 +8327,55 @@ var HttpTransport = class {
7189
8327
  if (this.disposed || this.readStream.destroyed || this.readStream.writableEnded) {
7190
8328
  return;
7191
8329
  }
8330
+ if (this.initializationRequestId !== void 0) {
8331
+ try {
8332
+ const payload = JSON.parse(line);
8333
+ for (const response of Array.isArray(payload) ? payload : [payload]) {
8334
+ if (!isObjectRecord5(response) || response.id !== this.initializationRequestId || !isObjectRecord5(response.result)) continue;
8335
+ const version = response.result.protocolVersion;
8336
+ if (typeof version === "string" && version !== "2026-07-28" && MCP_PROTOCOL_VERSIONS.includes(version))
8337
+ this.legacyProtocolVersion = version;
8338
+ this.initializationRequestId = void 0;
8339
+ this.maybeOpenGetSseStream();
8340
+ break;
8341
+ }
8342
+ } catch {
8343
+ }
8344
+ }
7192
8345
  this.readStream.write(`${line}
7193
8346
  `);
7194
8347
  }
7195
8348
  async fetchWithOAuthRetry(input) {
7196
- const request = async () => this.fetchWithAbort(
7197
- this.url,
7198
- {
8349
+ const controller = input.controller ?? new AbortController();
8350
+ this.inFlightOAuthAbortControllers.add(controller);
8351
+ const request = async () => {
8352
+ controller.signal.throwIfAborted();
8353
+ const headers = await input.createHeaders(controller.signal);
8354
+ const headerSnapshot = new Headers(headers);
8355
+ const tokens = this.oauthRequestTokens.get(headers) ?? null;
8356
+ controller.signal.throwIfAborted();
8357
+ const response = await this.fetchWithAbort(input.url ?? this.url, {
7199
8358
  method: input.method,
7200
- headers: await input.createHeaders(),
8359
+ headers,
7201
8360
  body: input.body
7202
- },
7203
- input.controller
7204
- );
7205
- let response = await request();
7206
- if (await this.maybeHandleUnauthorizedResponse(response)) {
7207
- response = await request();
7208
- }
7209
- const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
7210
- if (oauthError !== null) {
7211
- void response.body?.cancel().catch(() => void 0);
7212
- throw oauthError;
8361
+ }, controller);
8362
+ return { response, headers: headerSnapshot, tokens };
8363
+ };
8364
+ try {
8365
+ let attempt = await request();
8366
+ if (await this.maybeHandleUnauthorizedResponse(attempt.response, controller.signal, attempt.headers, attempt.tokens)) attempt = await request();
8367
+ const response = attempt.response;
8368
+ try {
8369
+ const oauthError = this.oauthProvider === void 0 ? null : this.readOAuthChallengeError(response);
8370
+ if (oauthError !== null) throw oauthError;
8371
+ } catch (error) {
8372
+ void response.body?.cancel().catch(() => void 0);
8373
+ throw error;
8374
+ }
8375
+ return response;
8376
+ } finally {
8377
+ this.inFlightOAuthAbortControllers.delete(controller);
7213
8378
  }
7214
- return response;
7215
8379
  }
7216
8380
  readOAuthChallengeError(response) {
7217
8381
  if (response.status !== 401 && response.status !== 403) {
@@ -7291,12 +8455,14 @@ async function* readLines(stream, maxLineBytes = 16 * 1024 * 1024) {
7291
8455
  if (parts.length > 0) yield normalizeLine(parts.join(""));
7292
8456
  }
7293
8457
  var SseParser = class {
7294
- constructor(maxEventBytes = 16 * 1024 * 1024) {
8458
+ constructor(maxEventBytes = 16 * 1024 * 1024, acceptEndpoint = false) {
7295
8459
  this.maxEventBytes = maxEventBytes;
8460
+ this.acceptEndpoint = acceptEndpoint;
7296
8461
  if (!Number.isSafeInteger(maxEventBytes) || maxEventBytes < 1)
7297
8462
  throw new Error("SSE event byte limit must be a positive safe integer");
7298
8463
  }
7299
8464
  maxEventBytes;
8465
+ acceptEndpoint;
7300
8466
  buffer = "";
7301
8467
  skipLf = false;
7302
8468
  eventType;
@@ -7377,13 +8543,14 @@ var SseParser = class {
7377
8543
  if (this.hasEventId) {
7378
8544
  this._lastEventId = this.eventId;
7379
8545
  }
7380
- if (this.dataLines.length === 0 || eventType !== "message") {
8546
+ if (this.dataLines.length === 0 || eventType !== "message" && !(this.acceptEndpoint && eventType === "endpoint")) {
7381
8547
  this.resetEvent();
7382
8548
  return;
7383
8549
  }
7384
8550
  const message = {
7385
8551
  data: this.dataLines.join("\n")
7386
8552
  };
8553
+ if (eventType === "endpoint") message.event = "endpoint";
7387
8554
  if (this.hasEventId) {
7388
8555
  message.id = this.eventId;
7389
8556
  }
@@ -7402,12 +8569,14 @@ var SseParser = class {
7402
8569
  throw new Error(`SSE event exceeds ${this.maxEventBytes} bytes`);
7403
8570
  }
7404
8571
  };
8572
+ function validateRequestTimer(value, name) {
8573
+ if (!Number.isFinite(value) || value < 0 || value > 2147483647)
8574
+ throw new Error(`${name} must be a non-negative finite number no greater than 2147483647`);
8575
+ }
7405
8576
  var JsonRpcMessageLayer = class {
7406
8577
  constructor(input, output, requestTimeoutMs = 3e4, inputClosedReason, maxConcurrentRequests = 128) {
7407
8578
  this.maxConcurrentRequests = maxConcurrentRequests;
7408
- if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs < 0) {
7409
- throw new Error("requestTimeoutMs must be a non-negative finite number");
7410
- }
8579
+ validateRequestTimer(requestTimeoutMs, "requestTimeoutMs");
7411
8580
  if (!Number.isSafeInteger(maxConcurrentRequests) || maxConcurrentRequests < 1)
7412
8581
  throw new Error("maxConcurrentRequests must be a positive safe integer");
7413
8582
  this.input = input;
@@ -7466,9 +8635,7 @@ var JsonRpcMessageLayer = class {
7466
8635
  sendRequest(method, params, options = {}) {
7467
8636
  if (this.disposedError !== void 0) throw this.disposedError;
7468
8637
  const timeoutMs = options.timeoutMs === null ? null : options.timeoutMs ?? this.requestTimeoutMs;
7469
- if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
7470
- throw new Error("timeoutMs must be a non-negative finite number");
7471
- }
8638
+ if (timeoutMs !== null) validateRequestTimer(timeoutMs, "timeoutMs");
7472
8639
  if (this.exchangeControllers.size >= this.maxConcurrentRequests)
7473
8640
  throw new Error("JSON-RPC request capacity exceeded");
7474
8641
  if (params !== void 0 && !isJsonValue(params)) {
@@ -7487,6 +8654,7 @@ var JsonRpcMessageLayer = class {
7487
8654
  rejectAbort = reject;
7488
8655
  });
7489
8656
  const cancel = () => {
8657
+ clearTimeout(deadline);
7490
8658
  if (currentRequestId !== void 0) this.cancelRequest(currentRequestId, controller.signal.reason);
7491
8659
  rejectAbort(controller.signal.reason);
7492
8660
  };
@@ -7498,6 +8666,15 @@ var JsonRpcMessageLayer = class {
7498
8666
  currentRequestId = id;
7499
8667
  onRequestId?.(id);
7500
8668
  } };
8669
+ const deadline = timeoutMs === null ? void 0 : setTimeout(() => {
8670
+ let reason = new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`);
8671
+ try {
8672
+ if (currentRequestId !== void 0) options.onTimeout?.(currentRequestId);
8673
+ } catch (error) {
8674
+ reason = error;
8675
+ }
8676
+ controller.abort(reason);
8677
+ }, timeoutMs);
7501
8678
  if (callerSignal?.aborted) forwardAbort();
7502
8679
  const work = (async () => {
7503
8680
  let retryParams = originalParams;
@@ -7570,6 +8747,7 @@ var JsonRpcMessageLayer = class {
7570
8747
  throw new McpError(ERROR_INTERNAL, "Unreachable MCP retry state");
7571
8748
  })();
7572
8749
  return Promise.race([work, aborted]).finally(() => {
8750
+ clearTimeout(deadline);
7573
8751
  callerSignal?.removeEventListener("abort", forwardAbort);
7574
8752
  controller.signal.removeEventListener("abort", cancel);
7575
8753
  this.exchangeControllers.delete(controller);
@@ -7581,10 +8759,6 @@ var JsonRpcMessageLayer = class {
7581
8759
  }
7582
8760
  const id = this.nextRequestId;
7583
8761
  this.nextRequestId += 1;
7584
- const timeoutMs = options.timeoutMs === null ? null : options.timeoutMs ?? this.requestTimeoutMs;
7585
- if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
7586
- throw new Error("timeoutMs must be a non-negative finite number");
7587
- }
7588
8762
  if (options.onRequestId !== void 0) {
7589
8763
  options.onRequestId(id);
7590
8764
  }
@@ -7607,16 +8781,10 @@ var JsonRpcMessageLayer = class {
7607
8781
  message.params = params;
7608
8782
  }
7609
8783
  return new Promise((resolve, reject) => {
7610
- const timeout = timeoutMs === null ? void 0 : setTimeout(() => {
7611
- this.pendingRequests.delete(id);
7612
- options.onTimeout?.(id);
7613
- reject(new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`));
7614
- }, timeoutMs);
7615
- this.pendingRequests.set(id, { resolve, reject, timeout });
8784
+ this.pendingRequests.set(id, { resolve, reject });
7616
8785
  try {
7617
8786
  this.output.write(serializeJsonRpcMessage(message));
7618
8787
  } catch (error) {
7619
- clearTimeout(timeout);
7620
8788
  this.pendingRequests.delete(id);
7621
8789
  reject(error);
7622
8790
  }
@@ -7628,7 +8796,6 @@ var JsonRpcMessageLayer = class {
7628
8796
  return false;
7629
8797
  }
7630
8798
  this.pendingRequests.delete(requestId);
7631
- clearTimeout(pending.timeout);
7632
8799
  pending.reject(reason);
7633
8800
  return true;
7634
8801
  }
@@ -7639,7 +8806,6 @@ var JsonRpcMessageLayer = class {
7639
8806
  this.disposedError = reason;
7640
8807
  for (const controller of this.exchangeControllers) controller.abort(reason);
7641
8808
  for (const pending of this.pendingRequests.values()) {
7642
- clearTimeout(pending.timeout);
7643
8809
  pending.reject(reason);
7644
8810
  }
7645
8811
  this.pendingRequests.clear();
@@ -7777,7 +8943,6 @@ var JsonRpcMessageLayer = class {
7777
8943
  return;
7778
8944
  }
7779
8945
  this.pendingRequests.delete(parsed.message.id);
7780
- clearTimeout(pending.timeout);
7781
8946
  if ("result" in parsed.message) {
7782
8947
  pending.resolve(parsed.message.result);
7783
8948
  return;
@@ -8113,7 +9278,7 @@ function parseJsonRpcPayload(parsed) {
8113
9278
 
8114
9279
  // packages/safe-js/src/modules/mcp-transport.ts
8115
9280
  import { spawn as spawn3 } from "node:child_process";
8116
- import path3 from "node:path";
9281
+ import path5 from "node:path";
8117
9282
  function parseMcpConfig(source, directory) {
8118
9283
  const parsed = JSON.parse(source);
8119
9284
  const options = normalizeMcpOptions(parsed, [
@@ -8124,9 +9289,9 @@ function parseMcpConfig(source, directory) {
8124
9289
  ]);
8125
9290
  for (const server of Object.values(options.servers)) {
8126
9291
  if ("command" in server) {
8127
- server.cwd = path3.resolve(directory, server.cwd ?? ".");
9292
+ server.cwd = path5.resolve(directory, server.cwd ?? ".");
8128
9293
  if (server.command.includes("/") || server.command.includes("\\")) {
8129
- server.command = path3.resolve(directory, server.command);
9294
+ server.command = path5.resolve(directory, server.command);
8130
9295
  }
8131
9296
  }
8132
9297
  }
@@ -8591,4 +9756,4 @@ CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
8591
9756
  the full license text.
8592
9757
 
8593
9758
  */
8594
- //# sourceMappingURL=chunk-YPVGFXBW.js.map
9759
+ //# sourceMappingURL=chunk-HYD4K4HN.js.map