brightspace-mcp-server 1.0.0

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.
Files changed (171) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +372 -0
  3. package/build/api/cache.d.ts +15 -0
  4. package/build/api/cache.d.ts.map +1 -0
  5. package/build/api/cache.js +49 -0
  6. package/build/api/cache.js.map +1 -0
  7. package/build/api/client.d.ts +109 -0
  8. package/build/api/client.d.ts.map +1 -0
  9. package/build/api/client.js +356 -0
  10. package/build/api/client.js.map +1 -0
  11. package/build/api/errors.d.ts +23 -0
  12. package/build/api/errors.d.ts.map +1 -0
  13. package/build/api/errors.js +47 -0
  14. package/build/api/errors.js.map +1 -0
  15. package/build/api/index.d.ts +13 -0
  16. package/build/api/index.d.ts.map +1 -0
  17. package/build/api/index.js +17 -0
  18. package/build/api/index.js.map +1 -0
  19. package/build/api/rate-limiter.d.ts +17 -0
  20. package/build/api/rate-limiter.d.ts.map +1 -0
  21. package/build/api/rate-limiter.js +57 -0
  22. package/build/api/rate-limiter.js.map +1 -0
  23. package/build/api/types.d.ts +36 -0
  24. package/build/api/types.d.ts.map +1 -0
  25. package/build/api/types.js +16 -0
  26. package/build/api/types.js.map +1 -0
  27. package/build/api/version-discovery.d.ts +16 -0
  28. package/build/api/version-discovery.d.ts.map +1 -0
  29. package/build/api/version-discovery.js +56 -0
  30. package/build/api/version-discovery.js.map +1 -0
  31. package/build/auth/auth-runner.d.ts +26 -0
  32. package/build/auth/auth-runner.d.ts.map +1 -0
  33. package/build/auth/auth-runner.js +69 -0
  34. package/build/auth/auth-runner.js.map +1 -0
  35. package/build/auth/browser-auth.d.ts +56 -0
  36. package/build/auth/browser-auth.d.ts.map +1 -0
  37. package/build/auth/browser-auth.js +496 -0
  38. package/build/auth/browser-auth.js.map +1 -0
  39. package/build/auth/index.d.ts +11 -0
  40. package/build/auth/index.d.ts.map +1 -0
  41. package/build/auth/index.js +11 -0
  42. package/build/auth/index.js.map +1 -0
  43. package/build/auth/purdue-sso.d.ts +32 -0
  44. package/build/auth/purdue-sso.d.ts.map +1 -0
  45. package/build/auth/purdue-sso.js +185 -0
  46. package/build/auth/purdue-sso.js.map +1 -0
  47. package/build/auth/session-store.d.ts +45 -0
  48. package/build/auth/session-store.d.ts.map +1 -0
  49. package/build/auth/session-store.js +152 -0
  50. package/build/auth/session-store.js.map +1 -0
  51. package/build/auth/token-manager.d.ts +40 -0
  52. package/build/auth/token-manager.d.ts.map +1 -0
  53. package/build/auth/token-manager.js +83 -0
  54. package/build/auth/token-manager.js.map +1 -0
  55. package/build/auth-cli.d.ts +10 -0
  56. package/build/auth-cli.d.ts.map +1 -0
  57. package/build/auth-cli.js +82 -0
  58. package/build/auth-cli.js.map +1 -0
  59. package/build/index.d.ts +10 -0
  60. package/build/index.d.ts.map +1 -0
  61. package/build/index.js +157 -0
  62. package/build/index.js.map +1 -0
  63. package/build/setup.d.ts +10 -0
  64. package/build/setup.d.ts.map +1 -0
  65. package/build/setup.js +321 -0
  66. package/build/setup.js.map +1 -0
  67. package/build/tools/download-file.d.ts +12 -0
  68. package/build/tools/download-file.d.ts.map +1 -0
  69. package/build/tools/download-file.js +167 -0
  70. package/build/tools/download-file.js.map +1 -0
  71. package/build/tools/get-announcements.d.ts +13 -0
  72. package/build/tools/get-announcements.d.ts.map +1 -0
  73. package/build/tools/get-announcements.js +104 -0
  74. package/build/tools/get-announcements.js.map +1 -0
  75. package/build/tools/get-assignments.d.ts +13 -0
  76. package/build/tools/get-assignments.d.ts.map +1 -0
  77. package/build/tools/get-assignments.js +241 -0
  78. package/build/tools/get-assignments.js.map +1 -0
  79. package/build/tools/get-classlist-emails.d.ts +12 -0
  80. package/build/tools/get-classlist-emails.d.ts.map +1 -0
  81. package/build/tools/get-classlist-emails.js +48 -0
  82. package/build/tools/get-classlist-emails.js.map +1 -0
  83. package/build/tools/get-course-content.d.ts +12 -0
  84. package/build/tools/get-course-content.d.ts.map +1 -0
  85. package/build/tools/get-course-content.js +190 -0
  86. package/build/tools/get-course-content.js.map +1 -0
  87. package/build/tools/get-discussions.d.ts +12 -0
  88. package/build/tools/get-discussions.d.ts.map +1 -0
  89. package/build/tools/get-discussions.js +212 -0
  90. package/build/tools/get-discussions.js.map +1 -0
  91. package/build/tools/get-my-courses.d.ts +13 -0
  92. package/build/tools/get-my-courses.d.ts.map +1 -0
  93. package/build/tools/get-my-courses.js +51 -0
  94. package/build/tools/get-my-courses.js.map +1 -0
  95. package/build/tools/get-my-grades.d.ts +13 -0
  96. package/build/tools/get-my-grades.d.ts.map +1 -0
  97. package/build/tools/get-my-grades.js +100 -0
  98. package/build/tools/get-my-grades.js.map +1 -0
  99. package/build/tools/get-roster.d.ts +12 -0
  100. package/build/tools/get-roster.d.ts.map +1 -0
  101. package/build/tools/get-roster.js +108 -0
  102. package/build/tools/get-roster.js.map +1 -0
  103. package/build/tools/get-syllabus.d.ts +12 -0
  104. package/build/tools/get-syllabus.d.ts.map +1 -0
  105. package/build/tools/get-syllabus.js +162 -0
  106. package/build/tools/get-syllabus.js.map +1 -0
  107. package/build/tools/get-upcoming-due-dates.d.ts +13 -0
  108. package/build/tools/get-upcoming-due-dates.d.ts.map +1 -0
  109. package/build/tools/get-upcoming-due-dates.js +74 -0
  110. package/build/tools/get-upcoming-due-dates.js.map +1 -0
  111. package/build/tools/index.d.ts +19 -0
  112. package/build/tools/index.d.ts.map +1 -0
  113. package/build/tools/index.js +21 -0
  114. package/build/tools/index.js.map +1 -0
  115. package/build/tools/schemas.d.ts +66 -0
  116. package/build/tools/schemas.d.ts.map +1 -0
  117. package/build/tools/schemas.js +80 -0
  118. package/build/tools/schemas.js.map +1 -0
  119. package/build/tools/tool-helpers.d.ts +21 -0
  120. package/build/tools/tool-helpers.d.ts.map +1 -0
  121. package/build/tools/tool-helpers.js +70 -0
  122. package/build/tools/tool-helpers.js.map +1 -0
  123. package/build/types/index.d.ts +48 -0
  124. package/build/types/index.d.ts.map +1 -0
  125. package/build/types/index.js +7 -0
  126. package/build/types/index.js.map +1 -0
  127. package/build/update.d.ts +10 -0
  128. package/build/update.d.ts.map +1 -0
  129. package/build/update.js +130 -0
  130. package/build/update.js.map +1 -0
  131. package/build/utils/config-store.d.ts +23 -0
  132. package/build/utils/config-store.d.ts.map +1 -0
  133. package/build/utils/config-store.js +29 -0
  134. package/build/utils/config-store.js.map +1 -0
  135. package/build/utils/config.d.ts +9 -0
  136. package/build/utils/config.d.ts.map +1 -0
  137. package/build/utils/config.js +66 -0
  138. package/build/utils/config.js.map +1 -0
  139. package/build/utils/course-filter.d.ts +24 -0
  140. package/build/utils/course-filter.d.ts.map +1 -0
  141. package/build/utils/course-filter.js +39 -0
  142. package/build/utils/course-filter.js.map +1 -0
  143. package/build/utils/download-helpers.d.ts +31 -0
  144. package/build/utils/download-helpers.d.ts.map +1 -0
  145. package/build/utils/download-helpers.js +93 -0
  146. package/build/utils/download-helpers.js.map +1 -0
  147. package/build/utils/errors.d.ts +21 -0
  148. package/build/utils/errors.d.ts.map +1 -0
  149. package/build/utils/errors.js +36 -0
  150. package/build/utils/errors.js.map +1 -0
  151. package/build/utils/file-validator.d.ts +56 -0
  152. package/build/utils/file-validator.d.ts.map +1 -0
  153. package/build/utils/file-validator.js +134 -0
  154. package/build/utils/file-validator.js.map +1 -0
  155. package/build/utils/html-converter.d.ts +17 -0
  156. package/build/utils/html-converter.d.ts.map +1 -0
  157. package/build/utils/html-converter.js +36 -0
  158. package/build/utils/html-converter.js.map +1 -0
  159. package/build/utils/logger.d.ts +10 -0
  160. package/build/utils/logger.d.ts.map +1 -0
  161. package/build/utils/logger.js +42 -0
  162. package/build/utils/logger.js.map +1 -0
  163. package/build/utils/pdf-extractor.d.ts +14 -0
  164. package/build/utils/pdf-extractor.d.ts.map +1 -0
  165. package/build/utils/pdf-extractor.js +27 -0
  166. package/build/utils/pdf-extractor.js.map +1 -0
  167. package/build/utils/update-checker.d.ts +8 -0
  168. package/build/utils/update-checker.d.ts.map +1 -0
  169. package/build/utils/update-checker.js +33 -0
  170. package/build/utils/update-checker.js.map +1 -0
  171. package/package.json +73 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { DownloadFileSchema } from "./schemas.js";
7
+ import { toolResponse, sanitizeError, errorResponse } from "./tool-helpers.js";
8
+ import { log } from "../utils/logger.js";
9
+ import { validateContentId, MAX_FILE_SIZE, } from "../utils/file-validator.js";
10
+ import { secureDownload } from "../utils/download-helpers.js";
11
+ import fs from "node:fs/promises";
12
+ import path from "node:path";
13
+ /**
14
+ * Register download_file tool
15
+ */
16
+ export function registerDownloadFile(server, apiClient) {
17
+ server.registerTool("download_file", {
18
+ title: "Download File",
19
+ description: "Download a file from course content or assignment submissions to a local directory. Use this when the user wants to download, save, or get a file from Brightspace course content or dropbox submissions. IMPORTANT: You MUST ask the user where they want to save the file before calling this tool. Never guess or assume a download directory. After identifying the file to download, suggest a clean readable filename to the user (e.g., 'Lecture 7 - Memory Management.pdf' instead of 'L07_CS251_2026SP_v2.pdf') and ask if they'd like to rename it. Pass their preferred name as customFilename, or omit it to keep the original.",
20
+ inputSchema: DownloadFileSchema,
21
+ }, async (args) => {
22
+ try {
23
+ log("DEBUG", "download_file tool called", { args });
24
+ // Parse and validate input
25
+ const { courseId, topicId, folderId, fileId, downloadPath, customFilename } = DownloadFileSchema.parse(args);
26
+ // Validate courseId
27
+ validateContentId(courseId);
28
+ // Validate download path is absolute
29
+ if (!path.isAbsolute(downloadPath)) {
30
+ return errorResponse("Download path must be an absolute path (e.g., /Users/username/Downloads on Mac or C:\\Users\\username\\Downloads on Windows)");
31
+ }
32
+ // Validate download directory exists and is a directory
33
+ try {
34
+ const stats = await fs.stat(downloadPath);
35
+ if (!stats.isDirectory()) {
36
+ return errorResponse(`Download path is not a directory: ${downloadPath}`);
37
+ }
38
+ }
39
+ catch (error) {
40
+ if (error?.code === "ENOENT") {
41
+ return errorResponse(`Download directory does not exist: ${downloadPath}`);
42
+ }
43
+ throw error;
44
+ }
45
+ // Determine download source
46
+ if (topicId !== undefined) {
47
+ // Content file download
48
+ validateContentId(topicId);
49
+ return await downloadContentFile(apiClient, courseId, topicId, downloadPath, customFilename);
50
+ }
51
+ else if (folderId !== undefined && fileId !== undefined) {
52
+ // Submission file download
53
+ validateContentId(folderId);
54
+ validateContentId(fileId);
55
+ return await downloadSubmissionFile(apiClient, courseId, folderId, fileId, downloadPath, customFilename);
56
+ }
57
+ else {
58
+ return errorResponse("Either topicId (for content files) or both folderId and fileId (for submission files) must be provided");
59
+ }
60
+ }
61
+ catch (error) {
62
+ return sanitizeError(error);
63
+ }
64
+ });
65
+ }
66
+ /**
67
+ * Download a content file using topicId
68
+ */
69
+ async function downloadContentFile(apiClient, courseId, topicId, downloadPath, customFilename) {
70
+ log("INFO", `Downloading content file: courseId=${courseId}, topicId=${topicId}`);
71
+ // Build download URL using D2L API path helper
72
+ const apiPath = apiClient.le(courseId, `/content/topics/${topicId}/file`);
73
+ // Fetch file using getRaw (returns Response object, not parsed JSON)
74
+ const response = await apiClient.getRaw(apiPath);
75
+ // Check Content-Length BEFORE downloading body (prevent memory exhaustion)
76
+ const contentLength = parseInt(response.headers.get("Content-Length") ?? "0", 10);
77
+ if (contentLength > MAX_FILE_SIZE) {
78
+ return errorResponse(`File too large (${Math.round(contentLength / 1024 / 1024)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`);
79
+ }
80
+ // Get filename from Content-Disposition header
81
+ const disposition = response.headers.get("Content-Disposition") ?? "";
82
+ let filename = "download";
83
+ const match = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
84
+ if (match?.[1]) {
85
+ filename = match[1].replace(/['"]/g, "");
86
+ }
87
+ log("DEBUG", `Content-Disposition filename: ${filename}`);
88
+ // Download body as buffer
89
+ const buffer = Buffer.from(await response.arrayBuffer());
90
+ // Double-check actual size
91
+ if (buffer.length > MAX_FILE_SIZE) {
92
+ return errorResponse(`File too large (${Math.round(buffer.length / 1024 / 1024)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`);
93
+ }
94
+ // Use custom filename if provided, otherwise use Content-Disposition filename
95
+ const originalFilename = filename;
96
+ const effectiveFilename = customFilename || filename;
97
+ // Use secureDownload for path traversal prevention, file type validation, and conflict resolution
98
+ const result = await secureDownload({
99
+ targetDir: downloadPath,
100
+ filename: effectiveFilename,
101
+ data: buffer,
102
+ });
103
+ log("INFO", `File downloaded successfully: ${result.path} (${result.size} bytes, ${result.mime})`);
104
+ return toolResponse({
105
+ success: true,
106
+ filePath: result.path,
107
+ fileSize: result.size,
108
+ mimeType: result.mime,
109
+ originalFilename,
110
+ message: `File downloaded successfully to ${result.path}`,
111
+ });
112
+ }
113
+ /**
114
+ * Download a submission/feedback file using folderId + fileId
115
+ */
116
+ async function downloadSubmissionFile(apiClient, courseId, folderId, fileId, downloadPath, customFilename) {
117
+ log("INFO", `Downloading submission file: courseId=${courseId}, folderId=${folderId}, fileId=${fileId}`);
118
+ // D2L API pattern for submission file downloads:
119
+ // GET /d2l/api/le/(version)/(orgUnitId)/dropbox/folders/(folderId)/submissions/mysubmissions/
120
+ // Then find the file by fileId and construct its download URL
121
+ // First, fetch the submission to get file metadata
122
+ const submissionsPath = apiClient.le(courseId, `/dropbox/folders/${folderId}/submissions/mysubmissions/`);
123
+ const submissions = await apiClient.get(submissionsPath);
124
+ if (!submissions || submissions.length === 0) {
125
+ return errorResponse("No submissions found for this assignment. Upload a submission first.");
126
+ }
127
+ // Find the file in the submission
128
+ const submission = submissions[0];
129
+ const file = submission.Files.find((f) => f.FileId === fileId);
130
+ if (!file) {
131
+ return errorResponse(`File ID ${fileId} not found in submission. Available files: ${submission.Files.map((f) => `${f.FileName} (ID: ${f.FileId})`).join(", ")}`);
132
+ }
133
+ // Check file size before downloading
134
+ if (file.Size > MAX_FILE_SIZE) {
135
+ return errorResponse(`File too large (${Math.round(file.Size / 1024 / 1024)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`);
136
+ }
137
+ // D2L file download URL pattern for submission files
138
+ // GET /d2l/api/le/(version)/(orgUnitId)/dropbox/folders/(folderId)/submissions/(submissionId)/files/(fileId)/download
139
+ const downloadApiPath = apiClient.le(courseId, `/dropbox/folders/${folderId}/submissions/${submission.Id}/files/${fileId}/download`);
140
+ // Fetch file
141
+ const response = await apiClient.getRaw(downloadApiPath);
142
+ // Download body as buffer
143
+ const buffer = Buffer.from(await response.arrayBuffer());
144
+ // Double-check actual size
145
+ if (buffer.length > MAX_FILE_SIZE) {
146
+ return errorResponse(`File too large (${Math.round(buffer.length / 1024 / 1024)}MB). Maximum allowed: ${MAX_FILE_SIZE / 1024 / 1024}MB`);
147
+ }
148
+ // Use custom filename if provided, otherwise use original submission filename
149
+ const originalFilename = file.FileName;
150
+ const effectiveFilename = customFilename || file.FileName;
151
+ // Use secureDownload for path traversal prevention, file type validation, and conflict resolution
152
+ const result = await secureDownload({
153
+ targetDir: downloadPath,
154
+ filename: effectiveFilename,
155
+ data: buffer,
156
+ });
157
+ log("INFO", `Submission file downloaded successfully: ${result.path} (${result.size} bytes, ${result.mime})`);
158
+ return toolResponse({
159
+ success: true,
160
+ filePath: result.path,
161
+ fileSize: result.size,
162
+ mimeType: result.mime,
163
+ originalFilename,
164
+ message: `File downloaded successfully to ${result.path}`,
165
+ });
166
+ }
167
+ //# sourceMappingURL=download-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-file.js","sourceRoot":"","sources":["../../src/tools/download-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { D2LApiClient } from "../api/index.js";
8
+ import type { AppConfig } from "../types/index.js";
9
+ /**
10
+ * Register get_announcements tool
11
+ */
12
+ export declare function registerGetAnnouncements(server: McpServer, apiClient: D2LApiClient, config: AppConfig): void;
13
+ //# sourceMappingURL=get-announcements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-announcements.d.ts","sourceRoot":"","sources":["../../src/tools/get-announcements.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAsB,MAAM,iBAAiB,CAAC;AAOnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAuCnD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,YAAY,EACvB,MAAM,EAAE,SAAS,GAChB,IAAI,CAkIN"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { DEFAULT_CACHE_TTLS } from "../api/index.js";
7
+ import { GetAnnouncementsSchema, } from "./schemas.js";
8
+ import { toolResponse, sanitizeError } from "./tool-helpers.js";
9
+ import { log } from "../utils/logger.js";
10
+ import { applyCourseFilter } from "../utils/course-filter.js";
11
+ /**
12
+ * Register get_announcements tool
13
+ */
14
+ export function registerGetAnnouncements(server, apiClient, config) {
15
+ server.registerTool("get_announcements", {
16
+ title: "Get Announcements",
17
+ description: "Fetch recent announcements from your courses. Can filter to a specific course or get announcements across all courses. Use this when the user asks about announcements, news, updates from instructors, recent posts, or what professors said.",
18
+ inputSchema: GetAnnouncementsSchema,
19
+ }, async (args) => {
20
+ try {
21
+ log("DEBUG", "get_announcements tool called", { args });
22
+ // Parse and validate input
23
+ const { courseId, count } = GetAnnouncementsSchema.parse(args);
24
+ // Single course case
25
+ if (courseId) {
26
+ const path = apiClient.le(courseId, "/news/");
27
+ const newsItems = await apiClient.get(path, {
28
+ ttl: DEFAULT_CACHE_TTLS.announcements,
29
+ });
30
+ // Map to clean objects
31
+ const announcements = newsItems
32
+ .map((item) => ({
33
+ id: item.Id,
34
+ title: item.Title,
35
+ body: item.Body.Text,
36
+ createdBy: item.CreatedBy.DisplayName,
37
+ createdDate: item.CreatedDate,
38
+ startDate: item.StartDate,
39
+ isPinned: item.IsPinned,
40
+ }))
41
+ .sort((a, b) => new Date(b.createdDate).getTime() -
42
+ new Date(a.createdDate).getTime())
43
+ .slice(0, count);
44
+ log("INFO", `get_announcements: Retrieved ${announcements.length} announcements for course ${courseId}`);
45
+ return toolResponse(announcements);
46
+ }
47
+ // All courses case
48
+ // First, fetch enrolled courses
49
+ const enrollmentPath = apiClient.lp("/enrollments/myenrollments/?orgUnitTypeId=3&isActive=true");
50
+ const enrollmentResponse = await apiClient.get(enrollmentPath, { ttl: DEFAULT_CACHE_TTLS.enrollments });
51
+ // Apply course filter
52
+ const filteredEnrollments = applyCourseFilter(enrollmentResponse.Items.map(item => ({
53
+ id: item.OrgUnit.Id,
54
+ name: item.OrgUnit.Name,
55
+ code: item.OrgUnit.Code,
56
+ isActive: item.Access.IsActive,
57
+ ...item,
58
+ })), config.courseFilter);
59
+ // Fetch announcements for each course (handle 403s gracefully)
60
+ const announcementPromises = filteredEnrollments.map(async (item) => {
61
+ try {
62
+ const path = apiClient.le(item.OrgUnit.Id, "/news/");
63
+ const newsItems = await apiClient.get(path, {
64
+ ttl: DEFAULT_CACHE_TTLS.announcements,
65
+ });
66
+ return newsItems.map((newsItem) => ({
67
+ id: newsItem.Id,
68
+ title: newsItem.Title,
69
+ body: newsItem.Body.Text,
70
+ createdBy: newsItem.CreatedBy.DisplayName,
71
+ createdDate: newsItem.CreatedDate,
72
+ startDate: newsItem.StartDate,
73
+ isPinned: newsItem.IsPinned,
74
+ courseId: item.OrgUnit.Id,
75
+ courseName: item.OrgUnit.Name,
76
+ }));
77
+ }
78
+ catch (error) {
79
+ // 403 means no access (past course, etc) - log and skip
80
+ if (error?.status === 403) {
81
+ log("DEBUG", `get_announcements: 403 Forbidden for course ${item.OrgUnit.Id} (${item.OrgUnit.Name}) - skipping`);
82
+ return [];
83
+ }
84
+ throw error; // Re-throw other errors
85
+ }
86
+ });
87
+ const results = await Promise.allSettled(announcementPromises);
88
+ const allAnnouncements = results
89
+ .filter((r) => r.status === "fulfilled")
90
+ .flatMap((r) => r.value);
91
+ // Sort by created date and slice to count
92
+ const announcements = allAnnouncements
93
+ .sort((a, b) => new Date(b.createdDate).getTime() -
94
+ new Date(a.createdDate).getTime())
95
+ .slice(0, count);
96
+ log("INFO", `get_announcements: Retrieved ${announcements.length} announcements (out of ${allAnnouncements.length} total across ${enrollmentResponse.Items.length} courses)`);
97
+ return toolResponse(announcements);
98
+ }
99
+ catch (error) {
100
+ return sanitizeError(error);
101
+ }
102
+ });
103
+ }
104
+ //# sourceMappingURL=get-announcements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-announcements.js","sourceRoot":"","sources":["../../src/tools/get-announcements.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { D2LApiClient } from "../api/index.js";
8
+ import type { AppConfig } from "../types/index.js";
9
+ /**
10
+ * Register get_assignments tool
11
+ */
12
+ export declare function registerGetAssignments(server: McpServer, apiClient: D2LApiClient, config: AppConfig): void;
13
+ //# sourceMappingURL=get-assignments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-assignments.d.ts","sourceRoot":"","sources":["../../src/tools/get-assignments.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAsB,MAAM,iBAAiB,CAAC;AAMnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AA4RnD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,YAAY,EACvB,MAAM,EAAE,SAAS,GAChB,IAAI,CA2FN"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { DEFAULT_CACHE_TTLS } from "../api/index.js";
7
+ import { GetAssignmentsSchema } from "./schemas.js";
8
+ import { toolResponse, sanitizeError } from "./tool-helpers.js";
9
+ import { convertHtmlToMarkdown } from "../utils/html-converter.js";
10
+ import { log } from "../utils/logger.js";
11
+ import { applyCourseFilter } from "../utils/course-filter.js";
12
+ /**
13
+ * Fetch assignments (dropbox + quizzes) for a single course
14
+ */
15
+ async function fetchCourseAssignments(apiClient, courseId) {
16
+ const assignments = [];
17
+ // Fetch dropbox folders and quizzes in parallel
18
+ const [dropboxResult, quizResult] = await Promise.allSettled([
19
+ apiClient.get(apiClient.le(courseId, "/dropbox/folders/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
20
+ apiClient.get(apiClient.le(courseId, "/quizzes/"), { ttl: DEFAULT_CACHE_TTLS.assignments }),
21
+ ]);
22
+ // Process Dropbox folders
23
+ if (dropboxResult.status === "fulfilled") {
24
+ // D2L dropbox endpoint may return paged { Objects: [...] } or flat array
25
+ const dropboxRaw = dropboxResult.value;
26
+ const folders = Array.isArray(dropboxRaw) ? dropboxRaw : dropboxRaw.Objects ?? [];
27
+ for (const folder of folders) {
28
+ // Skip hidden folders
29
+ if (folder.IsHidden)
30
+ continue;
31
+ // Fetch submissions for this folder
32
+ let submissions = [];
33
+ try {
34
+ const submissionsRaw = await apiClient.get(apiClient.le(courseId, `/dropbox/folders/${folder.Id}/submissions/mysubmissions/`), { ttl: DEFAULT_CACHE_TTLS.assignments });
35
+ submissions = Array.isArray(submissionsRaw) ? submissionsRaw : submissionsRaw.Objects ?? [];
36
+ }
37
+ catch (error) {
38
+ // 404 means no submissions yet - that's fine
39
+ if (error?.status !== 404) {
40
+ log("DEBUG", `Failed to fetch submissions for folder ${folder.Id}`, error);
41
+ }
42
+ }
43
+ // Fetch feedback if submissions exist
44
+ let feedback = null;
45
+ if (submissions.length > 0) {
46
+ try {
47
+ feedback = await apiClient.get(apiClient.le(courseId, `/dropbox/folders/${folder.Id}/feedback/myFeedback/`), { ttl: DEFAULT_CACHE_TTLS.assignments });
48
+ }
49
+ catch (error) {
50
+ // 404/403 means no feedback available - that's fine
51
+ if (error?.status !== 404 && error?.status !== 403) {
52
+ log("DEBUG", `Failed to fetch feedback for folder ${folder.Id}`, error);
53
+ }
54
+ }
55
+ }
56
+ // Build assignment object
57
+ const assignment = {
58
+ type: "assignment",
59
+ id: folder.Id,
60
+ name: folder.Name,
61
+ instructions: folder.CustomInstructions?.Html
62
+ ? convertHtmlToMarkdown(folder.CustomInstructions.Html)
63
+ : { markdown: "", html: "" },
64
+ dueDate: folder.DueDate,
65
+ points: folder.Assessment?.ScoreDenominator ?? null,
66
+ isGroup: folder.GroupTypeId !== null,
67
+ rubric: folder.Assessment?.Rubrics?.map((r) => ({
68
+ name: r.Name,
69
+ criteria: r.Criteria?.map((c) => ({
70
+ name: c.Name,
71
+ levels: c.Levels?.map((l) => ({
72
+ name: l.Name,
73
+ points: l.Points,
74
+ description: l.Description?.Text ?? null,
75
+ })) ?? [],
76
+ })) ?? [],
77
+ })) ?? null,
78
+ submission: submissions.length > 0
79
+ ? {
80
+ submittedDate: submissions[0].SubmissionDate,
81
+ files: submissions[0].Files?.map((f) => ({
82
+ name: f.FileName,
83
+ size: f.Size,
84
+ fileId: f.FileId,
85
+ })) ?? [],
86
+ comment: submissions[0].Comment?.Text ?? null,
87
+ }
88
+ : null,
89
+ feedback: feedback
90
+ ? {
91
+ score: feedback.Score,
92
+ feedback: feedback.Feedback?.Html
93
+ ? convertHtmlToMarkdown(feedback.Feedback.Html)
94
+ : null,
95
+ }
96
+ : null,
97
+ };
98
+ assignments.push(assignment);
99
+ }
100
+ }
101
+ else {
102
+ // Log dropbox fetch failure but don't throw
103
+ log("DEBUG", `Failed to fetch dropbox folders for course ${courseId}`, dropboxResult.reason);
104
+ }
105
+ // Process Quizzes
106
+ if (quizResult.status === "fulfilled") {
107
+ const quizResponse = quizResult.value;
108
+ // D2L quizzes API returns paged result { Objects: [...] } or a plain array
109
+ const quizzes = Array.isArray(quizResponse)
110
+ ? quizResponse
111
+ : quizResponse?.Objects ?? [];
112
+ for (const quiz of quizzes) {
113
+ // Skip inactive quizzes
114
+ if (!quiz.IsActive)
115
+ continue;
116
+ // Fetch quiz attempts
117
+ let attempts = [];
118
+ try {
119
+ const attemptsRaw = await apiClient.get(apiClient.le(courseId, `/quizzes/${quiz.QuizId}/attempts/`), { ttl: DEFAULT_CACHE_TTLS.assignments });
120
+ // D2L attempts endpoint may return paged { Objects: [...] } or flat array
121
+ attempts = Array.isArray(attemptsRaw) ? attemptsRaw : attemptsRaw.Objects ?? [];
122
+ }
123
+ catch (error) {
124
+ // 404 means no attempts yet - that's fine
125
+ if (error?.status !== 404 && error?.status !== 403) {
126
+ log("DEBUG", `Failed to fetch attempts for quiz ${quiz.QuizId}`, error);
127
+ }
128
+ }
129
+ // Calculate remaining attempts
130
+ const completedAttempts = attempts.filter((a) => a.IsCompleted);
131
+ let attemptsRemaining = "Unlimited";
132
+ let attemptWarning = null;
133
+ if (quiz.AttemptsAllowed && !quiz.AttemptsAllowed.IsUnlimited) {
134
+ const allowed = quiz.AttemptsAllowed.NumberOfAttemptsAllowed ?? 0;
135
+ attemptsRemaining = allowed - completedAttempts.length;
136
+ // Generate warning for low attempts
137
+ if (attemptsRemaining <= 0) {
138
+ attemptWarning = "WARNING: No attempts remaining";
139
+ }
140
+ else if (attemptsRemaining === 1) {
141
+ attemptWarning = "WARNING: Only 1 attempt remaining";
142
+ }
143
+ }
144
+ // Build quiz object
145
+ const quizAssignment = {
146
+ type: "quiz",
147
+ id: quiz.QuizId,
148
+ name: quiz.Name,
149
+ instructions: quiz.Description?.Html
150
+ ? convertHtmlToMarkdown(quiz.Description.Html)
151
+ : { markdown: "", html: "" },
152
+ dueDate: quiz.DueDate,
153
+ startDate: quiz.StartDate,
154
+ endDate: quiz.EndDate,
155
+ timeLimit: quiz.TimeLimit?.IsEnforced ? quiz.TimeLimit.TimeLimitValue : null,
156
+ attemptsAllowed: quiz.AttemptsAllowed?.IsUnlimited
157
+ ? "Unlimited"
158
+ : quiz.AttemptsAllowed?.NumberOfAttemptsAllowed ?? null,
159
+ attemptsUsed: completedAttempts.length,
160
+ attemptsRemaining,
161
+ attemptWarning,
162
+ bestScore: completedAttempts.length > 0
163
+ ? Math.max(...completedAttempts.map((a) => a.Score ?? 0))
164
+ : null,
165
+ };
166
+ assignments.push(quizAssignment);
167
+ }
168
+ }
169
+ else {
170
+ // Log quiz fetch failure but don't throw
171
+ log("DEBUG", `Failed to fetch quizzes for course ${courseId}`, quizResult.reason);
172
+ }
173
+ return assignments;
174
+ }
175
+ /**
176
+ * Register get_assignments tool
177
+ */
178
+ export function registerGetAssignments(server, apiClient, config) {
179
+ server.registerTool("get_assignments", {
180
+ title: "Get Assignments",
181
+ 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.",
182
+ inputSchema: GetAssignmentsSchema,
183
+ }, async (args) => {
184
+ try {
185
+ log("DEBUG", "get_assignments tool called", { args });
186
+ // Parse and validate input
187
+ const { courseId } = GetAssignmentsSchema.parse(args);
188
+ // Single course case
189
+ if (courseId) {
190
+ const assignments = await fetchCourseAssignments(apiClient, courseId);
191
+ log("INFO", `get_assignments: Retrieved ${assignments.length} assignments for course ${courseId}`);
192
+ return toolResponse({ courseId, assignments });
193
+ }
194
+ // All courses case
195
+ // First, fetch enrolled courses
196
+ const enrollmentPath = apiClient.lp("/enrollments/myenrollments/?orgUnitTypeId=3&isActive=true");
197
+ const enrollmentResponse = await apiClient.get(enrollmentPath, { ttl: DEFAULT_CACHE_TTLS.enrollments });
198
+ // Apply course filter
199
+ const filteredEnrollments = applyCourseFilter(enrollmentResponse.Items.map(item => ({
200
+ id: item.OrgUnit.Id,
201
+ name: item.OrgUnit.Name,
202
+ code: item.OrgUnit.Code,
203
+ isActive: item.Access.IsActive,
204
+ ...item,
205
+ })), config.courseFilter);
206
+ // Fetch assignments for each course (handle 403s gracefully)
207
+ const assignmentPromises = filteredEnrollments.map(async (item) => {
208
+ try {
209
+ const assignments = await fetchCourseAssignments(apiClient, item.OrgUnit.Id);
210
+ return {
211
+ courseId: item.OrgUnit.Id,
212
+ courseName: item.OrgUnit.Name,
213
+ assignments,
214
+ };
215
+ }
216
+ catch (error) {
217
+ // 403 means no access (past course, etc) - log and skip
218
+ if (error?.status === 403) {
219
+ log("DEBUG", `get_assignments: 403 Forbidden for course ${item.OrgUnit.Id} (${item.OrgUnit.Name}) - skipping`);
220
+ return null;
221
+ }
222
+ throw error; // Re-throw other errors
223
+ }
224
+ });
225
+ const results = await Promise.allSettled(assignmentPromises);
226
+ const courses = results
227
+ .filter((r) => r.status === "fulfilled" && r.value !== null)
228
+ .map((r) => r.value);
229
+ log("INFO", `get_assignments: Retrieved assignments for ${courses.length} courses (out of ${enrollmentResponse.Items.length} enrolled)`);
230
+ return toolResponse({ courses });
231
+ }
232
+ catch (error) {
233
+ // Temporary: log full error details to stderr for debugging
234
+ if (error instanceof Error) {
235
+ log("ERROR", `get_assignments failed: ${error.message}\n${error.stack}`);
236
+ }
237
+ return sanitizeError(error);
238
+ }
239
+ });
240
+ }
241
+ //# sourceMappingURL=get-assignments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-assignments.js","sourceRoot":"","sources":["../../src/tools/get-assignments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2025 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { D2LApiClient } from "../api/index.js";
8
+ /**
9
+ * Register get_classlist_emails tool
10
+ */
11
+ export declare function registerGetClasslistEmails(server: McpServer, apiClient: D2LApiClient): void;
12
+ //# sourceMappingURL=get-classlist-emails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-classlist-emails.d.ts","sourceRoot":"","sources":["../../src/tools/get-classlist-emails.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAsB,MAAM,iBAAiB,CAAC;AAiBnE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,YAAY,GACtB,IAAI,CA+CN"}