infinitecampus-mcp 2.4.5 → 2.5.0
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/bundle.js +475 -91
- package/dist/client.js +161 -13
- package/dist/index.js +1 -1
- package/dist/session-cache.js +95 -0
- package/mint.yaml +72 -0
- package/package.json +8 -5
- package/server.json +14 -2
package/dist/bundle.js
CHANGED
|
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
|
|
|
2991
2991
|
const schOrFunc = root.refs[ref];
|
|
2992
2992
|
if (schOrFunc)
|
|
2993
2993
|
return schOrFunc;
|
|
2994
|
-
let _sch =
|
|
2994
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2995
2995
|
if (_sch === void 0) {
|
|
2996
2996
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2997
2997
|
const { schemaId } = this.opts;
|
|
@@ -3018,7 +3018,7 @@ var require_compile = __commonJS({
|
|
|
3018
3018
|
function sameSchemaEnv(s1, s2) {
|
|
3019
3019
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3020
3020
|
}
|
|
3021
|
-
function
|
|
3021
|
+
function resolve2(root, ref) {
|
|
3022
3022
|
let sch;
|
|
3023
3023
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3024
3024
|
ref = sch;
|
|
@@ -3649,7 +3649,7 @@ var require_fast_uri = __commonJS({
|
|
|
3649
3649
|
}
|
|
3650
3650
|
return uri;
|
|
3651
3651
|
}
|
|
3652
|
-
function
|
|
3652
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3653
3653
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3654
3654
|
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3655
3655
|
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
@@ -3933,7 +3933,7 @@ var require_fast_uri = __commonJS({
|
|
|
3933
3933
|
var fastUri = {
|
|
3934
3934
|
SCHEMES,
|
|
3935
3935
|
normalize,
|
|
3936
|
-
resolve,
|
|
3936
|
+
resolve: resolve2,
|
|
3937
3937
|
resolveComponent,
|
|
3938
3938
|
equal,
|
|
3939
3939
|
serialize,
|
|
@@ -9161,7 +9161,7 @@ var require_websocket = __commonJS({
|
|
|
9161
9161
|
var http = __require("http");
|
|
9162
9162
|
var net = __require("net");
|
|
9163
9163
|
var tls = __require("tls");
|
|
9164
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
9164
|
+
var { randomBytes: randomBytes2, createHash } = __require("crypto");
|
|
9165
9165
|
var { Duplex, Readable } = __require("stream");
|
|
9166
9166
|
var { URL: URL2 } = __require("url");
|
|
9167
9167
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9699,7 +9699,7 @@ var require_websocket = __commonJS({
|
|
|
9699
9699
|
}
|
|
9700
9700
|
}
|
|
9701
9701
|
const defaultPort = isSecure ? 443 : 80;
|
|
9702
|
-
const key =
|
|
9702
|
+
const key = randomBytes2(16).toString("base64");
|
|
9703
9703
|
const request = isSecure ? https.request : http.request;
|
|
9704
9704
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
9705
9705
|
let perMessageDeflate;
|
|
@@ -10593,7 +10593,7 @@ var require_websocket_server = __commonJS({
|
|
|
10593
10593
|
});
|
|
10594
10594
|
|
|
10595
10595
|
// src/index.ts
|
|
10596
|
-
import { dirname as
|
|
10596
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
10597
10597
|
import { fileURLToPath } from "url";
|
|
10598
10598
|
|
|
10599
10599
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -32554,7 +32554,7 @@ var Protocol = class {
|
|
|
32554
32554
|
return;
|
|
32555
32555
|
}
|
|
32556
32556
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
32557
|
-
await new Promise((
|
|
32557
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
32558
32558
|
options?.signal?.throwIfAborted();
|
|
32559
32559
|
}
|
|
32560
32560
|
} catch (error51) {
|
|
@@ -32571,7 +32571,7 @@ var Protocol = class {
|
|
|
32571
32571
|
*/
|
|
32572
32572
|
request(request, resultSchema, options) {
|
|
32573
32573
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
32574
|
-
return new Promise((
|
|
32574
|
+
return new Promise((resolve2, reject) => {
|
|
32575
32575
|
const earlyReject = (error51) => {
|
|
32576
32576
|
reject(error51);
|
|
32577
32577
|
};
|
|
@@ -32649,7 +32649,7 @@ var Protocol = class {
|
|
|
32649
32649
|
if (!parseResult.success) {
|
|
32650
32650
|
reject(parseResult.error);
|
|
32651
32651
|
} else {
|
|
32652
|
-
|
|
32652
|
+
resolve2(parseResult.data);
|
|
32653
32653
|
}
|
|
32654
32654
|
} catch (error51) {
|
|
32655
32655
|
reject(error51);
|
|
@@ -32910,12 +32910,12 @@ var Protocol = class {
|
|
|
32910
32910
|
}
|
|
32911
32911
|
} catch {
|
|
32912
32912
|
}
|
|
32913
|
-
return new Promise((
|
|
32913
|
+
return new Promise((resolve2, reject) => {
|
|
32914
32914
|
if (signal.aborted) {
|
|
32915
32915
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
32916
32916
|
return;
|
|
32917
32917
|
}
|
|
32918
|
-
const timeoutId = setTimeout(
|
|
32918
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
32919
32919
|
signal.addEventListener("abort", () => {
|
|
32920
32920
|
clearTimeout(timeoutId);
|
|
32921
32921
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -34006,7 +34006,7 @@ var McpServer = class {
|
|
|
34006
34006
|
let task = createTaskResult.task;
|
|
34007
34007
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
34008
34008
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
34009
|
-
await new Promise((
|
|
34009
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
34010
34010
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
34011
34011
|
if (!updatedTask) {
|
|
34012
34012
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -34670,20 +34670,95 @@ var StdioServerTransport = class {
|
|
|
34670
34670
|
this.onclose?.();
|
|
34671
34671
|
}
|
|
34672
34672
|
send(message) {
|
|
34673
|
-
return new Promise((
|
|
34673
|
+
return new Promise((resolve2) => {
|
|
34674
34674
|
const json2 = serializeMessage(message);
|
|
34675
34675
|
if (this._stdout.write(json2)) {
|
|
34676
|
-
|
|
34676
|
+
resolve2();
|
|
34677
34677
|
} else {
|
|
34678
|
-
this._stdout.once("drain",
|
|
34678
|
+
this._stdout.once("drain", resolve2);
|
|
34679
34679
|
}
|
|
34680
34680
|
});
|
|
34681
34681
|
}
|
|
34682
34682
|
};
|
|
34683
34683
|
|
|
34684
|
+
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
34685
|
+
var McpToolError = class extends Error {
|
|
34686
|
+
/** Actionable remediation text, when one applies. */
|
|
34687
|
+
hint;
|
|
34688
|
+
constructor(message, opts) {
|
|
34689
|
+
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
34690
|
+
this.name = "McpToolError";
|
|
34691
|
+
if (opts?.hint !== void 0)
|
|
34692
|
+
this.hint = opts.hint;
|
|
34693
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34694
|
+
}
|
|
34695
|
+
};
|
|
34696
|
+
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
34697
|
+
var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
34698
|
+
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
34699
|
+
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,\s]+)=[^;,\s]*/gi;
|
|
34700
|
+
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,\s]+=[^;,\s]*)(?:;\s*[^=;,\s]+=[^;,\s]*)*)/gi;
|
|
34701
|
+
var API_KEY_RE = new RegExp([
|
|
34702
|
+
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
34703
|
+
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
34704
|
+
"gh[pousr]_[A-Za-z0-9]{36,}\\b",
|
|
34705
|
+
// GitHub ghp_/gho_/ghu_/ghs_/ghr_
|
|
34706
|
+
"xox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])",
|
|
34707
|
+
// Slack
|
|
34708
|
+
"AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])",
|
|
34709
|
+
// Google API key (39 chars total)
|
|
34710
|
+
"AKIA[0-9A-Z]{16}\\b",
|
|
34711
|
+
// AWS access key id (20 chars total)
|
|
34712
|
+
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
34713
|
+
// webhook signing secret (Stripe-style)
|
|
34714
|
+
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34715
|
+
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
34716
|
+
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
34717
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
34718
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
34719
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
34720
|
+
function redactSecrets(text) {
|
|
34721
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
34722
|
+
}
|
|
34723
|
+
|
|
34724
|
+
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34725
|
+
function textResult(data) {
|
|
34726
|
+
return {
|
|
34727
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34728
|
+
};
|
|
34729
|
+
}
|
|
34730
|
+
function errorResult(message) {
|
|
34731
|
+
return {
|
|
34732
|
+
content: [{ type: "text", text: redactSecrets(message) }],
|
|
34733
|
+
isError: true
|
|
34734
|
+
};
|
|
34735
|
+
}
|
|
34736
|
+
|
|
34684
34737
|
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34738
|
+
function hintResultOrRethrow(err) {
|
|
34739
|
+
if (err instanceof McpToolError && err.hint) {
|
|
34740
|
+
return errorResult(`${err.message}
|
|
34741
|
+
|
|
34742
|
+
Hint: ${err.hint}`);
|
|
34743
|
+
}
|
|
34744
|
+
throw err;
|
|
34745
|
+
}
|
|
34746
|
+
function surfaceToolHints(server) {
|
|
34747
|
+
const register = server.registerTool.bind(server);
|
|
34748
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
34749
|
+
let result;
|
|
34750
|
+
try {
|
|
34751
|
+
result = cb(...args);
|
|
34752
|
+
} catch (err) {
|
|
34753
|
+
return hintResultOrRethrow(err);
|
|
34754
|
+
}
|
|
34755
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
34756
|
+
});
|
|
34757
|
+
}
|
|
34685
34758
|
async function createMcpServer(opts) {
|
|
34686
34759
|
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34760
|
+
if (opts.surfaceHints !== false)
|
|
34761
|
+
surfaceToolHints(server);
|
|
34687
34762
|
if (opts.banner !== void 0) {
|
|
34688
34763
|
console.error(opts.banner);
|
|
34689
34764
|
}
|
|
@@ -34728,32 +34803,6 @@ async function runMcp(opts) {
|
|
|
34728
34803
|
return server;
|
|
34729
34804
|
}
|
|
34730
34805
|
|
|
34731
|
-
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
34732
|
-
var API_KEY_RE = new RegExp([
|
|
34733
|
-
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
34734
|
-
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
34735
|
-
"gh[pousr]_[A-Za-z0-9]{36,}\\b",
|
|
34736
|
-
// GitHub ghp_/gho_/ghu_/ghs_/ghr_
|
|
34737
|
-
"xox[baprs]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])",
|
|
34738
|
-
// Slack
|
|
34739
|
-
"AIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])",
|
|
34740
|
-
// Google API key (39 chars total)
|
|
34741
|
-
"AKIA[0-9A-Z]{16}\\b",
|
|
34742
|
-
// AWS access key id (20 chars total)
|
|
34743
|
-
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
34744
|
-
// webhook signing secret (Stripe-style)
|
|
34745
|
-
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34746
|
-
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
34747
|
-
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
34748
|
-
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
34749
|
-
|
|
34750
|
-
// node_modules/@chrischall/mcp-utils/dist/response/index.js
|
|
34751
|
-
function textResult(data) {
|
|
34752
|
-
return {
|
|
34753
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34754
|
-
};
|
|
34755
|
-
}
|
|
34756
|
-
|
|
34757
34806
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34758
34807
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
34759
34808
|
function readEnvVar(key, opts = {}) {
|
|
@@ -36047,7 +36096,7 @@ var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
|
36047
36096
|
async function electRole(opts) {
|
|
36048
36097
|
const server = createServer();
|
|
36049
36098
|
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
36050
|
-
return new Promise((
|
|
36099
|
+
return new Promise((resolve2, reject) => {
|
|
36051
36100
|
let timer;
|
|
36052
36101
|
const clearBindTimer = () => {
|
|
36053
36102
|
if (timer) {
|
|
@@ -36063,7 +36112,7 @@ async function electRole(opts) {
|
|
|
36063
36112
|
server.close();
|
|
36064
36113
|
} catch {
|
|
36065
36114
|
}
|
|
36066
|
-
|
|
36115
|
+
resolve2({ role: "peer" });
|
|
36067
36116
|
} else {
|
|
36068
36117
|
reject(e);
|
|
36069
36118
|
}
|
|
@@ -36071,7 +36120,7 @@ async function electRole(opts) {
|
|
|
36071
36120
|
const onListening = () => {
|
|
36072
36121
|
clearBindTimer();
|
|
36073
36122
|
server.removeListener("error", onError);
|
|
36074
|
-
|
|
36123
|
+
resolve2({ role: "host", server });
|
|
36075
36124
|
};
|
|
36076
36125
|
server.once("error", onError);
|
|
36077
36126
|
server.once("listening", onListening);
|
|
@@ -36417,8 +36466,8 @@ async function startHost(opts) {
|
|
|
36417
36466
|
let rejectOwnSession;
|
|
36418
36467
|
let ownSessionReady;
|
|
36419
36468
|
function resetSessionPromise() {
|
|
36420
|
-
ownSessionReady = new Promise((
|
|
36421
|
-
resolveOwnSession =
|
|
36469
|
+
ownSessionReady = new Promise((resolve2, reject) => {
|
|
36470
|
+
resolveOwnSession = resolve2;
|
|
36422
36471
|
rejectOwnSession = reject;
|
|
36423
36472
|
});
|
|
36424
36473
|
ownSessionReady.catch(() => {
|
|
@@ -36639,7 +36688,7 @@ async function startHost(opts) {
|
|
|
36639
36688
|
});
|
|
36640
36689
|
});
|
|
36641
36690
|
return {
|
|
36642
|
-
close: () => new Promise((
|
|
36691
|
+
close: () => new Promise((resolve2) => {
|
|
36643
36692
|
for (const client of wss.clients) {
|
|
36644
36693
|
try {
|
|
36645
36694
|
client.terminate();
|
|
@@ -36647,7 +36696,7 @@ async function startHost(opts) {
|
|
|
36647
36696
|
}
|
|
36648
36697
|
}
|
|
36649
36698
|
wss.close(() => {
|
|
36650
|
-
opts.httpServer.close(() =>
|
|
36699
|
+
opts.httpServer.close(() => resolve2());
|
|
36651
36700
|
});
|
|
36652
36701
|
}),
|
|
36653
36702
|
sendOwnInner: async (inner) => {
|
|
@@ -36677,8 +36726,8 @@ async function startHost(opts) {
|
|
|
36677
36726
|
var enc3 = new TextEncoder();
|
|
36678
36727
|
async function startPeer(opts) {
|
|
36679
36728
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`);
|
|
36680
|
-
await new Promise((
|
|
36681
|
-
ws.once("open", () =>
|
|
36729
|
+
await new Promise((resolve2, reject) => {
|
|
36730
|
+
ws.once("open", () => resolve2());
|
|
36682
36731
|
ws.once("error", reject);
|
|
36683
36732
|
});
|
|
36684
36733
|
const hello = await buildServerHello({
|
|
@@ -36708,8 +36757,8 @@ async function startPeer(opts) {
|
|
|
36708
36757
|
let pendingPairCode = null;
|
|
36709
36758
|
let resolveFirstReady;
|
|
36710
36759
|
let rejectFirstReady;
|
|
36711
|
-
const sessionPromise = new Promise((
|
|
36712
|
-
resolveFirstReady =
|
|
36760
|
+
const sessionPromise = new Promise((resolve2, reject) => {
|
|
36761
|
+
resolveFirstReady = resolve2;
|
|
36713
36762
|
rejectFirstReady = reject;
|
|
36714
36763
|
});
|
|
36715
36764
|
let extensionHello = null;
|
|
@@ -36910,6 +36959,17 @@ function classifyBridgeError(err) {
|
|
|
36910
36959
|
}
|
|
36911
36960
|
|
|
36912
36961
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
36962
|
+
function envWsPort() {
|
|
36963
|
+
const raw = process.env.FETCHPROXY_WS_PORT;
|
|
36964
|
+
if (raw === void 0 || raw.trim() === "")
|
|
36965
|
+
return void 0;
|
|
36966
|
+
if (!/^\d+$/.test(raw.trim()))
|
|
36967
|
+
return void 0;
|
|
36968
|
+
const port = Number(raw.trim());
|
|
36969
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
36970
|
+
return void 0;
|
|
36971
|
+
return port;
|
|
36972
|
+
}
|
|
36913
36973
|
var FetchproxyProtocolError = class extends Error {
|
|
36914
36974
|
constructor(message) {
|
|
36915
36975
|
super(message);
|
|
@@ -37157,7 +37217,7 @@ var FetchproxyServer = class {
|
|
|
37157
37217
|
}
|
|
37158
37218
|
}
|
|
37159
37219
|
this.opts = {
|
|
37160
|
-
port: opts.port ?? 37149,
|
|
37220
|
+
port: opts.port ?? envWsPort() ?? 37149,
|
|
37161
37221
|
host: opts.host ?? "127.0.0.1",
|
|
37162
37222
|
serverName: opts.serverName,
|
|
37163
37223
|
version: opts.version,
|
|
@@ -37587,8 +37647,8 @@ var FetchproxyServer = class {
|
|
|
37587
37647
|
async _fetchOnceWithTimeout(init) {
|
|
37588
37648
|
const id = this.nextRequestId++;
|
|
37589
37649
|
const inner = { type: "request", id, op: "fetch", init };
|
|
37590
|
-
const pending = new Promise((
|
|
37591
|
-
this.pending.set(id,
|
|
37650
|
+
const pending = new Promise((resolve2) => {
|
|
37651
|
+
this.pending.set(id, resolve2);
|
|
37592
37652
|
});
|
|
37593
37653
|
await this.sendInnerFrame(inner);
|
|
37594
37654
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
@@ -37599,12 +37659,12 @@ var FetchproxyServer = class {
|
|
|
37599
37659
|
try {
|
|
37600
37660
|
return await Promise.race([
|
|
37601
37661
|
pending,
|
|
37602
|
-
new Promise((
|
|
37662
|
+
new Promise((resolve2) => {
|
|
37603
37663
|
timer = setTimeout(() => {
|
|
37604
37664
|
this.pending.delete(id);
|
|
37605
37665
|
const elapsedMs = Date.now() - start;
|
|
37606
37666
|
const error51 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
37607
|
-
|
|
37667
|
+
resolve2({
|
|
37608
37668
|
ok: false,
|
|
37609
37669
|
error: error51,
|
|
37610
37670
|
kind: "timeout",
|
|
@@ -37966,8 +38026,8 @@ var FetchproxyServer = class {
|
|
|
37966
38026
|
const tabUrl = `https://${host}/`;
|
|
37967
38027
|
inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
|
|
37968
38028
|
}
|
|
37969
|
-
const pending = new Promise((
|
|
37970
|
-
this.pendingReadCookies.set(id,
|
|
38029
|
+
const pending = new Promise((resolve2) => {
|
|
38030
|
+
this.pendingReadCookies.set(id, resolve2);
|
|
37971
38031
|
});
|
|
37972
38032
|
await this.sendInnerFrame(inner);
|
|
37973
38033
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
@@ -38025,8 +38085,8 @@ var FetchproxyServer = class {
|
|
|
38025
38085
|
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
38026
38086
|
}
|
|
38027
38087
|
};
|
|
38028
|
-
const pending = new Promise((
|
|
38029
|
-
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
38088
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38089
|
+
this.pendingWriteCookies.set(id, { resolve: resolve2, reject });
|
|
38030
38090
|
});
|
|
38031
38091
|
await this.sendInnerFrame(inner);
|
|
38032
38092
|
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
@@ -38088,8 +38148,8 @@ var FetchproxyServer = class {
|
|
|
38088
38148
|
...opts.pointers ? { pointers: { ...opts.pointers } } : {}
|
|
38089
38149
|
}
|
|
38090
38150
|
};
|
|
38091
|
-
const pending = new Promise((
|
|
38092
|
-
this.pendingStorage.set(id, { resolve, reject });
|
|
38151
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38152
|
+
this.pendingStorage.set(id, { resolve: resolve2, reject });
|
|
38093
38153
|
});
|
|
38094
38154
|
await this.sendInnerFrame(inner);
|
|
38095
38155
|
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
@@ -38193,8 +38253,8 @@ var FetchproxyServer = class {
|
|
|
38193
38253
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38194
38254
|
}
|
|
38195
38255
|
};
|
|
38196
|
-
const pending = new Promise((
|
|
38197
|
-
this.pendingCapture.set(id, { resolve, reject });
|
|
38256
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38257
|
+
this.pendingCapture.set(id, { resolve: resolve2, reject });
|
|
38198
38258
|
});
|
|
38199
38259
|
await this.sendInnerFrame(inner);
|
|
38200
38260
|
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38278,8 +38338,8 @@ var FetchproxyServer = class {
|
|
|
38278
38338
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38279
38339
|
}
|
|
38280
38340
|
};
|
|
38281
|
-
const pending = new Promise((
|
|
38282
|
-
this.pendingRedirect.set(id, { resolve, reject });
|
|
38341
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38342
|
+
this.pendingRedirect.set(id, { resolve: resolve2, reject });
|
|
38283
38343
|
});
|
|
38284
38344
|
await this.sendInnerFrame(inner);
|
|
38285
38345
|
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38360,8 +38420,8 @@ var FetchproxyServer = class {
|
|
|
38360
38420
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38361
38421
|
}
|
|
38362
38422
|
};
|
|
38363
|
-
const pending = new Promise((
|
|
38364
|
-
this.pendingDownload.set(id, { resolve, reject });
|
|
38423
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38424
|
+
this.pendingDownload.set(id, { resolve: resolve2, reject });
|
|
38365
38425
|
});
|
|
38366
38426
|
await this.sendInnerFrame(inner);
|
|
38367
38427
|
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
@@ -38408,8 +38468,8 @@ var FetchproxyServer = class {
|
|
|
38408
38468
|
keys: [...opts.keys]
|
|
38409
38469
|
}
|
|
38410
38470
|
};
|
|
38411
|
-
const pending = new Promise((
|
|
38412
|
-
this.pendingIdb.set(id, { resolve, reject });
|
|
38471
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38472
|
+
this.pendingIdb.set(id, { resolve: resolve2, reject });
|
|
38413
38473
|
});
|
|
38414
38474
|
await this.sendInnerFrame(inner);
|
|
38415
38475
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
@@ -38448,8 +38508,8 @@ var FetchproxyServer = class {
|
|
|
38448
38508
|
op: "read_dom",
|
|
38449
38509
|
init: { origin, names: [...opts.names] }
|
|
38450
38510
|
};
|
|
38451
|
-
const pending = new Promise((
|
|
38452
|
-
this.pendingStorage.set(id, { resolve, reject });
|
|
38511
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38512
|
+
this.pendingStorage.set(id, { resolve: resolve2, reject });
|
|
38453
38513
|
});
|
|
38454
38514
|
await this.sendInnerFrame(inner);
|
|
38455
38515
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
@@ -38494,8 +38554,8 @@ var FetchproxyServer = class {
|
|
|
38494
38554
|
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
38495
38555
|
}
|
|
38496
38556
|
};
|
|
38497
|
-
const pending = new Promise((
|
|
38498
|
-
this.pendingGraphql.set(id, { resolve, reject });
|
|
38557
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38558
|
+
this.pendingGraphql.set(id, { resolve: resolve2, reject });
|
|
38499
38559
|
});
|
|
38500
38560
|
await this.sendInnerFrame(inner);
|
|
38501
38561
|
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
|
|
@@ -38985,7 +39045,7 @@ function loadAccount(env = process.env) {
|
|
|
38985
39045
|
// package.json
|
|
38986
39046
|
var package_default = {
|
|
38987
39047
|
name: "infinitecampus-mcp",
|
|
38988
|
-
version: "2.
|
|
39048
|
+
version: "2.5.0",
|
|
38989
39049
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
38990
39050
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
38991
39051
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -39017,17 +39077,20 @@ var package_default = {
|
|
|
39017
39077
|
".claude-plugin",
|
|
39018
39078
|
"skills",
|
|
39019
39079
|
".mcp.json",
|
|
39020
|
-
"server.json"
|
|
39080
|
+
"server.json",
|
|
39081
|
+
"mint.yaml"
|
|
39021
39082
|
],
|
|
39022
39083
|
scripts: {
|
|
39023
39084
|
build: "tsc && npm run bundle",
|
|
39024
39085
|
bundle: `esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js`,
|
|
39025
39086
|
dev: "node --env-file=.env dist/index.js",
|
|
39026
|
-
test: "vitest run",
|
|
39027
|
-
"test:
|
|
39087
|
+
test: "npm run typecheck && vitest run",
|
|
39088
|
+
"test:coverage": "npm run typecheck && vitest run --coverage",
|
|
39089
|
+
"test:watch": "vitest",
|
|
39090
|
+
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
39028
39091
|
},
|
|
39029
39092
|
dependencies: {
|
|
39030
|
-
"@chrischall/mcp-utils": "^0.
|
|
39093
|
+
"@chrischall/mcp-utils": "^0.18.0",
|
|
39031
39094
|
"@fetchproxy/bootstrap": "^2.0.0",
|
|
39032
39095
|
"@fetchproxy/server": "^2.0.0",
|
|
39033
39096
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -39138,10 +39201,128 @@ async function liftBrowserSession(account2) {
|
|
|
39138
39201
|
|
|
39139
39202
|
// src/client.ts
|
|
39140
39203
|
import { writeFile as writeFile3, stat } from "fs/promises";
|
|
39141
|
-
import { dirname } from "path";
|
|
39204
|
+
import { dirname as dirname2 } from "path";
|
|
39142
39205
|
|
|
39143
39206
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
39207
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
39208
|
+
import { dirname, join as join3, resolve } from "node:path";
|
|
39209
|
+
import { homedir as homedir2 } from "node:os";
|
|
39210
|
+
import { randomBytes, createHmac } from "node:crypto";
|
|
39211
|
+
function bindingDigest(secret, salt) {
|
|
39212
|
+
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
39213
|
+
}
|
|
39214
|
+
function isEnvelope(raw) {
|
|
39215
|
+
return raw !== null && typeof raw === "object" && raw.v === 1 && "state" in raw;
|
|
39216
|
+
}
|
|
39217
|
+
var StatePersistenceError = class extends Error {
|
|
39218
|
+
cause;
|
|
39219
|
+
constructor(cause) {
|
|
39220
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
39221
|
+
this.name = "StatePersistenceError";
|
|
39222
|
+
this.cause = cause;
|
|
39223
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
39224
|
+
}
|
|
39225
|
+
};
|
|
39226
|
+
function createFileStatePersistence(opts) {
|
|
39227
|
+
const { filePath, validate } = opts;
|
|
39228
|
+
const secret = opts.boundTo;
|
|
39229
|
+
return {
|
|
39230
|
+
load() {
|
|
39231
|
+
if (!existsSync(filePath))
|
|
39232
|
+
return null;
|
|
39233
|
+
try {
|
|
39234
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
39235
|
+
let state;
|
|
39236
|
+
if (isEnvelope(raw)) {
|
|
39237
|
+
const b = raw.boundTo;
|
|
39238
|
+
if (secret === void 0) {
|
|
39239
|
+
if (b !== void 0)
|
|
39240
|
+
return null;
|
|
39241
|
+
} else {
|
|
39242
|
+
if (b === void 0)
|
|
39243
|
+
return null;
|
|
39244
|
+
if (bindingDigest(secret, b.salt) !== b.digest)
|
|
39245
|
+
return null;
|
|
39246
|
+
}
|
|
39247
|
+
state = raw.state;
|
|
39248
|
+
} else {
|
|
39249
|
+
if (secret !== void 0)
|
|
39250
|
+
return null;
|
|
39251
|
+
state = raw;
|
|
39252
|
+
}
|
|
39253
|
+
if (validate !== void 0)
|
|
39254
|
+
return validate(state);
|
|
39255
|
+
return state;
|
|
39256
|
+
} catch {
|
|
39257
|
+
return null;
|
|
39258
|
+
}
|
|
39259
|
+
},
|
|
39260
|
+
save(state) {
|
|
39261
|
+
const dir = dirname(filePath);
|
|
39262
|
+
const tmp = `${filePath}.tmp-${randomBytes(6).toString("hex")}`;
|
|
39263
|
+
const dirExisted = existsSync(dir);
|
|
39264
|
+
try {
|
|
39265
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
39266
|
+
if (!dirExisted)
|
|
39267
|
+
chmodSync(dir, 448);
|
|
39268
|
+
let boundTo;
|
|
39269
|
+
if (secret !== void 0) {
|
|
39270
|
+
const salt = randomBytes(16).toString("hex");
|
|
39271
|
+
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
39272
|
+
}
|
|
39273
|
+
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
39274
|
+
writeFileSync(tmp, JSON.stringify(envelope, null, 2), { mode: 384 });
|
|
39275
|
+
chmodSync(tmp, 384);
|
|
39276
|
+
renameSync(tmp, filePath);
|
|
39277
|
+
chmodSync(filePath, 384);
|
|
39278
|
+
} catch (err) {
|
|
39279
|
+
try {
|
|
39280
|
+
if (existsSync(tmp))
|
|
39281
|
+
unlinkSync(tmp);
|
|
39282
|
+
} catch {
|
|
39283
|
+
}
|
|
39284
|
+
throw err;
|
|
39285
|
+
}
|
|
39286
|
+
},
|
|
39287
|
+
clear() {
|
|
39288
|
+
try {
|
|
39289
|
+
if (existsSync(filePath))
|
|
39290
|
+
unlinkSync(filePath);
|
|
39291
|
+
} catch {
|
|
39292
|
+
}
|
|
39293
|
+
}
|
|
39294
|
+
};
|
|
39295
|
+
}
|
|
39296
|
+
function homeOf(env) {
|
|
39297
|
+
return readEnvVar("HOME", { env }) ?? homedir2();
|
|
39298
|
+
}
|
|
39299
|
+
function resolveStateDir(opts = {}) {
|
|
39300
|
+
const env = opts.env;
|
|
39301
|
+
return join3(readEnvVar("MCP_DATA_DIR", { env }) ?? homeOf(env), opts.subdir ?? "");
|
|
39302
|
+
}
|
|
39303
|
+
function resolveStateFile(opts) {
|
|
39304
|
+
const override = opts.envVar !== void 0 ? readEnvVar(opts.envVar, { env: opts.env }) : void 0;
|
|
39305
|
+
if (override !== void 0) {
|
|
39306
|
+
if (override === "~")
|
|
39307
|
+
return homeOf(opts.env);
|
|
39308
|
+
if (override.startsWith("~/"))
|
|
39309
|
+
return join3(homeOf(opts.env), override.slice(2));
|
|
39310
|
+
return resolve(override);
|
|
39311
|
+
}
|
|
39312
|
+
const dirOpts = { env: opts.env };
|
|
39313
|
+
if (opts.subdir !== void 0)
|
|
39314
|
+
dirOpts.subdir = opts.subdir;
|
|
39315
|
+
return join3(resolveStateDir(dirOpts), opts.fileName);
|
|
39316
|
+
}
|
|
39144
39317
|
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
39318
|
+
function isPersistedCookieSession(raw) {
|
|
39319
|
+
if (raw === null || typeof raw !== "object")
|
|
39320
|
+
return false;
|
|
39321
|
+
const r = raw;
|
|
39322
|
+
if (typeof r.sessionAt !== "number" || !Number.isFinite(r.sessionAt))
|
|
39323
|
+
return false;
|
|
39324
|
+
return typeof r.session === "object" && r.session !== null;
|
|
39325
|
+
}
|
|
39145
39326
|
var CookieSessionManager = class {
|
|
39146
39327
|
session;
|
|
39147
39328
|
inFlight;
|
|
@@ -39155,6 +39336,17 @@ var CookieSessionManager = class {
|
|
|
39155
39336
|
maxAgeMs;
|
|
39156
39337
|
now;
|
|
39157
39338
|
onReplayLoginErrorFn;
|
|
39339
|
+
persistence;
|
|
39340
|
+
onPersistErrorFn;
|
|
39341
|
+
/** Persistence is consulted once per process; a miss must not be re-read. */
|
|
39342
|
+
persistenceRead = false;
|
|
39343
|
+
/**
|
|
39344
|
+
* Serializes persistence writes. `seed()` and `invalidate()` are synchronous
|
|
39345
|
+
* by contract and so fire-and-forget their save/clear; with an async backend a
|
|
39346
|
+
* slow save could otherwise land AFTER the clear that followed it and leave an
|
|
39347
|
+
* invalidated session on disk.
|
|
39348
|
+
*/
|
|
39349
|
+
persistChain = Promise.resolve();
|
|
39158
39350
|
constructor(opts) {
|
|
39159
39351
|
this.loginFn = opts.login;
|
|
39160
39352
|
this.isExpiredFn = opts.isExpired ?? (() => false);
|
|
@@ -39162,6 +39354,8 @@ var CookieSessionManager = class {
|
|
|
39162
39354
|
this.maxAgeMs = opts.maxAgeMs;
|
|
39163
39355
|
this.now = opts.now ?? Date.now;
|
|
39164
39356
|
this.onReplayLoginErrorFn = opts.onReplayLoginError;
|
|
39357
|
+
this.persistence = opts.persistence;
|
|
39358
|
+
this.onPersistErrorFn = opts.onPersistError;
|
|
39165
39359
|
}
|
|
39166
39360
|
/** The current session, or `undefined` before the first successful login. */
|
|
39167
39361
|
get current() {
|
|
@@ -39210,6 +39404,8 @@ var CookieSessionManager = class {
|
|
|
39210
39404
|
this.session = session;
|
|
39211
39405
|
this.sessionAt = this.now();
|
|
39212
39406
|
this.inFlight = void 0;
|
|
39407
|
+
this.persistenceRead = true;
|
|
39408
|
+
void this.persist(session, this.sessionAt);
|
|
39213
39409
|
}
|
|
39214
39410
|
/**
|
|
39215
39411
|
* One login attempt. Self-clears `inFlight` on settle so a rejected login
|
|
@@ -39221,13 +39417,25 @@ var CookieSessionManager = class {
|
|
|
39221
39417
|
const holder = {};
|
|
39222
39418
|
holder.p = (async () => {
|
|
39223
39419
|
try {
|
|
39420
|
+
const restored = this.persistence !== void 0 && !this.persistenceRead ? await this.restoreFromPersistence() : null;
|
|
39421
|
+
if (restored !== null) {
|
|
39422
|
+
if (this.inFlight === holder.p) {
|
|
39423
|
+
this.session = restored.session;
|
|
39424
|
+
this.sessionAt = restored.sessionAt;
|
|
39425
|
+
}
|
|
39426
|
+
return restored.session;
|
|
39427
|
+
}
|
|
39224
39428
|
const session = await this.loginFn();
|
|
39429
|
+
const at = this.now();
|
|
39225
39430
|
if (this.inFlight === holder.p) {
|
|
39226
39431
|
this.session = session;
|
|
39227
|
-
this.sessionAt =
|
|
39432
|
+
this.sessionAt = at;
|
|
39228
39433
|
}
|
|
39434
|
+
await this.persist(session, at);
|
|
39229
39435
|
return session;
|
|
39230
39436
|
} catch (err) {
|
|
39437
|
+
if (err instanceof StatePersistenceError)
|
|
39438
|
+
throw err.cause;
|
|
39231
39439
|
if (this.isPermanentErrorFn(err))
|
|
39232
39440
|
this.permanentError = err;
|
|
39233
39441
|
throw err;
|
|
@@ -39247,6 +39455,71 @@ var CookieSessionManager = class {
|
|
|
39247
39455
|
invalidate() {
|
|
39248
39456
|
this.session = void 0;
|
|
39249
39457
|
this.inFlight = void 0;
|
|
39458
|
+
void this.clearPersisted();
|
|
39459
|
+
}
|
|
39460
|
+
/**
|
|
39461
|
+
* The persisted session, if there is one worth using. Read at most once per
|
|
39462
|
+
* process — after that the in-memory session (or its absence) is the truth,
|
|
39463
|
+
* so an invalidate() cannot be undone by a stale file.
|
|
39464
|
+
*/
|
|
39465
|
+
async restoreFromPersistence() {
|
|
39466
|
+
if (this.persistence === void 0 || this.persistenceRead)
|
|
39467
|
+
return null;
|
|
39468
|
+
this.persistenceRead = true;
|
|
39469
|
+
try {
|
|
39470
|
+
let raw = null;
|
|
39471
|
+
await this.enqueuePersist(async () => {
|
|
39472
|
+
raw = await this.persistence?.load();
|
|
39473
|
+
});
|
|
39474
|
+
if (!isPersistedCookieSession(raw))
|
|
39475
|
+
return null;
|
|
39476
|
+
if (this.maxAgeMs !== void 0 && this.now() - raw.sessionAt >= this.maxAgeMs)
|
|
39477
|
+
return null;
|
|
39478
|
+
return raw;
|
|
39479
|
+
} catch {
|
|
39480
|
+
return null;
|
|
39481
|
+
}
|
|
39482
|
+
}
|
|
39483
|
+
/**
|
|
39484
|
+
* Append a persistence op to the chain, preserving call order.
|
|
39485
|
+
*
|
|
39486
|
+
* The RETURNED promise can reject — that is the whole `StatePersistenceError`
|
|
39487
|
+
* path, and the awaited login write depends on it. Only the retained chain is
|
|
39488
|
+
* swallowed, so one failed write cannot poison every later one.
|
|
39489
|
+
*/
|
|
39490
|
+
enqueuePersist(op) {
|
|
39491
|
+
const next = this.persistChain.then(op);
|
|
39492
|
+
this.persistChain = next.catch(() => void 0);
|
|
39493
|
+
return next;
|
|
39494
|
+
}
|
|
39495
|
+
/** Write the session. Silent unless `onPersistError` throws. */
|
|
39496
|
+
persist(session, sessionAt) {
|
|
39497
|
+
return this.enqueuePersist(async () => {
|
|
39498
|
+
if (this.persistence === void 0)
|
|
39499
|
+
return;
|
|
39500
|
+
try {
|
|
39501
|
+
await this.persistence.save({ session, sessionAt });
|
|
39502
|
+
} catch (err) {
|
|
39503
|
+
if (this.onPersistErrorFn === void 0)
|
|
39504
|
+
return;
|
|
39505
|
+
try {
|
|
39506
|
+
this.onPersistErrorFn(err);
|
|
39507
|
+
} catch (hookErr) {
|
|
39508
|
+
throw new StatePersistenceError(hookErr);
|
|
39509
|
+
}
|
|
39510
|
+
}
|
|
39511
|
+
});
|
|
39512
|
+
}
|
|
39513
|
+
/** Discard the persisted session. Never throws. */
|
|
39514
|
+
clearPersisted() {
|
|
39515
|
+
return this.enqueuePersist(async () => {
|
|
39516
|
+
if (this.persistence?.clear === void 0)
|
|
39517
|
+
return;
|
|
39518
|
+
try {
|
|
39519
|
+
await this.persistence.clear();
|
|
39520
|
+
} catch {
|
|
39521
|
+
}
|
|
39522
|
+
});
|
|
39250
39523
|
}
|
|
39251
39524
|
/**
|
|
39252
39525
|
* Run an authenticated `call` with the current session and reactive
|
|
@@ -39283,6 +39556,46 @@ function createCookieSessionManager(opts) {
|
|
|
39283
39556
|
return new CookieSessionManager(opts);
|
|
39284
39557
|
}
|
|
39285
39558
|
|
|
39559
|
+
// src/session-cache.ts
|
|
39560
|
+
function sessionCachePath(env = process.env, district) {
|
|
39561
|
+
const seg = (district ?? "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "_").slice(0, 64);
|
|
39562
|
+
return resolveStateFile({
|
|
39563
|
+
env,
|
|
39564
|
+
envVar: "IC_SESSION_FILE",
|
|
39565
|
+
subdir: ".infinitecampus-mcp",
|
|
39566
|
+
fileName: seg === "" ? "session.json" : `session-${seg}.json`
|
|
39567
|
+
});
|
|
39568
|
+
}
|
|
39569
|
+
function isStored(raw) {
|
|
39570
|
+
if (raw === null || typeof raw !== "object") return false;
|
|
39571
|
+
const r = raw;
|
|
39572
|
+
if (typeof r.sessionAt !== "number") return false;
|
|
39573
|
+
const s = r.session;
|
|
39574
|
+
if (s === null || typeof s !== "object") return false;
|
|
39575
|
+
if (typeof s.cookieHeader !== "string" || s.cookieHeader === "") return false;
|
|
39576
|
+
return typeof s.xsrfToken === "string";
|
|
39577
|
+
}
|
|
39578
|
+
function createSessionCache(opts) {
|
|
39579
|
+
const env = opts.env ?? process.env;
|
|
39580
|
+
if (!parseBoolEnv("IC_SESSION_CACHE", { env, default: true })) return null;
|
|
39581
|
+
const username = opts.username ?? readEnvVar("IC_USERNAME", { env });
|
|
39582
|
+
const password = opts.password ?? readEnvVar("IC_PASSWORD", { env });
|
|
39583
|
+
if (opts.browserBacked === true) return null;
|
|
39584
|
+
const boundTo = username && password ? ["login", opts.baseUrl, username.trim().toLowerCase(), password].join("\0") : null;
|
|
39585
|
+
if (boundTo === null) return null;
|
|
39586
|
+
return createFileStatePersistence({
|
|
39587
|
+
filePath: sessionCachePath(env, opts.district),
|
|
39588
|
+
boundTo,
|
|
39589
|
+
validate: (raw) => isStored(raw) ? raw : null
|
|
39590
|
+
});
|
|
39591
|
+
}
|
|
39592
|
+
function reportCacheWriteFailure(err) {
|
|
39593
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
39594
|
+
console.error(
|
|
39595
|
+
`[infinitecampus-mcp] could not cache the session (${detail}); continuing without the cache \u2014 every restart will authenticate again until this is fixed.`
|
|
39596
|
+
);
|
|
39597
|
+
}
|
|
39598
|
+
|
|
39286
39599
|
// src/client.ts
|
|
39287
39600
|
var SESSION_TTL_MS = 5 * 60 * 60 * 1e3;
|
|
39288
39601
|
var ICClient = class {
|
|
@@ -39308,6 +39621,32 @@ var ICClient = class {
|
|
|
39308
39621
|
* first call to `ensureDiscovery()`.
|
|
39309
39622
|
*/
|
|
39310
39623
|
fetchproxyMode = false;
|
|
39624
|
+
/**
|
|
39625
|
+
* Whether CUPS linked-district discovery has run IN THIS PROCESS. Not "is
|
|
39626
|
+
* there a session" — a session restored from the on-disk cache is real while
|
|
39627
|
+
* discovery has not run, and conflating the two leaves linked districts
|
|
39628
|
+
* invisible. See {@link InfiniteCampusClient.ensureDiscovery}.
|
|
39629
|
+
*/
|
|
39630
|
+
discoveryDone = false;
|
|
39631
|
+
/**
|
|
39632
|
+
* The in-flight discovery, so concurrent callers await the SAME one.
|
|
39633
|
+
*
|
|
39634
|
+
* A bare boolean is not enough: `discoveryDone` flips as soon as the CUPS
|
|
39635
|
+
* probe answers, but `accounts`/`linkedTo` are only populated further down.
|
|
39636
|
+
* A second request for a linked district landing in that window would read
|
|
39637
|
+
* the flag as done, find `accounts` still empty, and throw
|
|
39638
|
+
* UnknownDistrictError for a district that was moments from existing.
|
|
39639
|
+
*/
|
|
39640
|
+
discoveryInFlight = null;
|
|
39641
|
+
/**
|
|
39642
|
+
* The session the last discovery attempt used.
|
|
39643
|
+
*
|
|
39644
|
+
* `login()` runs discovery on every fresh mint. If that probe fails without
|
|
39645
|
+
* latching, `ensureDiscovery` would immediately re-probe with the very same
|
|
39646
|
+
* session and fail identically — two requests for one answer. Retrying is
|
|
39647
|
+
* right; retrying the same session inside one call is not.
|
|
39648
|
+
*/
|
|
39649
|
+
lastDiscoverySession = null;
|
|
39311
39650
|
/**
|
|
39312
39651
|
* fetchproxy lift. Called by the primary manager's login — on the first
|
|
39313
39652
|
* request AND on every renewal, which is what keeps the path alive past
|
|
@@ -39328,11 +39667,11 @@ var ICClient = class {
|
|
|
39328
39667
|
constructor(account2, opts = {}) {
|
|
39329
39668
|
this.accounts.set(account2.name, account2);
|
|
39330
39669
|
this.primaryName = account2.name;
|
|
39331
|
-
this.managers.set(account2.name, this.makeManager(account2));
|
|
39332
39670
|
if (opts.refreshSession) {
|
|
39333
39671
|
this.refreshSession = opts.refreshSession;
|
|
39334
39672
|
this.fetchproxyMode = true;
|
|
39335
39673
|
}
|
|
39674
|
+
this.managers.set(account2.name, this.makeManager(account2));
|
|
39336
39675
|
}
|
|
39337
39676
|
/**
|
|
39338
39677
|
* Build the per-district CookieSessionManager.
|
|
@@ -39352,8 +39691,17 @@ var ICClient = class {
|
|
|
39352
39691
|
* bookkeeping used to provide.
|
|
39353
39692
|
*/
|
|
39354
39693
|
makeManager(account2) {
|
|
39694
|
+
const cache = account2.name === this.primaryName ? createSessionCache({
|
|
39695
|
+
district: account2.name,
|
|
39696
|
+
baseUrl: account2.baseUrl,
|
|
39697
|
+
username: account2.username,
|
|
39698
|
+
password: account2.password,
|
|
39699
|
+
browserBacked: this.fetchproxyMode
|
|
39700
|
+
}) : null;
|
|
39355
39701
|
return createCookieSessionManager({
|
|
39356
39702
|
login: () => this.login(account2),
|
|
39703
|
+
persistence: cache ?? void 0,
|
|
39704
|
+
onPersistError: reportCacheWriteFailure,
|
|
39357
39705
|
isExpired: (res) => this.detectExpiredSession(account2.name, res),
|
|
39358
39706
|
isPermanentError: (e) => e instanceof AuthFailedError && e.permanent,
|
|
39359
39707
|
maxAgeMs: SESSION_TTL_MS,
|
|
@@ -39385,7 +39733,24 @@ var ICClient = class {
|
|
|
39385
39733
|
}
|
|
39386
39734
|
async ensureDiscovery() {
|
|
39387
39735
|
const session = await this.managers.get(this.primaryName).ensure();
|
|
39388
|
-
|
|
39736
|
+
if (this.discoveryDone) return;
|
|
39737
|
+
if (this.discoveryInFlight !== null) {
|
|
39738
|
+
await this.discoveryInFlight;
|
|
39739
|
+
return;
|
|
39740
|
+
}
|
|
39741
|
+
const primary = this.accounts.get(this.primaryName);
|
|
39742
|
+
const manager = this.managers.get(this.primaryName);
|
|
39743
|
+
this.discoveryInFlight = (async () => {
|
|
39744
|
+
if (this.lastDiscoverySession === session) {
|
|
39745
|
+
await manager.ensure();
|
|
39746
|
+
return;
|
|
39747
|
+
}
|
|
39748
|
+
if (await this.discoverLinkedDistricts(primary, session)) return;
|
|
39749
|
+
await manager.ensure();
|
|
39750
|
+
})().finally(() => {
|
|
39751
|
+
this.discoveryInFlight = null;
|
|
39752
|
+
});
|
|
39753
|
+
await this.discoveryInFlight;
|
|
39389
39754
|
}
|
|
39390
39755
|
listDistricts() {
|
|
39391
39756
|
return [...this.accounts.values()].map((a) => ({
|
|
@@ -39483,7 +39848,14 @@ var ICClient = class {
|
|
|
39483
39848
|
await this.discoverLinkedDistricts(account2, session);
|
|
39484
39849
|
return session;
|
|
39485
39850
|
}
|
|
39851
|
+
/**
|
|
39852
|
+
* @returns whether the CUPS probe ANSWERED. `false` means the session was
|
|
39853
|
+
* rejected (and has been invalidated), so the caller can mint a fresh one and
|
|
39854
|
+
* try once more — without that, recovery lands a call too late and the first
|
|
39855
|
+
* `ic_list_districts` after a restart silently omits linked districts.
|
|
39856
|
+
*/
|
|
39486
39857
|
async discoverLinkedDistricts(account2, session) {
|
|
39858
|
+
this.lastDiscoverySession = session;
|
|
39487
39859
|
try {
|
|
39488
39860
|
const baseHeaders = {
|
|
39489
39861
|
Cookie: session.cookieHeader,
|
|
@@ -39497,14 +39869,23 @@ var ICClient = class {
|
|
|
39497
39869
|
`${account2.baseUrl}/campus/api/campus/authentication/cups/linkedAccounts`,
|
|
39498
39870
|
{ headers: baseHeaders }
|
|
39499
39871
|
);
|
|
39500
|
-
if (!laRes.ok)
|
|
39872
|
+
if (!laRes.ok) {
|
|
39873
|
+
if (laRes.status === 401 || laRes.status === 403) {
|
|
39874
|
+
this.managers.get(account2.name)?.invalidate();
|
|
39875
|
+
return false;
|
|
39876
|
+
}
|
|
39877
|
+
return true;
|
|
39878
|
+
}
|
|
39501
39879
|
const laData = await laRes.json();
|
|
39502
|
-
if (!laData.accounts?.length)
|
|
39880
|
+
if (!laData.accounts?.length) {
|
|
39881
|
+
this.discoveryDone = true;
|
|
39882
|
+
return true;
|
|
39883
|
+
}
|
|
39503
39884
|
const [origRes, currRes] = await Promise.all([
|
|
39504
39885
|
fetch(`${account2.baseUrl}/campus/api/campus/user/userAccountSwitch/originalDistrict`, { headers: baseHeaders }),
|
|
39505
39886
|
fetch(`${account2.baseUrl}/campus/api/campus/districts/current`, { headers: baseHeaders })
|
|
39506
39887
|
]);
|
|
39507
|
-
if (!origRes.ok || !currRes.ok) return;
|
|
39888
|
+
if (!origRes.ok || !currRes.ok) return true;
|
|
39508
39889
|
const origData = await origRes.json();
|
|
39509
39890
|
const currData = await currRes.json();
|
|
39510
39891
|
for (const linked of laData.accounts) {
|
|
@@ -39567,8 +39948,11 @@ var ICClient = class {
|
|
|
39567
39948
|
console.error(`[ic] CUPS flow failed for ${linked.districtName}: ${e instanceof Error ? e.message : e}`);
|
|
39568
39949
|
}
|
|
39569
39950
|
}
|
|
39951
|
+
this.discoveryDone = true;
|
|
39952
|
+
return true;
|
|
39570
39953
|
} catch (e) {
|
|
39571
39954
|
console.error(`[ic] Linked district discovery failed: ${e instanceof Error ? e.message : e}`);
|
|
39955
|
+
return true;
|
|
39572
39956
|
}
|
|
39573
39957
|
}
|
|
39574
39958
|
async download(district, path, destinationPath, opts = {}) {
|
|
@@ -39579,7 +39963,7 @@ var ICClient = class {
|
|
|
39579
39963
|
}
|
|
39580
39964
|
if (destStat?.isDirectory()) throw new InvalidPathError(destinationPath);
|
|
39581
39965
|
if (destStat && !opts.overwrite) throw new FileExistsError(destinationPath);
|
|
39582
|
-
const parent =
|
|
39966
|
+
const parent = dirname2(destinationPath);
|
|
39583
39967
|
try {
|
|
39584
39968
|
await stat(parent);
|
|
39585
39969
|
} catch {
|
|
@@ -40605,7 +40989,7 @@ function registerFeaturesTools(server, client) {
|
|
|
40605
40989
|
|
|
40606
40990
|
// src/index.ts
|
|
40607
40991
|
await loadDotenvSafely({
|
|
40608
|
-
path:
|
|
40992
|
+
path: join4(dirname3(fileURLToPath(import.meta.url)), "..", ".env"),
|
|
40609
40993
|
override: false
|
|
40610
40994
|
});
|
|
40611
40995
|
var AI_NOTICE = "[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.";
|
|
@@ -40623,7 +41007,7 @@ try {
|
|
|
40623
41007
|
}
|
|
40624
41008
|
var COMMON = {
|
|
40625
41009
|
name: "infinitecampus",
|
|
40626
|
-
version: "2.
|
|
41010
|
+
version: "2.5.0"
|
|
40627
41011
|
// x-release-please-version
|
|
40628
41012
|
};
|
|
40629
41013
|
if (account) {
|