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 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 brightspace-mcp-server for me by following
39
- https://github.com/RohanMuppa/brightspace-mcp-server/blob/main/LLMs.md
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: [LLMs.md](LLMs.md).
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: [LLMs.md](LLMs.md).
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 [LLMs.md](LLMs.md#environment-variables).
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
- Tool reference, response fields, environment variables and the codebase map: [LLMs.md](LLMs.md). What's safe to build against: [STABILITY.md](STABILITY.md).
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} (29 tools, 4 prompts registered)`);
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 (29 tools)");
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
- const topicProgress = progressMap.get(item.Id);
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
- let progressArray = [];
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
- // 404/403 means no progress data available - not an error
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.0",
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",