@poe-platform/safe-js 0.1.714 → 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.
- package/dist/auth-store/create-secret-store.d.ts +3 -1
- package/dist/auth-store/encrypted-file-store.d.ts +6 -0
- package/dist/auth-store/index.d.ts +2 -1
- package/dist/auth-store/keychain-store.d.ts +8 -0
- package/dist/auth-store/transaction-lock.d.ts +24 -0
- package/dist/auth-store/types.d.ts +2 -0
- package/dist/safe-js/chunks/{chunk-YPVGFXBW.js → chunk-HYD4K4HN.js} +1933 -768
- package/dist/safe-js/chunks/chunk-HYD4K4HN.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-TUSBJIPM.js → chunk-RML35GIF.js} +2 -2
- package/dist/safe-js/chunks/{cli-runtime-WRORUW5B.js → cli-runtime-PO6DQDHP.js} +3 -3
- package/dist/safe-js/chunks/{mcp-UVOMYCG3.js → mcp-TNVEFJ6K.js} +3 -3
- package/dist/safe-js/cli.js +2 -2
- package/dist/safe-js/index.js +2 -2
- package/package.json +2 -2
- package/dist/safe-js/chunks/chunk-YPVGFXBW.js.map +0 -7
- /package/dist/safe-js/chunks/{chunk-TUSBJIPM.js.map → chunk-RML35GIF.js.map} +0 -0
- /package/dist/safe-js/chunks/{cli-runtime-WRORUW5B.js.map → cli-runtime-PO6DQDHP.js.map} +0 -0
- /package/dist/safe-js/chunks/{mcp-UVOMYCG3.js.map → mcp-TNVEFJ6K.js.map} +0 -0
|
@@ -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 >
|
|
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 >
|
|
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(
|
|
202
|
-
return { path:
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
1053
|
+
evaluateNumberKeywords(schema, value, path6, results);
|
|
1050
1054
|
}
|
|
1051
1055
|
if (typeof value === "string") {
|
|
1052
|
-
evaluateStringKeywords(schema, value,
|
|
1056
|
+
evaluateStringKeywords(schema, value, path6, results);
|
|
1053
1057
|
}
|
|
1054
1058
|
if (Array.isArray(value)) {
|
|
1055
|
-
evaluateArrayKeywords(schema, value,
|
|
1059
|
+
evaluateArrayKeywords(schema, value, path6, results);
|
|
1056
1060
|
}
|
|
1057
1061
|
if (isObject(value)) {
|
|
1058
|
-
evaluateObjectKeywords(node, schema, value,
|
|
1062
|
+
evaluateObjectKeywords(node, schema, value, path6, results);
|
|
1059
1063
|
}
|
|
1060
1064
|
return results;
|
|
1061
1065
|
}
|
|
1062
|
-
function evaluateNumberKeywords(schema, value,
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1099
|
+
issue(path6, `> ${schema.exclusiveMinimum}`, value, `must be > ${schema.exclusiveMinimum}`)
|
|
1096
1100
|
)
|
|
1097
1101
|
);
|
|
1098
1102
|
}
|
|
1099
1103
|
}
|
|
1100
|
-
function evaluateStringKeywords(schema, value,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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([...
|
|
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,
|
|
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,
|
|
1222
|
+
addMissingDependencies(value, dependencies, path6, results);
|
|
1219
1223
|
}
|
|
1220
1224
|
}
|
|
1221
1225
|
}
|
|
1222
1226
|
}
|
|
1223
|
-
function addMissingDependencies(value, dependencies,
|
|
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
|
-
[...
|
|
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/
|
|
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
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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
|
|
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 =
|
|
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(
|
|
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}.${
|
|
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 =
|
|
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
|
|
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 =
|
|
4377
|
+
const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
|
|
3694
4378
|
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
3695
|
-
return [
|
|
4379
|
+
return [path2.dirname(resolvedPath), resolvedPath];
|
|
3696
4380
|
}
|
|
3697
4381
|
const protectedPaths = [resolvedStartPath];
|
|
3698
4382
|
let currentPath = resolvedStartPath;
|
|
3699
|
-
for (const segment of
|
|
3700
|
-
currentPath =
|
|
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 (
|
|
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 =
|
|
3725
|
-
const segments = resolvedPath.slice(parsed.root.length).split(
|
|
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 =
|
|
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 =
|
|
3742
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
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 =
|
|
3791
|
-
const iv =
|
|
3792
|
-
const authTag =
|
|
3793
|
-
const 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
|
|
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 =
|
|
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 =
|
|
4673
|
+
const value = getOwnEntry3(result, key2);
|
|
3978
4674
|
return typeof value === "string" ? value : "";
|
|
3979
4675
|
}
|
|
3980
|
-
function
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
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
|
|
4102
|
-
const
|
|
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 :
|
|
4819
|
+
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4105
4820
|
const fileStore = {
|
|
4106
4821
|
...options.fileStore,
|
|
4107
|
-
|
|
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
|
|
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
|
|
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 =
|
|
4865
|
+
const value = getOwnEntry4(record2, key2);
|
|
4146
4866
|
return typeof value === "string" ? value : void 0;
|
|
4147
4867
|
}
|
|
4148
4868
|
function isStoredOAuthSession(value) {
|
|
4149
|
-
if (!
|
|
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
|
-
|
|
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 (!
|
|
4875
|
+
if (!isObjectRecord2(value)) {
|
|
4163
4876
|
return false;
|
|
4164
4877
|
}
|
|
4165
|
-
return isNonBlankOwnString(value, "resourceMetadataUrl") &&
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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/
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
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
|
-
|
|
4222
|
-
}
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
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
|
-
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
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
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
async function
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
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
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
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
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
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
|
|
4291
|
-
if (
|
|
4292
|
-
|
|
4293
|
-
|
|
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
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
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
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
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
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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
|
-
|
|
4515
|
-
|
|
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 =
|
|
4519
|
-
this.errorUri =
|
|
4520
|
-
this.error_description =
|
|
4521
|
-
this.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
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4671
|
-
const
|
|
4672
|
-
const
|
|
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
|
|
4675
|
-
const
|
|
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
|
|
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
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
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
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
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
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
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
|
-
|
|
4752
|
-
|
|
4753
|
-
return inFlight;
|
|
5649
|
+
if (session.tokens?.refreshToken === void 0) {
|
|
5650
|
+
return session;
|
|
4754
5651
|
}
|
|
4755
|
-
|
|
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
|
-
|
|
4758
|
-
|
|
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
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
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
|
-
|
|
4801
|
-
|
|
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
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
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
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
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
|
-
|
|
4910
|
-
if (
|
|
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
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
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
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
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
|
-
|
|
4962
|
-
redirectUri
|
|
5867
|
+
clientMetadata,
|
|
5868
|
+
redirectUri,
|
|
5869
|
+
registrationMethod
|
|
4963
5870
|
);
|
|
4964
|
-
const
|
|
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
|
|
4975
|
-
|
|
4976
|
-
|
|
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:
|
|
4981
|
-
|
|
5887
|
+
clientId: registration.client_id.trim(),
|
|
5888
|
+
...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
|
|
5889
|
+
...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
|
|
5890
|
+
registration
|
|
4982
5891
|
};
|
|
4983
|
-
|
|
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:
|
|
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 :
|
|
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
|
|
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
|
|
5091
|
-
if (
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
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
|
-
|
|
5103
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
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,
|
|
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 ||
|
|
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: [...
|
|
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" ? [...
|
|
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,
|
|
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,
|
|
5409
|
-
else visit(child,
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
6513
|
+
void response.body?.cancel().catch(() => void 0);
|
|
5540
6514
|
const statusDescriptor = `${response.status} ${response.statusText}`.trim();
|
|
5541
|
-
throw new
|
|
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,
|
|
5551
|
-
|
|
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,
|
|
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(
|
|
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.
|
|
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
|
|
5605
|
-
issuerUrl.pathname = `${
|
|
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, "
|
|
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(
|
|
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, "
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
6867
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
7197
|
-
|
|
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
|
|
8359
|
+
headers,
|
|
7201
8360
|
body: input.body
|
|
7202
|
-
},
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
response = await request();
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
9292
|
+
server.cwd = path5.resolve(directory, server.cwd ?? ".");
|
|
8128
9293
|
if (server.command.includes("/") || server.command.includes("\\")) {
|
|
8129
|
-
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-
|
|
9759
|
+
//# sourceMappingURL=chunk-HYD4K4HN.js.map
|