brightspace-mcp-server 3.9.11 → 3.9.13

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.
@@ -295,11 +295,18 @@ export class PurdueSSOFlow {
295
295
  return await this.hasCoVisiblePassword(page) && !await this.anyVisible(page, EMAIL_SELECTORS);
296
296
  }
297
297
  async hasPostCredentialChallenge(page) {
298
+ // Entra can land directly on its bare verification-code form - no
299
+ // number-match digits, no #idDiv_SAOTCAS_Title/#idDiv_SAOTCC_Title
300
+ // heading - when resuming a restored session. Without MFA_CODE_SELECTORS
301
+ // here, login() concluded no challenge was present and re-ran
302
+ // enterCredentials against a page with no username/password field.
303
+ // Idea from ElliotDrel/brightspace-mcp (branch codex/purdue-totp).
298
304
  return this.duoMfa.isChallenge(page) || await this.anyVisible(page, [
299
305
  NUMBER_MATCH_SELECTOR,
300
306
  "#idDiv_SAOTCAS_Title",
301
307
  "#idDiv_SAOTCC_Title",
302
308
  "#KmsiCheckboxField",
309
+ ...MFA_CODE_SELECTORS,
303
310
  ]);
304
311
  }
305
312
  /** Brightspace Bar's bounded number/auth/KMSI polling loop. */
@@ -13,6 +13,14 @@ import { log } from "../utils/logger.js";
13
13
  // Discover by fetching classlist for a known course and inspecting RoleId values.
14
14
  const INSTRUCTOR_ROLE_ID = 109;
15
15
  const TA_ROLE_ID = 135;
16
+ // When the role-ID fast path above returns nothing — a non-Purdue tenant
17
+ // where those IDs belong to different roles or none at all — staff are found
18
+ // instead by matching the role name the tenant itself reports. Role-name
19
+ // fallback adapted from matthewliu10/brightspace-mcp-server (MIT).
20
+ const TEACHING_ROLE_NAME = /\b(instructor|professor|lecturer|teaching assistant|coordinator|grader|ta)\b/i;
21
+ function isTeachingRoleByName(user) {
22
+ return TEACHING_ROLE_NAME.test(user.ClasslistRoleDisplayName ?? "");
23
+ }
16
24
  /**
17
25
  * Fetch every classlist user matching the optional filters, across all pages.
18
26
  *
@@ -48,6 +56,7 @@ export function registerGetRoster(server, apiClient) {
48
56
  // Parse and validate input
49
57
  const { courseId, includeStudents, searchTerm, limit } = GetRosterSchema.parse(args);
50
58
  const allUsers = [];
59
+ let roleFallbackUsed = false;
51
60
  if (!includeStudents) {
52
61
  // Fetch instructors and TAs in parallel
53
62
  const [instructorResult, taResult] = await Promise.allSettled([
@@ -77,6 +86,20 @@ export function registerGetRoster(server, apiClient) {
77
86
  error: taResult.reason,
78
87
  });
79
88
  }
89
+ // Purdue's role IDs found nobody — on a non-Purdue tenant they
90
+ // likely mean a different role, or none. Fall back to one
91
+ // unfiltered classlist fetch and match on the role display name
92
+ // the tenant itself reports instead of an institution-specific ID.
93
+ // A genuine failure of this fetch is intentionally not caught
94
+ // here: it should surface as a tool error rather than silently
95
+ // producing an empty staff list.
96
+ if (allUsers.length === 0) {
97
+ const everyone = await fetchClasslistUsers(apiClient, courseId, {
98
+ searchTerm,
99
+ });
100
+ allUsers.push(...everyone.filter(isTeachingRoleByName));
101
+ roleFallbackUsed = true;
102
+ }
80
103
  }
81
104
  else {
82
105
  // Fetch all users
@@ -110,6 +133,11 @@ export function registerGetRoster(server, apiClient) {
110
133
  ...(truncated
111
134
  ? { note: `Showing ${users.length} of ${total}. Raise the limit argument to see more.` }
112
135
  : {}),
136
+ ...(roleFallbackUsed
137
+ ? {
138
+ roleFilter: "No users matched the default instructor/TA role IDs, so staff were found by matching role display names (instructor, professor, lecturer, teaching assistant, coordinator, grader, TA) instead.",
139
+ }
140
+ : {}),
113
141
  users,
114
142
  });
115
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "3.9.11",
3
+ "version": "3.9.13",
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",