brightspace-mcp-server 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/build/api/cache.js +14 -1
- package/build/api/client.js +12 -3
- package/build/api/rate-limiter.js +22 -11
- package/build/auth/auth-cooldown.js +27 -3
- package/build/auth/credential-store.js +14 -1
- package/build/auth/duo-mfa.js +80 -5
- package/build/auth/purdue-sso.js +10 -2
- package/build/auth/session-store.js +22 -1
- package/build/auth/suny-sso.js +7 -0
- package/build/index.js +4 -3
- package/build/setup.js +123 -25
- package/build/tools/download-file.js +24 -6
- package/build/tools/get-announcements.js +11 -5
- package/build/tools/get-assignments.js +11 -5
- package/build/tools/get-course-content.js +12 -2
- package/build/tools/get-my-grades.js +11 -5
- package/build/tools/get-server-info.js +32 -0
- package/build/tools/get-upcoming-due-dates.js +15 -5
- package/build/tools/index.js +1 -0
- package/build/tools/schemas.js +1 -0
- package/build/update.js +38 -5
- package/build/utils/atomic-write.js +38 -12
- package/build/utils/deep-links.js +17 -3
- package/build/utils/download-helpers.js +11 -5
- package/build/utils/file-validator.js +71 -5
- package/build/utils/html-converter.js +66 -0
- package/build/utils/logger.js +19 -3
- package/package.json +1 -1
|
@@ -15,9 +15,75 @@ function getTurndownService() {
|
|
|
15
15
|
headingStyle: "atx",
|
|
16
16
|
codeBlockStyle: "fenced",
|
|
17
17
|
});
|
|
18
|
+
// Turndown has no rule for <script>, <style> or <noscript>, so it falls
|
|
19
|
+
// back to emitting their text: a D2L description carrying an inline
|
|
20
|
+
// stylesheet or an analytics snippet put raw CSS and JavaScript source
|
|
21
|
+
// into the markdown the model reads. None of it is content.
|
|
22
|
+
turndownService.remove(["script", "style", "noscript"]);
|
|
23
|
+
// Turndown has no table rules either, and its fallback treats every cell
|
|
24
|
+
// as a standalone block -- a syllabus grading table came out as a flat
|
|
25
|
+
// run of paragraphs ("Homework", "30%", "Exams", "70%") with nothing
|
|
26
|
+
// left to say which weight belonged to which item. Emit GFM pipe tables
|
|
27
|
+
// so the pairing survives into the markdown a model is asked to read.
|
|
28
|
+
// Rules are registered once here (not at module scope) because the
|
|
29
|
+
// service itself is now lazily constructed on first use.
|
|
30
|
+
turndownService.addRule("tableCell", {
|
|
31
|
+
filter: ["th", "td"],
|
|
32
|
+
replacement: (content) => ` ${cellText(content)} |`,
|
|
33
|
+
});
|
|
34
|
+
turndownService.addRule("tableRow", {
|
|
35
|
+
filter: "tr",
|
|
36
|
+
replacement: (content, node) => {
|
|
37
|
+
const row = `|${content}`;
|
|
38
|
+
const table = closestTable(node);
|
|
39
|
+
const rows = table
|
|
40
|
+
? Array.from(table.querySelectorAll("tr")).filter((candidate) => closestTable(candidate) === table)
|
|
41
|
+
: [];
|
|
42
|
+
// GFM needs a delimiter line after the first row, header row or not:
|
|
43
|
+
// without one the whole block renders as a paragraph and the
|
|
44
|
+
// columns are lost again.
|
|
45
|
+
if (rows.length > 0 && rows[0] === node) {
|
|
46
|
+
const columns = cellsOf(node).length || 1;
|
|
47
|
+
return `\n${row}\n|${" --- |".repeat(columns)}`;
|
|
48
|
+
}
|
|
49
|
+
return `\n${row}`;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
turndownService.addRule("tableSection", {
|
|
53
|
+
filter: ["thead", "tbody", "tfoot"],
|
|
54
|
+
replacement: (content) => content,
|
|
55
|
+
});
|
|
56
|
+
turndownService.addRule("table", {
|
|
57
|
+
filter: "table",
|
|
58
|
+
replacement: (content) => {
|
|
59
|
+
const body = content.trim();
|
|
60
|
+
return body ? `\n\n${body}\n\n` : "";
|
|
61
|
+
},
|
|
62
|
+
});
|
|
18
63
|
}
|
|
19
64
|
return turndownService;
|
|
20
65
|
}
|
|
66
|
+
/** A cell's text, flattened so one table cell stays one table cell. */
|
|
67
|
+
function cellText(content) {
|
|
68
|
+
return content
|
|
69
|
+
.replace(/\|/g, "\\|")
|
|
70
|
+
.replace(/\s*\n+\s*/g, " ")
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
/** The nearest enclosing <table>, so a nested table is scoped to itself. */
|
|
74
|
+
function closestTable(node) {
|
|
75
|
+
let current = node.parentNode;
|
|
76
|
+
while (current) {
|
|
77
|
+
if (current.nodeName === "TABLE")
|
|
78
|
+
return current;
|
|
79
|
+
current = current.parentNode;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** Direct <th>/<td> children of a row. */
|
|
84
|
+
function cellsOf(row) {
|
|
85
|
+
return Array.from(row.childNodes).filter((child) => child.nodeName === "TH" || child.nodeName === "TD");
|
|
86
|
+
}
|
|
21
87
|
/**
|
|
22
88
|
* Convert D2L HTML content to clean markdown.
|
|
23
89
|
* Returns both markdown (for LLM readability) and raw HTML (for fallback).
|
package/build/utils/logger.js
CHANGED
|
@@ -19,16 +19,32 @@ export function setLogLevel(level) {
|
|
|
19
19
|
*
|
|
20
20
|
* Deliberately no blanket uppercase or base32 rule: it would eat course codes
|
|
21
21
|
* like ECE264, which are exactly what a useful log line contains.
|
|
22
|
+
*
|
|
23
|
+
* Order matters: the JSON-shaped rules run before the free-text ones, so a
|
|
24
|
+
* serialized object is redacted by key and the looser rules never see the
|
|
25
|
+
* value they would otherwise re-match across the quoting.
|
|
22
26
|
*/
|
|
23
27
|
function redact(value) {
|
|
24
28
|
// JSON web tokens, wherever they appear
|
|
25
29
|
value = value.replace(/eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]*)?/g, "eyJ...REDACTED");
|
|
26
30
|
// Redact Bearer tokens
|
|
27
31
|
value = value.replace(/Bearer\s+([A-Za-z0-9._~+/=-]{8})[A-Za-z0-9._~+/=-]*/g, "Bearer $1...REDACTED");
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
// JSON-
|
|
32
|
+
// JSON-serialized header fields: {"Authorization":"..."}, {"Cookie":"..."}.
|
|
33
|
+
// Runs before the raw-header rule below so a serialized object is redacted
|
|
34
|
+
// as JSON and the raw-header rule cannot then chew through the quoting.
|
|
31
35
|
value = value.replace(/("(?:authorization|cookie|set-cookie)"\s*:\s*")[^"]*(")/gi, "$1...REDACTED$2");
|
|
36
|
+
// JSON-serialized secret fields: {"password":"..."}, {"accessToken":"..."},
|
|
37
|
+
// {"refresh_token":"..."}, {"xsrfToken":"..."}. Only string values match, so
|
|
38
|
+
// a numeric field like "tokenExpiry": 1750000000 is left readable.
|
|
39
|
+
value = value.replace(/("[^"]*(?:password|passwd|secret|token|credential|api[_-]?key)[^"]*"\s*:\s*")[^"]*(")/gi, "$1...REDACTED$2");
|
|
40
|
+
// Raw header lines: "Cookie: d2lSessionVal=..." / "Set-Cookie: ...".
|
|
41
|
+
// The whole remainder of the line goes, because a cookie header carries
|
|
42
|
+
// every cookie for the host, not just the first pair. Case-insensitive and
|
|
43
|
+
// tolerant of the space HTTP actually puts after the colon -- without both,
|
|
44
|
+
// this rule only ever fired on a form no real header takes.
|
|
45
|
+
value = value.replace(/(^|[\s{[(,;])((?:set-)?cookie)\s*:[ \t]*([^\r\n]+)/gi, (_match, prefix, name, secret) => `${prefix}${name}: ${secret.slice(0, 8)}...REDACTED`);
|
|
46
|
+
// password=... / secret=... in a query string, form body, or free text.
|
|
47
|
+
value = value.replace(/\b(password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token)\b(\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s&;,"']+)/gi, (_match, key, separator) => `${key}${separator}...REDACTED`);
|
|
32
48
|
// Credentials embedded in a URL: scheme://user:pass@host
|
|
33
49
|
value = value.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, "$1***:***@");
|
|
34
50
|
// Redact anything that looks like a long token (40+ chars of base64-like)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
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",
|