brightspace-mcp-server 1.5.1 → 1.6.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 (73) hide show
  1. package/README.md +2 -2
  2. package/build/api/cache.d.ts.map +1 -1
  3. package/build/api/cache.js +6 -0
  4. package/build/api/cache.js.map +1 -1
  5. package/build/api/client.d.ts +5 -0
  6. package/build/api/client.d.ts.map +1 -1
  7. package/build/api/client.js +57 -14
  8. package/build/api/client.js.map +1 -1
  9. package/build/api/paginate.d.ts +23 -0
  10. package/build/api/paginate.d.ts.map +1 -0
  11. package/build/api/paginate.js +94 -0
  12. package/build/api/paginate.js.map +1 -0
  13. package/build/api/retry.d.ts +42 -0
  14. package/build/api/retry.d.ts.map +1 -0
  15. package/build/api/retry.js +50 -0
  16. package/build/api/retry.js.map +1 -0
  17. package/build/api/types.d.ts +3 -0
  18. package/build/api/types.d.ts.map +1 -1
  19. package/build/api/types.js.map +1 -1
  20. package/build/auth/browser-auth.d.ts.map +1 -1
  21. package/build/auth/browser-auth.js +2 -1
  22. package/build/auth/browser-auth.js.map +1 -1
  23. package/build/auth/session-store.d.ts.map +1 -1
  24. package/build/auth/session-store.js +4 -4
  25. package/build/auth/session-store.js.map +1 -1
  26. package/build/index.js +3 -2
  27. package/build/index.js.map +1 -1
  28. package/build/tools/get-assignment-files.d.ts +12 -0
  29. package/build/tools/get-assignment-files.d.ts.map +1 -0
  30. package/build/tools/get-assignment-files.js +170 -0
  31. package/build/tools/get-assignment-files.js.map +1 -0
  32. package/build/tools/get-assignments.d.ts.map +1 -1
  33. package/build/tools/get-assignments.js +58 -27
  34. package/build/tools/get-assignments.js.map +1 -1
  35. package/build/tools/get-classlist-emails.d.ts.map +1 -1
  36. package/build/tools/get-classlist-emails.js +4 -6
  37. package/build/tools/get-classlist-emails.js.map +1 -1
  38. package/build/tools/get-course-content.d.ts.map +1 -1
  39. package/build/tools/get-course-content.js +12 -2
  40. package/build/tools/get-course-content.js.map +1 -1
  41. package/build/tools/get-my-courses.d.ts.map +1 -1
  42. package/build/tools/get-my-courses.js +4 -7
  43. package/build/tools/get-my-courses.js.map +1 -1
  44. package/build/tools/get-roster.d.ts.map +1 -1
  45. package/build/tools/get-roster.js +33 -24
  46. package/build/tools/get-roster.js.map +1 -1
  47. package/build/tools/index.d.ts +1 -0
  48. package/build/tools/index.d.ts.map +1 -1
  49. package/build/tools/index.js +1 -0
  50. package/build/tools/index.js.map +1 -1
  51. package/build/tools/schemas.d.ts +8 -0
  52. package/build/tools/schemas.d.ts.map +1 -1
  53. package/build/tools/schemas.js +14 -0
  54. package/build/tools/schemas.js.map +1 -1
  55. package/build/utils/atomic-write.d.ts +18 -0
  56. package/build/utils/atomic-write.d.ts.map +1 -0
  57. package/build/utils/atomic-write.js +84 -0
  58. package/build/utils/atomic-write.js.map +1 -0
  59. package/build/utils/config-store.d.ts.map +1 -1
  60. package/build/utils/config-store.js +2 -3
  61. package/build/utils/config-store.js.map +1 -1
  62. package/build/utils/logger.d.ts.map +1 -1
  63. package/build/utils/logger.js +45 -1
  64. package/build/utils/logger.js.map +1 -1
  65. package/build/utils/update-checker.d.ts +30 -5
  66. package/build/utils/update-checker.d.ts.map +1 -1
  67. package/build/utils/update-checker.js +79 -45
  68. package/build/utils/update-checker.js.map +1 -1
  69. package/build/utils/zip-extract.d.ts +15 -0
  70. package/build/utils/zip-extract.d.ts.map +1 -0
  71. package/build/utils/zip-extract.js +170 -0
  72. package/build/utils/zip-extract.js.map +1 -0
  73. package/package.json +1 -1
@@ -1 +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,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;QACpE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;KACtC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,WAAW,CAAC;AACrB,CAAC"}
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;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgBxD,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,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,mBAAmB,CACjB,WAAW,EACX,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EACtC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAWlD,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAyBD,wBAAgB,GAAG,CACjB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,IAAI,CAKN;AAGD,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAWlD,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AA2ED,wBAAgB,GAAG,CACjB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,IAAI,CAMN;AAGD,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC"}
@@ -16,22 +16,66 @@ export function setLogLevel(level) {
16
16
  /**
17
17
  * Redact sensitive patterns from log output.
18
18
  * Tokens, passwords, and secrets are replaced with first 8 chars + "...REDACTED".
19
+ *
20
+ * Deliberately no blanket uppercase or base32 rule: it would eat course codes
21
+ * like ECE264, which are exactly what a useful log line contains.
19
22
  */
20
23
  function redact(value) {
24
+ // JSON web tokens, wherever they appear
25
+ value = value.replace(/eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]*)?/g, "eyJ...REDACTED");
21
26
  // Redact Bearer tokens
22
27
  value = value.replace(/Bearer\s+([A-Za-z0-9._~+/=-]{8})[A-Za-z0-9._~+/=-]*/g, "Bearer $1...REDACTED");
23
28
  // Redact cookie: prefixed tokens
24
29
  value = value.replace(/cookie:([^\s]{8})[^\s]*/g, "cookie:$1...REDACTED");
30
+ // JSON-serialized header fields: {"Authorization":"..."}, {"Cookie":"..."}
31
+ value = value.replace(/("(?:authorization|cookie|set-cookie)"\s*:\s*")[^"]*(")/gi, "$1...REDACTED$2");
32
+ // Credentials embedded in a URL: scheme://user:pass@host
33
+ value = value.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, "$1***:***@");
25
34
  // Redact anything that looks like a long token (40+ chars of base64-like)
26
35
  value = value.replace(/([A-Za-z0-9._~+/=-]{40,})/g, (match) => match.substring(0, 8) + "...REDACTED");
27
36
  return value;
28
37
  }
38
+ const MAX_ARG_LENGTH = 2000;
39
+ /**
40
+ * Turn a log argument into text so the redactor can see inside it. Errors
41
+ * keep their name, message, and first stack frame; objects are serialized
42
+ * with cycles broken; everything else is stringified.
43
+ */
44
+ function serializeArg(arg) {
45
+ if (arg instanceof Error) {
46
+ const frame = arg.stack?.split("\n").find((line) => line.trim().startsWith("at ")) ?? "";
47
+ return `${arg.name}: ${arg.message}${frame ? ` ${frame.trim()}` : ""}`;
48
+ }
49
+ if (typeof arg === "string")
50
+ return arg;
51
+ if (typeof arg === "object" && arg !== null) {
52
+ const seen = new WeakSet();
53
+ try {
54
+ const json = JSON.stringify(arg, (_key, value) => {
55
+ if (typeof value === "bigint")
56
+ return value.toString();
57
+ if (typeof value === "object" && value !== null) {
58
+ if (seen.has(value))
59
+ return "[Circular]";
60
+ seen.add(value);
61
+ }
62
+ return value;
63
+ });
64
+ return json.length > MAX_ARG_LENGTH ? `${json.slice(0, MAX_ARG_LENGTH)}...` : json;
65
+ }
66
+ catch {
67
+ return String(arg);
68
+ }
69
+ }
70
+ return String(arg);
71
+ }
29
72
  export function log(level, message, ...args) {
30
73
  if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevel])
31
74
  return;
32
75
  const timestamp = new Date().toISOString();
33
76
  const safeMessage = redact(message);
34
- console.error(`[${timestamp}] [${level}] ${safeMessage}`, ...args);
77
+ const safeArgs = args.map((arg) => redact(serializeArg(arg)));
78
+ console.error(`[${timestamp}] [${level}] ${safeMessage}`, ...safeArgs);
35
79
  }
36
80
  // Override console.log in production to prevent accidental stdout writes
37
81
  export function enableStdoutGuard() {
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,IAAI,YAAY,GAAa,MAAM,CAAC;AAEpC,MAAM,WAAW,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,SAAS,MAAM,CAAC,KAAa;IAC3B,uBAAuB;IACvB,KAAK,GAAG,KAAK,CAAC,OAAO,CACnB,sDAAsD,EACtD,sBAAsB,CACvB,CAAC;IACF,iCAAiC;IACjC,KAAK,GAAG,KAAK,CAAC,OAAO,CACnB,0BAA0B,EAC1B,sBAAsB,CACvB,CAAC;IACF,0EAA0E;IAC1E,KAAK,GAAG,KAAK,CAAC,OAAO,CACnB,4BAA4B,EAC5B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,aAAa,CACjD,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,GAAG,CACjB,KAAe,EACf,OAAe,EACf,GAAG,IAAe;IAElB,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC;QAAE,OAAO;IAC3D,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,KAAK,KAAK,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE;QACnC,OAAO,CAAC,KAAK,CACX,uDAAuD,EACvD,GAAG,IAAI,CACR,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,34 @@
1
1
  /**
2
- * Background npm update checker — non-blocking fetch on startup.
3
- * Compares installed version against latest on npm registry.
4
- * If a newer version exists, auto-updates (clears npx cache or
5
- * runs npm install -g) so the next launch picks up the new version.
2
+ * Background npm update checker: look, and tell, but never touch.
3
+ *
4
+ * On startup the server asks the npm registry for the latest published
5
+ * version. If it is newer than the running one, the user is told how to
6
+ * update. Nothing is ever installed by this module. The one side effect it
7
+ * keeps is scoped to this package's own stale npx cache directories, because
8
+ * clearing them is what lets `npx brightspace-mcp-server@latest` actually
9
+ * pick up the new version on the next start.
10
+ *
11
+ * Set D2L_NO_UPDATE_CHECK to any value to switch the check off entirely.
6
12
  */
7
- export declare function initUpdateChecker(): void;
13
+ /**
14
+ * True only when `latest` is strictly newer than `installed`, compared as
15
+ * numeric major, minor, patch. Prerelease suffixes are ignored, and anything
16
+ * that does not parse as a version is never "newer", so a registry hiccup
17
+ * cannot announce an update that does not exist.
18
+ */
19
+ export declare function isNewerVersion(latest: string, installed: string): boolean;
20
+ export interface UpdateCheckDeps {
21
+ fetchImpl?: typeof fetch;
22
+ env?: Record<string, string | undefined>;
23
+ installedVersion?: string;
24
+ runningFromNpxCache?: boolean;
25
+ clearCaches?: () => Promise<number>;
26
+ }
27
+ /**
28
+ * Run one update check. Never throws and never blocks the caller for long:
29
+ * the registry request is bounded by a timeout and every failure is
30
+ * swallowed, because a version check must not affect the server.
31
+ */
32
+ export declare function initUpdateChecker(deps?: UpdateCheckDeps): Promise<void>;
8
33
  export declare function getUpdateNotice(): string | null;
9
34
  //# sourceMappingURL=update-checker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"update-checker.d.ts","sourceRoot":"","sources":["../../src/utils/update-checker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwDH,wBAAgB,iBAAiB,IAAI,IAAI,CA8BxC;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAI/C"}
1
+ {"version":3,"file":"update-checker.d.ts","sourceRoot":"","sources":["../../src/utils/update-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA8DH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAQzE;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjF;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAI/C"}
@@ -1,14 +1,23 @@
1
1
  /**
2
- * Background npm update checker — non-blocking fetch on startup.
3
- * Compares installed version against latest on npm registry.
4
- * If a newer version exists, auto-updates (clears npx cache or
5
- * runs npm install -g) so the next launch picks up the new version.
2
+ * Background npm update checker: look, and tell, but never touch.
3
+ *
4
+ * On startup the server asks the npm registry for the latest published
5
+ * version. If it is newer than the running one, the user is told how to
6
+ * update. Nothing is ever installed by this module. The one side effect it
7
+ * keeps is scoped to this package's own stale npx cache directories, because
8
+ * clearing them is what lets `npx brightspace-mcp-server@latest` actually
9
+ * pick up the new version on the next start.
10
+ *
11
+ * Set D2L_NO_UPDATE_CHECK to any value to switch the check off entirely.
6
12
  */
7
- import { exec } from "node:child_process";
8
13
  import { readFileSync } from "node:fs";
9
- import { rm } from "node:fs/promises";
14
+ import { access, readdir, rm } from "node:fs/promises";
15
+ import { homedir } from "node:os";
10
16
  import { fileURLToPath } from "node:url";
11
17
  import { dirname, resolve, sep } from "node:path";
18
+ const PACKAGE_NAME = "brightspace-mcp-server";
19
+ const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
20
+ const REGISTRY_TIMEOUT_MS = 5000;
12
21
  const __filename = fileURLToPath(import.meta.url);
13
22
  const projectRoot = resolve(dirname(__filename), "..", "..");
14
23
  let notice = null;
@@ -25,65 +34,90 @@ function isNpxCache() {
25
34
  const normalized = projectRoot.split(sep).join("/");
26
35
  return /[\\/]_npx[\\/][^\\/]+[\\/]node_modules[\\/]brightspace-mcp-server/.test(normalized);
27
36
  }
37
+ /**
38
+ * Remove every npx cache entry that holds a copy of this package, so the next
39
+ * `npx brightspace-mcp-server@latest` downloads the new version instead of
40
+ * reusing a stale one. Touches nothing outside those directories.
41
+ */
28
42
  async function clearAllNpxCaches() {
29
- const { homedir } = await import("node:os");
30
43
  const npxCacheRoot = resolve(homedir(), ".npm", "_npx");
31
- const { readdir } = await import("node:fs/promises");
32
44
  let cleared = 0;
33
45
  try {
34
- const entries = await readdir(npxCacheRoot);
35
- for (const entry of entries) {
36
- const pkgPath = resolve(npxCacheRoot, entry, "node_modules", "brightspace-mcp-server");
46
+ for (const entry of await readdir(npxCacheRoot)) {
47
+ const entryDir = resolve(npxCacheRoot, entry);
37
48
  try {
38
- const { access } = await import("node:fs/promises");
39
- await access(pkgPath);
40
- await rm(resolve(npxCacheRoot, entry), { recursive: true, force: true });
49
+ await access(resolve(entryDir, "node_modules", PACKAGE_NAME));
50
+ await rm(entryDir, { recursive: true, force: true });
41
51
  cleared++;
42
52
  }
43
53
  catch {
44
- // Not a brightspace cache entry, skip
54
+ // Not one of ours, leave it alone.
45
55
  }
46
56
  }
47
57
  }
48
58
  catch {
49
- // npx cache dir doesn't exist or not readable
59
+ // No npx cache, or not readable. Nothing to clear.
50
60
  }
51
61
  return cleared;
52
62
  }
53
- const FALLBACK_MSG = (old, latest) => `Update available: v${old} → v${latest}. ` +
54
- "Run `npx brightspace-mcp-server@latest` or clear your npx cache to update.";
55
- export function initUpdateChecker() {
56
- const installed = getInstalledVersion();
57
- exec("npm view brightspace-mcp-server version", { timeout: 10000 }, (err, stdout) => {
58
- if (err)
63
+ function parseTriple(version) {
64
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
65
+ if (!match)
66
+ return null;
67
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
68
+ }
69
+ /**
70
+ * True only when `latest` is strictly newer than `installed`, compared as
71
+ * numeric major, minor, patch. Prerelease suffixes are ignored, and anything
72
+ * that does not parse as a version is never "newer", so a registry hiccup
73
+ * cannot announce an update that does not exist.
74
+ */
75
+ export function isNewerVersion(latest, installed) {
76
+ const a = parseTriple(latest);
77
+ const b = parseTriple(installed);
78
+ if (!a || !b)
79
+ return false;
80
+ for (let i = 0; i < 3; i++) {
81
+ if (a[i] !== b[i])
82
+ return a[i] > b[i];
83
+ }
84
+ return false;
85
+ }
86
+ /**
87
+ * Run one update check. Never throws and never blocks the caller for long:
88
+ * the registry request is bounded by a timeout and every failure is
89
+ * swallowed, because a version check must not affect the server.
90
+ */
91
+ export async function initUpdateChecker(deps = {}) {
92
+ const { fetchImpl = fetch, env = process.env, installedVersion = getInstalledVersion(), runningFromNpxCache = isNpxCache(), clearCaches = clearAllNpxCaches, } = deps;
93
+ if (env.D2L_NO_UPDATE_CHECK)
94
+ return;
95
+ try {
96
+ const response = await fetchImpl(REGISTRY_URL, {
97
+ headers: { accept: "application/json" },
98
+ signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),
99
+ });
100
+ if (!response.ok)
59
101
  return;
60
- const latest = stdout.trim();
61
- if (!latest || latest === installed)
102
+ const latest = (await response.json()).version;
103
+ if (typeof latest !== "string" || !isNewerVersion(latest, installedVersion))
62
104
  return;
63
- if (isNpxCache()) {
64
- clearAllNpxCaches()
65
- .then((count) => {
66
- notice =
67
- `Auto-updated: cleared ${count} npx cache(s) (v${installed} → v${latest}). ` +
68
- "The new version will be downloaded on next restart.";
69
- })
70
- .catch(() => {
71
- notice = FALLBACK_MSG(installed, latest);
72
- });
105
+ if (runningFromNpxCache) {
106
+ const count = await clearCaches();
107
+ notice =
108
+ `Update available: v${installedVersion} to v${latest}. ` +
109
+ `Cleared ${count} stale npx cache director${count === 1 ? "y" : "ies"} for ${PACKAGE_NAME} ` +
110
+ `so the next start downloads v${latest}. Restart your MCP client to pick it up.`;
73
111
  }
74
112
  else {
75
- exec("npm install -g brightspace-mcp-server@latest", { timeout: 60000 }, (installErr) => {
76
- if (installErr) {
77
- notice = FALLBACK_MSG(installed, latest);
78
- }
79
- else {
80
- notice =
81
- `Auto-updated from v${installed} to v${latest}. ` +
82
- "Restart your MCP client to use the new version.";
83
- }
84
- });
113
+ notice =
114
+ `Update available: v${installedVersion} to v${latest}. ` +
115
+ `Run: npx ${PACKAGE_NAME}@latest, or npm install -g ${PACKAGE_NAME}@latest`;
85
116
  }
86
- });
117
+ }
118
+ catch {
119
+ // A version check must never take the server down.
120
+ }
87
121
  }
88
122
  export function getUpdateNotice() {
89
123
  const result = notice;
@@ -1 +1 @@
1
- {"version":3,"file":"update-checker.js","sourceRoot":"","sources":["../../src/utils/update-checker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAE7D,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,SAAS,mBAAmB;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACpF,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpD,OAAO,mEAAmE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC9F,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAErD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;QAC5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,wBAAwB,CAAC,CAAC;YACvF,IAAI,CAAC;gBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;gBACpD,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACzE,OAAO,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,sCAAsC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,MAAc,EAAE,EAAE,CACnD,sBAAsB,GAAG,OAAO,MAAM,IAAI;IAC1C,4EAA4E,CAAC;AAE/E,MAAM,UAAU,iBAAiB;IAC/B,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;IAExC,IAAI,CAAC,yCAAyC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;QAClF,IAAI,GAAG;YAAE,OAAO;QAChB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QAE5C,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,iBAAiB,EAAE;iBAChB,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACd,MAAM;oBACJ,yBAAyB,KAAK,mBAAmB,SAAS,OAAO,MAAM,KAAK;wBAC5E,qDAAqD,CAAC;YAC1D,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE;gBACV,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,8CAA8C,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,EAAE,EAAE;gBACtF,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;gBAC3C,CAAC;qBAAM,CAAC;oBACN,MAAM;wBACJ,sBAAsB,SAAS,QAAQ,MAAM,IAAI;4BACjD,iDAAiD,CAAC;gBACtD,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,GAAG,IAAI,CAAC;IACd,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"update-checker.js","sourceRoot":"","sources":["../../src/utils/update-checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
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
+ /** One entry from a ZIP archive by exact name, or null. Never throws. */
7
+ export declare function extractZipEntry(buffer: Buffer, entryName: string): Buffer | null;
8
+ /** Every entry name in the archive, or null when it does not parse. */
9
+ export declare function listZipEntries(buffer: Buffer): string[] | null;
10
+ /**
11
+ * Readable text from a DOCX, XLSX, or PPTX buffer, or null when the archive
12
+ * is not an Office document or carries nothing readable.
13
+ */
14
+ export declare function officeDocumentText(buffer: Buffer): string | null;
15
+ //# sourceMappingURL=zip-extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip-extract.d.ts","sourceRoot":"","sources":["../../src/utils/zip-extract.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0GH,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhF;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAM9D;AAuBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBhE"}
@@ -0,0 +1,170 @@
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 { inflateRawSync } from "node:zlib";
7
+ /**
8
+ * A dependency-free reader for the one thing we need out of a ZIP: a single
9
+ * named entry. DOCX, XLSX, and PPTX are all ZIP archives, and on a live course
10
+ * the assignment attachments were a DOCX and an XLSX, so reading them is what
11
+ * makes an attachment useful rather than just listed.
12
+ *
13
+ * The central directory is the index, and it is what this walks. Scanning the
14
+ * file for local header magic is the obvious shortcut and it is wrong: those
15
+ * four bytes can appear inside compressed content, and then the reader is
16
+ * parsing a header that does not exist.
17
+ *
18
+ * Nothing here throws. A truncated download, an unsupported compression
19
+ * method, or a ZIP64 archive all return null, because the caller's fallback
20
+ * is simply to report the file without its text.
21
+ */
22
+ const EOCD_SIGNATURE = 0x06054b50;
23
+ const CENTRAL_FILE_SIGNATURE = 0x02014b50;
24
+ const EOCD_MIN_SIZE = 22;
25
+ const EOCD_MAX_COMMENT = 0xffff;
26
+ const ZIP64_MARKER = 0xffffffff;
27
+ const METHOD_STORED = 0;
28
+ const METHOD_DEFLATE = 8;
29
+ /** Find the End of Central Directory record by scanning backwards. */
30
+ function findEocd(buffer) {
31
+ const earliest = Math.max(0, buffer.length - EOCD_MIN_SIZE - EOCD_MAX_COMMENT);
32
+ for (let i = buffer.length - EOCD_MIN_SIZE; i >= earliest; i--) {
33
+ if (buffer.readUInt32LE(i) === EOCD_SIGNATURE)
34
+ return i;
35
+ }
36
+ return null;
37
+ }
38
+ function readCentralDirectory(buffer) {
39
+ const eocd = findEocd(buffer);
40
+ if (eocd === null)
41
+ return null;
42
+ const count = buffer.readUInt16LE(eocd + 10);
43
+ let offset = buffer.readUInt32LE(eocd + 16);
44
+ if (offset === ZIP64_MARKER)
45
+ return null; // ZIP64 is out of scope.
46
+ const entries = [];
47
+ for (let i = 0; i < count; i++) {
48
+ if (offset + 46 > buffer.length)
49
+ return null;
50
+ if (buffer.readUInt32LE(offset) !== CENTRAL_FILE_SIGNATURE)
51
+ return null;
52
+ const method = buffer.readUInt16LE(offset + 10);
53
+ const compressedSize = buffer.readUInt32LE(offset + 20);
54
+ const uncompressedSize = buffer.readUInt32LE(offset + 24);
55
+ const nameLength = buffer.readUInt16LE(offset + 28);
56
+ const extraLength = buffer.readUInt16LE(offset + 30);
57
+ const commentLength = buffer.readUInt16LE(offset + 32);
58
+ const localHeaderOffset = buffer.readUInt32LE(offset + 42);
59
+ const nameStart = offset + 46;
60
+ if (nameStart + nameLength > buffer.length)
61
+ return null;
62
+ entries.push({
63
+ name: buffer.subarray(nameStart, nameStart + nameLength).toString("utf-8"),
64
+ method,
65
+ compressedSize,
66
+ uncompressedSize,
67
+ localHeaderOffset,
68
+ });
69
+ offset = nameStart + nameLength + extraLength + commentLength;
70
+ }
71
+ return entries;
72
+ }
73
+ function readEntryData(buffer, entry) {
74
+ const header = entry.localHeaderOffset;
75
+ if (header + 30 > buffer.length)
76
+ return null;
77
+ // The local header repeats the name and extra lengths, and its extra field
78
+ // can differ in length from the central one, so read them from here.
79
+ const nameLength = buffer.readUInt16LE(header + 26);
80
+ const extraLength = buffer.readUInt16LE(header + 28);
81
+ const start = header + 30 + nameLength + extraLength;
82
+ const end = start + entry.compressedSize;
83
+ if (end > buffer.length)
84
+ return null;
85
+ const raw = buffer.subarray(start, end);
86
+ if (entry.method === METHOD_STORED)
87
+ return Buffer.from(raw);
88
+ if (entry.method !== METHOD_DEFLATE)
89
+ return null;
90
+ try {
91
+ return inflateRawSync(raw);
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ /** One entry from a ZIP archive by exact name, or null. Never throws. */
98
+ export function extractZipEntry(buffer, entryName) {
99
+ try {
100
+ const entries = readCentralDirectory(buffer);
101
+ if (!entries)
102
+ return null;
103
+ const entry = entries.find((e) => e.name === entryName);
104
+ if (!entry)
105
+ return null;
106
+ return readEntryData(buffer, entry);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ /** Every entry name in the archive, or null when it does not parse. */
113
+ export function listZipEntries(buffer) {
114
+ try {
115
+ return readCentralDirectory(buffer)?.map((e) => e.name) ?? null;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ const XML_ENTITIES = {
122
+ "&amp;": "&",
123
+ "&lt;": "<",
124
+ "&gt;": ">",
125
+ "&quot;": '"',
126
+ "&apos;": "'",
127
+ "&#39;": "'",
128
+ };
129
+ /**
130
+ * Strip XML markup down to the words a reader cares about. Paragraph and
131
+ * cell boundaries become spaces so words from adjacent runs do not fuse.
132
+ */
133
+ function xmlToText(xml) {
134
+ return xml
135
+ .replace(/<[^>]+>/g, " ")
136
+ .replace(/&(?:amp|lt|gt|quot|apos|#39);/g, (m) => XML_ENTITIES[m] ?? m)
137
+ .replace(/\s+/g, " ")
138
+ .trim();
139
+ }
140
+ /**
141
+ * Readable text from a DOCX, XLSX, or PPTX buffer, or null when the archive
142
+ * is not an Office document or carries nothing readable.
143
+ */
144
+ export function officeDocumentText(buffer) {
145
+ const names = listZipEntries(buffer);
146
+ if (!names)
147
+ return null;
148
+ const read = (name) => xmlToText(extractZipEntry(buffer, name)?.toString("utf-8") ?? "");
149
+ let text = "";
150
+ if (names.includes("word/document.xml")) {
151
+ text = read("word/document.xml");
152
+ }
153
+ else if (names.includes("xl/sharedStrings.xml")) {
154
+ text = read("xl/sharedStrings.xml");
155
+ }
156
+ else if (names.some((n) => n.startsWith("ppt/slides/slide"))) {
157
+ const slides = names
158
+ .filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n))
159
+ .sort((a, b) => slideNumber(a) - slideNumber(b));
160
+ text = slides.map(read).filter(Boolean).join("\n\n");
161
+ }
162
+ else {
163
+ return null;
164
+ }
165
+ return text.length > 0 ? text : null;
166
+ }
167
+ function slideNumber(name) {
168
+ return Number(/slide(\d+)\.xml$/.exec(name)?.[1] ?? 0);
169
+ }
170
+ //# sourceMappingURL=zip-extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip-extract.js","sourceRoot":"","sources":["../../src/utils/zip-extract.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "mcpName": "io.github.rohanmuppa/brightspace",
5
5
  "description": "MCP server for Brightspace (D2L). Check grades, due dates, assignments, announcements, syllabus, rosters and more via Claude, ChatGPT, Cursor, Windsurf, or any MCP client.",
6
6
  "type": "module",