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.
Files changed (281) hide show
  1. package/README.md +1 -0
  2. package/build/api/cache.js +0 -1
  3. package/build/api/client.js +0 -1
  4. package/build/api/errors.js +0 -1
  5. package/build/api/index.js +0 -1
  6. package/build/api/paginate.js +0 -1
  7. package/build/api/rate-limiter.js +0 -1
  8. package/build/api/retry.js +0 -1
  9. package/build/api/types.js +0 -1
  10. package/build/api/version-discovery.js +0 -1
  11. package/build/auth/auth-cooldown.js +0 -1
  12. package/build/auth/auth-lock.js +0 -1
  13. package/build/auth/auth-runner.js +0 -1
  14. package/build/auth/browser-auth.js +0 -1
  15. package/build/auth/browser-state-store.js +0 -1
  16. package/build/auth/credential-store.js +0 -1
  17. package/build/auth/duo-mfa.js +0 -1
  18. package/build/auth/encrypted-store.js +0 -1
  19. package/build/auth/index.js +0 -1
  20. package/build/auth/legacy-profile.js +0 -1
  21. package/build/auth/legacy-state.js +0 -1
  22. package/build/auth/purdue-sso.js +0 -1
  23. package/build/auth/session-store.js +0 -1
  24. package/build/auth/sso-flow.js +0 -1
  25. package/build/auth/suny-sso.js +0 -1
  26. package/build/auth/token-manager.js +0 -1
  27. package/build/auth/token-mint.js +0 -1
  28. package/build/auth/western-sso.js +0 -1
  29. package/build/auth-cli.js +0 -1
  30. package/build/index.js +4 -4
  31. package/build/setup.js +0 -1
  32. package/build/tools/download-file.js +0 -1
  33. package/build/tools/get-announcements.js +0 -3
  34. package/build/tools/get-assignment-files.js +0 -1
  35. package/build/tools/get-assignments.js +5 -6
  36. package/build/tools/get-classlist-emails.js +0 -1
  37. package/build/tools/get-course-content.js +8 -9
  38. package/build/tools/get-discussions.js +0 -1
  39. package/build/tools/get-my-courses.js +0 -1
  40. package/build/tools/get-my-grades.js +0 -1
  41. package/build/tools/get-roster.js +0 -1
  42. package/build/tools/get-syllabus.js +1 -2
  43. package/build/tools/get-upcoming-due-dates.js +0 -1
  44. package/build/tools/get-video-transcript.js +167 -0
  45. package/build/tools/index.js +1 -1
  46. package/build/tools/schemas.js +12 -1
  47. package/build/tools/tool-helpers.js +1 -2
  48. package/build/types/index.js +0 -1
  49. package/build/update.js +0 -1
  50. package/build/utils/atomic-write.js +0 -1
  51. package/build/utils/browser-install.js +0 -1
  52. package/build/utils/commands.js +0 -1
  53. package/build/utils/config-store.js +0 -1
  54. package/build/utils/config.js +0 -1
  55. package/build/utils/course-filter.js +0 -1
  56. package/build/utils/deep-links.js +0 -1
  57. package/build/utils/download-errors.js +0 -1
  58. package/build/utils/download-helpers.js +0 -1
  59. package/build/utils/errors.js +0 -1
  60. package/build/utils/file-validator.js +10 -2
  61. package/build/utils/html-converter.js +16 -11
  62. package/build/utils/install-sites.js +0 -1
  63. package/build/utils/logger.js +0 -1
  64. package/build/utils/mcp-client-cli.js +0 -1
  65. package/build/utils/modified-since.js +0 -1
  66. package/build/utils/pdf-extractor.js +0 -1
  67. package/build/utils/secure-config.js +0 -1
  68. package/build/utils/self-update.js +0 -1
  69. package/build/utils/transcript/captions.js +62 -0
  70. package/build/utils/transcript/errors.js +19 -0
  71. package/build/utils/transcript/kaltura.js +93 -0
  72. package/build/utils/transcript/platform.js +97 -0
  73. package/build/utils/transcript/types.js +6 -0
  74. package/build/utils/transcript/youtube.js +56 -0
  75. package/build/utils/update-checker.js +0 -1
  76. package/build/utils/zip-extract.js +0 -1
  77. package/package.json +1 -2
  78. package/build/api/cache.d.ts +0 -15
  79. package/build/api/cache.d.ts.map +0 -1
  80. package/build/api/cache.js.map +0 -1
  81. package/build/api/client.d.ts +0 -141
  82. package/build/api/client.d.ts.map +0 -1
  83. package/build/api/client.js.map +0 -1
  84. package/build/api/errors.d.ts +0 -27
  85. package/build/api/errors.d.ts.map +0 -1
  86. package/build/api/errors.js.map +0 -1
  87. package/build/api/index.d.ts +0 -13
  88. package/build/api/index.d.ts.map +0 -1
  89. package/build/api/index.js.map +0 -1
  90. package/build/api/paginate.d.ts +0 -23
  91. package/build/api/paginate.d.ts.map +0 -1
  92. package/build/api/paginate.js.map +0 -1
  93. package/build/api/rate-limiter.d.ts +0 -17
  94. package/build/api/rate-limiter.d.ts.map +0 -1
  95. package/build/api/rate-limiter.js.map +0 -1
  96. package/build/api/retry.d.ts +0 -42
  97. package/build/api/retry.d.ts.map +0 -1
  98. package/build/api/retry.js.map +0 -1
  99. package/build/api/types.d.ts +0 -39
  100. package/build/api/types.d.ts.map +0 -1
  101. package/build/api/types.js.map +0 -1
  102. package/build/api/version-discovery.d.ts +0 -16
  103. package/build/api/version-discovery.d.ts.map +0 -1
  104. package/build/api/version-discovery.js.map +0 -1
  105. package/build/auth/auth-cooldown.d.ts +0 -24
  106. package/build/auth/auth-cooldown.d.ts.map +0 -1
  107. package/build/auth/auth-cooldown.js.map +0 -1
  108. package/build/auth/auth-lock.d.ts +0 -7
  109. package/build/auth/auth-lock.d.ts.map +0 -1
  110. package/build/auth/auth-lock.js.map +0 -1
  111. package/build/auth/auth-runner.d.ts +0 -61
  112. package/build/auth/auth-runner.d.ts.map +0 -1
  113. package/build/auth/auth-runner.js.map +0 -1
  114. package/build/auth/browser-auth.d.ts +0 -94
  115. package/build/auth/browser-auth.d.ts.map +0 -1
  116. package/build/auth/browser-auth.js.map +0 -1
  117. package/build/auth/browser-state-store.d.ts +0 -19
  118. package/build/auth/browser-state-store.d.ts.map +0 -1
  119. package/build/auth/browser-state-store.js.map +0 -1
  120. package/build/auth/credential-store.d.ts +0 -29
  121. package/build/auth/credential-store.d.ts.map +0 -1
  122. package/build/auth/credential-store.js.map +0 -1
  123. package/build/auth/duo-mfa.d.ts +0 -23
  124. package/build/auth/duo-mfa.d.ts.map +0 -1
  125. package/build/auth/duo-mfa.js.map +0 -1
  126. package/build/auth/encrypted-store.d.ts +0 -25
  127. package/build/auth/encrypted-store.d.ts.map +0 -1
  128. package/build/auth/encrypted-store.js.map +0 -1
  129. package/build/auth/index.d.ts +0 -14
  130. package/build/auth/index.d.ts.map +0 -1
  131. package/build/auth/index.js.map +0 -1
  132. package/build/auth/legacy-profile.d.ts +0 -3
  133. package/build/auth/legacy-profile.d.ts.map +0 -1
  134. package/build/auth/legacy-profile.js.map +0 -1
  135. package/build/auth/legacy-state.d.ts +0 -14
  136. package/build/auth/legacy-state.d.ts.map +0 -1
  137. package/build/auth/legacy-state.js.map +0 -1
  138. package/build/auth/purdue-sso.d.ts +0 -57
  139. package/build/auth/purdue-sso.d.ts.map +0 -1
  140. package/build/auth/purdue-sso.js.map +0 -1
  141. package/build/auth/session-store.d.ts +0 -28
  142. package/build/auth/session-store.d.ts.map +0 -1
  143. package/build/auth/session-store.js.map +0 -1
  144. package/build/auth/sso-flow.d.ts +0 -42
  145. package/build/auth/sso-flow.d.ts.map +0 -1
  146. package/build/auth/sso-flow.js.map +0 -1
  147. package/build/auth/suny-sso.d.ts +0 -53
  148. package/build/auth/suny-sso.d.ts.map +0 -1
  149. package/build/auth/suny-sso.js.map +0 -1
  150. package/build/auth/token-manager.d.ts +0 -74
  151. package/build/auth/token-manager.d.ts.map +0 -1
  152. package/build/auth/token-manager.js.map +0 -1
  153. package/build/auth/token-mint.d.ts +0 -32
  154. package/build/auth/token-mint.d.ts.map +0 -1
  155. package/build/auth/token-mint.js.map +0 -1
  156. package/build/auth/western-sso.d.ts +0 -15
  157. package/build/auth/western-sso.d.ts.map +0 -1
  158. package/build/auth/western-sso.js.map +0 -1
  159. package/build/auth-cli.d.ts +0 -4
  160. package/build/auth-cli.d.ts.map +0 -1
  161. package/build/auth-cli.js.map +0 -1
  162. package/build/index.d.ts +0 -10
  163. package/build/index.d.ts.map +0 -1
  164. package/build/index.js.map +0 -1
  165. package/build/setup.d.ts +0 -10
  166. package/build/setup.d.ts.map +0 -1
  167. package/build/setup.js.map +0 -1
  168. package/build/tools/download-file.d.ts +0 -16
  169. package/build/tools/download-file.d.ts.map +0 -1
  170. package/build/tools/download-file.js.map +0 -1
  171. package/build/tools/get-announcements.d.ts +0 -77
  172. package/build/tools/get-announcements.d.ts.map +0 -1
  173. package/build/tools/get-announcements.js.map +0 -1
  174. package/build/tools/get-assignment-files.d.ts +0 -12
  175. package/build/tools/get-assignment-files.d.ts.map +0 -1
  176. package/build/tools/get-assignment-files.js.map +0 -1
  177. package/build/tools/get-assignments.d.ts +0 -19
  178. package/build/tools/get-assignments.d.ts.map +0 -1
  179. package/build/tools/get-assignments.js.map +0 -1
  180. package/build/tools/get-classlist-emails.d.ts +0 -12
  181. package/build/tools/get-classlist-emails.d.ts.map +0 -1
  182. package/build/tools/get-classlist-emails.js.map +0 -1
  183. package/build/tools/get-course-content.d.ts +0 -12
  184. package/build/tools/get-course-content.d.ts.map +0 -1
  185. package/build/tools/get-course-content.js.map +0 -1
  186. package/build/tools/get-discussions.d.ts +0 -12
  187. package/build/tools/get-discussions.d.ts.map +0 -1
  188. package/build/tools/get-discussions.js.map +0 -1
  189. package/build/tools/get-my-courses.d.ts +0 -13
  190. package/build/tools/get-my-courses.d.ts.map +0 -1
  191. package/build/tools/get-my-courses.js.map +0 -1
  192. package/build/tools/get-my-grades.d.ts +0 -13
  193. package/build/tools/get-my-grades.d.ts.map +0 -1
  194. package/build/tools/get-my-grades.js.map +0 -1
  195. package/build/tools/get-roster.d.ts +0 -12
  196. package/build/tools/get-roster.d.ts.map +0 -1
  197. package/build/tools/get-roster.js.map +0 -1
  198. package/build/tools/get-syllabus.d.ts +0 -12
  199. package/build/tools/get-syllabus.d.ts.map +0 -1
  200. package/build/tools/get-syllabus.js.map +0 -1
  201. package/build/tools/get-upcoming-due-dates.d.ts +0 -13
  202. package/build/tools/get-upcoming-due-dates.d.ts.map +0 -1
  203. package/build/tools/get-upcoming-due-dates.js.map +0 -1
  204. package/build/tools/index.d.ts +0 -20
  205. package/build/tools/index.d.ts.map +0 -1
  206. package/build/tools/index.js.map +0 -1
  207. package/build/tools/schemas.d.ts +0 -76
  208. package/build/tools/schemas.d.ts.map +0 -1
  209. package/build/tools/schemas.js.map +0 -1
  210. package/build/tools/tool-helpers.d.ts +0 -27
  211. package/build/tools/tool-helpers.d.ts.map +0 -1
  212. package/build/tools/tool-helpers.js.map +0 -1
  213. package/build/types/index.d.ts +0 -63
  214. package/build/types/index.d.ts.map +0 -1
  215. package/build/types/index.js.map +0 -1
  216. package/build/update.d.ts +0 -10
  217. package/build/update.d.ts.map +0 -1
  218. package/build/update.js.map +0 -1
  219. package/build/utils/atomic-write.d.ts +0 -18
  220. package/build/utils/atomic-write.d.ts.map +0 -1
  221. package/build/utils/atomic-write.js.map +0 -1
  222. package/build/utils/browser-install.d.ts +0 -23
  223. package/build/utils/browser-install.d.ts.map +0 -1
  224. package/build/utils/browser-install.js.map +0 -1
  225. package/build/utils/commands.d.ts +0 -26
  226. package/build/utils/commands.d.ts.map +0 -1
  227. package/build/utils/commands.js.map +0 -1
  228. package/build/utils/config-store.d.ts +0 -24
  229. package/build/utils/config-store.d.ts.map +0 -1
  230. package/build/utils/config-store.js.map +0 -1
  231. package/build/utils/config.d.ts +0 -10
  232. package/build/utils/config.d.ts.map +0 -1
  233. package/build/utils/config.js.map +0 -1
  234. package/build/utils/course-filter.d.ts +0 -31
  235. package/build/utils/course-filter.d.ts.map +0 -1
  236. package/build/utils/course-filter.js.map +0 -1
  237. package/build/utils/deep-links.d.ts +0 -17
  238. package/build/utils/deep-links.d.ts.map +0 -1
  239. package/build/utils/deep-links.js.map +0 -1
  240. package/build/utils/download-errors.d.ts +0 -45
  241. package/build/utils/download-errors.d.ts.map +0 -1
  242. package/build/utils/download-errors.js.map +0 -1
  243. package/build/utils/download-helpers.d.ts +0 -31
  244. package/build/utils/download-helpers.d.ts.map +0 -1
  245. package/build/utils/download-helpers.js.map +0 -1
  246. package/build/utils/errors.d.ts +0 -21
  247. package/build/utils/errors.d.ts.map +0 -1
  248. package/build/utils/errors.js.map +0 -1
  249. package/build/utils/file-validator.d.ts +0 -56
  250. package/build/utils/file-validator.d.ts.map +0 -1
  251. package/build/utils/file-validator.js.map +0 -1
  252. package/build/utils/html-converter.d.ts +0 -17
  253. package/build/utils/html-converter.d.ts.map +0 -1
  254. package/build/utils/html-converter.js.map +0 -1
  255. package/build/utils/install-sites.d.ts +0 -60
  256. package/build/utils/install-sites.d.ts.map +0 -1
  257. package/build/utils/install-sites.js.map +0 -1
  258. package/build/utils/logger.d.ts +0 -10
  259. package/build/utils/logger.d.ts.map +0 -1
  260. package/build/utils/logger.js.map +0 -1
  261. package/build/utils/mcp-client-cli.d.ts +0 -12
  262. package/build/utils/mcp-client-cli.d.ts.map +0 -1
  263. package/build/utils/mcp-client-cli.js.map +0 -1
  264. package/build/utils/modified-since.d.ts +0 -19
  265. package/build/utils/modified-since.d.ts.map +0 -1
  266. package/build/utils/modified-since.js.map +0 -1
  267. package/build/utils/pdf-extractor.d.ts +0 -14
  268. package/build/utils/pdf-extractor.d.ts.map +0 -1
  269. package/build/utils/pdf-extractor.js.map +0 -1
  270. package/build/utils/secure-config.d.ts +0 -6
  271. package/build/utils/secure-config.d.ts.map +0 -1
  272. package/build/utils/secure-config.js.map +0 -1
  273. package/build/utils/self-update.d.ts +0 -61
  274. package/build/utils/self-update.d.ts.map +0 -1
  275. package/build/utils/self-update.js.map +0 -1
  276. package/build/utils/update-checker.d.ts +0 -112
  277. package/build/utils/update-checker.d.ts.map +0 -1
  278. package/build/utils/update-checker.js.map +0 -1
  279. package/build/utils/zip-extract.d.ts +0 -15
  280. package/build/utils/zip-extract.d.ts.map +0 -1
  281. 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
 
@@ -52,4 +52,3 @@ export class TTLCache {
52
52
  return this.cache.size;
53
53
  }
54
54
  }
55
- //# sourceMappingURL=cache.js.map
@@ -476,4 +476,3 @@ export class D2LApiClient {
476
476
  return this.cache.size;
477
477
  }
478
478
  }
479
- //# sourceMappingURL=client.js.map
@@ -51,4 +51,3 @@ export class TokenRefreshError extends NetworkError {
51
51
  this.name = "TokenRefreshError";
52
52
  }
53
53
  }
54
- //# sourceMappingURL=errors.js.map
@@ -14,4 +14,3 @@ export { TokenBucket } from "./rate-limiter.js";
14
14
  // Errors
15
15
  export { ApiError, RateLimitError, NetworkError } from "./errors.js";
16
16
  export { DEFAULT_CACHE_TTLS } from "./types.js";
17
- //# sourceMappingURL=index.js.map
@@ -91,4 +91,3 @@ function nextPath(firstPath, next) {
91
91
  }
92
92
  return withBookmark(firstPath, next);
93
93
  }
94
- //# sourceMappingURL=paginate.js.map
@@ -54,4 +54,3 @@ export class TokenBucket {
54
54
  return this.tokens;
55
55
  }
56
56
  }
57
- //# sourceMappingURL=rate-limiter.js.map
@@ -47,4 +47,3 @@ export function retryAfterMsFrom(error) {
47
47
  }
48
48
  return undefined;
49
49
  }
50
- //# sourceMappingURL=retry.js.map
@@ -13,4 +13,3 @@ export const DEFAULT_CACHE_TTLS = {
13
13
  roster: 3_600_000, // 1 hour
14
14
  profile: 3_600_000, // 1 hour
15
15
  };
16
- //# sourceMappingURL=types.js.map
@@ -53,4 +53,3 @@ export async function discoverVersions(baseUrl, timeoutMs = 15000) {
53
53
  throw new NetworkError(`Failed to discover API versions: ${message}`, error instanceof Error ? error : undefined);
54
54
  }
55
55
  }
56
- //# sourceMappingURL=version-discovery.js.map
@@ -57,4 +57,3 @@ export class AuthCooldown {
57
57
  await fs.rename(temporary, this.file);
58
58
  }
59
59
  }
60
- //# sourceMappingURL=auth-cooldown.js.map
@@ -159,4 +159,3 @@ async function acquireLease(lockPath, depth) {
159
159
  }
160
160
  throw new AuthenticationInProgressError();
161
161
  }
162
- //# sourceMappingURL=auth-lock.js.map
@@ -256,4 +256,3 @@ export class AuthRunner {
256
256
  });
257
257
  }
258
258
  }
259
- //# sourceMappingURL=auth-runner.js.map
@@ -694,4 +694,3 @@ export class BrowserAuth {
694
694
  }
695
695
  }
696
696
  }
697
- //# sourceMappingURL=browser-auth.js.map
@@ -98,4 +98,3 @@ export class BrowserStateStore {
98
98
  }
99
99
  }
100
100
  }
101
- //# sourceMappingURL=browser-state-store.js.map
@@ -240,4 +240,3 @@ export async function getSessionEncryptionKey(sessionDir, backend = nativeCreden
240
240
  await release?.();
241
241
  }
242
242
  }
243
- //# sourceMappingURL=credential-store.js.map
@@ -68,4 +68,3 @@ export class DuoMfaHandler {
68
68
  log("INFO", "Duo passcode submitted");
69
69
  }
70
70
  }
71
- //# sourceMappingURL=duo-mfa.js.map
@@ -58,4 +58,3 @@ export async function trashFile(file, options) {
58
58
  const { default: trash } = await import("trash");
59
59
  await trash(file);
60
60
  }
61
- //# sourceMappingURL=encrypted-store.js.map
@@ -10,4 +10,3 @@ export { createSSOFlow } from "./sso-flow.js";
10
10
  export { TokenManager } from "./token-manager.js";
11
11
  export { SessionStore } from "./session-store.js";
12
12
  export { AuthRunner } from "./auth-runner.js";
13
- //# sourceMappingURL=index.js.map
@@ -38,4 +38,3 @@ export async function retireLegacyProfile(sessionDir) {
38
38
  }
39
39
  }
40
40
  }
41
- //# sourceMappingURL=legacy-profile.js.map
@@ -104,4 +104,3 @@ export async function migrateLegacyState(sessionRoot) {
104
104
  await release();
105
105
  }
106
106
  }
107
- //# sourceMappingURL=legacy-state.js.map
@@ -296,4 +296,3 @@ export class PurdueSSOFlow {
296
296
  return number && /^\d{1,3}$/.test(number) ? number : null;
297
297
  }
298
298
  }
299
- //# sourceMappingURL=purdue-sso.js.map
@@ -195,4 +195,3 @@ export class SessionStore {
195
195
  }
196
196
  }
197
197
  }
198
- //# sourceMappingURL=session-store.js.map
@@ -51,4 +51,3 @@ export function createSSOFlow(config, requestMfaCode) {
51
51
  }
52
52
  return new PurdueSSOFlow(credentials);
53
53
  }
54
- //# sourceMappingURL=sso-flow.js.map
@@ -136,4 +136,3 @@ export class SunySSOFlow {
136
136
  options.find((option) => option.label.toLowerCase().startsWith(wanted)));
137
137
  }
138
138
  }
139
- //# sourceMappingURL=suny-sso.js.map
@@ -195,4 +195,3 @@ export class TokenManager {
195
195
  return token === null;
196
196
  }
197
197
  }
198
- //# sourceMappingURL=token-manager.js.map
@@ -90,4 +90,3 @@ export async function mintAccessToken({ baseUrl, cookieHeader, csrfToken, fetchI
90
90
  }
91
91
  return { ok: true, accessToken };
92
92
  }
93
- //# sourceMappingURL=token-mint.js.map
@@ -46,4 +46,3 @@ export class WesternSSOFlow {
46
46
  await button.click();
47
47
  }
48
48
  }
49
- //# sourceMappingURL=western-sso.js.map
package/build/auth-cli.js CHANGED
@@ -101,4 +101,3 @@ async function main() {
101
101
  }
102
102
  }
103
103
  await main();
104
- //# sourceMappingURL=auth-cli.js.map
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
- log("DEBUG", "MCP tools registered (12 tools)");
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 (12 tools registered)");
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
@@ -471,4 +471,3 @@ main().catch((err) => {
471
471
  console.error("Setup failed:", err instanceof Error ? err.message : String(err));
472
472
  process.exit(1);
473
473
  });
474
- //# sourceMappingURL=setup.js.map
@@ -184,4 +184,3 @@ async function downloadSubmissionFile(apiClient, courseId, folderId, fileId, dow
184
184
  message: `File downloaded successfully to ${result.path}`,
185
185
  });
186
186
  }
187
- //# sourceMappingURL=download-file.js.map
@@ -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
@@ -167,4 +167,3 @@ export function registerGetAssignmentFiles(server, apiClient, baseUrl) {
167
167
  }
168
168
  });
169
169
  }
170
- //# sourceMappingURL=get-assignment-files.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
- : { markdown: "", html: "" },
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
- : { markdown: "", html: "" },
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
@@ -43,4 +43,3 @@ export function registerGetClasslistEmails(server, apiClient) {
43
43
  }
44
44
  });
45
45
  }
46
- //# sourceMappingURL=get-classlist-emails.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 ?? null,
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 ?? null,
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?.DateCompleted ?? null,
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
@@ -209,4 +209,3 @@ function formatPosts(posts) {
209
209
  }))
210
210
  .sort((a, b) => new Date(a.datePosted).getTime() - new Date(b.datePosted).getTime());
211
211
  }
212
- //# sourceMappingURL=get-discussions.js.map
@@ -52,4 +52,3 @@ export function registerGetMyCourses(server, apiClient, config) {
52
52
  }
53
53
  });
54
54
  }
55
- //# sourceMappingURL=get-my-courses.js.map
@@ -98,4 +98,3 @@ export function registerGetMyGrades(server, apiClient, config) {
98
98
  }
99
99
  });
100
100
  }
101
- //# sourceMappingURL=get-my-grades.js.map
@@ -114,4 +114,3 @@ export function registerGetRoster(server, apiClient) {
114
114
  }
115
115
  });
116
116
  }
117
- //# sourceMappingURL=get-roster.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
@@ -194,4 +194,3 @@ export function registerGetUpcomingDueDates(server, apiClient, config) {
194
194
  }
195
195
  });
196
196
  }
197
- //# sourceMappingURL=get-upcoming-due-dates.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
+ }
@@ -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
@@ -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, null, 2),
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
@@ -4,4 +4,3 @@
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
6
  export {};
7
- //# sourceMappingURL=index.js.map
package/build/update.js CHANGED
@@ -127,4 +127,3 @@ function main() {
127
127
  console.log("");
128
128
  }
129
129
  main();
130
- //# sourceMappingURL=update.js.map
@@ -81,4 +81,3 @@ export function writeFileAtomicSync(target, data, options = {}) {
81
81
  throw error;
82
82
  }
83
83
  }
84
- //# sourceMappingURL=atomic-write.js.map
@@ -30,4 +30,3 @@ export function isMissingBrowserError(error) {
30
30
  return false;
31
31
  return /Executable doesn't exist|playwright install|Failed to launch.*because executable doesn't exist/i.test(message);
32
32
  }
33
- //# sourceMappingURL=browser-install.js.map
@@ -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
@@ -29,4 +29,3 @@ export function saveConfigStore(config) {
29
29
  export function getConfigStorePath() {
30
30
  return CONFIG_FILE;
31
31
  }
32
- //# sourceMappingURL=config-store.js.map
@@ -85,4 +85,3 @@ function expandTilde(filePath) {
85
85
  }
86
86
  return filePath;
87
87
  }
88
- //# sourceMappingURL=config.js.map