brightspace-mcp-server 5.1.31 → 5.1.33
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.
|
@@ -62,6 +62,7 @@ export function registerGetMyGroups(server, apiClient) {
|
|
|
62
62
|
// though it looks numeric, so it must be coerced before it can be
|
|
63
63
|
// used as a key alongside the numeric Enrollments ids.
|
|
64
64
|
let classlistById = new Map();
|
|
65
|
+
let namesUnavailable = false;
|
|
65
66
|
try {
|
|
66
67
|
const classlist = await fetchClasslistUsers(apiClient, courseId);
|
|
67
68
|
classlistById = new Map(classlist.map((u) => [Number(u.Identifier), u]));
|
|
@@ -69,6 +70,7 @@ export function registerGetMyGroups(server, apiClient) {
|
|
|
69
70
|
catch (error) {
|
|
70
71
|
if (isMissingOrForbidden(error)) {
|
|
71
72
|
log("INFO", "get_my_groups: Classlist not accessible, member names will be null", { courseId });
|
|
73
|
+
namesUnavailable = true;
|
|
72
74
|
}
|
|
73
75
|
else {
|
|
74
76
|
throw error;
|
|
@@ -90,7 +92,14 @@ export function registerGetMyGroups(server, apiClient) {
|
|
|
90
92
|
};
|
|
91
93
|
});
|
|
92
94
|
log("INFO", `get_my_groups: Found ${groups.length} groups for course ${courseId}`);
|
|
93
|
-
|
|
95
|
+
// Null names are only meaningful with a reason attached: without one,
|
|
96
|
+
// a caller can't tell "the classlist couldn't be read" from "these
|
|
97
|
+
// members have no names", and may report the group as anonymous.
|
|
98
|
+
const namesNote = namesUnavailable
|
|
99
|
+
? "Couldn't read this course's classlist, so member names are null; only user ids are listed."
|
|
100
|
+
: undefined;
|
|
101
|
+
const note = [unreadNote, namesNote].filter(Boolean).join(" ") || undefined;
|
|
102
|
+
return toolResponse({ courseId, groups, ...(note ? { note } : {}) });
|
|
94
103
|
}
|
|
95
104
|
catch (error) {
|
|
96
105
|
return sanitizeError(error);
|
|
@@ -21,6 +21,15 @@ const TEACHING_ROLE_NAME = /\b(instructor|professor|lecturer|teaching assistant|
|
|
|
21
21
|
function isTeachingRoleByName(user) {
|
|
22
22
|
return TEACHING_ROLE_NAME.test(user.ClasslistRoleDisplayName ?? "");
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Whether a failed role route must be reported rather than skipped. A 403
|
|
26
|
+
* means the classlist is restricted for that role: nothing was missed.
|
|
27
|
+
* Anything else (a 404, a 5xx, a dropped connection) leaves a hole the caller
|
|
28
|
+
* would read as "this course has no TAs".
|
|
29
|
+
*/
|
|
30
|
+
function isReportableFailure(error) {
|
|
31
|
+
return error?.status !== 403;
|
|
32
|
+
}
|
|
24
33
|
/**
|
|
25
34
|
* Fetch every classlist user matching the optional filters, across all pages.
|
|
26
35
|
*
|
|
@@ -60,6 +69,7 @@ export function registerGetRoster(server, apiClient) {
|
|
|
60
69
|
const allUsers = [];
|
|
61
70
|
let authFailure;
|
|
62
71
|
let roleFallbackUsed = false;
|
|
72
|
+
let unavailable = [];
|
|
63
73
|
if (!includeStudents) {
|
|
64
74
|
// Fetch instructors and TAs in parallel
|
|
65
75
|
const [instructorResult, taResult] = await Promise.allSettled([
|
|
@@ -87,6 +97,10 @@ export function registerGetRoster(server, apiClient) {
|
|
|
87
97
|
allUsers.push(...instructorResult.value);
|
|
88
98
|
}
|
|
89
99
|
else {
|
|
100
|
+
if (!isAuthUnavailable(instructorResult.reason) &&
|
|
101
|
+
isReportableFailure(instructorResult.reason)) {
|
|
102
|
+
unavailable.push("instructors");
|
|
103
|
+
}
|
|
90
104
|
log("WARN", "get_roster: Failed to fetch instructors", {
|
|
91
105
|
error: instructorResult.reason,
|
|
92
106
|
});
|
|
@@ -95,6 +109,9 @@ export function registerGetRoster(server, apiClient) {
|
|
|
95
109
|
allUsers.push(...taResult.value);
|
|
96
110
|
}
|
|
97
111
|
else {
|
|
112
|
+
if (!isAuthUnavailable(taResult.reason) && isReportableFailure(taResult.reason)) {
|
|
113
|
+
unavailable.push("TAs");
|
|
114
|
+
}
|
|
98
115
|
log("WARN", "get_roster: Failed to fetch TAs", {
|
|
99
116
|
error: taResult.reason,
|
|
100
117
|
});
|
|
@@ -112,6 +129,9 @@ export function registerGetRoster(server, apiClient) {
|
|
|
112
129
|
});
|
|
113
130
|
allUsers.push(...everyone.filter(isTeachingRoleByName));
|
|
114
131
|
roleFallbackUsed = true;
|
|
132
|
+
// The unfiltered fetch covered every role, so nothing the
|
|
133
|
+
// failed role routes would have returned is missing any more.
|
|
134
|
+
unavailable = [];
|
|
115
135
|
}
|
|
116
136
|
}
|
|
117
137
|
else {
|
|
@@ -139,6 +159,17 @@ export function registerGetRoster(server, apiClient) {
|
|
|
139
159
|
}));
|
|
140
160
|
log("INFO", `get_roster: Retrieved ${users.length} users for course ${courseId}` +
|
|
141
161
|
(authFailure ? " (sign-in pending for one route)" : ""));
|
|
162
|
+
const notices = [];
|
|
163
|
+
if (unavailable.length > 0) {
|
|
164
|
+
notices.push(`The ${unavailable.join(" and ")} for this course could not be read from ` +
|
|
165
|
+
"Brightspace, so they may be missing from this list. Do not report this " +
|
|
166
|
+
`course as having no ${unavailable.join(" or ")}; call get_roster again to retry.`);
|
|
167
|
+
}
|
|
168
|
+
if (authFailure) {
|
|
169
|
+
notices.push("Sign-in to Brightspace is still in progress, so part of the roster for this " +
|
|
170
|
+
`course could not be fetched yet. ${authPendingNotice(authFailure.reason)} Call ` +
|
|
171
|
+
"get_roster again once sign-in finishes.");
|
|
172
|
+
}
|
|
142
173
|
return toolResponse({
|
|
143
174
|
courseId,
|
|
144
175
|
total,
|
|
@@ -153,14 +184,9 @@ export function registerGetRoster(server, apiClient) {
|
|
|
153
184
|
}
|
|
154
185
|
: {}),
|
|
155
186
|
users,
|
|
156
|
-
...(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
notice: "Sign-in to Brightspace is still in progress, so part of the roster for this " +
|
|
160
|
-
`course could not be fetched yet. ${authPendingNotice(authFailure.reason)} Call ` +
|
|
161
|
-
"get_roster again once sign-in finishes.",
|
|
162
|
-
}
|
|
163
|
-
: {}),
|
|
187
|
+
...(unavailable.length > 0 ? { unavailable } : {}),
|
|
188
|
+
...(authFailure ? { authPending: true } : {}),
|
|
189
|
+
...(notices.length > 0 ? { notice: notices.join(" ") } : {}),
|
|
164
190
|
});
|
|
165
191
|
}
|
|
166
192
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.33",
|
|
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",
|