brightspace-d2l-mcp 1.4.3 → 1.4.4

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/CHANGELOG.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.4
4
+
5
+ - Make a stale course id actionable: the error now says to call `get_my_courses` to list valid course IDs, and a course tool that finds no enrollments at all points at running `npx brightspace-d2l-mcp setup` again
6
+
3
7
  ## 1.4.3
4
8
 
5
9
  - Add README section on switching from brightspace-mcp-server
10
+ - Add docs/troubleshooting.md for connection, browser and sign in failures, linked from the README and llms.txt
6
11
 
7
12
  ## 1.4.2
8
13
 
package/README.md CHANGED
@@ -251,6 +251,8 @@ App ids: `claude-desktop`, `claude-code`, `cursor`, `vscode`, `windsurf`, `codex
251
251
 
252
252
  ## Troubleshooting
253
253
 
254
+ Something not working? **[docs/troubleshooting.md](docs/troubleshooting.md)** walks through an app that shows no Brightspace tools, a setup window that never opens, and a single sign on that keeps failing, with the exact message each one prints. It starts with `npx brightspace-d2l-mcp doctor`.
255
+
254
256
  <details>
255
257
  <summary><b>"No supported browser was found"</b></summary>
256
258
 
@@ -386,6 +388,20 @@ node build/index.js setup # connects your AI apps to this local build
386
388
  Tools live in [`src/tools/`](src/tools), sign-in in [`src/auth/`](src/auth), and setup in [`src/cli/`](src/cli).
387
389
  Issues and pull requests are welcome.
388
390
 
391
+ ## More free student MCP servers
392
+
393
+ All free, open source and read only where they touch your accounts. Browse the full list at [brightspacemcp.com/more-mcps](https://brightspacemcp.com/more-mcps/).
394
+
395
+ | Server | What it does |
396
+ | --- | --- |
397
+ | [Canvas MCP](https://github.com/AnonymousOnyx22/canvas-lms-student-mcp) | Canvas LMS for AI, read only. Due dates, missing work, grades and announcements. |
398
+ | [Moodle MCP](https://github.com/AnonymousOnyx22/moodle-student-mcp) | Moodle for AI, read only. Due dates, overdue work, grades and announcements. |
399
+ | [Google Classroom MCP](https://github.com/AnonymousOnyx22/google-classroom-student-mcp) | Google Classroom for AI, read only. Todos, due dates, missing work and grades. |
400
+ | [GPA Calculator MCP](https://github.com/AnonymousOnyx22/gpa-calculator-mcp) | GPA, grade conversion and the grade you need on the final, for AI. Works offline, no account. |
401
+ | [Citations MCP](https://github.com/AnonymousOnyx22/student-citations-mcp) | APA, MLA and Chicago citations from a DOI, an ISBN or typed details. No account. |
402
+ | [Study Planner MCP](https://github.com/AnonymousOnyx22/study-planner-mcp) | Exam study schedules, spaced repetition dates, pomodoro plans and assignment milestones. Works offline. |
403
+ | [Paper Finder MCP](https://github.com/AnonymousOnyx22/paper-finder-student-mcp) | Search scholarly papers, find open access copies and related work through OpenAlex. No key needed. |
404
+
389
405
  ## License
390
406
 
391
407
  [MIT](LICENSE) © AnonymousOnyx22
@@ -12,13 +12,28 @@ function withSessionHint(status, message) {
12
12
  const detail = message.trim();
13
13
  return detail ? `${detail} ${SESSION_EXPIRED_HINT}` : SESSION_EXPIRED_HINT;
14
14
  }
15
+ // A stale course id must never reach the person as a bare 404. Every 404
16
+ // message names the tool that lists the valid ids, so the next step is obvious
17
+ // wherever the text shows up: a tool result mid-chat, a log line, or the CLI.
18
+ export const COURSE_NOT_FOUND_HINT = `Call \`get_my_courses\` to list valid course IDs, then retry.`;
19
+ // An empty enrollment list gives the caller nothing to work with, so the
20
+ // message points at the command that checks the sign in again.
21
+ export const NO_COURSES_HINT = `No courses came back from Brightspace. Run \`${SETUP_COMMAND}\` again to check your sign in, then retry.`;
22
+ // Appends the get_my_courses hint to a 404. A caller that already named the
23
+ // tool keeps its own wording, so the hint is never repeated in one message.
24
+ function withCourseHint(status, message) {
25
+ if (status !== 404 || message.includes("get_my_courses"))
26
+ return message;
27
+ const detail = message.trim();
28
+ return detail ? `${detail} ${COURSE_NOT_FOUND_HINT}` : COURSE_NOT_FOUND_HINT;
29
+ }
15
30
  // Base class for all HTTP API errors
16
31
  export class ApiError extends AuthError {
17
32
  status;
18
33
  endpoint;
19
34
  responseBody;
20
35
  constructor(status, endpoint, message, responseBody, cause) {
21
- super(`[PBMCP-2001] API error (${status}) at ${endpoint}: ${withSessionHint(status, message)}`, cause);
36
+ super(`[PBMCP-2001] API error (${status}) at ${endpoint}: ${withSessionHint(status, withCourseHint(status, message))}`, cause);
22
37
  this.status = status;
23
38
  this.endpoint = endpoint;
24
39
  this.responseBody = responseBody;
@@ -58,4 +73,23 @@ export class TokenRefreshError extends NetworkError {
58
73
  this.name = "TokenRefreshError";
59
74
  }
60
75
  }
76
+ // The caller named a course id that is not in their enrollments. Thrown from
77
+ // the enrollment check, before any course scoped request goes out, so the
78
+ // person sees this instead of a bare 404 from Brightspace.
79
+ export class CourseNotFoundError extends Error {
80
+ courseId;
81
+ constructor(courseId) {
82
+ super(`Course ${courseId} is not one of your Brightspace courses. ${COURSE_NOT_FOUND_HINT}`);
83
+ this.courseId = courseId;
84
+ this.name = "CourseNotFoundError";
85
+ }
86
+ }
87
+ // The enrollment list came back empty, so there is nothing for any course tool
88
+ // to read. Setting up again checks the saved sign in and the school address.
89
+ export class NoCoursesError extends Error {
90
+ constructor() {
91
+ super(NO_COURSES_HINT);
92
+ this.name = "NoCoursesError";
93
+ }
94
+ }
61
95
  //# sourceMappingURL=errors.js.map
@@ -7,6 +7,6 @@ export { discoverVersions } from "./version-discovery.js";
7
7
  export { TTLCache } from "./cache.js";
8
8
  export { TokenBucket } from "./rate-limiter.js";
9
9
  // Errors
10
- export { ApiError, RateLimitError, NetworkError } from "./errors.js";
10
+ export { ApiError, RateLimitError, NetworkError, CourseNotFoundError, NoCoursesError, COURSE_NOT_FOUND_HINT, NO_COURSES_HINT, } from "./errors.js";
11
11
  export { DEFAULT_CACHE_TTLS } from "./types.js";
12
12
  //# sourceMappingURL=index.js.map
@@ -38,7 +38,7 @@ export function mapNewsItem(item) {
38
38
  export function registerGetAnnouncements(server, apiClient, config) {
39
39
  server.registerTool("get_announcements", {
40
40
  title: "Get Announcements",
41
- description: "Fetch recent announcements from your courses. Can filter to a specific course or get announcements across all courses. Use this when the user asks about announcements, news, updates from instructors, recent posts, or what professors said. Example: \"Did my professors post anything new?\"",
41
+ description: "Fetch recent announcements (news items) from your courses in D2L Brightspace. Can filter to a specific course or get announcements across all courses. Use this when the user asks about announcements, news, news items, updates from instructors, recent posts, or what professors said in Brightspace or D2L. Example: \"Did my professors post anything new?\"",
42
42
  inputSchema: GetAnnouncementsSchema,
43
43
  annotations: { readOnlyHint: true, openWorldHint: true },
44
44
  }, async (args) => {
@@ -279,7 +279,7 @@ function gradebookHeadsUp(raw, fetched, courseId, baseUrl) {
279
279
  export function registerGetAssignments(server, apiClient, config) {
280
280
  server.registerTool("get_assignments", {
281
281
  title: "Get Assignments",
282
- description: "Fetch assignments and quizzes for a specific course or all enrolled courses. Shows dropbox submissions and quizzes with due dates, status, and rubric info. Use this when the user asks about assignments, homework, what to submit, quizzes, or assignment details and rubrics. Example: \"What assignments do I have in Calculus this week?\"",
282
+ description: "Fetch assignments and quizzes for a specific course or all enrolled courses in D2L Brightspace. Shows dropbox (assignment) submissions and quizzes with due dates, status, and rubric info. Use this when the user asks about assignments, homework, dropboxes, dropbox folders, what to submit, quizzes, or assignment details and rubrics in Brightspace or D2L. Example: \"What assignments do I have in Calculus this week?\"",
283
283
  inputSchema: GetAssignmentsSchema,
284
284
  annotations: { readOnlyHint: true, openWorldHint: true },
285
285
  }, async (args) => {
@@ -2,6 +2,7 @@ import { DEFAULT_CACHE_TTLS } from "../api/index.js";
2
2
  import { fetchAllItems } from "../api/paginate.js";
3
3
  import { GetMyCoursesSchema, } from "./schemas.js";
4
4
  import { toolResponse, sanitizeError } from "./tool-helpers.js";
5
+ import { NoCoursesError } from "../api/errors.js";
5
6
  import { log } from "../utils/logger.js";
6
7
  import { applyCourseFilter } from "../utils/course-filter.js";
7
8
  export function registerGetMyCourses(server, apiClient, config) {
@@ -32,6 +33,10 @@ export function registerGetMyCourses(server, apiClient, config) {
32
33
  canAccess: item.Access.CanAccess,
33
34
  lastAccessed: item.Access.LastAccessed,
34
35
  })), { ...config.courseFilter, activeOnly });
36
+ // An empty list leaves the caller stuck, so say which command checks
37
+ // the sign in again instead of returning nothing to act on.
38
+ if (courses.length === 0)
39
+ throw new NoCoursesError();
35
40
  log("INFO", `get_my_courses: Retrieved ${courses.length} courses`);
36
41
  return toolResponse(courses);
37
42
  }
@@ -6,7 +6,7 @@ import { applyCourseFilter } from "../utils/course-filter.js";
6
6
  export function registerGetMyGrades(server, apiClient, config) {
7
7
  server.registerTool("get_my_grades", {
8
8
  title: "Get My Grades",
9
- description: "Fetch your grade breakdown for a specific course or all enrolled courses. Shows grade items with points, percentages, and comments. Use this when the user asks about grades, scores, marks, GPA, academic performance, or how they're doing in a class. Example: \"What's my current grade in Calculus?\"",
9
+ description: "Fetch your grade breakdown for a specific course or all enrolled courses in D2L Brightspace. Shows grade items (grade columns) with points, percentages, and comments. Use this when the user asks about grades, grade items, grade item, scores, marks, GPA, academic performance, or how they're doing in a class in Brightspace or D2L. Example: \"What's my current grade in Calculus?\"",
10
10
  inputSchema: GetMyGradesSchema,
11
11
  annotations: { readOnlyHint: true, openWorldHint: true },
12
12
  }, async (args) => {
@@ -1,4 +1,6 @@
1
1
  import { DEFAULT_CACHE_TTLS } from "../api/index.js";
2
+ import { CourseNotFoundError, NoCoursesError } from "../api/errors.js";
3
+ import { fetchAllItems } from "../api/paginate.js";
2
4
  import { GetUpcomingDueDatesSchema } from "./schemas.js";
3
5
  import { toolResponse, sanitizeError } from "./tool-helpers.js";
4
6
  import { log } from "../utils/logger.js";
@@ -10,9 +12,10 @@ function unwrapList(raw) {
10
12
  }
11
13
  export async function resolveCourses(apiClient, config, courseId) {
12
14
  let items = [];
15
+ let enrollmentsLoaded = false;
13
16
  try {
14
- const response = await apiClient.get(apiClient.lp("/enrollments/myenrollments/?orgUnitTypeId=3&isActive=true"), { ttl: DEFAULT_CACHE_TTLS.enrollments });
15
- items = response.Items ?? [];
17
+ items = await fetchAllItems(apiClient, apiClient.lp("/enrollments/myenrollments/?orgUnitTypeId=3&isActive=true"), { ttl: DEFAULT_CACHE_TTLS.enrollments });
18
+ enrollmentsLoaded = true;
16
19
  }
17
20
  catch (error) {
18
21
  if (!courseId)
@@ -21,6 +24,10 @@ export async function resolveCourses(apiClient, config, courseId) {
21
24
  }
22
25
  if (courseId) {
23
26
  const match = items.find((item) => item.OrgUnit.Id === courseId);
27
+ // Only claim the id is unknown when the list really loaded. After a failed
28
+ // lookup the course scoped request is still the better test.
29
+ if (enrollmentsLoaded && !match)
30
+ throw new CourseNotFoundError(courseId);
24
31
  return [{ id: courseId, name: match?.OrgUnit.Name ?? null }];
25
32
  }
26
33
  const filtered = applyCourseFilter(items.map((item) => ({
@@ -30,6 +37,8 @@ export async function resolveCourses(apiClient, config, courseId) {
30
37
  isActive: item.Access.IsActive,
31
38
  canAccess: item.Access.CanAccess,
32
39
  })), config.courseFilter);
40
+ if (enrollmentsLoaded && filtered.length === 0)
41
+ throw new NoCoursesError();
33
42
  return filtered.map((course) => ({ id: course.id, name: course.name }));
34
43
  }
35
44
  async function fetchCourseDueItems(apiClient, baseUrl, course) {
@@ -1,6 +1,6 @@
1
1
  import { ZodError } from "zod";
2
2
  import { ApiError, RateLimitError, NetworkError } from "../api/index.js";
3
- import { TokenRefreshError } from "../api/errors.js";
3
+ import { TokenRefreshError, CourseNotFoundError, NoCoursesError, COURSE_NOT_FOUND_HINT, } from "../api/errors.js";
4
4
  import { AuthRequiredError } from "../auth/authenticator.js";
5
5
  import { log } from "../utils/logger.js";
6
6
  import { SETUP_COMMAND } from "../utils/paths.js";
@@ -36,9 +36,13 @@ export function sanitizeError(error) {
36
36
  const detail = error.detail && isSafeDetail(error.detail) ? ` (detected type: ${error.detail})` : "";
37
37
  return errorResponse(`Could not save the file.${detail} ${DOWNLOAD_FAILURE_GUIDANCE[error.kind]}`);
38
38
  }
39
+ // Messages are written by this package, never copied from a remote response.
40
+ if (error instanceof CourseNotFoundError || error instanceof NoCoursesError) {
41
+ return errorResponse(error.message);
42
+ }
39
43
  if (error instanceof ApiError) {
40
44
  if (error.status === 404) {
41
- return errorResponse("Resource not found. The course or item may not exist, or you may not have access.");
45
+ return errorResponse(`Resource not found. The course or item may not exist, or you may not have access. ${COURSE_NOT_FOUND_HINT}`);
42
46
  }
43
47
  if (error.status === 401) {
44
48
  return errorResponse(`Your Brightspace session expired and signing in again did not finish. Run \`${SETUP_COMMAND}\` in a terminal, then try again.`);
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Brightspace D2L MCP Server
2
2
 
3
- > MCP server that gives AI assistants and agents read access to D2L Brightspace. It returns due dates with real submission status, grades, assignments, rubrics, course content, announcements, discussions and class lists. It works at any school that uses D2L Brightspace, including schools with Microsoft, Duo, Okta or Shibboleth sign-in. It is read only and never stores a password. Current version: 1.4.3.
3
+ > MCP server that gives AI assistants and agents read access to D2L Brightspace. It returns due dates with real submission status, grades, assignments, rubrics, course content, announcements, discussions and class lists. It works at any school that uses D2L Brightspace, including schools with Microsoft, Duo, Okta or Shibboleth sign-in. It is read only and never stores a password. Current version: 1.4.4.
4
4
 
5
5
  ## Install
6
6
 
@@ -49,6 +49,30 @@ The server also offers MCP prompts: what_should_i_do_now, plan_my_week, weekly_d
49
49
 
50
50
  Submission status values are submitted, graded, not_submitted, draft, not_open_yet, closed, past_due, restricted and unknown. Items that are submitted or graded are done and should never be suggested as a to-do. Quizzes can show unknown because Brightspace does not show students their quiz attempts, so ask the user.
51
51
 
52
+ ## Which tool to use (decision table, routing table)
53
+
54
+ Short routing: due today -> get_todo, target grade -> get_grade_outlook, catch up -> get_whats_new.
55
+
56
+ | If the user asks about | Use this tool |
57
+ |---|---|
58
+ | What is due today, or what should I do now | get_todo |
59
+ | What they need on the remaining work to hit a target grade | get_grade_outlook |
60
+ | Catching up on what is new across every course | get_whats_new |
61
+ | Upcoming deadlines with real submission status | get_upcoming_due_dates |
62
+ | Exams, tests, labs or classes | get_calendar_events |
63
+ | Current grades and instructor comments | get_my_grades |
64
+ | Assignments and quizzes with rubrics | get_assignments |
65
+ | Files attached to an assignment | get_assignment_files |
66
+ | Course modules, slides, files and links | get_course_content |
67
+ | Syllabus and course overview | get_syllabus |
68
+ | Recent announcements | get_announcements |
69
+ | Discussion forums and posts | get_discussions |
70
+ | Instructors and TAs with contact info | get_roster |
71
+ | Email addresses for a course | get_classlist_emails |
72
+ | Saving a course file or submission | download_file |
73
+ | Exporting unfinished deadlines to a calendar | export_due_dates_calendar |
74
+ | Enrolled courses | get_my_courses |
75
+
52
76
  ## Other commands
53
77
 
54
78
  - npx brightspace-d2l-mcp login signs in again
@@ -71,5 +95,6 @@ The password is never seen or stored because the user types it into the school's
71
95
  ## Links
72
96
 
73
97
  - Repository https://github.com/AnonymousOnyx22/brightspace-d2l-mcp
98
+ - Troubleshooting https://github.com/AnonymousOnyx22/brightspace-d2l-mcp/blob/main/docs/troubleshooting.md
74
99
  - npm https://www.npmjs.com/package/brightspace-d2l-mcp
75
100
  - Agent setup guides https://github.com/AnonymousOnyx22/brightspace-d2l-mcp/blob/main/docs/agents.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-d2l-mcp",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "mcpName": "io.github.AnonymousOnyx22/brightspace-d2l-mcp",
5
5
  "description": "Brightspace MCP server for D2L: connect Brightspace to Claude, ChatGPT, Cursor, Copilot and any AI agent. One-command setup at any school with normal browser sign-in (SSO, Duo, MFA). Due dates with real submission status, exam and calendar events, what's new catch-up, grades and target-grade calculator, calendar export, assignments, content, announcements.",
6
6
  "type": "module",