brightspace-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +372 -0
  3. package/build/api/cache.d.ts +15 -0
  4. package/build/api/cache.d.ts.map +1 -0
  5. package/build/api/cache.js +49 -0
  6. package/build/api/cache.js.map +1 -0
  7. package/build/api/client.d.ts +109 -0
  8. package/build/api/client.d.ts.map +1 -0
  9. package/build/api/client.js +356 -0
  10. package/build/api/client.js.map +1 -0
  11. package/build/api/errors.d.ts +23 -0
  12. package/build/api/errors.d.ts.map +1 -0
  13. package/build/api/errors.js +47 -0
  14. package/build/api/errors.js.map +1 -0
  15. package/build/api/index.d.ts +13 -0
  16. package/build/api/index.d.ts.map +1 -0
  17. package/build/api/index.js +17 -0
  18. package/build/api/index.js.map +1 -0
  19. package/build/api/rate-limiter.d.ts +17 -0
  20. package/build/api/rate-limiter.d.ts.map +1 -0
  21. package/build/api/rate-limiter.js +57 -0
  22. package/build/api/rate-limiter.js.map +1 -0
  23. package/build/api/types.d.ts +36 -0
  24. package/build/api/types.d.ts.map +1 -0
  25. package/build/api/types.js +16 -0
  26. package/build/api/types.js.map +1 -0
  27. package/build/api/version-discovery.d.ts +16 -0
  28. package/build/api/version-discovery.d.ts.map +1 -0
  29. package/build/api/version-discovery.js +56 -0
  30. package/build/api/version-discovery.js.map +1 -0
  31. package/build/auth/auth-runner.d.ts +26 -0
  32. package/build/auth/auth-runner.d.ts.map +1 -0
  33. package/build/auth/auth-runner.js +69 -0
  34. package/build/auth/auth-runner.js.map +1 -0
  35. package/build/auth/browser-auth.d.ts +56 -0
  36. package/build/auth/browser-auth.d.ts.map +1 -0
  37. package/build/auth/browser-auth.js +496 -0
  38. package/build/auth/browser-auth.js.map +1 -0
  39. package/build/auth/index.d.ts +11 -0
  40. package/build/auth/index.d.ts.map +1 -0
  41. package/build/auth/index.js +11 -0
  42. package/build/auth/index.js.map +1 -0
  43. package/build/auth/purdue-sso.d.ts +32 -0
  44. package/build/auth/purdue-sso.d.ts.map +1 -0
  45. package/build/auth/purdue-sso.js +185 -0
  46. package/build/auth/purdue-sso.js.map +1 -0
  47. package/build/auth/session-store.d.ts +45 -0
  48. package/build/auth/session-store.d.ts.map +1 -0
  49. package/build/auth/session-store.js +152 -0
  50. package/build/auth/session-store.js.map +1 -0
  51. package/build/auth/token-manager.d.ts +40 -0
  52. package/build/auth/token-manager.d.ts.map +1 -0
  53. package/build/auth/token-manager.js +83 -0
  54. package/build/auth/token-manager.js.map +1 -0
  55. package/build/auth-cli.d.ts +10 -0
  56. package/build/auth-cli.d.ts.map +1 -0
  57. package/build/auth-cli.js +82 -0
  58. package/build/auth-cli.js.map +1 -0
  59. package/build/index.d.ts +10 -0
  60. package/build/index.d.ts.map +1 -0
  61. package/build/index.js +157 -0
  62. package/build/index.js.map +1 -0
  63. package/build/setup.d.ts +10 -0
  64. package/build/setup.d.ts.map +1 -0
  65. package/build/setup.js +321 -0
  66. package/build/setup.js.map +1 -0
  67. package/build/tools/download-file.d.ts +12 -0
  68. package/build/tools/download-file.d.ts.map +1 -0
  69. package/build/tools/download-file.js +167 -0
  70. package/build/tools/download-file.js.map +1 -0
  71. package/build/tools/get-announcements.d.ts +13 -0
  72. package/build/tools/get-announcements.d.ts.map +1 -0
  73. package/build/tools/get-announcements.js +104 -0
  74. package/build/tools/get-announcements.js.map +1 -0
  75. package/build/tools/get-assignments.d.ts +13 -0
  76. package/build/tools/get-assignments.d.ts.map +1 -0
  77. package/build/tools/get-assignments.js +241 -0
  78. package/build/tools/get-assignments.js.map +1 -0
  79. package/build/tools/get-classlist-emails.d.ts +12 -0
  80. package/build/tools/get-classlist-emails.d.ts.map +1 -0
  81. package/build/tools/get-classlist-emails.js +48 -0
  82. package/build/tools/get-classlist-emails.js.map +1 -0
  83. package/build/tools/get-course-content.d.ts +12 -0
  84. package/build/tools/get-course-content.d.ts.map +1 -0
  85. package/build/tools/get-course-content.js +190 -0
  86. package/build/tools/get-course-content.js.map +1 -0
  87. package/build/tools/get-discussions.d.ts +12 -0
  88. package/build/tools/get-discussions.d.ts.map +1 -0
  89. package/build/tools/get-discussions.js +212 -0
  90. package/build/tools/get-discussions.js.map +1 -0
  91. package/build/tools/get-my-courses.d.ts +13 -0
  92. package/build/tools/get-my-courses.d.ts.map +1 -0
  93. package/build/tools/get-my-courses.js +51 -0
  94. package/build/tools/get-my-courses.js.map +1 -0
  95. package/build/tools/get-my-grades.d.ts +13 -0
  96. package/build/tools/get-my-grades.d.ts.map +1 -0
  97. package/build/tools/get-my-grades.js +100 -0
  98. package/build/tools/get-my-grades.js.map +1 -0
  99. package/build/tools/get-roster.d.ts +12 -0
  100. package/build/tools/get-roster.d.ts.map +1 -0
  101. package/build/tools/get-roster.js +108 -0
  102. package/build/tools/get-roster.js.map +1 -0
  103. package/build/tools/get-syllabus.d.ts +12 -0
  104. package/build/tools/get-syllabus.d.ts.map +1 -0
  105. package/build/tools/get-syllabus.js +162 -0
  106. package/build/tools/get-syllabus.js.map +1 -0
  107. package/build/tools/get-upcoming-due-dates.d.ts +13 -0
  108. package/build/tools/get-upcoming-due-dates.d.ts.map +1 -0
  109. package/build/tools/get-upcoming-due-dates.js +74 -0
  110. package/build/tools/get-upcoming-due-dates.js.map +1 -0
  111. package/build/tools/index.d.ts +19 -0
  112. package/build/tools/index.d.ts.map +1 -0
  113. package/build/tools/index.js +21 -0
  114. package/build/tools/index.js.map +1 -0
  115. package/build/tools/schemas.d.ts +66 -0
  116. package/build/tools/schemas.d.ts.map +1 -0
  117. package/build/tools/schemas.js +80 -0
  118. package/build/tools/schemas.js.map +1 -0
  119. package/build/tools/tool-helpers.d.ts +21 -0
  120. package/build/tools/tool-helpers.d.ts.map +1 -0
  121. package/build/tools/tool-helpers.js +70 -0
  122. package/build/tools/tool-helpers.js.map +1 -0
  123. package/build/types/index.d.ts +48 -0
  124. package/build/types/index.d.ts.map +1 -0
  125. package/build/types/index.js +7 -0
  126. package/build/types/index.js.map +1 -0
  127. package/build/update.d.ts +10 -0
  128. package/build/update.d.ts.map +1 -0
  129. package/build/update.js +130 -0
  130. package/build/update.js.map +1 -0
  131. package/build/utils/config-store.d.ts +23 -0
  132. package/build/utils/config-store.d.ts.map +1 -0
  133. package/build/utils/config-store.js +29 -0
  134. package/build/utils/config-store.js.map +1 -0
  135. package/build/utils/config.d.ts +9 -0
  136. package/build/utils/config.d.ts.map +1 -0
  137. package/build/utils/config.js +66 -0
  138. package/build/utils/config.js.map +1 -0
  139. package/build/utils/course-filter.d.ts +24 -0
  140. package/build/utils/course-filter.d.ts.map +1 -0
  141. package/build/utils/course-filter.js +39 -0
  142. package/build/utils/course-filter.js.map +1 -0
  143. package/build/utils/download-helpers.d.ts +31 -0
  144. package/build/utils/download-helpers.d.ts.map +1 -0
  145. package/build/utils/download-helpers.js +93 -0
  146. package/build/utils/download-helpers.js.map +1 -0
  147. package/build/utils/errors.d.ts +21 -0
  148. package/build/utils/errors.d.ts.map +1 -0
  149. package/build/utils/errors.js +36 -0
  150. package/build/utils/errors.js.map +1 -0
  151. package/build/utils/file-validator.d.ts +56 -0
  152. package/build/utils/file-validator.d.ts.map +1 -0
  153. package/build/utils/file-validator.js +134 -0
  154. package/build/utils/file-validator.js.map +1 -0
  155. package/build/utils/html-converter.d.ts +17 -0
  156. package/build/utils/html-converter.d.ts.map +1 -0
  157. package/build/utils/html-converter.js +36 -0
  158. package/build/utils/html-converter.js.map +1 -0
  159. package/build/utils/logger.d.ts +10 -0
  160. package/build/utils/logger.d.ts.map +1 -0
  161. package/build/utils/logger.js +42 -0
  162. package/build/utils/logger.js.map +1 -0
  163. package/build/utils/pdf-extractor.d.ts +14 -0
  164. package/build/utils/pdf-extractor.d.ts.map +1 -0
  165. package/build/utils/pdf-extractor.js +27 -0
  166. package/build/utils/pdf-extractor.js.map +1 -0
  167. package/build/utils/update-checker.d.ts +8 -0
  168. package/build/utils/update-checker.d.ts.map +1 -0
  169. package/build/utils/update-checker.js +33 -0
  170. package/build/utils/update-checker.js.map +1 -0
  171. package/package.json +73 -0
@@ -0,0 +1,66 @@
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 { z } from "zod";
7
+ /**
8
+ * Zod schemas for MCP tool input validation.
9
+ * Passed directly to MCP SDK as inputSchema — SDK detects Zod v4 via ._zod property.
10
+ * Also used in tool handlers for runtime parsing via .parse(args).
11
+ */
12
+ export declare const GetMyCoursesSchema: z.ZodObject<{
13
+ activeOnly: z.ZodDefault<z.ZodBoolean>;
14
+ }, z.core.$strip>;
15
+ export declare const GetUpcomingDueDatesSchema: z.ZodObject<{
16
+ daysAhead: z.ZodDefault<z.ZodNumber>;
17
+ courseId: z.ZodOptional<z.ZodNumber>;
18
+ }, z.core.$strip>;
19
+ export declare const GetMyGradesSchema: z.ZodObject<{
20
+ courseId: z.ZodOptional<z.ZodNumber>;
21
+ }, z.core.$strip>;
22
+ export declare const GetAnnouncementsSchema: z.ZodObject<{
23
+ courseId: z.ZodOptional<z.ZodNumber>;
24
+ count: z.ZodDefault<z.ZodNumber>;
25
+ }, z.core.$strip>;
26
+ export declare const GetAssignmentsSchema: z.ZodObject<{
27
+ courseId: z.ZodOptional<z.ZodNumber>;
28
+ }, z.core.$strip>;
29
+ export declare const GetCourseContentSchema: z.ZodObject<{
30
+ courseId: z.ZodNumber;
31
+ typeFilter: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
32
+ link: "link";
33
+ html: "html";
34
+ video: "video";
35
+ file: "file";
36
+ all: "all";
37
+ }>>>;
38
+ moduleTitle: z.ZodOptional<z.ZodString>;
39
+ maxDepth: z.ZodOptional<z.ZodNumber>;
40
+ }, z.core.$strip>;
41
+ export declare const GetClasslistEmailsSchema: z.ZodObject<{
42
+ courseId: z.ZodNumber;
43
+ }, z.core.$strip>;
44
+ export declare const DownloadFileSchema: z.ZodObject<{
45
+ courseId: z.ZodNumber;
46
+ topicId: z.ZodOptional<z.ZodNumber>;
47
+ folderId: z.ZodOptional<z.ZodNumber>;
48
+ fileId: z.ZodOptional<z.ZodNumber>;
49
+ downloadPath: z.ZodString;
50
+ customFilename: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strip>;
52
+ export declare const GetSyllabusSchema: z.ZodObject<{
53
+ courseId: z.ZodNumber;
54
+ downloadPath: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strip>;
56
+ export declare const GetDiscussionsSchema: z.ZodObject<{
57
+ courseId: z.ZodNumber;
58
+ forumId: z.ZodOptional<z.ZodNumber>;
59
+ topicId: z.ZodOptional<z.ZodNumber>;
60
+ }, z.core.$strip>;
61
+ export declare const GetRosterSchema: z.ZodObject<{
62
+ courseId: z.ZodNumber;
63
+ includeStudents: z.ZodDefault<z.ZodBoolean>;
64
+ searchTerm: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>;
66
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/tools/schemas.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AAEH,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;iBAGpC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;iBAE5B,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;iBAGjC,CAAC;AAEH,eAAO,MAAM,oBAAoB;;iBAG/B,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;;;;;;iBASjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;iBAGnC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;iBAa7B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;iBAK5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;iBAO/B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;iBAO1B,CAAC"}
@@ -0,0 +1,80 @@
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 { z } from "zod";
7
+ /**
8
+ * Zod schemas for MCP tool input validation.
9
+ * Passed directly to MCP SDK as inputSchema — SDK detects Zod v4 via ._zod property.
10
+ * Also used in tool handlers for runtime parsing via .parse(args).
11
+ */
12
+ export const GetMyCoursesSchema = z.object({
13
+ activeOnly: z.boolean().default(true).describe("Only return currently active courses"),
14
+ });
15
+ export const GetUpcomingDueDatesSchema = z.object({
16
+ daysAhead: z.number().int().min(1).max(90).default(7).describe("Number of days ahead to look for due dates"),
17
+ courseId: z.number().int().positive().optional().describe("Filter to a specific course ID"),
18
+ });
19
+ export const GetMyGradesSchema = z.object({
20
+ courseId: z.number().int().positive().optional().describe("Course ID to get grades for. If omitted, returns grades for all enrolled courses."),
21
+ });
22
+ export const GetAnnouncementsSchema = z.object({
23
+ courseId: z.number().int().positive().optional().describe("Course ID to get announcements for. If omitted, returns recent announcements across all courses."),
24
+ count: z.number().int().min(1).max(50).default(10).describe("Maximum number of announcements to return"),
25
+ });
26
+ export const GetAssignmentsSchema = z.object({
27
+ courseId: z.number().int().positive().optional()
28
+ .describe("Course ID to get assignments for. If omitted, returns assignments for all enrolled courses."),
29
+ });
30
+ export const GetCourseContentSchema = z.object({
31
+ courseId: z.number().int().positive()
32
+ .describe("Course ID to get content tree for."),
33
+ typeFilter: z.enum(["file", "link", "html", "video", "all"]).default("all").optional()
34
+ .describe("Optional filter to narrow results by content type."),
35
+ moduleTitle: z.string().optional()
36
+ .describe("Case-insensitive substring match on module titles. Only returns modules whose title contains this string (e.g. 'Labs', 'Staff', 'Homeworks'). Children of matching modules are included in full."),
37
+ maxDepth: z.number().int().min(1).max(10).optional()
38
+ .describe("Limit recursive depth of the content tree. Depth 1 returns top-level modules with direct children only. Useful for getting a table of contents without all nested content."),
39
+ });
40
+ export const GetClasslistEmailsSchema = z.object({
41
+ courseId: z.number().int().positive()
42
+ .describe("Course ID to get emails for."),
43
+ });
44
+ export const DownloadFileSchema = z.object({
45
+ courseId: z.number().int().positive()
46
+ .describe("Course ID the file belongs to."),
47
+ topicId: z.number().int().positive().optional()
48
+ .describe("Content topic ID to download (for course content files)."),
49
+ folderId: z.number().int().positive().optional()
50
+ .describe("Dropbox folder ID (for submission/feedback file downloads)."),
51
+ fileId: z.number().int().positive().optional()
52
+ .describe("Specific file ID within a dropbox submission."),
53
+ downloadPath: z.string().min(1)
54
+ .describe("Absolute path to the directory where the file should be saved."),
55
+ customFilename: z.string().optional()
56
+ .describe("Custom filename for the downloaded file (include extension). If not provided, uses the original filename from Brightspace."),
57
+ });
58
+ export const GetSyllabusSchema = z.object({
59
+ courseId: z.number().int().positive()
60
+ .describe("Course ID to get syllabus for."),
61
+ downloadPath: z.string().min(1).optional()
62
+ .describe("Absolute path to the directory where the attachment should be saved."),
63
+ });
64
+ export const GetDiscussionsSchema = z.object({
65
+ courseId: z.number().int().positive()
66
+ .describe("Course ID to get discussion boards for."),
67
+ forumId: z.number().int().positive().optional()
68
+ .describe("Specific forum ID to get topics and posts for. If omitted, returns all forums."),
69
+ topicId: z.number().int().positive().optional()
70
+ .describe("Specific topic ID to get posts for. Requires forumId."),
71
+ });
72
+ export const GetRosterSchema = z.object({
73
+ courseId: z.number().int().positive()
74
+ .describe("Course ID to get roster for."),
75
+ includeStudents: z.boolean().default(false)
76
+ .describe("Include students in results. Default is instructors and TAs only."),
77
+ searchTerm: z.string().optional()
78
+ .describe("Optional search term to filter by name."),
79
+ });
80
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/tools/schemas.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,sCAAsC,CAAC;CACvF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,4CAA4C,CAAC;IAC5G,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;CAC5F,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mFAAmF,CAAC;CAC/I,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kGAAkG,CAAC;IAC7J,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,2CAA2C,CAAC;CACzG,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC7C,QAAQ,CAAC,6FAA6F,CAAC;CAC3G,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,oCAAoC,CAAC;IACjD,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;SACnF,QAAQ,CAAC,oDAAoD,CAAC;IACjE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC/B,QAAQ,CAAC,kMAAkM,CAAC;IAC/M,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;SACjD,QAAQ,CAAC,4KAA4K,CAAC;CAC1L,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,8BAA8B,CAAC;CAC5C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,gCAAgC,CAAC;IAC7C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC5C,QAAQ,CAAC,0DAA0D,CAAC;IACvE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC7C,QAAQ,CAAC,6DAA6D,CAAC;IAC1E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC3C,QAAQ,CAAC,+CAA+C,CAAC;IAC5D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SAC5B,QAAQ,CAAC,gEAAgE,CAAC;IAC7E,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,4HAA4H,CAAC;CAC1I,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,gCAAgC,CAAC;IAC7C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;SACvC,QAAQ,CAAC,sEAAsE,CAAC;CACpF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,yCAAyC,CAAC;IACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC5C,QAAQ,CAAC,gFAAgF,CAAC;IAC7F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;SAC5C,QAAQ,CAAC,uDAAuD,CAAC;CACrE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAClC,QAAQ,CAAC,8BAA8B,CAAC;IAC3C,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;SACxC,QAAQ,CAAC,mEAAmE,CAAC;IAChF,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC9B,QAAQ,CAAC,yCAAyC,CAAC;CACvD,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
7
+ /**
8
+ * Wrap data as MCP-compatible tool result
9
+ */
10
+ export declare function toolResponse(data: unknown): CallToolResult;
11
+ /**
12
+ * Wrap error message as MCP-compatible tool result
13
+ */
14
+ export declare function errorResponse(message: string): CallToolResult;
15
+ /**
16
+ * Sanitize errors for user-friendly messages
17
+ *
18
+ * SECURITY: Never include stack traces, raw API responses, or token values
19
+ */
20
+ export declare function sanitizeError(error: unknown): CallToolResult;
21
+ //# sourceMappingURL=tool-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../../src/tools/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAKpE;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAS1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAU7D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CA2C5D"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Purdue Brightspace MCP Server
3
+ * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
5
+ */
6
+ import { ZodError } from "zod";
7
+ import { ApiError, RateLimitError, NetworkError } from "../api/index.js";
8
+ import { log } from "../utils/logger.js";
9
+ /**
10
+ * Wrap data as MCP-compatible tool result
11
+ */
12
+ export function toolResponse(data) {
13
+ return {
14
+ content: [
15
+ {
16
+ type: "text",
17
+ text: JSON.stringify(data, null, 2),
18
+ },
19
+ ],
20
+ };
21
+ }
22
+ /**
23
+ * Wrap error message as MCP-compatible tool result
24
+ */
25
+ export function errorResponse(message) {
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: message,
31
+ },
32
+ ],
33
+ isError: true,
34
+ };
35
+ }
36
+ /**
37
+ * Sanitize errors for user-friendly messages
38
+ *
39
+ * SECURITY: Never include stack traces, raw API responses, or token values
40
+ */
41
+ export function sanitizeError(error) {
42
+ // Log full error to stderr for debugging (token redaction handled by logger)
43
+ log("ERROR", "Tool error", error);
44
+ // Map to user-friendly messages
45
+ if (error instanceof ApiError) {
46
+ if (error.status === 404) {
47
+ return errorResponse("Resource not found. The course or item may not exist, or you may not have access.");
48
+ }
49
+ if (error.status === 401) {
50
+ return errorResponse("Authentication expired. Auto-reauthentication was attempted but failed. " +
51
+ "Please run `brightspace-auth` manually in your terminal, then try again.");
52
+ }
53
+ if (error.status === 403) {
54
+ return errorResponse("Access denied. You may not have permission to access this resource.");
55
+ }
56
+ }
57
+ if (error instanceof RateLimitError) {
58
+ return errorResponse("Rate limited by Brightspace. Please wait a moment and try again.");
59
+ }
60
+ if (error instanceof NetworkError) {
61
+ return errorResponse("Could not connect to Brightspace. Check your internet connection.");
62
+ }
63
+ if (error instanceof ZodError) {
64
+ const issues = error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
65
+ return errorResponse(`Invalid input: ${issues.join(", ")}`);
66
+ }
67
+ // Default fallback
68
+ return errorResponse("An unexpected error occurred. Please try again.");
69
+ }
70
+ //# sourceMappingURL=tool-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/tools/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAEzC;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;aACpC;SACF;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,OAAO;aACd;SACF;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,6EAA6E;IAC7E,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IAElC,gCAAgC;IAChC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO,aAAa,CAClB,mFAAmF,CACpF,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO,aAAa,CAClB,0EAA0E;gBAC1E,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,OAAO,aAAa,CAClB,qEAAqE,CACtE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;QACpC,OAAO,aAAa,CAClB,kEAAkE,CACnE,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;QAClC,OAAO,aAAa,CAClB,mEAAmE,CACpE,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5E,OAAO,aAAa,CAAC,kBAAkB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,mBAAmB;IACnB,OAAO,aAAa,CAAC,iDAAiD,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,48 @@
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
+ export interface TokenData {
7
+ accessToken: string;
8
+ capturedAt: number;
9
+ expiresAt: number;
10
+ source: "browser" | "cache";
11
+ }
12
+ export interface EncryptedData {
13
+ iv: string;
14
+ authTag: string;
15
+ data: string;
16
+ }
17
+ export interface SessionFile {
18
+ version: 1;
19
+ encrypted: EncryptedData;
20
+ createdAt: number;
21
+ expiresAt: number;
22
+ }
23
+ export interface AppConfig {
24
+ baseUrl: string;
25
+ sessionDir: string;
26
+ tokenTtl: number;
27
+ headless: boolean;
28
+ username?: string;
29
+ password?: string;
30
+ totpSecret?: string;
31
+ courseFilter: CourseFilterConfig;
32
+ }
33
+ export interface AuthResult {
34
+ token: TokenData;
35
+ cookies?: Array<{
36
+ name: string;
37
+ value: string;
38
+ domain: string;
39
+ path: string;
40
+ }>;
41
+ }
42
+ export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
43
+ export interface CourseFilterConfig {
44
+ includeCourseIds?: number[];
45
+ excludeCourseIds?: number[];
46
+ activeOnly: boolean;
47
+ }
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;CAC7B;AAGD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,kBAAkB,CAAC;CAClC;AAGD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAGD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAG3D,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;CACrB"}
@@ -0,0 +1,7 @@
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
+ export {};
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Brightspace MCP Server
4
+ * Copyright (c) 2026 Rohan Muppa. All rights reserved.
5
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
6
+ *
7
+ * https://github.com/rohanmuppa/brightspace-mcp-server
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Brightspace MCP Server
4
+ * Copyright (c) 2026 Rohan Muppa. All rights reserved.
5
+ * Licensed under AGPL-3.0 — see LICENSE file for details.
6
+ *
7
+ * https://github.com/rohanmuppa/brightspace-mcp-server
8
+ */
9
+ import { execSync } from "node:child_process";
10
+ import { readFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import dotenv from "dotenv";
14
+ dotenv.config({ quiet: true });
15
+ // ANSI color helpers
16
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
17
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
18
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
19
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
20
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
21
+ const projectRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
22
+ function run(cmd, opts) {
23
+ try {
24
+ return execSync(cmd, {
25
+ cwd: projectRoot,
26
+ encoding: "utf-8",
27
+ stdio: opts?.silent ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "inherit"],
28
+ }).trim();
29
+ }
30
+ catch (err) {
31
+ const error = err;
32
+ throw new Error(error.stderr || error.message || "Command failed");
33
+ }
34
+ }
35
+ function getVersion() {
36
+ const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
37
+ return pkg.version || "unknown";
38
+ }
39
+ function main() {
40
+ console.log("");
41
+ console.log(bold("=== Brightspace MCP Server — Update ==="));
42
+ console.log("");
43
+ // Check if we're in a git repo
44
+ try {
45
+ run("git rev-parse --is-inside-work-tree", { silent: true });
46
+ }
47
+ catch {
48
+ console.error(red("Error: Not a git repository. Cannot update."));
49
+ console.error("Make sure you cloned this project with git.");
50
+ process.exit(1);
51
+ }
52
+ // Show current version
53
+ const currentVersion = getVersion();
54
+ console.log(` Current version: ${bold(currentVersion)}`);
55
+ console.log("");
56
+ // Check for uncommitted changes
57
+ const status = run("git status --porcelain", { silent: true });
58
+ if (status) {
59
+ console.log(yellow(" Warning: You have uncommitted changes."));
60
+ console.log(dim(" The update will proceed, but your local changes may conflict."));
61
+ console.log("");
62
+ }
63
+ // Fetch latest from remote
64
+ console.log(dim(" Fetching latest changes..."));
65
+ try {
66
+ run("git fetch origin main", { silent: true });
67
+ }
68
+ catch {
69
+ console.error(red(" Error: Failed to fetch from remote."));
70
+ console.error(" Check your network connection and try again.");
71
+ process.exit(1);
72
+ }
73
+ // Check if we're behind
74
+ const behindCount = run("git rev-list --count HEAD..origin/main", { silent: true });
75
+ if (behindCount === "0") {
76
+ console.log("");
77
+ console.log(green(" Already up to date!"));
78
+ console.log("");
79
+ return;
80
+ }
81
+ // Show what's new
82
+ console.log("");
83
+ console.log(bold(` ${behindCount} new commit${behindCount === "1" ? "" : "s"}:`));
84
+ console.log("");
85
+ const log = run("git log HEAD..origin/main --oneline", { silent: true });
86
+ for (const line of log.split("\n")) {
87
+ console.log(` ${dim("•")} ${line}`);
88
+ }
89
+ console.log("");
90
+ // Pull changes
91
+ console.log(dim(" Pulling changes..."));
92
+ try {
93
+ run("git pull origin main", { silent: true });
94
+ }
95
+ catch {
96
+ console.error(red(" Error: Failed to pull changes."));
97
+ console.error(" You may have merge conflicts. Resolve them manually and try again.");
98
+ process.exit(1);
99
+ }
100
+ // Install dependencies
101
+ console.log(dim(" Installing dependencies..."));
102
+ try {
103
+ run("npm install", { silent: true });
104
+ }
105
+ catch {
106
+ console.error(red(" Error: Failed to install dependencies."));
107
+ process.exit(1);
108
+ }
109
+ // Build
110
+ console.log(dim(" Building..."));
111
+ try {
112
+ run("npm run build", { silent: true });
113
+ }
114
+ catch {
115
+ console.error(red(" Error: Build failed."));
116
+ process.exit(1);
117
+ }
118
+ // Show new version
119
+ const newVersion = getVersion();
120
+ console.log("");
121
+ console.log(green(" Update complete!"));
122
+ if (newVersion !== currentVersion) {
123
+ console.log(` Version: ${currentVersion} → ${bold(newVersion)}`);
124
+ }
125
+ console.log("");
126
+ console.log(" Restart your MCP client to use the latest version.");
127
+ console.log("");
128
+ }
129
+ main();
130
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAyC,CAAC,CAAC;AAEtE,qBAAqB;AACrB,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;AACjD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC;AACnD,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC;AACpD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC;AACjD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;AAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAExE,SAAS,GAAG,CAAC,GAAW,EAAE,IAA2B;IACnD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,GAAG,EAAE;YACnB,GAAG,EAAE,WAAW;YAChB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC;SAC7E,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,GAA4C,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACjF,OAAO,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;AAClC,CAAC;AAED,SAAS,IAAI;IACX,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,+BAA+B;IAC/B,IAAI,CAAC;QACH,GAAG,CAAC,qCAAqC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,uBAAuB;IACvB,MAAM,cAAc,GAAG,UAAU,EAAE,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,gCAAgC;IAChC,MAAM,MAAM,GAAG,GAAG,CAAC,wBAAwB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,0CAA0C,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC,CAAC;QACpF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,2BAA2B;IAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,GAAG,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,wBAAwB;IACxB,MAAM,WAAW,GAAG,GAAG,CAAC,wCAAwC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACpF,IAAI,WAAW,KAAK,GAAG,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IAED,kBAAkB;IAClB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,WAAW,cAAc,WAAW,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,GAAG,GAAG,GAAG,CAAC,qCAAqC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,eAAe;IACf,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,GAAG,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,KAAK,CAAC,sEAAsE,CAAC,CAAC;QACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,uBAAuB;IACvB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,GAAG,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,QAAQ;IACR,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,GAAG,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,mBAAmB;IACnB,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACzC,IAAI,UAAU,KAAK,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,cAAc,cAAc,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 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
+ /** JSON schema for ~/.brightspace-mcp/config.json */
7
+ export interface ConfigStoreData {
8
+ baseUrl?: string;
9
+ username?: string;
10
+ password?: string;
11
+ mfaTotpSecret?: string;
12
+ sessionDir?: string;
13
+ tokenTtl?: number;
14
+ headless?: boolean;
15
+ includeCourses?: number[];
16
+ excludeCourses?: number[];
17
+ activeOnly?: boolean;
18
+ }
19
+ export declare function configStoreExists(): boolean;
20
+ export declare function loadConfigStore(): ConfigStoreData;
21
+ export declare function saveConfigStore(config: ConfigStoreData): void;
22
+ export declare function getConfigStorePath(): string;
23
+ //# sourceMappingURL=config-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/utils/config-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,eAAe,IAAI,eAAe,CAGjD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAO7D;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 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 * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import * as os from "node:os";
9
+ const CONFIG_DIR = path.join(os.homedir(), ".brightspace-mcp");
10
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
11
+ export function configStoreExists() {
12
+ return fs.existsSync(CONFIG_FILE);
13
+ }
14
+ export function loadConfigStore() {
15
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
16
+ return JSON.parse(raw);
17
+ }
18
+ export function saveConfigStore(config) {
19
+ if (!fs.existsSync(CONFIG_DIR)) {
20
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
21
+ }
22
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
23
+ mode: 0o600,
24
+ });
25
+ }
26
+ export function getConfigStorePath() {
27
+ return CONFIG_FILE;
28
+ }
29
+ //# sourceMappingURL=config-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.js","sourceRoot":"","sources":["../../src/utils/config-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAgB9B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,UAAU,iBAAiB;IAC/B,OAAO,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;QACpE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 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 type { AppConfig } from "../types/index.js";
7
+ export declare function loadConfig(): AppConfig;
8
+ export type { AppConfig };
9
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,wBAAgB,UAAU,IAAI,SAAS,CAyDtC;AASD,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 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 * as path from "node:path";
7
+ import * as os from "node:os";
8
+ import { configStoreExists, loadConfigStore } from "./config-store.js";
9
+ export function loadConfig() {
10
+ const store = configStoreExists() ? loadConfigStore() : null;
11
+ if (store) {
12
+ console.error("[config] Loaded base config from ~/.brightspace-mcp/config.json");
13
+ }
14
+ else {
15
+ console.error("[config] No config.json found, using environment variables");
16
+ }
17
+ // Resolve sessionDir: env > store > default
18
+ const sessionDir = process.env.D2L_SESSION_DIR
19
+ ? expandTilde(process.env.D2L_SESSION_DIR)
20
+ : store?.sessionDir
21
+ ? expandTilde(store.sessionDir)
22
+ : path.join(os.homedir(), ".d2l-session");
23
+ // Resolve headless: env > store > default (false)
24
+ let headless = store?.headless ?? false;
25
+ if (process.env.D2L_HEADLESS !== undefined) {
26
+ headless = process.env.D2L_HEADLESS === "true";
27
+ }
28
+ // Resolve tokenTtl: env > store > default (3600)
29
+ const tokenTtl = process.env.D2L_TOKEN_TTL
30
+ ? parseInt(process.env.D2L_TOKEN_TTL, 10)
31
+ : store?.tokenTtl ?? 3600;
32
+ // Resolve includeCourseIds: env > store > undefined
33
+ const includeCourseIds = process.env.D2L_INCLUDE_COURSES
34
+ ? process.env.D2L_INCLUDE_COURSES.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n))
35
+ : store?.includeCourses;
36
+ // Resolve excludeCourseIds: env > store > undefined
37
+ const excludeCourseIds = process.env.D2L_EXCLUDE_COURSES
38
+ ? process.env.D2L_EXCLUDE_COURSES.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n))
39
+ : store?.excludeCourses;
40
+ // Resolve activeOnly: env > store > default (true)
41
+ let activeOnly = store?.activeOnly ?? true;
42
+ if (process.env.D2L_ACTIVE_ONLY !== undefined) {
43
+ activeOnly = process.env.D2L_ACTIVE_ONLY !== 'false';
44
+ }
45
+ return {
46
+ baseUrl: process.env.D2L_BASE_URL || store?.baseUrl || "https://purdue.brightspace.com",
47
+ sessionDir,
48
+ tokenTtl,
49
+ headless,
50
+ username: process.env.D2L_USERNAME || store?.username,
51
+ password: process.env.D2L_PASSWORD || store?.password,
52
+ totpSecret: process.env.MFA_TOTP_SECRET || store?.mfaTotpSecret,
53
+ courseFilter: {
54
+ includeCourseIds,
55
+ excludeCourseIds,
56
+ activeOnly,
57
+ },
58
+ };
59
+ }
60
+ function expandTilde(filePath) {
61
+ if (filePath.startsWith("~")) {
62
+ return path.join(os.homedir(), filePath.slice(1));
63
+ }
64
+ return filePath;
65
+ }
66
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"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"}