infinitecampus-mcp 2.4.3 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/auth.js +12 -0
- package/dist/bundle.js +71 -14
- package/dist/index.js +1 -1
- package/package.json +2 -2
- 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.4.
|
|
10
|
+
"version": "2.4.4"
|
|
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.4.
|
|
18
|
+
"version": "2.4.4",
|
|
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.4.
|
|
4
|
+
"version": "2.4.4",
|
|
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/auth.js
CHANGED
|
@@ -115,6 +115,18 @@ function lifterFor(host) {
|
|
|
115
115
|
// <district>.infinitecampus.org, etc.). Declare just this host
|
|
116
116
|
// — each district is its own root, no wildcard needed.
|
|
117
117
|
domains: [host],
|
|
118
|
+
// IC is a Tomcat app deployed at /campus, and Tomcat scopes JSESSIONID
|
|
119
|
+
// to the servlet context by default:
|
|
120
|
+
//
|
|
121
|
+
// JSESSIONID=...; Path=/campus; Secure; HttpOnly; SameSite=Lax
|
|
122
|
+
//
|
|
123
|
+
// chrome.cookies.get matches that Path against the URL it is given, so a
|
|
124
|
+
// read aimed at the origin root silently returns nothing — which is
|
|
125
|
+
// indistinguishable from "the user is signed out", and is why this path
|
|
126
|
+
// never worked on any tenant. Verified live: without this the lift found
|
|
127
|
+
// no cookies while a fetch through the same tab returned authenticated
|
|
128
|
+
// data; with it, JSESSIONID + XSRF-TOKEN both come back.
|
|
129
|
+
storagePath: '/campus',
|
|
118
130
|
declare: {
|
|
119
131
|
// JSESSIONID is HttpOnly (chrome.cookies.get sees it; the
|
|
120
132
|
// security gate is the declared key list). XSRF-TOKEN is
|
package/dist/bundle.js
CHANGED
|
@@ -34977,6 +34977,15 @@ function assertHttpUrl(x, label) {
|
|
|
34977
34977
|
throw new ProtocolError(`${label}: must be http(s), got ${u.protocol}`);
|
|
34978
34978
|
}
|
|
34979
34979
|
}
|
|
34980
|
+
function assertCookiePath(x, label) {
|
|
34981
|
+
assertString(x, label);
|
|
34982
|
+
if (!x.startsWith("/") || x.startsWith("//")) {
|
|
34983
|
+
throw new ProtocolError(`${label}: must be an absolute path like "/campus"`);
|
|
34984
|
+
}
|
|
34985
|
+
if (x.includes("?") || x.includes("#") || x.includes("\\")) {
|
|
34986
|
+
throw new ProtocolError(`${label}: must not contain a query, fragment, or backslash`);
|
|
34987
|
+
}
|
|
34988
|
+
}
|
|
34980
34989
|
function assertHttpsOriginOnly(x, label) {
|
|
34981
34990
|
assertString(x, label);
|
|
34982
34991
|
let u;
|
|
@@ -35421,8 +35430,10 @@ function validateInnerRequest(raw) {
|
|
|
35421
35430
|
}
|
|
35422
35431
|
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
35423
35432
|
assertNonEmptyKeyArray(raw.init.keys, "inner.init.keys");
|
|
35433
|
+
if (raw.init.path !== void 0)
|
|
35434
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
35424
35435
|
for (const k of Object.keys(raw.init)) {
|
|
35425
|
-
if (k !== "origin" && k !== "keys") {
|
|
35436
|
+
if (k !== "origin" && k !== "keys" && k !== "path") {
|
|
35426
35437
|
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_cookies`);
|
|
35427
35438
|
}
|
|
35428
35439
|
}
|
|
@@ -36653,6 +36664,22 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
36653
36664
|
this.hint = hint;
|
|
36654
36665
|
}
|
|
36655
36666
|
};
|
|
36667
|
+
var FetchproxyScopeError = class extends FetchproxyProtocolError {
|
|
36668
|
+
/** The extension's raw rejection, unmodified. */
|
|
36669
|
+
originalError;
|
|
36670
|
+
hint;
|
|
36671
|
+
constructor(originalError) {
|
|
36672
|
+
const hint = "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the Transporter extension popup, then re-run \u2014 you will be asked to approve the new scope. This is not a version problem and does not need an update.";
|
|
36673
|
+
super(`${originalError} \u2014 ${hint}`);
|
|
36674
|
+
this.name = "FetchproxyScopeError";
|
|
36675
|
+
this.originalError = originalError;
|
|
36676
|
+
this.hint = hint;
|
|
36677
|
+
}
|
|
36678
|
+
};
|
|
36679
|
+
var SCOPE_REJECTION = /not in declared/;
|
|
36680
|
+
function protocolErrorFrom(error51) {
|
|
36681
|
+
return SCOPE_REJECTION.test(error51) ? new FetchproxyScopeError(error51) : new FetchproxyProtocolError(error51);
|
|
36682
|
+
}
|
|
36656
36683
|
var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
36657
36684
|
url;
|
|
36658
36685
|
timeoutMs;
|
|
@@ -36682,6 +36709,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
36682
36709
|
}
|
|
36683
36710
|
};
|
|
36684
36711
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
36712
|
+
function normalizeCookiePath(path) {
|
|
36713
|
+
if (path === void 0 || path === "")
|
|
36714
|
+
return void 0;
|
|
36715
|
+
const trimmed = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
|
|
36716
|
+
try {
|
|
36717
|
+
assertCookiePath(trimmed, "path");
|
|
36718
|
+
} catch (e) {
|
|
36719
|
+
throw new Error(`FetchproxyServer: ${e instanceof Error ? e.message : String(e)} (got ${JSON.stringify(path)})`);
|
|
36720
|
+
}
|
|
36721
|
+
return trimmed;
|
|
36722
|
+
}
|
|
36685
36723
|
function assertSubdomainLabel(label) {
|
|
36686
36724
|
if (!SUBDOMAIN_LABEL_RE.test(label)) {
|
|
36687
36725
|
throw new Error(`FetchproxyServer: subdomain must be a DNS label like "www" or "api" (or dot-separated like "auth.api"), got ${JSON.stringify(label)}`);
|
|
@@ -37323,7 +37361,7 @@ var FetchproxyServer = class {
|
|
|
37323
37361
|
port: this.opts.port
|
|
37324
37362
|
});
|
|
37325
37363
|
}
|
|
37326
|
-
return
|
|
37364
|
+
return protocolErrorFrom(result.error);
|
|
37327
37365
|
}
|
|
37328
37366
|
/**
|
|
37329
37367
|
* Convenience wrapper around `fetch()`. Builds the URL from a path
|
|
@@ -37574,9 +37612,14 @@ var FetchproxyServer = class {
|
|
|
37574
37612
|
let inner;
|
|
37575
37613
|
if (opts.keys !== void 0) {
|
|
37576
37614
|
this.assertScopeSubset(opts.keys, this.opts.cookieKeys, "cookieKeys");
|
|
37615
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
37577
37616
|
const initV3 = {
|
|
37617
|
+
// Origin stays BARE. The path travels as its own validated field —
|
|
37618
|
+
// `assertHttpsOriginOnly` deliberately refuses a path here so one
|
|
37619
|
+
// cannot be used to re-point the read past the domain gate.
|
|
37578
37620
|
origin: `https://${host}`,
|
|
37579
|
-
keys: [...opts.keys]
|
|
37621
|
+
keys: [...opts.keys],
|
|
37622
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
37580
37623
|
};
|
|
37581
37624
|
inner = { type: "request", id, op: "read_cookies", init: initV3 };
|
|
37582
37625
|
} else {
|
|
@@ -37589,7 +37632,7 @@ var FetchproxyServer = class {
|
|
|
37589
37632
|
await this.sendInnerFrame(inner);
|
|
37590
37633
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37591
37634
|
if (!result.ok) {
|
|
37592
|
-
throw
|
|
37635
|
+
throw protocolErrorFrom(result.error);
|
|
37593
37636
|
}
|
|
37594
37637
|
return result.cookies;
|
|
37595
37638
|
}
|
|
@@ -38139,7 +38182,7 @@ var FetchproxyServer = class {
|
|
|
38139
38182
|
storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
|
|
38140
38183
|
}
|
|
38141
38184
|
} else {
|
|
38142
|
-
storageCb.reject(
|
|
38185
|
+
storageCb.reject(protocolErrorFrom(inner.error));
|
|
38143
38186
|
}
|
|
38144
38187
|
return;
|
|
38145
38188
|
}
|
|
@@ -38153,7 +38196,7 @@ var FetchproxyServer = class {
|
|
|
38153
38196
|
captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
|
|
38154
38197
|
}
|
|
38155
38198
|
} else {
|
|
38156
|
-
captureCb.reject(
|
|
38199
|
+
captureCb.reject(protocolErrorFrom(inner.error));
|
|
38157
38200
|
}
|
|
38158
38201
|
return;
|
|
38159
38202
|
}
|
|
@@ -38167,7 +38210,7 @@ var FetchproxyServer = class {
|
|
|
38167
38210
|
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
38168
38211
|
}
|
|
38169
38212
|
} else {
|
|
38170
|
-
redirectCb.reject(
|
|
38213
|
+
redirectCb.reject(protocolErrorFrom(inner.error));
|
|
38171
38214
|
}
|
|
38172
38215
|
return;
|
|
38173
38216
|
}
|
|
@@ -38181,7 +38224,7 @@ var FetchproxyServer = class {
|
|
|
38181
38224
|
idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
|
|
38182
38225
|
}
|
|
38183
38226
|
} else {
|
|
38184
|
-
idbCb.reject(
|
|
38227
|
+
idbCb.reject(protocolErrorFrom(inner.error));
|
|
38185
38228
|
}
|
|
38186
38229
|
return;
|
|
38187
38230
|
}
|
|
@@ -38195,7 +38238,7 @@ var FetchproxyServer = class {
|
|
|
38195
38238
|
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
38196
38239
|
}
|
|
38197
38240
|
} else {
|
|
38198
|
-
downloadCb.reject(
|
|
38241
|
+
downloadCb.reject(protocolErrorFrom(inner.error));
|
|
38199
38242
|
}
|
|
38200
38243
|
return;
|
|
38201
38244
|
}
|
|
@@ -38209,7 +38252,7 @@ var FetchproxyServer = class {
|
|
|
38209
38252
|
graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
|
|
38210
38253
|
}
|
|
38211
38254
|
} else {
|
|
38212
|
-
graphqlCb.reject(
|
|
38255
|
+
graphqlCb.reject(protocolErrorFrom(inner.error));
|
|
38213
38256
|
}
|
|
38214
38257
|
return;
|
|
38215
38258
|
}
|
|
@@ -38402,7 +38445,9 @@ async function runOneLift(opts) {
|
|
|
38402
38445
|
if (opts.declare.cookies.length > 0) {
|
|
38403
38446
|
const joined = await server.readCookies({
|
|
38404
38447
|
keys: opts.declare.cookies,
|
|
38405
|
-
...storageDomainOpts
|
|
38448
|
+
...storageDomainOpts,
|
|
38449
|
+
// Cookie-only: the other buckets are origin-scoped and ignore path.
|
|
38450
|
+
...opts.storagePath !== void 0 ? { path: opts.storagePath } : {}
|
|
38406
38451
|
});
|
|
38407
38452
|
for (const piece of joined.split("; ")) {
|
|
38408
38453
|
if (!piece)
|
|
@@ -38532,7 +38577,7 @@ function loadAccount(env = process.env) {
|
|
|
38532
38577
|
// package.json
|
|
38533
38578
|
var package_default = {
|
|
38534
38579
|
name: "infinitecampus-mcp",
|
|
38535
|
-
version: "2.4.
|
|
38580
|
+
version: "2.4.4",
|
|
38536
38581
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
38537
38582
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
38538
38583
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38575,7 +38620,7 @@ var package_default = {
|
|
|
38575
38620
|
},
|
|
38576
38621
|
dependencies: {
|
|
38577
38622
|
"@chrischall/mcp-utils": "^0.14.0",
|
|
38578
|
-
"@fetchproxy/bootstrap": "^1.
|
|
38623
|
+
"@fetchproxy/bootstrap": "^1.11.0",
|
|
38579
38624
|
"@fetchproxy/server": "^1.7.0",
|
|
38580
38625
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38581
38626
|
dotenv: "^17.4.2",
|
|
@@ -38622,6 +38667,18 @@ function lifterFor(host) {
|
|
|
38622
38667
|
// <district>.infinitecampus.org, etc.). Declare just this host
|
|
38623
38668
|
// — each district is its own root, no wildcard needed.
|
|
38624
38669
|
domains: [host],
|
|
38670
|
+
// IC is a Tomcat app deployed at /campus, and Tomcat scopes JSESSIONID
|
|
38671
|
+
// to the servlet context by default:
|
|
38672
|
+
//
|
|
38673
|
+
// JSESSIONID=...; Path=/campus; Secure; HttpOnly; SameSite=Lax
|
|
38674
|
+
//
|
|
38675
|
+
// chrome.cookies.get matches that Path against the URL it is given, so a
|
|
38676
|
+
// read aimed at the origin root silently returns nothing — which is
|
|
38677
|
+
// indistinguishable from "the user is signed out", and is why this path
|
|
38678
|
+
// never worked on any tenant. Verified live: without this the lift found
|
|
38679
|
+
// no cookies while a fetch through the same tab returned authenticated
|
|
38680
|
+
// data; with it, JSESSIONID + XSRF-TOKEN both come back.
|
|
38681
|
+
storagePath: "/campus",
|
|
38625
38682
|
declare: {
|
|
38626
38683
|
// JSESSIONID is HttpOnly (chrome.cookies.get sees it; the
|
|
38627
38684
|
// security gate is the declared key list). XSRF-TOKEN is
|
|
@@ -40158,7 +40215,7 @@ try {
|
|
|
40158
40215
|
}
|
|
40159
40216
|
var COMMON = {
|
|
40160
40217
|
name: "infinitecampus",
|
|
40161
|
-
version: "2.4.
|
|
40218
|
+
version: "2.4.4"
|
|
40162
40219
|
// x-release-please-version
|
|
40163
40220
|
};
|
|
40164
40221
|
if (account) {
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ catch (e) {
|
|
|
57
57
|
// actionable stderr message (banner) instead of a crash loop.
|
|
58
58
|
const COMMON = {
|
|
59
59
|
name: 'infinitecampus',
|
|
60
|
-
version: '2.4.
|
|
60
|
+
version: '2.4.4', // x-release-please-version
|
|
61
61
|
};
|
|
62
62
|
if (account) {
|
|
63
63
|
const client = new ICClient(account, { refreshSession });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinitecampus-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
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>",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@chrischall/mcp-utils": "^0.14.0",
|
|
46
|
-
"@fetchproxy/bootstrap": "^1.
|
|
46
|
+
"@fetchproxy/bootstrap": "^1.11.0",
|
|
47
47
|
"@fetchproxy/server": "^1.7.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
49
|
"dotenv": "^17.4.2",
|
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.4.
|
|
9
|
+
"version": "2.4.4",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "infinitecampus-mcp",
|
|
14
|
-
"version": "2.4.
|
|
14
|
+
"version": "2.4.4",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|