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.
@@ -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.1.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.1.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.1.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
- Set a single set of env vars for your primary Infinite Campus account:
31
+ `infinitecampus-mcp` tries two auth paths in priority order; whichever succeeds first is used. Existing setups keep working unchanged.
32
32
 
33
- ```
34
- IC_BASE_URL=https://campus.springfield.k12.example.us
35
- IC_DISTRICT=springfield
36
- IC_USERNAME=parent@example.com
37
- IC_PASSWORD=...
38
- IC_NAME=Springfield # optional, defaults to IC_DISTRICT
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
+ }