brightspace-mcp-server 3.9.18 → 3.9.19

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.
@@ -130,6 +130,8 @@ export function registerGetAnnouncements(server, apiClient, config) {
130
130
  code: item.OrgUnit.Code,
131
131
  isActive: item.Access.IsActive,
132
132
  canAccess: item.Access.CanAccess,
133
+ startDate: item.Access.StartDate,
134
+ endDate: item.Access.EndDate,
133
135
  ...item,
134
136
  })), config.courseFilter);
135
137
  // Fetch announcements for each course (handle 403s gracefully)
@@ -457,6 +457,8 @@ export function registerGetAssignments(server, apiClient, config) {
457
457
  code: item.OrgUnit.Code,
458
458
  isActive: item.Access.IsActive,
459
459
  canAccess: item.Access.CanAccess,
460
+ startDate: item.Access.StartDate,
461
+ endDate: item.Access.EndDate,
460
462
  ...item,
461
463
  })), config.courseFilter);
462
464
  // Fetch assignments for each course (handle 403s gracefully)
@@ -42,6 +42,8 @@ export function registerGetMyCourses(server, apiClient, config) {
42
42
  role: item.Access.ClasslistRoleName,
43
43
  isActive: item.Access.IsActive,
44
44
  canAccess: item.Access.CanAccess,
45
+ startDate: item.Access.StartDate,
46
+ endDate: item.Access.EndDate,
45
47
  lastAccessed: item.Access.LastAccessed,
46
48
  })), { ...config.courseFilter, activeOnly });
47
49
  log("INFO", `get_my_courses: Retrieved ${courses.length} courses`);
@@ -77,6 +77,8 @@ export function registerGetMyGrades(server, apiClient, config) {
77
77
  code: item.OrgUnit.Code,
78
78
  isActive: item.Access.IsActive,
79
79
  canAccess: item.Access.CanAccess,
80
+ startDate: item.Access.StartDate,
81
+ endDate: item.Access.EndDate,
80
82
  ...item,
81
83
  })), config.courseFilter);
82
84
  // Fetch grades for each course (handle 403s gracefully)
@@ -41,6 +41,8 @@ export async function resolveCourses(apiClient, config, courseId) {
41
41
  code: item.OrgUnit.Code,
42
42
  isActive: item.Access.IsActive,
43
43
  canAccess: item.Access.CanAccess,
44
+ startDate: item.Access.StartDate,
45
+ endDate: item.Access.EndDate,
44
46
  })), config.courseFilter);
45
47
  return filtered.map((course) => ({ id: course.id, name: course.name }));
46
48
  }
@@ -67,6 +67,12 @@ export async function loadConfig() {
67
67
  const activeOnly = envBoolean(process.env.D2L_ACTIVE_ONLY, "D2L_ACTIVE_ONLY")
68
68
  ?? store?.activeOnly
69
69
  ?? true;
70
+ // Resolve currentOnly: env > store > default (false). When true, a course
71
+ // only passes the filter if Access.StartDate <= now <= Access.EndDate,
72
+ // matching Brightspace's "Current Courses" widget.
73
+ const currentOnly = envBoolean(process.env.D2L_CURRENT_ONLY, "D2L_CURRENT_ONLY")
74
+ ?? store?.currentOnly
75
+ ?? false;
70
76
  // D2L_ACCESS_TOKEN beats D2L_SESSION_COOKIE beats the normal stored-credential
71
77
  // browser flow; both are validated whenever present regardless of which one
72
78
  // wins, so a typo in the losing variable still fails loudly at startup.
@@ -100,6 +106,7 @@ export async function loadConfig() {
100
106
  includeCourseIds,
101
107
  excludeCourseIds,
102
108
  activeOnly,
109
+ currentOnly,
103
110
  },
104
111
  };
105
112
  }
@@ -4,18 +4,49 @@
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
6
  import { log } from "./logger.js";
7
+ /**
8
+ * True if `now` falls within [startDate, endDate].
9
+ *
10
+ * When both dates are null the course has no enrollment window at all — it's
11
+ * almost certainly an informational org unit (e.g. "Waterloo Campus",
12
+ * "Academic Integrity") rather than a term-bound course, so we return false.
13
+ * A single null side is treated as open-ended (start-only means "not yet
14
+ * ended", end-only means "already started"). A malformed date string is
15
+ * likewise treated as open-ended on that side rather than excluding the
16
+ * course outright.
17
+ *
18
+ * Adapted from LunaParker/brightspace-mcp-server (MIT), src/utils/course-filter.ts.
19
+ */
20
+ function isCurrentlyInWindow(startDate, endDate, now) {
21
+ // Both null → undated org unit, not a current semester course.
22
+ if (!startDate && !endDate)
23
+ return false;
24
+ if (startDate) {
25
+ const start = Date.parse(startDate);
26
+ if (!Number.isNaN(start) && start > now)
27
+ return false;
28
+ }
29
+ if (endDate) {
30
+ const end = Date.parse(endDate);
31
+ if (!Number.isNaN(end) && end < now)
32
+ return false;
33
+ }
34
+ return true;
35
+ }
7
36
  /**
8
37
  * Apply course filtering based on environment variable configuration.
9
38
  *
10
39
  * Filter priority:
11
40
  * 1. activeOnly — exclude inactive courses (default: true)
12
- * 2. includeCourseIds — whitelist (only these courses)
13
- * 3. excludeCourseIds — blacklist (remove these courses)
41
+ * 2. currentOnly — exclude courses outside their Access.StartDate/EndDate
42
+ * window, matching Brightspace's "Current Courses" widget (default: false)
43
+ * 3. includeCourseIds — whitelist (only these courses)
44
+ * 4. excludeCourseIds — blacklist (remove these courses)
14
45
  *
15
46
  * Tool-level courseId params bypass this filter entirely —
16
47
  * if user explicitly requests courseId=X, honor it regardless of config.
17
48
  */
18
- export function applyCourseFilter(courses, config) {
49
+ export function applyCourseFilter(courses, config, now = Date.now()) {
19
50
  let filtered = courses;
20
51
  const originalCount = courses.length;
21
52
  if (config.activeOnly) {
@@ -26,6 +57,9 @@ export function applyCourseFilter(courses, config) {
26
57
  // removes about 70 percent of the requests an all-courses call would make.
27
58
  filtered = filtered.filter(c => c.canAccess !== false);
28
59
  }
60
+ if (config.currentOnly) {
61
+ filtered = filtered.filter(c => isCurrentlyInWindow(c.startDate, c.endDate, now));
62
+ }
29
63
  if (config.includeCourseIds && config.includeCourseIds.length > 0) {
30
64
  filtered = filtered.filter(c => config.includeCourseIds.includes(c.id));
31
65
  }
@@ -35,6 +69,7 @@ export function applyCourseFilter(courses, config) {
35
69
  if (filtered.length !== originalCount) {
36
70
  log("DEBUG", `Course filter: ${originalCount} -> ${filtered.length} courses`, {
37
71
  activeOnly: config.activeOnly,
72
+ currentOnly: config.currentOnly,
38
73
  include: config.includeCourseIds,
39
74
  exclude: config.excludeCourseIds,
40
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "3.9.18",
3
+ "version": "3.9.19",
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",