infinitecampus-mcp 2.8.4 → 2.8.5
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 +89 -26
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Infinite Campus (Campus Parent) — grades, attendance, assignments, messages, documents",
|
|
10
|
-
"version": "2.8.
|
|
10
|
+
"version": "2.8.5"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Infinite Campus",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Infinite Campus (Campus Parent) MCP server for Claude — grades, attendance, assignments, messages, and documents via natural language",
|
|
18
|
-
"version": "2.8.
|
|
18
|
+
"version": "2.8.5",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinitecampus-mcp",
|
|
3
3
|
"displayName": "Infinite Campus",
|
|
4
|
-
"version": "2.8.
|
|
4
|
+
"version": "2.8.5",
|
|
5
5
|
"description": "Infinite Campus (Campus Parent) MCP server for Claude — grades, attendance, assignments, messages, and documents",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/dist/bundle.js
CHANGED
|
@@ -9444,7 +9444,7 @@ var require_websocket = __commonJS({
|
|
|
9444
9444
|
var http = __require("http");
|
|
9445
9445
|
var net = __require("net");
|
|
9446
9446
|
var tls = __require("tls");
|
|
9447
|
-
var { randomBytes:
|
|
9447
|
+
var { randomBytes: randomBytes3, createHash } = __require("crypto");
|
|
9448
9448
|
var { Duplex, Readable } = __require("stream");
|
|
9449
9449
|
var { URL: URL2 } = __require("url");
|
|
9450
9450
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9982,7 +9982,7 @@ var require_websocket = __commonJS({
|
|
|
9982
9982
|
}
|
|
9983
9983
|
}
|
|
9984
9984
|
const defaultPort = isSecure ? 443 : 80;
|
|
9985
|
-
const key =
|
|
9985
|
+
const key = randomBytes3(16).toString("base64");
|
|
9986
9986
|
const request = isSecure ? https.request : http.request;
|
|
9987
9987
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
9988
9988
|
let perMessageDeflate;
|
|
@@ -42072,9 +42072,29 @@ var SessionState = class {
|
|
|
42072
42072
|
outboundSeq = 0;
|
|
42073
42073
|
lastInboundSeq = 0;
|
|
42074
42074
|
inflightInbound = /* @__PURE__ */ new Set();
|
|
42075
|
+
saturationWarned = false;
|
|
42075
42076
|
constructor(sessionKey) {
|
|
42076
42077
|
this.sessionKey = sessionKey;
|
|
42077
42078
|
}
|
|
42079
|
+
/**
|
|
42080
|
+
* Whether the caller holding this claim verdict should log saturation now.
|
|
42081
|
+
* True only on the TRANSITION into it: a line per dropped frame turned a
|
|
42082
|
+
* flood — or a set that never drains — into a log flood amplifying the very
|
|
42083
|
+
* condition it reported (#376). An `'ok'` claim re-arms the latch, since it
|
|
42084
|
+
* is the one signal every caller sees that the set has drained below the
|
|
42085
|
+
* bound; a replay needs no capacity and leaves it alone. Latched per
|
|
42086
|
+
* session, so a renegotiated one starts armed. A set hovering at the bound
|
|
42087
|
+
* can still alternate ok/saturated, but that logs at the rate frames finish
|
|
42088
|
+
* opening, not the rate they arrive.
|
|
42089
|
+
*/
|
|
42090
|
+
saturationWarningDue(claim2) {
|
|
42091
|
+
if (claim2 === "ok")
|
|
42092
|
+
this.saturationWarned = false;
|
|
42093
|
+
if (claim2 !== "saturated" || this.saturationWarned)
|
|
42094
|
+
return false;
|
|
42095
|
+
this.saturationWarned = true;
|
|
42096
|
+
return true;
|
|
42097
|
+
}
|
|
42078
42098
|
nextOutboundSeq() {
|
|
42079
42099
|
this.outboundSeq += 1;
|
|
42080
42100
|
return this.outboundSeq;
|
|
@@ -42085,19 +42105,21 @@ var SessionState = class {
|
|
|
42085
42105
|
* the first `await` of the receive path, or a duplicate frame read in the
|
|
42086
42106
|
* same pass will be judged against a counter neither frame has moved yet.
|
|
42087
42107
|
*
|
|
42088
|
-
*
|
|
42089
|
-
* duplicate of one still in flight
|
|
42090
|
-
*
|
|
42108
|
+
* Anything but `'ok'` means "not this frame's to process": `'replay'` for
|
|
42109
|
+
* a spent seq or a duplicate of one still in flight, `'saturated'` when the
|
|
42110
|
+
* in-flight bound is full. Replay is checked first, since refusing a stale
|
|
42111
|
+
* seq needs no capacity. Every `'ok'` MUST be answered by exactly one
|
|
42112
|
+
* {@link commitInboundSeq} or {@link releaseInboundSeq}.
|
|
42091
42113
|
*/
|
|
42092
42114
|
claimInboundSeq(seq) {
|
|
42093
42115
|
if (seq <= this.lastInboundSeq)
|
|
42094
|
-
return
|
|
42116
|
+
return "replay";
|
|
42095
42117
|
if (this.inflightInbound.has(seq))
|
|
42096
|
-
return
|
|
42118
|
+
return "replay";
|
|
42097
42119
|
if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
|
|
42098
|
-
return
|
|
42120
|
+
return "saturated";
|
|
42099
42121
|
this.inflightInbound.add(seq);
|
|
42100
|
-
return
|
|
42122
|
+
return "ok";
|
|
42101
42123
|
}
|
|
42102
42124
|
/**
|
|
42103
42125
|
* Give a claim back without spending the seq — for a frame that did not
|
|
@@ -42192,11 +42214,12 @@ async function awaitSessionReady(ready, opts) {
|
|
|
42192
42214
|
}
|
|
42193
42215
|
|
|
42194
42216
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
42195
|
-
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42217
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42196
42218
|
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
42197
42219
|
|
|
42198
42220
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
42199
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
42221
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
42222
|
+
import { randomBytes } from "node:crypto";
|
|
42200
42223
|
import { isAbsolute, join } from "node:path";
|
|
42201
42224
|
import { homedir } from "node:os";
|
|
42202
42225
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
@@ -42252,16 +42275,46 @@ function parseIdentity(text) {
|
|
|
42252
42275
|
createdAt: j.createdAt
|
|
42253
42276
|
};
|
|
42254
42277
|
}
|
|
42278
|
+
async function openIdentityDir(dir) {
|
|
42279
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
42280
|
+
try {
|
|
42281
|
+
await chmod(dir, 448);
|
|
42282
|
+
} catch (e) {
|
|
42283
|
+
const code = e.code;
|
|
42284
|
+
if (code !== "EROFS" && code !== "EPERM")
|
|
42285
|
+
throw e;
|
|
42286
|
+
}
|
|
42287
|
+
}
|
|
42288
|
+
var STAGING_BYTES = 6;
|
|
42289
|
+
async function sweepStagingFiles(dir, serverName) {
|
|
42290
|
+
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
42291
|
+
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
42292
|
+
let names;
|
|
42293
|
+
try {
|
|
42294
|
+
names = await readdir(dir);
|
|
42295
|
+
} catch {
|
|
42296
|
+
return;
|
|
42297
|
+
}
|
|
42298
|
+
await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join(dir, name)).catch(() => void 0)));
|
|
42299
|
+
}
|
|
42255
42300
|
async function writeIdentityFile(dir, serverName, id) {
|
|
42256
42301
|
const path = identityFilePath(dir, serverName);
|
|
42257
|
-
await
|
|
42258
|
-
await
|
|
42259
|
-
|
|
42302
|
+
await openIdentityDir(dir);
|
|
42303
|
+
await sweepStagingFiles(dir, serverName);
|
|
42304
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
42305
|
+
try {
|
|
42306
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
42307
|
+
await chmod(tmp, 384);
|
|
42308
|
+
await rename(tmp, path);
|
|
42309
|
+
} catch (e) {
|
|
42310
|
+
await rm(tmp, { force: true });
|
|
42311
|
+
throw e;
|
|
42312
|
+
}
|
|
42260
42313
|
return path;
|
|
42261
42314
|
}
|
|
42262
42315
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
42263
42316
|
const path = identityFilePath(dir, serverName);
|
|
42264
|
-
await
|
|
42317
|
+
await openIdentityDir(dir);
|
|
42265
42318
|
try {
|
|
42266
42319
|
return parseIdentity(await readFile(path, "utf8"));
|
|
42267
42320
|
} catch (e) {
|
|
@@ -42374,7 +42427,7 @@ async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
|
42374
42427
|
const tmp = `${path}.tmp`;
|
|
42375
42428
|
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
42376
42429
|
await chmod2(tmp, 384);
|
|
42377
|
-
await
|
|
42430
|
+
await rename2(tmp, path);
|
|
42378
42431
|
}
|
|
42379
42432
|
|
|
42380
42433
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
@@ -42729,7 +42782,11 @@ async function startHost(opts) {
|
|
|
42729
42782
|
const session = ownSession;
|
|
42730
42783
|
if (!session)
|
|
42731
42784
|
return;
|
|
42732
|
-
|
|
42785
|
+
const claim2 = session.claimInboundSeq(frame.seq);
|
|
42786
|
+
if (session.saturationWarningDue(claim2)) {
|
|
42787
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
42788
|
+
}
|
|
42789
|
+
if (claim2 !== "ok")
|
|
42733
42790
|
return;
|
|
42734
42791
|
let inner;
|
|
42735
42792
|
try {
|
|
@@ -42858,7 +42915,9 @@ async function startHost(opts) {
|
|
|
42858
42915
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
42859
42916
|
var enc5 = new TextEncoder();
|
|
42860
42917
|
async function startPeer(opts) {
|
|
42861
|
-
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}
|
|
42918
|
+
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
42919
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
42920
|
+
});
|
|
42862
42921
|
await new Promise((resolve2, reject) => {
|
|
42863
42922
|
const onOpen = () => {
|
|
42864
42923
|
cleanup();
|
|
@@ -43096,7 +43155,11 @@ async function startPeer(opts) {
|
|
|
43096
43155
|
const inboundSession = session;
|
|
43097
43156
|
if (!inboundSession)
|
|
43098
43157
|
return;
|
|
43099
|
-
|
|
43158
|
+
const claim2 = inboundSession.claimInboundSeq(frame.seq);
|
|
43159
|
+
if (inboundSession.saturationWarningDue(claim2)) {
|
|
43160
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
43161
|
+
}
|
|
43162
|
+
if (claim2 !== "ok")
|
|
43100
43163
|
return;
|
|
43101
43164
|
let result;
|
|
43102
43165
|
try {
|
|
@@ -45652,7 +45715,7 @@ function loadAccount(env = process.env) {
|
|
|
45652
45715
|
// package.json
|
|
45653
45716
|
var package_default = {
|
|
45654
45717
|
name: "infinitecampus-mcp",
|
|
45655
|
-
version: "2.8.
|
|
45718
|
+
version: "2.8.5",
|
|
45656
45719
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
45657
45720
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
45658
45721
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45698,8 +45761,8 @@ var package_default = {
|
|
|
45698
45761
|
},
|
|
45699
45762
|
dependencies: {
|
|
45700
45763
|
"@chrischall/mcp-utils": "^0.26.1",
|
|
45701
|
-
"@fetchproxy/bootstrap": "^3.0.
|
|
45702
|
-
"@fetchproxy/server": "^3.0.
|
|
45764
|
+
"@fetchproxy/bootstrap": "^3.0.1",
|
|
45765
|
+
"@fetchproxy/server": "^3.0.1",
|
|
45703
45766
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
45704
45767
|
dotenv: "^17.4.2",
|
|
45705
45768
|
"node-html-parser": "^9.0.0",
|
|
@@ -45814,7 +45877,7 @@ import { dirname as dirname2 } from "path";
|
|
|
45814
45877
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
45815
45878
|
import { dirname, join as join3, resolve } from "node:path";
|
|
45816
45879
|
import { homedir as homedir2 } from "node:os";
|
|
45817
|
-
import { randomBytes, createHmac } from "node:crypto";
|
|
45880
|
+
import { randomBytes as randomBytes2, createHmac } from "node:crypto";
|
|
45818
45881
|
function bindingDigest(secret, salt) {
|
|
45819
45882
|
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
45820
45883
|
}
|
|
@@ -45866,7 +45929,7 @@ function createFileStatePersistence(opts) {
|
|
|
45866
45929
|
},
|
|
45867
45930
|
save(state) {
|
|
45868
45931
|
const dir = dirname(filePath);
|
|
45869
|
-
const tmp = `${filePath}.tmp-${
|
|
45932
|
+
const tmp = `${filePath}.tmp-${randomBytes2(6).toString("hex")}`;
|
|
45870
45933
|
const dirExisted = existsSync(dir);
|
|
45871
45934
|
try {
|
|
45872
45935
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -45874,7 +45937,7 @@ function createFileStatePersistence(opts) {
|
|
|
45874
45937
|
chmodSync(dir, 448);
|
|
45875
45938
|
let boundTo;
|
|
45876
45939
|
if (secret !== void 0) {
|
|
45877
|
-
const salt =
|
|
45940
|
+
const salt = randomBytes2(16).toString("hex");
|
|
45878
45941
|
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
45879
45942
|
}
|
|
45880
45943
|
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
@@ -47670,7 +47733,7 @@ try {
|
|
|
47670
47733
|
}
|
|
47671
47734
|
var COMMON = {
|
|
47672
47735
|
name: "infinitecampus",
|
|
47673
|
-
version: "2.8.
|
|
47736
|
+
version: "2.8.5"
|
|
47674
47737
|
// x-release-please-version
|
|
47675
47738
|
};
|
|
47676
47739
|
var healthState = { account, source, configError, client: null };
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ catch (e) {
|
|
|
56
56
|
// avoids — see `unconfiguredClient`.
|
|
57
57
|
const COMMON = {
|
|
58
58
|
name: 'infinitecampus',
|
|
59
|
-
version: '2.8.
|
|
59
|
+
version: '2.8.5', // x-release-please-version
|
|
60
60
|
};
|
|
61
61
|
// Shared with the healthcheck so it can report the SAME state the tools see,
|
|
62
62
|
// including on the unconfigured path where there are no tools at all.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinitecampus-mcp",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.5",
|
|
4
4
|
"mcpName": "io.github.chrischall/infinitecampus-mcp",
|
|
5
5
|
"description": "Infinite Campus (Campus Parent) MCP server — multi-district read + message/document write",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@chrischall/mcp-utils": "^0.26.1",
|
|
49
|
-
"@fetchproxy/bootstrap": "^3.0.
|
|
50
|
-
"@fetchproxy/server": "^3.0.
|
|
49
|
+
"@fetchproxy/bootstrap": "^3.0.1",
|
|
50
|
+
"@fetchproxy/server": "^3.0.1",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
52
52
|
"dotenv": "^17.4.2",
|
|
53
53
|
"node-html-parser": "^9.0.0",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/infinitecampus-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.8.
|
|
9
|
+
"version": "2.8.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "infinitecampus-mcp",
|
|
14
|
-
"version": "2.8.
|
|
14
|
+
"version": "2.8.5",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|