brightspace-mcp-server 4.0.6 → 4.0.7

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.
@@ -22,8 +22,9 @@ const QUIZ_FEEDBACK_NOTE = "The gradebook has no comment for this item, but it i
22
22
  "browser such as Respondus LockDown Browser. Open feedbackUrl to check; do not report " +
23
23
  "this item as having no feedback.";
24
24
  /**
25
- * The course's quizzes, or none when the route refuses or fails. A missing
26
- * quiz list only costs the feedback links, never the grades themselves.
25
+ * The course's quizzes, or null when the route refuses or fails. A missing
26
+ * quiz list only costs the feedback links, never the grades themselves, but
27
+ * the caller is told so a gradebook link isn't read as "not a quiz" (#278).
27
28
  */
28
29
  async function fetchQuizzes(apiClient, courseId) {
29
30
  try {
@@ -34,7 +35,7 @@ async function fetchQuizzes(apiClient, courseId) {
34
35
  }
35
36
  catch (error) {
36
37
  log("DEBUG", `get_my_grades: quiz list unavailable for course ${courseId}`, error);
37
- return [];
38
+ return null;
38
39
  }
39
40
  }
40
41
  /**
@@ -42,7 +43,7 @@ async function fetchQuizzes(apiClient, courseId) {
42
43
  * rows keep the quiz source URL and note; other rows link to the gradebook.
43
44
  */
44
45
  function toGradeItems(gradeValues, quizzes, baseUrl, courseId) {
45
- const quizByGradeItem = new Map(quizzes
46
+ const quizByGradeItem = new Map((quizzes ?? [])
46
47
  .filter((q) => q.GradeItemId != null)
47
48
  .map((q) => [String(q.GradeItemId), q.QuizId]));
48
49
  return gradeValues.map((gv) => {
@@ -65,6 +66,11 @@ function toGradeItems(gradeValues, quizzes, baseUrl, courseId) {
65
66
  };
66
67
  });
67
68
  }
69
+ function quizzesUnavailableNotice(courseIds) {
70
+ return (`The quiz list for ${courseIds.length} course(s) (${courseIds.join(", ")}) could not be read, so ` +
71
+ "quiz-scored grade items there link to the gradebook and lack the quiz feedback note. Quiz " +
72
+ "feedback may still exist on the quiz's own page; do not report those items as having no feedback.");
73
+ }
68
74
  /**
69
75
  * Register get_my_grades tool
70
76
  */
@@ -122,8 +128,17 @@ export function registerGetMyGrades(server, apiClient, config) {
122
128
  throw error;
123
129
  }
124
130
  // Map to clean objects
125
- const grades = toGradeItems(gradeValues, await quizzesPromise, config.baseUrl, courseId);
131
+ const quizzes = await quizzesPromise;
132
+ const grades = toGradeItems(gradeValues, quizzes, config.baseUrl, courseId);
126
133
  log("INFO", `get_my_grades: Retrieved ${grades.length} grade items for course ${courseId}`);
134
+ if (quizzes === null) {
135
+ return toolResponse({
136
+ courseId,
137
+ grades,
138
+ quizzesUnavailable: true,
139
+ notice: quizzesUnavailableNotice([courseId]),
140
+ });
141
+ }
127
142
  return toolResponse({ courseId, grades });
128
143
  }
129
144
  // All courses case
@@ -160,6 +175,7 @@ export function registerGetMyGrades(server, apiClient, config) {
160
175
  courseId: item.OrgUnit.Id,
161
176
  courseName: item.OrgUnit.Name,
162
177
  grades,
178
+ ...(quizzes === null ? { quizzesUnavailable: true } : {}),
163
179
  };
164
180
  }
165
181
  catch (error) {
@@ -216,7 +232,12 @@ export function registerGetMyGrades(server, apiClient, config) {
216
232
  .map(({ failed: _failed, ...rest }) => rest);
217
233
  const courses = settled
218
234
  .filter((c) => !c.authPending && !c.restricted && !c.failed)
219
- .map(({ courseId, courseName, grades }) => ({ courseId, courseName, grades }));
235
+ .map(({ courseId, courseName, grades, quizzesUnavailable }) => ({
236
+ courseId,
237
+ courseName,
238
+ grades,
239
+ ...(quizzesUnavailable ? { quizzesUnavailable } : {}),
240
+ }));
220
241
  log("INFO", `get_my_grades: Retrieved grades for ${courses.length} courses, ` +
221
242
  `${restrictedCourses.length} restricted (out of ${enrollmentItems.length} enrolled` +
222
243
  `${pending.length > 0 ? `, ${pending.length} pending sign-in` : ""}` +
@@ -255,6 +276,9 @@ export function registerGetMyGrades(server, apiClient, config) {
255
276
  "so they are listed in failedCourses instead of courses. Do not report these courses as " +
256
277
  "having no grades: open each gradeUrl, or call get_my_grades again for that courseId.");
257
278
  }
279
+ const quizGaps = courses.filter((c) => c.quizzesUnavailable).map((c) => c.courseId);
280
+ if (quizGaps.length > 0)
281
+ notices.push(quizzesUnavailableNotice(quizGaps));
258
282
  if (notices.length > 0)
259
283
  response.notice = notices.join(" ");
260
284
  return toolResponse(response);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "4.0.6",
3
+ "version": "4.0.7",
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",