brightspace-mcp-server 3.1.0 → 3.3.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/README.md +1 -0
- package/build/api/cache.js +0 -1
- package/build/api/client.js +0 -1
- package/build/api/errors.js +0 -1
- package/build/api/index.js +0 -1
- package/build/api/paginate.js +0 -1
- package/build/api/rate-limiter.js +0 -1
- package/build/api/retry.js +0 -1
- package/build/api/types.js +0 -1
- package/build/api/version-discovery.js +0 -1
- package/build/auth/auth-cooldown.js +0 -1
- package/build/auth/auth-lock.js +0 -1
- package/build/auth/auth-runner.js +0 -1
- package/build/auth/browser-auth.js +0 -1
- package/build/auth/browser-state-store.js +0 -1
- package/build/auth/credential-store.js +0 -1
- package/build/auth/duo-mfa.js +0 -1
- package/build/auth/encrypted-store.js +0 -1
- package/build/auth/index.js +0 -1
- package/build/auth/legacy-profile.js +0 -1
- package/build/auth/legacy-state.js +0 -1
- package/build/auth/purdue-sso.js +0 -1
- package/build/auth/session-store.js +0 -1
- package/build/auth/sso-flow.js +0 -1
- package/build/auth/suny-sso.js +0 -1
- package/build/auth/token-manager.js +0 -1
- package/build/auth/token-mint.js +0 -1
- package/build/auth/western-sso.js +0 -1
- package/build/auth-cli.js +0 -1
- package/build/index.js +4 -4
- package/build/setup.js +0 -1
- package/build/tools/download-file.js +0 -1
- package/build/tools/get-announcements.js +0 -3
- package/build/tools/get-assignment-files.js +0 -1
- package/build/tools/get-assignments.js +5 -6
- package/build/tools/get-classlist-emails.js +0 -1
- package/build/tools/get-course-content.js +8 -9
- package/build/tools/get-discussions.js +0 -1
- package/build/tools/get-my-courses.js +0 -1
- package/build/tools/get-my-grades.js +0 -1
- package/build/tools/get-roster.js +0 -1
- package/build/tools/get-syllabus.js +1 -2
- package/build/tools/get-upcoming-due-dates.js +0 -1
- package/build/tools/get-video-transcript.js +167 -0
- package/build/tools/index.js +1 -1
- package/build/tools/schemas.js +12 -1
- package/build/tools/tool-helpers.js +1 -2
- package/build/types/index.js +0 -1
- package/build/update.js +0 -1
- package/build/utils/atomic-write.js +0 -1
- package/build/utils/browser-install.js +0 -1
- package/build/utils/commands.js +0 -1
- package/build/utils/config-store.js +0 -1
- package/build/utils/config.js +0 -1
- package/build/utils/course-filter.js +0 -1
- package/build/utils/deep-links.js +0 -1
- package/build/utils/download-errors.js +0 -1
- package/build/utils/download-helpers.js +0 -1
- package/build/utils/errors.js +0 -1
- package/build/utils/file-validator.js +10 -2
- package/build/utils/html-converter.js +16 -11
- package/build/utils/install-sites.js +0 -1
- package/build/utils/logger.js +0 -1
- package/build/utils/mcp-client-cli.js +0 -1
- package/build/utils/modified-since.js +0 -1
- package/build/utils/pdf-extractor.js +0 -1
- package/build/utils/secure-config.js +0 -1
- package/build/utils/self-update.js +0 -1
- package/build/utils/transcript/captions.js +62 -0
- package/build/utils/transcript/errors.js +19 -0
- package/build/utils/transcript/kaltura.js +93 -0
- package/build/utils/transcript/platform.js +97 -0
- package/build/utils/transcript/types.js +6 -0
- package/build/utils/transcript/youtube.js +56 -0
- package/build/utils/update-checker.js +0 -1
- package/build/utils/zip-extract.js +0 -1
- package/package.json +1 -2
- package/build/api/cache.d.ts +0 -15
- package/build/api/cache.d.ts.map +0 -1
- package/build/api/cache.js.map +0 -1
- package/build/api/client.d.ts +0 -141
- package/build/api/client.d.ts.map +0 -1
- package/build/api/client.js.map +0 -1
- package/build/api/errors.d.ts +0 -27
- package/build/api/errors.d.ts.map +0 -1
- package/build/api/errors.js.map +0 -1
- package/build/api/index.d.ts +0 -13
- package/build/api/index.d.ts.map +0 -1
- package/build/api/index.js.map +0 -1
- package/build/api/paginate.d.ts +0 -23
- package/build/api/paginate.d.ts.map +0 -1
- package/build/api/paginate.js.map +0 -1
- package/build/api/rate-limiter.d.ts +0 -17
- package/build/api/rate-limiter.d.ts.map +0 -1
- package/build/api/rate-limiter.js.map +0 -1
- package/build/api/retry.d.ts +0 -42
- package/build/api/retry.d.ts.map +0 -1
- package/build/api/retry.js.map +0 -1
- package/build/api/types.d.ts +0 -39
- package/build/api/types.d.ts.map +0 -1
- package/build/api/types.js.map +0 -1
- package/build/api/version-discovery.d.ts +0 -16
- package/build/api/version-discovery.d.ts.map +0 -1
- package/build/api/version-discovery.js.map +0 -1
- package/build/auth/auth-cooldown.d.ts +0 -24
- package/build/auth/auth-cooldown.d.ts.map +0 -1
- package/build/auth/auth-cooldown.js.map +0 -1
- package/build/auth/auth-lock.d.ts +0 -7
- package/build/auth/auth-lock.d.ts.map +0 -1
- package/build/auth/auth-lock.js.map +0 -1
- package/build/auth/auth-runner.d.ts +0 -61
- package/build/auth/auth-runner.d.ts.map +0 -1
- package/build/auth/auth-runner.js.map +0 -1
- package/build/auth/browser-auth.d.ts +0 -94
- package/build/auth/browser-auth.d.ts.map +0 -1
- package/build/auth/browser-auth.js.map +0 -1
- package/build/auth/browser-state-store.d.ts +0 -19
- package/build/auth/browser-state-store.d.ts.map +0 -1
- package/build/auth/browser-state-store.js.map +0 -1
- package/build/auth/credential-store.d.ts +0 -29
- package/build/auth/credential-store.d.ts.map +0 -1
- package/build/auth/credential-store.js.map +0 -1
- package/build/auth/duo-mfa.d.ts +0 -23
- package/build/auth/duo-mfa.d.ts.map +0 -1
- package/build/auth/duo-mfa.js.map +0 -1
- package/build/auth/encrypted-store.d.ts +0 -25
- package/build/auth/encrypted-store.d.ts.map +0 -1
- package/build/auth/encrypted-store.js.map +0 -1
- package/build/auth/index.d.ts +0 -14
- package/build/auth/index.d.ts.map +0 -1
- package/build/auth/index.js.map +0 -1
- package/build/auth/legacy-profile.d.ts +0 -3
- package/build/auth/legacy-profile.d.ts.map +0 -1
- package/build/auth/legacy-profile.js.map +0 -1
- package/build/auth/legacy-state.d.ts +0 -14
- package/build/auth/legacy-state.d.ts.map +0 -1
- package/build/auth/legacy-state.js.map +0 -1
- package/build/auth/purdue-sso.d.ts +0 -57
- package/build/auth/purdue-sso.d.ts.map +0 -1
- package/build/auth/purdue-sso.js.map +0 -1
- package/build/auth/session-store.d.ts +0 -28
- package/build/auth/session-store.d.ts.map +0 -1
- package/build/auth/session-store.js.map +0 -1
- package/build/auth/sso-flow.d.ts +0 -42
- package/build/auth/sso-flow.d.ts.map +0 -1
- package/build/auth/sso-flow.js.map +0 -1
- package/build/auth/suny-sso.d.ts +0 -53
- package/build/auth/suny-sso.d.ts.map +0 -1
- package/build/auth/suny-sso.js.map +0 -1
- package/build/auth/token-manager.d.ts +0 -74
- package/build/auth/token-manager.d.ts.map +0 -1
- package/build/auth/token-manager.js.map +0 -1
- package/build/auth/token-mint.d.ts +0 -32
- package/build/auth/token-mint.d.ts.map +0 -1
- package/build/auth/token-mint.js.map +0 -1
- package/build/auth/western-sso.d.ts +0 -15
- package/build/auth/western-sso.d.ts.map +0 -1
- package/build/auth/western-sso.js.map +0 -1
- package/build/auth-cli.d.ts +0 -4
- package/build/auth-cli.d.ts.map +0 -1
- package/build/auth-cli.js.map +0 -1
- package/build/index.d.ts +0 -10
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/setup.d.ts +0 -10
- package/build/setup.d.ts.map +0 -1
- package/build/setup.js.map +0 -1
- package/build/tools/download-file.d.ts +0 -16
- package/build/tools/download-file.d.ts.map +0 -1
- package/build/tools/download-file.js.map +0 -1
- package/build/tools/get-announcements.d.ts +0 -77
- package/build/tools/get-announcements.d.ts.map +0 -1
- package/build/tools/get-announcements.js.map +0 -1
- package/build/tools/get-assignment-files.d.ts +0 -12
- package/build/tools/get-assignment-files.d.ts.map +0 -1
- package/build/tools/get-assignment-files.js.map +0 -1
- package/build/tools/get-assignments.d.ts +0 -19
- package/build/tools/get-assignments.d.ts.map +0 -1
- package/build/tools/get-assignments.js.map +0 -1
- package/build/tools/get-classlist-emails.d.ts +0 -12
- package/build/tools/get-classlist-emails.d.ts.map +0 -1
- package/build/tools/get-classlist-emails.js.map +0 -1
- package/build/tools/get-course-content.d.ts +0 -12
- package/build/tools/get-course-content.d.ts.map +0 -1
- package/build/tools/get-course-content.js.map +0 -1
- package/build/tools/get-discussions.d.ts +0 -12
- package/build/tools/get-discussions.d.ts.map +0 -1
- package/build/tools/get-discussions.js.map +0 -1
- package/build/tools/get-my-courses.d.ts +0 -13
- package/build/tools/get-my-courses.d.ts.map +0 -1
- package/build/tools/get-my-courses.js.map +0 -1
- package/build/tools/get-my-grades.d.ts +0 -13
- package/build/tools/get-my-grades.d.ts.map +0 -1
- package/build/tools/get-my-grades.js.map +0 -1
- package/build/tools/get-roster.d.ts +0 -12
- package/build/tools/get-roster.d.ts.map +0 -1
- package/build/tools/get-roster.js.map +0 -1
- package/build/tools/get-syllabus.d.ts +0 -12
- package/build/tools/get-syllabus.d.ts.map +0 -1
- package/build/tools/get-syllabus.js.map +0 -1
- package/build/tools/get-upcoming-due-dates.d.ts +0 -13
- package/build/tools/get-upcoming-due-dates.d.ts.map +0 -1
- package/build/tools/get-upcoming-due-dates.js.map +0 -1
- package/build/tools/index.d.ts +0 -20
- package/build/tools/index.d.ts.map +0 -1
- package/build/tools/index.js.map +0 -1
- package/build/tools/schemas.d.ts +0 -76
- package/build/tools/schemas.d.ts.map +0 -1
- package/build/tools/schemas.js.map +0 -1
- package/build/tools/tool-helpers.d.ts +0 -27
- package/build/tools/tool-helpers.d.ts.map +0 -1
- package/build/tools/tool-helpers.js.map +0 -1
- package/build/types/index.d.ts +0 -63
- package/build/types/index.d.ts.map +0 -1
- package/build/types/index.js.map +0 -1
- package/build/update.d.ts +0 -10
- package/build/update.d.ts.map +0 -1
- package/build/update.js.map +0 -1
- package/build/utils/atomic-write.d.ts +0 -18
- package/build/utils/atomic-write.d.ts.map +0 -1
- package/build/utils/atomic-write.js.map +0 -1
- package/build/utils/browser-install.d.ts +0 -23
- package/build/utils/browser-install.d.ts.map +0 -1
- package/build/utils/browser-install.js.map +0 -1
- package/build/utils/commands.d.ts +0 -26
- package/build/utils/commands.d.ts.map +0 -1
- package/build/utils/commands.js.map +0 -1
- package/build/utils/config-store.d.ts +0 -24
- package/build/utils/config-store.d.ts.map +0 -1
- package/build/utils/config-store.js.map +0 -1
- package/build/utils/config.d.ts +0 -10
- package/build/utils/config.d.ts.map +0 -1
- package/build/utils/config.js.map +0 -1
- package/build/utils/course-filter.d.ts +0 -31
- package/build/utils/course-filter.d.ts.map +0 -1
- package/build/utils/course-filter.js.map +0 -1
- package/build/utils/deep-links.d.ts +0 -17
- package/build/utils/deep-links.d.ts.map +0 -1
- package/build/utils/deep-links.js.map +0 -1
- package/build/utils/download-errors.d.ts +0 -45
- package/build/utils/download-errors.d.ts.map +0 -1
- package/build/utils/download-errors.js.map +0 -1
- package/build/utils/download-helpers.d.ts +0 -31
- package/build/utils/download-helpers.d.ts.map +0 -1
- package/build/utils/download-helpers.js.map +0 -1
- package/build/utils/errors.d.ts +0 -21
- package/build/utils/errors.d.ts.map +0 -1
- package/build/utils/errors.js.map +0 -1
- package/build/utils/file-validator.d.ts +0 -56
- package/build/utils/file-validator.d.ts.map +0 -1
- package/build/utils/file-validator.js.map +0 -1
- package/build/utils/html-converter.d.ts +0 -17
- package/build/utils/html-converter.d.ts.map +0 -1
- package/build/utils/html-converter.js.map +0 -1
- package/build/utils/install-sites.d.ts +0 -60
- package/build/utils/install-sites.d.ts.map +0 -1
- package/build/utils/install-sites.js.map +0 -1
- package/build/utils/logger.d.ts +0 -10
- package/build/utils/logger.d.ts.map +0 -1
- package/build/utils/logger.js.map +0 -1
- package/build/utils/mcp-client-cli.d.ts +0 -12
- package/build/utils/mcp-client-cli.d.ts.map +0 -1
- package/build/utils/mcp-client-cli.js.map +0 -1
- package/build/utils/modified-since.d.ts +0 -19
- package/build/utils/modified-since.d.ts.map +0 -1
- package/build/utils/modified-since.js.map +0 -1
- package/build/utils/pdf-extractor.d.ts +0 -14
- package/build/utils/pdf-extractor.d.ts.map +0 -1
- package/build/utils/pdf-extractor.js.map +0 -1
- package/build/utils/secure-config.d.ts +0 -6
- package/build/utils/secure-config.d.ts.map +0 -1
- package/build/utils/secure-config.js.map +0 -1
- package/build/utils/self-update.d.ts +0 -61
- package/build/utils/self-update.d.ts.map +0 -1
- package/build/utils/self-update.js.map +0 -1
- package/build/utils/update-checker.d.ts +0 -112
- package/build/utils/update-checker.d.ts.map +0 -1
- package/build/utils/update-checker.js.map +0 -1
- package/build/utils/zip-extract.d.ts +0 -15
- package/build/utils/zip-extract.d.ts.map +0 -1
- package/build/utils/zip-extract.js.map +0 -1
package/README.md
CHANGED
|
@@ -116,6 +116,7 @@ npx -y brightspace-mcp-server@latest auth
|
|
|
116
116
|
| Course content | "Find the midterm review slides" · "Download every PDF from Module 5" · "What's new in this course since I last checked?" |
|
|
117
117
|
| Roster | "Who are the TAs for ECE 264?" · "Get me my instructor's email" |
|
|
118
118
|
| Discussions | "What are people saying in the final project thread?" · "Summarize the latest discussion posts" |
|
|
119
|
+
| Video transcripts | "What did the professor say about pinch-off in Tuesday's lecture recording?" · "Summarize last week's BoilerCast video" — works for Kaltura and YouTube embeds; other platforms report that they aren't supported yet |
|
|
119
120
|
| Planning | "Build me a study schedule based on my upcoming due dates" · "Which class needs the most attention right now?" — pulls from assignments, quizzes, and graded discussion topics (any topic with a due date) |
|
|
120
121
|
|
|
121
122
|
|
package/build/api/cache.js
CHANGED
package/build/api/client.js
CHANGED
package/build/api/errors.js
CHANGED
package/build/api/index.js
CHANGED
package/build/api/paginate.js
CHANGED
package/build/api/retry.js
CHANGED
package/build/api/types.js
CHANGED
package/build/auth/auth-lock.js
CHANGED
package/build/auth/duo-mfa.js
CHANGED
package/build/auth/index.js
CHANGED
package/build/auth/purdue-sso.js
CHANGED
package/build/auth/sso-flow.js
CHANGED
package/build/auth/suny-sso.js
CHANGED
package/build/auth/token-mint.js
CHANGED
package/build/auth-cli.js
CHANGED
package/build/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { startUpdateChecks } from "./utils/update-checker.js";
|
|
|
16
16
|
import { readFileSync } from "node:fs";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { dirname, resolve } from "node:path";
|
|
19
|
-
import { registerGetMyCourses, registerGetUpcomingDueDates, registerGetMyGrades, registerGetAnnouncements, registerGetAssignments, registerGetAssignmentFiles, registerGetCourseContent, registerDownloadFile, registerGetClasslistEmails, registerGetRoster, registerGetSyllabus, registerGetDiscussions, } from "./tools/index.js";
|
|
19
|
+
import { registerGetMyCourses, registerGetUpcomingDueDates, registerGetMyGrades, registerGetAnnouncements, registerGetAssignments, registerGetAssignmentFiles, registerGetCourseContent, registerDownloadFile, registerGetClasslistEmails, registerGetRoster, registerGetSyllabus, registerGetDiscussions, registerGetVideoTranscript, } from "./tools/index.js";
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
21
|
const __dirname = dirname(__filename);
|
|
22
22
|
const PKG_VERSION = (() => {
|
|
@@ -112,11 +112,12 @@ else {
|
|
|
112
112
|
registerGetRoster(server, apiClient);
|
|
113
113
|
registerGetSyllabus(server, apiClient);
|
|
114
114
|
registerGetDiscussions(server, apiClient);
|
|
115
|
-
|
|
115
|
+
registerGetVideoTranscript(server, apiClient);
|
|
116
|
+
log("DEBUG", "MCP tools registered (13 tools)");
|
|
116
117
|
// Connect stdio transport
|
|
117
118
|
const transport = new StdioServerTransport();
|
|
118
119
|
await server.connect(transport);
|
|
119
|
-
log("INFO", "Brightspace MCP Server by Rohan Muppa — running on stdio (
|
|
120
|
+
log("INFO", "Brightspace MCP Server by Rohan Muppa — running on stdio (13 tools registered)");
|
|
120
121
|
log("INFO", "Setup: see README.md for MCP client configuration (Claude Desktop, ChatGPT Desktop, Cursor, etc.)");
|
|
121
122
|
}
|
|
122
123
|
catch (error) {
|
|
@@ -135,4 +136,3 @@ else {
|
|
|
135
136
|
});
|
|
136
137
|
main();
|
|
137
138
|
}
|
|
138
|
-
//# sourceMappingURL=index.js.map
|
package/build/setup.js
CHANGED
|
@@ -60,8 +60,6 @@ export function mapNewsItem(item) {
|
|
|
60
60
|
title: item.Title,
|
|
61
61
|
body: item.Body?.Text ?? "",
|
|
62
62
|
createdBy: item.CreatedBy?.DisplayName ?? "Unknown",
|
|
63
|
-
createdDate: item.CreatedDate,
|
|
64
|
-
startDate: item.StartDate,
|
|
65
63
|
date: effectiveDate(item),
|
|
66
64
|
isPinned: item.IsPinned,
|
|
67
65
|
lastModified: item.LastModifiedDate ?? null,
|
|
@@ -166,4 +164,3 @@ export function registerGetAnnouncements(server, apiClient, config) {
|
|
|
166
164
|
}
|
|
167
165
|
});
|
|
168
166
|
}
|
|
169
|
-
//# sourceMappingURL=get-announcements.js.map
|
|
@@ -147,8 +147,8 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
147
147
|
name: folder.Name,
|
|
148
148
|
url: baseUrl ? assignmentUrl(baseUrl, courseId, folder.Id) : null,
|
|
149
149
|
instructions: folder.CustomInstructions?.Html
|
|
150
|
-
? convertHtmlToMarkdown(folder.CustomInstructions.Html)
|
|
151
|
-
:
|
|
150
|
+
? convertHtmlToMarkdown(folder.CustomInstructions.Html).markdown
|
|
151
|
+
: "",
|
|
152
152
|
dueDate: folder.DueDate,
|
|
153
153
|
points: folder.Assessment?.ScoreDenominator ?? null,
|
|
154
154
|
isGroup: folder.GroupTypeId !== null,
|
|
@@ -178,7 +178,7 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
178
178
|
? {
|
|
179
179
|
score: feedback.Score,
|
|
180
180
|
feedback: feedback.Feedback?.Html
|
|
181
|
-
? convertHtmlToMarkdown(feedback.Feedback.Html)
|
|
181
|
+
? convertHtmlToMarkdown(feedback.Feedback.Html).markdown
|
|
182
182
|
: null,
|
|
183
183
|
}
|
|
184
184
|
: null,
|
|
@@ -268,8 +268,8 @@ export async function fetchCourseAssignments(apiClient, courseId, baseUrl) {
|
|
|
268
268
|
name: quiz.Name,
|
|
269
269
|
url: baseUrl ? quizUrl(baseUrl, courseId, quiz.QuizId) : null,
|
|
270
270
|
instructions: descriptionHtml
|
|
271
|
-
? convertHtmlToMarkdown(descriptionHtml)
|
|
272
|
-
:
|
|
271
|
+
? convertHtmlToMarkdown(descriptionHtml).markdown
|
|
272
|
+
: "",
|
|
273
273
|
dueDate: quiz.DueDate,
|
|
274
274
|
startDate: quiz.StartDate,
|
|
275
275
|
endDate: quiz.EndDate,
|
|
@@ -404,4 +404,3 @@ export function registerGetAssignments(server, apiClient, config) {
|
|
|
404
404
|
}
|
|
405
405
|
});
|
|
406
406
|
}
|
|
407
|
-
//# sourceMappingURL=get-assignments.js.map
|
|
@@ -69,9 +69,9 @@ async function buildContentTree(apiClient, courseId, modules, progressMap, typeF
|
|
|
69
69
|
id: item.Id,
|
|
70
70
|
title: item.Title,
|
|
71
71
|
description: item.Description?.Text ?? null,
|
|
72
|
-
dueDate: item.ModuleDueDate
|
|
73
|
-
isHidden: item.IsHidden,
|
|
74
|
-
isLocked: item.IsLocked,
|
|
72
|
+
...(item.ModuleDueDate ? { dueDate: item.ModuleDueDate } : {}),
|
|
73
|
+
...(item.IsHidden ? { isHidden: item.IsHidden } : {}),
|
|
74
|
+
...(item.IsLocked ? { isLocked: item.IsLocked } : {}),
|
|
75
75
|
lastModified: item.LastModifiedDate ?? null,
|
|
76
76
|
children: processedChildren,
|
|
77
77
|
});
|
|
@@ -90,12 +90,12 @@ async function buildContentTree(apiClient, courseId, modules, progressMap, typeF
|
|
|
90
90
|
topicType,
|
|
91
91
|
id: item.Id,
|
|
92
92
|
title: item.Title,
|
|
93
|
-
isHidden: item.IsHidden,
|
|
94
|
-
isLocked: item.IsLocked,
|
|
95
|
-
dueDate: item.DueDate
|
|
93
|
+
...(item.IsHidden ? { isHidden: item.IsHidden } : {}),
|
|
94
|
+
...(item.IsLocked ? { isLocked: item.IsLocked } : {}),
|
|
95
|
+
...(item.DueDate ? { dueDate: item.DueDate } : {}),
|
|
96
96
|
lastModified: item.LastModifiedDate ?? null,
|
|
97
97
|
isCompleted: topicProgress?.IsRead ?? false,
|
|
98
|
-
completedDate: topicProgress
|
|
98
|
+
...(topicProgress?.DateCompleted ? { completedDate: topicProgress.DateCompleted } : {}),
|
|
99
99
|
};
|
|
100
100
|
// Add type-specific content
|
|
101
101
|
if (item.TopicType === 1) {
|
|
@@ -109,7 +109,7 @@ async function buildContentTree(apiClient, courseId, modules, progressMap, typeF
|
|
|
109
109
|
}
|
|
110
110
|
// HTML content — include body converted to markdown
|
|
111
111
|
if (item.Description?.Html) {
|
|
112
|
-
topic.content = convertHtmlToMarkdown(item.Description.Html);
|
|
112
|
+
topic.content = convertHtmlToMarkdown(item.Description.Html).markdown;
|
|
113
113
|
}
|
|
114
114
|
tree.push(topic);
|
|
115
115
|
}
|
|
@@ -237,4 +237,3 @@ export function registerGetCourseContent(server, apiClient) {
|
|
|
237
237
|
}
|
|
238
238
|
});
|
|
239
239
|
}
|
|
240
|
-
//# sourceMappingURL=get-course-content.js.map
|
|
@@ -61,7 +61,7 @@ export function registerGetSyllabus(server, apiClient) {
|
|
|
61
61
|
}
|
|
62
62
|
// Convert description HTML to markdown
|
|
63
63
|
const description = overview?.Description?.Html
|
|
64
|
-
? convertHtmlToMarkdown(overview.Description.Html)
|
|
64
|
+
? convertHtmlToMarkdown(overview.Description.Html).markdown
|
|
65
65
|
: null;
|
|
66
66
|
// Always attempt to fetch the attachment so we can extract PDF text
|
|
67
67
|
let attachmentBuffer = null;
|
|
@@ -159,4 +159,3 @@ export function registerGetSyllabus(server, apiClient) {
|
|
|
159
159
|
}
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
-
//# sourceMappingURL=get-syllabus.js.map
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purdue Brightspace MCP Server
|
|
3
|
+
* Copyright (c) 2026 Rohan Muppa. All rights reserved.
|
|
4
|
+
* Licensed under MIT — see LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
import { DEFAULT_CACHE_TTLS } from "../api/index.js";
|
|
7
|
+
import { GetVideoTranscriptSchema } from "./schemas.js";
|
|
8
|
+
import { toolResponse, errorResponse, sanitizeError } from "./tool-helpers.js";
|
|
9
|
+
import { cuesToText, paginateText } from "../utils/transcript/captions.js";
|
|
10
|
+
import { detectVideoPlatform, extractKalturaIds, extractYouTubeVideoId, } from "../utils/transcript/platform.js";
|
|
11
|
+
import { getKalturaTranscript } from "../utils/transcript/kaltura.js";
|
|
12
|
+
import { getYouTubeTranscript } from "../utils/transcript/youtube.js";
|
|
13
|
+
import { NoTranscriptError, TranscriptFetchError } from "../utils/transcript/errors.js";
|
|
14
|
+
import { log } from "../utils/logger.js";
|
|
15
|
+
const UNSUPPORTED_PLATFORM_LABEL = {
|
|
16
|
+
panopto: "Panopto",
|
|
17
|
+
yuja: "YuJa",
|
|
18
|
+
echo360: "Echo360",
|
|
19
|
+
vimeo: "Vimeo",
|
|
20
|
+
};
|
|
21
|
+
async function resolveVideoUrl(apiClient, courseId, topicId) {
|
|
22
|
+
const topic = await apiClient.get(apiClient.le(courseId, `/content/topics/${topicId}`), { ttl: DEFAULT_CACHE_TTLS.courseContent });
|
|
23
|
+
if (!topic.Url) {
|
|
24
|
+
return {
|
|
25
|
+
error: `Content topic "${topic.Title}" (id ${topicId}) has no URL Brightspace can resolve to a video. ` +
|
|
26
|
+
"This is common for audio or embedded objects with no direct link. Open it in Brightspace directly.",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { url: topic.Url };
|
|
30
|
+
}
|
|
31
|
+
async function fetchTranscript(platform, videoUrl, fetchImpl) {
|
|
32
|
+
switch (platform) {
|
|
33
|
+
case "kaltura": {
|
|
34
|
+
const ids = extractKalturaIds(videoUrl);
|
|
35
|
+
if (!ids) {
|
|
36
|
+
return {
|
|
37
|
+
failed: `Could not find a Kaltura entry ID and partner ID in this URL: ${videoUrl}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return { result: await getKalturaTranscript(ids.partnerId, ids.entryId, fetchImpl) };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof NoTranscriptError)
|
|
45
|
+
return { noTranscript: error.message };
|
|
46
|
+
if (error instanceof TranscriptFetchError) {
|
|
47
|
+
return {
|
|
48
|
+
failed: `${error.message} This can happen when the video needs an authenticated Kaltura ` +
|
|
49
|
+
"session rather than an anonymous one. Open it in Brightspace directly.",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
case "youtube": {
|
|
56
|
+
const videoId = extractYouTubeVideoId(videoUrl);
|
|
57
|
+
if (!videoId) {
|
|
58
|
+
return { failed: `Could not find a YouTube video ID in this URL: ${videoUrl}` };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return { result: await getYouTubeTranscript(videoId, fetchImpl) };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof NoTranscriptError)
|
|
65
|
+
return { noTranscript: error.message };
|
|
66
|
+
if (error instanceof TranscriptFetchError)
|
|
67
|
+
return { failed: error.message };
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
case "panopto":
|
|
72
|
+
case "yuja":
|
|
73
|
+
case "echo360":
|
|
74
|
+
case "vimeo":
|
|
75
|
+
return {
|
|
76
|
+
unsupported: `Video transcripts from ${UNSUPPORTED_PLATFORM_LABEL[platform]} are not supported yet. ` +
|
|
77
|
+
"Open the video in Brightspace directly.",
|
|
78
|
+
};
|
|
79
|
+
default:
|
|
80
|
+
return {
|
|
81
|
+
unsupported: `Could not identify a supported video platform for this URL: ${videoUrl}. ` +
|
|
82
|
+
"Open the video in Brightspace directly.",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Register get_video_transcript tool
|
|
88
|
+
*/
|
|
89
|
+
export function registerGetVideoTranscript(server, apiClient, fetchImpl = fetch) {
|
|
90
|
+
server.registerTool("get_video_transcript", {
|
|
91
|
+
title: "Get Video Transcript",
|
|
92
|
+
description: "Read the transcript of a video embedded in course content, such as a recorded lecture or explainer clip. " +
|
|
93
|
+
"Call it with courseId and topicId from get_course_content (typeFilter: 'video' or 'other'), or with videoUrl " +
|
|
94
|
+
"directly if you already have the link. Returns transcript text with timestamps, plus title and duration when " +
|
|
95
|
+
"available. Currently supports Kaltura (e.g. BoilerCast) and YouTube; other platforms return a clear message " +
|
|
96
|
+
"naming what isn't supported yet. Use offset/maxChars to page through a long transcript. Read only — this " +
|
|
97
|
+
"never marks the video as watched.",
|
|
98
|
+
inputSchema: GetVideoTranscriptSchema,
|
|
99
|
+
}, async (args) => {
|
|
100
|
+
try {
|
|
101
|
+
log("DEBUG", "get_video_transcript tool called", { args });
|
|
102
|
+
const { courseId, topicId, videoUrl, offset, maxChars } = GetVideoTranscriptSchema.parse(args);
|
|
103
|
+
let resolvedUrl;
|
|
104
|
+
if (videoUrl) {
|
|
105
|
+
resolvedUrl = videoUrl;
|
|
106
|
+
}
|
|
107
|
+
else if (courseId !== undefined && topicId !== undefined) {
|
|
108
|
+
const resolved = await resolveVideoUrl(apiClient, courseId, topicId);
|
|
109
|
+
if ("error" in resolved) {
|
|
110
|
+
return toolResponse({ courseId, topicId, hasTranscript: false, message: resolved.error });
|
|
111
|
+
}
|
|
112
|
+
resolvedUrl = resolved.url;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return errorResponse("Provide either videoUrl, or both courseId and topicId to look up the video from course content.");
|
|
116
|
+
}
|
|
117
|
+
const platform = detectVideoPlatform(resolvedUrl);
|
|
118
|
+
const outcome = await fetchTranscript(platform, resolvedUrl, fetchImpl);
|
|
119
|
+
if ("unsupported" in outcome) {
|
|
120
|
+
return toolResponse({
|
|
121
|
+
courseId,
|
|
122
|
+
topicId,
|
|
123
|
+
videoUrl: resolvedUrl,
|
|
124
|
+
platform,
|
|
125
|
+
hasTranscript: false,
|
|
126
|
+
message: outcome.unsupported,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if ("noTranscript" in outcome) {
|
|
130
|
+
return toolResponse({
|
|
131
|
+
courseId,
|
|
132
|
+
topicId,
|
|
133
|
+
videoUrl: resolvedUrl,
|
|
134
|
+
platform,
|
|
135
|
+
hasTranscript: false,
|
|
136
|
+
message: outcome.noTranscript,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if ("failed" in outcome) {
|
|
140
|
+
return errorResponse(outcome.failed);
|
|
141
|
+
}
|
|
142
|
+
const { result } = outcome;
|
|
143
|
+
const fullText = cuesToText(result.cues);
|
|
144
|
+
const { window, truncated, nextOffset, totalChars } = paginateText(fullText, offset, maxChars);
|
|
145
|
+
log("INFO", `get_video_transcript: ${platform} transcript for ${resolvedUrl} (${result.cues.length} cues, ${totalChars} chars)`);
|
|
146
|
+
return toolResponse({
|
|
147
|
+
courseId,
|
|
148
|
+
topicId,
|
|
149
|
+
videoUrl: resolvedUrl,
|
|
150
|
+
platform,
|
|
151
|
+
hasTranscript: true,
|
|
152
|
+
title: result.title,
|
|
153
|
+
durationSeconds: result.durationSeconds,
|
|
154
|
+
language: result.language,
|
|
155
|
+
captionFormat: result.format,
|
|
156
|
+
cueCount: result.cues.length,
|
|
157
|
+
transcript: window,
|
|
158
|
+
truncated,
|
|
159
|
+
nextOffset,
|
|
160
|
+
totalChars,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return sanitizeError(error);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
package/build/tools/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { registerGetClasslistEmails } from "./get-classlist-emails.js";
|
|
|
16
16
|
export { registerGetRoster } from "./get-roster.js";
|
|
17
17
|
export { registerGetSyllabus } from "./get-syllabus.js";
|
|
18
18
|
export { registerGetDiscussions } from "./get-discussions.js";
|
|
19
|
+
export { registerGetVideoTranscript } from "./get-video-transcript.js";
|
|
19
20
|
// Re-export shared helpers and schemas for convenience
|
|
20
21
|
export { toolResponse, errorResponse, sanitizeError } from "./tool-helpers.js";
|
|
21
22
|
export * from "./schemas.js";
|
|
22
|
-
//# sourceMappingURL=index.js.map
|
package/build/tools/schemas.js
CHANGED
|
@@ -91,6 +91,18 @@ export const GetAssignmentFilesSchema = z.object({
|
|
|
91
91
|
maxChars: z.coerce.number().int().positive().max(100000).default(12000)
|
|
92
92
|
.describe("Maximum characters of extracted text to return. The response reports whether it was truncated."),
|
|
93
93
|
});
|
|
94
|
+
export const GetVideoTranscriptSchema = z.object({
|
|
95
|
+
courseId: z.coerce.number().int().positive().optional()
|
|
96
|
+
.describe("Course ID the video belongs to. Required together with topicId unless videoUrl is given directly."),
|
|
97
|
+
topicId: z.coerce.number().int().positive().optional()
|
|
98
|
+
.describe("Content topic ID (from get_course_content) whose embedded video to transcribe. Requires courseId."),
|
|
99
|
+
videoUrl: z.string().url().optional()
|
|
100
|
+
.describe("Direct video URL to transcribe, e.g. the url field get_course_content already returned. Use instead of courseId/topicId when you already have the link."),
|
|
101
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
102
|
+
.describe("Character offset into the transcript to resume from. Pass back nextOffset from a truncated response to fetch the next piece."),
|
|
103
|
+
maxChars: z.coerce.number().int().positive().max(100000).default(12000)
|
|
104
|
+
.describe("Maximum characters of transcript text to return in one call. The response reports whether it was truncated."),
|
|
105
|
+
});
|
|
94
106
|
export const GetRosterSchema = z.object({
|
|
95
107
|
courseId: z.coerce.number().int().positive()
|
|
96
108
|
.describe("Course ID to get roster for."),
|
|
@@ -101,4 +113,3 @@ export const GetRosterSchema = z.object({
|
|
|
101
113
|
limit: z.coerce.number().int().positive().max(1000).default(100)
|
|
102
114
|
.describe("Maximum users to return. Default 100. The response reports the true total and whether it was truncated."),
|
|
103
115
|
});
|
|
104
|
-
//# sourceMappingURL=schemas.js.map
|
|
@@ -24,7 +24,7 @@ export function toolResponse(data) {
|
|
|
24
24
|
const content = [
|
|
25
25
|
{
|
|
26
26
|
type: "text",
|
|
27
|
-
text: JSON.stringify(data
|
|
27
|
+
text: JSON.stringify(data),
|
|
28
28
|
},
|
|
29
29
|
];
|
|
30
30
|
const notice = getUpdateNotice();
|
|
@@ -166,4 +166,3 @@ export function sanitizeError(error) {
|
|
|
166
166
|
// Default fallback
|
|
167
167
|
return errorResponse("An unexpected error occurred. Please try again.");
|
|
168
168
|
}
|
|
169
|
-
//# sourceMappingURL=tool-helpers.js.map
|
package/build/types/index.js
CHANGED
package/build/update.js
CHANGED
package/build/utils/commands.js
CHANGED
|
@@ -23,4 +23,3 @@ export const SETUP_COMMAND = `npx -y ${PACKAGE_NAME}@latest setup`;
|
|
|
23
23
|
export const GLOBAL_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}@latest`;
|
|
24
24
|
/** Drop cached npx copies so the next `@latest` genuinely refetches. */
|
|
25
25
|
export const CLEAR_NPX_CACHE_COMMAND = "npx clear-npx-cache";
|
|
26
|
-
//# sourceMappingURL=commands.js.map
|
package/build/utils/config.js
CHANGED