infinitecampus-mcp 2.1.2 → 2.2.3
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/README.md +34 -8
- package/dist/auth.js +134 -0
- package/dist/bundle.js +11845 -4909
- package/dist/client.js +55 -1
- package/dist/config.js +27 -7
- package/dist/index.js +46 -22
- package/package.json +9 -8
- package/server.json +12 -6
|
@@ -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.
|
|
10
|
+
"version": "2.2.3"
|
|
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.
|
|
18
|
+
"version": "2.2.3",
|
|
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
|
+
"version": "2.2.3",
|
|
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/README.md
CHANGED
|
@@ -28,15 +28,19 @@ Tools that the harness will gate as write/IO operations: `ic_download_document`.
|
|
|
28
28
|
|
|
29
29
|
## Configuration
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
`infinitecampus-mcp` tries two auth paths in priority order; whichever succeeds first is used. Existing setups keep working unchanged.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
1. **Env-var credentials (legacy).** Set all four:
|
|
34
|
+
```
|
|
35
|
+
IC_BASE_URL=https://campus.springfield.k12.example.us
|
|
36
|
+
IC_DISTRICT=springfield
|
|
37
|
+
IC_USERNAME=parent@example.com
|
|
38
|
+
IC_PASSWORD=...
|
|
39
|
+
IC_NAME=Springfield # optional, defaults to IC_DISTRICT
|
|
40
|
+
```
|
|
41
|
+
2. **fetchproxy fallback (no password needed).** Set only `IC_BASE_URL` + `IC_DISTRICT` (still required so the MCP knows which host to talk to and which district to dispatch on), then install the [fetchproxy 0.3.0 extension](https://github.com/chrischall/fetchproxy) (Chrome Web Store / Safari `.dmg`) and sign into your IC portal once. The MCP reads the `JSESSIONID` (HttpOnly) + `XSRF-TOKEN` cookies on startup and goes direct-to-API from Node thereafter — the extension is **not** in the request hot path.
|
|
42
|
+
|
|
43
|
+
Set `IC_DISABLE_FETCHPROXY=1` to opt out of the fallback (turns missing credentials into a hard error — useful in headless CI).
|
|
40
44
|
|
|
41
45
|
Linked districts (via CUPS SSO) are auto-discovered after primary login — a parent with kids in two districts only configures the primary. No extra config needed. If you have truly separate IC instances with different credentials, run two MCP instances.
|
|
42
46
|
|
|
@@ -45,3 +49,25 @@ See `.env.example`.
|
|
|
45
49
|
## Status
|
|
46
50
|
|
|
47
51
|
Unofficial — not affiliated with Infinite Campus. AI-maintained.
|
|
52
|
+
|
|
53
|
+
## Acknowledgement of Terms
|
|
54
|
+
|
|
55
|
+
By using this MCP server, you acknowledge and agree to the following:
|
|
56
|
+
|
|
57
|
+
**1. This server accesses your own Campus Parent account.** Auth happens via your own credentials, scoped to the student records your school district has authorized you to view. It does not — and cannot — access anyone else's student records.
|
|
58
|
+
|
|
59
|
+
**2. [Infinite Campus's Terms of Use](https://www.infinitecampus.com/terms/terms-of-use) govern your use of this server**, just as they govern your direct use of Campus Parent. The clauses most relevant here:
|
|
60
|
+
|
|
61
|
+
> Users may not access, use, or search the Services by any means other than our publicly supported interfaces (for example, scraping or using the content to train artificial intelligence software).
|
|
62
|
+
|
|
63
|
+
And: *"You are responsible for safeguarding the password that you use to access the Service and you agree not to disclose your password to any third party."*
|
|
64
|
+
|
|
65
|
+
You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server. This server uses Infinite Campus's mobile-app JSON endpoints (`/campus/api/oneRosterCampus`, `/portal/api/...`) which are not "publicly supported interfaces" — IC may treat this as a ToS violation.
|
|
66
|
+
|
|
67
|
+
**3. Personal, parent/student use only.** This project is not affiliated with, endorsed by, sponsored by, or in partnership with Infinite Campus, Inc. or any school district. It is a personal automation tool for an authorized Campus Parent / Campus Student user to read their own (or their student's) records. **Do not** use it to bulk-extract student data, share another family's grades, or train AI models on student records.
|
|
68
|
+
|
|
69
|
+
**4. FERPA + COPPA apply.** Student educational records are protected under the federal Family Educational Rights and Privacy Act (FERPA), and student data for under-13 users is additionally covered by COPPA. Even though *your* parent-portal access is lawful, **how you store, redistribute, or feed that data into LLMs is regulated**. Treat any output from this server (grades, attendance, behavior records, schedules) as confidential student data. Do not put it in shared LLM contexts, do not paste it into screenshots, and do not train models on it.
|
|
70
|
+
|
|
71
|
+
**5. You accept full responsibility** for any consequences of using this server in connection with your Campus Parent account — rate limiting, account warnings, district IT investigations, or any enforcement action your district or Infinite Campus takes. Your district may have additional acceptable-use policies (acceptable-use agreements, parent handbooks) that further restrict automation. If your district or Infinite Campus objects to your use, stop using this server.
|
|
72
|
+
|
|
73
|
+
This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede Infinite Campus's actual ToU or any school district's policies.
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Auth resolution — Pattern A template
|
|
3
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Infinite Campus supports two auth paths. This file picks one, in priority
|
|
6
|
+
// order, and hands the chosen path to `ICClient`. It mirrors the Pattern A
|
|
7
|
+
// shape used by ofw-mcp/src/auth.ts (and signupgenius-mcp, zola-mcp, …) so
|
|
8
|
+
// all sibling MCPs in this family stay structurally aligned.
|
|
9
|
+
//
|
|
10
|
+
// THE PATHS, in priority order:
|
|
11
|
+
//
|
|
12
|
+
// 1. Password login (existing)
|
|
13
|
+
// IC_BASE_URL + IC_DISTRICT + IC_USERNAME + IC_PASSWORD set →
|
|
14
|
+
// `loadAccount()` returns an Account with credentials; `ICClient.login()`
|
|
15
|
+
// POSTs to `verify.jsp` exactly as before. Unchanged from pre-fetchproxy
|
|
16
|
+
// behavior. The 401 retry + linked-district CUPS SSO discovery in
|
|
17
|
+
// `client.ts` both keep working.
|
|
18
|
+
//
|
|
19
|
+
// 2. fetchproxy fallback (new)
|
|
20
|
+
// IC_BASE_URL + IC_DISTRICT set but no IC_USERNAME/IC_PASSWORD →
|
|
21
|
+
// `@fetchproxy/bootstrap` opens a one-shot WebSocket bridge to the
|
|
22
|
+
// 0.3.0 extension, reads `JSESSIONID` (HttpOnly) and `XSRF-TOKEN`
|
|
23
|
+
// (non-HttpOnly) cookies from the user's signed-in IC portal tab via
|
|
24
|
+
// `chrome.cookies.get`, then closes the bridge. The client receives
|
|
25
|
+
// pre-loaded session cookies and skips the login POST. All subsequent
|
|
26
|
+
// IC calls go out via plain Node `fetch()` — fetchproxy is NOT in the
|
|
27
|
+
// request hot path.
|
|
28
|
+
//
|
|
29
|
+
// The CUPS linked-district token-minting flow in `client.ts` continues
|
|
30
|
+
// to work unchanged because it only needs the primary district's
|
|
31
|
+
// cookies, which fetchproxy provides on initial bootstrap.
|
|
32
|
+
//
|
|
33
|
+
// IC_BASE_URL + IC_DISTRICT remain REQUIRED in this mode — the MCP
|
|
34
|
+
// needs to know which host to declare (the extension whitelists by
|
|
35
|
+
// host suffix) and which district appName to dispatch CUPS calls on.
|
|
36
|
+
//
|
|
37
|
+
// Users opt out with IC_DISABLE_FETCHPROXY=1 (e.g. headless CI where
|
|
38
|
+
// no extension is available — turns missing creds into a hard error).
|
|
39
|
+
//
|
|
40
|
+
// 3. Error
|
|
41
|
+
// Nothing to authenticate with. We throw a message that names both
|
|
42
|
+
// escape hatches: set IC_USERNAME/IC_PASSWORD, OR install the extension
|
|
43
|
+
// and sign in.
|
|
44
|
+
//
|
|
45
|
+
// Testability:
|
|
46
|
+
// - `@fetchproxy/bootstrap` is mocked at the module boundary in tests.
|
|
47
|
+
// - `loadAccount()` (the existing env-var resolver) is reused as-is so the
|
|
48
|
+
// legacy paths keep working unchanged.
|
|
49
|
+
import { bootstrap } from '@fetchproxy/bootstrap';
|
|
50
|
+
import { loadAccount } from './config.js';
|
|
51
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
52
|
+
function readEnv(key) {
|
|
53
|
+
const raw = process.env[key];
|
|
54
|
+
if (typeof raw !== 'string')
|
|
55
|
+
return undefined;
|
|
56
|
+
const trimmed = raw.trim();
|
|
57
|
+
if (trimmed.length === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (trimmed === 'undefined' || trimmed === 'null')
|
|
60
|
+
return undefined;
|
|
61
|
+
if (/^\$\{[^}]*\}$/.test(trimmed))
|
|
62
|
+
return undefined;
|
|
63
|
+
return trimmed;
|
|
64
|
+
}
|
|
65
|
+
function fetchproxyDisabled() {
|
|
66
|
+
const raw = readEnv('IC_DISABLE_FETCHPROXY');
|
|
67
|
+
if (raw === undefined)
|
|
68
|
+
return false;
|
|
69
|
+
return ['1', 'true', 'yes', 'on'].includes(raw.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve IC auth using the path priority described at the top of this file.
|
|
73
|
+
* Throws with an actionable message when no path succeeds.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveAuth() {
|
|
76
|
+
// `loadAccount()` enforces IC_BASE_URL + IC_DISTRICT + https validation
|
|
77
|
+
// and the "both username/password or neither" rule. Any of those failing
|
|
78
|
+
// is a user mistake; we let them propagate so the user can fix the typo.
|
|
79
|
+
const account = loadAccount();
|
|
80
|
+
// ── Path 1: full env-var credentials.
|
|
81
|
+
if (account.username && account.password) {
|
|
82
|
+
return { account, source: 'env' };
|
|
83
|
+
}
|
|
84
|
+
// ── Path 2: fetchproxy fallback.
|
|
85
|
+
if (!fetchproxyDisabled()) {
|
|
86
|
+
try {
|
|
87
|
+
const host = new URL(account.baseUrl).hostname;
|
|
88
|
+
const session = await bootstrap({
|
|
89
|
+
serverName: pkg.name,
|
|
90
|
+
version: pkg.version,
|
|
91
|
+
// IC tenants live on per-district hosts (campus.<district>.org,
|
|
92
|
+
// <district>.infinitecampus.org, etc.). Declare just this host
|
|
93
|
+
// — each district is its own root, no wildcard needed.
|
|
94
|
+
domains: [host],
|
|
95
|
+
declare: {
|
|
96
|
+
// JSESSIONID is HttpOnly (chrome.cookies.get sees it; the
|
|
97
|
+
// security gate is the declared key list). XSRF-TOKEN is
|
|
98
|
+
// non-HttpOnly because the page JS reads it back and echoes
|
|
99
|
+
// it on every state-changing call as the X-XSRF-TOKEN header.
|
|
100
|
+
cookies: ['JSESSIONID', 'XSRF-TOKEN'],
|
|
101
|
+
localStorage: [],
|
|
102
|
+
sessionStorage: [],
|
|
103
|
+
captureHeaders: [],
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const jsessionid = session.cookies['JSESSIONID'];
|
|
107
|
+
const xsrf = session.cookies['XSRF-TOKEN'];
|
|
108
|
+
if (!jsessionid) {
|
|
109
|
+
throw new Error(`JSESSIONID cookie not found on ${host}. ` +
|
|
110
|
+
'Sign into your IC portal in your browser (with the fetchproxy extension installed) and retry.');
|
|
111
|
+
}
|
|
112
|
+
if (!xsrf) {
|
|
113
|
+
throw new Error(`XSRF-TOKEN cookie not found on ${host}. ` +
|
|
114
|
+
'Sign into your IC portal in your browser (with the fetchproxy extension installed) and retry.');
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
account,
|
|
118
|
+
preloaded: {
|
|
119
|
+
cookieHeader: `JSESSIONID=${jsessionid}; XSRF-TOKEN=${xsrf}`,
|
|
120
|
+
xsrfToken: xsrf,
|
|
121
|
+
},
|
|
122
|
+
source: 'fetchproxy',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
127
|
+
throw new Error('IC auth: no IC_USERNAME/IC_PASSWORD set, and fetchproxy fallback failed: ' + msg);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ── Path 3: nothing configured and fetchproxy explicitly disabled.
|
|
131
|
+
throw new Error('IC auth: set IC_USERNAME + IC_PASSWORD, ' +
|
|
132
|
+
'or install the fetchproxy extension and sign into your IC portal ' +
|
|
133
|
+
'(unset IC_DISABLE_FETCHPROXY if it is set).');
|
|
134
|
+
}
|