brightspace-mcp-server 3.9.14 → 3.9.15
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-assignments.js +35 -4
- package/build/utils/config.js +20 -2
- package/package.json +1 -1
|
@@ -129,8 +129,14 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
129
129
|
// Skip hidden folders
|
|
130
130
|
if (folder.IsHidden)
|
|
131
131
|
continue;
|
|
132
|
-
// Fetch submissions for this folder
|
|
132
|
+
// Fetch submissions for this folder. Some tenants deny students access
|
|
133
|
+
// to mysubmissions outright (403), in which case an empty list would be
|
|
134
|
+
// indistinguishable from genuinely unsubmitted work — track whether the
|
|
135
|
+
// answer is actually known so the caller never reports denied-but-real
|
|
136
|
+
// work as missing.
|
|
137
|
+
// Adapted from JoshuaMontclair/brightspace-mcp-server (MIT).
|
|
133
138
|
let submissions = [];
|
|
139
|
+
let submissionsKnown = true;
|
|
134
140
|
try {
|
|
135
141
|
const submissionsRaw = await apiClient.get(apiClient.le(courseId, `/dropbox/folders/${folder.Id}/submissions/mysubmissions/`), { ttl: DEFAULT_CACHE_TTLS.assignments });
|
|
136
142
|
submissions = Array.isArray(submissionsRaw) ? submissionsRaw : submissionsRaw.Objects ?? [];
|
|
@@ -138,9 +144,10 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
138
144
|
catch (error) {
|
|
139
145
|
if (isAuthUnavailable(error))
|
|
140
146
|
throw error;
|
|
141
|
-
// 404
|
|
147
|
+
// 404 is how D2L reports "nothing submitted yet" - that one is known.
|
|
142
148
|
if (error?.status !== 404) {
|
|
143
|
-
|
|
149
|
+
submissionsKnown = false;
|
|
150
|
+
log("DEBUG", `Could not determine submission state for folder ${folder.Id}`, error);
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
// Fetch feedback independently of submissions
|
|
@@ -191,6 +198,19 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
191
198
|
})) ?? [],
|
|
192
199
|
})) ?? [],
|
|
193
200
|
})) ?? null,
|
|
201
|
+
// New, additive fields: existing "submission" stays null either way
|
|
202
|
+
// when nothing is known, but submissionStatus tells apart "nothing
|
|
203
|
+
// was submitted" (404, a known answer) from "the answer is denied"
|
|
204
|
+
// (any other error), so a caller never states real work as missing.
|
|
205
|
+
submissionStatus: !submissionsKnown
|
|
206
|
+
? "unknown"
|
|
207
|
+
: submissions.length > 0
|
|
208
|
+
? "submitted"
|
|
209
|
+
: "not_submitted",
|
|
210
|
+
...(submissionsKnown ? {} : {
|
|
211
|
+
submissionStatusNote: "Brightspace did not return this assignment's submission list, so whether it was submitted " +
|
|
212
|
+
"is unknown. Do not report it as missing or unsubmitted - tell the user to check Brightspace directly.",
|
|
213
|
+
}),
|
|
194
214
|
submission: submissions.length > 0
|
|
195
215
|
? {
|
|
196
216
|
submittedDate: submissions[0].SubmissionDate,
|
|
@@ -311,6 +331,16 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
311
331
|
// False when the tenant refused the attempts endpoint, in which case
|
|
312
332
|
// every count below is null rather than a guess of zero.
|
|
313
333
|
attemptsAvailable: completedAttempts !== null,
|
|
334
|
+
// New, additive field mirroring submissionStatus above: "known" is
|
|
335
|
+
// completedAttempts !== null (the same condition attemptsAvailable
|
|
336
|
+
// already tracks), named explicitly so a caller does not have to
|
|
337
|
+
// infer "unknown" from a boolean meant for something else.
|
|
338
|
+
// Adapted from JoshuaMontclair/brightspace-mcp-server (MIT).
|
|
339
|
+
attemptStatus: completedAttempts !== null ? "known" : "unknown",
|
|
340
|
+
...(completedAttempts !== null ? {} : {
|
|
341
|
+
attemptStatusNote: "Brightspace did not provide this quiz's attempt data, so it is unknown whether it was taken. " +
|
|
342
|
+
"Do not report it as not attempted.",
|
|
343
|
+
}),
|
|
314
344
|
attemptsUsed: completedAttempts?.length ?? null,
|
|
315
345
|
attemptsRemaining,
|
|
316
346
|
attemptWarning,
|
|
@@ -375,7 +405,8 @@ function gradebookHeadsUp(raw, fetched, courseId, baseUrl) {
|
|
|
375
405
|
export function registerGetAssignments(server, apiClient, config) {
|
|
376
406
|
server.registerTool("get_assignments", {
|
|
377
407
|
title: "Get Assignments",
|
|
378
|
-
description: "Fetch assignments and quizzes for a specific course or all enrolled courses. Shows dropbox submissions and quizzes with due dates, status, and rubric info. Use this when the user asks about assignments, homework, what to submit, quizzes, or assignment details and rubrics."
|
|
408
|
+
description: "Fetch assignments and quizzes for a specific course or all enrolled courses. Shows dropbox submissions and quizzes with due dates, status, and rubric info. Use this when the user asks about assignments, homework, what to submit, quizzes, or assignment details and rubrics. " +
|
|
409
|
+
"Read submissionStatus/attemptStatus, not just submission/attemptsUsed, to decide whether something was turned in: some tenants deny students access to submission or attempt data, in which case the status is \"unknown\" even though submission is null or attemptsUsed is 0. Never report an \"unknown\" item as missing, unsubmitted, or not attempted - say it could not be verified and point the user to Brightspace.",
|
|
379
410
|
inputSchema: GetAssignmentsSchema,
|
|
380
411
|
}, async (args) => {
|
|
381
412
|
try {
|
package/build/utils/config.js
CHANGED
|
@@ -7,15 +7,33 @@ import * as path from "node:path";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
import dotenv from "dotenv";
|
|
10
|
-
import { configStoreExists, loadConfigStore } from "./config-store.js";
|
|
10
|
+
import { configStoreExists, getConfigStorePath, loadConfigStore } from "./config-store.js";
|
|
11
11
|
import { resolveStoredPassword } from "./secure-config.js";
|
|
12
12
|
import { migrateLegacyState } from "../auth/legacy-state.js";
|
|
13
13
|
export async function loadConfig() {
|
|
14
14
|
dotenv.config({ quiet: true });
|
|
15
|
-
|
|
15
|
+
// A corrupt or permission-denied config.json must not take the whole server
|
|
16
|
+
// down at startup: env vars alone are a complete, if less convenient,
|
|
17
|
+
// configuration. The failure is still surfaced loudly (never silent) so a
|
|
18
|
+
// broken file doesn't masquerade as "no config.json was ever created".
|
|
19
|
+
// Idea from lmgveerhoek's fork (MIT).
|
|
20
|
+
let store = null;
|
|
21
|
+
let storeLoadError;
|
|
22
|
+
if (configStoreExists()) {
|
|
23
|
+
try {
|
|
24
|
+
store = loadConfigStore();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
storeLoadError = error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
if (store) {
|
|
17
31
|
console.error("[config] Loaded base config from ~/.brightspace-mcp/config.json");
|
|
18
32
|
}
|
|
33
|
+
else if (storeLoadError) {
|
|
34
|
+
console.error(`[config] WARN: Failed to read ${getConfigStorePath()} (${storeLoadError instanceof Error ? storeLoadError.message : String(storeLoadError)}); ` +
|
|
35
|
+
"continuing with environment variables only.");
|
|
36
|
+
}
|
|
19
37
|
else {
|
|
20
38
|
console.error("[config] No config.json found, using environment variables");
|
|
21
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.15",
|
|
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",
|