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.
- package/LICENSE +661 -0
- package/README.md +372 -0
- package/build/api/cache.d.ts +15 -0
- package/build/api/cache.d.ts.map +1 -0
- package/build/api/cache.js +49 -0
- package/build/api/cache.js.map +1 -0
- package/build/api/client.d.ts +109 -0
- package/build/api/client.d.ts.map +1 -0
- package/build/api/client.js +356 -0
- package/build/api/client.js.map +1 -0
- package/build/api/errors.d.ts +23 -0
- package/build/api/errors.d.ts.map +1 -0
- package/build/api/errors.js +47 -0
- package/build/api/errors.js.map +1 -0
- package/build/api/index.d.ts +13 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +17 -0
- package/build/api/index.js.map +1 -0
- package/build/api/rate-limiter.d.ts +17 -0
- package/build/api/rate-limiter.d.ts.map +1 -0
- package/build/api/rate-limiter.js +57 -0
- package/build/api/rate-limiter.js.map +1 -0
- package/build/api/types.d.ts +36 -0
- package/build/api/types.d.ts.map +1 -0
- package/build/api/types.js +16 -0
- package/build/api/types.js.map +1 -0
- package/build/api/version-discovery.d.ts +16 -0
- package/build/api/version-discovery.d.ts.map +1 -0
- package/build/api/version-discovery.js +56 -0
- package/build/api/version-discovery.js.map +1 -0
- package/build/auth/auth-runner.d.ts +26 -0
- package/build/auth/auth-runner.d.ts.map +1 -0
- package/build/auth/auth-runner.js +69 -0
- package/build/auth/auth-runner.js.map +1 -0
- package/build/auth/browser-auth.d.ts +56 -0
- package/build/auth/browser-auth.d.ts.map +1 -0
- package/build/auth/browser-auth.js +496 -0
- package/build/auth/browser-auth.js.map +1 -0
- package/build/auth/index.d.ts +11 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +11 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/purdue-sso.d.ts +32 -0
- package/build/auth/purdue-sso.d.ts.map +1 -0
- package/build/auth/purdue-sso.js +185 -0
- package/build/auth/purdue-sso.js.map +1 -0
- package/build/auth/session-store.d.ts +45 -0
- package/build/auth/session-store.d.ts.map +1 -0
- package/build/auth/session-store.js +152 -0
- package/build/auth/session-store.js.map +1 -0
- package/build/auth/token-manager.d.ts +40 -0
- package/build/auth/token-manager.d.ts.map +1 -0
- package/build/auth/token-manager.js +83 -0
- package/build/auth/token-manager.js.map +1 -0
- package/build/auth-cli.d.ts +10 -0
- package/build/auth-cli.d.ts.map +1 -0
- package/build/auth-cli.js +82 -0
- package/build/auth-cli.js.map +1 -0
- package/build/index.d.ts +10 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +157 -0
- package/build/index.js.map +1 -0
- package/build/setup.d.ts +10 -0
- package/build/setup.d.ts.map +1 -0
- package/build/setup.js +321 -0
- package/build/setup.js.map +1 -0
- package/build/tools/download-file.d.ts +12 -0
- package/build/tools/download-file.d.ts.map +1 -0
- package/build/tools/download-file.js +167 -0
- package/build/tools/download-file.js.map +1 -0
- package/build/tools/get-announcements.d.ts +13 -0
- package/build/tools/get-announcements.d.ts.map +1 -0
- package/build/tools/get-announcements.js +104 -0
- package/build/tools/get-announcements.js.map +1 -0
- package/build/tools/get-assignments.d.ts +13 -0
- package/build/tools/get-assignments.d.ts.map +1 -0
- package/build/tools/get-assignments.js +241 -0
- package/build/tools/get-assignments.js.map +1 -0
- package/build/tools/get-classlist-emails.d.ts +12 -0
- package/build/tools/get-classlist-emails.d.ts.map +1 -0
- package/build/tools/get-classlist-emails.js +48 -0
- package/build/tools/get-classlist-emails.js.map +1 -0
- package/build/tools/get-course-content.d.ts +12 -0
- package/build/tools/get-course-content.d.ts.map +1 -0
- package/build/tools/get-course-content.js +190 -0
- package/build/tools/get-course-content.js.map +1 -0
- package/build/tools/get-discussions.d.ts +12 -0
- package/build/tools/get-discussions.d.ts.map +1 -0
- package/build/tools/get-discussions.js +212 -0
- package/build/tools/get-discussions.js.map +1 -0
- package/build/tools/get-my-courses.d.ts +13 -0
- package/build/tools/get-my-courses.d.ts.map +1 -0
- package/build/tools/get-my-courses.js +51 -0
- package/build/tools/get-my-courses.js.map +1 -0
- package/build/tools/get-my-grades.d.ts +13 -0
- package/build/tools/get-my-grades.d.ts.map +1 -0
- package/build/tools/get-my-grades.js +100 -0
- package/build/tools/get-my-grades.js.map +1 -0
- package/build/tools/get-roster.d.ts +12 -0
- package/build/tools/get-roster.d.ts.map +1 -0
- package/build/tools/get-roster.js +108 -0
- package/build/tools/get-roster.js.map +1 -0
- package/build/tools/get-syllabus.d.ts +12 -0
- package/build/tools/get-syllabus.d.ts.map +1 -0
- package/build/tools/get-syllabus.js +162 -0
- package/build/tools/get-syllabus.js.map +1 -0
- package/build/tools/get-upcoming-due-dates.d.ts +13 -0
- package/build/tools/get-upcoming-due-dates.d.ts.map +1 -0
- package/build/tools/get-upcoming-due-dates.js +74 -0
- package/build/tools/get-upcoming-due-dates.js.map +1 -0
- package/build/tools/index.d.ts +19 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +21 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/schemas.d.ts +66 -0
- package/build/tools/schemas.d.ts.map +1 -0
- package/build/tools/schemas.js +80 -0
- package/build/tools/schemas.js.map +1 -0
- package/build/tools/tool-helpers.d.ts +21 -0
- package/build/tools/tool-helpers.d.ts.map +1 -0
- package/build/tools/tool-helpers.js +70 -0
- package/build/tools/tool-helpers.js.map +1 -0
- package/build/types/index.d.ts +48 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +7 -0
- package/build/types/index.js.map +1 -0
- package/build/update.d.ts +10 -0
- package/build/update.d.ts.map +1 -0
- package/build/update.js +130 -0
- package/build/update.js.map +1 -0
- package/build/utils/config-store.d.ts +23 -0
- package/build/utils/config-store.d.ts.map +1 -0
- package/build/utils/config-store.js +29 -0
- package/build/utils/config-store.js.map +1 -0
- package/build/utils/config.d.ts +9 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +66 -0
- package/build/utils/config.js.map +1 -0
- package/build/utils/course-filter.d.ts +24 -0
- package/build/utils/course-filter.d.ts.map +1 -0
- package/build/utils/course-filter.js +39 -0
- package/build/utils/course-filter.js.map +1 -0
- package/build/utils/download-helpers.d.ts +31 -0
- package/build/utils/download-helpers.d.ts.map +1 -0
- package/build/utils/download-helpers.js +93 -0
- package/build/utils/download-helpers.js.map +1 -0
- package/build/utils/errors.d.ts +21 -0
- package/build/utils/errors.d.ts.map +1 -0
- package/build/utils/errors.js +36 -0
- package/build/utils/errors.js.map +1 -0
- package/build/utils/file-validator.d.ts +56 -0
- package/build/utils/file-validator.d.ts.map +1 -0
- package/build/utils/file-validator.js +134 -0
- package/build/utils/file-validator.js.map +1 -0
- package/build/utils/html-converter.d.ts +17 -0
- package/build/utils/html-converter.d.ts.map +1 -0
- package/build/utils/html-converter.js +36 -0
- package/build/utils/html-converter.js.map +1 -0
- package/build/utils/logger.d.ts +10 -0
- package/build/utils/logger.d.ts.map +1 -0
- package/build/utils/logger.js +42 -0
- package/build/utils/logger.js.map +1 -0
- package/build/utils/pdf-extractor.d.ts +14 -0
- package/build/utils/pdf-extractor.d.ts.map +1 -0
- package/build/utils/pdf-extractor.js +27 -0
- package/build/utils/pdf-extractor.js.map +1 -0
- package/build/utils/update-checker.d.ts +8 -0
- package/build/utils/update-checker.d.ts.map +1 -0
- package/build/utils/update-checker.js +33 -0
- package/build/utils/update-checker.js.map +1 -0
- 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"}
|