infinitecampus-mcp 2.1.2 → 2.1.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 +13 -9
- package/dist/auth.js +134 -0
- package/dist/bundle.js +11670 -5024
- package/dist/client.js +55 -1
- package/dist/config.js +27 -7
- package/dist/index.js +46 -22
- package/package.json +4 -3
- 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.1.
|
|
10
|
+
"version": "2.1.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.
|
|
18
|
+
"version": "2.1.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.
|
|
4
|
+
"version": "2.1.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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
`infinitecampus-mcp` tries two auth paths in priority order; whichever succeeds first is used. Existing setups keep working unchanged.
|
|
32
|
+
|
|
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
|
|
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
|
+
}
|