canvas-parent-mcp 2.1.1 → 2.1.3
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/dist/bundle.js +1173 -404
- package/dist/client.js +87 -7
- package/dist/index.js +1 -1
- package/dist/tools/files.js +4 -4
- package/package.json +5 -5
- package/server.json +2 -2
- package/skills/canvas-parent/SKILL.md +1 -1
- package/skills/canvas-parent-api/references/canvas-endpoints.md +3 -1
package/dist/bundle.js
CHANGED
|
@@ -4858,7 +4858,7 @@ var require_memoizer = __commonJS({
|
|
|
4858
4858
|
var NONE2 = 0;
|
|
4859
4859
|
var ASSUMED2 = 1;
|
|
4860
4860
|
var PROVEN2 = 2;
|
|
4861
|
-
function isRecursive2(inst, stack,
|
|
4861
|
+
function isRecursive2(inst, stack, resolve5) {
|
|
4862
4862
|
const cached2 = recursive2.get(inst);
|
|
4863
4863
|
if (cached2 !== void 0)
|
|
4864
4864
|
return cached2 ? PROVEN2 : NONE2;
|
|
@@ -4868,7 +4868,7 @@ var require_memoizer = __commonJS({
|
|
|
4868
4868
|
let result = NONE2;
|
|
4869
4869
|
const check2 = (child) => {
|
|
4870
4870
|
if (result !== PROVEN2 && child?._zod) {
|
|
4871
|
-
const answer = isRecursive2(child, stack,
|
|
4871
|
+
const answer = isRecursive2(child, stack, resolve5);
|
|
4872
4872
|
if (answer > result)
|
|
4873
4873
|
result = answer;
|
|
4874
4874
|
}
|
|
@@ -4879,7 +4879,7 @@ var require_memoizer = __commonJS({
|
|
|
4879
4879
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
4880
4880
|
if (spread && !desc.enumerable)
|
|
4881
4881
|
continue;
|
|
4882
|
-
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack,
|
|
4882
|
+
const child = desc.get ? ASSUMED2 : desc.value?._zod ? isRecursive2(desc.value, stack, resolve5) : NONE2;
|
|
4883
4883
|
if (child > answer)
|
|
4884
4884
|
answer = child;
|
|
4885
4885
|
}
|
|
@@ -4943,7 +4943,7 @@ var require_memoizer = __commonJS({
|
|
|
4943
4943
|
break;
|
|
4944
4944
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
4945
4945
|
case "lazy": {
|
|
4946
|
-
const inner = def._cachedInner ?? (
|
|
4946
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
4947
4947
|
merge2(inner ? isRecursive2(inner, stack, false) : ASSUMED2);
|
|
4948
4948
|
break;
|
|
4949
4949
|
}
|
|
@@ -25548,7 +25548,7 @@ var require_websocket = __commonJS({
|
|
|
25548
25548
|
var http = __require("http");
|
|
25549
25549
|
var net = __require("net");
|
|
25550
25550
|
var tls = __require("tls");
|
|
25551
|
-
var { randomBytes:
|
|
25551
|
+
var { randomBytes: randomBytes4, createHash } = __require("crypto");
|
|
25552
25552
|
var { Duplex, Readable } = __require("stream");
|
|
25553
25553
|
var { URL: URL2 } = __require("url");
|
|
25554
25554
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -26086,7 +26086,7 @@ var require_websocket = __commonJS({
|
|
|
26086
26086
|
}
|
|
26087
26087
|
}
|
|
26088
26088
|
const defaultPort = isSecure ? 443 : 80;
|
|
26089
|
-
const key =
|
|
26089
|
+
const key = randomBytes4(16).toString("base64");
|
|
26090
26090
|
const request = isSecure ? https.request : http.request;
|
|
26091
26091
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
26092
26092
|
let perMessageDeflate;
|
|
@@ -26980,9 +26980,23 @@ var require_websocket_server = __commonJS({
|
|
|
26980
26980
|
});
|
|
26981
26981
|
|
|
26982
26982
|
// src/index.ts
|
|
26983
|
-
import { dirname as
|
|
26983
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
26984
26984
|
import { fileURLToPath } from "url";
|
|
26985
26985
|
|
|
26986
|
+
// node_modules/@chrischall/mcp-utils/dist/cancel/index.js
|
|
26987
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
26988
|
+
var storage = new AsyncLocalStorage();
|
|
26989
|
+
function withCallSignal(signal, fn, request) {
|
|
26990
|
+
return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
|
|
26991
|
+
}
|
|
26992
|
+
|
|
26993
|
+
// node_modules/@chrischall/mcp-utils/dist/caller/index.js
|
|
26994
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
26995
|
+
var storage2 = new AsyncLocalStorage2();
|
|
26996
|
+
function withCallerCapabilities(capabilities, fn) {
|
|
26997
|
+
return capabilities ? storage2.run(capabilities, fn) : fn();
|
|
26998
|
+
}
|
|
26999
|
+
|
|
26986
27000
|
// node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
26987
27001
|
var __create2 = Object.create;
|
|
26988
27002
|
var __defProp2 = Object.defineProperty;
|
|
@@ -31385,14 +31399,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
31385
31399
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
31386
31400
|
}
|
|
31387
31401
|
function sleep(ms, signal) {
|
|
31388
|
-
return new Promise((
|
|
31402
|
+
return new Promise((resolve5, reject) => {
|
|
31389
31403
|
if (signal?.aborted) {
|
|
31390
31404
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
31391
31405
|
return;
|
|
31392
31406
|
}
|
|
31393
31407
|
const timer = setTimeout(() => {
|
|
31394
31408
|
signal?.removeEventListener("abort", onAbort);
|
|
31395
|
-
|
|
31409
|
+
resolve5();
|
|
31396
31410
|
}, ms);
|
|
31397
31411
|
const onAbort = () => {
|
|
31398
31412
|
clearTimeout(timer);
|
|
@@ -32186,7 +32200,7 @@ var Protocol = class {
|
|
|
32186
32200
|
const flowStartedAt = Date.now();
|
|
32187
32201
|
let onAbort;
|
|
32188
32202
|
let cleanupMessageId;
|
|
32189
|
-
return new Promise((
|
|
32203
|
+
return new Promise((resolve5, reject) => {
|
|
32190
32204
|
const earlyReject = (error62) => {
|
|
32191
32205
|
reject(error62);
|
|
32192
32206
|
};
|
|
@@ -32254,7 +32268,7 @@ var Protocol = class {
|
|
|
32254
32268
|
}
|
|
32255
32269
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
32256
32270
|
if (decoded.kind === "input_required") {
|
|
32257
|
-
if (options?.allowInputRequired === true) return
|
|
32271
|
+
if (options?.allowInputRequired === true) return resolve5(manualInputRequiredValue(decoded));
|
|
32258
32272
|
const flow = {
|
|
32259
32273
|
codec: codec2,
|
|
32260
32274
|
request,
|
|
@@ -32266,11 +32280,11 @@ var Protocol = class {
|
|
|
32266
32280
|
params
|
|
32267
32281
|
}, resultSchema, legOptions)
|
|
32268
32282
|
};
|
|
32269
|
-
return
|
|
32283
|
+
return resolve5(this._resolveNonCompleteResult(decoded, flow));
|
|
32270
32284
|
}
|
|
32271
32285
|
const result = decoded.result;
|
|
32272
32286
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
32273
|
-
if (parseResult.success)
|
|
32287
|
+
if (parseResult.success) resolve5(parseResult.data);
|
|
32274
32288
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
32275
32289
|
}, reject);
|
|
32276
32290
|
});
|
|
@@ -35182,7 +35196,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
35182
35196
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
35183
35197
|
const schOrFunc = root.refs[ref];
|
|
35184
35198
|
if (schOrFunc) return schOrFunc;
|
|
35185
|
-
let _sch =
|
|
35199
|
+
let _sch = resolve5.call(this, root, ref);
|
|
35186
35200
|
if (_sch === void 0) {
|
|
35187
35201
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
35188
35202
|
const { schemaId } = this.opts;
|
|
@@ -35208,7 +35222,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
35208
35222
|
function sameSchemaEnv(s1, s2) {
|
|
35209
35223
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
35210
35224
|
}
|
|
35211
|
-
function
|
|
35225
|
+
function resolve5(root, ref) {
|
|
35212
35226
|
let sch;
|
|
35213
35227
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
35214
35228
|
return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
|
|
@@ -35658,47 +35672,47 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
35658
35672
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
35659
35673
|
return uri;
|
|
35660
35674
|
}
|
|
35661
|
-
function
|
|
35675
|
+
function resolve5(baseURI, relativeURI, options) {
|
|
35662
35676
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
35663
35677
|
const resolved2 = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
35664
35678
|
schemelessOptions.skipEscape = true;
|
|
35665
35679
|
return serialize(resolved2, schemelessOptions);
|
|
35666
35680
|
}
|
|
35667
|
-
function resolveComponent(base,
|
|
35681
|
+
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
35668
35682
|
const target = {};
|
|
35669
35683
|
if (!skipNormalization) {
|
|
35670
35684
|
base = parse3(serialize(base, options), options);
|
|
35671
|
-
|
|
35685
|
+
relative2 = parse3(serialize(relative2, options), options);
|
|
35672
35686
|
}
|
|
35673
35687
|
options = options || {};
|
|
35674
|
-
if (!options.tolerant &&
|
|
35675
|
-
target.scheme =
|
|
35676
|
-
target.userinfo =
|
|
35677
|
-
target.host =
|
|
35678
|
-
target.port =
|
|
35679
|
-
target.path = removeDotSegments(
|
|
35680
|
-
target.query =
|
|
35688
|
+
if (!options.tolerant && relative2.scheme) {
|
|
35689
|
+
target.scheme = relative2.scheme;
|
|
35690
|
+
target.userinfo = relative2.userinfo;
|
|
35691
|
+
target.host = relative2.host;
|
|
35692
|
+
target.port = relative2.port;
|
|
35693
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
35694
|
+
target.query = relative2.query;
|
|
35681
35695
|
} else {
|
|
35682
|
-
if (
|
|
35683
|
-
target.userinfo =
|
|
35684
|
-
target.host =
|
|
35685
|
-
target.port =
|
|
35686
|
-
target.path = removeDotSegments(
|
|
35687
|
-
target.query =
|
|
35696
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
35697
|
+
target.userinfo = relative2.userinfo;
|
|
35698
|
+
target.host = relative2.host;
|
|
35699
|
+
target.port = relative2.port;
|
|
35700
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
35701
|
+
target.query = relative2.query;
|
|
35688
35702
|
} else {
|
|
35689
|
-
if (!
|
|
35703
|
+
if (!relative2.path) {
|
|
35690
35704
|
target.path = base.path;
|
|
35691
|
-
if (
|
|
35705
|
+
if (relative2.query !== void 0) target.query = relative2.query;
|
|
35692
35706
|
else target.query = base.query;
|
|
35693
35707
|
} else {
|
|
35694
|
-
if (
|
|
35708
|
+
if (relative2.path[0] === "/") target.path = removeDotSegments(relative2.path);
|
|
35695
35709
|
else {
|
|
35696
|
-
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" +
|
|
35697
|
-
else if (!base.path) target.path =
|
|
35698
|
-
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
35710
|
+
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) target.path = "/" + relative2.path;
|
|
35711
|
+
else if (!base.path) target.path = relative2.path;
|
|
35712
|
+
else target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
|
|
35699
35713
|
target.path = removeDotSegments(target.path);
|
|
35700
35714
|
}
|
|
35701
|
-
target.query =
|
|
35715
|
+
target.query = relative2.query;
|
|
35702
35716
|
}
|
|
35703
35717
|
target.userinfo = base.userinfo;
|
|
35704
35718
|
target.host = base.host;
|
|
@@ -35706,7 +35720,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
35706
35720
|
}
|
|
35707
35721
|
target.scheme = base.scheme;
|
|
35708
35722
|
}
|
|
35709
|
-
target.fragment =
|
|
35723
|
+
target.fragment = relative2.fragment;
|
|
35710
35724
|
return target;
|
|
35711
35725
|
}
|
|
35712
35726
|
function equal(uriA, uriB, options) {
|
|
@@ -35832,7 +35846,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
35832
35846
|
const fastUri = {
|
|
35833
35847
|
SCHEMES,
|
|
35834
35848
|
normalize,
|
|
35835
|
-
resolve:
|
|
35849
|
+
resolve: resolve5,
|
|
35836
35850
|
resolveComponent,
|
|
35837
35851
|
equal,
|
|
35838
35852
|
serialize,
|
|
@@ -41205,7 +41219,7 @@ var StdioServerTransport = class {
|
|
|
41205
41219
|
}
|
|
41206
41220
|
send(message) {
|
|
41207
41221
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
41208
|
-
return new Promise((
|
|
41222
|
+
return new Promise((resolve5, reject) => {
|
|
41209
41223
|
const json2 = serializeMessage(message);
|
|
41210
41224
|
let settled = false;
|
|
41211
41225
|
const onError = (error62) => {
|
|
@@ -41220,14 +41234,14 @@ var StdioServerTransport = class {
|
|
|
41220
41234
|
settled = true;
|
|
41221
41235
|
this._stdout.off("error", onError);
|
|
41222
41236
|
this._stdout.off("drain", onDrain);
|
|
41223
|
-
|
|
41237
|
+
resolve5();
|
|
41224
41238
|
};
|
|
41225
41239
|
this._stdout.once("error", onError);
|
|
41226
41240
|
if (this._stdout.write(json2)) {
|
|
41227
41241
|
if (settled) return;
|
|
41228
41242
|
settled = true;
|
|
41229
41243
|
this._stdout.off("error", onError);
|
|
41230
|
-
|
|
41244
|
+
resolve5();
|
|
41231
41245
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
41232
41246
|
});
|
|
41233
41247
|
}
|
|
@@ -41281,14 +41295,14 @@ var StdioConnectionChannel = class {
|
|
|
41281
41295
|
*/
|
|
41282
41296
|
async whenRequestsAnswered(timeoutMs) {
|
|
41283
41297
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
41284
|
-
return await new Promise((
|
|
41298
|
+
return await new Promise((resolve5) => {
|
|
41285
41299
|
const waiter = () => {
|
|
41286
41300
|
clearTimeout(timer);
|
|
41287
|
-
|
|
41301
|
+
resolve5(true);
|
|
41288
41302
|
};
|
|
41289
41303
|
const timer = setTimeout(() => {
|
|
41290
41304
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
41291
|
-
|
|
41305
|
+
resolve5(false);
|
|
41292
41306
|
}, timeoutMs);
|
|
41293
41307
|
this._drainWaiters.push(waiter);
|
|
41294
41308
|
});
|
|
@@ -41662,13 +41676,28 @@ var API_KEY_RE = new RegExp([
|
|
|
41662
41676
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
41663
41677
|
// webhook signing secret (Stripe-style)
|
|
41664
41678
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
41665
|
-
var QUERY_SECRET_RE = /([?&](?:
|
|
41679
|
+
var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
41680
|
+
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
41681
|
+
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
41682
|
+
var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
|
|
41666
41683
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
41667
|
-
var JSON_SECRET_KEYS =
|
|
41668
|
-
|
|
41669
|
-
|
|
41684
|
+
var JSON_SECRET_KEYS = [
|
|
41685
|
+
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
41686
|
+
"client[_-]?secret",
|
|
41687
|
+
"api[_-]?secret",
|
|
41688
|
+
"(?:x[_-])?api[_-]?key",
|
|
41689
|
+
"private[_-]?key",
|
|
41690
|
+
"x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
|
|
41691
|
+
"(?:proxy-)?authorization",
|
|
41692
|
+
"password",
|
|
41693
|
+
"passwd",
|
|
41694
|
+
"secret",
|
|
41695
|
+
"token"
|
|
41696
|
+
].join("|");
|
|
41697
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
41698
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
41670
41699
|
function redactSecrets(text) {
|
|
41671
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
41700
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
41672
41701
|
}
|
|
41673
41702
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
41674
41703
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -46709,7 +46738,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
46709
46738
|
var NONE = 0;
|
|
46710
46739
|
var ASSUMED = 1;
|
|
46711
46740
|
var PROVEN = 2;
|
|
46712
|
-
function isRecursive(inst, stack,
|
|
46741
|
+
function isRecursive(inst, stack, resolve5) {
|
|
46713
46742
|
const cached2 = recursive.get(inst);
|
|
46714
46743
|
if (cached2 !== void 0)
|
|
46715
46744
|
return cached2 ? PROVEN : NONE;
|
|
@@ -46719,7 +46748,7 @@ function isRecursive(inst, stack, resolve2) {
|
|
|
46719
46748
|
let result = NONE;
|
|
46720
46749
|
const check2 = (child) => {
|
|
46721
46750
|
if (result !== PROVEN && child?._zod) {
|
|
46722
|
-
const answer = isRecursive(child, stack,
|
|
46751
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
46723
46752
|
if (answer > result)
|
|
46724
46753
|
result = answer;
|
|
46725
46754
|
}
|
|
@@ -46730,7 +46759,7 @@ function isRecursive(inst, stack, resolve2) {
|
|
|
46730
46759
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
46731
46760
|
if (spread && !desc.enumerable)
|
|
46732
46761
|
continue;
|
|
46733
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
46762
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
|
|
46734
46763
|
if (child > answer)
|
|
46735
46764
|
answer = child;
|
|
46736
46765
|
}
|
|
@@ -46794,7 +46823,7 @@ function isRecursive(inst, stack, resolve2) {
|
|
|
46794
46823
|
break;
|
|
46795
46824
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
46796
46825
|
case "lazy": {
|
|
46797
|
-
const inner = def._cachedInner ?? (
|
|
46826
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
46798
46827
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
46799
46828
|
break;
|
|
46800
46829
|
}
|
|
@@ -61447,17 +61476,73 @@ Hint: ${err.hint}`);
|
|
|
61447
61476
|
}
|
|
61448
61477
|
throw err;
|
|
61449
61478
|
}
|
|
61479
|
+
function mcpRequestFrom(args) {
|
|
61480
|
+
const ctx = args.at(-1);
|
|
61481
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61482
|
+
return void 0;
|
|
61483
|
+
const req = ctx.mcpReq;
|
|
61484
|
+
if (typeof req !== "object" || req === null)
|
|
61485
|
+
return void 0;
|
|
61486
|
+
return typeof req.notify === "function" ? req : void 0;
|
|
61487
|
+
}
|
|
61488
|
+
function callSignalFrom(args) {
|
|
61489
|
+
const ctx = args.at(-1);
|
|
61490
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61491
|
+
return void 0;
|
|
61492
|
+
const req = ctx.mcpReq;
|
|
61493
|
+
if (typeof req !== "object" || req === null)
|
|
61494
|
+
return void 0;
|
|
61495
|
+
const signal = req.signal;
|
|
61496
|
+
return signal instanceof AbortSignal ? signal : void 0;
|
|
61497
|
+
}
|
|
61498
|
+
function declaredCapabilitiesFrom(server) {
|
|
61499
|
+
const inner = server.server;
|
|
61500
|
+
if (typeof inner !== "object" || inner === null)
|
|
61501
|
+
return void 0;
|
|
61502
|
+
const read = inner.getClientCapabilities;
|
|
61503
|
+
if (typeof read !== "function")
|
|
61504
|
+
return void 0;
|
|
61505
|
+
try {
|
|
61506
|
+
const declared = read.call(inner);
|
|
61507
|
+
return typeof declared === "object" && declared !== null ? declared : void 0;
|
|
61508
|
+
} catch {
|
|
61509
|
+
return void 0;
|
|
61510
|
+
}
|
|
61511
|
+
}
|
|
61450
61512
|
function surfaceToolHints(server) {
|
|
61451
61513
|
const register2 = server.registerTool.bind(server);
|
|
61452
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
61453
|
-
|
|
61454
|
-
|
|
61455
|
-
|
|
61456
|
-
|
|
61457
|
-
|
|
61458
|
-
|
|
61459
|
-
|
|
61460
|
-
|
|
61514
|
+
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
|
|
61515
|
+
// THE CALLER'S CANCELLATION, made ambient for the whole handler
|
|
61516
|
+
// (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
|
|
61517
|
+
// measured against @modelcontextprotocol/server 2.0.0, a cancelled
|
|
61518
|
+
// call aborts `ctx.mcpReq.signal` with the caller's reason and the
|
|
61519
|
+
// handler runs to completion regardless — so the HTTP request stays
|
|
61520
|
+
// in flight, the child keeps burning metered CPU, and the upstream
|
|
61521
|
+
// keeps being hit for somebody who has gone. claude.ai sent 101
|
|
61522
|
+
// cancellations in the week to 2026-09-20.
|
|
61523
|
+
//
|
|
61524
|
+
// Here because this is the one wrapper every tool already passes
|
|
61525
|
+
// through: threading the signal by hand would mean editing several
|
|
61526
|
+
// hundred handlers and missing exactly the ones nobody edits.
|
|
61527
|
+
withCallSignal(callSignalFrom(args), () => (
|
|
61528
|
+
// WHAT THE CALLER CAN DO, made ambient for the same reason and in
|
|
61529
|
+
// the same place (`caller/index.ts`). Only the 2025-era half needs
|
|
61530
|
+
// the wrapper — a 2026-07-28 request answers for itself off its own
|
|
61531
|
+
// envelope — but a guarded tool that cannot tell whether the caller
|
|
61532
|
+
// can be shown a prompt returns one the SDK then refuses to deliver,
|
|
61533
|
+
// and that refusal reaches the caller as an unexplained protocol
|
|
61534
|
+
// error it had no chance to catch.
|
|
61535
|
+
withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
|
|
61536
|
+
let result;
|
|
61537
|
+
try {
|
|
61538
|
+
result = cb(...args);
|
|
61539
|
+
} catch (err) {
|
|
61540
|
+
return hintResultOrRethrow(err);
|
|
61541
|
+
}
|
|
61542
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
61543
|
+
})
|
|
61544
|
+
), mcpRequestFrom(args))
|
|
61545
|
+
));
|
|
61461
61546
|
}
|
|
61462
61547
|
async function createMcpServer(opts) {
|
|
61463
61548
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -61472,13 +61557,47 @@ async function createMcpServer(opts) {
|
|
|
61472
61557
|
}
|
|
61473
61558
|
return server;
|
|
61474
61559
|
}
|
|
61560
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
61561
|
+
var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
|
|
61475
61562
|
function withGracefulShutdown(target, opts = {}) {
|
|
61476
61563
|
const shouldExit = opts.exit ?? true;
|
|
61564
|
+
if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
|
|
61565
|
+
throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
|
|
61566
|
+
}
|
|
61567
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
61568
|
+
if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
|
|
61569
|
+
throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
|
|
61570
|
+
}
|
|
61571
|
+
const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
|
|
61477
61572
|
let shuttingDown = false;
|
|
61573
|
+
let firstSignalAt = 0;
|
|
61574
|
+
let exited = false;
|
|
61575
|
+
const exitOnce = () => {
|
|
61576
|
+
if (exited)
|
|
61577
|
+
return;
|
|
61578
|
+
exited = true;
|
|
61579
|
+
process.exit(0);
|
|
61580
|
+
};
|
|
61478
61581
|
const handler = (signal) => {
|
|
61479
|
-
if (shuttingDown)
|
|
61582
|
+
if (shuttingDown) {
|
|
61583
|
+
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
61584
|
+
return;
|
|
61585
|
+
if (shouldExit) {
|
|
61586
|
+
console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
|
|
61587
|
+
exitOnce();
|
|
61588
|
+
}
|
|
61480
61589
|
return;
|
|
61590
|
+
}
|
|
61481
61591
|
shuttingDown = true;
|
|
61592
|
+
firstSignalAt = Date.now();
|
|
61593
|
+
let timer;
|
|
61594
|
+
if (shouldExit) {
|
|
61595
|
+
timer = setTimeout(() => {
|
|
61596
|
+
console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
|
|
61597
|
+
exitOnce();
|
|
61598
|
+
}, timeoutMs);
|
|
61599
|
+
timer.unref?.();
|
|
61600
|
+
}
|
|
61482
61601
|
void (async () => {
|
|
61483
61602
|
try {
|
|
61484
61603
|
if (opts.onSignal)
|
|
@@ -61487,8 +61606,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
61487
61606
|
} catch (err) {
|
|
61488
61607
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
61489
61608
|
} finally {
|
|
61609
|
+
if (timer !== void 0)
|
|
61610
|
+
clearTimeout(timer);
|
|
61490
61611
|
if (shouldExit)
|
|
61491
|
-
|
|
61612
|
+
exitOnce();
|
|
61492
61613
|
}
|
|
61493
61614
|
})();
|
|
61494
61615
|
};
|
|
@@ -61518,6 +61639,8 @@ function runMcp(opts) {
|
|
|
61518
61639
|
}
|
|
61519
61640
|
|
|
61520
61641
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
61642
|
+
import { homedir } from "node:os";
|
|
61643
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
61521
61644
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
61522
61645
|
function readEnvVar(key, opts = {}) {
|
|
61523
61646
|
const env = opts.env ?? process.env;
|
|
@@ -61544,6 +61667,15 @@ function parseBoolEnv(key, opts = {}) {
|
|
|
61544
61667
|
return false;
|
|
61545
61668
|
return fallback;
|
|
61546
61669
|
}
|
|
61670
|
+
function expandPath(p) {
|
|
61671
|
+
let expanded = p;
|
|
61672
|
+
if (p === "~") {
|
|
61673
|
+
expanded = homedir();
|
|
61674
|
+
} else if (p.startsWith("~/")) {
|
|
61675
|
+
expanded = join(homedir(), p.slice(2));
|
|
61676
|
+
}
|
|
61677
|
+
return isAbsolute(expanded) ? expanded : resolve(expanded);
|
|
61678
|
+
}
|
|
61547
61679
|
async function loadDotenvSafely(opts = {}) {
|
|
61548
61680
|
try {
|
|
61549
61681
|
const mod = await import(
|
|
@@ -61561,6 +61693,41 @@ async function loadDotenvSafely(opts = {}) {
|
|
|
61561
61693
|
}
|
|
61562
61694
|
}
|
|
61563
61695
|
|
|
61696
|
+
// node_modules/@chrischall/mcp-utils/dist/fs/confine.js
|
|
61697
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
61698
|
+
import { dirname, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path";
|
|
61699
|
+
function isInside(root, target) {
|
|
61700
|
+
const rel = relative(root, target);
|
|
61701
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute2(rel);
|
|
61702
|
+
}
|
|
61703
|
+
function realPathAllowingMissing(p) {
|
|
61704
|
+
const abs = resolve2(p);
|
|
61705
|
+
let existing = abs;
|
|
61706
|
+
const tail = [];
|
|
61707
|
+
while (!existsSync(existing)) {
|
|
61708
|
+
const parent = dirname(existing);
|
|
61709
|
+
if (parent === existing)
|
|
61710
|
+
break;
|
|
61711
|
+
tail.unshift(existing.slice(parent.length).replace(/^[/\\]+/, ""));
|
|
61712
|
+
existing = parent;
|
|
61713
|
+
}
|
|
61714
|
+
let real;
|
|
61715
|
+
try {
|
|
61716
|
+
real = realpathSync(existing);
|
|
61717
|
+
} catch {
|
|
61718
|
+
real = existing;
|
|
61719
|
+
}
|
|
61720
|
+
return tail.length > 0 ? resolve2(real, ...tail) : real;
|
|
61721
|
+
}
|
|
61722
|
+
function assertPathWithinRoots(path, roots) {
|
|
61723
|
+
const target = realPathAllowingMissing(expandPath(path));
|
|
61724
|
+
for (const root of roots) {
|
|
61725
|
+
if (isInside(realPathAllowingMissing(expandPath(root)), target))
|
|
61726
|
+
return target;
|
|
61727
|
+
}
|
|
61728
|
+
throw new Error(`Path is outside the allowed directories: ${path}`);
|
|
61729
|
+
}
|
|
61730
|
+
|
|
61564
61731
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
61565
61732
|
function buildQueryString(params) {
|
|
61566
61733
|
const parts = [];
|
|
@@ -61695,6 +61862,33 @@ var pageSchema = {
|
|
|
61695
61862
|
};
|
|
61696
61863
|
|
|
61697
61864
|
// node_modules/@chrischall/mcp-utils/dist/auth/index.js
|
|
61865
|
+
var OAuth2RefreshError = class extends McpToolError {
|
|
61866
|
+
status;
|
|
61867
|
+
constructor(status, message, opts) {
|
|
61868
|
+
super(message, opts);
|
|
61869
|
+
this.name = "OAuth2RefreshError";
|
|
61870
|
+
this.status = status;
|
|
61871
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
61872
|
+
}
|
|
61873
|
+
};
|
|
61874
|
+
var OAuth2RotationPersistError = class extends McpToolError {
|
|
61875
|
+
persistenceFailure = true;
|
|
61876
|
+
result;
|
|
61877
|
+
constructor(result, cause) {
|
|
61878
|
+
super(`OAuth2 refresh token rotated, but saving it failed: ${truncateErrorMessage(messageOf2(cause), 200)}`, {
|
|
61879
|
+
hint: "The new refresh token is still held in memory; fix the storage problem before the process restarts.",
|
|
61880
|
+
cause
|
|
61881
|
+
});
|
|
61882
|
+
this.name = "OAuth2RotationPersistError";
|
|
61883
|
+
this.result = result;
|
|
61884
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
61885
|
+
}
|
|
61886
|
+
};
|
|
61887
|
+
function isRetryable(err) {
|
|
61888
|
+
if (!(err instanceof OAuth2RefreshError))
|
|
61889
|
+
return true;
|
|
61890
|
+
return err.status >= 500 || err.status === 429 || err.status === 408;
|
|
61891
|
+
}
|
|
61698
61892
|
var sleep2 = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
61699
61893
|
function createOAuth2Refresher(opts) {
|
|
61700
61894
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
@@ -61702,10 +61896,13 @@ function createOAuth2Refresher(opts) {
|
|
|
61702
61896
|
const maxRetries = opts.retry?.count ?? 0;
|
|
61703
61897
|
const retryDelayMs = opts.retry?.delayMs ?? 0;
|
|
61704
61898
|
let inFlight = null;
|
|
61899
|
+
let currentRefreshToken = opts.refreshToken;
|
|
61900
|
+
const spent = /* @__PURE__ */ new Set();
|
|
61901
|
+
let rotatedTo;
|
|
61705
61902
|
async function exchangeOnce() {
|
|
61706
61903
|
const body = new URLSearchParams({
|
|
61707
61904
|
grant_type: grantType,
|
|
61708
|
-
refresh_token:
|
|
61905
|
+
refresh_token: currentRefreshToken,
|
|
61709
61906
|
...opts.params
|
|
61710
61907
|
}).toString();
|
|
61711
61908
|
const res = await doFetch(opts.endpoint, {
|
|
@@ -61718,7 +61915,7 @@ function createOAuth2Refresher(opts) {
|
|
|
61718
61915
|
});
|
|
61719
61916
|
if (!res.ok) {
|
|
61720
61917
|
const errText = await res.text().catch(() => "");
|
|
61721
|
-
throw
|
|
61918
|
+
throw new OAuth2RefreshError(res.status, `OAuth2 token refresh failed: ${res.status} ${res.statusText}: ${truncateErrorMessage(errText, 200)}`, { hint: "The refresh token may be expired or revoked \u2014 re-authenticate." });
|
|
61722
61919
|
}
|
|
61723
61920
|
const data = await res.json().catch(() => null);
|
|
61724
61921
|
const accessToken = data?.access_token;
|
|
@@ -61730,6 +61927,11 @@ function createOAuth2Refresher(opts) {
|
|
|
61730
61927
|
const result = { accessToken };
|
|
61731
61928
|
if (typeof data?.refresh_token === "string" && data.refresh_token.length > 0) {
|
|
61732
61929
|
result.refreshToken = data.refresh_token;
|
|
61930
|
+
if (data.refresh_token !== currentRefreshToken) {
|
|
61931
|
+
spent.add(currentRefreshToken);
|
|
61932
|
+
currentRefreshToken = data.refresh_token;
|
|
61933
|
+
rotatedTo = data.refresh_token;
|
|
61934
|
+
}
|
|
61733
61935
|
}
|
|
61734
61936
|
if (typeof data?.expires_in === "number") {
|
|
61735
61937
|
result.expiresIn = data.expires_in;
|
|
@@ -61744,16 +61946,32 @@ function createOAuth2Refresher(opts) {
|
|
|
61744
61946
|
return await exchangeOnce();
|
|
61745
61947
|
} catch (e) {
|
|
61746
61948
|
lastErr = e;
|
|
61949
|
+
if (!isRetryable(e))
|
|
61950
|
+
break;
|
|
61747
61951
|
if (attempt < maxRetries)
|
|
61748
61952
|
await sleep2(retryDelayMs);
|
|
61749
61953
|
}
|
|
61750
61954
|
}
|
|
61751
61955
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
61752
61956
|
}
|
|
61753
|
-
return function refresh() {
|
|
61957
|
+
return function refresh(refreshToken) {
|
|
61754
61958
|
if (inFlight)
|
|
61755
61959
|
return inFlight;
|
|
61756
|
-
|
|
61960
|
+
if (typeof refreshToken === "string" && refreshToken.length > 0 && !spent.has(refreshToken)) {
|
|
61961
|
+
currentRefreshToken = refreshToken;
|
|
61962
|
+
}
|
|
61963
|
+
const p = (async () => {
|
|
61964
|
+
rotatedTo = void 0;
|
|
61965
|
+
const result = await exchangeWithRetry();
|
|
61966
|
+
if (rotatedTo !== void 0 && opts.onRotate) {
|
|
61967
|
+
try {
|
|
61968
|
+
await opts.onRotate(rotatedTo);
|
|
61969
|
+
} catch (err) {
|
|
61970
|
+
throw new OAuth2RotationPersistError(result, err);
|
|
61971
|
+
}
|
|
61972
|
+
}
|
|
61973
|
+
return result;
|
|
61974
|
+
})().finally(() => {
|
|
61757
61975
|
if (inFlight === p)
|
|
61758
61976
|
inFlight = null;
|
|
61759
61977
|
});
|
|
@@ -61761,6 +61979,9 @@ function createOAuth2Refresher(opts) {
|
|
|
61761
61979
|
return p;
|
|
61762
61980
|
};
|
|
61763
61981
|
}
|
|
61982
|
+
function messageOf2(err) {
|
|
61983
|
+
return err instanceof Error ? err.message : String(err);
|
|
61984
|
+
}
|
|
61764
61985
|
|
|
61765
61986
|
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
61766
61987
|
var subtle = globalThis.crypto.subtle;
|
|
@@ -61959,6 +62180,7 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
61959
62180
|
"capture_redirect",
|
|
61960
62181
|
"read_indexed_db",
|
|
61961
62182
|
"read_dom",
|
|
62183
|
+
"read_dom_list",
|
|
61962
62184
|
"download",
|
|
61963
62185
|
"graphql",
|
|
61964
62186
|
"write_cookies"
|
|
@@ -62459,6 +62681,79 @@ function assertDomSelectorsArray(value, label) {
|
|
|
62459
62681
|
}
|
|
62460
62682
|
}
|
|
62461
62683
|
}
|
|
62684
|
+
function assertDomListSelectorsArray(value, label) {
|
|
62685
|
+
if (!Array.isArray(value)) {
|
|
62686
|
+
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
62687
|
+
}
|
|
62688
|
+
const seen = /* @__PURE__ */ new Set();
|
|
62689
|
+
for (let i = 0; i < value.length; i++) {
|
|
62690
|
+
const entry = value[i];
|
|
62691
|
+
assertObject(entry, `${label}[${i}]`);
|
|
62692
|
+
if (entry.name === void 0) {
|
|
62693
|
+
throw new ProtocolError3(`${label}[${i}].name: missing`);
|
|
62694
|
+
}
|
|
62695
|
+
if (entry.itemSelector === void 0) {
|
|
62696
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: missing`);
|
|
62697
|
+
}
|
|
62698
|
+
if (entry.fields === void 0) {
|
|
62699
|
+
throw new ProtocolError3(`${label}[${i}].fields: missing`);
|
|
62700
|
+
}
|
|
62701
|
+
if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
|
|
62702
|
+
throw new ProtocolError3(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
|
|
62703
|
+
}
|
|
62704
|
+
if (typeof entry.itemSelector !== "string" || !DOM_SELECTOR_RE.test(entry.itemSelector)) {
|
|
62705
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: invalid ${JSON.stringify(entry.itemSelector)}`);
|
|
62706
|
+
}
|
|
62707
|
+
if (!Array.isArray(entry.fields) || entry.fields.length === 0) {
|
|
62708
|
+
throw new ProtocolError3(`${label}[${i}].fields: expected non-empty array`);
|
|
62709
|
+
}
|
|
62710
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
62711
|
+
for (let j = 0; j < entry.fields.length; j++) {
|
|
62712
|
+
const field = entry.fields[j];
|
|
62713
|
+
const fieldLabel = `${label}[${i}].fields[${j}]`;
|
|
62714
|
+
assertObject(field, fieldLabel);
|
|
62715
|
+
if (field.name === void 0) {
|
|
62716
|
+
throw new ProtocolError3(`${fieldLabel}.name: missing`);
|
|
62717
|
+
}
|
|
62718
|
+
if (typeof field.name !== "string" || !SCOPE_KEY_RE.test(field.name)) {
|
|
62719
|
+
throw new ProtocolError3(`${fieldLabel}.name: invalid ${JSON.stringify(field.name)}`);
|
|
62720
|
+
}
|
|
62721
|
+
if (field.selector !== void 0) {
|
|
62722
|
+
if (typeof field.selector !== "string" || !DOM_SELECTOR_RE.test(field.selector)) {
|
|
62723
|
+
throw new ProtocolError3(`${fieldLabel}.selector: invalid ${JSON.stringify(field.selector)}`);
|
|
62724
|
+
}
|
|
62725
|
+
}
|
|
62726
|
+
if (field.attribute !== void 0) {
|
|
62727
|
+
if (typeof field.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(field.attribute)) {
|
|
62728
|
+
throw new ProtocolError3(`${fieldLabel}.attribute: invalid ${JSON.stringify(field.attribute)}`);
|
|
62729
|
+
}
|
|
62730
|
+
}
|
|
62731
|
+
if (fieldNames.has(field.name)) {
|
|
62732
|
+
throw new ProtocolError3(`${label}[${i}].fields: duplicate name ${JSON.stringify(field.name)}`);
|
|
62733
|
+
}
|
|
62734
|
+
fieldNames.add(field.name);
|
|
62735
|
+
for (const k of Object.keys(field)) {
|
|
62736
|
+
if (k !== "name" && k !== "selector" && k !== "attribute") {
|
|
62737
|
+
throw new ProtocolError3(`${fieldLabel}: unexpected field ${JSON.stringify(k)}`);
|
|
62738
|
+
}
|
|
62739
|
+
}
|
|
62740
|
+
}
|
|
62741
|
+
if (entry.maxItems !== void 0) {
|
|
62742
|
+
if (typeof entry.maxItems !== "number" || !Number.isInteger(entry.maxItems) || entry.maxItems < 1 || entry.maxItems > 1e3) {
|
|
62743
|
+
throw new ProtocolError3(`${label}[${i}].maxItems: invalid ${JSON.stringify(entry.maxItems)}`);
|
|
62744
|
+
}
|
|
62745
|
+
}
|
|
62746
|
+
if (seen.has(entry.name)) {
|
|
62747
|
+
throw new ProtocolError3(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
|
|
62748
|
+
}
|
|
62749
|
+
seen.add(entry.name);
|
|
62750
|
+
for (const k of Object.keys(entry)) {
|
|
62751
|
+
if (k !== "name" && k !== "itemSelector" && k !== "fields" && k !== "maxItems") {
|
|
62752
|
+
throw new ProtocolError3(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
62753
|
+
}
|
|
62754
|
+
}
|
|
62755
|
+
}
|
|
62756
|
+
}
|
|
62462
62757
|
function assertGraphqlOpsArray(value, label) {
|
|
62463
62758
|
if (!Array.isArray(value)) {
|
|
62464
62759
|
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
@@ -62505,6 +62800,8 @@ function validateFrame(raw) {
|
|
|
62505
62800
|
return validateHelloRejected(raw);
|
|
62506
62801
|
if (t === "extension-disconnected")
|
|
62507
62802
|
return { type: "extension-disconnected" };
|
|
62803
|
+
if (t === "peer-gone")
|
|
62804
|
+
return validatePeerGone(raw);
|
|
62508
62805
|
throw new ProtocolError3(`unknown frame type: ${String(t)}`);
|
|
62509
62806
|
}
|
|
62510
62807
|
function validateHello(raw) {
|
|
@@ -62575,16 +62872,11 @@ function validateHello(raw) {
|
|
|
62575
62872
|
if (raw.domSelectors !== void 0) {
|
|
62576
62873
|
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
62577
62874
|
}
|
|
62578
|
-
if (raw.
|
|
62579
|
-
|
|
62580
|
-
throw new ProtocolError3("hello.accepts: expected array");
|
|
62581
|
-
}
|
|
62582
|
-
for (const a of raw.accepts) {
|
|
62583
|
-
if (typeof a !== "string") {
|
|
62584
|
-
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62585
|
-
}
|
|
62586
|
-
}
|
|
62875
|
+
if (raw.domListSelectors !== void 0) {
|
|
62876
|
+
assertDomListSelectorsArray(raw.domListSelectors, "hello.domListSelectors");
|
|
62587
62877
|
}
|
|
62878
|
+
if (raw.accepts !== void 0)
|
|
62879
|
+
assertAcceptsList(raw.accepts);
|
|
62588
62880
|
if (raw.graphqlOps !== void 0) {
|
|
62589
62881
|
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
62590
62882
|
}
|
|
@@ -62606,6 +62898,8 @@ function validateHello(raw) {
|
|
|
62606
62898
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
62607
62899
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
62608
62900
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
62901
|
+
if (raw.accepts !== void 0)
|
|
62902
|
+
assertAcceptsList(raw.accepts);
|
|
62609
62903
|
return raw;
|
|
62610
62904
|
}
|
|
62611
62905
|
throw new ProtocolError3(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
@@ -62661,6 +62955,27 @@ function validatePairPending(raw) {
|
|
|
62661
62955
|
}
|
|
62662
62956
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
62663
62957
|
}
|
|
62958
|
+
function assertAcceptsList(accepts) {
|
|
62959
|
+
if (!Array.isArray(accepts)) {
|
|
62960
|
+
throw new ProtocolError3("hello.accepts: expected array");
|
|
62961
|
+
}
|
|
62962
|
+
for (const a of accepts) {
|
|
62963
|
+
if (typeof a !== "string") {
|
|
62964
|
+
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62965
|
+
}
|
|
62966
|
+
}
|
|
62967
|
+
}
|
|
62968
|
+
function validatePeerGone(raw) {
|
|
62969
|
+
assertString(raw.mcpId, "peer-gone.mcpId");
|
|
62970
|
+
if (!isValidMcpId(raw.mcpId))
|
|
62971
|
+
throw new ProtocolError3("peer-gone.mcpId: invalid format");
|
|
62972
|
+
for (const k of Object.keys(raw)) {
|
|
62973
|
+
if (k !== "type" && k !== "mcpId") {
|
|
62974
|
+
throw new ProtocolError3(`peer-gone: unexpected field ${JSON.stringify(k)}`);
|
|
62975
|
+
}
|
|
62976
|
+
}
|
|
62977
|
+
return { type: "peer-gone", mcpId: raw.mcpId };
|
|
62978
|
+
}
|
|
62664
62979
|
function validateHelloRejected(raw) {
|
|
62665
62980
|
assertString(raw.mcpId, "hello-rejected.mcpId");
|
|
62666
62981
|
if (!isValidMcpId(raw.mcpId))
|
|
@@ -62910,6 +63225,24 @@ function validateInnerRequest(raw) {
|
|
|
62910
63225
|
}
|
|
62911
63226
|
return raw;
|
|
62912
63227
|
}
|
|
63228
|
+
if (raw.op === "read_dom_list") {
|
|
63229
|
+
assertObject(raw.init, "inner.init");
|
|
63230
|
+
if (raw.init.origin === void 0)
|
|
63231
|
+
throw new ProtocolError3("inner.init.origin: missing");
|
|
63232
|
+
if (raw.init.name === void 0)
|
|
63233
|
+
throw new ProtocolError3("inner.init.name: missing");
|
|
63234
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
63235
|
+
assertString(raw.init.name, "inner.init.name");
|
|
63236
|
+
if (raw.init.name.length === 0) {
|
|
63237
|
+
throw new ProtocolError3("inner.init.name: must be non-empty");
|
|
63238
|
+
}
|
|
63239
|
+
for (const k of Object.keys(raw.init)) {
|
|
63240
|
+
if (k !== "origin" && k !== "name") {
|
|
63241
|
+
throw new ProtocolError3(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom_list`);
|
|
63242
|
+
}
|
|
63243
|
+
}
|
|
63244
|
+
return raw;
|
|
63245
|
+
}
|
|
62913
63246
|
if (raw.op === "graphql_query") {
|
|
62914
63247
|
assertObject(raw.init, "inner.init");
|
|
62915
63248
|
if (raw.init.name === void 0)
|
|
@@ -62957,7 +63290,7 @@ function validateInnerRequest(raw) {
|
|
|
62957
63290
|
}
|
|
62958
63291
|
return raw;
|
|
62959
63292
|
}
|
|
62960
|
-
throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
63293
|
+
throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "read_dom_list", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
62961
63294
|
}
|
|
62962
63295
|
function assertNonEmptyKeyArray(value, label) {
|
|
62963
63296
|
if (!Array.isArray(value)) {
|
|
@@ -63065,6 +63398,18 @@ function validateInnerResponse(raw) {
|
|
|
63065
63398
|
assertStringMap(raw.values, "inner.values");
|
|
63066
63399
|
return raw;
|
|
63067
63400
|
}
|
|
63401
|
+
if (op === "read_dom_list") {
|
|
63402
|
+
if (raw.rows === void 0) {
|
|
63403
|
+
throw new ProtocolError3("inner.rows: missing on read_dom_list response");
|
|
63404
|
+
}
|
|
63405
|
+
if (!Array.isArray(raw.rows)) {
|
|
63406
|
+
throw new ProtocolError3("inner.rows: expected array, got " + typeof raw.rows);
|
|
63407
|
+
}
|
|
63408
|
+
for (const [i, row] of raw.rows.entries()) {
|
|
63409
|
+
assertStringMap(row, `inner.rows[${i}]`);
|
|
63410
|
+
}
|
|
63411
|
+
return raw;
|
|
63412
|
+
}
|
|
63068
63413
|
if (op === "graphql_query") {
|
|
63069
63414
|
if (raw.data === void 0) {
|
|
63070
63415
|
throw new ProtocolError3("inner.data: missing on graphql_query response");
|
|
@@ -63166,12 +63511,6 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
|
|
|
63166
63511
|
ciphertext: toB64(ct)
|
|
63167
63512
|
};
|
|
63168
63513
|
}
|
|
63169
|
-
async function openEncryptedFrame(sessionKey, frame, direction) {
|
|
63170
|
-
const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
|
|
63171
|
-
if (result.stage === "ok")
|
|
63172
|
-
return result.inner;
|
|
63173
|
-
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
63174
|
-
}
|
|
63175
63514
|
async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
63176
63515
|
let pt;
|
|
63177
63516
|
try {
|
|
@@ -63202,7 +63541,7 @@ var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
|
63202
63541
|
async function electRole(opts) {
|
|
63203
63542
|
const server = createServer();
|
|
63204
63543
|
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
63205
|
-
return new Promise((
|
|
63544
|
+
return new Promise((resolve5, reject) => {
|
|
63206
63545
|
let timer;
|
|
63207
63546
|
const clearBindTimer = () => {
|
|
63208
63547
|
if (timer) {
|
|
@@ -63218,7 +63557,7 @@ async function electRole(opts) {
|
|
|
63218
63557
|
server.close();
|
|
63219
63558
|
} catch {
|
|
63220
63559
|
}
|
|
63221
|
-
|
|
63560
|
+
resolve5({ role: "peer" });
|
|
63222
63561
|
} else {
|
|
63223
63562
|
reject(e);
|
|
63224
63563
|
}
|
|
@@ -63226,7 +63565,7 @@ async function electRole(opts) {
|
|
|
63226
63565
|
const onListening = () => {
|
|
63227
63566
|
clearBindTimer();
|
|
63228
63567
|
server.removeListener("error", onError);
|
|
63229
|
-
|
|
63568
|
+
resolve5({ role: "host", server });
|
|
63230
63569
|
};
|
|
63231
63570
|
server.once("error", onError);
|
|
63232
63571
|
server.once("listening", onListening);
|
|
@@ -63323,6 +63662,18 @@ async function buildServerHello(opts) {
|
|
|
63323
63662
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
63324
63663
|
}));
|
|
63325
63664
|
}
|
|
63665
|
+
if (opts.domListSelectors && opts.domListSelectors.length > 0) {
|
|
63666
|
+
hello.domListSelectors = opts.domListSelectors.map((d) => ({
|
|
63667
|
+
name: d.name,
|
|
63668
|
+
itemSelector: d.itemSelector,
|
|
63669
|
+
fields: d.fields.map((f) => ({
|
|
63670
|
+
name: f.name,
|
|
63671
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
63672
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
63673
|
+
})),
|
|
63674
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
63675
|
+
}));
|
|
63676
|
+
}
|
|
63326
63677
|
if (opts.graphqlOps && opts.graphqlOps.length > 0) {
|
|
63327
63678
|
hello.graphqlOps = opts.graphqlOps.map((d) => ({
|
|
63328
63679
|
name: d.name,
|
|
@@ -63491,14 +63842,15 @@ async function awaitSessionReady(ready, opts) {
|
|
|
63491
63842
|
}
|
|
63492
63843
|
|
|
63493
63844
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
63494
|
-
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
63495
|
-
import {
|
|
63845
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2, rm as rm2 } from "node:fs/promises";
|
|
63846
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
63847
|
+
import { isAbsolute as isAbsolute4, join as join3 } from "node:path";
|
|
63496
63848
|
|
|
63497
63849
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
63498
|
-
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
63850
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink, link, lstat } from "node:fs/promises";
|
|
63499
63851
|
import { randomBytes } from "node:crypto";
|
|
63500
|
-
import { isAbsolute, join } from "node:path";
|
|
63501
|
-
import { homedir } from "node:os";
|
|
63852
|
+
import { isAbsolute as isAbsolute3, join as join2 } from "node:path";
|
|
63853
|
+
import { homedir as homedir2 } from "node:os";
|
|
63502
63854
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
63503
63855
|
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
63504
63856
|
function envIdentityDir() {
|
|
@@ -63506,12 +63858,12 @@ function envIdentityDir() {
|
|
|
63506
63858
|
if (raw === void 0)
|
|
63507
63859
|
return void 0;
|
|
63508
63860
|
const dir = raw.trim();
|
|
63509
|
-
if (dir === "" || !
|
|
63861
|
+
if (dir === "" || !isAbsolute3(dir))
|
|
63510
63862
|
return void 0;
|
|
63511
63863
|
return dir;
|
|
63512
63864
|
}
|
|
63513
63865
|
function defaultIdentityDir() {
|
|
63514
|
-
return envIdentityDir() ??
|
|
63866
|
+
return envIdentityDir() ?? join2(homedir2(), ".fetchproxy", "identity");
|
|
63515
63867
|
}
|
|
63516
63868
|
function safeIdentityFileBase(serverName) {
|
|
63517
63869
|
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
@@ -63520,7 +63872,7 @@ function safeIdentityFileBase(serverName) {
|
|
|
63520
63872
|
return serverName.replace(/\//g, "_");
|
|
63521
63873
|
}
|
|
63522
63874
|
function identityFilePath(dir, serverName) {
|
|
63523
|
-
return
|
|
63875
|
+
return join2(dir, `${safeIdentityFileBase(serverName)}.json`);
|
|
63524
63876
|
}
|
|
63525
63877
|
async function generateIdentity() {
|
|
63526
63878
|
const x = await generateX25519();
|
|
@@ -63563,7 +63915,7 @@ async function openIdentityDir(dir) {
|
|
|
63563
63915
|
}
|
|
63564
63916
|
}
|
|
63565
63917
|
var STAGING_BYTES = 6;
|
|
63566
|
-
async function sweepStagingFiles(dir, serverName) {
|
|
63918
|
+
async function sweepStagingFiles(dir, serverName, opts = {}) {
|
|
63567
63919
|
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
63568
63920
|
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
63569
63921
|
let names;
|
|
@@ -63572,35 +63924,80 @@ async function sweepStagingFiles(dir, serverName) {
|
|
|
63572
63924
|
} catch {
|
|
63573
63925
|
return;
|
|
63574
63926
|
}
|
|
63575
|
-
|
|
63927
|
+
const cutoff = opts.olderThanMs === void 0 ? null : Date.now() - opts.olderThanMs;
|
|
63928
|
+
await Promise.all(names.filter((name) => staging.test(name)).map(async (name) => {
|
|
63929
|
+
const path = join2(dir, name);
|
|
63930
|
+
if (cutoff !== null) {
|
|
63931
|
+
const st = await lstat(path).catch(() => null);
|
|
63932
|
+
if (st === null || st.mtimeMs > cutoff)
|
|
63933
|
+
return;
|
|
63934
|
+
}
|
|
63935
|
+
await unlink(path).catch(() => void 0);
|
|
63936
|
+
}));
|
|
63576
63937
|
}
|
|
63577
|
-
async function
|
|
63938
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
63578
63939
|
const path = identityFilePath(dir, serverName);
|
|
63579
63940
|
await openIdentityDir(dir);
|
|
63580
|
-
await sweepStagingFiles(dir, serverName);
|
|
63581
|
-
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63582
63941
|
try {
|
|
63583
|
-
|
|
63584
|
-
await chmod(tmp, 384);
|
|
63585
|
-
await rename(tmp, path);
|
|
63942
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63586
63943
|
} catch (e) {
|
|
63587
|
-
|
|
63588
|
-
|
|
63944
|
+
if (e.code !== "ENOENT" && !(e instanceof SyntaxError))
|
|
63945
|
+
throw e;
|
|
63946
|
+
}
|
|
63947
|
+
let unreadable = null;
|
|
63948
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
63949
|
+
const id = await generateIdentity();
|
|
63950
|
+
if (await publishIdentityIfAbsent(dir, serverName, id))
|
|
63951
|
+
return id;
|
|
63952
|
+
try {
|
|
63953
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63954
|
+
} catch (e) {
|
|
63955
|
+
if (e.code === "ENOENT") {
|
|
63956
|
+
continue;
|
|
63957
|
+
}
|
|
63958
|
+
if (!(e instanceof SyntaxError))
|
|
63959
|
+
throw e;
|
|
63960
|
+
unreadable = e;
|
|
63961
|
+
await new Promise((r) => setTimeout(r, 20 * (attempt + 1)));
|
|
63962
|
+
}
|
|
63589
63963
|
}
|
|
63590
|
-
|
|
63964
|
+
if (unreadable !== null)
|
|
63965
|
+
throw unreadable;
|
|
63966
|
+
throw new Error(`fetchproxy: could not create an identity for ${JSON.stringify(serverName)} in ${dir}`);
|
|
63591
63967
|
}
|
|
63592
|
-
|
|
63968
|
+
var NO_HARD_LINKS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
|
|
63969
|
+
async function publishIdentityIfAbsent(dir, serverName, id) {
|
|
63593
63970
|
const path = identityFilePath(dir, serverName);
|
|
63594
|
-
await
|
|
63971
|
+
await sweepStagingFiles(dir, serverName, { olderThanMs: 6e4 });
|
|
63972
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63595
63973
|
try {
|
|
63596
|
-
|
|
63974
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63975
|
+
await chmod(tmp, 384);
|
|
63976
|
+
try {
|
|
63977
|
+
await link(tmp, path);
|
|
63978
|
+
} catch (e) {
|
|
63979
|
+
const code = e.code;
|
|
63980
|
+
if (code === void 0 || !NO_HARD_LINKS.has(code))
|
|
63981
|
+
throw e;
|
|
63982
|
+
try {
|
|
63983
|
+
await writeFile(path, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63984
|
+
} catch (writeErr) {
|
|
63985
|
+
if (writeErr.code !== "EEXIST") {
|
|
63986
|
+
await rm(path, { force: true }).catch(() => void 0);
|
|
63987
|
+
}
|
|
63988
|
+
throw writeErr;
|
|
63989
|
+
}
|
|
63990
|
+
await chmod(path, 384);
|
|
63991
|
+
}
|
|
63992
|
+
return true;
|
|
63597
63993
|
} catch (e) {
|
|
63598
|
-
|
|
63599
|
-
|
|
63994
|
+
const code = e.code;
|
|
63995
|
+
if (code === "EEXIST" || code === "ENOENT")
|
|
63996
|
+
return false;
|
|
63997
|
+
throw e;
|
|
63998
|
+
} finally {
|
|
63999
|
+
await rm(tmp, { force: true });
|
|
63600
64000
|
}
|
|
63601
|
-
const id = await generateIdentity();
|
|
63602
|
-
await writeIdentityFile(dir, serverName, id);
|
|
63603
|
-
return id;
|
|
63604
64001
|
}
|
|
63605
64002
|
|
|
63606
64003
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
@@ -63610,7 +64007,7 @@ function envTrustDir(env = process.env) {
|
|
|
63610
64007
|
if (raw === void 0)
|
|
63611
64008
|
return void 0;
|
|
63612
64009
|
const dir = raw.trim();
|
|
63613
|
-
if (dir !== "" &&
|
|
64010
|
+
if (dir !== "" && isAbsolute4(dir))
|
|
63614
64011
|
return dir;
|
|
63615
64012
|
console.warn(`[fetchproxy] ignoring ${TRUST_DIR_ENV}=${JSON.stringify(raw)}: it is not an absolute path. The extension pin will be written beside the identity instead, which is not where you pointed it \u2014 if that directory is read-only, no pin is ever kept.`);
|
|
63616
64013
|
return void 0;
|
|
@@ -63658,13 +64055,13 @@ function decideExtensionTrust(args) {
|
|
|
63658
64055
|
};
|
|
63659
64056
|
}
|
|
63660
64057
|
function extensionTrustPath(serverName, dir = defaultTrustDir()) {
|
|
63661
|
-
return
|
|
64058
|
+
return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
63662
64059
|
}
|
|
63663
64060
|
function extensionTrustPathHint(serverName) {
|
|
63664
64061
|
try {
|
|
63665
64062
|
return extensionTrustPath(serverName);
|
|
63666
64063
|
} catch {
|
|
63667
|
-
return
|
|
64064
|
+
return join3(defaultTrustDir(), "<server-name>.extension-trust.json");
|
|
63668
64065
|
}
|
|
63669
64066
|
}
|
|
63670
64067
|
function isPin(x) {
|
|
@@ -63701,10 +64098,15 @@ async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
|
63701
64098
|
async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
63702
64099
|
const path = extensionTrustPath(serverName, dir);
|
|
63703
64100
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
63704
|
-
const tmp = `${path}.tmp`;
|
|
63705
|
-
|
|
63706
|
-
|
|
63707
|
-
|
|
64101
|
+
const tmp = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
64102
|
+
try {
|
|
64103
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384, flag: "wx" });
|
|
64104
|
+
await chmod2(tmp, 384);
|
|
64105
|
+
await rename2(tmp, path);
|
|
64106
|
+
} catch (e) {
|
|
64107
|
+
await rm2(tmp, { force: true });
|
|
64108
|
+
throw e;
|
|
64109
|
+
}
|
|
63708
64110
|
}
|
|
63709
64111
|
|
|
63710
64112
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
@@ -63741,6 +64143,7 @@ function originVerdict(origin, allowLocal) {
|
|
|
63741
64143
|
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
63742
64144
|
var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
|
|
63743
64145
|
var enc4 = new TextEncoder();
|
|
64146
|
+
var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
|
|
63744
64147
|
async function startHost(opts) {
|
|
63745
64148
|
const allowLocalOrigins = envAllowsLocalOrigins();
|
|
63746
64149
|
if (allowLocalOrigins) {
|
|
@@ -63784,6 +64187,7 @@ async function startHost(opts) {
|
|
|
63784
64187
|
localStoragePointers: opts.ownLocalStoragePointers,
|
|
63785
64188
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
63786
64189
|
domSelectors: opts.ownDomSelectors,
|
|
64190
|
+
domListSelectors: opts.ownDomListSelectors,
|
|
63787
64191
|
graphqlOps: opts.ownGraphqlOps
|
|
63788
64192
|
};
|
|
63789
64193
|
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
@@ -63810,8 +64214,8 @@ async function startHost(opts) {
|
|
|
63810
64214
|
let rejectOwnSession;
|
|
63811
64215
|
let ownSessionReady;
|
|
63812
64216
|
function resetSessionPromise() {
|
|
63813
|
-
ownSessionReady = new Promise((
|
|
63814
|
-
resolveOwnSession =
|
|
64217
|
+
ownSessionReady = new Promise((resolve5, reject) => {
|
|
64218
|
+
resolveOwnSession = resolve5;
|
|
63815
64219
|
rejectOwnSession = reject;
|
|
63816
64220
|
});
|
|
63817
64221
|
ownSessionReady.catch(() => {
|
|
@@ -63854,6 +64258,31 @@ async function startHost(opts) {
|
|
|
63854
64258
|
return null;
|
|
63855
64259
|
}
|
|
63856
64260
|
};
|
|
64261
|
+
let ownDecryptFailures = 0;
|
|
64262
|
+
const rehandshakeOwnSession = async (ws) => {
|
|
64263
|
+
const hello = extensionHello;
|
|
64264
|
+
if (extensionWs !== ws || !hello)
|
|
64265
|
+
return;
|
|
64266
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: ${OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE} frames in a row from the extension failed authentication under this MCP's session key \u2014 re-handshaking this MCP's session (the extension stays connected).`);
|
|
64267
|
+
ownSession = dropOwnSessionAndEphemeral();
|
|
64268
|
+
resetSessionPromise();
|
|
64269
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
64270
|
+
const mintedHello = await buildServerHello({
|
|
64271
|
+
...ownHelloBase,
|
|
64272
|
+
sessionPub: mintedKeypair.publicKey,
|
|
64273
|
+
answersExtNonce: fromB64(hello.sessionNonce)
|
|
64274
|
+
});
|
|
64275
|
+
if (extensionWs !== ws || extensionHello !== hello || ownSession !== null) {
|
|
64276
|
+
mintedKeypair.privateKey.fill(0);
|
|
64277
|
+
return;
|
|
64278
|
+
}
|
|
64279
|
+
installOwnEphemeral({
|
|
64280
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
64281
|
+
pub: mintedKeypair.publicKey,
|
|
64282
|
+
priv: mintedKeypair.privateKey
|
|
64283
|
+
});
|
|
64284
|
+
ws.send(JSON.stringify(mintedHello));
|
|
64285
|
+
};
|
|
63857
64286
|
wss.on("connection", (ws) => {
|
|
63858
64287
|
let identified = null;
|
|
63859
64288
|
let peerMcpId = null;
|
|
@@ -64044,6 +64473,7 @@ async function startHost(opts) {
|
|
|
64044
64473
|
if (extensionWs !== ws)
|
|
64045
64474
|
return;
|
|
64046
64475
|
ownSession = new SessionState(key);
|
|
64476
|
+
ownDecryptFailures = 0;
|
|
64047
64477
|
ownPendingPairCode = null;
|
|
64048
64478
|
resolveOwnSession(ownSession);
|
|
64049
64479
|
} else {
|
|
@@ -64065,15 +64495,42 @@ async function startHost(opts) {
|
|
|
64065
64495
|
}
|
|
64066
64496
|
if (claim2 !== "ok")
|
|
64067
64497
|
return;
|
|
64068
|
-
let
|
|
64498
|
+
let result;
|
|
64069
64499
|
try {
|
|
64070
|
-
|
|
64500
|
+
result = await openEncryptedFrameDetailed(session.sessionKey, frame, "e2s");
|
|
64071
64501
|
} catch (e) {
|
|
64072
64502
|
session.releaseInboundSeq(frame.seq);
|
|
64073
64503
|
throw e;
|
|
64074
64504
|
}
|
|
64505
|
+
if (result.stage === "decrypt-failed") {
|
|
64506
|
+
session.releaseInboundSeq(frame.seq);
|
|
64507
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) that failed authentication \u2014 most likely a straggler from a previous session.`);
|
|
64508
|
+
if (session === ownSession) {
|
|
64509
|
+
ownDecryptFailures += 1;
|
|
64510
|
+
if (ownDecryptFailures >= OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE) {
|
|
64511
|
+
ownDecryptFailures = 0;
|
|
64512
|
+
void rehandshakeOwnSession(ws).catch((e) => console.error(`[fetchproxy] ${opts.ownServerName}: re-handshake failed:`, e));
|
|
64513
|
+
}
|
|
64514
|
+
}
|
|
64515
|
+
return;
|
|
64516
|
+
}
|
|
64517
|
+
if (session === ownSession)
|
|
64518
|
+
ownDecryptFailures = 0;
|
|
64075
64519
|
session.commitInboundSeq(frame.seq);
|
|
64076
|
-
|
|
64520
|
+
if (result.stage === "ok") {
|
|
64521
|
+
ownInnerListeners.forEach((cb) => cb(result.inner));
|
|
64522
|
+
} else {
|
|
64523
|
+
console.error("[fetchproxy] host: received a frame that decrypted OK but failed validation:", result.error);
|
|
64524
|
+
const recoveredId = result.recoveredId;
|
|
64525
|
+
if (recoveredId !== void 0) {
|
|
64526
|
+
ownInnerListeners.forEach((cb) => cb({
|
|
64527
|
+
type: "response",
|
|
64528
|
+
id: recoveredId,
|
|
64529
|
+
ok: false,
|
|
64530
|
+
error: `malformed response failed protocol validation: ${String(result.error)}`
|
|
64531
|
+
}));
|
|
64532
|
+
}
|
|
64533
|
+
}
|
|
64077
64534
|
} else {
|
|
64078
64535
|
const slot = peers.get(frame.mcpId);
|
|
64079
64536
|
if (slot)
|
|
@@ -64132,6 +64589,7 @@ async function startHost(opts) {
|
|
|
64132
64589
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
64133
64590
|
}
|
|
64134
64591
|
ownSession = dropOwnSessionAndEphemeral();
|
|
64592
|
+
ownDecryptFailures = 0;
|
|
64135
64593
|
ownPendingPairCode = null;
|
|
64136
64594
|
resetSessionPromise();
|
|
64137
64595
|
disconnectListeners.forEach((cb) => cb());
|
|
@@ -64146,13 +64604,20 @@ async function startHost(opts) {
|
|
|
64146
64604
|
}
|
|
64147
64605
|
}
|
|
64148
64606
|
if (identified === "peer" && peerMcpId) {
|
|
64149
|
-
if (peers.get(peerMcpId)?.ws === ws)
|
|
64607
|
+
if (peers.get(peerMcpId)?.ws === ws) {
|
|
64150
64608
|
peers.delete(peerMcpId);
|
|
64609
|
+
if (extensionWs && extensionHello?.accepts?.includes("peer-gone")) {
|
|
64610
|
+
try {
|
|
64611
|
+
extensionWs.send(JSON.stringify({ type: "peer-gone", mcpId: peerMcpId }));
|
|
64612
|
+
} catch {
|
|
64613
|
+
}
|
|
64614
|
+
}
|
|
64615
|
+
}
|
|
64151
64616
|
}
|
|
64152
64617
|
});
|
|
64153
64618
|
});
|
|
64154
64619
|
return {
|
|
64155
|
-
close: () => new Promise((
|
|
64620
|
+
close: () => new Promise((resolve5) => {
|
|
64156
64621
|
for (const client of wss.clients) {
|
|
64157
64622
|
try {
|
|
64158
64623
|
client.terminate();
|
|
@@ -64160,7 +64625,7 @@ async function startHost(opts) {
|
|
|
64160
64625
|
}
|
|
64161
64626
|
}
|
|
64162
64627
|
wss.close(() => {
|
|
64163
|
-
opts.httpServer.close(() =>
|
|
64628
|
+
opts.httpServer.close(() => resolve5());
|
|
64164
64629
|
});
|
|
64165
64630
|
}),
|
|
64166
64631
|
sendOwnInner: async (inner) => {
|
|
@@ -64190,26 +64655,42 @@ async function startHost(opts) {
|
|
|
64190
64655
|
}
|
|
64191
64656
|
|
|
64192
64657
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
64658
|
+
var PEER_DIAL_TIMEOUT_MS = 5e3;
|
|
64193
64659
|
var enc5 = new TextEncoder();
|
|
64660
|
+
function notSentError() {
|
|
64661
|
+
return new Error("fetchproxy: request not sent \u2014 the connection to the bridge host closed before it went out, so nothing reached the browser. It is safe to retry.");
|
|
64662
|
+
}
|
|
64194
64663
|
async function startPeer(opts) {
|
|
64195
64664
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
64196
64665
|
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
64197
64666
|
});
|
|
64198
|
-
|
|
64667
|
+
const dialTimeoutMs = opts.dialTimeoutMs ?? PEER_DIAL_TIMEOUT_MS;
|
|
64668
|
+
await new Promise((resolve5, reject) => {
|
|
64669
|
+
let timer;
|
|
64199
64670
|
const onOpen = () => {
|
|
64200
64671
|
cleanup();
|
|
64201
|
-
|
|
64672
|
+
resolve5();
|
|
64202
64673
|
};
|
|
64203
64674
|
const onError = (e) => {
|
|
64204
64675
|
cleanup();
|
|
64205
64676
|
reject(e);
|
|
64206
64677
|
};
|
|
64207
64678
|
const cleanup = () => {
|
|
64679
|
+
if (timer !== void 0)
|
|
64680
|
+
clearTimeout(timer);
|
|
64208
64681
|
ws.off("open", onOpen);
|
|
64209
64682
|
ws.off("error", onError);
|
|
64210
64683
|
};
|
|
64211
64684
|
ws.once("open", onOpen);
|
|
64212
64685
|
ws.once("error", onError);
|
|
64686
|
+
if (dialTimeoutMs > 0) {
|
|
64687
|
+
timer = setTimeout(() => {
|
|
64688
|
+
cleanup();
|
|
64689
|
+
ws.on("error", () => void 0);
|
|
64690
|
+
ws.terminate();
|
|
64691
|
+
reject(new Error(`fetchproxy: the process holding ${opts.host}:${opts.port} did not answer the WebSocket upgrade within ${dialTimeoutMs}ms \u2014 it may be wedged or not a fetchproxy host. Restart the MCP that owns the port (or free it) and retry.`));
|
|
64692
|
+
}, dialTimeoutMs);
|
|
64693
|
+
}
|
|
64213
64694
|
});
|
|
64214
64695
|
ws.on("error", (e) => {
|
|
64215
64696
|
console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
|
|
@@ -64228,6 +64709,7 @@ async function startPeer(opts) {
|
|
|
64228
64709
|
captureHeaders: opts.captureHeaders,
|
|
64229
64710
|
indexedDbScopes: opts.indexedDbScopes,
|
|
64230
64711
|
domSelectors: opts.domSelectors,
|
|
64712
|
+
domListSelectors: opts.domListSelectors,
|
|
64231
64713
|
localStoragePointers: opts.localStoragePointers,
|
|
64232
64714
|
sessionStoragePointers: opts.sessionStoragePointers,
|
|
64233
64715
|
graphqlOps: opts.graphqlOps,
|
|
@@ -64251,6 +64733,7 @@ async function startPeer(opts) {
|
|
|
64251
64733
|
const renegotiateListeners = [];
|
|
64252
64734
|
const pendingPairListeners = [];
|
|
64253
64735
|
const closeListeners = [];
|
|
64736
|
+
const extensionDisconnectListeners = [];
|
|
64254
64737
|
let session = null;
|
|
64255
64738
|
let sessionEphemeral = null;
|
|
64256
64739
|
function dropSessionEphemeral() {
|
|
@@ -64270,10 +64753,24 @@ async function startPeer(opts) {
|
|
|
64270
64753
|
let warnedUnverifiablePairCode = false;
|
|
64271
64754
|
let resolveFirstReady;
|
|
64272
64755
|
let rejectFirstReady;
|
|
64273
|
-
|
|
64274
|
-
|
|
64275
|
-
|
|
64276
|
-
|
|
64756
|
+
let sessionPromise;
|
|
64757
|
+
let sessionPromiseSettled = false;
|
|
64758
|
+
function resetSessionPromise() {
|
|
64759
|
+
sessionPromiseSettled = false;
|
|
64760
|
+
sessionPromise = new Promise((resolve5, reject) => {
|
|
64761
|
+
resolveFirstReady = (st) => {
|
|
64762
|
+
sessionPromiseSettled = true;
|
|
64763
|
+
resolve5(st);
|
|
64764
|
+
};
|
|
64765
|
+
rejectFirstReady = (e) => {
|
|
64766
|
+
sessionPromiseSettled = true;
|
|
64767
|
+
reject(e);
|
|
64768
|
+
};
|
|
64769
|
+
});
|
|
64770
|
+
sessionPromise.catch(() => {
|
|
64771
|
+
});
|
|
64772
|
+
}
|
|
64773
|
+
resetSessionPromise();
|
|
64277
64774
|
let extensionHello = null;
|
|
64278
64775
|
let extensionGone = false;
|
|
64279
64776
|
let cachedPin = void 0;
|
|
@@ -64370,6 +64867,12 @@ async function startPeer(opts) {
|
|
|
64370
64867
|
extensionGone = true;
|
|
64371
64868
|
dropSessionEphemeral();
|
|
64372
64869
|
pendingPairCode = null;
|
|
64870
|
+
if (session !== null) {
|
|
64871
|
+
session = null;
|
|
64872
|
+
if (sessionPromiseSettled)
|
|
64873
|
+
resetSessionPromise();
|
|
64874
|
+
}
|
|
64875
|
+
extensionDisconnectListeners.forEach((cb) => cb());
|
|
64373
64876
|
return;
|
|
64374
64877
|
}
|
|
64375
64878
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
@@ -64486,19 +64989,30 @@ async function startPeer(opts) {
|
|
|
64486
64989
|
}
|
|
64487
64990
|
closeListeners.forEach((cb) => cb());
|
|
64488
64991
|
});
|
|
64489
|
-
sessionPromise.catch(() => {
|
|
64490
|
-
});
|
|
64491
64992
|
const handle = {
|
|
64492
64993
|
ws,
|
|
64493
|
-
|
|
64994
|
+
// A getter: `extension-disconnected` replaces the promise (B-BUG-5).
|
|
64995
|
+
get session() {
|
|
64996
|
+
return sessionPromise;
|
|
64997
|
+
},
|
|
64494
64998
|
sendInner: async (inner) => {
|
|
64495
|
-
|
|
64496
|
-
|
|
64497
|
-
|
|
64498
|
-
|
|
64999
|
+
try {
|
|
65000
|
+
await awaitSessionReady(sessionPromise, {
|
|
65001
|
+
mcpId: opts.mcpId,
|
|
65002
|
+
pendingPairCode: () => pendingPairCode
|
|
65003
|
+
});
|
|
65004
|
+
} catch (e) {
|
|
65005
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
65006
|
+
throw notSentError();
|
|
65007
|
+
throw e;
|
|
65008
|
+
}
|
|
64499
65009
|
const s = session;
|
|
65010
|
+
if (s === null)
|
|
65011
|
+
throw new Error("peer: extension disconnected");
|
|
64500
65012
|
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
64501
65013
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
65014
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
65015
|
+
throw notSentError();
|
|
64502
65016
|
ws.send(JSON.stringify(sealed));
|
|
64503
65017
|
},
|
|
64504
65018
|
onInner: (cb) => {
|
|
@@ -64516,6 +65030,9 @@ async function startPeer(opts) {
|
|
|
64516
65030
|
onClose: (cb) => {
|
|
64517
65031
|
closeListeners.push(cb);
|
|
64518
65032
|
},
|
|
65033
|
+
onExtensionDisconnect: (cb) => {
|
|
65034
|
+
extensionDisconnectListeners.push(cb);
|
|
65035
|
+
},
|
|
64519
65036
|
close: () => ws.close()
|
|
64520
65037
|
};
|
|
64521
65038
|
return handle;
|
|
@@ -64523,15 +65040,15 @@ async function startPeer(opts) {
|
|
|
64523
65040
|
|
|
64524
65041
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
64525
65042
|
function classifyFetchError(error62) {
|
|
65043
|
+
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
65044
|
+
return "tab_fetch_failed";
|
|
65045
|
+
}
|
|
64526
65046
|
if (/Could not establish connection/i.test(error62) || /Receiving end does not exist/i.test(error62)) {
|
|
64527
65047
|
return "content_script_unreachable";
|
|
64528
65048
|
}
|
|
64529
65049
|
if (/^tab fetch failed:/.test(error62)) {
|
|
64530
65050
|
return "tab_fetch_failed";
|
|
64531
65051
|
}
|
|
64532
|
-
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
64533
|
-
return "tab_fetch_failed";
|
|
64534
|
-
}
|
|
64535
65052
|
if (/^no tab matching /.test(error62)) {
|
|
64536
65053
|
return "no_tab";
|
|
64537
65054
|
}
|
|
@@ -64723,6 +65240,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64723
65240
|
* the message says which number that was and where it came from.
|
|
64724
65241
|
*/
|
|
64725
65242
|
requestedTimeoutMs;
|
|
65243
|
+
/**
|
|
65244
|
+
* B-BUG-1: whether re-sending this request is safe. A timeout only means
|
|
65245
|
+
* the reply did not arrive in time — the extension may already have run the
|
|
65246
|
+
* request in the tab — so this is `false` for a non-idempotent `fetch`
|
|
65247
|
+
* (anything but GET/HEAD/OPTIONS) the caller did not mark `retryOnTimeout`,
|
|
65248
|
+
* for `download` and `writeCookies`, and for `graphqlQuery` unless the
|
|
65249
|
+
* caller marked it `retryOnTimeout` (a declared operation may be a
|
|
65250
|
+
* mutation). `retryOnceOnTimeout` honours it. Defaults to `true` (the read
|
|
65251
|
+
* verbs).
|
|
65252
|
+
*/
|
|
65253
|
+
retrySafe;
|
|
64726
65254
|
constructor(args) {
|
|
64727
65255
|
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
64728
65256
|
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
@@ -64735,6 +65263,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64735
65263
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
64736
65264
|
this.retryAttempted = args.retryAttempted ?? false;
|
|
64737
65265
|
this.requestedTimeoutMs = args.requestedTimeoutMs ?? null;
|
|
65266
|
+
this.retrySafe = args.retrySafe ?? true;
|
|
64738
65267
|
}
|
|
64739
65268
|
};
|
|
64740
65269
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
@@ -64774,6 +65303,32 @@ function assertUrlInDomains(field, url4, domains) {
|
|
|
64774
65303
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
64775
65304
|
}
|
|
64776
65305
|
var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
|
|
65306
|
+
function parseJsonBody(response) {
|
|
65307
|
+
if (response.status === 204 || response.body.trim() === "")
|
|
65308
|
+
return null;
|
|
65309
|
+
return JSON.parse(response.body);
|
|
65310
|
+
}
|
|
65311
|
+
var TIMEOUT_RETRY_SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
65312
|
+
function isRetrySafeOnTimeout(method, retryOnTimeout) {
|
|
65313
|
+
if (retryOnTimeout !== void 0)
|
|
65314
|
+
return retryOnTimeout;
|
|
65315
|
+
return TIMEOUT_RETRY_SAFE_METHODS.has(method.toUpperCase());
|
|
65316
|
+
}
|
|
65317
|
+
function isResendableRequest(inner) {
|
|
65318
|
+
switch (inner.op) {
|
|
65319
|
+
case "fetch":
|
|
65320
|
+
return isRetrySafeOnTimeout(inner.init.method);
|
|
65321
|
+
case "write_cookies":
|
|
65322
|
+
case "download":
|
|
65323
|
+
// A declared GraphQL operation may be a mutation; the server holds only
|
|
65324
|
+
// its name, so it cannot tell. `graphqlQuery({ retryOnTimeout: true })`
|
|
65325
|
+
// passes `resendable` explicitly for a known query.
|
|
65326
|
+
case "graphql_query":
|
|
65327
|
+
return false;
|
|
65328
|
+
default:
|
|
65329
|
+
return true;
|
|
65330
|
+
}
|
|
65331
|
+
}
|
|
64777
65332
|
var FetchproxyServer = class {
|
|
64778
65333
|
/**
|
|
64779
65334
|
* Bridge role. `null` until the first verb call (or an explicit
|
|
@@ -64822,12 +65377,22 @@ var FetchproxyServer = class {
|
|
|
64822
65377
|
pendingRedirect = /* @__PURE__ */ new Map();
|
|
64823
65378
|
// 0.4.0+: read_indexed_db awaiters resolve a JSON-typed values map.
|
|
64824
65379
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
65380
|
+
// 3.1.0+: read_dom_list awaiters resolve an array of field-value rows —
|
|
65381
|
+
// a different shape from `pendingStorage`'s single map, so its own map.
|
|
65382
|
+
pendingDomList = /* @__PURE__ */ new Map();
|
|
64825
65383
|
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
64826
65384
|
pendingDownload = /* @__PURE__ */ new Map();
|
|
64827
65385
|
// 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
|
|
64828
65386
|
// shape is operation-specific, so the awaiter resolves `unknown` and the
|
|
64829
65387
|
// caller narrows.
|
|
64830
65388
|
pendingGraphql = /* @__PURE__ */ new Map();
|
|
65389
|
+
/**
|
|
65390
|
+
* B-BUG-6: the frames of in-flight requests that are safe to send again
|
|
65391
|
+
* if the host that relayed them exits before replying (see
|
|
65392
|
+
* `recoverFromHostLoss`). Keyed by request id; an entry outlives its
|
|
65393
|
+
* request only until the next prune or response.
|
|
65394
|
+
*/
|
|
65395
|
+
resendable = /* @__PURE__ */ new Map();
|
|
64831
65396
|
mcpId = null;
|
|
64832
65397
|
identity = null;
|
|
64833
65398
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -64873,7 +65438,7 @@ var FetchproxyServer = class {
|
|
|
64873
65438
|
}
|
|
64874
65439
|
for (const c of opts.capabilities) {
|
|
64875
65440
|
if (!KNOWN_CAPABILITIES.has(c)) {
|
|
64876
|
-
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: [
|
|
65441
|
+
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: ${JSON.stringify([...KNOWN_CAPABILITIES])}`);
|
|
64877
65442
|
}
|
|
64878
65443
|
}
|
|
64879
65444
|
capabilities = [...opts.capabilities];
|
|
@@ -64920,6 +65485,16 @@ var FetchproxyServer = class {
|
|
|
64920
65485
|
selector: d.selector,
|
|
64921
65486
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
64922
65487
|
})),
|
|
65488
|
+
domListSelectors: (opts.domListSelectors ?? []).map((d) => ({
|
|
65489
|
+
name: d.name,
|
|
65490
|
+
itemSelector: d.itemSelector,
|
|
65491
|
+
fields: d.fields.map((f) => ({
|
|
65492
|
+
name: f.name,
|
|
65493
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
65494
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
65495
|
+
})),
|
|
65496
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
65497
|
+
})),
|
|
64923
65498
|
graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
|
|
64924
65499
|
name: d.name,
|
|
64925
65500
|
operationName: d.operationName
|
|
@@ -65049,6 +65624,7 @@ var FetchproxyServer = class {
|
|
|
65049
65624
|
ownLocalStoragePointers: this.opts.localStoragePointers,
|
|
65050
65625
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
65051
65626
|
ownDomSelectors: this.opts.domSelectors,
|
|
65627
|
+
ownDomListSelectors: this.opts.domListSelectors,
|
|
65052
65628
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
65053
65629
|
onPairCode: this.opts.onPairCode,
|
|
65054
65630
|
extensionTrust: this.extensionTrust()
|
|
@@ -65080,6 +65656,7 @@ var FetchproxyServer = class {
|
|
|
65080
65656
|
localStoragePointers: this.opts.localStoragePointers,
|
|
65081
65657
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
65082
65658
|
domSelectors: this.opts.domSelectors,
|
|
65659
|
+
domListSelectors: this.opts.domListSelectors,
|
|
65083
65660
|
graphqlOps: this.opts.graphqlOps,
|
|
65084
65661
|
extensionTrust: this.extensionTrust(),
|
|
65085
65662
|
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
@@ -65089,6 +65666,10 @@ var FetchproxyServer = class {
|
|
|
65089
65666
|
this.stopKeepalive();
|
|
65090
65667
|
this.rejectAllPending();
|
|
65091
65668
|
});
|
|
65669
|
+
this.peerHandle.onExtensionDisconnect(() => {
|
|
65670
|
+
this.stopKeepalive();
|
|
65671
|
+
this.rejectAllPending();
|
|
65672
|
+
});
|
|
65092
65673
|
this.peerHandle.onPendingPair((code) => {
|
|
65093
65674
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
65094
65675
|
});
|
|
@@ -65100,9 +65681,9 @@ var FetchproxyServer = class {
|
|
|
65100
65681
|
if (this.closing || this.peerHandle === null)
|
|
65101
65682
|
return;
|
|
65102
65683
|
this.stopKeepalive();
|
|
65103
|
-
this.rejectAllPending();
|
|
65104
65684
|
this.peerHandle = null;
|
|
65105
65685
|
this.role = null;
|
|
65686
|
+
void this.recoverFromHostLoss();
|
|
65106
65687
|
});
|
|
65107
65688
|
}
|
|
65108
65689
|
}
|
|
@@ -65123,7 +65704,7 @@ var FetchproxyServer = class {
|
|
|
65123
65704
|
* only when the bridge itself failed (no signed-in tab, extension
|
|
65124
65705
|
* offline, etc.).
|
|
65125
65706
|
*/
|
|
65126
|
-
async fetch(init) {
|
|
65707
|
+
async fetch(init, fetchOpts = {}) {
|
|
65127
65708
|
await this.ensureConnected();
|
|
65128
65709
|
const pendingCode = this.currentPendingPairCode();
|
|
65129
65710
|
if (pendingCode !== null) {
|
|
@@ -65135,16 +65716,17 @@ var FetchproxyServer = class {
|
|
|
65135
65716
|
retryAttempted: false
|
|
65136
65717
|
};
|
|
65137
65718
|
}
|
|
65138
|
-
const first = await this._fetchOnceWithTimeout(init);
|
|
65719
|
+
const first = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65139
65720
|
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
65140
65721
|
const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
|
|
65141
65722
|
if (isColdStartSymptom) {
|
|
65142
65723
|
this.lastEvictionDetectedAt = Date.now();
|
|
65143
65724
|
}
|
|
65144
|
-
|
|
65725
|
+
const retryable = isColdStartSymptom && (first.kind === "content_script_unreachable" || isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout));
|
|
65726
|
+
if (retryable && reviveMs !== void 0 && reviveMs > 0) {
|
|
65145
65727
|
this.lazyReviveAttempts += 1;
|
|
65146
65728
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65147
|
-
const second = await this._fetchOnceWithTimeout(init);
|
|
65729
|
+
const second = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65148
65730
|
if (second.ok) {
|
|
65149
65731
|
this.lazyReviveSuccesses += 1;
|
|
65150
65732
|
this.recordSuccess();
|
|
@@ -65293,13 +65875,16 @@ var FetchproxyServer = class {
|
|
|
65293
65875
|
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
65294
65876
|
* server closes, then rethrow.
|
|
65295
65877
|
*/
|
|
65296
|
-
async sendInnerFrame(inner) {
|
|
65878
|
+
async sendInnerFrame(inner, opts = {}) {
|
|
65297
65879
|
try {
|
|
65298
65880
|
if (this.hostHandle) {
|
|
65299
65881
|
await this.hostHandle.sendOwnInner(inner);
|
|
65300
65882
|
} else if (this.peerHandle) {
|
|
65301
65883
|
await this.peerHandle.sendInner(inner);
|
|
65302
65884
|
}
|
|
65885
|
+
if (inner.type === "request" && (opts.resendable ?? isResendableRequest(inner))) {
|
|
65886
|
+
this.noteResendable(inner);
|
|
65887
|
+
}
|
|
65303
65888
|
} catch (err) {
|
|
65304
65889
|
if ("id" in inner && typeof inner.id === "number") {
|
|
65305
65890
|
const { id } = inner;
|
|
@@ -65311,6 +65896,7 @@ var FetchproxyServer = class {
|
|
|
65311
65896
|
this.pendingRedirect.delete(id);
|
|
65312
65897
|
this.pendingDownload.delete(id);
|
|
65313
65898
|
this.pendingIdb.delete(id);
|
|
65899
|
+
this.pendingDomList.delete(id);
|
|
65314
65900
|
this.pendingGraphql.delete(id);
|
|
65315
65901
|
}
|
|
65316
65902
|
throw err;
|
|
@@ -65321,13 +65907,25 @@ var FetchproxyServer = class {
|
|
|
65321
65907
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
65322
65908
|
* the throwing surface is the convenience methods.
|
|
65323
65909
|
*/
|
|
65324
|
-
async _fetchOnceWithTimeout(init) {
|
|
65910
|
+
async _fetchOnceWithTimeout(init, fetchOpts = {}) {
|
|
65325
65911
|
const id = this.nextRequestId++;
|
|
65326
65912
|
const inner = { type: "request", id, op: "fetch", init };
|
|
65327
|
-
const pending = this.guardPending(new Promise((
|
|
65328
|
-
this.pending.set(id,
|
|
65913
|
+
const pending = this.guardPending(new Promise((resolve5) => {
|
|
65914
|
+
this.pending.set(id, resolve5);
|
|
65329
65915
|
}));
|
|
65330
|
-
|
|
65916
|
+
try {
|
|
65917
|
+
await this.sendInnerFrame(inner, {
|
|
65918
|
+
resendable: isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout)
|
|
65919
|
+
});
|
|
65920
|
+
} catch (err) {
|
|
65921
|
+
return {
|
|
65922
|
+
ok: false,
|
|
65923
|
+
error: err instanceof Error ? err.message : String(err),
|
|
65924
|
+
kind: "session_not_ready",
|
|
65925
|
+
retryAttempted: false,
|
|
65926
|
+
cause: err
|
|
65927
|
+
};
|
|
65928
|
+
}
|
|
65331
65929
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
65332
65930
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
65333
65931
|
return pending;
|
|
@@ -65336,12 +65934,12 @@ var FetchproxyServer = class {
|
|
|
65336
65934
|
try {
|
|
65337
65935
|
return await Promise.race([
|
|
65338
65936
|
pending,
|
|
65339
|
-
new Promise((
|
|
65937
|
+
new Promise((resolve5) => {
|
|
65340
65938
|
timer = setTimeout(() => {
|
|
65341
65939
|
this.pending.delete(id);
|
|
65342
65940
|
const elapsedMs = Date.now() - start;
|
|
65343
65941
|
const error62 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
65344
|
-
|
|
65942
|
+
resolve5({
|
|
65345
65943
|
ok: false,
|
|
65346
65944
|
error: error62,
|
|
65347
65945
|
kind: "timeout",
|
|
@@ -65401,7 +65999,7 @@ var FetchproxyServer = class {
|
|
|
65401
65999
|
});
|
|
65402
66000
|
return pending;
|
|
65403
66001
|
}
|
|
65404
|
-
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs) {
|
|
66002
|
+
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs, retrySafe = true) {
|
|
65405
66003
|
const transportMs = this.opts.fetchTimeoutMs;
|
|
65406
66004
|
if (transportMs === void 0 || transportMs <= 0)
|
|
65407
66005
|
return pending;
|
|
@@ -65422,6 +66020,7 @@ var FetchproxyServer = class {
|
|
|
65422
66020
|
port: this.opts.port,
|
|
65423
66021
|
elapsedMs: Date.now() - start,
|
|
65424
66022
|
retryAttempted: false,
|
|
66023
|
+
retrySafe,
|
|
65425
66024
|
graceMs,
|
|
65426
66025
|
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
65427
66026
|
}));
|
|
@@ -65439,7 +66038,7 @@ var FetchproxyServer = class {
|
|
|
65439
66038
|
* logic re-implemented inline) `captureRequestHeader()` agree on what
|
|
65440
66039
|
* to throw.
|
|
65441
66040
|
*/
|
|
65442
|
-
_typedErrorFor(result, url4, op, retryAttempted) {
|
|
66041
|
+
_typedErrorFor(result, url4, op, retryAttempted, retrySafe = true) {
|
|
65443
66042
|
if (result.kind === "timeout") {
|
|
65444
66043
|
return new FetchproxyTimeoutError({
|
|
65445
66044
|
url: url4,
|
|
@@ -65447,7 +66046,8 @@ var FetchproxyServer = class {
|
|
|
65447
66046
|
role: this.role,
|
|
65448
66047
|
port: this.opts.port,
|
|
65449
66048
|
elapsedMs: result.elapsedMs,
|
|
65450
|
-
retryAttempted
|
|
66049
|
+
retryAttempted,
|
|
66050
|
+
retrySafe
|
|
65451
66051
|
});
|
|
65452
66052
|
}
|
|
65453
66053
|
if (result.kind === "content_script_unreachable") {
|
|
@@ -65483,9 +66083,9 @@ var FetchproxyServer = class {
|
|
|
65483
66083
|
if (opts.subdomain !== void 0)
|
|
65484
66084
|
assertSubdomainLabel(opts.subdomain);
|
|
65485
66085
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
65486
|
-
const
|
|
66086
|
+
const isAbsolute6 = path.startsWith("http://") || path.startsWith("https://");
|
|
65487
66087
|
let host;
|
|
65488
|
-
if (
|
|
66088
|
+
if (isAbsolute6) {
|
|
65489
66089
|
try {
|
|
65490
66090
|
host = new URL(path).host;
|
|
65491
66091
|
} catch {
|
|
@@ -65494,7 +66094,7 @@ var FetchproxyServer = class {
|
|
|
65494
66094
|
} else {
|
|
65495
66095
|
host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
65496
66096
|
}
|
|
65497
|
-
const url4 =
|
|
66097
|
+
const url4 = isAbsolute6 ? path : `https://${host}${path}`;
|
|
65498
66098
|
assertUrlInDomains("request url", url4, this.opts.domains);
|
|
65499
66099
|
let tabUrl = `https://${host}/`;
|
|
65500
66100
|
if (opts.viaTab !== void 0) {
|
|
@@ -65521,9 +66121,12 @@ var FetchproxyServer = class {
|
|
|
65521
66121
|
// every version before this option existed.
|
|
65522
66122
|
...opts.credentials === "omit" ? { credentials: "omit" } : {}
|
|
65523
66123
|
};
|
|
65524
|
-
const result = await this.fetch(init);
|
|
66124
|
+
const result = await this.fetch(init, opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {});
|
|
65525
66125
|
if (!result.ok) {
|
|
65526
|
-
|
|
66126
|
+
if (result.kind === "session_not_ready" && result.cause instanceof Error) {
|
|
66127
|
+
throw result.cause;
|
|
66128
|
+
}
|
|
66129
|
+
throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false, isRetrySafeOnTimeout(method, opts.retryOnTimeout));
|
|
65527
66130
|
}
|
|
65528
66131
|
const response = {
|
|
65529
66132
|
status: result.status,
|
|
@@ -65563,17 +66166,21 @@ var FetchproxyServer = class {
|
|
|
65563
66166
|
* GET a path and parse the response body as JSON. Throws
|
|
65564
66167
|
* `FetchproxyHttpError` if the status is outside the default 2xx
|
|
65565
66168
|
* happy-path set (`[200, 201, 202, 204]`); pass a custom
|
|
65566
|
-
* `expectStatus` to override.
|
|
66169
|
+
* `expectStatus` to override. A 204 or an empty/whitespace body
|
|
66170
|
+
* resolves to `null` (the declared `T` does not say so, to keep
|
|
66171
|
+
* existing call sites compiling — include `| null` in `T` if the
|
|
66172
|
+
* endpoint can answer empty).
|
|
65567
66173
|
*/
|
|
65568
66174
|
async getJson(path, opts = {}) {
|
|
65569
66175
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
65570
|
-
return
|
|
66176
|
+
return parseJsonBody(response);
|
|
65571
66177
|
}
|
|
65572
66178
|
/**
|
|
65573
66179
|
* POST a JSON body and parse the response body as JSON. The body is
|
|
65574
66180
|
* `JSON.stringify`'d; `Content-Type: application/json` is set unless
|
|
65575
66181
|
* the caller already provided one. Defaults `expectStatus` to the 2xx
|
|
65576
|
-
* happy-path set.
|
|
66182
|
+
* happy-path set. A 204 or an empty/whitespace body resolves to `null`
|
|
66183
|
+
* rather than throwing after the write already happened (see `getJson`).
|
|
65577
66184
|
*/
|
|
65578
66185
|
async postJson(path, body, opts = {}) {
|
|
65579
66186
|
const headers = { ...opts.headers ?? {} };
|
|
@@ -65585,7 +66192,7 @@ var FetchproxyServer = class {
|
|
|
65585
66192
|
headers,
|
|
65586
66193
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
65587
66194
|
});
|
|
65588
|
-
return
|
|
66195
|
+
return parseJsonBody(response);
|
|
65589
66196
|
}
|
|
65590
66197
|
/**
|
|
65591
66198
|
* GET a path and return the response body as a string. Throws
|
|
@@ -65606,7 +66213,8 @@ var FetchproxyServer = class {
|
|
|
65606
66213
|
* - adds `Content-Type: application/json` only for a non-GET request
|
|
65607
66214
|
* that carries a `body` (and only if the caller didn't set one);
|
|
65608
66215
|
* - `JSON.stringify`s the body (GET / no-body sends nothing);
|
|
65609
|
-
* - treats a `204
|
|
66216
|
+
* - treats a `204`, an empty body, or a whitespace-only body as
|
|
66217
|
+
* `data: null` (no parse), the same rule as getJson/postJson;
|
|
65610
66218
|
* - otherwise `JSON.parse`s the body.
|
|
65611
66219
|
*
|
|
65612
66220
|
* Scope is serialization + header defaults + 204-handling +
|
|
@@ -65620,6 +66228,11 @@ var FetchproxyServer = class {
|
|
|
65620
66228
|
* Bridge-level failures (no signed-in tab, SW down, timeout) still
|
|
65621
66229
|
* throw the typed errors via `request()`, exactly like the verb
|
|
65622
66230
|
* helpers — only successful round-trips (any HTTP status) return.
|
|
66231
|
+
*
|
|
66232
|
+
* `retryOnTimeout` is `RequestOpts.retryOnTimeout`: a timed-out POST is
|
|
66233
|
+
* NOT re-sent unless the caller says it is safe to repeat. A read-only POST
|
|
66234
|
+
* (a search, a GraphQL query) should pass `true` to keep the cold-start
|
|
66235
|
+
* timeout retry (#90).
|
|
65623
66236
|
*/
|
|
65624
66237
|
async requestJson(method, path, opts = {}) {
|
|
65625
66238
|
const isGet = method.toUpperCase() === "GET";
|
|
@@ -65633,7 +66246,8 @@ var FetchproxyServer = class {
|
|
|
65633
66246
|
headers,
|
|
65634
66247
|
body: sendBody ? JSON.stringify(opts.body) : void 0,
|
|
65635
66248
|
...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
|
|
65636
|
-
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
66249
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {},
|
|
66250
|
+
...opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {}
|
|
65637
66251
|
});
|
|
65638
66252
|
const result = {
|
|
65639
66253
|
ok: true,
|
|
@@ -65641,12 +66255,9 @@ var FetchproxyServer = class {
|
|
|
65641
66255
|
url: response.url,
|
|
65642
66256
|
body: response.body
|
|
65643
66257
|
};
|
|
65644
|
-
if (response.status === 204 || response.body === "") {
|
|
65645
|
-
return { data: null, result };
|
|
65646
|
-
}
|
|
65647
66258
|
let data;
|
|
65648
66259
|
try {
|
|
65649
|
-
data =
|
|
66260
|
+
data = parseJsonBody(response);
|
|
65650
66261
|
} catch (e) {
|
|
65651
66262
|
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
65652
66263
|
}
|
|
@@ -65703,18 +66314,19 @@ var FetchproxyServer = class {
|
|
|
65703
66314
|
};
|
|
65704
66315
|
}
|
|
65705
66316
|
/**
|
|
65706
|
-
*
|
|
66317
|
+
* Read the user's cookies for the chosen domain.
|
|
65707
66318
|
*
|
|
65708
66319
|
* Requires `'read_cookies'` in `FetchproxyServerOpts.capabilities`.
|
|
65709
66320
|
* Throws a developer-facing `Error` at the call site if the MCP did
|
|
65710
66321
|
* not declare the capability — this is a programming mistake, not a
|
|
65711
66322
|
* runtime condition.
|
|
65712
66323
|
*
|
|
65713
|
-
* The returned string is
|
|
65714
|
-
*
|
|
65715
|
-
*
|
|
65716
|
-
*
|
|
65717
|
-
*
|
|
66324
|
+
* The returned string is semicolon-separated `k=v` pairs. With `keys`
|
|
66325
|
+
* (each in the declared `cookieKeys`), the extension reads each name via
|
|
66326
|
+
* `chrome.cookies.get`, which INCLUDES HttpOnly cookies — the login
|
|
66327
|
+
* session cookie among them; that is what cookie-session MCPs use it
|
|
66328
|
+
* for. Without `keys` (legacy), it is the tab's `document.cookie`, which
|
|
66329
|
+
* omits HttpOnly cookies. See docs/SECURITY.md §T-cookie-exfil.
|
|
65718
66330
|
*
|
|
65719
66331
|
* Throws `FetchproxyProtocolError` if the bridge could not deliver
|
|
65720
66332
|
* the request (no signed-in tab, extension offline, etc.).
|
|
@@ -65747,8 +66359,8 @@ var FetchproxyServer = class {
|
|
|
65747
66359
|
const tabUrl = `https://${host}/`;
|
|
65748
66360
|
inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
|
|
65749
66361
|
}
|
|
65750
|
-
const pending = this.guardPending(new Promise((
|
|
65751
|
-
this.pendingReadCookies.set(id,
|
|
66362
|
+
const pending = this.guardPending(new Promise((resolve5) => {
|
|
66363
|
+
this.pendingReadCookies.set(id, resolve5);
|
|
65752
66364
|
}));
|
|
65753
66365
|
await this.sendInnerFrame(inner);
|
|
65754
66366
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
@@ -65806,11 +66418,19 @@ var FetchproxyServer = class {
|
|
|
65806
66418
|
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
65807
66419
|
}
|
|
65808
66420
|
};
|
|
65809
|
-
const pending = this.guardPending(new Promise((
|
|
65810
|
-
this.pendingWriteCookies.set(id, { resolve:
|
|
66421
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66422
|
+
this.pendingWriteCookies.set(id, { resolve: resolve5, reject });
|
|
65811
66423
|
}));
|
|
65812
66424
|
await this.sendInnerFrame(inner);
|
|
65813
|
-
return this._withVerbTimeout(
|
|
66425
|
+
return this._withVerbTimeout(
|
|
66426
|
+
pending,
|
|
66427
|
+
this.pendingWriteCookies,
|
|
66428
|
+
id,
|
|
66429
|
+
`https://${host}`,
|
|
66430
|
+
void 0,
|
|
66431
|
+
// A write: never re-sent after an ambiguous timeout.
|
|
66432
|
+
false
|
|
66433
|
+
);
|
|
65814
66434
|
}
|
|
65815
66435
|
/**
|
|
65816
66436
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
@@ -65869,8 +66489,8 @@ var FetchproxyServer = class {
|
|
|
65869
66489
|
...opts.pointers ? { pointers: { ...opts.pointers } } : {}
|
|
65870
66490
|
}
|
|
65871
66491
|
};
|
|
65872
|
-
const pending = this.guardPending(new Promise((
|
|
65873
|
-
this.pendingStorage.set(id, { resolve:
|
|
66492
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66493
|
+
this.pendingStorage.set(id, { resolve: resolve5, reject });
|
|
65874
66494
|
}));
|
|
65875
66495
|
await this.sendInnerFrame(inner);
|
|
65876
66496
|
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
@@ -65913,53 +66533,7 @@ var FetchproxyServer = class {
|
|
|
65913
66533
|
throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
|
|
65914
66534
|
}
|
|
65915
66535
|
const callOpts = { ...resolved2, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
65916
|
-
|
|
65917
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65918
|
-
this.recordSuccess();
|
|
65919
|
-
return result;
|
|
65920
|
-
} catch (err) {
|
|
65921
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
65922
|
-
if (!swDown) {
|
|
65923
|
-
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
65924
|
-
throw err;
|
|
65925
|
-
}
|
|
65926
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
65927
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
65928
|
-
if (reviveMs > 0) {
|
|
65929
|
-
this.lazyReviveAttempts += 1;
|
|
65930
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65931
|
-
try {
|
|
65932
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65933
|
-
this.lazyReviveSuccesses += 1;
|
|
65934
|
-
this.recordSuccess();
|
|
65935
|
-
return result;
|
|
65936
|
-
} catch (retryErr) {
|
|
65937
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
65938
|
-
if (!stillDown) {
|
|
65939
|
-
this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
|
|
65940
|
-
throw retryErr;
|
|
65941
|
-
}
|
|
65942
|
-
this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
|
|
65943
|
-
throw new FetchproxyBridgeDownError({
|
|
65944
|
-
originalError: retryErr.message,
|
|
65945
|
-
retryAttempted: true,
|
|
65946
|
-
op: "capture_request_header",
|
|
65947
|
-
url: `https://${resolved2.host}${resolved2.path ?? "/*"}`,
|
|
65948
|
-
role: this.role,
|
|
65949
|
-
port: this.opts.port
|
|
65950
|
-
});
|
|
65951
|
-
}
|
|
65952
|
-
}
|
|
65953
|
-
this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
|
|
65954
|
-
throw new FetchproxyBridgeDownError({
|
|
65955
|
-
originalError: err.message,
|
|
65956
|
-
retryAttempted: false,
|
|
65957
|
-
op: "capture_request_header",
|
|
65958
|
-
url: `https://${resolved2.host}${resolved2.path ?? "/*"}`,
|
|
65959
|
-
role: this.role,
|
|
65960
|
-
port: this.opts.port
|
|
65961
|
-
});
|
|
65962
|
-
}
|
|
66536
|
+
return this.withLazyRevive("capture_request_header", `https://${resolved2.host}${resolved2.path ?? "/*"}`, () => this._captureRequestHeaderOnce(callOpts));
|
|
65963
66537
|
}
|
|
65964
66538
|
async _captureRequestHeaderOnce(opts) {
|
|
65965
66539
|
const id = this.nextRequestId++;
|
|
@@ -65974,8 +66548,8 @@ var FetchproxyServer = class {
|
|
|
65974
66548
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
65975
66549
|
}
|
|
65976
66550
|
};
|
|
65977
|
-
const pending = this.guardPending(new Promise((
|
|
65978
|
-
this.pendingCapture.set(id, { resolve:
|
|
66551
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66552
|
+
this.pendingCapture.set(id, { resolve: resolve5, reject });
|
|
65979
66553
|
}));
|
|
65980
66554
|
await this.sendInnerFrame(inner);
|
|
65981
66555
|
return this._withVerbTimeout(
|
|
@@ -66008,53 +66582,7 @@ var FetchproxyServer = class {
|
|
|
66008
66582
|
}
|
|
66009
66583
|
await this.ensureConnected();
|
|
66010
66584
|
this.throwIfPendingPair();
|
|
66011
|
-
|
|
66012
|
-
const result = await this._captureRedirectOnce(opts);
|
|
66013
|
-
this.recordSuccess();
|
|
66014
|
-
return result;
|
|
66015
|
-
} catch (err) {
|
|
66016
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
66017
|
-
if (!swDown) {
|
|
66018
|
-
this.recordFailure(`capture_redirect: ${err.message ?? String(err)}`);
|
|
66019
|
-
throw err;
|
|
66020
|
-
}
|
|
66021
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
66022
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
66023
|
-
if (reviveMs > 0) {
|
|
66024
|
-
this.lazyReviveAttempts += 1;
|
|
66025
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
66026
|
-
try {
|
|
66027
|
-
const result = await this._captureRedirectOnce(opts);
|
|
66028
|
-
this.lazyReviveSuccesses += 1;
|
|
66029
|
-
this.recordSuccess();
|
|
66030
|
-
return result;
|
|
66031
|
-
} catch (retryErr) {
|
|
66032
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
66033
|
-
if (!stillDown) {
|
|
66034
|
-
this.recordFailure(`capture_redirect: ${retryErr.message ?? String(retryErr)}`);
|
|
66035
|
-
throw retryErr;
|
|
66036
|
-
}
|
|
66037
|
-
this.recordFailure(`capture_redirect bridge-down: ${retryErr.message}`);
|
|
66038
|
-
throw new FetchproxyBridgeDownError({
|
|
66039
|
-
originalError: retryErr.message,
|
|
66040
|
-
retryAttempted: true,
|
|
66041
|
-
op: "capture_redirect",
|
|
66042
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
66043
|
-
role: this.role,
|
|
66044
|
-
port: this.opts.port
|
|
66045
|
-
});
|
|
66046
|
-
}
|
|
66047
|
-
}
|
|
66048
|
-
this.recordFailure(`capture_redirect bridge-down: ${err.message}`);
|
|
66049
|
-
throw new FetchproxyBridgeDownError({
|
|
66050
|
-
originalError: err.message,
|
|
66051
|
-
retryAttempted: false,
|
|
66052
|
-
op: "capture_redirect",
|
|
66053
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
66054
|
-
role: this.role,
|
|
66055
|
-
port: this.opts.port
|
|
66056
|
-
});
|
|
66057
|
-
}
|
|
66585
|
+
return this.withLazyRevive("capture_redirect", `https://${opts.host}${opts.path ?? "/*"}`, () => this._captureRedirectOnce(opts));
|
|
66058
66586
|
}
|
|
66059
66587
|
async _captureRedirectOnce(opts) {
|
|
66060
66588
|
const id = this.nextRequestId++;
|
|
@@ -66068,8 +66596,8 @@ var FetchproxyServer = class {
|
|
|
66068
66596
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
66069
66597
|
}
|
|
66070
66598
|
};
|
|
66071
|
-
const pending = this.guardPending(new Promise((
|
|
66072
|
-
this.pendingRedirect.set(id, { resolve:
|
|
66599
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66600
|
+
this.pendingRedirect.set(id, { resolve: resolve5, reject });
|
|
66073
66601
|
}));
|
|
66074
66602
|
await this.sendInnerFrame(inner);
|
|
66075
66603
|
return this._withVerbTimeout(
|
|
@@ -66099,52 +66627,57 @@ var FetchproxyServer = class {
|
|
|
66099
66627
|
assertUrlInDomains("download url", opts.url, this.opts.domains);
|
|
66100
66628
|
await this.ensureConnected();
|
|
66101
66629
|
this.throwIfPendingPair();
|
|
66630
|
+
return this.withLazyRevive("download", opts.url, () => this._downloadOnce(opts));
|
|
66631
|
+
}
|
|
66632
|
+
/**
|
|
66633
|
+
* B-QUAL-1: the lazy-revive policy for the throwing verbs
|
|
66634
|
+
* (`capture_request_header`, `capture_redirect`, `download`), in one place.
|
|
66635
|
+
* Runs `once`; on the SW-eviction symptom (`content_script_unreachable` —
|
|
66636
|
+
* the request provably never reached a tab, so re-sending is safe) stamps
|
|
66637
|
+
* the eviction counter, waits `bridgeReviveDelayMs` and retries once, then
|
|
66638
|
+
* surfaces a `FetchproxyBridgeDownError`. Every other error is recorded and
|
|
66639
|
+
* rethrown untouched. `fetch()` keeps its own envelope-shaped variant.
|
|
66640
|
+
*/
|
|
66641
|
+
async withLazyRevive(op, url4, once) {
|
|
66642
|
+
const isSwDown = (e) => e instanceof FetchproxyProtocolError && classifyFetchError(e.message) === "content_script_unreachable";
|
|
66643
|
+
const bridgeDown = (e, retryAttempted) => {
|
|
66644
|
+
this.recordFailure(`${op} bridge-down: ${e.message}`);
|
|
66645
|
+
return new FetchproxyBridgeDownError({
|
|
66646
|
+
originalError: e.message,
|
|
66647
|
+
retryAttempted,
|
|
66648
|
+
op,
|
|
66649
|
+
url: url4,
|
|
66650
|
+
role: this.role,
|
|
66651
|
+
port: this.opts.port
|
|
66652
|
+
});
|
|
66653
|
+
};
|
|
66102
66654
|
try {
|
|
66103
|
-
const result = await
|
|
66655
|
+
const result = await once();
|
|
66104
66656
|
this.recordSuccess();
|
|
66105
66657
|
return result;
|
|
66106
66658
|
} catch (err) {
|
|
66107
|
-
|
|
66108
|
-
|
|
66109
|
-
this.recordFailure(`download: ${err.message ?? String(err)}`);
|
|
66659
|
+
if (!isSwDown(err)) {
|
|
66660
|
+
this.recordFailure(`${op}: ${err.message ?? String(err)}`);
|
|
66110
66661
|
throw err;
|
|
66111
66662
|
}
|
|
66112
66663
|
this.lastEvictionDetectedAt = Date.now();
|
|
66113
66664
|
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
66114
|
-
if (reviveMs
|
|
66115
|
-
|
|
66116
|
-
|
|
66117
|
-
|
|
66118
|
-
|
|
66119
|
-
|
|
66120
|
-
|
|
66121
|
-
|
|
66122
|
-
|
|
66123
|
-
|
|
66124
|
-
|
|
66125
|
-
|
|
66126
|
-
|
|
66127
|
-
}
|
|
66128
|
-
this.recordFailure(`download bridge-down: ${retryErr.message}`);
|
|
66129
|
-
throw new FetchproxyBridgeDownError({
|
|
66130
|
-
originalError: retryErr.message,
|
|
66131
|
-
retryAttempted: true,
|
|
66132
|
-
op: "download",
|
|
66133
|
-
url: opts.url,
|
|
66134
|
-
role: this.role,
|
|
66135
|
-
port: this.opts.port
|
|
66136
|
-
});
|
|
66665
|
+
if (reviveMs <= 0)
|
|
66666
|
+
throw bridgeDown(err, false);
|
|
66667
|
+
this.lazyReviveAttempts += 1;
|
|
66668
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
66669
|
+
try {
|
|
66670
|
+
const result = await once();
|
|
66671
|
+
this.lazyReviveSuccesses += 1;
|
|
66672
|
+
this.recordSuccess();
|
|
66673
|
+
return result;
|
|
66674
|
+
} catch (retryErr) {
|
|
66675
|
+
if (!isSwDown(retryErr)) {
|
|
66676
|
+
this.recordFailure(`${op}: ${retryErr.message ?? String(retryErr)}`);
|
|
66677
|
+
throw retryErr;
|
|
66137
66678
|
}
|
|
66679
|
+
throw bridgeDown(retryErr, true);
|
|
66138
66680
|
}
|
|
66139
|
-
this.recordFailure(`download bridge-down: ${err.message}`);
|
|
66140
|
-
throw new FetchproxyBridgeDownError({
|
|
66141
|
-
originalError: err.message,
|
|
66142
|
-
retryAttempted: false,
|
|
66143
|
-
op: "download",
|
|
66144
|
-
url: opts.url,
|
|
66145
|
-
role: this.role,
|
|
66146
|
-
port: this.opts.port
|
|
66147
|
-
});
|
|
66148
66681
|
}
|
|
66149
66682
|
}
|
|
66150
66683
|
async _downloadOnce(opts) {
|
|
@@ -66159,8 +66692,8 @@ var FetchproxyServer = class {
|
|
|
66159
66692
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
66160
66693
|
}
|
|
66161
66694
|
};
|
|
66162
|
-
const pending = this.guardPending(new Promise((
|
|
66163
|
-
this.pendingDownload.set(id, { resolve:
|
|
66695
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66696
|
+
this.pendingDownload.set(id, { resolve: resolve5, reject });
|
|
66164
66697
|
}));
|
|
66165
66698
|
await this.sendInnerFrame(inner);
|
|
66166
66699
|
return this._withVerbTimeout(
|
|
@@ -66173,7 +66706,9 @@ var FetchproxyServer = class {
|
|
|
66173
66706
|
// CAP and need the identical explanation; missing it then was the point
|
|
66174
66707
|
// of #279 — "both verbs" was counted off the two call sites in view
|
|
66175
66708
|
// rather than off the type. Three is still the number.
|
|
66176
|
-
opts.timeoutMs
|
|
66709
|
+
opts.timeoutMs,
|
|
66710
|
+
// A second download saves a duplicate "file (1)".
|
|
66711
|
+
false
|
|
66177
66712
|
);
|
|
66178
66713
|
}
|
|
66179
66714
|
/**
|
|
@@ -66218,8 +66753,8 @@ var FetchproxyServer = class {
|
|
|
66218
66753
|
keys: [...opts.keys]
|
|
66219
66754
|
}
|
|
66220
66755
|
};
|
|
66221
|
-
const pending = this.guardPending(new Promise((
|
|
66222
|
-
this.pendingIdb.set(id, { resolve:
|
|
66756
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66757
|
+
this.pendingIdb.set(id, { resolve: resolve5, reject });
|
|
66223
66758
|
}));
|
|
66224
66759
|
await this.sendInnerFrame(inner);
|
|
66225
66760
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
@@ -66258,12 +66793,58 @@ var FetchproxyServer = class {
|
|
|
66258
66793
|
op: "read_dom",
|
|
66259
66794
|
init: { origin, names: [...opts.names] }
|
|
66260
66795
|
};
|
|
66261
|
-
const pending = this.guardPending(new Promise((
|
|
66262
|
-
this.pendingStorage.set(id, { resolve:
|
|
66796
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66797
|
+
this.pendingStorage.set(id, { resolve: resolve5, reject });
|
|
66263
66798
|
}));
|
|
66264
66799
|
await this.sendInnerFrame(inner);
|
|
66265
66800
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
66266
66801
|
}
|
|
66802
|
+
/**
|
|
66803
|
+
* 3.1.0+: read a declared REPEATED DOM structure from the user's
|
|
66804
|
+
* signed-in tab. Requires `'read_dom_list'` in capabilities AND `name` to
|
|
66805
|
+
* match a declared `domListSelectors` entry. The extension resolves the
|
|
66806
|
+
* entry's `itemSelector` via `document.querySelectorAll` in the matched
|
|
66807
|
+
* tab's DOM (isolated world), then resolves each declared field against
|
|
66808
|
+
* every matched item in turn — no page-JS execution, same as `readDom`.
|
|
66809
|
+
*
|
|
66810
|
+
* Returns one row per matched item, in document order, truncated to the
|
|
66811
|
+
* entry's declared `maxItems` when set. A field absent on a given item is
|
|
66812
|
+
* omitted from that item's row (the row itself is still returned). Throws
|
|
66813
|
+
* `FetchproxyProtocolError` on bridge failures and a plain `Error` on
|
|
66814
|
+
* developer mistakes (undeclared capability, undeclared name).
|
|
66815
|
+
*
|
|
66816
|
+
* Reads only what is CURRENTLY rendered — a virtualized or lazily-loaded
|
|
66817
|
+
* list returns only its visible rows. A caller that needs more scrolls
|
|
66818
|
+
* the list (out of band) and calls again.
|
|
66819
|
+
*/
|
|
66820
|
+
async readDomList(opts) {
|
|
66821
|
+
if (!this.opts.capabilities.includes("read_dom_list")) {
|
|
66822
|
+
throw new Error('FetchproxyServer.readDomList(): MCP did not declare "read_dom_list" in capabilities');
|
|
66823
|
+
}
|
|
66824
|
+
await this.ensureConnected();
|
|
66825
|
+
this.throwIfPendingPair();
|
|
66826
|
+
if (typeof opts.name !== "string" || opts.name.length === 0) {
|
|
66827
|
+
throw new Error("FetchproxyServer.readDomList: opts.name must be a non-empty string");
|
|
66828
|
+
}
|
|
66829
|
+
this.assertScopeSubset([opts.name], this.opts.domListSelectors.map((d) => d.name), "domListSelectors");
|
|
66830
|
+
if (opts.subdomain !== void 0)
|
|
66831
|
+
assertSubdomainLabel(opts.subdomain);
|
|
66832
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
66833
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
66834
|
+
const origin = `https://${host}`;
|
|
66835
|
+
const id = this.nextRequestId++;
|
|
66836
|
+
const inner = {
|
|
66837
|
+
type: "request",
|
|
66838
|
+
id,
|
|
66839
|
+
op: "read_dom_list",
|
|
66840
|
+
init: { origin, name: opts.name }
|
|
66841
|
+
};
|
|
66842
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66843
|
+
this.pendingDomList.set(id, { resolve: resolve5, reject });
|
|
66844
|
+
}));
|
|
66845
|
+
await this.sendInnerFrame(inner);
|
|
66846
|
+
return this._withVerbTimeout(pending, this.pendingDomList, id, origin);
|
|
66847
|
+
}
|
|
66267
66848
|
/**
|
|
66268
66849
|
* 1.x+: run a declared GraphQL operation through the page's own Apollo
|
|
66269
66850
|
* client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
|
|
@@ -66304,11 +66885,12 @@ var FetchproxyServer = class {
|
|
|
66304
66885
|
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
66305
66886
|
}
|
|
66306
66887
|
};
|
|
66307
|
-
const pending = this.guardPending(new Promise((
|
|
66308
|
-
this.pendingGraphql.set(id, { resolve:
|
|
66888
|
+
const pending = this.guardPending(new Promise((resolve5, reject) => {
|
|
66889
|
+
this.pendingGraphql.set(id, { resolve: resolve5, reject });
|
|
66309
66890
|
}));
|
|
66310
|
-
|
|
66311
|
-
|
|
66891
|
+
const retrySafe = opts.retryOnTimeout === true;
|
|
66892
|
+
await this.sendInnerFrame(inner, { resendable: retrySafe });
|
|
66893
|
+
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name, void 0, retrySafe);
|
|
66312
66894
|
}
|
|
66313
66895
|
assertScopeSubset(requested, declared, label) {
|
|
66314
66896
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -66345,6 +66927,7 @@ var FetchproxyServer = class {
|
|
|
66345
66927
|
onInner(inner) {
|
|
66346
66928
|
if (inner.type !== "response")
|
|
66347
66929
|
return;
|
|
66930
|
+
this.resendable.delete(inner.id);
|
|
66348
66931
|
this.lastExtensionMessageAt = Date.now();
|
|
66349
66932
|
const fetchCb = this.pending.get(inner.id);
|
|
66350
66933
|
if (fetchCb) {
|
|
@@ -66433,6 +67016,20 @@ var FetchproxyServer = class {
|
|
|
66433
67016
|
}
|
|
66434
67017
|
return;
|
|
66435
67018
|
}
|
|
67019
|
+
const domListCb = this.pendingDomList.get(inner.id);
|
|
67020
|
+
if (domListCb) {
|
|
67021
|
+
this.pendingDomList.delete(inner.id);
|
|
67022
|
+
if (inner.ok) {
|
|
67023
|
+
if (inner.op === "read_dom_list" && inner.rows) {
|
|
67024
|
+
domListCb.resolve(inner.rows.map((row) => ({ ...row })));
|
|
67025
|
+
} else {
|
|
67026
|
+
domListCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_dom_list awaiter`));
|
|
67027
|
+
}
|
|
67028
|
+
} else {
|
|
67029
|
+
domListCb.reject(protocolErrorFrom(inner.error));
|
|
67030
|
+
}
|
|
67031
|
+
return;
|
|
67032
|
+
}
|
|
66436
67033
|
const downloadCb = this.pendingDownload.get(inner.id);
|
|
66437
67034
|
if (downloadCb) {
|
|
66438
67035
|
this.pendingDownload.delete(inner.id);
|
|
@@ -66492,42 +67089,133 @@ var FetchproxyServer = class {
|
|
|
66492
67089
|
}
|
|
66493
67090
|
}
|
|
66494
67091
|
}
|
|
66495
|
-
rejectAllPending(reason = "extension disconnected") {
|
|
67092
|
+
rejectAllPending(reason = "extension disconnected", keep = /* @__PURE__ */ new Set()) {
|
|
67093
|
+
for (const id of this.allPendingIds()) {
|
|
67094
|
+
if (!keep.has(id))
|
|
67095
|
+
this.rejectPendingId(id, reason);
|
|
67096
|
+
}
|
|
67097
|
+
if (keep.size === 0)
|
|
67098
|
+
this.resendable.clear();
|
|
67099
|
+
}
|
|
67100
|
+
/** Every request id with a live awaiter, across all op maps. */
|
|
67101
|
+
allPendingIds() {
|
|
67102
|
+
const ids = [];
|
|
67103
|
+
for (const map2 of this.pendingMaps())
|
|
67104
|
+
for (const id of map2.keys())
|
|
67105
|
+
ids.push(id);
|
|
67106
|
+
return ids;
|
|
67107
|
+
}
|
|
67108
|
+
pendingMaps() {
|
|
67109
|
+
return [
|
|
67110
|
+
this.pending,
|
|
67111
|
+
this.pendingReadCookies,
|
|
67112
|
+
this.pendingStorage,
|
|
67113
|
+
this.pendingWriteCookies,
|
|
67114
|
+
this.pendingCapture,
|
|
67115
|
+
this.pendingRedirect,
|
|
67116
|
+
this.pendingIdb,
|
|
67117
|
+
this.pendingDomList,
|
|
67118
|
+
this.pendingDownload,
|
|
67119
|
+
this.pendingGraphql
|
|
67120
|
+
];
|
|
67121
|
+
}
|
|
67122
|
+
isPendingId(id) {
|
|
67123
|
+
return this.pendingMaps().some((m) => m.has(id));
|
|
67124
|
+
}
|
|
67125
|
+
/** Fail the one awaiter registered under `id`, in its own map's shape. */
|
|
67126
|
+
rejectPendingId(id, reason) {
|
|
66496
67127
|
const err = new FetchproxyProtocolError(reason);
|
|
66497
|
-
|
|
66498
|
-
|
|
67128
|
+
this.resendable.delete(id);
|
|
67129
|
+
const fetchCb = this.pending.get(id);
|
|
67130
|
+
if (fetchCb) {
|
|
67131
|
+
this.pending.delete(id);
|
|
67132
|
+
fetchCb({
|
|
66499
67133
|
ok: false,
|
|
66500
67134
|
error: err.message,
|
|
66501
67135
|
kind: classifyFetchError(err.message),
|
|
66502
67136
|
retryAttempted: false
|
|
66503
67137
|
});
|
|
67138
|
+
return;
|
|
67139
|
+
}
|
|
67140
|
+
const cookieCb = this.pendingReadCookies.get(id);
|
|
67141
|
+
if (cookieCb) {
|
|
67142
|
+
this.pendingReadCookies.delete(id);
|
|
67143
|
+
cookieCb({ ok: false, error: err.message });
|
|
67144
|
+
return;
|
|
67145
|
+
}
|
|
67146
|
+
for (const map2 of [
|
|
67147
|
+
this.pendingStorage,
|
|
67148
|
+
this.pendingWriteCookies,
|
|
67149
|
+
this.pendingCapture,
|
|
67150
|
+
this.pendingRedirect,
|
|
67151
|
+
this.pendingIdb,
|
|
67152
|
+
this.pendingDomList,
|
|
67153
|
+
this.pendingDownload,
|
|
67154
|
+
this.pendingGraphql
|
|
67155
|
+
]) {
|
|
67156
|
+
const entry = map2.get(id);
|
|
67157
|
+
if (entry) {
|
|
67158
|
+
map2.delete(id);
|
|
67159
|
+
entry.reject(err);
|
|
67160
|
+
return;
|
|
67161
|
+
}
|
|
67162
|
+
}
|
|
67163
|
+
}
|
|
67164
|
+
noteResendable(inner) {
|
|
67165
|
+
this.resendable.set(inner.id, inner);
|
|
67166
|
+
if (this.resendable.size > 256) {
|
|
67167
|
+
for (const id of [...this.resendable.keys()]) {
|
|
67168
|
+
if (!this.isPendingId(id))
|
|
67169
|
+
this.resendable.delete(id);
|
|
67170
|
+
}
|
|
66504
67171
|
}
|
|
66505
|
-
|
|
66506
|
-
|
|
66507
|
-
|
|
66508
|
-
|
|
66509
|
-
|
|
66510
|
-
|
|
66511
|
-
|
|
66512
|
-
|
|
66513
|
-
|
|
66514
|
-
|
|
66515
|
-
|
|
66516
|
-
|
|
66517
|
-
|
|
66518
|
-
|
|
66519
|
-
|
|
66520
|
-
|
|
66521
|
-
this.
|
|
66522
|
-
|
|
66523
|
-
|
|
66524
|
-
|
|
66525
|
-
|
|
66526
|
-
|
|
66527
|
-
|
|
66528
|
-
|
|
66529
|
-
|
|
66530
|
-
|
|
67172
|
+
}
|
|
67173
|
+
/**
|
|
67174
|
+
* B-BUG-6: the host this peer relayed through has gone — most often a
|
|
67175
|
+
* short-lived process (a bootstrap lift, an `fpx` call) that won the
|
|
67176
|
+
* election and then closed. Fail only the in-flight requests that may
|
|
67177
|
+
* already have run (a non-idempotent fetch, a cookie write, a download);
|
|
67178
|
+
* re-elect and send the rest again. The replies to the originals died with
|
|
67179
|
+
* the old host, and each re-sent request keeps its awaiter and its
|
|
67180
|
+
* original deadline.
|
|
67181
|
+
*/
|
|
67182
|
+
async recoverFromHostLoss() {
|
|
67183
|
+
const toResend = /* @__PURE__ */ new Map();
|
|
67184
|
+
for (const [id, inner] of this.resendable) {
|
|
67185
|
+
if (this.isPendingId(id))
|
|
67186
|
+
toResend.set(id, inner);
|
|
67187
|
+
}
|
|
67188
|
+
this.resendable.clear();
|
|
67189
|
+
this.rejectAllPending("fetchproxy: the MCP process holding the bridge port exited while this request was in flight. It was not re-sent because it may already have run in the browser \u2014 check before retrying.", new Set(toResend.keys()));
|
|
67190
|
+
if (toResend.size === 0)
|
|
67191
|
+
return;
|
|
67192
|
+
const lost = (id, e) => this.rejectPendingId(id, `fetchproxy: the bridge host exited and reconnecting failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
67193
|
+
try {
|
|
67194
|
+
await this.ensureConnected();
|
|
67195
|
+
} catch (e) {
|
|
67196
|
+
for (const id of toResend.keys())
|
|
67197
|
+
lost(id, e);
|
|
67198
|
+
return;
|
|
67199
|
+
}
|
|
67200
|
+
for (const [id, inner] of toResend) {
|
|
67201
|
+
if (!this.isPendingId(id))
|
|
67202
|
+
continue;
|
|
67203
|
+
try {
|
|
67204
|
+
await this.resendInnerFrame(inner);
|
|
67205
|
+
} catch (e) {
|
|
67206
|
+
lost(id, e);
|
|
67207
|
+
}
|
|
67208
|
+
}
|
|
67209
|
+
}
|
|
67210
|
+
/** Send an already-registered request again, keeping its awaiter on failure. */
|
|
67211
|
+
async resendInnerFrame(inner) {
|
|
67212
|
+
if (this.hostHandle)
|
|
67213
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
67214
|
+
else if (this.peerHandle)
|
|
67215
|
+
await this.peerHandle.sendInner(inner);
|
|
67216
|
+
else
|
|
67217
|
+
throw new Error("no bridge handle after re-election");
|
|
67218
|
+
this.noteResendable(inner);
|
|
66531
67219
|
}
|
|
66532
67220
|
/**
|
|
66533
67221
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -66630,6 +67318,12 @@ async function runOneLift(opts) {
|
|
|
66630
67318
|
if (envVal !== void 0 && envVal !== "" && envVal !== "0" && envVal !== "false") {
|
|
66631
67319
|
throw new BootstrapDisabledError(opts.serverName, envVar);
|
|
66632
67320
|
}
|
|
67321
|
+
for (const bucket of ["cookies", "localStorage", "sessionStorage"]) {
|
|
67322
|
+
const glob = opts.declare[bucket].find((k) => k.endsWith("*"));
|
|
67323
|
+
if (glob !== void 0) {
|
|
67324
|
+
throw new Error(`fetchproxy bootstrap: declare.${bucket} contains ${JSON.stringify(glob)}, a glob. A lift reads each key by its exact name, so a glob can never match \u2014 list the concrete key names instead.`);
|
|
67325
|
+
}
|
|
67326
|
+
}
|
|
66633
67327
|
const factory = opts._serverFactory ?? defaultFactory;
|
|
66634
67328
|
const indexedDb = opts.declare.indexedDb ?? [];
|
|
66635
67329
|
const localStoragePointers = opts.declare.localStoragePointers ?? [];
|
|
@@ -66887,7 +67581,7 @@ function loadAccount(env = process.env) {
|
|
|
66887
67581
|
// package.json
|
|
66888
67582
|
var package_default = {
|
|
66889
67583
|
name: "canvas-parent-mcp",
|
|
66890
|
-
version: "2.1.
|
|
67584
|
+
version: "2.1.3",
|
|
66891
67585
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
66892
67586
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
66893
67587
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -66934,12 +67628,12 @@ var package_default = {
|
|
|
66934
67628
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
66935
67629
|
},
|
|
66936
67630
|
dependencies: {
|
|
66937
|
-
"@chrischall/mcp-utils": "^
|
|
66938
|
-
"@fetchproxy/bootstrap": "^3.0
|
|
66939
|
-
"@fetchproxy/server": "^3.0
|
|
67631
|
+
"@chrischall/mcp-utils": "^2.4.0",
|
|
67632
|
+
"@fetchproxy/bootstrap": "^3.2.0",
|
|
67633
|
+
"@fetchproxy/server": "^3.2.0",
|
|
66940
67634
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
66941
67635
|
dotenv: "^18.0.0",
|
|
66942
|
-
zod: "^4.6.
|
|
67636
|
+
zod: "^4.6.5"
|
|
66943
67637
|
},
|
|
66944
67638
|
devDependencies: {
|
|
66945
67639
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
@@ -67196,10 +67890,10 @@ function registerCredentialHealthcheckTool(args) {
|
|
|
67196
67890
|
import { writeFile as writeFile3, stat } from "fs/promises";
|
|
67197
67891
|
|
|
67198
67892
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
67199
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
67200
|
-
import { dirname, join as
|
|
67201
|
-
import { homedir as
|
|
67202
|
-
import { randomBytes as
|
|
67893
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
67894
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
|
|
67895
|
+
import { homedir as homedir3 } from "node:os";
|
|
67896
|
+
import { randomBytes as randomBytes3, createHmac } from "node:crypto";
|
|
67203
67897
|
function bindingDigest(secret, salt) {
|
|
67204
67898
|
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
67205
67899
|
}
|
|
@@ -67220,7 +67914,7 @@ function createFileStatePersistence(opts) {
|
|
|
67220
67914
|
const secret = opts.boundTo;
|
|
67221
67915
|
return {
|
|
67222
67916
|
load() {
|
|
67223
|
-
if (!
|
|
67917
|
+
if (!existsSync2(filePath))
|
|
67224
67918
|
return null;
|
|
67225
67919
|
try {
|
|
67226
67920
|
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
@@ -67250,16 +67944,16 @@ function createFileStatePersistence(opts) {
|
|
|
67250
67944
|
}
|
|
67251
67945
|
},
|
|
67252
67946
|
save(state) {
|
|
67253
|
-
const dir =
|
|
67254
|
-
const tmp = `${filePath}.tmp-${
|
|
67255
|
-
const dirExisted =
|
|
67947
|
+
const dir = dirname2(filePath);
|
|
67948
|
+
const tmp = `${filePath}.tmp-${randomBytes3(6).toString("hex")}`;
|
|
67949
|
+
const dirExisted = existsSync2(dir);
|
|
67256
67950
|
try {
|
|
67257
67951
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
67258
67952
|
if (!dirExisted)
|
|
67259
67953
|
chmodSync(dir, 448);
|
|
67260
67954
|
let boundTo;
|
|
67261
67955
|
if (secret !== void 0) {
|
|
67262
|
-
const salt =
|
|
67956
|
+
const salt = randomBytes3(16).toString("hex");
|
|
67263
67957
|
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
67264
67958
|
}
|
|
67265
67959
|
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
@@ -67269,7 +67963,7 @@ function createFileStatePersistence(opts) {
|
|
|
67269
67963
|
chmodSync(filePath, 384);
|
|
67270
67964
|
} catch (err) {
|
|
67271
67965
|
try {
|
|
67272
|
-
if (
|
|
67966
|
+
if (existsSync2(tmp))
|
|
67273
67967
|
unlinkSync(tmp);
|
|
67274
67968
|
} catch {
|
|
67275
67969
|
}
|
|
@@ -67278,7 +67972,7 @@ function createFileStatePersistence(opts) {
|
|
|
67278
67972
|
},
|
|
67279
67973
|
clear() {
|
|
67280
67974
|
try {
|
|
67281
|
-
if (
|
|
67975
|
+
if (existsSync2(filePath))
|
|
67282
67976
|
unlinkSync(filePath);
|
|
67283
67977
|
} catch {
|
|
67284
67978
|
}
|
|
@@ -67286,11 +67980,11 @@ function createFileStatePersistence(opts) {
|
|
|
67286
67980
|
};
|
|
67287
67981
|
}
|
|
67288
67982
|
function homeOf(env) {
|
|
67289
|
-
return readEnvVar("HOME", { env }) ??
|
|
67983
|
+
return readEnvVar("HOME", { env }) ?? homedir3();
|
|
67290
67984
|
}
|
|
67291
67985
|
function resolveStateDir(opts = {}) {
|
|
67292
67986
|
const env = opts.env;
|
|
67293
|
-
return
|
|
67987
|
+
return join4(readEnvVar("MCP_DATA_DIR", { env }) ?? homeOf(env), opts.subdir ?? "");
|
|
67294
67988
|
}
|
|
67295
67989
|
function resolveStateFile(opts) {
|
|
67296
67990
|
const override = opts.envVar !== void 0 ? readEnvVar(opts.envVar, { env: opts.env }) : void 0;
|
|
@@ -67298,13 +67992,13 @@ function resolveStateFile(opts) {
|
|
|
67298
67992
|
if (override === "~")
|
|
67299
67993
|
return homeOf(opts.env);
|
|
67300
67994
|
if (override.startsWith("~/"))
|
|
67301
|
-
return
|
|
67302
|
-
return
|
|
67995
|
+
return join4(homeOf(opts.env), override.slice(2));
|
|
67996
|
+
return resolve3(override);
|
|
67303
67997
|
}
|
|
67304
67998
|
const dirOpts = { env: opts.env };
|
|
67305
67999
|
if (opts.subdir !== void 0)
|
|
67306
68000
|
dirOpts.subdir = opts.subdir;
|
|
67307
|
-
return
|
|
68001
|
+
return join4(resolveStateDir(dirOpts), opts.fileName);
|
|
67308
68002
|
}
|
|
67309
68003
|
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
67310
68004
|
function isPersistedCookieSession(raw) {
|
|
@@ -67594,7 +68288,8 @@ function reportCacheWriteFailure(err) {
|
|
|
67594
68288
|
}
|
|
67595
68289
|
|
|
67596
68290
|
// src/client.ts
|
|
67597
|
-
import { dirname as
|
|
68291
|
+
import { dirname as dirname3, isAbsolute as isAbsolute5, join as join5, resolve as resolve4 } from "path";
|
|
68292
|
+
import { homedir as homedir4 } from "os";
|
|
67598
68293
|
|
|
67599
68294
|
// src/session-login.ts
|
|
67600
68295
|
var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36";
|
|
@@ -67692,6 +68387,8 @@ var CanvasClient = class {
|
|
|
67692
68387
|
* what keeps the path alive once the browser cookie lapses.
|
|
67693
68388
|
*/
|
|
67694
68389
|
refreshSession;
|
|
68390
|
+
/** Explicit download root; when unset, resolved per call (see {@link downloadRoot}). */
|
|
68391
|
+
outputDir;
|
|
67695
68392
|
auth;
|
|
67696
68393
|
/** Lazily-built shared refresh_token exchanger (oauth mode only). */
|
|
67697
68394
|
oauthRefresh = null;
|
|
@@ -67707,6 +68404,7 @@ var CanvasClient = class {
|
|
|
67707
68404
|
*/
|
|
67708
68405
|
constructor(account, opts = {}) {
|
|
67709
68406
|
this.account = account;
|
|
68407
|
+
this.outputDir = opts.outputDir;
|
|
67710
68408
|
this.sessionLoginFn = opts.sessionLogin ?? sessionLogin;
|
|
67711
68409
|
this.refreshSession = opts.refreshSession ?? null;
|
|
67712
68410
|
this.auth = new CookieSessionManager({
|
|
@@ -67729,7 +68427,7 @@ var CanvasClient = class {
|
|
|
67729
68427
|
const res = await this.doRawRequest(path, opts);
|
|
67730
68428
|
const text = await res.text();
|
|
67731
68429
|
if (opts.responseType === "text") return text;
|
|
67732
|
-
return
|
|
68430
|
+
return parseJsonBody2(text) ?? null;
|
|
67733
68431
|
}
|
|
67734
68432
|
async requestPaginated(path, opts = {}) {
|
|
67735
68433
|
const perPage = opts.perPage ?? DEFAULT_PER_PAGE;
|
|
@@ -67739,7 +68437,7 @@ var CanvasClient = class {
|
|
|
67739
68437
|
for (let page = 0; page < maxPages; page++) {
|
|
67740
68438
|
const res = await this.doRawRequest(url4, opts);
|
|
67741
68439
|
const text = await res.text();
|
|
67742
|
-
const parsed =
|
|
68440
|
+
const parsed = parseJsonBody2(text) ?? [];
|
|
67743
68441
|
for (const item of parsed) out.push(item);
|
|
67744
68442
|
const linkHeader = res.headers.get("link");
|
|
67745
68443
|
const next = linkHeader ? parseLinkHeader(linkHeader).next : void 0;
|
|
@@ -67748,33 +68446,84 @@ var CanvasClient = class {
|
|
|
67748
68446
|
}
|
|
67749
68447
|
return out;
|
|
67750
68448
|
}
|
|
68449
|
+
/**
|
|
68450
|
+
* Download a Canvas file to disk.
|
|
68451
|
+
*
|
|
68452
|
+
* Both inputs arrive from tool arguments, which a prompt injection in
|
|
68453
|
+
* classmate/teacher-authored Canvas content can steer, so both are pinned
|
|
68454
|
+
* BEFORE any credential is attached:
|
|
68455
|
+
* - `path` must resolve to the configured Canvas origin (https, same host —
|
|
68456
|
+
* the `https://canvas@evil/` userinfo trick parses to a foreign host and is
|
|
68457
|
+
* refused) and name a `/files/<id>` resource. Only the initial request is
|
|
68458
|
+
* pinned: Canvas legitimately redirects to its file store, and fetch
|
|
68459
|
+
* strips Authorization/Cookie on a cross-origin redirect.
|
|
68460
|
+
* - `destinationPath` must lie inside the download root (see
|
|
68461
|
+
* {@link downloadRoot}), checked through symlinks; a relative path is
|
|
68462
|
+
* resolved against that root.
|
|
68463
|
+
*/
|
|
67751
68464
|
async download(path, destinationPath, opts = {}) {
|
|
68465
|
+
const url4 = this.pinDownloadUrl(path);
|
|
68466
|
+
const dest = this.confineDestination(destinationPath);
|
|
67752
68467
|
let destStat = null;
|
|
67753
68468
|
try {
|
|
67754
|
-
destStat = await stat(
|
|
68469
|
+
destStat = await stat(dest);
|
|
67755
68470
|
} catch {
|
|
67756
68471
|
}
|
|
67757
68472
|
if (destStat?.isDirectory()) throw new InvalidPathError(destinationPath);
|
|
67758
68473
|
if (destStat && !opts.overwrite) throw new FileExistsError(destinationPath);
|
|
67759
|
-
const parent =
|
|
68474
|
+
const parent = dirname3(dest);
|
|
67760
68475
|
try {
|
|
67761
68476
|
await stat(parent);
|
|
67762
68477
|
} catch {
|
|
67763
68478
|
throw new ParentDirectoryMissingError(parent);
|
|
67764
68479
|
}
|
|
67765
|
-
const url4 = /^https?:\/\//i.test(path) ? path : `${this.account.baseUrl}${path}`;
|
|
67766
68480
|
const res = await this.authedFetch(url4, {});
|
|
67767
68481
|
if (res.status === 401) throw new TokenExpiredError(this.account.mode);
|
|
67768
68482
|
if (res.status === 404) throw new Error(`Canvas download 404 for ${path}`);
|
|
67769
68483
|
if (!res.ok) throw new Error(`Canvas download ${res.status} for ${path}`);
|
|
67770
68484
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
67771
|
-
await writeFile3(
|
|
68485
|
+
await writeFile3(dest, buf);
|
|
67772
68486
|
return {
|
|
67773
|
-
path:
|
|
68487
|
+
path: dest,
|
|
67774
68488
|
bytes: buf.byteLength,
|
|
67775
68489
|
contentType: res.headers.get("content-type") ?? "application/octet-stream"
|
|
67776
68490
|
};
|
|
67777
68491
|
}
|
|
68492
|
+
/**
|
|
68493
|
+
* The directory downloads are confined to: the constructor's `outputDir`,
|
|
68494
|
+
* else `CANVAS_OUTPUT_DIR`, else `~/Downloads`. Read per call so an env
|
|
68495
|
+
* change needs no client rebuild.
|
|
68496
|
+
*/
|
|
68497
|
+
downloadRoot() {
|
|
68498
|
+
return expandPath(this.outputDir ?? readEnvVar("CANVAS_OUTPUT_DIR") ?? join5(homedir4(), "Downloads"));
|
|
68499
|
+
}
|
|
68500
|
+
confineDestination(destinationPath) {
|
|
68501
|
+
const root = this.downloadRoot();
|
|
68502
|
+
const candidate = destinationPath.startsWith("~") || isAbsolute5(destinationPath) ? expandPath(destinationPath) : resolve4(root, destinationPath);
|
|
68503
|
+
try {
|
|
68504
|
+
assertPathWithinRoots(candidate, [root]);
|
|
68505
|
+
} catch {
|
|
68506
|
+
throw new DownloadDestinationRejectedError(destinationPath, root);
|
|
68507
|
+
}
|
|
68508
|
+
return candidate;
|
|
68509
|
+
}
|
|
68510
|
+
pinDownloadUrl(path) {
|
|
68511
|
+
const raw = /^[a-z][a-z0-9+.-]*:/i.test(path) ? path : `${this.account.baseUrl}${path}`;
|
|
68512
|
+
let parsed;
|
|
68513
|
+
try {
|
|
68514
|
+
parsed = new URL(raw);
|
|
68515
|
+
} catch {
|
|
68516
|
+
throw new DownloadUrlRejectedError(path, "not a valid URL");
|
|
68517
|
+
}
|
|
68518
|
+
const origin = new URL(this.account.baseUrl).origin;
|
|
68519
|
+
if (parsed.protocol !== "https:" || parsed.origin !== origin || parsed.username || parsed.password) {
|
|
68520
|
+
throw new DownloadUrlRejectedError(path, `must be an https URL on ${origin}`);
|
|
68521
|
+
}
|
|
68522
|
+
if (!/\/files\/[^/]+/.test(parsed.pathname)) {
|
|
68523
|
+
throw new DownloadUrlRejectedError(path, "must name a Canvas file (a /files/<id> path)");
|
|
68524
|
+
}
|
|
68525
|
+
return parsed.href;
|
|
68526
|
+
}
|
|
67778
68527
|
async doRawRequest(path, opts) {
|
|
67779
68528
|
const url4 = /^https?:\/\//i.test(path) ? path : `${this.account.baseUrl}${path}`;
|
|
67780
68529
|
const accept = opts.responseType === "text" ? "text/html, text/plain, */*" : "application/json+canvas-string-ids, application/json";
|
|
@@ -67880,15 +68629,15 @@ var CanvasClient = class {
|
|
|
67880
68629
|
};
|
|
67881
68630
|
}
|
|
67882
68631
|
};
|
|
67883
|
-
function
|
|
68632
|
+
function parseJsonBody2(text) {
|
|
67884
68633
|
if (!text) return null;
|
|
67885
68634
|
const stripped = text.replace(/^while\(1\);/, "");
|
|
67886
68635
|
return JSON.parse(stripped);
|
|
67887
68636
|
}
|
|
67888
68637
|
function injectPerPage(pathOrUrl, perPage) {
|
|
67889
68638
|
if (/[?&]per_page=/.test(pathOrUrl)) return pathOrUrl;
|
|
67890
|
-
const
|
|
67891
|
-
return `${pathOrUrl}${
|
|
68639
|
+
const sep2 = pathOrUrl.includes("?") ? "&" : "?";
|
|
68640
|
+
return `${pathOrUrl}${sep2}per_page=${perPage}`;
|
|
67892
68641
|
}
|
|
67893
68642
|
var TokenExpiredError = class extends Error {
|
|
67894
68643
|
constructor(mode, detail) {
|
|
@@ -67909,6 +68658,26 @@ var CanvasUnreachableError = class extends Error {
|
|
|
67909
68658
|
}
|
|
67910
68659
|
status;
|
|
67911
68660
|
};
|
|
68661
|
+
var DownloadUrlRejectedError = class extends Error {
|
|
68662
|
+
constructor(url4, reason) {
|
|
68663
|
+
super(`DownloadUrlRejected: ${reason}; use the \`url\` from canvas_list_course_files. Got: ${url4}`);
|
|
68664
|
+
this.url = url4;
|
|
68665
|
+
this.name = "DownloadUrlRejectedError";
|
|
68666
|
+
}
|
|
68667
|
+
url;
|
|
68668
|
+
};
|
|
68669
|
+
var DownloadDestinationRejectedError = class extends Error {
|
|
68670
|
+
constructor(path, root) {
|
|
68671
|
+
super(
|
|
68672
|
+
`DownloadDestinationRejected: destinationPath must be inside ${root} (set CANVAS_OUTPUT_DIR to change it). Got: ${path}`
|
|
68673
|
+
);
|
|
68674
|
+
this.path = path;
|
|
68675
|
+
this.root = root;
|
|
68676
|
+
this.name = "DownloadDestinationRejectedError";
|
|
68677
|
+
}
|
|
68678
|
+
path;
|
|
68679
|
+
root;
|
|
68680
|
+
};
|
|
67912
68681
|
var InvalidPathError = class extends Error {
|
|
67913
68682
|
constructor(path) {
|
|
67914
68683
|
super(`InvalidPath: destinationPath must be a filename, not a directory: ${path}`);
|
|
@@ -68315,8 +69084,8 @@ var listArgs4 = external_exports.object({
|
|
|
68315
69084
|
contentTypes: external_exports.array(external_exports.string()).optional()
|
|
68316
69085
|
});
|
|
68317
69086
|
var downloadArgs = external_exports.object({
|
|
68318
|
-
url: external_exports.string().describe("The url field returned by canvas_list_course_files (
|
|
68319
|
-
destinationPath: external_exports.string().describe("
|
|
69087
|
+
url: external_exports.string().describe("The url field returned by canvas_list_course_files (an https /files/ URL on the configured Canvas host; anything else is refused)."),
|
|
69088
|
+
destinationPath: external_exports.string().describe("Where to write the file. Must be inside the download directory (CANVAS_OUTPUT_DIR, default ~/Downloads); a relative path is resolved against it."),
|
|
68320
69089
|
overwrite: external_exports.boolean().optional()
|
|
68321
69090
|
});
|
|
68322
69091
|
function registerFileTools(server, client) {
|
|
@@ -68334,8 +69103,8 @@ function registerFileTools(server, client) {
|
|
|
68334
69103
|
return textContent(data);
|
|
68335
69104
|
});
|
|
68336
69105
|
server.registerTool("canvas_download_file", {
|
|
68337
|
-
description: "Download a Canvas file to disk. `url` is the absolute URL from canvas_list_course_files; `destinationPath` is required.",
|
|
68338
|
-
annotations: { destructiveHint: true },
|
|
69106
|
+
description: "Download a Canvas file to disk. `url` is the absolute URL from canvas_list_course_files (only file URLs on the configured Canvas host are accepted); `destinationPath` is required and must be inside the download directory (CANVAS_OUTPUT_DIR, default ~/Downloads).",
|
|
69107
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
68339
69108
|
inputSchema: downloadArgs
|
|
68340
69109
|
}, async (rawArgs) => {
|
|
68341
69110
|
const args = downloadArgs.parse(rawArgs);
|
|
@@ -68347,8 +69116,8 @@ function registerFileTools(server, client) {
|
|
|
68347
69116
|
}
|
|
68348
69117
|
|
|
68349
69118
|
// src/index.ts
|
|
68350
|
-
var __dirname =
|
|
68351
|
-
await loadDotenvSafely({ path:
|
|
69119
|
+
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
69120
|
+
await loadDotenvSafely({ path: join6(__dirname, "..", ".env"), override: false });
|
|
68352
69121
|
var resolved = null;
|
|
68353
69122
|
var configError = null;
|
|
68354
69123
|
try {
|
|
@@ -68358,7 +69127,7 @@ try {
|
|
|
68358
69127
|
}
|
|
68359
69128
|
await runMcp({
|
|
68360
69129
|
name: "canvas",
|
|
68361
|
-
version: "2.1.
|
|
69130
|
+
version: "2.1.3",
|
|
68362
69131
|
// x-release-please-version
|
|
68363
69132
|
tools: [
|
|
68364
69133
|
// Outside the `if (resolved)` on purpose: an unconfigured server must
|