brightspace-mcp-server 5.1.4 → 5.1.5
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/build/api/client.js +5 -5
- package/build/utils/dev-activity.js +42 -0
- package/package.json +1 -1
package/build/api/client.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ApiError, RateLimitError, NetworkError } from "./errors.js";
|
|
|
11
11
|
import { withRetry, isRetryableFailure, retryAfterMsFrom, parseRetryAfter, } from "./retry.js";
|
|
12
12
|
import { log } from "../utils/logger.js";
|
|
13
13
|
import { AUTH_COMMAND } from "../utils/commands.js";
|
|
14
|
-
import { devActivity } from "../utils/dev-activity.js";
|
|
14
|
+
import { devActivity, httpOperation } from "../utils/dev-activity.js";
|
|
15
15
|
/**
|
|
16
16
|
* True when a followed redirect ended on this Brightspace's sign-in page. A
|
|
17
17
|
* web route asked with a dead cookie 302s there rather than serving the
|
|
@@ -426,8 +426,8 @@ export class D2LApiClient {
|
|
|
426
426
|
return send(loggedIn);
|
|
427
427
|
}
|
|
428
428
|
/** Buckets one observed HTTP status into statsData.statusClasses. */
|
|
429
|
-
recordStatus(status) {
|
|
430
|
-
devActivity("http_response", { status });
|
|
429
|
+
recordStatus(status, path) {
|
|
430
|
+
devActivity("http_response", { status, operation: httpOperation(path) });
|
|
431
431
|
const classes = this.statsData.statusClasses;
|
|
432
432
|
if (status >= 200 && status < 300)
|
|
433
433
|
classes["2xx"]++;
|
|
@@ -490,7 +490,7 @@ export class D2LApiClient {
|
|
|
490
490
|
headers,
|
|
491
491
|
signal: AbortSignal.timeout(this.timeoutMs),
|
|
492
492
|
});
|
|
493
|
-
this.recordStatus(response.status);
|
|
493
|
+
this.recordStatus(response.status, path);
|
|
494
494
|
// Preserve cookie material: a 401 only rejects this access token.
|
|
495
495
|
if (response.status === 401) {
|
|
496
496
|
throw new ApiError(401, path, "Brightspace rejected the access token.");
|
|
@@ -558,7 +558,7 @@ export class D2LApiClient {
|
|
|
558
558
|
headers,
|
|
559
559
|
signal: idle.signal,
|
|
560
560
|
});
|
|
561
|
-
this.recordStatus(response.status);
|
|
561
|
+
this.recordStatus(response.status, path);
|
|
562
562
|
// Preserve cookie material for the shared HTTP refresh path.
|
|
563
563
|
if (response.status === 401) {
|
|
564
564
|
throw new ApiError(401, path, "Brightspace rejected the access token.");
|
|
@@ -4,6 +4,46 @@ import { appendFileSync, mkdirSync, statSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { AUTH_PHASES } from "../auth/auth-phases.js";
|
|
6
6
|
import { ERROR_KINDS, errorKind, resultErrorKind } from "./error-kind.js";
|
|
7
|
+
const API = String.raw `^/d2l/api/l[pe]/[^/]+/`;
|
|
8
|
+
const COURSE = API + String.raw `\d+/`;
|
|
9
|
+
/** First match wins; a path that matches none is "other". Matched on the path alone, query dropped. */
|
|
10
|
+
const HTTP_OPERATIONS = [
|
|
11
|
+
["versions", "^/d2l/api/versions/"],
|
|
12
|
+
["whoami", API + "users/whoami"],
|
|
13
|
+
["enrollments", API + "enrollments/"],
|
|
14
|
+
["groups", COURSE + String.raw `groupcategories/\d+/groups/`],
|
|
15
|
+
["groupCategories", COURSE + "groupcategories/"],
|
|
16
|
+
["ltiLinks", API + "lti/link/"],
|
|
17
|
+
["quizList", COURSE + "quizzes/?$"],
|
|
18
|
+
["quizAttempts", COURSE + String.raw `quizzes/\d+/attempts/`],
|
|
19
|
+
["quiz", COURSE + "quizzes/"],
|
|
20
|
+
["dropboxFolders", COURSE + "dropbox/folders/?$"],
|
|
21
|
+
["dropboxSubmissions", COURSE + String.raw `dropbox/folders/\d+/submissions/`],
|
|
22
|
+
["dropboxFeedback", COURSE + String.raw `dropbox/folders/\d+/feedback/`],
|
|
23
|
+
["dropboxAttachment", COURSE + String.raw `dropbox/folders/\d+/attachments/`],
|
|
24
|
+
["dropboxFolder", COURSE + "dropbox/folders/"],
|
|
25
|
+
["dropboxFeedbackPage", "^/d2l/lms/dropbox/user/folder_user_view_feedback"],
|
|
26
|
+
["rubrics", COURSE + "rubrics"],
|
|
27
|
+
["grades", COURSE + "grades/"],
|
|
28
|
+
["contentFile", COURSE + String.raw `content/topics/\d+/file`],
|
|
29
|
+
["content", COURSE + "content/"],
|
|
30
|
+
["newsAttachment", COURSE + String.raw `news/\d+/attachments/`],
|
|
31
|
+
["news", COURSE + "news/"],
|
|
32
|
+
["discussions", COURSE + "discussions/"],
|
|
33
|
+
["overviewAttachment", COURSE + "overview/attachment"],
|
|
34
|
+
["overview", COURSE + "overview"],
|
|
35
|
+
["classlist", COURSE + "classlist/"],
|
|
36
|
+
["quickLink", "^/d2l/common/dialogs/quickLink/"],
|
|
37
|
+
// The LTI launch page a video quickLink leads to (get_video_transcript).
|
|
38
|
+
["ltiLaunch", String.raw `^/d2l/le/lti/\d+/toolLaunch/`],
|
|
39
|
+
];
|
|
40
|
+
const OPERATION_PATTERNS = HTTP_OPERATIONS.map(([name, pattern]) => [name, new RegExp(pattern)]);
|
|
41
|
+
const OPERATION_NAMES = [...HTTP_OPERATIONS.map(([name]) => name), "other"];
|
|
42
|
+
/** Names what a request was for without keeping its endpoint, IDs or query. */
|
|
43
|
+
export function httpOperation(path) {
|
|
44
|
+
const bare = path.split(/[?#]/, 1)[0];
|
|
45
|
+
return OPERATION_PATTERNS.find(([, pattern]) => pattern.test(bare))?.[0] ?? "other";
|
|
46
|
+
}
|
|
7
47
|
const calls = new AsyncLocalStorage();
|
|
8
48
|
const runId = randomUUID();
|
|
9
49
|
const MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -45,6 +85,8 @@ function write(event, fields, context = calls.getStore(), idleMs, sinceSuccessMs
|
|
|
45
85
|
record.phase = fields.phase;
|
|
46
86
|
if (ERROR_KINDS.includes(fields.errorKind ?? ""))
|
|
47
87
|
record.errorKind = fields.errorKind;
|
|
88
|
+
if (OPERATION_NAMES.includes(fields.operation ?? ""))
|
|
89
|
+
record.operation = fields.operation;
|
|
48
90
|
try {
|
|
49
91
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
50
92
|
const file = join(directory, `activity-${at.slice(0, 10)}-${process.pid}-${runId}.jsonl`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.5",
|
|
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",
|