brightspace-mcp-server 4.0.5 → 4.0.6

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.
@@ -86,21 +86,23 @@ export function registerGetDropboxSubmissions(server, apiClient) {
86
86
  const truncated = total > limit;
87
87
  const kept = truncated ? rawSubmissions.slice(0, limit) : rawSubmissions;
88
88
  // Optionally fetch feedback for each kept submitter, a bounded
89
- // number at a time. Best-effort: a submitter with no feedback yet
90
- // simply has none in the map.
89
+ // number at a time. A 404 means no feedback yet, so that submitter
90
+ // simply has none in the map. Any other failure leaves their grading
91
+ // status unknown rather than "not graded", and the note names them.
91
92
  const feedbackMap = new Map();
93
+ const feedbackUnread = new Set();
92
94
  if (!ignoreFeedback) {
93
95
  await mapWithConcurrency(kept, FEEDBACK_FETCH_CONCURRENCY, async (sub) => {
96
+ const userId = sub.SubmittedBy.Identifier;
94
97
  try {
95
- const userId = sub.SubmittedBy.Identifier;
96
98
  const feedbackPath = apiClient.le(courseId, `/dropbox/folders/${folderId}/feedback/user/${userId}`);
97
99
  const fb = await apiClient.get(feedbackPath);
98
100
  feedbackMap.set(userId, fb);
99
101
  }
100
- catch {
101
- // No feedback yet, or this submitter's feedback route
102
- // otherwise failed — silently skip, matching the "not
103
- // graded yet" default below.
102
+ catch (error) {
103
+ if (!(error instanceof ApiError && error.status === 404)) {
104
+ feedbackUnread.add(userId);
105
+ }
104
106
  }
105
107
  });
106
108
  }
@@ -125,21 +127,41 @@ export function registerGetDropboxSubmissions(server, apiClient) {
125
127
  comment: sub.Comment?.Text ?? null,
126
128
  };
127
129
  if (!ignoreFeedback) {
128
- entry.feedbackStatus = feedback
129
- ? {
130
- isGraded: feedback.IsGraded,
131
- score: feedback.Score,
132
- maxScore,
133
- hasFeedbackText: !!feedback.Feedback?.Text,
134
- }
135
- : { isGraded: false, score: null, maxScore, hasFeedbackText: false };
130
+ entry.feedbackStatus = feedbackUnread.has(userId)
131
+ ? null
132
+ : feedback
133
+ ? {
134
+ isGraded: feedback.IsGraded,
135
+ score: feedback.Score,
136
+ maxScore,
137
+ hasFeedbackText: !!feedback.Feedback?.Text,
138
+ }
139
+ : { isGraded: false, score: null, maxScore, hasFeedbackText: false };
136
140
  }
137
141
  return entry;
138
142
  });
139
- // activeOnly: skip submissions that are already fully graded.
143
+ // activeOnly: skip submissions that are already fully graded. One
144
+ // whose feedback couldn't be read stays, since it may still need grading.
140
145
  const filtered = activeOnly && !ignoreFeedback
141
146
  ? submissions.filter((s) => !s.feedbackStatus?.isGraded)
142
147
  : submissions;
148
+ const unreadNames = kept
149
+ .filter((sub) => feedbackUnread.has(sub.SubmittedBy.Identifier))
150
+ .map((sub) => `"${sub.SubmittedBy.DisplayName || sub.SubmittedBy.Identifier}"`);
151
+ if (unreadNames.length > 0) {
152
+ log("INFO", "get_dropbox_submissions: Some submitters' feedback could not be read", {
153
+ courseId,
154
+ folderId,
155
+ userIds: [...feedbackUnread],
156
+ });
157
+ }
158
+ const notes = [
159
+ ...(truncated ? [`Showing ${kept.length} of ${total} submissions. Raise the limit argument to see more.`] : []),
160
+ ...(unreadNames.length > 0
161
+ ? [`Couldn't read the feedback for ${unreadNames.join(", ")}, so whether they are graded is unknown ` +
162
+ "(feedbackStatus is null)."]
163
+ : []),
164
+ ];
143
165
  log("INFO", `get_dropbox_submissions: ${filtered.length} submissions (of ${total} total) for folder ${folderId}`, { truncated });
144
166
  return toolResponse({
145
167
  courseId,
@@ -150,9 +172,7 @@ export function registerGetDropboxSubmissions(server, apiClient) {
150
172
  totalSubmissions: total,
151
173
  returnedSubmissions: filtered.length,
152
174
  truncated,
153
- ...(truncated
154
- ? { note: `Showing ${kept.length} of ${total} submissions. Raise the limit argument to see more.` }
155
- : {}),
175
+ ...(notes.length > 0 ? { note: notes.join(" ") } : {}),
156
176
  activeOnly,
157
177
  submissions: filtered,
158
178
  });
@@ -55,14 +55,20 @@ export function registerGetDropboxUserSubmissions(server, apiClient) {
55
55
  note: "No submissions found for this user in the specified folder.",
56
56
  });
57
57
  }
58
+ // A 404 means no feedback yet, matching the "not graded yet" default
59
+ // below. Any other failure leaves the grading status unknown.
58
60
  let feedback = null;
61
+ let feedbackUnread = false;
59
62
  if (!ignoreFeedback) {
60
63
  try {
61
64
  const feedbackPath = apiClient.le(courseId, `/dropbox/folders/${folderId}/feedback/user/${userId}`);
62
65
  feedback = await apiClient.get(feedbackPath);
63
66
  }
64
- catch {
65
- // No feedback yet — silently skip, matching the "not graded yet" default below.
67
+ catch (error) {
68
+ if (!(error instanceof ApiError && error.status === 404)) {
69
+ feedbackUnread = true;
70
+ log("INFO", "get_dropbox_user_submissions: Feedback could not be read", { courseId, folderId, userId });
71
+ }
66
72
  }
67
73
  }
68
74
  const submissions = rawSubmissions.map((sub) => ({
@@ -79,14 +85,16 @@ export function registerGetDropboxUserSubmissions(server, apiClient) {
79
85
  }));
80
86
  const feedbackStatus = ignoreFeedback
81
87
  ? undefined
82
- : feedback
83
- ? {
84
- isGraded: feedback.IsGraded,
85
- score: feedback.Score,
86
- hasFeedbackText: !!feedback.Feedback?.Text,
87
- feedbackText: feedback.Feedback?.Text ?? null,
88
- }
89
- : { isGraded: false, score: null, hasFeedbackText: false, feedbackText: null };
88
+ : feedbackUnread
89
+ ? null
90
+ : feedback
91
+ ? {
92
+ isGraded: feedback.IsGraded,
93
+ score: feedback.Score,
94
+ hasFeedbackText: !!feedback.Feedback?.Text,
95
+ feedbackText: feedback.Feedback?.Text ?? null,
96
+ }
97
+ : { isGraded: false, score: null, hasFeedbackText: false, feedbackText: null };
90
98
  log("INFO", `get_dropbox_user_submissions: ${submissions.length} submissions for user ${userId} in folder ${folderId}`);
91
99
  return toolResponse({
92
100
  courseId,
@@ -94,6 +102,9 @@ export function registerGetDropboxUserSubmissions(server, apiClient) {
94
102
  userId,
95
103
  submissions,
96
104
  feedbackStatus,
105
+ ...(feedbackUnread
106
+ ? { note: "Couldn't read this user's feedback, so whether this submission is graded is unknown (feedbackStatus is null)." }
107
+ : {}),
97
108
  });
98
109
  }
99
110
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "4.0.5",
3
+ "version": "4.0.6",
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",