brightspace-mcp-server 3.12.0 → 3.12.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.
@@ -48,6 +48,7 @@ export async function fetchAllItems(apiClient, firstPath, options) {
48
48
  }
49
49
  /**
50
50
  * Every object across every page of a { Objects, Next } endpoint, in order.
51
+ * A bare array response is taken as the complete list.
51
52
  *
52
53
  * Next arrives either as a full next-page URL or as a bare bookmark, so the
53
54
  * absolute form is reduced to its path and query and anything else is treated
@@ -65,6 +66,11 @@ export async function fetchAllObjects(apiClient, firstPath, options) {
65
66
  }
66
67
  const response = await apiClient.get(path, options);
67
68
  pages += 1;
69
+ // Some tenants answer a paged route with a bare array: that is the whole list.
70
+ if (Array.isArray(response)) {
71
+ objects.push(...response);
72
+ break;
73
+ }
68
74
  objects.push(...(response?.Objects ?? []));
69
75
  const next = response?.Next;
70
76
  if (!next)
@@ -4,7 +4,7 @@
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
6
  import { DEFAULT_CACHE_TTLS } from "../api/index.js";
7
- import { fetchAllItems } from "../api/paginate.js";
7
+ import { fetchAllItems, fetchAllObjects } from "../api/paginate.js";
8
8
  import { GetAssignmentsSchema } from "./schemas.js";
9
9
  import { toolResponse, sanitizeError, isAuthUnavailable, authPendingNotice } from "./tool-helpers.js";
10
10
  import { convertHtmlToMarkdown } from "../utils/html-converter.js";
@@ -110,7 +110,10 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
110
110
  // cannot be made until they have answered.
111
111
  const [dropboxResult, quizResult, gradebookResult, contentResult] = await Promise.allSettled([
112
112
  apiClient.get(apiClient.le(courseId, "/dropbox/folders/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
113
- apiClient.get(apiClient.le(courseId, "/quizzes/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
113
+ // Quizzes are paged; a course with many of them spills past page one.
114
+ fetchAllObjects(apiClient, apiClient.le(courseId, "/quizzes/"), {
115
+ ttl: DEFAULT_CACHE_TTLS.assignments,
116
+ }),
114
117
  apiClient.get(apiClient.le(courseId, "/grades/"), {
115
118
  ttl: DEFAULT_CACHE_TTLS.assignments,
116
119
  }),
@@ -263,11 +266,7 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
263
266
  // forbidden or unavailable, so a failed list starts from an empty set.
264
267
  let quizzes = [];
265
268
  if (quizResult.status === "fulfilled") {
266
- const quizResponse = quizResult.value;
267
- // D2L quizzes API returns paged result { Objects: [...] } or a plain array
268
- quizzes = Array.isArray(quizResponse)
269
- ? quizResponse
270
- : quizResponse?.Objects ?? [];
269
+ quizzes = quizResult.value;
271
270
  }
272
271
  else {
273
272
  log("DEBUG", `Failed to fetch quizzes for course ${courseId}`, quizResult.reason);
@@ -4,7 +4,7 @@
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
6
  import { DEFAULT_CACHE_TTLS } from "../api/index.js";
7
- import { fetchAllItems } from "../api/paginate.js";
7
+ import { fetchAllItems, fetchAllObjects } from "../api/paginate.js";
8
8
  import { GetMyGradesSchema, } from "./schemas.js";
9
9
  import { toolResponse, sanitizeError, errorResponse, isAuthUnavailable, authPendingNotice, } from "./tool-helpers.js";
10
10
  import { log } from "../utils/logger.js";
@@ -27,8 +27,10 @@ const QUIZ_FEEDBACK_NOTE = "The gradebook has no comment for this item, but it i
27
27
  */
28
28
  async function fetchQuizzes(apiClient, courseId) {
29
29
  try {
30
- const raw = await apiClient.get(apiClient.le(courseId, "/quizzes/"), { ttl: DEFAULT_CACHE_TTLS.assignments });
31
- return Array.isArray(raw) ? raw : raw?.Objects ?? [];
30
+ // Quizzes are paged; a course with many of them spills past page one.
31
+ return await fetchAllObjects(apiClient, apiClient.le(courseId, "/quizzes/"), {
32
+ ttl: DEFAULT_CACHE_TTLS.assignments,
33
+ });
32
34
  }
33
35
  catch (error) {
34
36
  log("DEBUG", `get_my_grades: quiz list unavailable for course ${courseId}`, error);
@@ -4,6 +4,7 @@
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
6
  import { DEFAULT_CACHE_TTLS } from "../api/index.js";
7
+ import { fetchAllObjects } from "../api/paginate.js";
7
8
  import { GetUpcomingDueDatesSchema, } from "./schemas.js";
8
9
  import { toolResponse, toolResponseWithNotice, sanitizeError, isAuthUnavailable, authPendingNotice } from "./tool-helpers.js";
9
10
  import { log } from "../utils/logger.js";
@@ -78,7 +79,10 @@ function calendarItems(events, items) {
78
79
  async function fetchCourseDueItems(apiClient, baseUrl, course, from, to) {
79
80
  const [dropboxResult, quizResult, discussionResult, calendarResult] = await Promise.allSettled([
80
81
  apiClient.get(apiClient.le(course.id, "/dropbox/folders/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
81
- apiClient.get(apiClient.le(course.id, "/quizzes/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
82
+ // Quizzes are paged; a course with many of them spills past page one.
83
+ fetchAllObjects(apiClient, apiClient.le(course.id, "/quizzes/"), {
84
+ ttl: DEFAULT_CACHE_TTLS.assignments,
85
+ }),
82
86
  fetchDiscussionDueTopics(apiClient, course.id),
83
87
  fetchCourseCalendarEvents(apiClient, baseUrl, course, from, to),
84
88
  ]);
@@ -117,7 +121,7 @@ async function fetchCourseDueItems(apiClient, baseUrl, course, from, to) {
117
121
  log("DEBUG", `get_upcoming_due_dates: failed to fetch dropbox folders for course ${course.id}`, dropboxResult.reason);
118
122
  }
119
123
  if (quizResult.status === "fulfilled") {
120
- for (const quiz of unwrapList(quizResult.value)) {
124
+ for (const quiz of quizResult.value) {
121
125
  if (quiz.IsActive === false)
122
126
  continue;
123
127
  // Many instructors set only an End Date, which is the effective deadline.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "3.12.0",
3
+ "version": "3.12.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",