brightspace-mcp-server 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/build/api/cache.d.ts.map +1 -1
- package/build/api/cache.js +6 -0
- package/build/api/cache.js.map +1 -1
- package/build/api/client.d.ts +5 -0
- package/build/api/client.d.ts.map +1 -1
- package/build/api/client.js +57 -14
- package/build/api/client.js.map +1 -1
- package/build/api/paginate.d.ts +23 -0
- package/build/api/paginate.d.ts.map +1 -0
- package/build/api/paginate.js +89 -0
- package/build/api/paginate.js.map +1 -0
- package/build/api/retry.d.ts +42 -0
- package/build/api/retry.d.ts.map +1 -0
- package/build/api/retry.js +50 -0
- package/build/api/retry.js.map +1 -0
- package/build/api/types.d.ts +3 -0
- package/build/api/types.d.ts.map +1 -1
- package/build/api/types.js.map +1 -1
- package/build/auth/browser-auth.d.ts.map +1 -1
- package/build/auth/browser-auth.js +2 -1
- package/build/auth/browser-auth.js.map +1 -1
- package/build/auth/session-store.d.ts.map +1 -1
- package/build/auth/session-store.js +4 -4
- package/build/auth/session-store.js.map +1 -1
- package/build/tools/get-assignments.d.ts.map +1 -1
- package/build/tools/get-assignments.js +58 -27
- package/build/tools/get-assignments.js.map +1 -1
- package/build/tools/get-classlist-emails.d.ts.map +1 -1
- package/build/tools/get-classlist-emails.js +4 -6
- package/build/tools/get-classlist-emails.js.map +1 -1
- package/build/tools/get-course-content.d.ts.map +1 -1
- package/build/tools/get-course-content.js +12 -2
- package/build/tools/get-course-content.js.map +1 -1
- package/build/tools/get-my-courses.d.ts.map +1 -1
- package/build/tools/get-my-courses.js +4 -7
- package/build/tools/get-my-courses.js.map +1 -1
- package/build/tools/get-roster.d.ts.map +1 -1
- package/build/tools/get-roster.js +7 -10
- package/build/tools/get-roster.js.map +1 -1
- package/build/utils/atomic-write.d.ts +18 -0
- package/build/utils/atomic-write.d.ts.map +1 -0
- package/build/utils/atomic-write.js +84 -0
- package/build/utils/atomic-write.js.map +1 -0
- package/build/utils/config-store.d.ts.map +1 -1
- package/build/utils/config-store.js +2 -3
- package/build/utils/config-store.js.map +1 -1
- package/build/utils/logger.d.ts.map +1 -1
- package/build/utils/logger.js +45 -1
- package/build/utils/logger.js.map +1 -1
- package/build/utils/update-checker.d.ts +30 -5
- package/build/utils/update-checker.d.ts.map +1 -1
- package/build/utils/update-checker.js +79 -45
- package/build/utils/update-checker.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Licensed under MIT — see LICENSE file for details.
|
|
5
5
|
*/
|
|
6
6
|
import { DEFAULT_CACHE_TTLS } from "../api/index.js";
|
|
7
|
+
import { fetchAllObjects } from "../api/paginate.js";
|
|
7
8
|
import { GetRosterSchema, } from "./schemas.js";
|
|
8
9
|
import { toolResponse, sanitizeError } from "./tool-helpers.js";
|
|
9
10
|
import { log } from "../utils/logger.js";
|
|
@@ -13,9 +14,9 @@ import { log } from "../utils/logger.js";
|
|
|
13
14
|
const INSTRUCTOR_ROLE_ID = 109;
|
|
14
15
|
const TA_ROLE_ID = 135;
|
|
15
16
|
/**
|
|
16
|
-
* Fetch
|
|
17
|
+
* Fetch every classlist user matching the optional filters, across all pages
|
|
17
18
|
*/
|
|
18
|
-
async function
|
|
19
|
+
async function fetchClasslistUsers(apiClient, courseId, options) {
|
|
19
20
|
const params = new URLSearchParams();
|
|
20
21
|
if (options?.roleId !== undefined) {
|
|
21
22
|
params.append("roleId", options.roleId.toString());
|
|
@@ -25,13 +26,9 @@ async function fetchClasslistPage(apiClient, courseId, options) {
|
|
|
25
26
|
}
|
|
26
27
|
const queryString = params.toString();
|
|
27
28
|
const path = apiClient.le(courseId, `/classlist/paged/${queryString ? "?" + queryString : ""}`);
|
|
28
|
-
|
|
29
|
+
return fetchAllObjects(apiClient, path, {
|
|
29
30
|
ttl: DEFAULT_CACHE_TTLS.roster,
|
|
30
31
|
});
|
|
31
|
-
if (response.Next) {
|
|
32
|
-
log("WARN", "get_roster: Pagination detected but not implemented. Some users may be missing.", { courseId, next: response.Next });
|
|
33
|
-
}
|
|
34
|
-
return response.Objects;
|
|
35
32
|
}
|
|
36
33
|
/**
|
|
37
34
|
* Register get_roster tool
|
|
@@ -50,11 +47,11 @@ export function registerGetRoster(server, apiClient) {
|
|
|
50
47
|
if (!includeStudents) {
|
|
51
48
|
// Fetch instructors and TAs in parallel
|
|
52
49
|
const [instructorResult, taResult] = await Promise.allSettled([
|
|
53
|
-
|
|
50
|
+
fetchClasslistUsers(apiClient, courseId, {
|
|
54
51
|
roleId: INSTRUCTOR_ROLE_ID,
|
|
55
52
|
searchTerm,
|
|
56
53
|
}),
|
|
57
|
-
|
|
54
|
+
fetchClasslistUsers(apiClient, courseId, {
|
|
58
55
|
roleId: TA_ROLE_ID,
|
|
59
56
|
searchTerm,
|
|
60
57
|
}),
|
|
@@ -79,7 +76,7 @@ export function registerGetRoster(server, apiClient) {
|
|
|
79
76
|
}
|
|
80
77
|
else {
|
|
81
78
|
// Fetch all users
|
|
82
|
-
allUsers = await
|
|
79
|
+
allUsers = await fetchClasslistUsers(apiClient, courseId, {
|
|
83
80
|
searchTerm,
|
|
84
81
|
});
|
|
85
82
|
// Cap at 100 users to prevent MCP response size issues
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-roster.js","sourceRoot":"","sources":["../../src/tools/get-roster.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAgB,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EACL,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-roster.js","sourceRoot":"","sources":["../../src/tools/get-roster.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
export interface AtomicWriteOptions {
|
|
7
|
+
mode?: number;
|
|
8
|
+
/** Injection seam for tests. */
|
|
9
|
+
renameImpl?: (from: string, to: string) => Promise<void>;
|
|
10
|
+
/** Injection seam for tests. */
|
|
11
|
+
sleep?: (ms: number) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function writeFileAtomic(target: string, data: string | Buffer, options?: AtomicWriteOptions): Promise<void>;
|
|
14
|
+
/** Synchronous twin for the config store, which is written from the CLI. */
|
|
15
|
+
export declare function writeFileAtomicSync(target: string, data: string | Buffer, options?: {
|
|
16
|
+
mode?: number;
|
|
17
|
+
}): void;
|
|
18
|
+
//# sourceMappingURL=atomic-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../../src/utils/atomic-write.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuBH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,gCAAgC;IAChC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAWD,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,IAAI,CAuBN"}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { randomBytes } from "node:crypto";
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as fsSync from "node:fs";
|
|
9
|
+
/**
|
|
10
|
+
* Write a file so that a reader never sees a half-written one.
|
|
11
|
+
*
|
|
12
|
+
* The content is staged to a sibling temp file and then renamed over the
|
|
13
|
+
* target. Rename is atomic on the same filesystem, so a crash, a signal, or
|
|
14
|
+
* a second writer mid-way leaves either the old file or the new one, never a
|
|
15
|
+
* truncated mix. The session store and the config store both hold secrets
|
|
16
|
+
* and are both written by more than one process, which is why they use this.
|
|
17
|
+
*
|
|
18
|
+
* On Windows a rename can fail transiently while antivirus or an indexer
|
|
19
|
+
* holds the target open, so those errors are retried a few times.
|
|
20
|
+
*/
|
|
21
|
+
const TRANSIENT_RENAME_ERRORS = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
22
|
+
const RENAME_ATTEMPTS = 5;
|
|
23
|
+
const RENAME_BASE_DELAY_MS = 50;
|
|
24
|
+
function tempPathFor(target) {
|
|
25
|
+
return `${target}.tmp-${randomBytes(6).toString("hex")}`;
|
|
26
|
+
}
|
|
27
|
+
function isTransient(error) {
|
|
28
|
+
const code = error?.code ?? "";
|
|
29
|
+
return TRANSIENT_RENAME_ERRORS.has(code);
|
|
30
|
+
}
|
|
31
|
+
export async function writeFileAtomic(target, data, options = {}) {
|
|
32
|
+
const { mode, renameImpl = (from, to) => fs.rename(from, to), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), } = options;
|
|
33
|
+
const tmp = tempPathFor(target);
|
|
34
|
+
await fs.writeFile(tmp, data, mode === undefined ? {} : { mode });
|
|
35
|
+
// writeFile's mode is subject to the umask; chmod is not.
|
|
36
|
+
if (mode !== undefined && process.platform !== "win32") {
|
|
37
|
+
await fs.chmod(tmp, mode);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
for (let attempt = 1;; attempt++) {
|
|
41
|
+
try {
|
|
42
|
+
await renameImpl(tmp, target);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!isTransient(error) || attempt >= RENAME_ATTEMPTS)
|
|
47
|
+
throw error;
|
|
48
|
+
await sleep(RENAME_BASE_DELAY_MS * attempt);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
await fs.rm(tmp, { force: true }).catch(() => { });
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Synchronous twin for the config store, which is written from the CLI. */
|
|
58
|
+
export function writeFileAtomicSync(target, data, options = {}) {
|
|
59
|
+
const { mode } = options;
|
|
60
|
+
const tmp = tempPathFor(target);
|
|
61
|
+
fsSync.writeFileSync(tmp, data, mode === undefined ? {} : { mode });
|
|
62
|
+
if (mode !== undefined && process.platform !== "win32") {
|
|
63
|
+
fsSync.chmodSync(tmp, mode);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
for (let attempt = 1;; attempt++) {
|
|
67
|
+
try {
|
|
68
|
+
fsSync.renameSync(tmp, target);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!isTransient(error) || attempt >= RENAME_ATTEMPTS)
|
|
73
|
+
throw error;
|
|
74
|
+
// A short synchronous pause; there is no event loop to yield to here.
|
|
75
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, RENAME_BASE_DELAY_MS * attempt);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
fsSync.rmSync(tmp, { force: true });
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/utils/atomic-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/utils/config-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/utils/config-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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,CAU7D;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import * as os from "node:os";
|
|
9
|
+
import { writeFileAtomicSync } from "./atomic-write.js";
|
|
9
10
|
const CONFIG_DIR = path.join(os.homedir(), ".brightspace-mcp");
|
|
10
11
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
11
12
|
export function configStoreExists() {
|
|
@@ -20,9 +21,7 @@ export function saveConfigStore(config) {
|
|
|
20
21
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
21
22
|
fs.mkdirSync(CONFIG_DIR, { recursive: true, ...(isWindows ? {} : { mode: 0o700 }) });
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
...(isWindows ? {} : { mode: 0o600 }),
|
|
25
|
-
});
|
|
24
|
+
writeFileAtomicSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", isWindows ? {} : { mode: 0o600 });
|
|
26
25
|
}
|
|
27
26
|
export function getConfigStorePath() {
|
|
28
27
|
return CONFIG_FILE;
|
|
@@ -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;
|
|
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;
|
|
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"}
|
package/build/utils/logger.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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;
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
await
|
|
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
|
|
54
|
+
// Not one of ours, leave it alone.
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
catch {
|
|
49
|
-
// npx cache
|
|
59
|
+
// No npx cache, or not readable. Nothing to clear.
|
|
50
60
|
}
|
|
51
61
|
return cleared;
|
|
52
62
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 =
|
|
61
|
-
if (
|
|
102
|
+
const latest = (await response.json()).version;
|
|
103
|
+
if (typeof latest !== "string" || !isNewerVersion(latest, installedVersion))
|
|
62
104
|
return;
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
1
|
+
{"version":3,"file":"update-checker.js","sourceRoot":"","sources":["../../src/utils/update-checker.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.
|
|
3
|
+
"version": "1.5.2",
|
|
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",
|