infinitecampus-mcp 2.3.0 → 2.3.2
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 +795 -279
- package/dist/client.js +15 -30
- 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";
|
|
@@ -34707,6 +34938,16 @@ function assertScopeKeyArray(value, label) {
|
|
|
34707
34938
|
seen.add(k);
|
|
34708
34939
|
}
|
|
34709
34940
|
}
|
|
34941
|
+
var CAPTURE_PATH_RE = /^\/[A-Za-z0-9._~%\-/]*\*?$/;
|
|
34942
|
+
function hostMatchesAnyDomain(host, domains) {
|
|
34943
|
+
const h = host.toLowerCase();
|
|
34944
|
+
for (const d of domains) {
|
|
34945
|
+
const dom = d.toLowerCase();
|
|
34946
|
+
if (h === dom || h.endsWith("." + dom))
|
|
34947
|
+
return true;
|
|
34948
|
+
}
|
|
34949
|
+
return false;
|
|
34950
|
+
}
|
|
34710
34951
|
function assertCaptureHeadersArray(value, label) {
|
|
34711
34952
|
if (!Array.isArray(value)) {
|
|
34712
34953
|
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
@@ -34715,14 +34956,25 @@ function assertCaptureHeadersArray(value, label) {
|
|
|
34715
34956
|
for (let i = 0; i < value.length; i++) {
|
|
34716
34957
|
const entry = value[i];
|
|
34717
34958
|
assertObject(entry, `${label}[${i}]`);
|
|
34718
|
-
if (entry.
|
|
34719
|
-
throw new ProtocolError(`${label}[${i}].
|
|
34959
|
+
if (entry.host === void 0) {
|
|
34960
|
+
throw new ProtocolError(`${label}[${i}].host: missing`);
|
|
34720
34961
|
}
|
|
34721
34962
|
if (entry.headerName === void 0) {
|
|
34722
34963
|
throw new ProtocolError(`${label}[${i}].headerName: missing`);
|
|
34723
34964
|
}
|
|
34724
|
-
if (typeof entry.
|
|
34725
|
-
throw new ProtocolError(`${label}[${i}].
|
|
34965
|
+
if (typeof entry.host !== "string") {
|
|
34966
|
+
throw new ProtocolError(`${label}[${i}].host: expected string, got ${typeof entry.host}`);
|
|
34967
|
+
}
|
|
34968
|
+
if (!HOSTNAME_RE.test(entry.host)) {
|
|
34969
|
+
throw new ProtocolError(`${label}[${i}].host: invalid hostname ${JSON.stringify(entry.host)}`);
|
|
34970
|
+
}
|
|
34971
|
+
if (entry.path !== void 0) {
|
|
34972
|
+
if (typeof entry.path !== "string") {
|
|
34973
|
+
throw new ProtocolError(`${label}[${i}].path: expected string, got ${typeof entry.path}`);
|
|
34974
|
+
}
|
|
34975
|
+
if (!CAPTURE_PATH_RE.test(entry.path)) {
|
|
34976
|
+
throw new ProtocolError(`${label}[${i}].path: must start with '/' ${JSON.stringify(entry.path)}`);
|
|
34977
|
+
}
|
|
34726
34978
|
}
|
|
34727
34979
|
if (typeof entry.headerName !== "string") {
|
|
34728
34980
|
throw new ProtocolError(`${label}[${i}].headerName: expected string, got ${typeof entry.headerName}`);
|
|
@@ -34730,19 +34982,29 @@ function assertCaptureHeadersArray(value, label) {
|
|
|
34730
34982
|
if (!HEADER_NAME_RE.test(entry.headerName)) {
|
|
34731
34983
|
throw new ProtocolError(`${label}[${i}].headerName: invalid name ${JSON.stringify(entry.headerName)}`);
|
|
34732
34984
|
}
|
|
34733
|
-
|
|
34734
|
-
const key = `${entry.
|
|
34985
|
+
const normalizedPath = entry.path ?? "/*";
|
|
34986
|
+
const key = `${entry.host}\0${normalizedPath}\0${entry.headerName}`;
|
|
34735
34987
|
if (seen.has(key)) {
|
|
34736
|
-
throw new ProtocolError(`${label}: duplicate ${JSON.stringify({
|
|
34988
|
+
throw new ProtocolError(`${label}: duplicate ${JSON.stringify({ host: entry.host, path: normalizedPath, headerName: entry.headerName })}`);
|
|
34737
34989
|
}
|
|
34738
34990
|
seen.add(key);
|
|
34739
34991
|
for (const k of Object.keys(entry)) {
|
|
34740
|
-
if (k !== "
|
|
34992
|
+
if (k !== "host" && k !== "path" && k !== "headerName") {
|
|
34741
34993
|
throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
34742
34994
|
}
|
|
34743
34995
|
}
|
|
34744
34996
|
}
|
|
34745
34997
|
}
|
|
34998
|
+
function validateCaptureHeaderDecls(value, domains, label = "captureHeaders") {
|
|
34999
|
+
assertCaptureHeadersArray(value, label);
|
|
35000
|
+
const arr = value;
|
|
35001
|
+
for (let i = 0; i < arr.length; i++) {
|
|
35002
|
+
const host = arr[i].host;
|
|
35003
|
+
if (!hostMatchesAnyDomain(host, domains)) {
|
|
35004
|
+
throw new ProtocolError(`${label}[${i}].host: ${JSON.stringify(host)} is not a declared domain or subdomain of [${domains.join(", ")}]`);
|
|
35005
|
+
}
|
|
35006
|
+
}
|
|
35007
|
+
}
|
|
34746
35008
|
function assertStoragePointersArray(value, label, declaredKeys) {
|
|
34747
35009
|
if (!Array.isArray(value)) {
|
|
34748
35010
|
throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
|
|
@@ -34830,23 +35092,6 @@ function assertIndexedDbScopesArray(value, label) {
|
|
|
34830
35092
|
}
|
|
34831
35093
|
}
|
|
34832
35094
|
}
|
|
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
35095
|
function validateFrame(raw) {
|
|
34851
35096
|
assertObject(raw, "frame");
|
|
34852
35097
|
const t = raw.type;
|
|
@@ -34911,7 +35156,7 @@ function validateHello(raw) {
|
|
|
34911
35156
|
assertScopeKeyArray(raw.sessionStorageKeys, "hello.sessionStorageKeys");
|
|
34912
35157
|
}
|
|
34913
35158
|
if (raw.captureHeaders !== void 0) {
|
|
34914
|
-
|
|
35159
|
+
validateCaptureHeaderDecls(raw.captureHeaders, raw.domains, "hello.captureHeaders");
|
|
34915
35160
|
}
|
|
34916
35161
|
if (raw.indexedDbScopes !== void 0) {
|
|
34917
35162
|
assertIndexedDbScopesArray(raw.indexedDbScopes, "hello.indexedDbScopes");
|
|
@@ -35079,19 +35324,28 @@ function validateInnerRequest(raw) {
|
|
|
35079
35324
|
}
|
|
35080
35325
|
if (raw.op === "capture_request_header") {
|
|
35081
35326
|
assertObject(raw.init, "inner.init");
|
|
35082
|
-
if (raw.init.
|
|
35083
|
-
throw new ProtocolError("inner.init.
|
|
35327
|
+
if (raw.init.host === void 0) {
|
|
35328
|
+
throw new ProtocolError("inner.init.host: missing");
|
|
35084
35329
|
}
|
|
35085
35330
|
if (raw.init.headerName === void 0) {
|
|
35086
35331
|
throw new ProtocolError("inner.init.headerName: missing");
|
|
35087
35332
|
}
|
|
35088
|
-
assertString(raw.init.
|
|
35333
|
+
assertString(raw.init.host, "inner.init.host");
|
|
35334
|
+
if (!HOSTNAME_RE.test(raw.init.host)) {
|
|
35335
|
+
throw new ProtocolError(`inner.init.host: invalid hostname ${JSON.stringify(raw.init.host)}`);
|
|
35336
|
+
}
|
|
35337
|
+
if (raw.init.path !== void 0) {
|
|
35338
|
+
assertString(raw.init.path, "inner.init.path");
|
|
35339
|
+
if (!CAPTURE_PATH_RE.test(raw.init.path)) {
|
|
35340
|
+
throw new ProtocolError(`inner.init.path: must start with '/' ${JSON.stringify(raw.init.path)}`);
|
|
35341
|
+
}
|
|
35342
|
+
}
|
|
35089
35343
|
assertString(raw.init.headerName, "inner.init.headerName");
|
|
35090
35344
|
if (raw.init.timeoutMs !== void 0) {
|
|
35091
35345
|
assertPositiveInt(raw.init.timeoutMs, "inner.init.timeoutMs");
|
|
35092
35346
|
}
|
|
35093
35347
|
for (const k of Object.keys(raw.init)) {
|
|
35094
|
-
if (k !== "
|
|
35348
|
+
if (k !== "host" && k !== "path" && k !== "headerName" && k !== "timeoutMs") {
|
|
35095
35349
|
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on capture_request_header`);
|
|
35096
35350
|
}
|
|
35097
35351
|
}
|
|
@@ -35407,13 +35661,13 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
35407
35661
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
35408
35662
|
import { createServer, Server as HttpServer } from "node:http";
|
|
35409
35663
|
async function electRole(opts) {
|
|
35410
|
-
const
|
|
35664
|
+
const server = createServer();
|
|
35411
35665
|
return new Promise((resolve, reject) => {
|
|
35412
35666
|
const onError = (e) => {
|
|
35413
|
-
|
|
35667
|
+
server.removeListener("listening", onListening);
|
|
35414
35668
|
if (e.code === "EADDRINUSE") {
|
|
35415
35669
|
try {
|
|
35416
|
-
|
|
35670
|
+
server.close();
|
|
35417
35671
|
} catch {
|
|
35418
35672
|
}
|
|
35419
35673
|
resolve({ role: "peer" });
|
|
@@ -35422,12 +35676,12 @@ async function electRole(opts) {
|
|
|
35422
35676
|
}
|
|
35423
35677
|
};
|
|
35424
35678
|
const onListening = () => {
|
|
35425
|
-
|
|
35426
|
-
resolve({ role: "host", server
|
|
35679
|
+
server.removeListener("error", onError);
|
|
35680
|
+
resolve({ role: "host", server });
|
|
35427
35681
|
};
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35682
|
+
server.once("error", onError);
|
|
35683
|
+
server.once("listening", onListening);
|
|
35684
|
+
server.listen(opts.port, opts.host);
|
|
35431
35685
|
});
|
|
35432
35686
|
}
|
|
35433
35687
|
|
|
@@ -35471,7 +35725,8 @@ async function buildServerHello(opts) {
|
|
|
35471
35725
|
}
|
|
35472
35726
|
if (opts.captureHeaders && opts.captureHeaders.length > 0) {
|
|
35473
35727
|
hello.captureHeaders = opts.captureHeaders.map((d) => ({
|
|
35474
|
-
|
|
35728
|
+
host: d.host,
|
|
35729
|
+
...d.path !== void 0 ? { path: d.path } : {},
|
|
35475
35730
|
headerName: d.headerName
|
|
35476
35731
|
}));
|
|
35477
35732
|
}
|
|
@@ -35709,7 +35964,9 @@ async function startHost(opts) {
|
|
|
35709
35964
|
} catch {
|
|
35710
35965
|
}
|
|
35711
35966
|
}
|
|
35712
|
-
wss.close(() =>
|
|
35967
|
+
wss.close(() => {
|
|
35968
|
+
opts.httpServer.close(() => resolve());
|
|
35969
|
+
});
|
|
35713
35970
|
}),
|
|
35714
35971
|
sendOwnInner: async (inner) => {
|
|
35715
35972
|
const session = await ownSessionReady;
|
|
@@ -35759,6 +36016,7 @@ async function startPeer(opts) {
|
|
|
35759
36016
|
const innerListeners = [];
|
|
35760
36017
|
const renegotiateListeners = [];
|
|
35761
36018
|
const pendingPairListeners = [];
|
|
36019
|
+
const closeListeners = [];
|
|
35762
36020
|
let session = null;
|
|
35763
36021
|
let pendingPairCode = null;
|
|
35764
36022
|
let resolveFirstReady;
|
|
@@ -35808,6 +36066,7 @@ async function startPeer(opts) {
|
|
|
35808
36066
|
ws.on("message", onMessage);
|
|
35809
36067
|
ws.once("close", () => {
|
|
35810
36068
|
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
36069
|
+
closeListeners.forEach((cb) => cb());
|
|
35811
36070
|
});
|
|
35812
36071
|
sessionPromise.catch(() => {
|
|
35813
36072
|
});
|
|
@@ -35830,6 +36089,9 @@ async function startPeer(opts) {
|
|
|
35830
36089
|
pendingPairListeners.push(cb);
|
|
35831
36090
|
},
|
|
35832
36091
|
pendingPairCode: () => pendingPairCode,
|
|
36092
|
+
onClose: (cb) => {
|
|
36093
|
+
closeListeners.push(cb);
|
|
36094
|
+
},
|
|
35833
36095
|
close: () => ws.close()
|
|
35834
36096
|
};
|
|
35835
36097
|
return handle;
|
|
@@ -35912,6 +36174,19 @@ function classifyFetchError(error51) {
|
|
|
35912
36174
|
return "other";
|
|
35913
36175
|
}
|
|
35914
36176
|
|
|
36177
|
+
// node_modules/@fetchproxy/server/dist/classify-bridge-error.js
|
|
36178
|
+
function classifyBridgeError(err) {
|
|
36179
|
+
if (err instanceof FetchproxyTimeoutError)
|
|
36180
|
+
return "timeout";
|
|
36181
|
+
if (err instanceof FetchproxyBridgeDownError)
|
|
36182
|
+
return "bridge_down";
|
|
36183
|
+
if (err instanceof FetchproxyHttpError)
|
|
36184
|
+
return "http";
|
|
36185
|
+
if (err instanceof FetchproxyProtocolError)
|
|
36186
|
+
return "protocol";
|
|
36187
|
+
return "other";
|
|
36188
|
+
}
|
|
36189
|
+
|
|
35915
36190
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
35916
36191
|
var FetchproxyProtocolError = class extends Error {
|
|
35917
36192
|
constructor(message) {
|
|
@@ -35941,7 +36216,7 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
35941
36216
|
const retryAttempted = args.retryAttempted ?? false;
|
|
35942
36217
|
const op = args.op ?? "fetch";
|
|
35943
36218
|
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}
|
|
36219
|
+
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
36220
|
super(`fetchproxy bridge down during ${op}${args.url ? ` (${args.url})` : ""}. ${hint}`);
|
|
35946
36221
|
this.name = "FetchproxyBridgeDownError";
|
|
35947
36222
|
this.originalError = args.originalError;
|
|
@@ -35963,6 +36238,14 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
35963
36238
|
port;
|
|
35964
36239
|
/** 0.8.0+: actual elapsed milliseconds when the timer won the race. */
|
|
35965
36240
|
elapsedMs;
|
|
36241
|
+
/**
|
|
36242
|
+
* 0.11.0+ (#90/#91): true when the server's lazy-revive retry path
|
|
36243
|
+
* fired for this timeout (a cold-start `timeout` symptom followed by
|
|
36244
|
+
* a warm-and-retry that also timed out). False when the retry was
|
|
36245
|
+
* disabled (`bridgeReviveDelayMs` unset/0) so the timeout surfaced on
|
|
36246
|
+
* the first attempt.
|
|
36247
|
+
*/
|
|
36248
|
+
retryAttempted;
|
|
35966
36249
|
constructor(args) {
|
|
35967
36250
|
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms`);
|
|
35968
36251
|
this.name = "FetchproxyTimeoutError";
|
|
@@ -35971,6 +36254,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
35971
36254
|
this.role = args.role ?? null;
|
|
35972
36255
|
this.port = args.port ?? 0;
|
|
35973
36256
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
36257
|
+
this.retryAttempted = args.retryAttempted ?? false;
|
|
35974
36258
|
}
|
|
35975
36259
|
};
|
|
35976
36260
|
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 +36293,12 @@ var FetchproxyServer = class {
|
|
|
36009
36293
|
opts;
|
|
36010
36294
|
hostHandle = null;
|
|
36011
36295
|
peerHandle = null;
|
|
36296
|
+
// 0.13.0+: true from the start of `close()` until the next `doConnect()`.
|
|
36297
|
+
// Distinguishes an intentional shutdown (whose WS close we must ignore)
|
|
36298
|
+
// from the host process dying (which strands a peer and must trigger
|
|
36299
|
+
// re-election). The WS `close` event fires asynchronously, so this stays
|
|
36300
|
+
// latched across `close()` rather than being reset in its tail.
|
|
36301
|
+
closing = false;
|
|
36012
36302
|
nextRequestId = 1;
|
|
36013
36303
|
// 0.8.0+: process-wide freshness counters surfaced via bridgeHealth().
|
|
36014
36304
|
// Replaces the local copies every downstream MCP was rolling on top
|
|
@@ -36044,6 +36334,25 @@ var FetchproxyServer = class {
|
|
|
36044
36334
|
// for "we're connecting right now" so two parallel first-calls don't
|
|
36045
36335
|
// race the port bind.
|
|
36046
36336
|
connectingPromise = null;
|
|
36337
|
+
// 0.8.1+ (#67): server-initiated keep-alive ping. Active when
|
|
36338
|
+
// `keepAliveIntervalMs` is set AND we've seen recent activity
|
|
36339
|
+
// (fetch/capture success or failure, or markActive()) within
|
|
36340
|
+
// `keepAliveMaxIdleMs`. The interval handle is created lazily on
|
|
36341
|
+
// first activity and torn down on close() / extension disconnect.
|
|
36342
|
+
keepAliveTimer = null;
|
|
36343
|
+
lastActiveAt = null;
|
|
36344
|
+
// 0.10.0+ (#73): observability counters surfaced via
|
|
36345
|
+
// bridgeHealth().keepAlive / .swEviction. Monotonic across the process
|
|
36346
|
+
// lifetime so a downstream healthcheck tool can verify the keep-alive
|
|
36347
|
+
// is actually preventing SW eviction. `lastPingAt` and `totalPings`
|
|
36348
|
+
// are stamped from `startKeepaliveIfIdle`'s tick. `lazyRevive*` and
|
|
36349
|
+
// `lastEvictionDetectedAt` are stamped from the lazy-revive code path
|
|
36350
|
+
// in fetch() / captureRequestHeader().
|
|
36351
|
+
lastPingAt = null;
|
|
36352
|
+
totalPings = 0;
|
|
36353
|
+
lazyReviveAttempts = 0;
|
|
36354
|
+
lazyReviveSuccesses = 0;
|
|
36355
|
+
lastEvictionDetectedAt = null;
|
|
36047
36356
|
constructor(opts) {
|
|
36048
36357
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
36049
36358
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
@@ -36062,6 +36371,14 @@ var FetchproxyServer = class {
|
|
|
36062
36371
|
}
|
|
36063
36372
|
capabilities = [...opts.capabilities];
|
|
36064
36373
|
}
|
|
36374
|
+
if (opts.captureHeaders !== void 0) {
|
|
36375
|
+
try {
|
|
36376
|
+
validateCaptureHeaderDecls(opts.captureHeaders, opts.domains);
|
|
36377
|
+
} catch (err) {
|
|
36378
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36379
|
+
throw new Error("FetchproxyServer: invalid captureHeaders \u2014 " + message);
|
|
36380
|
+
}
|
|
36381
|
+
}
|
|
36065
36382
|
this.opts = {
|
|
36066
36383
|
port: opts.port ?? 37149,
|
|
36067
36384
|
host: opts.host ?? "127.0.0.1",
|
|
@@ -36073,7 +36390,8 @@ var FetchproxyServer = class {
|
|
|
36073
36390
|
localStorageKeys: [...opts.localStorageKeys ?? []],
|
|
36074
36391
|
sessionStorageKeys: [...opts.sessionStorageKeys ?? []],
|
|
36075
36392
|
captureHeaders: (opts.captureHeaders ?? []).map((d) => ({
|
|
36076
|
-
|
|
36393
|
+
host: d.host,
|
|
36394
|
+
...d.path !== void 0 ? { path: d.path } : {},
|
|
36077
36395
|
headerName: d.headerName
|
|
36078
36396
|
})),
|
|
36079
36397
|
indexedDbScopes: (opts.indexedDbScopes ?? []).map((d) => ({
|
|
@@ -36096,6 +36414,22 @@ var FetchproxyServer = class {
|
|
|
36096
36414
|
// the legacy hang-forever / fail-once-on-SW-eviction behavior.
|
|
36097
36415
|
fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
|
|
36098
36416
|
bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
|
|
36417
|
+
// 0.10.0+ (#72): keep-alive defaults to 25s — round-3 #71 cohort
|
|
36418
|
+
// wave showed every Pattern A consumer was opting into this same
|
|
36419
|
+
// value. Pass `0` to disable; the existing `<= 0` guards in
|
|
36420
|
+
// `startKeepaliveIfIdle` / `noteActivityForKeepalive` honour that.
|
|
36421
|
+
//
|
|
36422
|
+
// #90 (P1-1): tightened to 20s. 25s left only ~5s of slack under
|
|
36423
|
+
// Chrome's ~30s SW-eviction window — slack that timer drift, a
|
|
36424
|
+
// busy host event loop (CPU-bound response parsing between calls),
|
|
36425
|
+
// and the ping's own round-trip latency routinely ate, so the SW
|
|
36426
|
+
// evicted before the next ping landed and the next call cold-
|
|
36427
|
+
// started. 20s restores real margin. (The extension
|
|
36428
|
+
// `chrome.alarms` backstop is clamped by Chrome to a 30s minimum
|
|
36429
|
+
// period, firing *at* the edge — it can't rescue a sub-30s race;
|
|
36430
|
+
// the server ping is the real defense.)
|
|
36431
|
+
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
36432
|
+
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
36099
36433
|
identityDir: opts.identityDir,
|
|
36100
36434
|
onPairCode: opts.onPairCode
|
|
36101
36435
|
};
|
|
@@ -36175,6 +36509,7 @@ var FetchproxyServer = class {
|
|
|
36175
36509
|
async doConnect() {
|
|
36176
36510
|
const identity = this.identity;
|
|
36177
36511
|
const mcpId = this.mcpId;
|
|
36512
|
+
this.closing = false;
|
|
36178
36513
|
const el = await electRole({ host: this.opts.host, port: this.opts.port });
|
|
36179
36514
|
if (el.role === "host") {
|
|
36180
36515
|
this.role = "host";
|
|
@@ -36196,7 +36531,10 @@ var FetchproxyServer = class {
|
|
|
36196
36531
|
onPairCode: this.opts.onPairCode
|
|
36197
36532
|
});
|
|
36198
36533
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
36199
|
-
this.hostHandle.onExtensionDisconnect(() =>
|
|
36534
|
+
this.hostHandle.onExtensionDisconnect(() => {
|
|
36535
|
+
this.stopKeepalive();
|
|
36536
|
+
this.rejectAllPending();
|
|
36537
|
+
});
|
|
36200
36538
|
this.hostHandle.onPendingPair((code) => {
|
|
36201
36539
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36202
36540
|
});
|
|
@@ -36220,7 +36558,10 @@ var FetchproxyServer = class {
|
|
|
36220
36558
|
sessionStoragePointers: this.opts.sessionStoragePointers
|
|
36221
36559
|
});
|
|
36222
36560
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
36223
|
-
this.peerHandle.onRenegotiate(() =>
|
|
36561
|
+
this.peerHandle.onRenegotiate(() => {
|
|
36562
|
+
this.stopKeepalive();
|
|
36563
|
+
this.rejectAllPending();
|
|
36564
|
+
});
|
|
36224
36565
|
this.peerHandle.onPendingPair((code) => {
|
|
36225
36566
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36226
36567
|
});
|
|
@@ -36228,6 +36569,14 @@ var FetchproxyServer = class {
|
|
|
36228
36569
|
const cb = this.opts.onPairCode;
|
|
36229
36570
|
this.peerHandle.onPendingPair((code) => cb(code));
|
|
36230
36571
|
}
|
|
36572
|
+
this.peerHandle.onClose(() => {
|
|
36573
|
+
if (this.closing || this.peerHandle === null)
|
|
36574
|
+
return;
|
|
36575
|
+
this.stopKeepalive();
|
|
36576
|
+
this.rejectAllPending();
|
|
36577
|
+
this.peerHandle = null;
|
|
36578
|
+
this.role = null;
|
|
36579
|
+
});
|
|
36231
36580
|
}
|
|
36232
36581
|
}
|
|
36233
36582
|
pairingErrorMessage(code) {
|
|
@@ -36261,13 +36610,18 @@ var FetchproxyServer = class {
|
|
|
36261
36610
|
}
|
|
36262
36611
|
const first = await this._fetchOnceWithTimeout(init);
|
|
36263
36612
|
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
36264
|
-
|
|
36265
|
-
if (
|
|
36613
|
+
const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
|
|
36614
|
+
if (isColdStartSymptom) {
|
|
36615
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
36616
|
+
}
|
|
36617
|
+
if (isColdStartSymptom && reviveMs !== void 0 && reviveMs > 0) {
|
|
36618
|
+
this.lazyReviveAttempts += 1;
|
|
36266
36619
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36267
36620
|
const second = await this._fetchOnceWithTimeout(init);
|
|
36268
|
-
if (second.ok)
|
|
36621
|
+
if (second.ok) {
|
|
36622
|
+
this.lazyReviveSuccesses += 1;
|
|
36269
36623
|
this.recordSuccess();
|
|
36270
|
-
else
|
|
36624
|
+
} else
|
|
36271
36625
|
this.recordFailure(`${second.kind ?? "other"}: ${second.error}`);
|
|
36272
36626
|
return { ...second, retryAttempted: true };
|
|
36273
36627
|
}
|
|
@@ -36289,6 +36643,9 @@ var FetchproxyServer = class {
|
|
|
36289
36643
|
* call (addresses #23 ask 4).
|
|
36290
36644
|
*/
|
|
36291
36645
|
bridgeHealth() {
|
|
36646
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36647
|
+
const maxIdleMs = this.opts.keepAliveMaxIdleMs;
|
|
36648
|
+
const idleSinceMs = this.lastActiveAt === null ? null : Date.now() - this.lastActiveAt;
|
|
36292
36649
|
return {
|
|
36293
36650
|
role: this.role,
|
|
36294
36651
|
port: this.opts.port,
|
|
@@ -36299,17 +36656,85 @@ var FetchproxyServer = class {
|
|
|
36299
36656
|
lastFailureAt: this.lastFailureAt,
|
|
36300
36657
|
lastFailureReason: this.lastFailureReason,
|
|
36301
36658
|
consecutiveFailures: this.consecutiveFailures,
|
|
36302
|
-
lastExtensionMessageAt: this.lastExtensionMessageAt
|
|
36659
|
+
lastExtensionMessageAt: this.lastExtensionMessageAt,
|
|
36660
|
+
keepAlive: {
|
|
36661
|
+
enabled: intervalMs > 0,
|
|
36662
|
+
intervalMs,
|
|
36663
|
+
maxIdleMs,
|
|
36664
|
+
lastPingAt: this.lastPingAt,
|
|
36665
|
+
totalPings: this.totalPings,
|
|
36666
|
+
idleSinceMs
|
|
36667
|
+
},
|
|
36668
|
+
swEviction: {
|
|
36669
|
+
lazyReviveAttempts: this.lazyReviveAttempts,
|
|
36670
|
+
lazyReviveSuccesses: this.lazyReviveSuccesses,
|
|
36671
|
+
lastEvictionDetectedAt: this.lastEvictionDetectedAt
|
|
36672
|
+
}
|
|
36303
36673
|
};
|
|
36304
36674
|
}
|
|
36305
36675
|
recordSuccess() {
|
|
36306
36676
|
this.lastSuccessAt = Date.now();
|
|
36307
36677
|
this.consecutiveFailures = 0;
|
|
36678
|
+
this.noteActivityForKeepalive();
|
|
36308
36679
|
}
|
|
36309
36680
|
recordFailure(reason) {
|
|
36310
36681
|
this.lastFailureAt = Date.now();
|
|
36311
36682
|
this.lastFailureReason = reason;
|
|
36312
36683
|
this.consecutiveFailures += 1;
|
|
36684
|
+
this.noteActivityForKeepalive();
|
|
36685
|
+
}
|
|
36686
|
+
/**
|
|
36687
|
+
* 0.8.1+ (#67): caller-side hint that work is happening or about to
|
|
36688
|
+
* happen — bumps the keep-alive idle gate so the server keeps pinging
|
|
36689
|
+
* the extension. Useful for MCPs that do a chain of side-effectful
|
|
36690
|
+
* work between bridge calls and don't want the SW to evict in the
|
|
36691
|
+
* gap (e.g. server-side parsing of a previous response that takes
|
|
36692
|
+
* tens of seconds). No-op when `keepAliveIntervalMs` is `0`.
|
|
36693
|
+
*/
|
|
36694
|
+
markActive() {
|
|
36695
|
+
this.noteActivityForKeepalive();
|
|
36696
|
+
}
|
|
36697
|
+
noteActivityForKeepalive() {
|
|
36698
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36699
|
+
if (intervalMs <= 0)
|
|
36700
|
+
return;
|
|
36701
|
+
this.lastActiveAt = Date.now();
|
|
36702
|
+
this.startKeepaliveIfIdle();
|
|
36703
|
+
}
|
|
36704
|
+
startKeepaliveIfIdle() {
|
|
36705
|
+
if (this.keepAliveTimer !== null)
|
|
36706
|
+
return;
|
|
36707
|
+
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
36708
|
+
if (intervalMs <= 0)
|
|
36709
|
+
return;
|
|
36710
|
+
this.keepAliveTimer = setInterval(() => {
|
|
36711
|
+
const now = Date.now();
|
|
36712
|
+
if (this.lastActiveAt === null || now - this.lastActiveAt > this.opts.keepAliveMaxIdleMs) {
|
|
36713
|
+
this.stopKeepalive();
|
|
36714
|
+
return;
|
|
36715
|
+
}
|
|
36716
|
+
this.totalPings += 1;
|
|
36717
|
+
this.lastPingAt = now;
|
|
36718
|
+
void this.sendKeepalivePing();
|
|
36719
|
+
}, intervalMs);
|
|
36720
|
+
}
|
|
36721
|
+
async sendKeepalivePing() {
|
|
36722
|
+
try {
|
|
36723
|
+
const inner = { type: "ping" };
|
|
36724
|
+
if (this.hostHandle) {
|
|
36725
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
36726
|
+
} else if (this.peerHandle) {
|
|
36727
|
+
await this.peerHandle.sendInner(inner);
|
|
36728
|
+
}
|
|
36729
|
+
} catch (e) {
|
|
36730
|
+
console.error("[fetchproxy] keepalive ping send failed:", e);
|
|
36731
|
+
}
|
|
36732
|
+
}
|
|
36733
|
+
stopKeepalive() {
|
|
36734
|
+
if (this.keepAliveTimer !== null) {
|
|
36735
|
+
clearInterval(this.keepAliveTimer);
|
|
36736
|
+
this.keepAliveTimer = null;
|
|
36737
|
+
}
|
|
36313
36738
|
}
|
|
36314
36739
|
/**
|
|
36315
36740
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
@@ -36368,7 +36793,8 @@ var FetchproxyServer = class {
|
|
|
36368
36793
|
timeoutMs: this.opts.fetchTimeoutMs ?? 0,
|
|
36369
36794
|
role: this.role,
|
|
36370
36795
|
port: this.opts.port,
|
|
36371
|
-
elapsedMs: result.elapsedMs
|
|
36796
|
+
elapsedMs: result.elapsedMs,
|
|
36797
|
+
retryAttempted
|
|
36372
36798
|
});
|
|
36373
36799
|
}
|
|
36374
36800
|
if (result.kind === "content_script_unreachable") {
|
|
@@ -36499,6 +36925,108 @@ var FetchproxyServer = class {
|
|
|
36499
36925
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
36500
36926
|
return response.body;
|
|
36501
36927
|
}
|
|
36928
|
+
/**
|
|
36929
|
+
* 0.11.0+: method-generic JSON convenience helper. Generalizes the
|
|
36930
|
+
* `fetchJson<T>(path, { method, headers, body })` that
|
|
36931
|
+
* zillow/redfin/compass/homes hand-rolled char-for-char in their
|
|
36932
|
+
* `src/client.ts`:
|
|
36933
|
+
*
|
|
36934
|
+
* - sets `Accept: application/json`;
|
|
36935
|
+
* - adds `Content-Type: application/json` only for a non-GET request
|
|
36936
|
+
* that carries a `body` (and only if the caller didn't set one);
|
|
36937
|
+
* - `JSON.stringify`s the body (GET / no-body sends nothing);
|
|
36938
|
+
* - treats a `204` or an empty body as `data: null` (no parse);
|
|
36939
|
+
* - otherwise `JSON.parse`s the body.
|
|
36940
|
+
*
|
|
36941
|
+
* Scope is serialization + header defaults + 204-handling +
|
|
36942
|
+
* JSON.parse ONLY. It deliberately does NOT assert on the HTTP status
|
|
36943
|
+
* or look for a sign-in interstitial — those guards differ per site
|
|
36944
|
+
* (Zillow's `captcha-delivery`, Redfin's AWS-WAF challenge, …), so it
|
|
36945
|
+
* returns BOTH the parsed `data` and the raw `FetchResult` and leaves
|
|
36946
|
+
* the consumer to run its own `throwIfNotOk` / `throwIfSignInPage`
|
|
36947
|
+
* over `result`.
|
|
36948
|
+
*
|
|
36949
|
+
* Bridge-level failures (no signed-in tab, SW down, timeout) still
|
|
36950
|
+
* throw the typed errors via `request()`, exactly like the verb
|
|
36951
|
+
* helpers — only successful round-trips (any HTTP status) return.
|
|
36952
|
+
*/
|
|
36953
|
+
async requestJson(method, path, opts = {}) {
|
|
36954
|
+
const isGet = method.toUpperCase() === "GET";
|
|
36955
|
+
const sendBody = !isGet && opts.body !== void 0;
|
|
36956
|
+
const headers = {
|
|
36957
|
+
Accept: "application/json",
|
|
36958
|
+
...sendBody && !this.hasContentType(opts.headers ?? {}) ? { "Content-Type": "application/json" } : {},
|
|
36959
|
+
...opts.headers ?? {}
|
|
36960
|
+
};
|
|
36961
|
+
const response = await this.request(method, path, {
|
|
36962
|
+
headers,
|
|
36963
|
+
body: sendBody ? JSON.stringify(opts.body) : void 0,
|
|
36964
|
+
...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
|
|
36965
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
36966
|
+
});
|
|
36967
|
+
const result = {
|
|
36968
|
+
ok: true,
|
|
36969
|
+
status: response.status,
|
|
36970
|
+
url: response.url,
|
|
36971
|
+
body: response.body
|
|
36972
|
+
};
|
|
36973
|
+
if (response.status === 204 || response.body === "") {
|
|
36974
|
+
return { data: null, result };
|
|
36975
|
+
}
|
|
36976
|
+
let data;
|
|
36977
|
+
try {
|
|
36978
|
+
data = JSON.parse(response.body);
|
|
36979
|
+
} catch (e) {
|
|
36980
|
+
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
36981
|
+
}
|
|
36982
|
+
return { data, result };
|
|
36983
|
+
}
|
|
36984
|
+
/**
|
|
36985
|
+
* 0.11.0+: run a single healthcheck probe through `fetchFn`, measure
|
|
36986
|
+
* the elapsed round-trip, classify any thrown error, and project the
|
|
36987
|
+
* post-probe `bridgeHealth()` into a snake-cased `bridge` sub-object.
|
|
36988
|
+
*
|
|
36989
|
+
* This is the transport half of the probe loop zillow/redfin/homes
|
|
36990
|
+
* had duplicated verbatim in `src/tools/healthcheck.ts`. The MCP
|
|
36991
|
+
* supplies its own probe call (`(path) => client.fetchHtml(path)`)
|
|
36992
|
+
* and probe path (e.g. `'/robots.txt'`); the tool registration and
|
|
36993
|
+
* the site-specific plain-English hint text STAY in the consumer.
|
|
36994
|
+
*
|
|
36995
|
+
* `bridgeHealth()` is read AFTER the probe so its freshness counters
|
|
36996
|
+
* (`lastSuccessAt` / `consecutiveFailures` / …) reflect this very
|
|
36997
|
+
* round-trip rather than a stale pre-probe snapshot.
|
|
36998
|
+
*/
|
|
36999
|
+
async runProbe(fetchFn, probePath) {
|
|
37000
|
+
const start = Date.now();
|
|
37001
|
+
let ok = false;
|
|
37002
|
+
let error51;
|
|
37003
|
+
try {
|
|
37004
|
+
await fetchFn(probePath);
|
|
37005
|
+
ok = true;
|
|
37006
|
+
} catch (e) {
|
|
37007
|
+
error51 = {
|
|
37008
|
+
kind: classifyBridgeError(e),
|
|
37009
|
+
message: e instanceof Error ? e.message : String(e)
|
|
37010
|
+
};
|
|
37011
|
+
}
|
|
37012
|
+
const elapsed_ms = Date.now() - start;
|
|
37013
|
+
const health = this.bridgeHealth();
|
|
37014
|
+
return {
|
|
37015
|
+
ok,
|
|
37016
|
+
elapsed_ms,
|
|
37017
|
+
bridge: {
|
|
37018
|
+
role: health.role,
|
|
37019
|
+
port: health.port,
|
|
37020
|
+
server_version: health.serverVersion,
|
|
37021
|
+
fetch_timeout_ms: health.fetchTimeoutMs,
|
|
37022
|
+
last_success_at: health.lastSuccessAt,
|
|
37023
|
+
last_failure_at: health.lastFailureAt,
|
|
37024
|
+
last_failure_reason: health.lastFailureReason,
|
|
37025
|
+
consecutive_failures: health.consecutiveFailures
|
|
37026
|
+
},
|
|
37027
|
+
...error51 ? { error: error51 } : {}
|
|
37028
|
+
};
|
|
37029
|
+
}
|
|
36502
37030
|
/**
|
|
36503
37031
|
* Snapshot the user's non-HttpOnly cookies for the chosen domain.
|
|
36504
37032
|
*
|
|
@@ -36623,12 +37151,12 @@ var FetchproxyServer = class {
|
|
|
36623
37151
|
/**
|
|
36624
37152
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
36625
37153
|
* 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).
|
|
37154
|
+
* filtered on `https://${host}${path ?? '/*'}`, captures the named
|
|
37155
|
+
* header on the first match, removes itself, and resolves with the
|
|
37156
|
+
* value. Times out after `timeoutMs` (default 30s on the extension).
|
|
36629
37157
|
*
|
|
36630
|
-
* `(
|
|
36631
|
-
* `FetchproxyServerOpts.captureHeaders
|
|
37158
|
+
* `(host, path?, headerName)` must match a declared entry in
|
|
37159
|
+
* `FetchproxyServerOpts.captureHeaders` (omitted path ≡ `/*`).
|
|
36632
37160
|
*/
|
|
36633
37161
|
async captureRequestHeader(opts) {
|
|
36634
37162
|
if (!this.opts.capabilities.includes("capture_request_header")) {
|
|
@@ -36637,24 +37165,25 @@ var FetchproxyServer = class {
|
|
|
36637
37165
|
await this.ensureConnected();
|
|
36638
37166
|
this.throwIfPendingPair();
|
|
36639
37167
|
const decls = this.opts.captureHeaders;
|
|
37168
|
+
const normPath = (p) => p ?? "/*";
|
|
36640
37169
|
let resolved;
|
|
36641
|
-
if (opts?.
|
|
36642
|
-
const found = decls.find((d) => d.
|
|
37170
|
+
if (opts?.host !== void 0 && opts?.headerName !== void 0) {
|
|
37171
|
+
const found = decls.find((d) => d.host === opts.host && normPath(d.path) === normPath(opts.path) && d.headerName === opts.headerName);
|
|
36643
37172
|
if (!found) {
|
|
36644
|
-
throw new Error(`FetchproxyServer.captureRequestHeader: (
|
|
37173
|
+
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
37174
|
}
|
|
36646
37175
|
resolved = found;
|
|
36647
|
-
} else if (opts?.
|
|
37176
|
+
} else if (opts?.host === void 0 && opts?.headerName === void 0) {
|
|
36648
37177
|
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 {
|
|
37178
|
+
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
37179
|
}
|
|
36651
37180
|
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 {
|
|
37181
|
+
const list = decls.map((d) => `${JSON.stringify(d.host)}${JSON.stringify(normPath(d.path))}/${JSON.stringify(d.headerName)}`).join(", ");
|
|
37182
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list}); pass {host, headerName} to disambiguate`);
|
|
36654
37183
|
}
|
|
36655
37184
|
resolved = decls[0];
|
|
36656
37185
|
} else {
|
|
36657
|
-
throw new Error("FetchproxyServer.captureRequestHeader: pass both
|
|
37186
|
+
throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
|
|
36658
37187
|
}
|
|
36659
37188
|
const callOpts = { ...resolved, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
36660
37189
|
try {
|
|
@@ -36667,11 +37196,14 @@ var FetchproxyServer = class {
|
|
|
36667
37196
|
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
36668
37197
|
throw err;
|
|
36669
37198
|
}
|
|
37199
|
+
this.lastEvictionDetectedAt = Date.now();
|
|
36670
37200
|
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
36671
37201
|
if (reviveMs > 0) {
|
|
37202
|
+
this.lazyReviveAttempts += 1;
|
|
36672
37203
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36673
37204
|
try {
|
|
36674
37205
|
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
37206
|
+
this.lazyReviveSuccesses += 1;
|
|
36675
37207
|
this.recordSuccess();
|
|
36676
37208
|
return result;
|
|
36677
37209
|
} catch (retryErr) {
|
|
@@ -36685,7 +37217,7 @@ var FetchproxyServer = class {
|
|
|
36685
37217
|
originalError: retryErr.message,
|
|
36686
37218
|
retryAttempted: true,
|
|
36687
37219
|
op: "capture_request_header",
|
|
36688
|
-
url: resolved.
|
|
37220
|
+
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
36689
37221
|
role: this.role,
|
|
36690
37222
|
port: this.opts.port
|
|
36691
37223
|
});
|
|
@@ -36696,7 +37228,7 @@ var FetchproxyServer = class {
|
|
|
36696
37228
|
originalError: err.message,
|
|
36697
37229
|
retryAttempted: false,
|
|
36698
37230
|
op: "capture_request_header",
|
|
36699
|
-
url: resolved.
|
|
37231
|
+
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
36700
37232
|
role: this.role,
|
|
36701
37233
|
port: this.opts.port
|
|
36702
37234
|
});
|
|
@@ -36709,7 +37241,8 @@ var FetchproxyServer = class {
|
|
|
36709
37241
|
id,
|
|
36710
37242
|
op: "capture_request_header",
|
|
36711
37243
|
init: {
|
|
36712
|
-
|
|
37244
|
+
host: opts.host,
|
|
37245
|
+
...opts.path !== void 0 ? { path: opts.path } : {},
|
|
36713
37246
|
headerName: opts.headerName,
|
|
36714
37247
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
36715
37248
|
}
|
|
@@ -36964,6 +37497,8 @@ var FetchproxyServer = class {
|
|
|
36964
37497
|
* twice in a row.
|
|
36965
37498
|
*/
|
|
36966
37499
|
async close() {
|
|
37500
|
+
this.closing = true;
|
|
37501
|
+
this.stopKeepalive();
|
|
36967
37502
|
this.rejectAllPending();
|
|
36968
37503
|
if (this.connectingPromise) {
|
|
36969
37504
|
await this.connectingPromise.catch(() => void 0);
|
|
@@ -36979,19 +37514,6 @@ var FetchproxyServer = class {
|
|
|
36979
37514
|
}
|
|
36980
37515
|
};
|
|
36981
37516
|
|
|
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
37517
|
// node_modules/@fetchproxy/bootstrap/dist/index.js
|
|
36996
37518
|
var defaultFactory = (opts) => new FetchproxyServer(opts);
|
|
36997
37519
|
async function bootstrap(opts) {
|
|
@@ -37023,7 +37545,7 @@ async function bootstrap(opts) {
|
|
|
37023
37545
|
const sessionStorageKeys = new Set(opts.declare.sessionStorage);
|
|
37024
37546
|
for (const p of sessionStoragePointers)
|
|
37025
37547
|
sessionStorageKeys.add(p.storageKey);
|
|
37026
|
-
const
|
|
37548
|
+
const server = factory({
|
|
37027
37549
|
serverName: opts.serverName,
|
|
37028
37550
|
version: opts.version,
|
|
37029
37551
|
domains: [...opts.domains],
|
|
@@ -37058,10 +37580,10 @@ async function bootstrap(opts) {
|
|
|
37058
37580
|
if (opts.storageSubdomain !== void 0)
|
|
37059
37581
|
storageDomainOpts.subdomain = opts.storageSubdomain;
|
|
37060
37582
|
try {
|
|
37061
|
-
await
|
|
37583
|
+
await server.listen();
|
|
37062
37584
|
const cookies = {};
|
|
37063
37585
|
if (opts.declare.cookies.length > 0) {
|
|
37064
|
-
const joined = await
|
|
37586
|
+
const joined = await server.readCookies({
|
|
37065
37587
|
keys: opts.declare.cookies,
|
|
37066
37588
|
...storageDomainOpts
|
|
37067
37589
|
});
|
|
@@ -37081,7 +37603,7 @@ async function bootstrap(opts) {
|
|
|
37081
37603
|
for (const p of localStoragePointers) {
|
|
37082
37604
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
37083
37605
|
}
|
|
37084
|
-
localStorage = await
|
|
37606
|
+
localStorage = await server.readLocalStorage({
|
|
37085
37607
|
keys: allKeys,
|
|
37086
37608
|
...storageDomainOpts,
|
|
37087
37609
|
...localStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -37094,7 +37616,7 @@ async function bootstrap(opts) {
|
|
|
37094
37616
|
for (const p of sessionStoragePointers) {
|
|
37095
37617
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
37096
37618
|
}
|
|
37097
|
-
sessionStorage = await
|
|
37619
|
+
sessionStorage = await server.readSessionStorage({
|
|
37098
37620
|
keys: allKeys,
|
|
37099
37621
|
...storageDomainOpts,
|
|
37100
37622
|
...sessionStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -37103,21 +37625,17 @@ async function bootstrap(opts) {
|
|
|
37103
37625
|
const capturedHeaders = {};
|
|
37104
37626
|
for (const h of opts.declare.captureHeaders) {
|
|
37105
37627
|
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
|
-
}
|
|
37628
|
+
opts.onWaiting(`waiting for next request to ${h.host} to capture ${h.headerName} \u2014 interact with the page in your browser`);
|
|
37112
37629
|
}
|
|
37113
|
-
capturedHeaders[h.headerName] = await
|
|
37114
|
-
|
|
37630
|
+
capturedHeaders[h.headerName] = await server.captureRequestHeader({
|
|
37631
|
+
host: h.host,
|
|
37632
|
+
...h.path !== void 0 ? { path: h.path } : {},
|
|
37115
37633
|
headerName: h.headerName
|
|
37116
37634
|
});
|
|
37117
37635
|
}
|
|
37118
37636
|
const indexedDbBucket = {};
|
|
37119
37637
|
for (const d of indexedDb) {
|
|
37120
|
-
const values = await
|
|
37638
|
+
const values = await server.readIndexedDb({
|
|
37121
37639
|
database: d.database,
|
|
37122
37640
|
store: d.store,
|
|
37123
37641
|
keys: [...d.keys],
|
|
@@ -37134,7 +37652,7 @@ async function bootstrap(opts) {
|
|
|
37134
37652
|
};
|
|
37135
37653
|
} finally {
|
|
37136
37654
|
try {
|
|
37137
|
-
await
|
|
37655
|
+
await server.close();
|
|
37138
37656
|
} catch {
|
|
37139
37657
|
}
|
|
37140
37658
|
}
|
|
@@ -37152,13 +37670,7 @@ var BootstrapDisabledError = class extends Error {
|
|
|
37152
37670
|
|
|
37153
37671
|
// src/config.ts
|
|
37154
37672
|
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;
|
|
37673
|
+
return readEnvVar(key, { env });
|
|
37162
37674
|
}
|
|
37163
37675
|
function loadAccount(env = process.env) {
|
|
37164
37676
|
const baseUrl = readVar(env, "IC_BASE_URL");
|
|
@@ -37197,7 +37709,7 @@ function loadAccount(env = process.env) {
|
|
|
37197
37709
|
// package.json
|
|
37198
37710
|
var package_default = {
|
|
37199
37711
|
name: "infinitecampus-mcp",
|
|
37200
|
-
version: "2.3.
|
|
37712
|
+
version: "2.3.2",
|
|
37201
37713
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
37202
37714
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
37203
37715
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -37239,10 +37751,12 @@ var package_default = {
|
|
|
37239
37751
|
"test:watch": "vitest"
|
|
37240
37752
|
},
|
|
37241
37753
|
dependencies: {
|
|
37242
|
-
"@
|
|
37243
|
-
"@fetchproxy/
|
|
37754
|
+
"@chrischall/mcp-utils": "^0.5.0",
|
|
37755
|
+
"@fetchproxy/bootstrap": "^1.0.0",
|
|
37756
|
+
"@fetchproxy/server": "^1.0.0",
|
|
37244
37757
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37245
37758
|
dotenv: "^17.4.2",
|
|
37759
|
+
"node-html-parser": "^7.1.0",
|
|
37246
37760
|
zod: "^4.4.3"
|
|
37247
37761
|
},
|
|
37248
37762
|
devDependencies: {
|
|
@@ -37255,19 +37769,8 @@ var package_default = {
|
|
|
37255
37769
|
};
|
|
37256
37770
|
|
|
37257
37771
|
// 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
37772
|
function fetchproxyDisabled() {
|
|
37268
|
-
|
|
37269
|
-
if (raw === void 0) return false;
|
|
37270
|
-
return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
|
|
37773
|
+
return parseBoolEnv("IC_DISABLE_FETCHPROXY", { default: false });
|
|
37271
37774
|
}
|
|
37272
37775
|
async function resolveAuth() {
|
|
37273
37776
|
const account2 = loadAccount();
|
|
@@ -37635,22 +38138,9 @@ var ICClient = class {
|
|
|
37635
38138
|
};
|
|
37636
38139
|
function parseSetCookies(headers) {
|
|
37637
38140
|
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 };
|
|
38141
|
+
const entries = raw.length > 0 ? raw : splitFallback(headers.get("set-cookie"));
|
|
38142
|
+
const { cookies, cookieHeader } = parseCookieJar(entries);
|
|
38143
|
+
return { cookieHeader, xsrfToken: cookies["XSRF-TOKEN"] ?? "" };
|
|
37654
38144
|
}
|
|
37655
38145
|
function splitFallback(header) {
|
|
37656
38146
|
if (!header) return [];
|
|
@@ -37724,7 +38214,7 @@ var FileExistsError = class extends Error {
|
|
|
37724
38214
|
|
|
37725
38215
|
// src/tools/_shared.ts
|
|
37726
38216
|
function textContent(data) {
|
|
37727
|
-
return
|
|
38217
|
+
return textResult(data);
|
|
37728
38218
|
}
|
|
37729
38219
|
function is404(e) {
|
|
37730
38220
|
return e instanceof Error && e.message.startsWith("IC 404 ");
|
|
@@ -37757,8 +38247,8 @@ function toArray(value) {
|
|
|
37757
38247
|
}
|
|
37758
38248
|
|
|
37759
38249
|
// src/tools/districts.ts
|
|
37760
|
-
function registerDistrictTools(
|
|
37761
|
-
|
|
38250
|
+
function registerDistrictTools(server, client) {
|
|
38251
|
+
server.registerTool("ic_list_districts", {
|
|
37762
38252
|
description: "List Infinite Campus districts configured for this MCP server. Returns names + base URLs (no credentials).",
|
|
37763
38253
|
annotations: { readOnlyHint: true }
|
|
37764
38254
|
}, async () => {
|
|
@@ -37772,8 +38262,8 @@ function registerDistrictTools(server2, client) {
|
|
|
37772
38262
|
var argsSchema = external_exports.object({
|
|
37773
38263
|
district: external_exports.string().describe("District name from ic_list_districts")
|
|
37774
38264
|
});
|
|
37775
|
-
function registerStudentTools(
|
|
37776
|
-
|
|
38265
|
+
function registerStudentTools(server, client) {
|
|
38266
|
+
server.registerTool("ic_list_students", {
|
|
37777
38267
|
description: "List students enrolled under the parent account for a given district.",
|
|
37778
38268
|
annotations: { readOnlyHint: true },
|
|
37779
38269
|
inputSchema: argsSchema.shape
|
|
@@ -37791,8 +38281,8 @@ var argsSchema2 = external_exports.object({
|
|
|
37791
38281
|
date: external_exports.string().describe("YYYY-MM-DD; defaults to today").optional(),
|
|
37792
38282
|
termFilter: external_exports.string().describe("Term name or ID; optional").optional()
|
|
37793
38283
|
});
|
|
37794
|
-
function registerScheduleTools(
|
|
37795
|
-
|
|
38284
|
+
function registerScheduleTools(server, client) {
|
|
38285
|
+
server.registerTool("ic_get_schedule", {
|
|
37796
38286
|
description: "Get a student's class schedule for a given date (default: today).",
|
|
37797
38287
|
annotations: { readOnlyHint: true },
|
|
37798
38288
|
inputSchema: argsSchema2.shape
|
|
@@ -37813,8 +38303,8 @@ var argsSchema3 = external_exports.object({
|
|
|
37813
38303
|
until: external_exports.string().describe("YYYY-MM-DD; filters dueDate <= until (client-side)").optional(),
|
|
37814
38304
|
missingOnly: external_exports.boolean().describe("Only return assignments flagged missing by the teacher").optional()
|
|
37815
38305
|
});
|
|
37816
|
-
function registerAssignmentTools(
|
|
37817
|
-
|
|
38306
|
+
function registerAssignmentTools(server, client) {
|
|
38307
|
+
server.registerTool("ic_list_assignments", {
|
|
37818
38308
|
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
38309
|
annotations: { readOnlyHint: true },
|
|
37820
38310
|
inputSchema: argsSchema3.shape
|
|
@@ -37848,8 +38338,8 @@ var argsSchema4 = external_exports.object({
|
|
|
37848
38338
|
studentId: external_exports.string(),
|
|
37849
38339
|
termId: external_exports.string().optional()
|
|
37850
38340
|
});
|
|
37851
|
-
function registerGradeTools(
|
|
37852
|
-
|
|
38341
|
+
function registerGradeTools(server, client) {
|
|
38342
|
+
server.registerTool("ic_list_grades", {
|
|
37853
38343
|
description: "List a student's term grades and in-progress course grades.",
|
|
37854
38344
|
annotations: { readOnlyHint: true },
|
|
37855
38345
|
inputSchema: argsSchema4.shape
|
|
@@ -37892,8 +38382,8 @@ function processList(list, since, until) {
|
|
|
37892
38382
|
if (list === void 0) return list;
|
|
37893
38383
|
return toArray(list).filter((e) => inRange(e.date, since, until)).map(trimEntry);
|
|
37894
38384
|
}
|
|
37895
|
-
function registerAttendanceTools(
|
|
37896
|
-
|
|
38385
|
+
function registerAttendanceTools(server, client) {
|
|
38386
|
+
server.registerTool("ic_list_attendance", {
|
|
37897
38387
|
description: "List a student's absences and tardies (per-course summary grouped by term). Auto-resolves enrollmentID from the student record.",
|
|
37898
38388
|
annotations: { readOnlyHint: true },
|
|
37899
38389
|
inputSchema: argsSchema5.shape
|
|
@@ -37941,8 +38431,8 @@ var argsSchema6 = external_exports.object({
|
|
|
37941
38431
|
since: external_exports.string().optional(),
|
|
37942
38432
|
until: external_exports.string().optional()
|
|
37943
38433
|
});
|
|
37944
|
-
function registerBehaviorTools(
|
|
37945
|
-
|
|
38434
|
+
function registerBehaviorTools(server, client) {
|
|
38435
|
+
server.registerTool("ic_list_behavior", {
|
|
37946
38436
|
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
38437
|
annotations: { readOnlyHint: true },
|
|
37948
38438
|
inputSchema: argsSchema6.shape
|
|
@@ -37973,8 +38463,8 @@ var argsSchema7 = external_exports.object({
|
|
|
37973
38463
|
until: external_exports.string().optional()
|
|
37974
38464
|
});
|
|
37975
38465
|
var EMPTY_FOOD = { balance: null, transactions: [] };
|
|
37976
|
-
function registerFoodServiceTools(
|
|
37977
|
-
|
|
38466
|
+
function registerFoodServiceTools(server, client) {
|
|
38467
|
+
server.registerTool("ic_list_food_service", {
|
|
37978
38468
|
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
38469
|
annotations: { readOnlyHint: true },
|
|
37980
38470
|
inputSchema: argsSchema7.shape
|
|
@@ -38005,6 +38495,29 @@ function registerFoodServiceTools(server2, client) {
|
|
|
38005
38495
|
});
|
|
38006
38496
|
}
|
|
38007
38497
|
|
|
38498
|
+
// node_modules/@chrischall/mcp-utils/dist/html/index.js
|
|
38499
|
+
var NAMED_ENTITIES = {
|
|
38500
|
+
nbsp: " ",
|
|
38501
|
+
amp: "&",
|
|
38502
|
+
lt: "<",
|
|
38503
|
+
gt: ">",
|
|
38504
|
+
quot: '"',
|
|
38505
|
+
apos: "'"
|
|
38506
|
+
};
|
|
38507
|
+
function extractPlainTextFromHtml(html) {
|
|
38508
|
+
if (!html)
|
|
38509
|
+
return "";
|
|
38510
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ");
|
|
38511
|
+
text = text.replace(/<[^>]+>/g, " ");
|
|
38512
|
+
text = text.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
|
|
38513
|
+
text = text.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)));
|
|
38514
|
+
text = text.replace(/&([a-zA-Z]+);/g, (whole, name) => {
|
|
38515
|
+
const decoded = NAMED_ENTITIES[name.toLowerCase()];
|
|
38516
|
+
return decoded ?? whole;
|
|
38517
|
+
});
|
|
38518
|
+
return text.replace(/\s+/g, " ").trim();
|
|
38519
|
+
}
|
|
38520
|
+
|
|
38008
38521
|
// src/tools/messages.ts
|
|
38009
38522
|
var listArgs = external_exports.object({
|
|
38010
38523
|
district: external_exports.string(),
|
|
@@ -38051,10 +38564,7 @@ function parseMessageHtml(html, url2) {
|
|
|
38051
38564
|
const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
|
|
38052
38565
|
let subject = titleMatch ? titleMatch[1].trim() : "";
|
|
38053
38566
|
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();
|
|
38567
|
+
const text = extractPlainTextFromHtml(html);
|
|
38058
38568
|
const dateMatch = text.match(/Date:\s*(\d{1,2}\/\d{1,2}\/\d{2,4})/);
|
|
38059
38569
|
const date5 = dateMatch ? dateMatch[1] : null;
|
|
38060
38570
|
let body = text;
|
|
@@ -38064,8 +38574,8 @@ function parseMessageHtml(html, url2) {
|
|
|
38064
38574
|
}
|
|
38065
38575
|
return { subject, date: date5, body, url: url2 };
|
|
38066
38576
|
}
|
|
38067
|
-
function registerMessageTools(
|
|
38068
|
-
|
|
38577
|
+
function registerMessageTools(server, client) {
|
|
38578
|
+
server.registerTool("ic_list_messages", {
|
|
38069
38579
|
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
38580
|
annotations: { readOnlyHint: true },
|
|
38071
38581
|
inputSchema: listArgs.shape
|
|
@@ -38103,7 +38613,7 @@ function registerMessageTools(server2, client) {
|
|
|
38103
38613
|
const [notifications, inbox, announcements] = await Promise.all([prismPromise, inboxPromise, noticePromise]);
|
|
38104
38614
|
return textContent({ notifications, inbox, announcements });
|
|
38105
38615
|
});
|
|
38106
|
-
|
|
38616
|
+
server.registerTool("ic_get_message", {
|
|
38107
38617
|
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
38618
|
annotations: { readOnlyHint: true },
|
|
38109
38619
|
inputSchema: getArgs.shape
|
|
@@ -38127,8 +38637,8 @@ var downloadArgs = external_exports.object({
|
|
|
38127
38637
|
destinationPath: external_exports.string().describe("Absolute path where the PDF should be written"),
|
|
38128
38638
|
overwrite: external_exports.boolean().optional()
|
|
38129
38639
|
});
|
|
38130
|
-
function registerDocumentTools(
|
|
38131
|
-
|
|
38640
|
+
function registerDocumentTools(server, client) {
|
|
38641
|
+
server.registerTool("ic_list_documents", {
|
|
38132
38642
|
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
38643
|
annotations: { readOnlyHint: true },
|
|
38134
38644
|
inputSchema: listArgs2.shape
|
|
@@ -38158,7 +38668,7 @@ function registerDocumentTools(server2, client) {
|
|
|
38158
38668
|
throw e;
|
|
38159
38669
|
}
|
|
38160
38670
|
});
|
|
38161
|
-
|
|
38671
|
+
server.registerTool("ic_download_document", {
|
|
38162
38672
|
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
38673
|
annotations: { destructiveHint: true },
|
|
38164
38674
|
inputSchema: downloadArgs.shape
|
|
@@ -38185,8 +38695,8 @@ var argsSchema8 = external_exports.object({
|
|
|
38185
38695
|
since: external_exports.string().describe("YYYY-MM-DD; include only days on or after this date").optional(),
|
|
38186
38696
|
until: external_exports.string().describe("YYYY-MM-DD; include only days on or before this date").optional()
|
|
38187
38697
|
});
|
|
38188
|
-
function registerCalendarTools(
|
|
38189
|
-
|
|
38698
|
+
function registerCalendarTools(server, client) {
|
|
38699
|
+
server.registerTool("ic_list_school_days", {
|
|
38190
38700
|
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
38701
|
annotations: { readOnlyHint: true },
|
|
38192
38702
|
inputSchema: argsSchema8.shape
|
|
@@ -38281,8 +38791,8 @@ function trimEvent(e) {
|
|
|
38281
38791
|
}
|
|
38282
38792
|
return out;
|
|
38283
38793
|
}
|
|
38284
|
-
function registerAttendanceEventsTools(
|
|
38285
|
-
|
|
38794
|
+
function registerAttendanceEventsTools(server, client) {
|
|
38795
|
+
server.registerTool("ic_list_attendance_events", {
|
|
38286
38796
|
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
38797
|
annotations: { readOnlyHint: true },
|
|
38288
38798
|
inputSchema: argsSchema9.shape
|
|
@@ -38353,8 +38863,8 @@ function defaultSinceDate(now) {
|
|
|
38353
38863
|
d.setUTCDate(d.getUTCDate() - 14);
|
|
38354
38864
|
return d.toISOString().substring(0, 10);
|
|
38355
38865
|
}
|
|
38356
|
-
function registerRecentGradesTools(
|
|
38357
|
-
|
|
38866
|
+
function registerRecentGradesTools(server, client) {
|
|
38867
|
+
server.registerTool("ic_list_recent_grades", {
|
|
38358
38868
|
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
38869
|
annotations: { readOnlyHint: true },
|
|
38360
38870
|
inputSchema: argsSchema10.shape
|
|
@@ -38404,8 +38914,8 @@ function trimRecord(raw) {
|
|
|
38404
38914
|
}
|
|
38405
38915
|
return out;
|
|
38406
38916
|
}
|
|
38407
|
-
function registerTeacherTools(
|
|
38408
|
-
|
|
38917
|
+
function registerTeacherTools(server, client) {
|
|
38918
|
+
server.registerTool("ic_list_teachers", {
|
|
38409
38919
|
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
38920
|
annotations: { readOnlyHint: true },
|
|
38411
38921
|
inputSchema: argsSchema11.shape
|
|
@@ -38438,8 +38948,8 @@ var argsSchema12 = external_exports.object({
|
|
|
38438
38948
|
district: external_exports.string(),
|
|
38439
38949
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38440
38950
|
});
|
|
38441
|
-
function registerAssessmentTools(
|
|
38442
|
-
|
|
38951
|
+
function registerAssessmentTools(server, client) {
|
|
38952
|
+
server.registerTool("ic_list_assessments", {
|
|
38443
38953
|
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
38954
|
annotations: { readOnlyHint: true },
|
|
38445
38955
|
inputSchema: argsSchema12.shape
|
|
@@ -38489,8 +38999,8 @@ var argsSchema13 = external_exports.object({
|
|
|
38489
38999
|
district: external_exports.string(),
|
|
38490
39000
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38491
39001
|
});
|
|
38492
|
-
function registerFeeTools(
|
|
38493
|
-
|
|
39002
|
+
function registerFeeTools(server, client) {
|
|
39003
|
+
server.registerTool("ic_list_fees", {
|
|
38494
39004
|
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
39005
|
annotations: { readOnlyHint: true },
|
|
38496
39006
|
inputSchema: argsSchema13.shape
|
|
@@ -38533,8 +39043,8 @@ var argsSchema14 = external_exports.object({
|
|
|
38533
39043
|
district: external_exports.string(),
|
|
38534
39044
|
studentId: external_exports.string().describe("Student personID from ic_list_students")
|
|
38535
39045
|
});
|
|
38536
|
-
function registerFeaturesTools(
|
|
38537
|
-
|
|
39046
|
+
function registerFeaturesTools(server, client) {
|
|
39047
|
+
server.registerTool("ic_get_features", {
|
|
38538
39048
|
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
39049
|
annotations: { readOnlyHint: true },
|
|
38540
39050
|
inputSchema: argsSchema14.shape
|
|
@@ -38557,12 +39067,11 @@ function registerFeaturesTools(server2, client) {
|
|
|
38557
39067
|
}
|
|
38558
39068
|
|
|
38559
39069
|
// src/index.ts
|
|
38560
|
-
|
|
38561
|
-
|
|
38562
|
-
|
|
38563
|
-
|
|
38564
|
-
|
|
38565
|
-
}
|
|
39070
|
+
await loadDotenvSafely({
|
|
39071
|
+
path: join2(dirname2(fileURLToPath(import.meta.url)), "..", ".env"),
|
|
39072
|
+
override: false
|
|
39073
|
+
});
|
|
39074
|
+
var AI_NOTICE = "[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.";
|
|
38566
39075
|
var account = null;
|
|
38567
39076
|
var preloaded;
|
|
38568
39077
|
var source;
|
|
@@ -38575,32 +39084,39 @@ try {
|
|
|
38575
39084
|
} catch (e) {
|
|
38576
39085
|
configError = e;
|
|
38577
39086
|
}
|
|
38578
|
-
var
|
|
39087
|
+
var COMMON = {
|
|
39088
|
+
name: "infinitecampus",
|
|
39089
|
+
version: "2.3.2"
|
|
39090
|
+
// x-release-please-version
|
|
39091
|
+
};
|
|
38579
39092
|
if (account) {
|
|
38580
39093
|
const client = new ICClient(account, { preloaded });
|
|
38581
|
-
|
|
38582
|
-
|
|
38583
|
-
|
|
38584
|
-
|
|
38585
|
-
|
|
38586
|
-
|
|
38587
|
-
|
|
38588
|
-
|
|
38589
|
-
|
|
38590
|
-
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
38594
|
-
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
39094
|
+
const tools = [
|
|
39095
|
+
registerDistrictTools,
|
|
39096
|
+
registerStudentTools,
|
|
39097
|
+
registerScheduleTools,
|
|
39098
|
+
registerAssignmentTools,
|
|
39099
|
+
registerGradeTools,
|
|
39100
|
+
registerAttendanceTools,
|
|
39101
|
+
registerBehaviorTools,
|
|
39102
|
+
registerFoodServiceTools,
|
|
39103
|
+
registerMessageTools,
|
|
39104
|
+
registerDocumentTools,
|
|
39105
|
+
registerCalendarTools,
|
|
39106
|
+
registerAttendanceEventsTools,
|
|
39107
|
+
registerRecentGradesTools,
|
|
39108
|
+
registerTeacherTools,
|
|
39109
|
+
registerAssessmentTools,
|
|
39110
|
+
registerFeeTools,
|
|
39111
|
+
registerFeaturesTools
|
|
39112
|
+
];
|
|
38598
39113
|
const suffix = source === "fetchproxy" ? " [via fetchproxy]" : "";
|
|
38599
|
-
|
|
39114
|
+
const districtLine = `[infinitecampus-mcp] District: ${account.name} (${account.baseUrl})${suffix}`;
|
|
39115
|
+
await runMcp({ ...COMMON, deps: client, tools, banner: `${districtLine}
|
|
39116
|
+
${AI_NOTICE}` });
|
|
38600
39117
|
} else {
|
|
38601
|
-
|
|
38602
|
-
|
|
39118
|
+
const notConfigured = `[infinitecampus-mcp] Not configured: ${configError?.message ?? "unknown error"}
|
|
39119
|
+
[infinitecampus-mcp] Server is running with no tools registered. Set the required env vars and reinstall.`;
|
|
39120
|
+
await runMcp({ ...COMMON, tools: [], banner: `${notConfigured}
|
|
39121
|
+
${AI_NOTICE}` });
|
|
38603
39122
|
}
|
|
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);
|