infinitecampus-mcp 2.3.1 → 2.3.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/dist/auth.js +6 -18
- package/dist/bundle.js +1123 -290
- package/dist/client.js +71 -46
- package/dist/config.js +5 -11
- package/dist/index.js +45 -36
- package/dist/tools/_shared.js +8 -2
- package/dist/tools/messages.js +7 -17
- package/package.json +5 -3
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -3112,6 +3112,9 @@ var require_utils = __commonJS({
|
|
|
3112
3112
|
"use strict";
|
|
3113
3113
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3114
3114
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3115
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3116
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3117
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3115
3118
|
function stringArrayToHexStripped(input) {
|
|
3116
3119
|
let acc = "";
|
|
3117
3120
|
let code = 0;
|
|
@@ -3304,27 +3307,77 @@ var require_utils = __commonJS({
|
|
|
3304
3307
|
}
|
|
3305
3308
|
return output.join("");
|
|
3306
3309
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3310
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3311
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3312
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3313
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3314
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3315
|
+
re.lastIndex = 0;
|
|
3316
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3317
|
+
}
|
|
3318
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3319
|
+
if (input.indexOf("%") === -1) {
|
|
3320
|
+
return input;
|
|
3317
3321
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3322
|
+
let output = "";
|
|
3323
|
+
for (let i = 0; i < input.length; i++) {
|
|
3324
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3325
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3326
|
+
if (isHexPair(hex3)) {
|
|
3327
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3328
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3329
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3330
|
+
output += decoded;
|
|
3331
|
+
} else {
|
|
3332
|
+
output += "%" + normalizedHex;
|
|
3333
|
+
}
|
|
3334
|
+
i += 2;
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
output += input[i];
|
|
3320
3339
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3340
|
+
return output;
|
|
3341
|
+
}
|
|
3342
|
+
function normalizePathEncoding(input) {
|
|
3343
|
+
let output = "";
|
|
3344
|
+
for (let i = 0; i < input.length; i++) {
|
|
3345
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3346
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3347
|
+
if (isHexPair(hex3)) {
|
|
3348
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3349
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3350
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3351
|
+
output += decoded;
|
|
3352
|
+
} else {
|
|
3353
|
+
output += "%" + normalizedHex;
|
|
3354
|
+
}
|
|
3355
|
+
i += 2;
|
|
3356
|
+
continue;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
if (isPathCharacter(input[i])) {
|
|
3360
|
+
output += input[i];
|
|
3361
|
+
} else {
|
|
3362
|
+
output += escape(input[i]);
|
|
3363
|
+
}
|
|
3323
3364
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3365
|
+
return output;
|
|
3366
|
+
}
|
|
3367
|
+
function escapePreservingEscapes(input) {
|
|
3368
|
+
let output = "";
|
|
3369
|
+
for (let i = 0; i < input.length; i++) {
|
|
3370
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3371
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3372
|
+
if (isHexPair(hex3)) {
|
|
3373
|
+
output += "%" + hex3.toUpperCase();
|
|
3374
|
+
i += 2;
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
output += escape(input[i]);
|
|
3326
3379
|
}
|
|
3327
|
-
return
|
|
3380
|
+
return output;
|
|
3328
3381
|
}
|
|
3329
3382
|
function recomposeAuthority(component) {
|
|
3330
3383
|
const uriTokens = [];
|
|
@@ -3339,7 +3392,7 @@ var require_utils = __commonJS({
|
|
|
3339
3392
|
if (ipV6res.isIPV6 === true) {
|
|
3340
3393
|
host = `[${ipV6res.escapedHost}]`;
|
|
3341
3394
|
} else {
|
|
3342
|
-
host =
|
|
3395
|
+
host = reescapeHostDelimiters(host, false);
|
|
3343
3396
|
}
|
|
3344
3397
|
}
|
|
3345
3398
|
uriTokens.push(host);
|
|
@@ -3353,7 +3406,10 @@ var require_utils = __commonJS({
|
|
|
3353
3406
|
module.exports = {
|
|
3354
3407
|
nonSimpleDomain,
|
|
3355
3408
|
recomposeAuthority,
|
|
3356
|
-
|
|
3409
|
+
reescapeHostDelimiters,
|
|
3410
|
+
normalizePercentEncoding,
|
|
3411
|
+
normalizePathEncoding,
|
|
3412
|
+
escapePreservingEscapes,
|
|
3357
3413
|
removeDotSegments,
|
|
3358
3414
|
isIPv4,
|
|
3359
3415
|
isUUID,
|
|
@@ -3577,12 +3633,12 @@ var require_schemes = __commonJS({
|
|
|
3577
3633
|
var require_fast_uri = __commonJS({
|
|
3578
3634
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3579
3635
|
"use strict";
|
|
3580
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3636
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3581
3637
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3582
3638
|
function normalize(uri, options) {
|
|
3583
3639
|
if (typeof uri === "string") {
|
|
3584
3640
|
uri = /** @type {T} */
|
|
3585
|
-
|
|
3641
|
+
normalizeString(uri, options);
|
|
3586
3642
|
} else if (typeof uri === "object") {
|
|
3587
3643
|
uri = /** @type {T} */
|
|
3588
3644
|
parse3(serialize(uri, options), options);
|
|
@@ -3649,19 +3705,9 @@ var require_fast_uri = __commonJS({
|
|
|
3649
3705
|
return target;
|
|
3650
3706
|
}
|
|
3651
3707
|
function equal(uriA, uriB, options) {
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
} else if (typeof uriA === "object") {
|
|
3656
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3657
|
-
}
|
|
3658
|
-
if (typeof uriB === "string") {
|
|
3659
|
-
uriB = unescape(uriB);
|
|
3660
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
3661
|
-
} else if (typeof uriB === "object") {
|
|
3662
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3663
|
-
}
|
|
3664
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3708
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3709
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3710
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3665
3711
|
}
|
|
3666
3712
|
function serialize(cmpts, opts) {
|
|
3667
3713
|
const component = {
|
|
@@ -3686,12 +3732,12 @@ var require_fast_uri = __commonJS({
|
|
|
3686
3732
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3687
3733
|
if (component.path !== void 0) {
|
|
3688
3734
|
if (!options.skipEscape) {
|
|
3689
|
-
component.path =
|
|
3735
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3690
3736
|
if (component.scheme !== void 0) {
|
|
3691
3737
|
component.path = component.path.split("%3A").join(":");
|
|
3692
3738
|
}
|
|
3693
3739
|
} else {
|
|
3694
|
-
component.path =
|
|
3740
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3695
3741
|
}
|
|
3696
3742
|
}
|
|
3697
3743
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3726,7 +3772,16 @@ var require_fast_uri = __commonJS({
|
|
|
3726
3772
|
return uriTokens.join("");
|
|
3727
3773
|
}
|
|
3728
3774
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3729
|
-
function
|
|
3775
|
+
function getParseError(parsed, matches) {
|
|
3776
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3777
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3778
|
+
}
|
|
3779
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3780
|
+
return "URI port is malformed.";
|
|
3781
|
+
}
|
|
3782
|
+
return void 0;
|
|
3783
|
+
}
|
|
3784
|
+
function parseWithStatus(uri, opts) {
|
|
3730
3785
|
const options = Object.assign({}, opts);
|
|
3731
3786
|
const parsed = {
|
|
3732
3787
|
scheme: void 0,
|
|
@@ -3737,6 +3792,7 @@ var require_fast_uri = __commonJS({
|
|
|
3737
3792
|
query: void 0,
|
|
3738
3793
|
fragment: void 0
|
|
3739
3794
|
};
|
|
3795
|
+
let malformedAuthorityOrPort = false;
|
|
3740
3796
|
let isIP = false;
|
|
3741
3797
|
if (options.reference === "suffix") {
|
|
3742
3798
|
if (options.scheme) {
|
|
@@ -3757,6 +3813,11 @@ var require_fast_uri = __commonJS({
|
|
|
3757
3813
|
if (isNaN(parsed.port)) {
|
|
3758
3814
|
parsed.port = matches[5];
|
|
3759
3815
|
}
|
|
3816
|
+
const parseError = getParseError(parsed, matches);
|
|
3817
|
+
if (parseError !== void 0) {
|
|
3818
|
+
parsed.error = parsed.error || parseError;
|
|
3819
|
+
malformedAuthorityOrPort = true;
|
|
3820
|
+
}
|
|
3760
3821
|
if (parsed.host) {
|
|
3761
3822
|
const ipv4result = isIPv4(parsed.host);
|
|
3762
3823
|
if (ipv4result === false) {
|
|
@@ -3795,14 +3856,18 @@ var require_fast_uri = __commonJS({
|
|
|
3795
3856
|
parsed.scheme = unescape(parsed.scheme);
|
|
3796
3857
|
}
|
|
3797
3858
|
if (parsed.host !== void 0) {
|
|
3798
|
-
parsed.host = unescape(parsed.host);
|
|
3859
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3799
3860
|
}
|
|
3800
3861
|
}
|
|
3801
3862
|
if (parsed.path) {
|
|
3802
|
-
parsed.path =
|
|
3863
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3803
3864
|
}
|
|
3804
3865
|
if (parsed.fragment) {
|
|
3805
|
-
|
|
3866
|
+
try {
|
|
3867
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3868
|
+
} catch {
|
|
3869
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3870
|
+
}
|
|
3806
3871
|
}
|
|
3807
3872
|
}
|
|
3808
3873
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3811,7 +3876,29 @@ var require_fast_uri = __commonJS({
|
|
|
3811
3876
|
} else {
|
|
3812
3877
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3813
3878
|
}
|
|
3814
|
-
return parsed;
|
|
3879
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3880
|
+
}
|
|
3881
|
+
function parse3(uri, opts) {
|
|
3882
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3883
|
+
}
|
|
3884
|
+
function normalizeString(uri, opts) {
|
|
3885
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3886
|
+
}
|
|
3887
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3888
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3889
|
+
return {
|
|
3890
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3891
|
+
malformedAuthorityOrPort
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
function normalizeComparableURI(uri, opts) {
|
|
3895
|
+
if (typeof uri === "string") {
|
|
3896
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3897
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
3898
|
+
}
|
|
3899
|
+
if (typeof uri === "object") {
|
|
3900
|
+
return serialize(uri, opts);
|
|
3901
|
+
}
|
|
3815
3902
|
}
|
|
3816
3903
|
var fastUri = {
|
|
3817
3904
|
SCHEMES,
|
|
@@ -7308,14 +7395,14 @@ var require_permessage_deflate = __commonJS({
|
|
|
7308
7395
|
}
|
|
7309
7396
|
};
|
|
7310
7397
|
module.exports = PerMessageDeflate2;
|
|
7311
|
-
function deflateOnData(
|
|
7312
|
-
this[kBuffers].push(
|
|
7313
|
-
this[kTotalLength] +=
|
|
7398
|
+
function deflateOnData(chunk2) {
|
|
7399
|
+
this[kBuffers].push(chunk2);
|
|
7400
|
+
this[kTotalLength] += chunk2.length;
|
|
7314
7401
|
}
|
|
7315
|
-
function inflateOnData(
|
|
7316
|
-
this[kTotalLength] +=
|
|
7402
|
+
function inflateOnData(chunk2) {
|
|
7403
|
+
this[kTotalLength] += chunk2.length;
|
|
7317
7404
|
if (this[kPerMessageDeflate]._maxPayload < 1 || this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload) {
|
|
7318
|
-
this[kBuffers].push(
|
|
7405
|
+
this[kBuffers].push(chunk2);
|
|
7319
7406
|
return;
|
|
7320
7407
|
}
|
|
7321
7408
|
this[kError] = new RangeError("Max payload size exceeded");
|
|
@@ -7615,7 +7702,7 @@ var require_receiver = __commonJS({
|
|
|
7615
7702
|
* @param {Function} cb Callback
|
|
7616
7703
|
* @private
|
|
7617
7704
|
*/
|
|
7618
|
-
_write(
|
|
7705
|
+
_write(chunk2, encoding, cb) {
|
|
7619
7706
|
if (this._opcode === 8 && this._state == GET_INFO) return cb();
|
|
7620
7707
|
if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) {
|
|
7621
7708
|
cb(
|
|
@@ -7629,8 +7716,8 @@ var require_receiver = __commonJS({
|
|
|
7629
7716
|
);
|
|
7630
7717
|
return;
|
|
7631
7718
|
}
|
|
7632
|
-
this._bufferedBytes +=
|
|
7633
|
-
this._buffers.push(
|
|
7719
|
+
this._bufferedBytes += chunk2.length;
|
|
7720
|
+
this._buffers.push(chunk2);
|
|
7634
7721
|
this.startLoop(cb);
|
|
7635
7722
|
}
|
|
7636
7723
|
/**
|
|
@@ -9904,8 +9991,8 @@ var require_websocket = __commonJS({
|
|
|
9904
9991
|
this.removeListener("end", socketOnEnd);
|
|
9905
9992
|
websocket._readyState = WebSocket2.CLOSING;
|
|
9906
9993
|
if (!this._readableState.endEmitted && !websocket._closeFrameReceived && !websocket._receiver._writableState.errorEmitted && this._readableState.length !== 0) {
|
|
9907
|
-
const
|
|
9908
|
-
websocket._receiver.write(
|
|
9994
|
+
const chunk2 = this.read(this._readableState.length);
|
|
9995
|
+
websocket._receiver.write(chunk2);
|
|
9909
9996
|
}
|
|
9910
9997
|
websocket._receiver.end();
|
|
9911
9998
|
this[kWebSocket] = void 0;
|
|
@@ -9917,8 +10004,8 @@ var require_websocket = __commonJS({
|
|
|
9917
10004
|
websocket._receiver.on("finish", receiverOnFinish);
|
|
9918
10005
|
}
|
|
9919
10006
|
}
|
|
9920
|
-
function socketOnData(
|
|
9921
|
-
if (!this[kWebSocket]._receiver.write(
|
|
10007
|
+
function socketOnData(chunk2) {
|
|
10008
|
+
if (!this[kWebSocket]._receiver.write(chunk2)) {
|
|
9922
10009
|
this.pause();
|
|
9923
10010
|
}
|
|
9924
10011
|
}
|
|
@@ -10021,14 +10108,14 @@ var require_stream = __commonJS({
|
|
|
10021
10108
|
duplex._read = function() {
|
|
10022
10109
|
if (ws.isPaused) ws.resume();
|
|
10023
10110
|
};
|
|
10024
|
-
duplex._write = function(
|
|
10111
|
+
duplex._write = function(chunk2, encoding, callback) {
|
|
10025
10112
|
if (ws.readyState === ws.CONNECTING) {
|
|
10026
10113
|
ws.once("open", function open() {
|
|
10027
|
-
duplex._write(
|
|
10114
|
+
duplex._write(chunk2, encoding, callback);
|
|
10028
10115
|
});
|
|
10029
10116
|
return;
|
|
10030
10117
|
}
|
|
10031
|
-
ws.send(
|
|
10118
|
+
ws.send(chunk2, callback);
|
|
10032
10119
|
};
|
|
10033
10120
|
duplex.on("end", duplexOnEnd);
|
|
10034
10121
|
duplex.on("error", duplexOnError);
|
|
@@ -10255,10 +10342,10 @@ var require_websocket_server = __commonJS({
|
|
|
10255
10342
|
process.nextTick(emitClose, this);
|
|
10256
10343
|
}
|
|
10257
10344
|
} else {
|
|
10258
|
-
const
|
|
10345
|
+
const server = this._server;
|
|
10259
10346
|
this._removeListeners();
|
|
10260
10347
|
this._removeListeners = this._server = null;
|
|
10261
|
-
|
|
10348
|
+
server.close(() => {
|
|
10262
10349
|
emitClose(this);
|
|
10263
10350
|
});
|
|
10264
10351
|
}
|
|
@@ -10443,17 +10530,17 @@ var require_websocket_server = __commonJS({
|
|
|
10443
10530
|
}
|
|
10444
10531
|
};
|
|
10445
10532
|
module.exports = WebSocketServer2;
|
|
10446
|
-
function addListeners(
|
|
10447
|
-
for (const event of Object.keys(map2))
|
|
10533
|
+
function addListeners(server, map2) {
|
|
10534
|
+
for (const event of Object.keys(map2)) server.on(event, map2[event]);
|
|
10448
10535
|
return function removeListeners() {
|
|
10449
10536
|
for (const event of Object.keys(map2)) {
|
|
10450
|
-
|
|
10537
|
+
server.removeListener(event, map2[event]);
|
|
10451
10538
|
}
|
|
10452
10539
|
};
|
|
10453
10540
|
}
|
|
10454
|
-
function emitClose(
|
|
10455
|
-
|
|
10456
|
-
|
|
10541
|
+
function emitClose(server) {
|
|
10542
|
+
server._state = CLOSED;
|
|
10543
|
+
server.emit("close");
|
|
10457
10544
|
}
|
|
10458
10545
|
function socketOnError() {
|
|
10459
10546
|
this.destroy();
|
|
@@ -10472,11 +10559,11 @@ var require_websocket_server = __commonJS({
|
|
|
10472
10559
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
10473
10560
|
);
|
|
10474
10561
|
}
|
|
10475
|
-
function abortHandshakeOrEmitwsClientError(
|
|
10476
|
-
if (
|
|
10562
|
+
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message, headers) {
|
|
10563
|
+
if (server.listenerCount("wsClientError")) {
|
|
10477
10564
|
const err = new Error(message);
|
|
10478
10565
|
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
|
|
10479
|
-
|
|
10566
|
+
server.emit("wsClientError", err, socket, req);
|
|
10480
10567
|
} else {
|
|
10481
10568
|
abortHandshake(socket, code, message, headers);
|
|
10482
10569
|
}
|
|
@@ -32106,11 +32193,11 @@ var Protocol = class {
|
|
|
32106
32193
|
*
|
|
32107
32194
|
* The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
|
|
32108
32195
|
*/
|
|
32109
|
-
async connect(
|
|
32196
|
+
async connect(transport) {
|
|
32110
32197
|
if (this._transport) {
|
|
32111
32198
|
throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");
|
|
32112
32199
|
}
|
|
32113
|
-
this._transport =
|
|
32200
|
+
this._transport = transport;
|
|
32114
32201
|
const _onclose = this.transport?.onclose;
|
|
32115
32202
|
this._transport.onclose = () => {
|
|
32116
32203
|
_onclose?.();
|
|
@@ -33700,8 +33787,8 @@ var McpServer = class {
|
|
|
33700
33787
|
*
|
|
33701
33788
|
* The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
|
|
33702
33789
|
*/
|
|
33703
|
-
async connect(
|
|
33704
|
-
return await this.server.connect(
|
|
33790
|
+
async connect(transport) {
|
|
33791
|
+
return await this.server.connect(transport);
|
|
33705
33792
|
}
|
|
33706
33793
|
/**
|
|
33707
33794
|
* Closes the connection.
|
|
@@ -34464,8 +34551,8 @@ import process3 from "node:process";
|
|
|
34464
34551
|
|
|
34465
34552
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
34466
34553
|
var ReadBuffer = class {
|
|
34467
|
-
append(
|
|
34468
|
-
this._buffer = this._buffer ? Buffer.concat([this._buffer,
|
|
34554
|
+
append(chunk2) {
|
|
34555
|
+
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
|
|
34469
34556
|
}
|
|
34470
34557
|
readMessage() {
|
|
34471
34558
|
if (!this._buffer) {
|
|
@@ -34497,8 +34584,8 @@ var StdioServerTransport = class {
|
|
|
34497
34584
|
this._stdout = _stdout;
|
|
34498
34585
|
this._readBuffer = new ReadBuffer();
|
|
34499
34586
|
this._started = false;
|
|
34500
|
-
this._ondata = (
|
|
34501
|
-
this._readBuffer.append(
|
|
34587
|
+
this._ondata = (chunk2) => {
|
|
34588
|
+
this._readBuffer.append(chunk2);
|
|
34502
34589
|
this.processReadBuffer();
|
|
34503
34590
|
};
|
|
34504
34591
|
this._onerror = (error51) => {
|
|
@@ -34551,6 +34638,150 @@ var StdioServerTransport = class {
|
|
|
34551
34638
|
}
|
|
34552
34639
|
};
|
|
34553
34640
|
|
|
34641
|
+
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34642
|
+
async function createMcpServer(opts) {
|
|
34643
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34644
|
+
if (opts.banner !== void 0) {
|
|
34645
|
+
console.error(opts.banner);
|
|
34646
|
+
}
|
|
34647
|
+
const deps = opts.deps;
|
|
34648
|
+
for (const register of opts.tools) {
|
|
34649
|
+
await register(server, deps);
|
|
34650
|
+
}
|
|
34651
|
+
return server;
|
|
34652
|
+
}
|
|
34653
|
+
function withGracefulShutdown(server, opts = {}) {
|
|
34654
|
+
const shouldExit = opts.exit ?? true;
|
|
34655
|
+
let shuttingDown = false;
|
|
34656
|
+
const handler = (signal) => {
|
|
34657
|
+
if (shuttingDown)
|
|
34658
|
+
return;
|
|
34659
|
+
shuttingDown = true;
|
|
34660
|
+
void (async () => {
|
|
34661
|
+
try {
|
|
34662
|
+
if (opts.onSignal)
|
|
34663
|
+
await opts.onSignal(signal);
|
|
34664
|
+
await server.close();
|
|
34665
|
+
} catch (err) {
|
|
34666
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34667
|
+
} finally {
|
|
34668
|
+
if (shouldExit)
|
|
34669
|
+
process.exit(0);
|
|
34670
|
+
}
|
|
34671
|
+
})();
|
|
34672
|
+
};
|
|
34673
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
34674
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
34675
|
+
}
|
|
34676
|
+
async function runMcp(opts) {
|
|
34677
|
+
const server = await createMcpServer(opts);
|
|
34678
|
+
const shutdown = opts.shutdown ?? true;
|
|
34679
|
+
if (shutdown !== false) {
|
|
34680
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
34681
|
+
}
|
|
34682
|
+
const spec = opts.transport ?? "stdio";
|
|
34683
|
+
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
34684
|
+
await server.connect(transport);
|
|
34685
|
+
return server;
|
|
34686
|
+
}
|
|
34687
|
+
|
|
34688
|
+
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34689
|
+
function textResult(data) {
|
|
34690
|
+
return {
|
|
34691
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34692
|
+
};
|
|
34693
|
+
}
|
|
34694
|
+
|
|
34695
|
+
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34696
|
+
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
34697
|
+
function readEnvVar(key, opts = {}) {
|
|
34698
|
+
const env = opts.env ?? process.env;
|
|
34699
|
+
const raw = env[key];
|
|
34700
|
+
if (typeof raw === "string") {
|
|
34701
|
+
const trimmed = raw.trim();
|
|
34702
|
+
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
34703
|
+
return trimmed;
|
|
34704
|
+
}
|
|
34705
|
+
}
|
|
34706
|
+
return opts.default;
|
|
34707
|
+
}
|
|
34708
|
+
var TRUE_TOKENS = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
34709
|
+
var FALSE_TOKENS = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
|
|
34710
|
+
function parseBoolEnv(key, opts = {}) {
|
|
34711
|
+
const fallback = opts.default ?? false;
|
|
34712
|
+
const raw = readEnvVar(key, { env: opts.env });
|
|
34713
|
+
if (raw === void 0)
|
|
34714
|
+
return fallback;
|
|
34715
|
+
const token = raw.toLowerCase();
|
|
34716
|
+
if (TRUE_TOKENS.has(token))
|
|
34717
|
+
return true;
|
|
34718
|
+
if (FALSE_TOKENS.has(token))
|
|
34719
|
+
return false;
|
|
34720
|
+
return fallback;
|
|
34721
|
+
}
|
|
34722
|
+
async function loadDotenvSafely(opts = {}) {
|
|
34723
|
+
try {
|
|
34724
|
+
const mod = await import(
|
|
34725
|
+
/* @vite-ignore */
|
|
34726
|
+
"dotenv"
|
|
34727
|
+
);
|
|
34728
|
+
const result = mod.config({
|
|
34729
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
34730
|
+
override: opts.override ?? false,
|
|
34731
|
+
quiet: true
|
|
34732
|
+
});
|
|
34733
|
+
return result.error === void 0;
|
|
34734
|
+
} catch {
|
|
34735
|
+
return false;
|
|
34736
|
+
}
|
|
34737
|
+
}
|
|
34738
|
+
|
|
34739
|
+
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
34740
|
+
var MAX_AGE_ZERO_RE = /(?:^|;)\s*Max-Age\s*=\s*0\s*(?:;|$)/i;
|
|
34741
|
+
var EXPIRES_EPOCH_RE = /(?:^|;)\s*Expires\s*=\s*Thu,\s*01\s*Jan\s*1970/i;
|
|
34742
|
+
function parseCookieJar(setCookieHeaders) {
|
|
34743
|
+
const entries = setCookieHeaders == null ? [] : Array.isArray(setCookieHeaders) ? setCookieHeaders : splitSetCookie(setCookieHeaders);
|
|
34744
|
+
const jar = /* @__PURE__ */ new Map();
|
|
34745
|
+
for (const entry of entries) {
|
|
34746
|
+
if (MAX_AGE_ZERO_RE.test(entry) || EXPIRES_EPOCH_RE.test(entry))
|
|
34747
|
+
continue;
|
|
34748
|
+
const nameValue = (entry.split(";")[0] ?? "").trim();
|
|
34749
|
+
const eqIdx = nameValue.indexOf("=");
|
|
34750
|
+
if (eqIdx < 1)
|
|
34751
|
+
continue;
|
|
34752
|
+
const name = nameValue.slice(0, eqIdx).trim();
|
|
34753
|
+
const value = nameValue.slice(eqIdx + 1).trim();
|
|
34754
|
+
if (!value)
|
|
34755
|
+
continue;
|
|
34756
|
+
jar.set(name, value);
|
|
34757
|
+
}
|
|
34758
|
+
const cookies = {};
|
|
34759
|
+
for (const [k, v] of jar)
|
|
34760
|
+
cookies[k] = v;
|
|
34761
|
+
const cookieHeader = [...jar.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
|
34762
|
+
return { cookies, cookieHeader };
|
|
34763
|
+
}
|
|
34764
|
+
function splitSetCookie(header) {
|
|
34765
|
+
return header.split(/,(?=\s*[^;,\s]+\s*=)/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
34766
|
+
}
|
|
34767
|
+
|
|
34768
|
+
// node_modules/@chrischall/mcp-utils/dist/zod/index.js
|
|
34769
|
+
var PositiveInt = external_exports.number().int().positive();
|
|
34770
|
+
var NonNegInt = external_exports.number().int().nonnegative();
|
|
34771
|
+
var NonEmptyString = external_exports.string().min(1);
|
|
34772
|
+
var IsoDate = external_exports.iso.date();
|
|
34773
|
+
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
34774
|
+
var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
34775
|
+
var schemaConfirm = external_exports.boolean().optional().describe("Must be true to proceed. Without this, the tool returns a preview.");
|
|
34776
|
+
var paginationSchema = {
|
|
34777
|
+
offset: NonNegInt.default(0).describe("Number of items to skip (0-based)."),
|
|
34778
|
+
limit: external_exports.number().int().min(1).max(200).default(50).describe("Maximum number of items to return (1-200).")
|
|
34779
|
+
};
|
|
34780
|
+
var pageSchema = {
|
|
34781
|
+
page_num: PositiveInt.default(1).describe("1-based page number."),
|
|
34782
|
+
page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
|
|
34783
|
+
};
|
|
34784
|
+
|
|
34554
34785
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
34555
34786
|
var PROTOCOL_VERSION = 2;
|
|
34556
34787
|
var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
@@ -34560,7 +34791,9 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
34560
34791
|
"read_local_storage",
|
|
34561
34792
|
"read_session_storage",
|
|
34562
34793
|
"capture_request_header",
|
|
34563
|
-
"
|
|
34794
|
+
"capture_redirect",
|
|
34795
|
+
"read_indexed_db",
|
|
34796
|
+
"download"
|
|
34564
34797
|
]);
|
|
34565
34798
|
|
|
34566
34799
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
@@ -34707,6 +34940,16 @@ function assertScopeKeyArray(value, label) {
|
|
|
34707
34940
|
seen.add(k);
|
|
34708
34941
|
}
|
|
34709
34942
|
}
|
|
34943
|
+
var CAPTURE_PATH_RE = /^\/[A-Za-z0-9._~%\-/]*\*?$/;
|
|
34944
|
+
function hostMatchesAnyDomain(host, domains) {
|
|
34945
|
+
const h = host.toLowerCase();
|
|
34946
|
+
for (const d of domains) {
|
|
34947
|
+
const dom = d.toLowerCase();
|
|
34948
|
+
if (h === dom || h.endsWith("." + dom))
|
|
34949
|
+
return true;
|
|
34950
|
+
}
|
|
34951
|
+
return false;
|
|
34952
|
+
}
|
|
34710
34953
|
function assertCaptureHeadersArray(value, label) {
|
|
34711
34954
|
if (!Array.isArray(value)) {
|
|
34712
34955
|
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
@@ -34715,14 +34958,25 @@ function assertCaptureHeadersArray(value, label) {
|
|
|
34715
34958
|
for (let i = 0; i < value.length; i++) {
|
|
34716
34959
|
const entry = value[i];
|
|
34717
34960
|
assertObject(entry, `${label}[${i}]`);
|
|
34718
|
-
if (entry.
|
|
34719
|
-
throw new ProtocolError(`${label}[${i}].
|
|
34961
|
+
if (entry.host === void 0) {
|
|
34962
|
+
throw new ProtocolError(`${label}[${i}].host: missing`);
|
|
34720
34963
|
}
|
|
34721
34964
|
if (entry.headerName === void 0) {
|
|
34722
34965
|
throw new ProtocolError(`${label}[${i}].headerName: missing`);
|
|
34723
34966
|
}
|
|
34724
|
-
if (typeof entry.
|
|
34725
|
-
throw new ProtocolError(`${label}[${i}].
|
|
34967
|
+
if (typeof entry.host !== "string") {
|
|
34968
|
+
throw new ProtocolError(`${label}[${i}].host: expected string, got ${typeof entry.host}`);
|
|
34969
|
+
}
|
|
34970
|
+
if (!HOSTNAME_RE.test(entry.host)) {
|
|
34971
|
+
throw new ProtocolError(`${label}[${i}].host: invalid hostname ${JSON.stringify(entry.host)}`);
|
|
34972
|
+
}
|
|
34973
|
+
if (entry.path !== void 0) {
|
|
34974
|
+
if (typeof entry.path !== "string") {
|
|
34975
|
+
throw new ProtocolError(`${label}[${i}].path: expected string, got ${typeof entry.path}`);
|
|
34976
|
+
}
|
|
34977
|
+
if (!CAPTURE_PATH_RE.test(entry.path)) {
|
|
34978
|
+
throw new ProtocolError(`${label}[${i}].path: must start with '/' ${JSON.stringify(entry.path)}`);
|
|
34979
|
+
}
|
|
34726
34980
|
}
|
|
34727
34981
|
if (typeof entry.headerName !== "string") {
|
|
34728
34982
|
throw new ProtocolError(`${label}[${i}].headerName: expected string, got ${typeof entry.headerName}`);
|
|
@@ -34730,19 +34984,29 @@ function assertCaptureHeadersArray(value, label) {
|
|
|
34730
34984
|
if (!HEADER_NAME_RE.test(entry.headerName)) {
|
|
34731
34985
|
throw new ProtocolError(`${label}[${i}].headerName: invalid name ${JSON.stringify(entry.headerName)}`);
|
|
34732
34986
|
}
|
|
34733
|
-
|
|
34734
|
-
const key = `${entry.
|
|
34987
|
+
const normalizedPath = entry.path ?? "/*";
|
|
34988
|
+
const key = `${entry.host}\0${normalizedPath}\0${entry.headerName}`;
|
|
34735
34989
|
if (seen.has(key)) {
|
|
34736
|
-
throw new ProtocolError(`${label}: duplicate ${JSON.stringify({
|
|
34990
|
+
throw new ProtocolError(`${label}: duplicate ${JSON.stringify({ host: entry.host, path: normalizedPath, headerName: entry.headerName })}`);
|
|
34737
34991
|
}
|
|
34738
34992
|
seen.add(key);
|
|
34739
34993
|
for (const k of Object.keys(entry)) {
|
|
34740
|
-
if (k !== "
|
|
34994
|
+
if (k !== "host" && k !== "path" && k !== "headerName") {
|
|
34741
34995
|
throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
34742
34996
|
}
|
|
34743
34997
|
}
|
|
34744
34998
|
}
|
|
34745
34999
|
}
|
|
35000
|
+
function validateCaptureHeaderDecls(value, domains, label = "captureHeaders") {
|
|
35001
|
+
assertCaptureHeadersArray(value, label);
|
|
35002
|
+
const arr = value;
|
|
35003
|
+
for (let i = 0; i < arr.length; i++) {
|
|
35004
|
+
const host = arr[i].host;
|
|
35005
|
+
if (!hostMatchesAnyDomain(host, domains)) {
|
|
35006
|
+
throw new ProtocolError(`${label}[${i}].host: ${JSON.stringify(host)} is not a declared domain or subdomain of [${domains.join(", ")}]`);
|
|
35007
|
+
}
|
|
35008
|
+
}
|
|
35009
|
+
}
|
|
34746
35010
|
function assertStoragePointersArray(value, label, declaredKeys) {
|
|
34747
35011
|
if (!Array.isArray(value)) {
|
|
34748
35012
|
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
@@ -34830,23 +35094,6 @@ function assertIndexedDbScopesArray(value, label) {
|
|
|
34830
35094
|
}
|
|
34831
35095
|
}
|
|
34832
35096
|
}
|
|
34833
|
-
function assertCaptureUrlPattern(pattern, label) {
|
|
34834
|
-
if (!pattern.startsWith("https://")) {
|
|
34835
|
-
throw new ProtocolError(`${label}: must start with https:// (got ${JSON.stringify(pattern)})`);
|
|
34836
|
-
}
|
|
34837
|
-
const afterScheme = pattern.slice("https://".length);
|
|
34838
|
-
const slash = afterScheme.indexOf("/");
|
|
34839
|
-
const host = slash === -1 ? afterScheme : afterScheme.slice(0, slash);
|
|
34840
|
-
if (host.length === 0) {
|
|
34841
|
-
throw new ProtocolError(`${label}: missing host (got ${JSON.stringify(pattern)})`);
|
|
34842
|
-
}
|
|
34843
|
-
if (host.includes("*")) {
|
|
34844
|
-
throw new ProtocolError(`${label}: wildcards not permitted in host (got ${JSON.stringify(pattern)})`);
|
|
34845
|
-
}
|
|
34846
|
-
if (!HOSTNAME_RE.test(host)) {
|
|
34847
|
-
throw new ProtocolError(`${label}: invalid host ${JSON.stringify(host)} in ${JSON.stringify(pattern)}`);
|
|
34848
|
-
}
|
|
34849
|
-
}
|
|
34850
35097
|
function validateFrame(raw) {
|
|
34851
35098
|
assertObject(raw, "frame");
|
|
34852
35099
|
const t = raw.type;
|
|
@@ -34911,7 +35158,7 @@ function validateHello(raw) {
|
|
|
34911
35158
|
assertScopeKeyArray(raw.sessionStorageKeys, "hello.sessionStorageKeys");
|
|
34912
35159
|
}
|
|
34913
35160
|
if (raw.captureHeaders !== void 0) {
|
|
34914
|
-
|
|
35161
|
+
validateCaptureHeaderDecls(raw.captureHeaders, raw.domains, "hello.captureHeaders");
|
|
34915
35162
|
}
|
|
34916
35163
|
if (raw.indexedDbScopes !== void 0) {
|
|
34917
35164
|
assertIndexedDbScopesArray(raw.indexedDbScopes, "hello.indexedDbScopes");
|
|
@@ -35079,24 +35326,58 @@ function validateInnerRequest(raw) {
|
|
|
35079
35326
|
}
|
|
35080
35327
|
if (raw.op === "capture_request_header") {
|
|
35081
35328
|
assertObject(raw.init, "inner.init");
|
|
35082
|
-
if (raw.init.
|
|
35083
|
-
throw new ProtocolError("inner.init.
|
|
35329
|
+
if (raw.init.host === void 0) {
|
|
35330
|
+
throw new ProtocolError("inner.init.host: missing");
|
|
35084
35331
|
}
|
|
35085
35332
|
if (raw.init.headerName === void 0) {
|
|
35086
35333
|
throw new ProtocolError("inner.init.headerName: missing");
|
|
35087
35334
|
}
|
|
35088
|
-
assertString(raw.init.
|
|
35335
|
+
assertString(raw.init.host, "inner.init.host");
|
|
35336
|
+
if (!HOSTNAME_RE.test(raw.init.host)) {
|
|
35337
|
+
throw new ProtocolError(`inner.init.host: invalid hostname ${JSON.stringify(raw.init.host)}`);
|
|
35338
|
+
}
|
|
35339
|
+
if (raw.init.path !== void 0) {
|
|
35340
|
+
assertString(raw.init.path, "inner.init.path");
|
|
35341
|
+
if (!CAPTURE_PATH_RE.test(raw.init.path)) {
|
|
35342
|
+
throw new ProtocolError(`inner.init.path: must start with '/' ${JSON.stringify(raw.init.path)}`);
|
|
35343
|
+
}
|
|
35344
|
+
}
|
|
35089
35345
|
assertString(raw.init.headerName, "inner.init.headerName");
|
|
35090
35346
|
if (raw.init.timeoutMs !== void 0) {
|
|
35091
35347
|
assertPositiveInt(raw.init.timeoutMs, "inner.init.timeoutMs");
|
|
35092
35348
|
}
|
|
35093
35349
|
for (const k of Object.keys(raw.init)) {
|
|
35094
|
-
if (k !== "
|
|
35350
|
+
if (k !== "host" && k !== "path" && k !== "headerName" && k !== "timeoutMs") {
|
|
35095
35351
|
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on capture_request_header`);
|
|
35096
35352
|
}
|
|
35097
35353
|
}
|
|
35098
35354
|
return raw;
|
|
35099
35355
|
}
|
|
35356
|
+
if (raw.op === "capture_redirect") {
|
|
35357
|
+
assertObject(raw.init, "inner.init");
|
|
35358
|
+
if (raw.init.host === void 0) {
|
|
35359
|
+
throw new ProtocolError("inner.init.host: missing");
|
|
35360
|
+
}
|
|
35361
|
+
assertString(raw.init.host, "inner.init.host");
|
|
35362
|
+
if (!HOSTNAME_RE.test(raw.init.host)) {
|
|
35363
|
+
throw new ProtocolError(`inner.init.host: invalid hostname ${JSON.stringify(raw.init.host)}`);
|
|
35364
|
+
}
|
|
35365
|
+
if (raw.init.path !== void 0) {
|
|
35366
|
+
assertString(raw.init.path, "inner.init.path");
|
|
35367
|
+
if (!CAPTURE_PATH_RE.test(raw.init.path)) {
|
|
35368
|
+
throw new ProtocolError(`inner.init.path: must start with '/' ${JSON.stringify(raw.init.path)}`);
|
|
35369
|
+
}
|
|
35370
|
+
}
|
|
35371
|
+
if (raw.init.timeoutMs !== void 0) {
|
|
35372
|
+
assertPositiveInt(raw.init.timeoutMs, "inner.init.timeoutMs");
|
|
35373
|
+
}
|
|
35374
|
+
for (const k of Object.keys(raw.init)) {
|
|
35375
|
+
if (k !== "host" && k !== "path" && k !== "timeoutMs") {
|
|
35376
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on capture_redirect`);
|
|
35377
|
+
}
|
|
35378
|
+
}
|
|
35379
|
+
return raw;
|
|
35380
|
+
}
|
|
35100
35381
|
if (raw.op === "read_indexed_db") {
|
|
35101
35382
|
assertObject(raw.init, "inner.init");
|
|
35102
35383
|
if (raw.init.origin === void 0)
|
|
@@ -35122,7 +35403,32 @@ function validateInnerRequest(raw) {
|
|
|
35122
35403
|
}
|
|
35123
35404
|
return raw;
|
|
35124
35405
|
}
|
|
35125
|
-
|
|
35406
|
+
if (raw.op === "download") {
|
|
35407
|
+
assertObject(raw.init, "inner.init");
|
|
35408
|
+
if (raw.init.url === void 0) {
|
|
35409
|
+
throw new ProtocolError("inner.init.url: missing");
|
|
35410
|
+
}
|
|
35411
|
+
assertHttpUrl(raw.init.url, "inner.init.url");
|
|
35412
|
+
if (raw.init.filename !== void 0) {
|
|
35413
|
+
assertString(raw.init.filename, "inner.init.filename");
|
|
35414
|
+
if (/^([/\\]|[A-Za-z]:)/.test(raw.init.filename)) {
|
|
35415
|
+
throw new ProtocolError(`inner.init.filename: must be relative ${JSON.stringify(raw.init.filename)}`);
|
|
35416
|
+
}
|
|
35417
|
+
if (raw.init.filename.split(/[/\\]/).includes("..")) {
|
|
35418
|
+
throw new ProtocolError(`inner.init.filename: must not contain '..' ${JSON.stringify(raw.init.filename)}`);
|
|
35419
|
+
}
|
|
35420
|
+
}
|
|
35421
|
+
if (raw.init.timeoutMs !== void 0) {
|
|
35422
|
+
assertPositiveInt(raw.init.timeoutMs, "inner.init.timeoutMs");
|
|
35423
|
+
}
|
|
35424
|
+
for (const k of Object.keys(raw.init)) {
|
|
35425
|
+
if (k !== "url" && k !== "filename" && k !== "timeoutMs") {
|
|
35426
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on download`);
|
|
35427
|
+
}
|
|
35428
|
+
}
|
|
35429
|
+
return raw;
|
|
35430
|
+
}
|
|
35431
|
+
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "download"; got ${JSON.stringify(raw.op)}`);
|
|
35126
35432
|
}
|
|
35127
35433
|
function assertNonEmptyKeyArray(value, label) {
|
|
35128
35434
|
if (!Array.isArray(value)) {
|
|
@@ -35193,6 +35499,13 @@ function validateInnerResponse(raw) {
|
|
|
35193
35499
|
assertString(raw.value, "inner.value");
|
|
35194
35500
|
return raw;
|
|
35195
35501
|
}
|
|
35502
|
+
if (op === "capture_redirect") {
|
|
35503
|
+
if (raw.value === void 0) {
|
|
35504
|
+
throw new ProtocolError("inner.value: missing on capture_redirect response");
|
|
35505
|
+
}
|
|
35506
|
+
assertString(raw.value, "inner.value");
|
|
35507
|
+
return raw;
|
|
35508
|
+
}
|
|
35196
35509
|
if (op === "read_indexed_db") {
|
|
35197
35510
|
if (raw.values === void 0) {
|
|
35198
35511
|
throw new ProtocolError("inner.values: missing on read_indexed_db response");
|
|
@@ -35200,6 +35513,25 @@ function validateInnerResponse(raw) {
|
|
|
35200
35513
|
assertObject(raw.values, "inner.values");
|
|
35201
35514
|
return raw;
|
|
35202
35515
|
}
|
|
35516
|
+
if (op === "download") {
|
|
35517
|
+
assertObject(raw.value, "inner.value");
|
|
35518
|
+
assertString(raw.value.path, "inner.value.path");
|
|
35519
|
+
if (typeof raw.value.bytes !== "number" || !Number.isInteger(raw.value.bytes) || raw.value.bytes < 0) {
|
|
35520
|
+
throw new ProtocolError("inner.value.bytes: expected non-negative integer");
|
|
35521
|
+
}
|
|
35522
|
+
if (raw.value.mime !== void 0) {
|
|
35523
|
+
assertString(raw.value.mime, "inner.value.mime");
|
|
35524
|
+
}
|
|
35525
|
+
if (raw.value.finalUrl !== void 0) {
|
|
35526
|
+
assertString(raw.value.finalUrl, "inner.value.finalUrl");
|
|
35527
|
+
}
|
|
35528
|
+
for (const k of Object.keys(raw.value)) {
|
|
35529
|
+
if (k !== "path" && k !== "bytes" && k !== "mime" && k !== "finalUrl") {
|
|
35530
|
+
throw new ProtocolError(`inner.value: unexpected field ${JSON.stringify(k)} on download response`);
|
|
35531
|
+
}
|
|
35532
|
+
}
|
|
35533
|
+
return raw;
|
|
35534
|
+
}
|
|
35203
35535
|
throw new ProtocolError(`inner.op: unknown success-response op ${JSON.stringify(raw.op)}`);
|
|
35204
35536
|
}
|
|
35205
35537
|
if (raw.ok === false) {
|
|
@@ -35407,13 +35739,13 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
35407
35739
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
35408
35740
|
import { createServer, Server as HttpServer } from "node:http";
|
|
35409
35741
|
async function electRole(opts) {
|
|
35410
|
-
const
|
|
35742
|
+
const server = createServer();
|
|
35411
35743
|
return new Promise((resolve, reject) => {
|
|
35412
35744
|
const onError = (e) => {
|
|
35413
|
-
|
|
35745
|
+
server.removeListener("listening", onListening);
|
|
35414
35746
|
if (e.code === "EADDRINUSE") {
|
|
35415
35747
|
try {
|
|
35416
|
-
|
|
35748
|
+
server.close();
|
|
35417
35749
|
} catch {
|
|
35418
35750
|
}
|
|
35419
35751
|
resolve({ role: "peer" });
|
|
@@ -35422,12 +35754,12 @@ async function electRole(opts) {
|
|
|
35422
35754
|
}
|
|
35423
35755
|
};
|
|
35424
35756
|
const onListening = () => {
|
|
35425
|
-
|
|
35426
|
-
resolve({ role: "host", server
|
|
35757
|
+
server.removeListener("error", onError);
|
|
35758
|
+
resolve({ role: "host", server });
|
|
35427
35759
|
};
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35760
|
+
server.once("error", onError);
|
|
35761
|
+
server.once("listening", onListening);
|
|
35762
|
+
server.listen(opts.port, opts.host);
|
|
35431
35763
|
});
|
|
35432
35764
|
}
|
|
35433
35765
|
|
|
@@ -35471,7 +35803,8 @@ async function buildServerHello(opts) {
|
|
|
35471
35803
|
}
|
|
35472
35804
|
if (opts.captureHeaders && opts.captureHeaders.length > 0) {
|
|
35473
35805
|
hello.captureHeaders = opts.captureHeaders.map((d) => ({
|
|
35474
|
-
|
|
35806
|
+
host: d.host,
|
|
35807
|
+
...d.path !== void 0 ? { path: d.path } : {},
|
|
35475
35808
|
headerName: d.headerName
|
|
35476
35809
|
}));
|
|
35477
35810
|
}
|
|
@@ -35709,7 +36042,9 @@ async function startHost(opts) {
|
|
|
35709
36042
|
} catch {
|
|
35710
36043
|
}
|
|
35711
36044
|
}
|
|
35712
|
-
wss.close(() =>
|
|
36045
|
+
wss.close(() => {
|
|
36046
|
+
opts.httpServer.close(() => resolve());
|
|
36047
|
+
});
|
|
35713
36048
|
}),
|
|
35714
36049
|
sendOwnInner: async (inner) => {
|
|
35715
36050
|
const session = await ownSessionReady;
|
|
@@ -35759,6 +36094,7 @@ async function startPeer(opts) {
|
|
|
35759
36094
|
const innerListeners = [];
|
|
35760
36095
|
const renegotiateListeners = [];
|
|
35761
36096
|
const pendingPairListeners = [];
|
|
36097
|
+
const closeListeners = [];
|
|
35762
36098
|
let session = null;
|
|
35763
36099
|
let pendingPairCode = null;
|
|
35764
36100
|
let resolveFirstReady;
|
|
@@ -35808,6 +36144,7 @@ async function startPeer(opts) {
|
|
|
35808
36144
|
ws.on("message", onMessage);
|
|
35809
36145
|
ws.once("close", () => {
|
|
35810
36146
|
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
36147
|
+
closeListeners.forEach((cb) => cb());
|
|
35811
36148
|
});
|
|
35812
36149
|
sessionPromise.catch(() => {
|
|
35813
36150
|
});
|
|
@@ -35830,6 +36167,9 @@ async function startPeer(opts) {
|
|
|
35830
36167
|
pendingPairListeners.push(cb);
|
|
35831
36168
|
},
|
|
35832
36169
|
pendingPairCode: () => pendingPairCode,
|
|
36170
|
+
onClose: (cb) => {
|
|
36171
|
+
closeListeners.push(cb);
|
|
36172
|
+
},
|
|
35833
36173
|
close: () => ws.close()
|
|
35834
36174
|
};
|
|
35835
36175
|
return handle;
|
|
@@ -35912,6 +36252,19 @@ function classifyFetchError(error51) {
|
|
|
35912
36252
|
return "other";
|
|
35913
36253
|
}
|
|
35914
36254
|
|
|
36255
|
+
// node_modules/@fetchproxy/server/dist/classify-bridge-error.js
|
|
36256
|
+
function classifyBridgeError(err) {
|
|
36257
|
+
if (err instanceof FetchproxyTimeoutError)
|
|
36258
|
+
return "timeout";
|
|
36259
|
+
if (err instanceof FetchproxyBridgeDownError)
|
|
36260
|
+
return "bridge_down";
|
|
36261
|
+
if (err instanceof FetchproxyHttpError)
|
|
36262
|
+
return "http";
|
|
36263
|
+
if (err instanceof FetchproxyProtocolError)
|
|
36264
|
+
return "protocol";
|
|
36265
|
+
return "other";
|
|
36266
|
+
}
|
|
36267
|
+
|
|
35915
36268
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
35916
36269
|
var FetchproxyProtocolError = class extends Error {
|
|
35917
36270
|
constructor(message) {
|
|
@@ -35941,7 +36294,7 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
35941
36294
|
const retryAttempted = args.retryAttempted ?? false;
|
|
35942
36295
|
const op = args.op ?? "fetch";
|
|
35943
36296
|
const retryClause = retryAttempted ? `Server already burned a one-shot lazy-revive retry; SW is still down. ` : `Server lazy-revive retry was disabled (bridgeReviveDelayMs unset/0). `;
|
|
35944
|
-
const hint = `the fetchproxy extension's service worker is not responding ("${args.originalError}"). Chrome evicts extension service workers after ~30s idle by default. ${retryClause}
|
|
36297
|
+
const hint = `the fetchproxy extension's service worker is not responding ("${args.originalError}"). Chrome evicts extension service workers after ~30s idle by default. ${retryClause}Make sure a tab for this domain is open, fully loaded, and signed in (the bridge fetches through that tab) \u2014 then retry. If it keeps happening, reload the extension from chrome://extensions and reload the tab.`;
|
|
35945
36298
|
super(`fetchproxy bridge down during ${op}${args.url ? ` (${args.url})` : ""}. ${hint}`);
|
|
35946
36299
|
this.name = "FetchproxyBridgeDownError";
|
|
35947
36300
|
this.originalError = args.originalError;
|
|
@@ -35963,6 +36316,14 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
35963
36316
|
port;
|
|
35964
36317
|
/** 0.8.0+: actual elapsed milliseconds when the timer won the race. */
|
|
35965
36318
|
elapsedMs;
|
|
36319
|
+
/**
|
|
36320
|
+
* 0.11.0+ (#90/#91): true when the server's lazy-revive retry path
|
|
36321
|
+
* fired for this timeout (a cold-start `timeout` symptom followed by
|
|
36322
|
+
* a warm-and-retry that also timed out). False when the retry was
|
|
36323
|
+
* disabled (`bridgeReviveDelayMs` unset/0) so the timeout surfaced on
|
|
36324
|
+
* the first attempt.
|
|
36325
|
+
*/
|
|
36326
|
+
retryAttempted;
|
|
35966
36327
|
constructor(args) {
|
|
35967
36328
|
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms`);
|
|
35968
36329
|
this.name = "FetchproxyTimeoutError";
|
|
@@ -35971,6 +36332,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
35971
36332
|
this.role = args.role ?? null;
|
|
35972
36333
|
this.port = args.port ?? 0;
|
|
35973
36334
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
36335
|
+
this.retryAttempted = args.retryAttempted ?? false;
|
|
35974
36336
|
}
|
|
35975
36337
|
};
|
|
35976
36338
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
@@ -36009,6 +36371,12 @@ var FetchproxyServer = class {
|
|
|
36009
36371
|
opts;
|
|
36010
36372
|
hostHandle = null;
|
|
36011
36373
|
peerHandle = null;
|
|
36374
|
+
// 0.13.0+: true from the start of `close()` until the next `doConnect()`.
|
|
36375
|
+
// Distinguishes an intentional shutdown (whose WS close we must ignore)
|
|
36376
|
+
// from the host process dying (which strands a peer and must trigger
|
|
36377
|
+
// re-election). The WS `close` event fires asynchronously, so this stays
|
|
36378
|
+
// latched across `close()` rather than being reset in its tail.
|
|
36379
|
+
closing = false;
|
|
36012
36380
|
nextRequestId = 1;
|
|
36013
36381
|
// 0.8.0+: process-wide freshness counters surfaced via bridgeHealth().
|
|
36014
36382
|
// Replaces the local copies every downstream MCP was rolling on top
|
|
@@ -36034,8 +36402,12 @@ var FetchproxyServer = class {
|
|
|
36034
36402
|
pendingStorage = /* @__PURE__ */ new Map();
|
|
36035
36403
|
// 0.3.0+: capture-header awaiters resolve a single string.
|
|
36036
36404
|
pendingCapture = /* @__PURE__ */ new Map();
|
|
36405
|
+
// capture_redirect awaiters resolve the captured redirect URL string.
|
|
36406
|
+
pendingRedirect = /* @__PURE__ */ new Map();
|
|
36037
36407
|
// 0.4.0+: read_indexed_db awaiters resolve a JSON-typed values map.
|
|
36038
36408
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
36409
|
+
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
36410
|
+
pendingDownload = /* @__PURE__ */ new Map();
|
|
36039
36411
|
mcpId = null;
|
|
36040
36412
|
identity = null;
|
|
36041
36413
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -36044,6 +36416,25 @@ var FetchproxyServer = class {
|
|
|
36044
36416
|
// for "we're connecting right now" so two parallel first-calls don't
|
|
36045
36417
|
// race the port bind.
|
|
36046
36418
|
connectingPromise = null;
|
|
36419
|
+
// 0.8.1+ (#67): server-initiated keep-alive ping. Active when
|
|
36420
|
+
// `keepAliveIntervalMs` is set AND we've seen recent activity
|
|
36421
|
+
// (fetch/capture success or failure, or markActive()) within
|
|
36422
|
+
// `keepAliveMaxIdleMs`. The interval handle is created lazily on
|
|
36423
|
+
// first activity and torn down on close() / extension disconnect.
|
|
36424
|
+
keepAliveTimer = null;
|
|
36425
|
+
lastActiveAt = null;
|
|
36426
|
+
// 0.10.0+ (#73): observability counters surfaced via
|
|
36427
|
+
// bridgeHealth().keepAlive / .swEviction. Monotonic across the process
|
|
36428
|
+
// lifetime so a downstream healthcheck tool can verify the keep-alive
|
|
36429
|
+
// is actually preventing SW eviction. `lastPingAt` and `totalPings`
|
|
36430
|
+
// are stamped from `startKeepaliveIfIdle`'s tick. `lazyRevive*` and
|
|
36431
|
+
// `lastEvictionDetectedAt` are stamped from the lazy-revive code path
|
|
36432
|
+
// in fetch() / captureRequestHeader().
|
|
36433
|
+
lastPingAt = null;
|
|
36434
|
+
totalPings = 0;
|
|
36435
|
+
lazyReviveAttempts = 0;
|
|
36436
|
+
lazyReviveSuccesses = 0;
|
|
36437
|
+
lastEvictionDetectedAt = null;
|
|
36047
36438
|
constructor(opts) {
|
|
36048
36439
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
36049
36440
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
@@ -36062,6 +36453,14 @@ var FetchproxyServer = class {
|
|
|
36062
36453
|
}
|
|
36063
36454
|
capabilities = [...opts.capabilities];
|
|
36064
36455
|
}
|
|
36456
|
+
if (opts.captureHeaders !== void 0) {
|
|
36457
|
+
try {
|
|
36458
|
+
validateCaptureHeaderDecls(opts.captureHeaders, opts.domains);
|
|
36459
|
+
} catch (err) {
|
|
36460
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36461
|
+
throw new Error("FetchproxyServer: invalid captureHeaders \u2014 " + message);
|
|
36462
|
+
}
|
|
36463
|
+
}
|
|
36065
36464
|
this.opts = {
|
|
36066
36465
|
port: opts.port ?? 37149,
|
|
36067
36466
|
host: opts.host ?? "127.0.0.1",
|
|
@@ -36073,7 +36472,8 @@ var FetchproxyServer = class {
|
|
|
36073
36472
|
localStorageKeys: [...opts.localStorageKeys ?? []],
|
|
36074
36473
|
sessionStorageKeys: [...opts.sessionStorageKeys ?? []],
|
|
36075
36474
|
captureHeaders: (opts.captureHeaders ?? []).map((d) => ({
|
|
36076
|
-
|
|
36475
|
+
host: d.host,
|
|
36476
|
+
...d.path !== void 0 ? { path: d.path } : {},
|
|
36077
36477
|
headerName: d.headerName
|
|
36078
36478
|
})),
|
|
36079
36479
|
indexedDbScopes: (opts.indexedDbScopes ?? []).map((d) => ({
|
|
@@ -36096,6 +36496,22 @@ var FetchproxyServer = class {
|
|
|
36096
36496
|
// the legacy hang-forever / fail-once-on-SW-eviction behavior.
|
|
36097
36497
|
fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
|
|
36098
36498
|
bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
|
|
36499
|
+
// 0.10.0+ (#72): keep-alive defaults to 25s — round-3 #71 cohort
|
|
36500
|
+
// wave showed every Pattern A consumer was opting into this same
|
|
36501
|
+
// value. Pass `0` to disable; the existing `<= 0` guards in
|
|
36502
|
+
// `startKeepaliveIfIdle` / `noteActivityForKeepalive` honour that.
|
|
36503
|
+
//
|
|
36504
|
+
// #90 (P1-1): tightened to 20s. 25s left only ~5s of slack under
|
|
36505
|
+
// Chrome's ~30s SW-eviction window — slack that timer drift, a
|
|
36506
|
+
// busy host event loop (CPU-bound response parsing between calls),
|
|
36507
|
+
// and the ping's own round-trip latency routinely ate, so the SW
|
|
36508
|
+
// evicted before the next ping landed and the next call cold-
|
|
36509
|
+
// started. 20s restores real margin. (The extension
|
|
36510
|
+
// `chrome.alarms` backstop is clamped by Chrome to a 30s minimum
|
|
36511
|
+
// period, firing *at* the edge — it can't rescue a sub-30s race;
|
|
36512
|
+
// the server ping is the real defense.)
|
|
36513
|
+
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
36514
|
+
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
36099
36515
|
identityDir: opts.identityDir,
|
|
36100
36516
|
onPairCode: opts.onPairCode
|
|
36101
36517
|
};
|
|
@@ -36175,6 +36591,7 @@ var FetchproxyServer = class {
|
|
|
36175
36591
|
async doConnect() {
|
|
36176
36592
|
const identity = this.identity;
|
|
36177
36593
|
const mcpId = this.mcpId;
|
|
36594
|
+
this.closing = false;
|
|
36178
36595
|
const el = await electRole({ host: this.opts.host, port: this.opts.port });
|
|
36179
36596
|
if (el.role === "host") {
|
|
36180
36597
|
this.role = "host";
|
|
@@ -36196,7 +36613,10 @@ var FetchproxyServer = class {
|
|
|
36196
36613
|
onPairCode: this.opts.onPairCode
|
|
36197
36614
|
});
|
|
36198
36615
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
36199
|
-
this.hostHandle.onExtensionDisconnect(() =>
|
|
36616
|
+
this.hostHandle.onExtensionDisconnect(() => {
|
|
36617
|
+
this.stopKeepalive();
|
|
36618
|
+
this.rejectAllPending();
|
|
36619
|
+
});
|
|
36200
36620
|
this.hostHandle.onPendingPair((code) => {
|
|
36201
36621
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36202
36622
|
});
|
|
@@ -36220,7 +36640,10 @@ var FetchproxyServer = class {
|
|
|
36220
36640
|
sessionStoragePointers: this.opts.sessionStoragePointers
|
|
36221
36641
|
});
|
|
36222
36642
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
36223
|
-
this.peerHandle.onRenegotiate(() =>
|
|
36643
|
+
this.peerHandle.onRenegotiate(() => {
|
|
36644
|
+
this.stopKeepalive();
|
|
36645
|
+
this.rejectAllPending();
|
|
36646
|
+
});
|
|
36224
36647
|
this.peerHandle.onPendingPair((code) => {
|
|
36225
36648
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36226
36649
|
});
|
|
@@ -36228,6 +36651,14 @@ var FetchproxyServer = class {
|
|
|
36228
36651
|
const cb = this.opts.onPairCode;
|
|
36229
36652
|
this.peerHandle.onPendingPair((code) => cb(code));
|
|
36230
36653
|
}
|
|
36654
|
+
this.peerHandle.onClose(() => {
|
|
36655
|
+
if (this.closing || this.peerHandle === null)
|
|
36656
|
+
return;
|
|
36657
|
+
this.stopKeepalive();
|
|
36658
|
+
this.rejectAllPending();
|
|
36659
|
+
this.peerHandle = null;
|
|
36660
|
+
this.role = null;
|
|
36661
|
+
});
|
|
36231
36662
|
}
|
|
36232
36663
|
}
|
|
36233
36664
|
pairingErrorMessage(code) {
|
|
@@ -36261,13 +36692,18 @@ var FetchproxyServer = class {
|
|
|
36261
36692
|
}
|
|
36262
36693
|
const first = await this._fetchOnceWithTimeout(init);
|
|
36263
36694
|
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
36264
|
-
|
|
36265
|
-
if (
|
|
36695
|
+
const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
|
|
36696
|
+
if (isColdStartSymptom) {
|
|
36697
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
36698
|
+
}
|
|
36699
|
+
if (isColdStartSymptom && reviveMs !== void 0 && reviveMs > 0) {
|
|
36700
|
+
this.lazyReviveAttempts += 1;
|
|
36266
36701
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36267
36702
|
const second = await this._fetchOnceWithTimeout(init);
|
|
36268
|
-
if (second.ok)
|
|
36703
|
+
if (second.ok) {
|
|
36704
|
+
this.lazyReviveSuccesses += 1;
|
|
36269
36705
|
this.recordSuccess();
|
|
36270
|
-
else
|
|
36706
|
+
} else
|
|
36271
36707
|
this.recordFailure(`${second.kind ?? "other"}: ${second.error}`);
|
|
36272
36708
|
return { ...second, retryAttempted: true };
|
|
36273
36709
|
}
|
|
@@ -36289,6 +36725,9 @@ var FetchproxyServer = class {
|
|
|
36289
36725
|
* call (addresses #23 ask 4).
|
|
36290
36726
|
*/
|
|
36291
36727
|
bridgeHealth() {
|
|
36728
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36729
|
+
const maxIdleMs = this.opts.keepAliveMaxIdleMs;
|
|
36730
|
+
const idleSinceMs = this.lastActiveAt === null ? null : Date.now() - this.lastActiveAt;
|
|
36292
36731
|
return {
|
|
36293
36732
|
role: this.role,
|
|
36294
36733
|
port: this.opts.port,
|
|
@@ -36299,17 +36738,85 @@ var FetchproxyServer = class {
|
|
|
36299
36738
|
lastFailureAt: this.lastFailureAt,
|
|
36300
36739
|
lastFailureReason: this.lastFailureReason,
|
|
36301
36740
|
consecutiveFailures: this.consecutiveFailures,
|
|
36302
|
-
lastExtensionMessageAt: this.lastExtensionMessageAt
|
|
36741
|
+
lastExtensionMessageAt: this.lastExtensionMessageAt,
|
|
36742
|
+
keepAlive: {
|
|
36743
|
+
enabled: intervalMs > 0,
|
|
36744
|
+
intervalMs,
|
|
36745
|
+
maxIdleMs,
|
|
36746
|
+
lastPingAt: this.lastPingAt,
|
|
36747
|
+
totalPings: this.totalPings,
|
|
36748
|
+
idleSinceMs
|
|
36749
|
+
},
|
|
36750
|
+
swEviction: {
|
|
36751
|
+
lazyReviveAttempts: this.lazyReviveAttempts,
|
|
36752
|
+
lazyReviveSuccesses: this.lazyReviveSuccesses,
|
|
36753
|
+
lastEvictionDetectedAt: this.lastEvictionDetectedAt
|
|
36754
|
+
}
|
|
36303
36755
|
};
|
|
36304
36756
|
}
|
|
36305
36757
|
recordSuccess() {
|
|
36306
36758
|
this.lastSuccessAt = Date.now();
|
|
36307
36759
|
this.consecutiveFailures = 0;
|
|
36760
|
+
this.noteActivityForKeepalive();
|
|
36308
36761
|
}
|
|
36309
36762
|
recordFailure(reason) {
|
|
36310
36763
|
this.lastFailureAt = Date.now();
|
|
36311
36764
|
this.lastFailureReason = reason;
|
|
36312
36765
|
this.consecutiveFailures += 1;
|
|
36766
|
+
this.noteActivityForKeepalive();
|
|
36767
|
+
}
|
|
36768
|
+
/**
|
|
36769
|
+
* 0.8.1+ (#67): caller-side hint that work is happening or about to
|
|
36770
|
+
* happen — bumps the keep-alive idle gate so the server keeps pinging
|
|
36771
|
+
* the extension. Useful for MCPs that do a chain of side-effectful
|
|
36772
|
+
* work between bridge calls and don't want the SW to evict in the
|
|
36773
|
+
* gap (e.g. server-side parsing of a previous response that takes
|
|
36774
|
+
* tens of seconds). No-op when `keepAliveIntervalMs` is `0`.
|
|
36775
|
+
*/
|
|
36776
|
+
markActive() {
|
|
36777
|
+
this.noteActivityForKeepalive();
|
|
36778
|
+
}
|
|
36779
|
+
noteActivityForKeepalive() {
|
|
36780
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36781
|
+
if (intervalMs <= 0)
|
|
36782
|
+
return;
|
|
36783
|
+
this.lastActiveAt = Date.now();
|
|
36784
|
+
this.startKeepaliveIfIdle();
|
|
36785
|
+
}
|
|
36786
|
+
startKeepaliveIfIdle() {
|
|
36787
|
+
if (this.keepAliveTimer !== null)
|
|
36788
|
+
return;
|
|
36789
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36790
|
+
if (intervalMs <= 0)
|
|
36791
|
+
return;
|
|
36792
|
+
this.keepAliveTimer = setInterval(() => {
|
|
36793
|
+
const now = Date.now();
|
|
36794
|
+
if (this.lastActiveAt === null || now - this.lastActiveAt > this.opts.keepAliveMaxIdleMs) {
|
|
36795
|
+
this.stopKeepalive();
|
|
36796
|
+
return;
|
|
36797
|
+
}
|
|
36798
|
+
this.totalPings += 1;
|
|
36799
|
+
this.lastPingAt = now;
|
|
36800
|
+
void this.sendKeepalivePing();
|
|
36801
|
+
}, intervalMs);
|
|
36802
|
+
}
|
|
36803
|
+
async sendKeepalivePing() {
|
|
36804
|
+
try {
|
|
36805
|
+
const inner = { type: "ping" };
|
|
36806
|
+
if (this.hostHandle) {
|
|
36807
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
36808
|
+
} else if (this.peerHandle) {
|
|
36809
|
+
await this.peerHandle.sendInner(inner);
|
|
36810
|
+
}
|
|
36811
|
+
} catch (e) {
|
|
36812
|
+
console.error("[fetchproxy] keepalive ping send failed:", e);
|
|
36813
|
+
}
|
|
36814
|
+
}
|
|
36815
|
+
stopKeepalive() {
|
|
36816
|
+
if (this.keepAliveTimer !== null) {
|
|
36817
|
+
clearInterval(this.keepAliveTimer);
|
|
36818
|
+
this.keepAliveTimer = null;
|
|
36819
|
+
}
|
|
36313
36820
|
}
|
|
36314
36821
|
/**
|
|
36315
36822
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
@@ -36368,7 +36875,8 @@ var FetchproxyServer = class {
|
|
|
36368
36875
|
timeoutMs: this.opts.fetchTimeoutMs ?? 0,
|
|
36369
36876
|
role: this.role,
|
|
36370
36877
|
port: this.opts.port,
|
|
36371
|
-
elapsedMs: result.elapsedMs
|
|
36878
|
+
elapsedMs: result.elapsedMs,
|
|
36879
|
+
retryAttempted
|
|
36372
36880
|
});
|
|
36373
36881
|
}
|
|
36374
36882
|
if (result.kind === "content_script_unreachable") {
|
|
@@ -36499,6 +37007,108 @@ var FetchproxyServer = class {
|
|
|
36499
37007
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
36500
37008
|
return response.body;
|
|
36501
37009
|
}
|
|
37010
|
+
/**
|
|
37011
|
+
* 0.11.0+: method-generic JSON convenience helper. Generalizes the
|
|
37012
|
+
* `fetchJson<T>(path, { method, headers, body })` that
|
|
37013
|
+
* zillow/redfin/compass/homes hand-rolled char-for-char in their
|
|
37014
|
+
* `src/client.ts`:
|
|
37015
|
+
*
|
|
37016
|
+
* - sets `Accept: application/json`;
|
|
37017
|
+
* - adds `Content-Type: application/json` only for a non-GET request
|
|
37018
|
+
* that carries a `body` (and only if the caller didn't set one);
|
|
37019
|
+
* - `JSON.stringify`s the body (GET / no-body sends nothing);
|
|
37020
|
+
* - treats a `204` or an empty body as `data: null` (no parse);
|
|
37021
|
+
* - otherwise `JSON.parse`s the body.
|
|
37022
|
+
*
|
|
37023
|
+
* Scope is serialization + header defaults + 204-handling +
|
|
37024
|
+
* JSON.parse ONLY. It deliberately does NOT assert on the HTTP status
|
|
37025
|
+
* or look for a sign-in interstitial — those guards differ per site
|
|
37026
|
+
* (Zillow's `captcha-delivery`, Redfin's AWS-WAF challenge, …), so it
|
|
37027
|
+
* returns BOTH the parsed `data` and the raw `FetchResult` and leaves
|
|
37028
|
+
* the consumer to run its own `throwIfNotOk` / `throwIfSignInPage`
|
|
37029
|
+
* over `result`.
|
|
37030
|
+
*
|
|
37031
|
+
* Bridge-level failures (no signed-in tab, SW down, timeout) still
|
|
37032
|
+
* throw the typed errors via `request()`, exactly like the verb
|
|
37033
|
+
* helpers — only successful round-trips (any HTTP status) return.
|
|
37034
|
+
*/
|
|
37035
|
+
async requestJson(method, path, opts = {}) {
|
|
37036
|
+
const isGet = method.toUpperCase() === "GET";
|
|
37037
|
+
const sendBody = !isGet && opts.body !== void 0;
|
|
37038
|
+
const headers = {
|
|
37039
|
+
Accept: "application/json",
|
|
37040
|
+
...sendBody && !this.hasContentType(opts.headers ?? {}) ? { "Content-Type": "application/json" } : {},
|
|
37041
|
+
...opts.headers ?? {}
|
|
37042
|
+
};
|
|
37043
|
+
const response = await this.request(method, path, {
|
|
37044
|
+
headers,
|
|
37045
|
+
body: sendBody ? JSON.stringify(opts.body) : void 0,
|
|
37046
|
+
...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
|
|
37047
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
37048
|
+
});
|
|
37049
|
+
const result = {
|
|
37050
|
+
ok: true,
|
|
37051
|
+
status: response.status,
|
|
37052
|
+
url: response.url,
|
|
37053
|
+
body: response.body
|
|
37054
|
+
};
|
|
37055
|
+
if (response.status === 204 || response.body === "") {
|
|
37056
|
+
return { data: null, result };
|
|
37057
|
+
}
|
|
37058
|
+
let data;
|
|
37059
|
+
try {
|
|
37060
|
+
data = JSON.parse(response.body);
|
|
37061
|
+
} catch (e) {
|
|
37062
|
+
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
37063
|
+
}
|
|
37064
|
+
return { data, result };
|
|
37065
|
+
}
|
|
37066
|
+
/**
|
|
37067
|
+
* 0.11.0+: run a single healthcheck probe through `fetchFn`, measure
|
|
37068
|
+
* the elapsed round-trip, classify any thrown error, and project the
|
|
37069
|
+
* post-probe `bridgeHealth()` into a snake-cased `bridge` sub-object.
|
|
37070
|
+
*
|
|
37071
|
+
* This is the transport half of the probe loop zillow/redfin/homes
|
|
37072
|
+
* had duplicated verbatim in `src/tools/healthcheck.ts`. The MCP
|
|
37073
|
+
* supplies its own probe call (`(path) => client.fetchHtml(path)`)
|
|
37074
|
+
* and probe path (e.g. `'/robots.txt'`); the tool registration and
|
|
37075
|
+
* the site-specific plain-English hint text STAY in the consumer.
|
|
37076
|
+
*
|
|
37077
|
+
* `bridgeHealth()` is read AFTER the probe so its freshness counters
|
|
37078
|
+
* (`lastSuccessAt` / `consecutiveFailures` / …) reflect this very
|
|
37079
|
+
* round-trip rather than a stale pre-probe snapshot.
|
|
37080
|
+
*/
|
|
37081
|
+
async runProbe(fetchFn, probePath) {
|
|
37082
|
+
const start = Date.now();
|
|
37083
|
+
let ok = false;
|
|
37084
|
+
let error51;
|
|
37085
|
+
try {
|
|
37086
|
+
await fetchFn(probePath);
|
|
37087
|
+
ok = true;
|
|
37088
|
+
} catch (e) {
|
|
37089
|
+
error51 = {
|
|
37090
|
+
kind: classifyBridgeError(e),
|
|
37091
|
+
message: e instanceof Error ? e.message : String(e)
|
|
37092
|
+
};
|
|
37093
|
+
}
|
|
37094
|
+
const elapsed_ms = Date.now() - start;
|
|
37095
|
+
const health = this.bridgeHealth();
|
|
37096
|
+
return {
|
|
37097
|
+
ok,
|
|
37098
|
+
elapsed_ms,
|
|
37099
|
+
bridge: {
|
|
37100
|
+
role: health.role,
|
|
37101
|
+
port: health.port,
|
|
37102
|
+
server_version: health.serverVersion,
|
|
37103
|
+
fetch_timeout_ms: health.fetchTimeoutMs,
|
|
37104
|
+
last_success_at: health.lastSuccessAt,
|
|
37105
|
+
last_failure_at: health.lastFailureAt,
|
|
37106
|
+
last_failure_reason: health.lastFailureReason,
|
|
37107
|
+
consecutive_failures: health.consecutiveFailures
|
|
37108
|
+
},
|
|
37109
|
+
...error51 ? { error: error51 } : {}
|
|
37110
|
+
};
|
|
37111
|
+
}
|
|
36502
37112
|
/**
|
|
36503
37113
|
* Snapshot the user's non-HttpOnly cookies for the chosen domain.
|
|
36504
37114
|
*
|
|
@@ -36623,12 +37233,12 @@ var FetchproxyServer = class {
|
|
|
36623
37233
|
/**
|
|
36624
37234
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
36625
37235
|
* shot: the extension registers a one-time `webRequest` listener
|
|
36626
|
-
* filtered on `
|
|
36627
|
-
* match, removes itself, and resolves with the
|
|
36628
|
-
* after `timeoutMs` (default 30s on the extension).
|
|
37236
|
+
* filtered on `https://${host}${path ?? '/*'}`, captures the named
|
|
37237
|
+
* header on the first match, removes itself, and resolves with the
|
|
37238
|
+
* value. Times out after `timeoutMs` (default 30s on the extension).
|
|
36629
37239
|
*
|
|
36630
|
-
* `(
|
|
36631
|
-
* `FetchproxyServerOpts.captureHeaders
|
|
37240
|
+
* `(host, path?, headerName)` must match a declared entry in
|
|
37241
|
+
* `FetchproxyServerOpts.captureHeaders` (omitted path ≡ `/*`).
|
|
36632
37242
|
*/
|
|
36633
37243
|
async captureRequestHeader(opts) {
|
|
36634
37244
|
if (!this.opts.capabilities.includes("capture_request_header")) {
|
|
@@ -36637,24 +37247,25 @@ var FetchproxyServer = class {
|
|
|
36637
37247
|
await this.ensureConnected();
|
|
36638
37248
|
this.throwIfPendingPair();
|
|
36639
37249
|
const decls = this.opts.captureHeaders;
|
|
37250
|
+
const normPath = (p) => p ?? "/*";
|
|
36640
37251
|
let resolved;
|
|
36641
|
-
if (opts?.
|
|
36642
|
-
const found = decls.find((d) => d.
|
|
37252
|
+
if (opts?.host !== void 0 && opts?.headerName !== void 0) {
|
|
37253
|
+
const found = decls.find((d) => d.host === opts.host && normPath(d.path) === normPath(opts.path) && d.headerName === opts.headerName);
|
|
36643
37254
|
if (!found) {
|
|
36644
|
-
throw new Error(`FetchproxyServer.captureRequestHeader: (
|
|
37255
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: (host=${JSON.stringify(opts.host)}, path=${JSON.stringify(normPath(opts.path))}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
|
|
36645
37256
|
}
|
|
36646
37257
|
resolved = found;
|
|
36647
|
-
} else if (opts?.
|
|
37258
|
+
} else if (opts?.host === void 0 && opts?.headerName === void 0) {
|
|
36648
37259
|
if (decls.length === 0) {
|
|
36649
|
-
throw new Error("FetchproxyServer.captureRequestHeader: no captureHeaders declared on this server \u2014 declare at least one entry in FetchproxyServerOpts.captureHeaders, or pass {
|
|
37260
|
+
throw new Error("FetchproxyServer.captureRequestHeader: no captureHeaders declared on this server \u2014 declare at least one entry in FetchproxyServerOpts.captureHeaders, or pass {host, headerName} explicitly");
|
|
36650
37261
|
}
|
|
36651
37262
|
if (decls.length > 1) {
|
|
36652
|
-
const list = decls.map((d) => `${JSON.stringify(d.
|
|
36653
|
-
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list}); pass {
|
|
37263
|
+
const list = decls.map((d) => `${JSON.stringify(d.host)}${JSON.stringify(normPath(d.path))}/${JSON.stringify(d.headerName)}`).join(", ");
|
|
37264
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list}); pass {host, headerName} to disambiguate`);
|
|
36654
37265
|
}
|
|
36655
37266
|
resolved = decls[0];
|
|
36656
37267
|
} else {
|
|
36657
|
-
throw new Error("FetchproxyServer.captureRequestHeader: pass both
|
|
37268
|
+
throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
|
|
36658
37269
|
}
|
|
36659
37270
|
const callOpts = { ...resolved, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
36660
37271
|
try {
|
|
@@ -36667,11 +37278,14 @@ var FetchproxyServer = class {
|
|
|
36667
37278
|
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
36668
37279
|
throw err;
|
|
36669
37280
|
}
|
|
37281
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
36670
37282
|
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
36671
37283
|
if (reviveMs > 0) {
|
|
37284
|
+
this.lazyReviveAttempts += 1;
|
|
36672
37285
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36673
37286
|
try {
|
|
36674
37287
|
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
37288
|
+
this.lazyReviveSuccesses += 1;
|
|
36675
37289
|
this.recordSuccess();
|
|
36676
37290
|
return result;
|
|
36677
37291
|
} catch (retryErr) {
|
|
@@ -36685,7 +37299,7 @@ var FetchproxyServer = class {
|
|
|
36685
37299
|
originalError: retryErr.message,
|
|
36686
37300
|
retryAttempted: true,
|
|
36687
37301
|
op: "capture_request_header",
|
|
36688
|
-
url: resolved.
|
|
37302
|
+
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
36689
37303
|
role: this.role,
|
|
36690
37304
|
port: this.opts.port
|
|
36691
37305
|
});
|
|
@@ -36696,7 +37310,7 @@ var FetchproxyServer = class {
|
|
|
36696
37310
|
originalError: err.message,
|
|
36697
37311
|
retryAttempted: false,
|
|
36698
37312
|
op: "capture_request_header",
|
|
36699
|
-
url: resolved.
|
|
37313
|
+
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
36700
37314
|
role: this.role,
|
|
36701
37315
|
port: this.opts.port
|
|
36702
37316
|
});
|
|
@@ -36709,7 +37323,8 @@ var FetchproxyServer = class {
|
|
|
36709
37323
|
id,
|
|
36710
37324
|
op: "capture_request_header",
|
|
36711
37325
|
init: {
|
|
36712
|
-
|
|
37326
|
+
host: opts.host,
|
|
37327
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
36713
37328
|
headerName: opts.headerName,
|
|
36714
37329
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
36715
37330
|
}
|
|
@@ -36724,6 +37339,181 @@ var FetchproxyServer = class {
|
|
|
36724
37339
|
}
|
|
36725
37340
|
return pending;
|
|
36726
37341
|
}
|
|
37342
|
+
/**
|
|
37343
|
+
* Snapshot the redirect target URL of the next request the browser
|
|
37344
|
+
* makes to `(host, path?)`. Single-shot: the extension registers a
|
|
37345
|
+
* one-time `chrome.webRequest.onBeforeRedirect` listener filtered on
|
|
37346
|
+
* `https://${host}${path ?? '/*'}`, captures `details.redirectUrl` on
|
|
37347
|
+
* the first match, removes itself, and resolves with the URL. Times out
|
|
37348
|
+
* after `timeoutMs` (default 30s on the extension).
|
|
37349
|
+
*
|
|
37350
|
+
* Use case: a Cloudflare-walled endpoint that 302-redirects cross-origin
|
|
37351
|
+
* to a presigned URL — a page-level fetch sees only an opaque redirect,
|
|
37352
|
+
* but `onBeforeRedirect` exposes the target. Capture is limited to the
|
|
37353
|
+
* MCP's own declared `domains`; no per-entry declared scope is required.
|
|
37354
|
+
*/
|
|
37355
|
+
async captureRedirect(opts) {
|
|
37356
|
+
if (!this.opts.capabilities.includes("capture_redirect")) {
|
|
37357
|
+
throw new Error('FetchproxyServer.captureRedirect(): MCP did not declare "capture_redirect" in capabilities');
|
|
37358
|
+
}
|
|
37359
|
+
await this.ensureConnected();
|
|
37360
|
+
this.throwIfPendingPair();
|
|
37361
|
+
try {
|
|
37362
|
+
const result = await this._captureRedirectOnce(opts);
|
|
37363
|
+
this.recordSuccess();
|
|
37364
|
+
return result;
|
|
37365
|
+
} catch (err) {
|
|
37366
|
+
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
37367
|
+
if (!swDown) {
|
|
37368
|
+
this.recordFailure(`capture_redirect: ${err.message ?? String(err)}`);
|
|
37369
|
+
throw err;
|
|
37370
|
+
}
|
|
37371
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
37372
|
+
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
37373
|
+
if (reviveMs > 0) {
|
|
37374
|
+
this.lazyReviveAttempts += 1;
|
|
37375
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
37376
|
+
try {
|
|
37377
|
+
const result = await this._captureRedirectOnce(opts);
|
|
37378
|
+
this.lazyReviveSuccesses += 1;
|
|
37379
|
+
this.recordSuccess();
|
|
37380
|
+
return result;
|
|
37381
|
+
} catch (retryErr) {
|
|
37382
|
+
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
37383
|
+
if (!stillDown) {
|
|
37384
|
+
this.recordFailure(`capture_redirect: ${retryErr.message ?? String(retryErr)}`);
|
|
37385
|
+
throw retryErr;
|
|
37386
|
+
}
|
|
37387
|
+
this.recordFailure(`capture_redirect bridge-down: ${retryErr.message}`);
|
|
37388
|
+
throw new FetchproxyBridgeDownError({
|
|
37389
|
+
originalError: retryErr.message,
|
|
37390
|
+
retryAttempted: true,
|
|
37391
|
+
op: "capture_redirect",
|
|
37392
|
+
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
37393
|
+
role: this.role,
|
|
37394
|
+
port: this.opts.port
|
|
37395
|
+
});
|
|
37396
|
+
}
|
|
37397
|
+
}
|
|
37398
|
+
this.recordFailure(`capture_redirect bridge-down: ${err.message}`);
|
|
37399
|
+
throw new FetchproxyBridgeDownError({
|
|
37400
|
+
originalError: err.message,
|
|
37401
|
+
retryAttempted: false,
|
|
37402
|
+
op: "capture_redirect",
|
|
37403
|
+
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
37404
|
+
role: this.role,
|
|
37405
|
+
port: this.opts.port
|
|
37406
|
+
});
|
|
37407
|
+
}
|
|
37408
|
+
}
|
|
37409
|
+
async _captureRedirectOnce(opts) {
|
|
37410
|
+
const id = this.nextRequestId++;
|
|
37411
|
+
const inner = {
|
|
37412
|
+
type: "request",
|
|
37413
|
+
id,
|
|
37414
|
+
op: "capture_redirect",
|
|
37415
|
+
init: {
|
|
37416
|
+
host: opts.host,
|
|
37417
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
37418
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
37419
|
+
}
|
|
37420
|
+
};
|
|
37421
|
+
const pending = new Promise((resolve, reject) => {
|
|
37422
|
+
this.pendingRedirect.set(id, { resolve, reject });
|
|
37423
|
+
});
|
|
37424
|
+
if (this.hostHandle) {
|
|
37425
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
37426
|
+
} else if (this.peerHandle) {
|
|
37427
|
+
await this.peerHandle.sendInner(inner);
|
|
37428
|
+
}
|
|
37429
|
+
return pending;
|
|
37430
|
+
}
|
|
37431
|
+
/**
|
|
37432
|
+
* Download `url` through the BROWSER's own network stack via
|
|
37433
|
+
* `chrome.downloads` (real cookies + TLS/JA3 fingerprint). Unlike a
|
|
37434
|
+
* page-level `fetch()` (cors mode), this clears a Cloudflare bot-challenge
|
|
37435
|
+
* on the endpoint and follows the cross-origin redirect to the final file.
|
|
37436
|
+
* Resolves the saved local file path + size; the bridge is loopback-only /
|
|
37437
|
+
* single-host, so the MCP reads the bytes from the same disk. Requires
|
|
37438
|
+
* `'download'` in capabilities and the `url` host to be a declared `domain`.
|
|
37439
|
+
*/
|
|
37440
|
+
async download(opts) {
|
|
37441
|
+
if (!this.opts.capabilities.includes("download")) {
|
|
37442
|
+
throw new Error('FetchproxyServer.download(): MCP did not declare "download" in capabilities');
|
|
37443
|
+
}
|
|
37444
|
+
assertUrlInDomains("download url", opts.url, this.opts.domains);
|
|
37445
|
+
await this.ensureConnected();
|
|
37446
|
+
this.throwIfPendingPair();
|
|
37447
|
+
try {
|
|
37448
|
+
const result = await this._downloadOnce(opts);
|
|
37449
|
+
this.recordSuccess();
|
|
37450
|
+
return result;
|
|
37451
|
+
} catch (err) {
|
|
37452
|
+
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
37453
|
+
if (!swDown) {
|
|
37454
|
+
this.recordFailure(`download: ${err.message ?? String(err)}`);
|
|
37455
|
+
throw err;
|
|
37456
|
+
}
|
|
37457
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
37458
|
+
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
37459
|
+
if (reviveMs > 0) {
|
|
37460
|
+
this.lazyReviveAttempts += 1;
|
|
37461
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
37462
|
+
try {
|
|
37463
|
+
const result = await this._downloadOnce(opts);
|
|
37464
|
+
this.lazyReviveSuccesses += 1;
|
|
37465
|
+
this.recordSuccess();
|
|
37466
|
+
return result;
|
|
37467
|
+
} catch (retryErr) {
|
|
37468
|
+
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
37469
|
+
if (!stillDown) {
|
|
37470
|
+
this.recordFailure(`download: ${retryErr.message ?? String(retryErr)}`);
|
|
37471
|
+
throw retryErr;
|
|
37472
|
+
}
|
|
37473
|
+
this.recordFailure(`download bridge-down: ${retryErr.message}`);
|
|
37474
|
+
throw new FetchproxyBridgeDownError({
|
|
37475
|
+
originalError: retryErr.message,
|
|
37476
|
+
retryAttempted: true,
|
|
37477
|
+
op: "download",
|
|
37478
|
+
url: opts.url,
|
|
37479
|
+
role: this.role,
|
|
37480
|
+
port: this.opts.port
|
|
37481
|
+
});
|
|
37482
|
+
}
|
|
37483
|
+
}
|
|
37484
|
+
this.recordFailure(`download bridge-down: ${err.message}`);
|
|
37485
|
+
throw new FetchproxyBridgeDownError({
|
|
37486
|
+
originalError: err.message,
|
|
37487
|
+
retryAttempted: false,
|
|
37488
|
+
op: "download",
|
|
37489
|
+
url: opts.url,
|
|
37490
|
+
role: this.role,
|
|
37491
|
+
port: this.opts.port
|
|
37492
|
+
});
|
|
37493
|
+
}
|
|
37494
|
+
}
|
|
37495
|
+
async _downloadOnce(opts) {
|
|
37496
|
+
const id = this.nextRequestId++;
|
|
37497
|
+
const inner = {
|
|
37498
|
+
type: "request",
|
|
37499
|
+
id,
|
|
37500
|
+
op: "download",
|
|
37501
|
+
init: {
|
|
37502
|
+
url: opts.url,
|
|
37503
|
+
...opts.filename !== void 0 ? { filename: opts.filename } : {},
|
|
37504
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
37505
|
+
}
|
|
37506
|
+
};
|
|
37507
|
+
const pending = new Promise((resolve, reject) => {
|
|
37508
|
+
this.pendingDownload.set(id, { resolve, reject });
|
|
37509
|
+
});
|
|
37510
|
+
if (this.hostHandle) {
|
|
37511
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
37512
|
+
} else if (this.peerHandle) {
|
|
37513
|
+
await this.peerHandle.sendInner(inner);
|
|
37514
|
+
}
|
|
37515
|
+
return pending;
|
|
37516
|
+
}
|
|
36727
37517
|
/**
|
|
36728
37518
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
36729
37519
|
* tab. Requires `'read_indexed_db'` in capabilities AND the
|
|
@@ -36871,6 +37661,20 @@ var FetchproxyServer = class {
|
|
|
36871
37661
|
}
|
|
36872
37662
|
return;
|
|
36873
37663
|
}
|
|
37664
|
+
const redirectCb = this.pendingRedirect.get(inner.id);
|
|
37665
|
+
if (redirectCb) {
|
|
37666
|
+
this.pendingRedirect.delete(inner.id);
|
|
37667
|
+
if (inner.ok) {
|
|
37668
|
+
if (inner.op === "capture_redirect" && typeof inner.value === "string") {
|
|
37669
|
+
redirectCb.resolve(inner.value);
|
|
37670
|
+
} else {
|
|
37671
|
+
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
37672
|
+
}
|
|
37673
|
+
} else {
|
|
37674
|
+
redirectCb.reject(new FetchproxyProtocolError(inner.error));
|
|
37675
|
+
}
|
|
37676
|
+
return;
|
|
37677
|
+
}
|
|
36874
37678
|
const idbCb = this.pendingIdb.get(inner.id);
|
|
36875
37679
|
if (idbCb) {
|
|
36876
37680
|
this.pendingIdb.delete(inner.id);
|
|
@@ -36885,6 +37689,20 @@ var FetchproxyServer = class {
|
|
|
36885
37689
|
}
|
|
36886
37690
|
return;
|
|
36887
37691
|
}
|
|
37692
|
+
const downloadCb = this.pendingDownload.get(inner.id);
|
|
37693
|
+
if (downloadCb) {
|
|
37694
|
+
this.pendingDownload.delete(inner.id);
|
|
37695
|
+
if (inner.ok) {
|
|
37696
|
+
if (inner.op === "download" && inner.value && typeof inner.value === "object") {
|
|
37697
|
+
downloadCb.resolve({ ...inner.value });
|
|
37698
|
+
} else {
|
|
37699
|
+
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
37700
|
+
}
|
|
37701
|
+
} else {
|
|
37702
|
+
downloadCb.reject(new FetchproxyProtocolError(inner.error));
|
|
37703
|
+
}
|
|
37704
|
+
return;
|
|
37705
|
+
}
|
|
36888
37706
|
const cookiesCb = this.pendingReadCookies.get(inner.id);
|
|
36889
37707
|
if (cookiesCb) {
|
|
36890
37708
|
this.pendingReadCookies.delete(inner.id);
|
|
@@ -36927,9 +37745,15 @@ var FetchproxyServer = class {
|
|
|
36927
37745
|
for (const { reject } of this.pendingCapture.values())
|
|
36928
37746
|
reject(err);
|
|
36929
37747
|
this.pendingCapture.clear();
|
|
37748
|
+
for (const { reject } of this.pendingRedirect.values())
|
|
37749
|
+
reject(err);
|
|
37750
|
+
this.pendingRedirect.clear();
|
|
36930
37751
|
for (const { reject } of this.pendingIdb.values())
|
|
36931
37752
|
reject(err);
|
|
36932
37753
|
this.pendingIdb.clear();
|
|
37754
|
+
for (const { reject } of this.pendingDownload.values())
|
|
37755
|
+
reject(err);
|
|
37756
|
+
this.pendingDownload.clear();
|
|
36933
37757
|
}
|
|
36934
37758
|
/**
|
|
36935
37759
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -36964,6 +37788,8 @@ var FetchproxyServer = class {
|
|
|
36964
37788
|
* twice in a row.
|
|
36965
37789
|
*/
|
|
36966
37790
|
async close() {
|
|
37791
|
+
this.closing = true;
|
|
37792
|
+
this.stopKeepalive();
|
|
36967
37793
|
this.rejectAllPending();
|
|
36968
37794
|
if (this.connectingPromise) {
|
|
36969
37795
|
await this.connectingPromise.catch(() => void 0);
|
|
@@ -36979,19 +37805,6 @@ var FetchproxyServer = class {
|
|
|
36979
37805
|
}
|
|
36980
37806
|
};
|
|
36981
37807
|
|
|
36982
|
-
// node_modules/@fetchproxy/server/dist/classify-bridge-error.js
|
|
36983
|
-
function classifyBridgeError(err) {
|
|
36984
|
-
if (err instanceof FetchproxyTimeoutError)
|
|
36985
|
-
return "timeout";
|
|
36986
|
-
if (err instanceof FetchproxyBridgeDownError)
|
|
36987
|
-
return "bridge_down";
|
|
36988
|
-
if (err instanceof FetchproxyHttpError)
|
|
36989
|
-
return "http";
|
|
36990
|
-
if (err instanceof FetchproxyProtocolError)
|
|
36991
|
-
return "protocol";
|
|
36992
|
-
return "other";
|
|
36993
|
-
}
|
|
36994
|
-
|
|
36995
37808
|
// node_modules/@fetchproxy/bootstrap/dist/index.js
|
|
36996
37809
|
var defaultFactory = (opts) => new FetchproxyServer(opts);
|
|
36997
37810
|
async function bootstrap(opts) {
|
|
@@ -37023,7 +37836,7 @@ async function bootstrap(opts) {
|
|
|
37023
37836
|
const sessionStorageKeys = new Set(opts.declare.sessionStorage);
|
|
37024
37837
|
for (const p of sessionStoragePointers)
|
|
37025
37838
|
sessionStorageKeys.add(p.storageKey);
|
|
37026
|
-
const
|
|
37839
|
+
const server = factory({
|
|
37027
37840
|
serverName: opts.serverName,
|
|
37028
37841
|
version: opts.version,
|
|
37029
37842
|
domains: [...opts.domains],
|
|
@@ -37058,10 +37871,10 @@ async function bootstrap(opts) {
|
|
|
37058
37871
|
if (opts.storageSubdomain !== void 0)
|
|
37059
37872
|
storageDomainOpts.subdomain = opts.storageSubdomain;
|
|
37060
37873
|
try {
|
|
37061
|
-
await
|
|
37874
|
+
await server.listen();
|
|
37062
37875
|
const cookies = {};
|
|
37063
37876
|
if (opts.declare.cookies.length > 0) {
|
|
37064
|
-
const joined = await
|
|
37877
|
+
const joined = await server.readCookies({
|
|
37065
37878
|
keys: opts.declare.cookies,
|
|
37066
37879
|
...storageDomainOpts
|
|
37067
37880
|
});
|
|
@@ -37081,7 +37894,7 @@ async function bootstrap(opts) {
|
|
|
37081
37894
|
for (const p of localStoragePointers) {
|
|
37082
37895
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
37083
37896
|
}
|
|
37084
|
-
localStorage = await
|
|
37897
|
+
localStorage = await server.readLocalStorage({
|
|
37085
37898
|
keys: allKeys,
|
|
37086
37899
|
...storageDomainOpts,
|
|
37087
37900
|
...localStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -37094,7 +37907,7 @@ async function bootstrap(opts) {
|
|
|
37094
37907
|
for (const p of sessionStoragePointers) {
|
|
37095
37908
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
37096
37909
|
}
|
|
37097
|
-
sessionStorage = await
|
|
37910
|
+
sessionStorage = await server.readSessionStorage({
|
|
37098
37911
|
keys: allKeys,
|
|
37099
37912
|
...storageDomainOpts,
|
|
37100
37913
|
...sessionStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -37103,21 +37916,17 @@ async function bootstrap(opts) {
|
|
|
37103
37916
|
const capturedHeaders = {};
|
|
37104
37917
|
for (const h of opts.declare.captureHeaders) {
|
|
37105
37918
|
if (opts.onWaiting) {
|
|
37106
|
-
|
|
37107
|
-
const url2 = new URL(h.urlPattern.replace(/\*+/g, "placeholder"));
|
|
37108
|
-
opts.onWaiting(`waiting for next request to ${url2.host} to capture ${h.headerName} \u2014 interact with the page in your browser`);
|
|
37109
|
-
} catch {
|
|
37110
|
-
opts.onWaiting(`waiting to capture ${h.headerName} \u2014 interact with the page in your browser`);
|
|
37111
|
-
}
|
|
37919
|
+
opts.onWaiting(`waiting for next request to ${h.host} to capture ${h.headerName} \u2014 interact with the page in your browser`);
|
|
37112
37920
|
}
|
|
37113
|
-
capturedHeaders[h.headerName] = await
|
|
37114
|
-
|
|
37921
|
+
capturedHeaders[h.headerName] = await server.captureRequestHeader({
|
|
37922
|
+
host: h.host,
|
|
37923
|
+
...h.path !== void 0 ? { path: h.path } : {},
|
|
37115
37924
|
headerName: h.headerName
|
|
37116
37925
|
});
|
|
37117
37926
|
}
|
|
37118
37927
|
const indexedDbBucket = {};
|
|
37119
37928
|
for (const d of indexedDb) {
|
|
37120
|
-
const values = await
|
|
37929
|
+
const values = await server.readIndexedDb({
|
|
37121
37930
|
database: d.database,
|
|
37122
37931
|
store: d.store,
|
|
37123
37932
|
keys: [...d.keys],
|
|
@@ -37134,7 +37943,7 @@ async function bootstrap(opts) {
|
|
|
37134
37943
|
};
|
|
37135
37944
|
} finally {
|
|
37136
37945
|
try {
|
|
37137
|
-
await
|
|
37946
|
+
await server.close();
|
|
37138
37947
|
} catch {
|
|
37139
37948
|
}
|
|
37140
37949
|
}
|
|
@@ -37152,13 +37961,7 @@ var BootstrapDisabledError = class extends Error {
|
|
|
37152
37961
|
|
|
37153
37962
|
// src/config.ts
|
|
37154
37963
|
function readVar(env, key) {
|
|
37155
|
-
|
|
37156
|
-
if (typeof raw !== "string") return void 0;
|
|
37157
|
-
const trimmed = raw.trim();
|
|
37158
|
-
if (trimmed.length === 0) return void 0;
|
|
37159
|
-
if (trimmed === "undefined" || trimmed === "null") return void 0;
|
|
37160
|
-
if (/^\$\{[^}]*\}$/.test(trimmed)) return void 0;
|
|
37161
|
-
return trimmed;
|
|
37964
|
+
return readEnvVar(key, { env });
|
|
37162
37965
|
}
|
|
37163
37966
|
function loadAccount(env = process.env) {
|
|
37164
37967
|
const baseUrl = readVar(env, "IC_BASE_URL");
|
|
@@ -37197,7 +38000,7 @@ function loadAccount(env = process.env) {
|
|
|
37197
38000
|
// package.json
|
|
37198
38001
|
var package_default = {
|
|
37199
38002
|
name: "infinitecampus-mcp",
|
|
37200
|
-
version: "2.3.
|
|
38003
|
+
version: "2.3.3",
|
|
37201
38004
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
37202
38005
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
37203
38006
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -37239,10 +38042,12 @@ var package_default = {
|
|
|
37239
38042
|
"test:watch": "vitest"
|
|
37240
38043
|
},
|
|
37241
38044
|
dependencies: {
|
|
37242
|
-
"@
|
|
37243
|
-
"@fetchproxy/
|
|
38045
|
+
"@chrischall/mcp-utils": "^0.5.0",
|
|
38046
|
+
"@fetchproxy/bootstrap": "^1.0.0",
|
|
38047
|
+
"@fetchproxy/server": "^1.0.0",
|
|
37244
38048
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37245
38049
|
dotenv: "^17.4.2",
|
|
38050
|
+
"node-html-parser": "^7.1.0",
|
|
37246
38051
|
zod: "^4.4.3"
|
|
37247
38052
|
},
|
|
37248
38053
|
devDependencies: {
|
|
@@ -37255,19 +38060,8 @@ var package_default = {
|
|
|
37255
38060
|
};
|
|
37256
38061
|
|
|
37257
38062
|
// src/auth.ts
|
|
37258
|
-
function readEnv(key) {
|
|
37259
|
-
const raw = process.env[key];
|
|
37260
|
-
if (typeof raw !== "string") return void 0;
|
|
37261
|
-
const trimmed = raw.trim();
|
|
37262
|
-
if (trimmed.length === 0) return void 0;
|
|
37263
|
-
if (trimmed === "undefined" || trimmed === "null") return void 0;
|
|
37264
|
-
if (/^\$\{[^}]*\}$/.test(trimmed)) return void 0;
|
|
37265
|
-
return trimmed;
|
|
37266
|
-
}
|
|
37267
38063
|
function fetchproxyDisabled() {
|
|
37268
|
-
|
|
37269
|
-
if (raw === void 0) return false;
|
|
37270
|
-
return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
|
|
38064
|
+
return parseBoolEnv("IC_DISABLE_FETCHPROXY", { default: false });
|
|
37271
38065
|
}
|
|
37272
38066
|
async function resolveAuth() {
|
|
37273
38067
|
const account2 = loadAccount();
|
|
@@ -37438,6 +38232,20 @@ var ICClient = class {
|
|
|
37438
38232
|
}
|
|
37439
38233
|
}
|
|
37440
38234
|
async login(account2) {
|
|
38235
|
+
const primaryName = this.linkedTo.get(account2.name);
|
|
38236
|
+
if (primaryName) {
|
|
38237
|
+
this.sessions.delete(account2.name);
|
|
38238
|
+
const primaryAccount = this.accounts.get(primaryName);
|
|
38239
|
+
await this.ensureSession(primaryAccount);
|
|
38240
|
+
if (!this.sessions.has(account2.name)) {
|
|
38241
|
+
throw new AuthFailedError(
|
|
38242
|
+
account2.name,
|
|
38243
|
+
"linked-district session could not be re-established via the primary district",
|
|
38244
|
+
{ credentialHint: false }
|
|
38245
|
+
);
|
|
38246
|
+
}
|
|
38247
|
+
return;
|
|
38248
|
+
}
|
|
37441
38249
|
if (!account2.username || !account2.password) {
|
|
37442
38250
|
throw new AuthFailedError(
|
|
37443
38251
|
account2.name,
|
|
@@ -37445,8 +38253,17 @@ var ICClient = class {
|
|
|
37445
38253
|
);
|
|
37446
38254
|
}
|
|
37447
38255
|
const postRes = await fetch(
|
|
37448
|
-
`${account2.baseUrl}/campus/verify.jsp?nonBrowser=true
|
|
37449
|
-
{
|
|
38256
|
+
`${account2.baseUrl}/campus/verify.jsp?nonBrowser=true`,
|
|
38257
|
+
{
|
|
38258
|
+
method: "POST",
|
|
38259
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
38260
|
+
body: new URLSearchParams({
|
|
38261
|
+
username: account2.username,
|
|
38262
|
+
password: account2.password,
|
|
38263
|
+
appName: account2.district,
|
|
38264
|
+
portalLoginPage: "parents"
|
|
38265
|
+
}).toString()
|
|
38266
|
+
}
|
|
37450
38267
|
);
|
|
37451
38268
|
if (postRes.status >= 500) throw new PortalUnreachableError(account2.name, postRes.status);
|
|
37452
38269
|
const body = await postRes.text();
|
|
@@ -37469,9 +38286,7 @@ var ICClient = class {
|
|
|
37469
38286
|
session.cookie = cookies.cookieHeader;
|
|
37470
38287
|
session.xsrfToken = cookies.xsrfToken;
|
|
37471
38288
|
session.loggedInAt = Date.now();
|
|
37472
|
-
|
|
37473
|
-
await this.discoverLinkedDistricts(account2);
|
|
37474
|
-
}
|
|
38289
|
+
await this.discoverLinkedDistricts(account2);
|
|
37475
38290
|
}
|
|
37476
38291
|
async discoverLinkedDistricts(account2) {
|
|
37477
38292
|
try {
|
|
@@ -37635,22 +38450,9 @@ var ICClient = class {
|
|
|
37635
38450
|
};
|
|
37636
38451
|
function parseSetCookies(headers) {
|
|
37637
38452
|
const raw = headers.getSetCookie?.() ?? [];
|
|
37638
|
-
const
|
|
37639
|
-
const
|
|
37640
|
-
|
|
37641
|
-
for (const entry of headerStrings) {
|
|
37642
|
-
if (/Max-Age=0/i.test(entry)) continue;
|
|
37643
|
-
const nameValue = entry.split(";")[0].trim();
|
|
37644
|
-
const eqIdx = nameValue.indexOf("=");
|
|
37645
|
-
if (eqIdx < 1) continue;
|
|
37646
|
-
const name = nameValue.substring(0, eqIdx);
|
|
37647
|
-
const value = nameValue.substring(eqIdx + 1);
|
|
37648
|
-
if (!value) continue;
|
|
37649
|
-
jar.set(name, value);
|
|
37650
|
-
if (name === "XSRF-TOKEN") xsrfToken = value;
|
|
37651
|
-
}
|
|
37652
|
-
const cookieHeader = [...jar.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
|
37653
|
-
return { cookieHeader, xsrfToken };
|
|
38453
|
+
const entries = raw.length > 0 ? raw : splitFallback(headers.get("set-cookie"));
|
|
38454
|
+
const { cookies, cookieHeader } = parseCookieJar(entries);
|
|
38455
|
+
return { cookieHeader, xsrfToken: cookies["XSRF-TOKEN"] ?? "" };
|
|
37654
38456
|
}
|
|
37655
38457
|
function splitFallback(header) {
|
|
37656
38458
|
if (!header) return [];
|
|
@@ -37667,11 +38469,16 @@ var UnknownDistrictError = class extends Error {
|
|
|
37667
38469
|
available;
|
|
37668
38470
|
};
|
|
37669
38471
|
var AuthFailedError = class extends Error {
|
|
37670
|
-
|
|
38472
|
+
/**
|
|
38473
|
+
* @param opts.credentialHint When `false`, the message omits the
|
|
38474
|
+
* "Check IC_USERNAME and IC_PASSWORD" suffix — used for failures where the
|
|
38475
|
+
* credentials are known-good (e.g. a linked-district CUPS/SSO re-discovery
|
|
38476
|
+
* failure) and pointing the user at their creds would be misleading.
|
|
38477
|
+
*/
|
|
38478
|
+
constructor(district, reason, opts) {
|
|
37671
38479
|
const detail = reason ? ` (${reason})` : "";
|
|
37672
|
-
|
|
37673
|
-
|
|
37674
|
-
);
|
|
38480
|
+
const remedy = opts?.credentialHint === false ? "Sign in again at the IC portal in your browser, then restart the MCP." : "Check IC_USERNAME and IC_PASSWORD; if those are correct, the account may be locked or the portal may be down.";
|
|
38481
|
+
super(`Login failed for district '${district}'${detail}. ${remedy}`);
|
|
37675
38482
|
this.district = district;
|
|
37676
38483
|
this.reason = reason;
|
|
37677
38484
|
this.name = "AuthFailedError";
|
|
@@ -37724,7 +38531,7 @@ var FileExistsError = class extends Error {
|
|
|
37724
38531
|
|
|
37725
38532
|
// src/tools/_shared.ts
|
|
37726
38533
|
function textContent(data) {
|
|
37727
|
-
return
|
|
38534
|
+
return textResult(data);
|
|
37728
38535
|
}
|
|
37729
38536
|
function is404(e) {
|
|
37730
38537
|
return e instanceof Error && e.message.startsWith("IC 404 ");
|
|
@@ -37757,8 +38564,8 @@ function toArray(value) {
|
|
|
37757
38564
|
}
|
|
37758
38565
|
|
|
37759
38566
|
// src/tools/districts.ts
|
|
37760
|
-
function registerDistrictTools(
|
|
37761
|
-
|
|
38567
|
+
function registerDistrictTools(server, client) {
|
|
38568
|
+
server.registerTool("ic_list_districts", {
|
|
37762
38569
|
description: "List Infinite Campus districts configured for this MCP server. Returns names + base URLs (no credentials).",
|
|
37763
38570
|
annotations: { readOnlyHint: true }
|
|
37764
38571
|
}, async () => {
|
|
@@ -37772,8 +38579,8 @@ function registerDistrictTools(server2, client) {
|
|
|
37772
38579
|
var argsSchema = external_exports.object({
|
|
37773
38580
|
district: external_exports.string().describe("District name from ic_list_districts")
|
|
37774
38581
|
});
|
|
37775
|
-
function registerStudentTools(
|
|
37776
|
-
|
|
38582
|
+
function registerStudentTools(server, client) {
|
|
38583
|
+
server.registerTool("ic_list_students", {
|
|
37777
38584
|
description: "List students enrolled under the parent account for a given district.",
|
|
37778
38585
|
annotations: { readOnlyHint: true },
|
|
37779
38586
|
inputSchema: argsSchema.shape
|
|
@@ -37791,8 +38598,8 @@ var argsSchema2 = external_exports.object({
|
|
|
37791
38598
|
date: external_exports.string().describe("YYYY-MM-DD; defaults to today").optional(),
|
|
37792
38599
|
termFilter: external_exports.string().describe("Term name or ID; optional").optional()
|
|
37793
38600
|
});
|
|
37794
|
-
function registerScheduleTools(
|
|
37795
|
-
|
|
38601
|
+
function registerScheduleTools(server, client) {
|
|
38602
|
+
server.registerTool("ic_get_schedule", {
|
|
37796
38603
|
description: "Get a student's class schedule for a given date (default: today).",
|
|
37797
38604
|
annotations: { readOnlyHint: true },
|
|
37798
38605
|
inputSchema: argsSchema2.shape
|
|
@@ -37813,8 +38620,8 @@ var argsSchema3 = external_exports.object({
|
|
|
37813
38620
|
until: external_exports.string().describe("YYYY-MM-DD; filters dueDate <= until (client-side)").optional(),
|
|
37814
38621
|
missingOnly: external_exports.boolean().describe("Only return assignments flagged missing by the teacher").optional()
|
|
37815
38622
|
});
|
|
37816
|
-
function registerAssignmentTools(
|
|
37817
|
-
|
|
38623
|
+
function registerAssignmentTools(server, client) {
|
|
38624
|
+
server.registerTool("ic_list_assignments", {
|
|
37818
38625
|
description: "List a student's assignments. The IC endpoint returns the full term history (~hundreds of items); date and missing filters are applied client-side. For a single course, pass courseId (the sectionID from ic_get_schedule).",
|
|
37819
38626
|
annotations: { readOnlyHint: true },
|
|
37820
38627
|
inputSchema: argsSchema3.shape
|
|
@@ -37848,8 +38655,8 @@ var argsSchema4 = external_exports.object({
|
|
|
37848
38655
|
studentId: external_exports.string(),
|
|
37849
38656
|
termId: external_exports.string().optional()
|
|
37850
38657
|
});
|
|
37851
|
-
function registerGradeTools(
|
|
37852
|
-
|
|
38658
|
+
function registerGradeTools(server, client) {
|
|
38659
|
+
server.registerTool("ic_list_grades", {
|
|
37853
38660
|
description: "List a student's term grades and in-progress course grades.",
|
|
37854
38661
|
annotations: { readOnlyHint: true },
|
|
37855
38662
|
inputSchema: argsSchema4.shape
|
|
@@ -37892,8 +38699,8 @@ function processList(list, since, until) {
|
|
|
37892
38699
|
if (list === void 0) return list;
|
|
37893
38700
|
return toArray(list).filter((e) => inRange(e.date, since, until)).map(trimEntry);
|
|
37894
38701
|
}
|
|
37895
|
-
function registerAttendanceTools(
|
|
37896
|
-
|
|
38702
|
+
function registerAttendanceTools(server, client) {
|
|
38703
|
+
server.registerTool("ic_list_attendance", {
|
|
37897
38704
|
description: "List a student's absences and tardies (per-course summary grouped by term). Auto-resolves enrollmentID from the student record.",
|
|
37898
38705
|
annotations: { readOnlyHint: true },
|
|
37899
38706
|
inputSchema: argsSchema5.shape
|
|
@@ -37941,8 +38748,8 @@ var argsSchema6 = external_exports.object({
|
|
|
37941
38748
|
since: external_exports.string().optional(),
|
|
37942
38749
|
until: external_exports.string().optional()
|
|
37943
38750
|
});
|
|
37944
|
-
function registerBehaviorTools(
|
|
37945
|
-
|
|
38751
|
+
function registerBehaviorTools(server, client) {
|
|
38752
|
+
server.registerTool("ic_list_behavior", {
|
|
37946
38753
|
description: "List a student's behavior events / referrals. Returns FeatureDisabled if the district has the behavior module turned off (detected via displayOptions or a 404 backstop).",
|
|
37947
38754
|
annotations: { readOnlyHint: true },
|
|
37948
38755
|
inputSchema: argsSchema6.shape
|
|
@@ -37973,8 +38780,8 @@ var argsSchema7 = external_exports.object({
|
|
|
37973
38780
|
until: external_exports.string().optional()
|
|
37974
38781
|
});
|
|
37975
38782
|
var EMPTY_FOOD = { balance: null, transactions: [] };
|
|
37976
|
-
function registerFoodServiceTools(
|
|
37977
|
-
|
|
38783
|
+
function registerFoodServiceTools(server, client) {
|
|
38784
|
+
server.registerTool("ic_list_food_service", {
|
|
37978
38785
|
description: "List a student's lunch balance and recent food-service transactions. Returns FeatureDisabled if the district has the module turned off (detected via displayOptions or a 404 backstop).",
|
|
37979
38786
|
annotations: { readOnlyHint: true },
|
|
37980
38787
|
inputSchema: argsSchema7.shape
|
|
@@ -38005,6 +38812,29 @@ function registerFoodServiceTools(server2, client) {
|
|
|
38005
38812
|
});
|
|
38006
38813
|
}
|
|
38007
38814
|
|
|
38815
|
+
// node_modules/@chrischall/mcp-utils/dist/html/index.js
|
|
38816
|
+
var NAMED_ENTITIES = {
|
|
38817
|
+
nbsp: " ",
|
|
38818
|
+
amp: "&",
|
|
38819
|
+
lt: "<",
|
|
38820
|
+
gt: ">",
|
|
38821
|
+
quot: '"',
|
|
38822
|
+
apos: "'"
|
|
38823
|
+
};
|
|
38824
|
+
function extractPlainTextFromHtml(html) {
|
|
38825
|
+
if (!html)
|
|
38826
|
+
return "";
|
|
38827
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ");
|
|
38828
|
+
text = text.replace(/<[^>]+>/g, " ");
|
|
38829
|
+
text = text.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
|
|
38830
|
+
text = text.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)));
|
|
38831
|
+
text = text.replace(/&([a-zA-Z]+);/g, (whole, name) => {
|
|
38832
|
+
const decoded = NAMED_ENTITIES[name.toLowerCase()];
|
|
38833
|
+
return decoded ?? whole;
|
|
38834
|
+
});
|
|
38835
|
+
return text.replace(/\s+/g, " ").trim();
|
|
38836
|
+
}
|
|
38837
|
+
|
|
38008
38838
|
// src/tools/messages.ts
|
|
38009
38839
|
var listArgs = external_exports.object({
|
|
38010
38840
|
district: external_exports.string(),
|
|
@@ -38051,10 +38881,7 @@ function parseMessageHtml(html, url2) {
|
|
|
38051
38881
|
const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
|
|
38052
38882
|
let subject = titleMatch ? titleMatch[1].trim() : "";
|
|
38053
38883
|
subject = subject.replace(/^Message\s*--\s*/i, "");
|
|
38054
|
-
|
|
38055
|
-
text = text.replace(/<[^>]+>/g, " ");
|
|
38056
|
-
text = text.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
38057
|
-
text = text.replace(/\s+/g, " ").trim();
|
|
38884
|
+
const text = extractPlainTextFromHtml(html);
|
|
38058
38885
|
const dateMatch = text.match(/Date:\s*(\d{1,2}\/\d{1,2}\/\d{2,4})/);
|
|
38059
38886
|
const date5 = dateMatch ? dateMatch[1] : null;
|
|
38060
38887
|
let body = text;
|
|
@@ -38064,8 +38891,8 @@ function parseMessageHtml(html, url2) {
|
|
|
38064
38891
|
}
|
|
38065
38892
|
return { subject, date: date5, body, url: url2 };
|
|
38066
38893
|
}
|
|
38067
|
-
function registerMessageTools(
|
|
38068
|
-
|
|
38894
|
+
function registerMessageTools(server, client) {
|
|
38895
|
+
server.registerTool("ic_list_messages", {
|
|
38069
38896
|
description: "List all parent-visible messages from three IC sources combined: (1) prism notifications (assignment alerts, grade postings, attendance alerts), (2) Messenger 2.0 inbox (teacher messages, district announcements with newMessage/actionRequired flags), and (3) portal userNotice announcements. Each section has its own count and items; if any source errors, that section contains an error field and the others still return normally. The `limit` arg caps the prism notifications only (the high-volume source). Note: listing inbox messages does not mark them as read in normal portal behavior, but some district configurations may update read-tracking; use ic_get_message for the full HTML body.",
|
|
38070
38897
|
annotations: { readOnlyHint: true },
|
|
38071
38898
|
inputSchema: listArgs.shape
|
|
@@ -38103,7 +38930,7 @@ function registerMessageTools(server2, client) {
|
|
|
38103
38930
|
const [notifications, inbox, announcements] = await Promise.all([prismPromise, inboxPromise, noticePromise]);
|
|
38104
38931
|
return textContent({ notifications, inbox, announcements });
|
|
38105
38932
|
});
|
|
38106
|
-
|
|
38933
|
+
server.registerTool("ic_get_message", {
|
|
38107
38934
|
description: "Fetch the HTML body of an inbox message and return it parsed into { subject, date, body, url }. Takes a `messageUrl` which is the `url` field from an item returned by ic_list_messages' inbox section (e.g. 'portal/messageView.xsl?x=messenger.MessengerEngine-getMessageRecipientView&messageID=...'). Relative and /campus/-prefixed URLs are both accepted. Note: fetching the HTML body may mark the message as read on some district configurations; probe against an empty inbox could not confirm the side effect.",
|
|
38108
38935
|
annotations: { readOnlyHint: true },
|
|
38109
38936
|
inputSchema: getArgs.shape
|
|
@@ -38127,8 +38954,8 @@ var downloadArgs = external_exports.object({
|
|
|
38127
38954
|
destinationPath: external_exports.string().describe("Absolute path where the PDF should be written"),
|
|
38128
38955
|
overwrite: external_exports.boolean().optional()
|
|
38129
38956
|
});
|
|
38130
|
-
function registerDocumentTools(
|
|
38131
|
-
|
|
38957
|
+
function registerDocumentTools(server, client) {
|
|
38958
|
+
server.registerTool("ic_list_documents", {
|
|
38132
38959
|
description: "List a student's available documents (report cards, transcripts, schedules). Returns metadata only \u2014 use ic_download_document to fetch the file. Returns FeatureDisabled if the district has the module turned off.",
|
|
38133
38960
|
annotations: { readOnlyHint: true },
|
|
38134
38961
|
inputSchema: listArgs2.shape
|
|
@@ -38158,7 +38985,7 @@ function registerDocumentTools(server2, client) {
|
|
|
38158
38985
|
throw e;
|
|
38159
38986
|
}
|
|
38160
38987
|
});
|
|
38161
|
-
|
|
38988
|
+
server.registerTool("ic_download_document", {
|
|
38162
38989
|
description: "Download a student's document (PDF) to disk. documentId is the url field returned by ic_list_documents. Returns FeatureDisabled if the district has the module turned off.",
|
|
38163
38990
|
annotations: { destructiveHint: true },
|
|
38164
38991
|
inputSchema: downloadArgs.shape
|
|
@@ -38185,8 +39012,8 @@ var argsSchema8 = external_exports.object({
|
|
|
38185
39012
|
since: external_exports.string().describe("YYYY-MM-DD; include only days on or after this date").optional(),
|
|
38186
39013
|
until: external_exports.string().describe("YYYY-MM-DD; include only days on or before this date").optional()
|
|
38187
39014
|
});
|
|
38188
|
-
function registerCalendarTools(
|
|
38189
|
-
|
|
39015
|
+
function registerCalendarTools(server, client) {
|
|
39016
|
+
server.registerTool("ic_list_school_days", {
|
|
38190
39017
|
description: "List a student's school days (instructional calendar) grouped by term. Returns one entry per enrollment, with term boundaries (Q1-Q4 start/end dates) and the school days inside each term \u2014 including comments like 'Teacher Workday' or 'Spring Break'. Use since/until to narrow the range.",
|
|
38191
39018
|
annotations: { readOnlyHint: true },
|
|
38192
39019
|
inputSchema: argsSchema8.shape
|
|
@@ -38281,8 +39108,8 @@ function trimEvent(e) {
|
|
|
38281
39108
|
}
|
|
38282
39109
|
return out;
|
|
38283
39110
|
}
|
|
38284
|
-
function registerAttendanceEventsTools(
|
|
38285
|
-
|
|
39111
|
+
function registerAttendanceEventsTools(server, client) {
|
|
39112
|
+
server.registerTool("ic_list_attendance_events", {
|
|
38286
39113
|
description: "List individual attendance events (absences, tardies, early releases) for a student. Each event has a code, description, excuse reason, and optional human-readable comments. Auto-resolves enrollmentID from the student record. Use since/until to filter by date and excusedOnly to show only excused events.",
|
|
38287
39114
|
annotations: { readOnlyHint: true },
|
|
38288
39115
|
inputSchema: argsSchema9.shape
|
|
@@ -38353,8 +39180,8 @@ function defaultSinceDate(now) {
|
|
|
38353
39180
|
d.setUTCDate(d.getUTCDate() - 14);
|
|
38354
39181
|
return d.toISOString().substring(0, 10);
|
|
38355
39182
|
}
|
|
38356
|
-
function registerRecentGradesTools(
|
|
38357
|
-
|
|
39183
|
+
function registerRecentGradesTools(server, client) {
|
|
39184
|
+
server.registerTool("ic_list_recent_grades", {
|
|
38358
39185
|
description: "List recently-graded assignments for a student. Server-side filtered by scoreModifiedDate. Pass since=YYYY-MM-DD to set the cutoff; defaults to 14 days ago.",
|
|
38359
39186
|
annotations: { readOnlyHint: true },
|
|
38360
39187
|
inputSchema: argsSchema10.shape
|
|
@@ -38404,8 +39231,8 @@ function trimRecord(raw) {
|
|
|
38404
39231
|
}
|
|
38405
39232
|
return out;
|
|
38406
39233
|
}
|
|
38407
|
-
function registerTeacherTools(
|
|
38408
|
-
|
|
39234
|
+
function registerTeacherTools(server, client) {
|
|
39235
|
+
server.registerTool("ic_list_teachers", {
|
|
38409
39236
|
description: "List a student's teachers (per enrolled section) and assigned counselor(s). Combines two endpoints (section/contacts and studentCounselor/byUser). Response field shapes may vary slightly by district \u2014 core fields (firstName, lastName, email) are consistent; additional fields are passed through.",
|
|
38410
39237
|
annotations: { readOnlyHint: true },
|
|
38411
39238
|
inputSchema: argsSchema11.shape
|
|
@@ -38438,8 +39265,8 @@ var argsSchema12 = external_exports.object({
|
|
|
38438
39265
|
district: external_exports.string(),
|
|
38439
39266
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38440
39267
|
});
|
|
38441
|
-
function registerAssessmentTools(
|
|
38442
|
-
|
|
39268
|
+
function registerAssessmentTools(server, client) {
|
|
39269
|
+
server.registerTool("ic_list_assessments", {
|
|
38443
39270
|
description: "List a student's standardized test scores (state, national, district tests). Auto-resolves calendarID from each of the student's enrollments and returns one entry per enrollment. The shape of individual test records varies by district and test type \u2014 fields are passed through unchanged.",
|
|
38444
39271
|
annotations: { readOnlyHint: true },
|
|
38445
39272
|
inputSchema: argsSchema12.shape
|
|
@@ -38489,8 +39316,8 @@ var argsSchema13 = external_exports.object({
|
|
|
38489
39316
|
district: external_exports.string(),
|
|
38490
39317
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38491
39318
|
});
|
|
38492
|
-
function registerFeeTools(
|
|
38493
|
-
|
|
39319
|
+
function registerFeeTools(server, client) {
|
|
39320
|
+
server.registerTool("ic_list_fees", {
|
|
38494
39321
|
description: "List a student's fee assignments (charges owed) and running balance/surplus. Combines two endpoints: fee assignments and totalSurplus. Returns FeatureDisabled only if both endpoints 404; if only one works, returns that side with warning: 'PartialSuccess' and an issues[] explaining which endpoint failed.",
|
|
38495
39322
|
annotations: { readOnlyHint: true },
|
|
38496
39323
|
inputSchema: argsSchema13.shape
|
|
@@ -38533,8 +39360,8 @@ var argsSchema14 = external_exports.object({
|
|
|
38533
39360
|
district: external_exports.string(),
|
|
38534
39361
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38535
39362
|
});
|
|
38536
|
-
function registerFeaturesTools(
|
|
38537
|
-
|
|
39363
|
+
function registerFeaturesTools(server, client) {
|
|
39364
|
+
server.registerTool("ic_get_features", {
|
|
38538
39365
|
description: "List the district's displayOptions feature-flag allow-list for each of a student's enrollments. Each enrollment's `features` object is a map of ~90 flag names (attendance, behavior, assessment, documents, grades, schedule, etc.) to booleans. A `false` value means the district has that feature disabled for this enrollment; `true` or missing means it's available. Used internally by other tools to short-circuit disabled features, but exposed here so the LLM can answer capability questions directly.",
|
|
38539
39366
|
annotations: { readOnlyHint: true },
|
|
38540
39367
|
inputSchema: argsSchema14.shape
|
|
@@ -38557,12 +39384,11 @@ function registerFeaturesTools(server2, client) {
|
|
|
38557
39384
|
}
|
|
38558
39385
|
|
|
38559
39386
|
// src/index.ts
|
|
38560
|
-
|
|
38561
|
-
|
|
38562
|
-
|
|
38563
|
-
|
|
38564
|
-
|
|
38565
|
-
}
|
|
39387
|
+
await loadDotenvSafely({
|
|
39388
|
+
path: join2(dirname2(fileURLToPath(import.meta.url)), "..", ".env"),
|
|
39389
|
+
override: false
|
|
39390
|
+
});
|
|
39391
|
+
var AI_NOTICE = "[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.";
|
|
38566
39392
|
var account = null;
|
|
38567
39393
|
var preloaded;
|
|
38568
39394
|
var source;
|
|
@@ -38575,32 +39401,39 @@ try {
|
|
|
38575
39401
|
} catch (e) {
|
|
38576
39402
|
configError = e;
|
|
38577
39403
|
}
|
|
38578
|
-
var
|
|
39404
|
+
var COMMON = {
|
|
39405
|
+
name: "infinitecampus",
|
|
39406
|
+
version: "2.3.3"
|
|
39407
|
+
// x-release-please-version
|
|
39408
|
+
};
|
|
38579
39409
|
if (account) {
|
|
38580
39410
|
const client = new ICClient(account, { preloaded });
|
|
38581
|
-
|
|
38582
|
-
|
|
38583
|
-
|
|
38584
|
-
|
|
38585
|
-
|
|
38586
|
-
|
|
38587
|
-
|
|
38588
|
-
|
|
38589
|
-
|
|
38590
|
-
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
38594
|
-
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
39411
|
+
const tools = [
|
|
39412
|
+
registerDistrictTools,
|
|
39413
|
+
registerStudentTools,
|
|
39414
|
+
registerScheduleTools,
|
|
39415
|
+
registerAssignmentTools,
|
|
39416
|
+
registerGradeTools,
|
|
39417
|
+
registerAttendanceTools,
|
|
39418
|
+
registerBehaviorTools,
|
|
39419
|
+
registerFoodServiceTools,
|
|
39420
|
+
registerMessageTools,
|
|
39421
|
+
registerDocumentTools,
|
|
39422
|
+
registerCalendarTools,
|
|
39423
|
+
registerAttendanceEventsTools,
|
|
39424
|
+
registerRecentGradesTools,
|
|
39425
|
+
registerTeacherTools,
|
|
39426
|
+
registerAssessmentTools,
|
|
39427
|
+
registerFeeTools,
|
|
39428
|
+
registerFeaturesTools
|
|
39429
|
+
];
|
|
38598
39430
|
const suffix = source === "fetchproxy" ? " [via fetchproxy]" : "";
|
|
38599
|
-
|
|
39431
|
+
const districtLine = `[infinitecampus-mcp] District: ${account.name} (${account.baseUrl})${suffix}`;
|
|
39432
|
+
await runMcp({ ...COMMON, deps: client, tools, banner: `${districtLine}
|
|
39433
|
+
${AI_NOTICE}` });
|
|
38600
39434
|
} else {
|
|
38601
|
-
|
|
38602
|
-
|
|
39435
|
+
const notConfigured = `[infinitecampus-mcp] Not configured: ${configError?.message ?? "unknown error"}
|
|
39436
|
+
[infinitecampus-mcp] Server is running with no tools registered. Set the required env vars and reinstall.`;
|
|
39437
|
+
await runMcp({ ...COMMON, tools: [], banner: `${notConfigured}
|
|
39438
|
+
${AI_NOTICE}` });
|
|
38603
39439
|
}
|
|
38604
|
-
console.error("[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.");
|
|
38605
|
-
var transport = new StdioServerTransport();
|
|
38606
|
-
await server.connect(transport);
|