brightspace-mcp-server 5.2.0 → 5.2.1

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
@@ -37,7 +37,7 @@ Works with Claude Desktop, Claude Code, Cursor, ChatGPT Desktop, Windsurf, and a
37
37
  ```
38
38
  Install brightspace-mcp-server for me by following
39
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).
40
+ (use --purdue at Purdue, --suny at SUNY, --western at Western University, --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).
41
41
  ```
42
42
 
43
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.
@@ -108,6 +108,18 @@ Every file in the course's Content area is saved into folders named after its mo
108
108
 
109
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).
110
110
 
111
+ ## Tools
112
+
113
+ The server registers these 29 tools. Each tool's arguments and response fields are in [LLMs.md](LLMs.md#available-tools).
114
+
115
+ - **Courses and grades:** `get_my_courses`, `get_my_grades`, `get_upcoming_due_dates`, `get_calendar_events`
116
+ - **Assignments and rubrics:** `get_assignments`, `get_assignment_rubric`, `get_assignment_files`, `get_rubrics_for_object`
117
+ - **Announcements and discussions:** `get_announcements`, `get_announcement_files`, `get_discussions`
118
+ - **Course content:** `get_course_content`, `search_course`, `get_syllabus`, `get_video_transcript`, `download_file`, `download_course_files`
119
+ - **People and groups:** `get_roster`, `get_classlist_emails`, `get_my_groups`, `get_available_groups`
120
+ - **Dropbox (instructors and TAs):** `get_dropbox_folders`, `get_dropbox_submissions`, `get_dropbox_user_submissions`, `get_dropbox_feedback`, `download_dropbox_submission_file`
121
+ - **Account and sign-in:** `sign_in`, `sign_out`, `get_server_info`
122
+
111
123
  ## Prompts
112
124
 
113
125
  Apps with a prompt picker (like Claude Desktop) also offer four one-click prompts:
@@ -137,6 +149,8 @@ How often your school asks for two-factor is up to the school, not this tool.
137
149
 
138
150
  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
151
 
152
+ **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).
153
+
140
154
  ## Settings you might want
141
155
 
142
156
  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).
@@ -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.1",
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",