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.
- package/build/tools/get-announcements.js +2 -0
- package/build/tools/get-assignments.js +2 -0
- package/build/tools/get-my-courses.js +2 -0
- package/build/tools/get-my-grades.js +2 -0
- package/build/tools/resolve-courses.js +2 -0
- package/build/utils/config.js +7 -0
- package/build/utils/course-filter.js +38 -3
- package/package.json +1 -1
|
@@ -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
|
}
|
package/build/utils/config.js
CHANGED
|
@@ -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.
|
|
13
|
-
*
|
|
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.
|
|
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",
|