brightspace-mcp-server 5.2.0 → 5.2.2
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 +23 -8
- package/build/index.js +1 -1
- package/build/server.js +1 -1
- package/build/tools/get-course-content.js +22 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,12 +35,11 @@ Works with Claude Desktop, Claude Code, Cursor, ChatGPT Desktop, Windsurf, and a
|
|
|
35
35
|
**Rather have the AI install it?** Paste this into Claude Code, Cursor, Windsurf, Copilot, or Codex:
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
Install
|
|
39
|
-
https://
|
|
40
|
-
(use --purdue at Purdue, --suny at SUNY, --tudelft at TU Delft, --cuny at CUNY, --leiden at Leiden, --mcgill at McGill, --mun at Memorial University, --ngeeann at Ngee Ann Polytechnic, or --javeriana at Javeriana Cali).
|
|
38
|
+
Install the Brightspace MCP server for me. Follow the steps at
|
|
39
|
+
https://raw.githubusercontent.com/RohanMuppa/brightspace-mcp-server/main/LLMs.md
|
|
41
40
|
```
|
|
42
41
|
|
|
43
|
-
The AI does everything it can by itself, then gives you one terminal command. That command asks for your password and waits for you to approve the sign-in on your phone. That's the only part that needs you. Afterwards the AI checks that it can see your courses.
|
|
42
|
+
The AI asks which school you attend and your username, does everything it can by itself, then gives you one terminal command. That command asks for your password and waits for you to approve the sign-in on your phone. That's the only part that needs you. Afterwards the AI checks that it can see your courses.
|
|
44
43
|
|
|
45
44
|
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`). Codex and Claude Code commands, and general steps, are in [docs/troubleshooting.md](docs/troubleshooting.md).
|
|
46
45
|
|
|
@@ -81,7 +80,7 @@ School-specific quirks are in [docs/sign-in.md](docs/sign-in.md#per-school-notes
|
|
|
81
80
|
| Calendar | "When is my midterm?" · "Is lab cancelled on Thursday?" |
|
|
82
81
|
| Instructors and TAs | "Which students haven't submitted Lab 4 yet?" (students see "Instructor or TA access required" for these) |
|
|
83
82
|
|
|
84
|
-
If Brightspace hides part of the data, such as a submission, quiz attempts, or part of the quiz list, the answer says "unknown" or "incomplete" rather than "missing" or "complete". Full tool reference: [
|
|
83
|
+
If Brightspace hides part of the data, such as a submission, quiz attempts, or part of the quiz list, the answer says "unknown" or "incomplete" rather than "missing" or "complete". Full tool reference: [docs/reference/tools.md](docs/reference/tools.md).
|
|
85
84
|
|
|
86
85
|
## Download a whole course
|
|
87
86
|
|
|
@@ -106,7 +105,19 @@ Every file in the course's Content area is saved into folders named after its mo
|
|
|
106
105
|
- Each run is capped at 300 files and 5 GB. The assistant's version also stops cleanly after 8 minutes. Ask it again to continue. Ctrl-C stops the CLI cleanly (exit 130) with no partial file left behind.
|
|
107
106
|
- Exit codes: 0 when everything was done, 1 if a file failed or wasn't attempted (including a sign-in that expired partway; saved files are kept), 2 for a usage error. Links and embedded videos aren't files, so the result counts them separately rather than as missing downloads.
|
|
108
107
|
|
|
109
|
-
**Some folders are refused.** Downloads can't go to your home folder itself, a drive root, a hidden (dot) folder such as `~/.ssh`, `~/Library` (except cloud-drive and iCloud folders), system folders such as `/etc` or `/System`, or on Windows `Windows`, `Program Files`, `ProgramData` and `AppData`. Anything else is fine, including `/tmp`, external drives and `~/Documents`. Choose a folder like `~/Documents/Brightspace`; the refusal names the rule. The same rule applies to `download_file`, `get_syllabus` and every other tool that saves to disk. The person running the server can allow a folder anyway with `D2L_DOWNLOAD_ROOTS` (see [Settings](#settings-you-might-want)). Full rules: [
|
|
108
|
+
**Some folders are refused.** Downloads can't go to your home folder itself, a drive root, a hidden (dot) folder such as `~/.ssh`, `~/Library` (except cloud-drive and iCloud folders), system folders such as `/etc` or `/System`, or on Windows `Windows`, `Program Files`, `ProgramData` and `AppData`. Anything else is fine, including `/tmp`, external drives and `~/Documents`. Choose a folder like `~/Documents/Brightspace`; the refusal names the rule. The same rule applies to `download_file`, `get_syllabus` and every other tool that saves to disk. The person running the server can allow a folder anyway with `D2L_DOWNLOAD_ROOTS` (see [Settings](#settings-you-might-want)). Full rules: [docs/internals/downloads.md](docs/internals/downloads.md).
|
|
109
|
+
|
|
110
|
+
## Tools
|
|
111
|
+
|
|
112
|
+
Every tool the server registers. Each tool's arguments and response fields are in [docs/reference/tools.md](docs/reference/tools.md).
|
|
113
|
+
|
|
114
|
+
- **Courses and grades:** `get_my_courses`, `get_my_grades`, `get_upcoming_due_dates`, `get_calendar_events`
|
|
115
|
+
- **Assignments and rubrics:** `get_assignments`, `get_assignment_rubric`, `get_assignment_files`, `get_rubrics_for_object`
|
|
116
|
+
- **Announcements and discussions:** `get_announcements`, `get_announcement_files`, `get_discussions`
|
|
117
|
+
- **Course content:** `get_course_content`, `search_course`, `get_syllabus`, `get_video_transcript`, `download_file`, `download_course_files`
|
|
118
|
+
- **People and groups:** `get_roster`, `get_classlist_emails`, `get_my_groups`, `get_available_groups`
|
|
119
|
+
- **Dropbox (instructors and TAs):** `get_dropbox_folders`, `get_dropbox_submissions`, `get_dropbox_user_submissions`, `get_dropbox_feedback`, `download_dropbox_submission_file`
|
|
120
|
+
- **Account and sign-in:** `sign_in`, `sign_out`, `get_server_info`
|
|
110
121
|
|
|
111
122
|
## Prompts
|
|
112
123
|
|
|
@@ -137,9 +148,11 @@ How often your school asks for two-factor is up to the school, not this tool.
|
|
|
137
148
|
|
|
138
149
|
Duo, authenticator codes, visible-browser mode, and running without a browser at all (Docker, WSL, hardware keys) are covered in [docs/sign-in.md](docs/sign-in.md).
|
|
139
150
|
|
|
151
|
+
**What a sign-in message means,** including Microsoft's "having trouble verifying your account" hold and what to do about each one, is in [docs/troubleshooting.md](docs/troubleshooting.md#what-each-sign-in-message-means). Numbers that expire, and signing in on more than one computer, are in [docs/sign-in.md](docs/sign-in.md#when-a-number-expires).
|
|
152
|
+
|
|
140
153
|
## Settings you might want
|
|
141
154
|
|
|
142
|
-
All optional. Set them in your AI app's MCP `env` config or in your shell. The full list is in [
|
|
155
|
+
All optional. Set them in your AI app's MCP `env` config or in your shell. The full list is in [docs/reference/settings.md](docs/reference/settings.md).
|
|
143
156
|
|
|
144
157
|
| Setting | What it does |
|
|
145
158
|
|---------|--------------|
|
|
@@ -177,7 +190,9 @@ Your password is stored in your computer's password store, never in a plaintext
|
|
|
177
190
|
|
|
178
191
|
## For developers
|
|
179
192
|
|
|
180
|
-
|
|
193
|
+
- **Changing the code:** start with [AGENTS.md](AGENTS.md). It says which doc to read before touching each area.
|
|
194
|
+
- **Exact behaviour:** [tools](docs/reference/tools.md), [commands](docs/reference/cli.md) and [settings](docs/reference/settings.md).
|
|
195
|
+
- **What's safe to build against:** [STABILITY.md](STABILITY.md). **What changed:** [CHANGELOG.md](CHANGELOG.md).
|
|
181
196
|
|
|
182
197
|
## License
|
|
183
198
|
|
package/build/index.js
CHANGED
|
@@ -142,7 +142,7 @@ else {
|
|
|
142
142
|
const { resolveHttpPort, resolveHttpToken, startHttpServer } = await import("./http-server.js");
|
|
143
143
|
const running = await startHttpServer({ runtime, token: resolveHttpToken(), port: resolveHttpPort() });
|
|
144
144
|
stopServing = running.close;
|
|
145
|
-
log("INFO", `Brightspace MCP Server by Rohan Muppa — shared HTTP server listening on ${running.url} (
|
|
145
|
+
log("INFO", `Brightspace MCP Server by Rohan Muppa — shared HTTP server listening on ${running.url} (tools and prompts registered)`);
|
|
146
146
|
log("INFO", "Leave this running; every MCP client on this computer connects to that URL. Press Ctrl+C to stop.");
|
|
147
147
|
}
|
|
148
148
|
catch (error) {
|
package/build/server.js
CHANGED
|
@@ -104,7 +104,7 @@ export function createMcpServer(runtime) {
|
|
|
104
104
|
registerGetDropboxFeedback(server, apiClient);
|
|
105
105
|
registerGetRubricsForObject(server, apiClient);
|
|
106
106
|
registerDownloadDropboxSubmissionFile(server, apiClient);
|
|
107
|
-
log("DEBUG", "MCP tools registered
|
|
107
|
+
log("DEBUG", "MCP tools registered");
|
|
108
108
|
// Register MCP prompts — surfaced in clients (e.g. Claude Desktop) as a
|
|
109
109
|
// picker, distinct from tools. Each one is a canned user message that
|
|
110
110
|
// names our own tools by name, so a client with no idea what to ask
|
|
@@ -170,7 +170,9 @@ export async function buildContentTree(apiClient, courseId, modules, progressMap
|
|
|
170
170
|
if (typeFilter !== 'all' && !matchesTypeFilter(item, typeFilter)) {
|
|
171
171
|
continue;
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
// A null map means progress couldn't be read: completion is unknown,
|
|
174
|
+
// not false, so a failed read can't pass for "nothing completed".
|
|
175
|
+
const topicProgress = progressMap?.get(item.Id);
|
|
174
176
|
const topic = {
|
|
175
177
|
type: 'topic',
|
|
176
178
|
topicType,
|
|
@@ -181,7 +183,7 @@ export async function buildContentTree(apiClient, courseId, modules, progressMap
|
|
|
181
183
|
...availabilityFields(availability),
|
|
182
184
|
...(item.DueDate ? { dueDate: item.DueDate } : {}),
|
|
183
185
|
lastModified: item.LastModifiedDate ?? null,
|
|
184
|
-
isCompleted: topicProgress?.IsRead ?? false,
|
|
186
|
+
isCompleted: progressMap === null ? null : topicProgress?.IsRead ?? false,
|
|
185
187
|
...(topicProgress?.DateCompleted ? { completedDate: topicProgress.DateCompleted } : {}),
|
|
186
188
|
};
|
|
187
189
|
// Add type-specific content
|
|
@@ -307,22 +309,25 @@ export function registerGetCourseContent(server, apiClient) {
|
|
|
307
309
|
const searchTerm = moduleTitle.toLowerCase();
|
|
308
310
|
rootModules = rootModules.filter(m => m.Title.toLowerCase().includes(searchTerm));
|
|
309
311
|
}
|
|
310
|
-
// Fetch user progress for the course (graceful degradation)
|
|
311
|
-
|
|
312
|
+
// Fetch user progress for the course (graceful degradation). 403/404
|
|
313
|
+
// means this course has no progress data for the user. Any other
|
|
314
|
+
// failure leaves completion unknown, so the map stays null and every
|
|
315
|
+
// topic reports isCompleted: null rather than false.
|
|
316
|
+
let progressMap = new Map();
|
|
312
317
|
try {
|
|
313
|
-
progressArray = await apiClient.get(apiClient.le(courseId, '/content/userprogress/'), { ttl: DEFAULT_CACHE_TTLS.courseContent });
|
|
318
|
+
const progressArray = await apiClient.get(apiClient.le(courseId, '/content/userprogress/'), { ttl: DEFAULT_CACHE_TTLS.courseContent });
|
|
319
|
+
for (const p of Array.isArray(progressArray) ? progressArray : []) {
|
|
320
|
+
progressMap.set(p.ContentObjectId, p);
|
|
321
|
+
}
|
|
314
322
|
}
|
|
315
323
|
catch (error) {
|
|
316
|
-
|
|
324
|
+
if (isAuthUnavailable(error))
|
|
325
|
+
throw error;
|
|
317
326
|
if (error?.status !== 404 && error?.status !== 403) {
|
|
318
327
|
log('DEBUG', `Failed to fetch progress for course ${courseId}`, error);
|
|
328
|
+
progressMap = null;
|
|
319
329
|
}
|
|
320
330
|
}
|
|
321
|
-
// Build progress map for O(1) lookups
|
|
322
|
-
const progressMap = new Map();
|
|
323
|
-
for (const p of progressArray) {
|
|
324
|
-
progressMap.set(p.ContentObjectId, p);
|
|
325
|
-
}
|
|
326
331
|
// Recursively build content tree
|
|
327
332
|
let contentTree = await buildContentTree(apiClient, courseId, rootModules, progressMap, typeFilter, maxDepth);
|
|
328
333
|
// Apply modifiedSince filter, if requested, after the tree is built:
|
|
@@ -346,6 +351,12 @@ export function registerGetCourseContent(server, apiClient) {
|
|
|
346
351
|
topicCount,
|
|
347
352
|
moduleCount,
|
|
348
353
|
...(modifiedSince ? { modifiedSince, returned: topicCount, filteredOut } : {}),
|
|
354
|
+
...(progressMap === null
|
|
355
|
+
? {
|
|
356
|
+
progressUnavailable: "Couldn't read this user's completion progress, so every topic has isCompleted null. " +
|
|
357
|
+
"Do not report topics as not completed; try again later.",
|
|
358
|
+
}
|
|
359
|
+
: {}),
|
|
349
360
|
...(unreadable > 0
|
|
350
361
|
? {
|
|
351
362
|
notice: `The contents of ${unreadable} module(s) couldn't be read; they have children null and a ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2",
|
|
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",
|