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/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 = resolve.call(this, root, ref);
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 resolve(root, ref) {
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 resolve(baseURI, relativeURI, options) {
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 = randomBytes(16).toString("base64");
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 dirname2, join as join3 } from "path";
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((resolve) => setTimeout(resolve, pollInterval));
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((resolve, reject) => {
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
- resolve(parseResult.data);
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((resolve, reject) => {
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(resolve, interval);
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((resolve) => setTimeout(resolve, pollInterval));
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((resolve) => {
34673
+ return new Promise((resolve2) => {
34674
34674
  const json2 = serializeMessage(message);
34675
34675
  if (this._stdout.write(json2)) {
34676
- resolve();
34676
+ resolve2();
34677
34677
  } else {
34678
- this._stdout.once("drain", resolve);
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((resolve, reject) => {
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
- resolve({ role: "peer" });
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
- resolve({ role: "host", server });
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((resolve, reject) => {
36421
- resolveOwnSession = resolve;
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((resolve) => {
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(() => resolve());
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((resolve, reject) => {
36681
- ws.once("open", () => resolve());
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((resolve, reject) => {
36712
- resolveFirstReady = resolve;
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((resolve) => {
37591
- this.pending.set(id, resolve);
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((resolve) => {
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
- resolve({
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((resolve) => {
37970
- this.pendingReadCookies.set(id, resolve);
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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((resolve, reject) => {
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.4.5",
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:watch": "vitest"
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.14.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 = this.now();
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
- void session;
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) return;
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) return;
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 = dirname(destinationPath);
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: join3(dirname2(fileURLToPath(import.meta.url)), "..", ".env"),
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.4.5"
41010
+ version: "2.5.0"
40627
41011
  // x-release-please-version
40628
41012
  };
40629
41013
  if (account) {