brightspace-mcp-server 3.13.0 → 3.13.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.
|
@@ -24,6 +24,20 @@ const ENTITY_TYPES = {
|
|
|
24
24
|
"D2L.LE.Survey.Survey": "survey",
|
|
25
25
|
};
|
|
26
26
|
const HOUR_MS = 60 * 60 * 1000;
|
|
27
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
28
|
+
/**
|
|
29
|
+
* When an event stops counting as upcoming (epoch ms). A timed event counts
|
|
30
|
+
* only until it starts. An all-day event lasts through the end of its last
|
|
31
|
+
* day — EndDay names that day, not the midnight after it — so today's exam
|
|
32
|
+
* day or a week-long break still underway is not dropped as already past.
|
|
33
|
+
*/
|
|
34
|
+
export function eventEndsAt(event) {
|
|
35
|
+
const start = new Date(event.start).getTime();
|
|
36
|
+
if (!event.allDay)
|
|
37
|
+
return start;
|
|
38
|
+
const lastDay = event.end ? new Date(event.end).getTime() : start;
|
|
39
|
+
return Math.max(start, Number.isFinite(lastDay) ? lastDay : start) + DAY_MS;
|
|
40
|
+
}
|
|
27
41
|
function descriptionMarkdown(description) {
|
|
28
42
|
const html = typeof description === "string" ? description : description?.Html || description?.Text || "";
|
|
29
43
|
return convertHtmlToMarkdown(html).markdown.trim();
|
|
@@ -34,6 +48,7 @@ function toCalendarEvent(raw, baseUrl, course) {
|
|
|
34
48
|
const start = raw.StartDateTime ?? raw.StartDay ?? null;
|
|
35
49
|
if (!start)
|
|
36
50
|
return null;
|
|
51
|
+
const allDay = !raw.StartDateTime;
|
|
37
52
|
const end = raw.EndDateTime ?? raw.EndDay ?? null;
|
|
38
53
|
const location = raw.LocationName?.trim();
|
|
39
54
|
const description = descriptionMarkdown(raw.Description);
|
|
@@ -45,6 +60,7 @@ function toCalendarEvent(raw, baseUrl, course) {
|
|
|
45
60
|
courseName: course.name,
|
|
46
61
|
start,
|
|
47
62
|
...(end ? { end } : {}),
|
|
63
|
+
...(allDay ? { allDay: true } : {}),
|
|
48
64
|
...(location ? { location } : {}),
|
|
49
65
|
...(description ? { description } : {}),
|
|
50
66
|
url: calendarUrl(baseUrl, course.id),
|
|
@@ -59,13 +75,16 @@ function toCalendarEvent(raw, baseUrl, course) {
|
|
|
59
75
|
};
|
|
60
76
|
}
|
|
61
77
|
/**
|
|
62
|
-
* Every calendar event in one course that starts inside [from, to] (epoch ms)
|
|
78
|
+
* Every calendar event in one course that starts inside [from, to] (epoch ms),
|
|
79
|
+
* plus any all-day event still underway at `from`.
|
|
63
80
|
*
|
|
64
81
|
* The request window is widened to whole hours so repeated calls within the
|
|
65
|
-
* cache TTL share one cached response
|
|
82
|
+
* cache TTL share one cached response, and its start reaches back a day so an
|
|
83
|
+
* all-day event that began at today's midnight is in the response at all; the
|
|
84
|
+
* exact window is applied here.
|
|
66
85
|
*/
|
|
67
86
|
export async function fetchCourseCalendarEvents(apiClient, baseUrl, course, from, to) {
|
|
68
|
-
const requestFrom = new Date(Math.floor(from / HOUR_MS) * HOUR_MS).toISOString();
|
|
87
|
+
const requestFrom = new Date(Math.floor((from - DAY_MS) / HOUR_MS) * HOUR_MS).toISOString();
|
|
69
88
|
const requestTo = new Date(Math.ceil(to / HOUR_MS) * HOUR_MS).toISOString();
|
|
70
89
|
const raw = await fetchAllObjects(apiClient, apiClient.le(course.id, `/calendar/events/myEvents/?startDateTime=${encodeURIComponent(requestFrom)}&endDateTime=${encodeURIComponent(requestTo)}`), { ttl: DEFAULT_CACHE_TTLS.calendar });
|
|
71
90
|
return raw
|
|
@@ -74,6 +93,6 @@ export async function fetchCourseCalendarEvents(apiClient, baseUrl, course, from
|
|
|
74
93
|
if (!event)
|
|
75
94
|
return false;
|
|
76
95
|
const start = new Date(event.start).getTime();
|
|
77
|
-
return Number.isFinite(start) &&
|
|
96
|
+
return Number.isFinite(start) && eventEndsAt(event) >= from && start <= to;
|
|
78
97
|
});
|
|
79
98
|
}
|
|
@@ -12,7 +12,7 @@ import { quizUrl, discussionUrl } from "../utils/deep-links.js";
|
|
|
12
12
|
import { assignmentLinkResolver } from "./assignment-links.js";
|
|
13
13
|
import { dueIn } from "../utils/due-in.js";
|
|
14
14
|
import { resolveCourses } from "./resolve-courses.js";
|
|
15
|
-
import { fetchCourseCalendarEvents } from "./calendar-events.js";
|
|
15
|
+
import { fetchCourseCalendarEvents, eventEndsAt } from "./calendar-events.js";
|
|
16
16
|
/** D2L list endpoints return either a paged { Objects: [...] } or a flat array. */
|
|
17
17
|
function unwrapList(raw) {
|
|
18
18
|
return Array.isArray(raw) ? raw : (raw?.Objects ?? []);
|
|
@@ -63,6 +63,7 @@ function calendarItems(events, items) {
|
|
|
63
63
|
dueIn: dueIn(event.start),
|
|
64
64
|
startDate: null,
|
|
65
65
|
endDate: event.end ?? null,
|
|
66
|
+
...(event.allDay ? { allDay: true } : {}),
|
|
66
67
|
...(event.location ? { location: event.location } : {}),
|
|
67
68
|
url: event.url,
|
|
68
69
|
}));
|
|
@@ -214,11 +215,15 @@ export function registerGetUpcomingDueDates(server, apiClient, config) {
|
|
|
214
215
|
log("DEBUG", "get_upcoming_due_dates: skipping course after fetch failure", result.reason);
|
|
215
216
|
return [];
|
|
216
217
|
});
|
|
217
|
-
// Keep what falls inside the window, soonest first
|
|
218
|
+
// Keep what falls inside the window, soonest first. An all-day
|
|
219
|
+
// event counts until its last day ends, not just until its midnight start.
|
|
218
220
|
const upcoming = items
|
|
219
221
|
.filter((item) => {
|
|
220
222
|
const due = new Date(item.dueDate).getTime();
|
|
221
|
-
|
|
223
|
+
const until = item.allDay
|
|
224
|
+
? eventEndsAt({ start: item.dueDate, end: item.endDate ?? undefined, allDay: true })
|
|
225
|
+
: due;
|
|
226
|
+
return Number.isFinite(due) && until >= now && due <= windowEnd;
|
|
222
227
|
})
|
|
223
228
|
.sort((a, b) => new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime());
|
|
224
229
|
log("INFO", `get_upcoming_due_dates: Retrieved ${upcoming.length} items across ${courses.length} courses` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.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",
|