brightspace-mcp-server 3.9.21 → 3.9.23
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/README.md +4 -2
- package/build/auth/index.js +1 -0
- package/build/auth/mcgill-sso.js +82 -0
- package/build/auth/sso-flow.js +4 -0
- package/build/setup.js +15 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,14 +28,14 @@ Works with Claude Desktop, Claude Code, Cursor, ChatGPT Desktop, Windsurf, and a
|
|
|
28
28
|
It asks for your school's Brightspace address, your username and password, and how you normally do two-factor sign-in (phone approval, authenticator code, or a browser window). Your password goes into your computer's own password store (Keychain on Mac, Credential Manager on Windows), never into a file. At the end it connects itself to Claude Desktop, Cursor, Codex, or Claude Code if you have them.
|
|
29
29
|
3. **Restart your AI app and ask it something** — the first question signs you in. If your phone asks you to approve a sign-in, approve it and ask again.
|
|
30
30
|
|
|
31
|
-
At one of these schools, add the flag and skip typing the address: `--purdue`, `--suny`, `--western`, `--tudelft`, `--cuny`, `--leiden`, `--javeriana`.
|
|
31
|
+
At one of these schools, add the flag and skip typing the address: `--purdue`, `--suny`, `--western`, `--tudelft`, `--cuny`, `--leiden`, `--mcgill`, `--ngeeann`, `--javeriana`.
|
|
32
32
|
|
|
33
33
|
**Rather have the AI install it for you?** Paste this into Claude Code, Cursor, Windsurf, Copilot, or Codex:
|
|
34
34
|
|
|
35
35
|
```
|
|
36
36
|
Install brightspace-mcp-server for me by following
|
|
37
37
|
https://github.com/RohanMuppa/brightspace-mcp-server/blob/main/LLMs.md
|
|
38
|
-
(use --purdue at Purdue, --suny at SUNY, --tudelft at TU Delft, --cuny at CUNY, --leiden at Leiden, or --javeriana at Javeriana Cali).
|
|
38
|
+
(use --purdue at Purdue, --suny at SUNY, --tudelft at TU Delft, --cuny at CUNY, --leiden at Leiden, --mcgill at McGill, --ngeeann at Ngee Ann Polytechnic, or --javeriana at Javeriana Cali).
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Using a different AI app? Add the command `npx -y brightspace-mcp-server@latest` to its MCP settings (on Windows: `cmd /c npx -y brightspace-mcp-server@latest`). Exact steps for each app: [docs/troubleshooting.md](docs/troubleshooting.md).
|
|
@@ -52,7 +52,9 @@ If your school uses D2L Brightspace, yes. These have automatic sign-in built in:
|
|
|
52
52
|
| TU Delft | NetID (no two-factor) | `--tudelft` |
|
|
53
53
|
| CUNY | CUNY Login, authenticator code each time | `--cuny` |
|
|
54
54
|
| Leiden University | SURFconext → Microsoft | `--leiden` |
|
|
55
|
+
| McGill University | Microsoft Entra | `--mcgill` |
|
|
55
56
|
| Javeriana Cali | OneGate (password + authenticator code) | `--javeriana` |
|
|
57
|
+
| Ngee Ann Polytechnic | Microsoft Entra | `--ngeeann` |
|
|
56
58
|
| Any other D2L school | Paste your Brightspace address; if the login page isn't recognized, a browser window opens so you can sign in by hand | none |
|
|
57
59
|
|
|
58
60
|
School-specific quirks: [docs/sign-in.md](docs/sign-in.md#per-school-notes).
|
package/build/auth/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { SunySSOFlow, isSunyBrightspace } from "./suny-sso.js";
|
|
|
9
9
|
export { TUDelftSSOFlow, isTUDelftBrightspace } from "./tudelft-sso.js";
|
|
10
10
|
export { CunySSOFlow, isCunyBrightspace } from "./cuny-sso.js";
|
|
11
11
|
export { LeidenSSOFlow, isLeidenBrightspace } from "./leiden-sso.js";
|
|
12
|
+
export { McgillSSOFlow, isMcgillBrightspace } from "./mcgill-sso.js";
|
|
12
13
|
export { JaverianaSSOFlow, isJaverianaBrightspace } from "./javeriana-sso.js";
|
|
13
14
|
export { createSSOFlow } from "./sso-flow.js";
|
|
14
15
|
export { TokenManager } from "./token-manager.js";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McGill University login entry point. Licensed under MIT; see LICENSE.
|
|
3
|
+
*
|
|
4
|
+
* McGill flow ported from chrldb's mcgill-sso branch (MIT).
|
|
5
|
+
*/
|
|
6
|
+
import { PurdueSSOFlow } from "./purdue-sso.js";
|
|
7
|
+
import { UnsupportedAuthenticationError } from "./sso-flow.js";
|
|
8
|
+
const MCGILL_HOST = "mycourses2.mcgill.ca";
|
|
9
|
+
export function isMcgillBrightspace(baseUrl) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(baseUrl).hostname.toLowerCase() === MCGILL_HOST;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* McGill's myCourses login page offers a "McGill" control that is really a
|
|
19
|
+
* plain anchor to `/d2l/lp/auth/saml/login`, alongside separate guest/external
|
|
20
|
+
* options. The tenant sits behind exactly one identity provider, so going
|
|
21
|
+
* straight to that SAML endpoint is simpler than depending on the control's
|
|
22
|
+
* markup, and skips it entirely. From there this reuses the shared credential
|
|
23
|
+
* and MFA flow; only McGill's first click differs.
|
|
24
|
+
*/
|
|
25
|
+
export class McgillSSOFlow {
|
|
26
|
+
common;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.common = new PurdueSSOFlow(config);
|
|
29
|
+
}
|
|
30
|
+
hasCredentials() {
|
|
31
|
+
return this.common.hasCredentials();
|
|
32
|
+
}
|
|
33
|
+
rememberMfaResult() {
|
|
34
|
+
return this.common.rememberMfaResult();
|
|
35
|
+
}
|
|
36
|
+
async prepareLogin(page) {
|
|
37
|
+
await this.startSamlLogin(page);
|
|
38
|
+
}
|
|
39
|
+
async identifyAccount(page) {
|
|
40
|
+
return this.common.identifyAccount(page);
|
|
41
|
+
}
|
|
42
|
+
async login(page) {
|
|
43
|
+
await this.startSamlLogin(page);
|
|
44
|
+
return this.common.login(page);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Skip McGill's login-option page entirely by going straight to the SAML
|
|
48
|
+
* endpoint its "McGill" control would have reached. A no-op once already
|
|
49
|
+
* past it (e.g. mid-MFA, or the account picker resumed a saved IdP
|
|
50
|
+
* session), matching the other flows' idempotent prepareLogin/login calls.
|
|
51
|
+
*/
|
|
52
|
+
async startSamlLogin(page) {
|
|
53
|
+
let current;
|
|
54
|
+
try {
|
|
55
|
+
current = new URL(page.url());
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (current.protocol !== "https:" || current.hostname.toLowerCase() !== MCGILL_HOST || !current.pathname.includes("/d2l/login"))
|
|
61
|
+
return;
|
|
62
|
+
await page.goto(`${current.origin}/d2l/lp/auth/saml/login`, {
|
|
63
|
+
waitUntil: "domcontentloaded",
|
|
64
|
+
timeout: 30000,
|
|
65
|
+
});
|
|
66
|
+
// A healthy SAML endpoint redirects off mycourses2.mcgill.ca towards
|
|
67
|
+
// Microsoft Entra. If the host is unchanged, the endpoint 404'd or
|
|
68
|
+
// otherwise failed to redirect — fail fast with a clear message instead
|
|
69
|
+
// of letting the inner Microsoft sign-in flow time out looking for
|
|
70
|
+
// fields that will never appear.
|
|
71
|
+
let afterGoto;
|
|
72
|
+
try {
|
|
73
|
+
afterGoto = new URL(page.url());
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (afterGoto.hostname.toLowerCase() === MCGILL_HOST) {
|
|
79
|
+
throw new UnsupportedAuthenticationError("McGill sign-in could not reach the SAML endpoint (mycourses2.mcgill.ca/d2l/lp/auth/saml/login stayed on the same host) — myCourses may have changed its login flow.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/build/auth/sso-flow.js
CHANGED
|
@@ -9,6 +9,7 @@ import { WesternSSOFlow, isWesternBrightspace } from "./western-sso.js";
|
|
|
9
9
|
import { TUDelftSSOFlow, isTUDelftBrightspace } from "./tudelft-sso.js";
|
|
10
10
|
import { CunySSOFlow, isCunyBrightspace } from "./cuny-sso.js";
|
|
11
11
|
import { LeidenSSOFlow, isLeidenBrightspace } from "./leiden-sso.js";
|
|
12
|
+
import { McgillSSOFlow, isMcgillBrightspace } from "./mcgill-sso.js";
|
|
12
13
|
import { JaverianaSSOFlow, isJaverianaBrightspace } from "./javeriana-sso.js";
|
|
13
14
|
import { BrowserAuthError } from "../utils/errors.js";
|
|
14
15
|
import { AUTH_COMMAND } from "../utils/commands.js";
|
|
@@ -64,6 +65,9 @@ export function createSSOFlow(config, requestMfaCode, onMfaChallenge) {
|
|
|
64
65
|
if (isCunyBrightspace(config.baseUrl)) {
|
|
65
66
|
return new CunySSOFlow(credentials);
|
|
66
67
|
}
|
|
68
|
+
if (isMcgillBrightspace(config.baseUrl)) {
|
|
69
|
+
return new McgillSSOFlow(credentials);
|
|
70
|
+
}
|
|
67
71
|
if (isJaverianaBrightspace(config.baseUrl)) {
|
|
68
72
|
return new JaverianaSSOFlow(credentials);
|
|
69
73
|
}
|
package/build/setup.js
CHANGED
|
@@ -66,6 +66,20 @@ export const SCHOOL_PRESETS = {
|
|
|
66
66
|
mfaNote: "Type the 6-digit code from your authenticator app when prompted.",
|
|
67
67
|
usernameHint: "Use your full CUNY Login address, e.g. firstname.lastname01@login.cuny.edu",
|
|
68
68
|
},
|
|
69
|
+
ngeeann: {
|
|
70
|
+
name: "Ngee Ann Polytechnic",
|
|
71
|
+
baseUrl: "https://nplms.polite.edu.sg",
|
|
72
|
+
usernameLabel: "Ngee Ann Polytechnic account username or full email",
|
|
73
|
+
mfaNote: "Approve the sign-in request from your MFA app.",
|
|
74
|
+
usernameHint: "Use your full sign-in address if your Ngee Ann Polytechnic account requires it.",
|
|
75
|
+
},
|
|
76
|
+
mcgill: {
|
|
77
|
+
name: "McGill University",
|
|
78
|
+
baseUrl: "https://mycourses2.mcgill.ca",
|
|
79
|
+
usernameLabel: "McGill username or full email",
|
|
80
|
+
mfaNote: "Approve the sign-in request from your MFA app.",
|
|
81
|
+
usernameHint: "Use your full sign-in address if your McGill account requires it.",
|
|
82
|
+
},
|
|
69
83
|
javeriana: {
|
|
70
84
|
name: "Pontificia Universidad Javeriana Cali",
|
|
71
85
|
baseUrl: "https://auladigital.javerianacali.edu.co",
|
|
@@ -74,7 +88,7 @@ export const SCHOOL_PRESETS = {
|
|
|
74
88
|
},
|
|
75
89
|
};
|
|
76
90
|
/**
|
|
77
|
-
* Pick the school preset named by `--purdue`, `--suny`, `--western`, `--cuny`, etc.
|
|
91
|
+
* Pick the school preset named by `--purdue`, `--suny`, `--western`, `--cuny`, `--mcgill`, `--ngeeann`, `--javeriana`, etc.
|
|
78
92
|
*
|
|
79
93
|
* Own properties only: a bare index would make `--constructor` or
|
|
80
94
|
* `--__proto__` resolve to something off `Object.prototype` and hand the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.23",
|
|
4
4
|
"mcpName": "io.github.rohanmuppa/brightspace",
|
|
5
5
|
"description": "MCP server for Brightspace (D2L). Check grades, due dates, assignments, announcements, syllabus, rosters and more via Claude, ChatGPT, Cursor, Windsurf, or any MCP client.",
|
|
6
6
|
"type": "module",
|