brightspace-mcp-server 5.1.5 → 5.1.6
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/auth/auth-runner.js +8 -6
- package/build/auth-cli.js +1 -1
- package/build/index.js +1 -1
- package/build/utils/dev-activity.js +27 -3
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import { log } from "../utils/logger.js";
|
|
11
11
|
import { AuthError } from "../utils/errors.js";
|
|
12
12
|
import { AUTH_COMMAND } from "../utils/commands.js";
|
|
13
|
-
import { devActivity } from "../utils/dev-activity.js";
|
|
13
|
+
import { devActivity, newRecoveryId, RECOVERY_ID_ENV } from "../utils/dev-activity.js";
|
|
14
14
|
import { parsePhaseMarker } from "./auth-phases.js";
|
|
15
15
|
import { AUTOMATIC_PENDING_MARKER, RELAYED_CHALLENGE_MARKER, RELAYED_STARTING_MARKER, authLockPath, challengeRelayedAt, relayChallenge } from "./mfa-challenge.js";
|
|
16
16
|
import { inspectLockOwner } from "./auth-lock.js";
|
|
@@ -693,7 +693,8 @@ export class AuthRunner {
|
|
|
693
693
|
*/
|
|
694
694
|
async spawnAuth() {
|
|
695
695
|
const started = Date.now();
|
|
696
|
-
|
|
696
|
+
const recoveryId = newRecoveryId();
|
|
697
|
+
devActivity("recovery_started", { recoveryId });
|
|
697
698
|
log("INFO", "Auto-launching brightspace-auth...");
|
|
698
699
|
return await new Promise((resolve, reject) => {
|
|
699
700
|
const child = spawn(process.execPath, // use the same Node binary
|
|
@@ -702,7 +703,8 @@ export class AuthRunner {
|
|
|
702
703
|
// Background recovery never opens a browser over the user's work
|
|
703
704
|
// unless D2L_HEADLESS explicitly asks for one; the saved
|
|
704
705
|
// visible-browser preference applies to manual sign-in only.
|
|
705
|
-
|
|
706
|
+
// The recovery id lets the child's dev activity join this recovery.
|
|
707
|
+
env: { D2L_HEADLESS: "true", ...process.env, [RECOVERY_ID_ENV]: recoveryId },
|
|
706
708
|
stdio: ["ignore", "pipe", "pipe"],
|
|
707
709
|
detached: process.platform !== "win32",
|
|
708
710
|
windowsHide: true,
|
|
@@ -831,7 +833,7 @@ export class AuthRunner {
|
|
|
831
833
|
const firstChallenge = this.pendingChallenge === null || this.pendingChallenge.kind === "automaticPending";
|
|
832
834
|
startWatching();
|
|
833
835
|
if (firstChallenge)
|
|
834
|
-
devActivity("mfa_observed", { elapsedMs: Date.now() - started });
|
|
836
|
+
devActivity("mfa_observed", { elapsedMs: Date.now() - started, recoveryId });
|
|
835
837
|
if (firstChallenge || matched) {
|
|
836
838
|
this.pendingChallenge = { kind: "mfaPending", numberMatch: matched ?? this.pendingChallenge?.numberMatch };
|
|
837
839
|
}
|
|
@@ -872,7 +874,7 @@ export class AuthRunner {
|
|
|
872
874
|
if (childFinished)
|
|
873
875
|
return;
|
|
874
876
|
childFinished = true;
|
|
875
|
-
devActivity("recovery_finished", { outcome: error ? "error" : "success", reason: error?.kind, elapsedMs: Date.now() - started });
|
|
877
|
+
devActivity("recovery_finished", { outcome: error ? "error" : "success", reason: error?.kind, elapsedMs: Date.now() - started, recoveryId });
|
|
876
878
|
clearTimeout(timer);
|
|
877
879
|
if (killTimer)
|
|
878
880
|
clearTimeout(killTimer);
|
|
@@ -914,7 +916,7 @@ export class AuthRunner {
|
|
|
914
916
|
const numberMarker = MFA_NUMBER_MARKER.exec(line);
|
|
915
917
|
const phaseMarker = parsePhaseMarker(line);
|
|
916
918
|
if (phaseMarker) {
|
|
917
|
-
devActivity("auth_phase", phaseMarker);
|
|
919
|
+
devActivity("auth_phase", { ...phaseMarker, recoveryId });
|
|
918
920
|
}
|
|
919
921
|
else if (numberMarker) {
|
|
920
922
|
numberMatch = numberMarker[1];
|
package/build/auth-cli.js
CHANGED
|
@@ -59,7 +59,7 @@ async function main() {
|
|
|
59
59
|
const config = await loadConfig();
|
|
60
60
|
// The sign-in itself runs here, not in the server, so the dev activity log
|
|
61
61
|
// only sees its MFA events if this process is configured for them too.
|
|
62
|
-
configureDevActivity(config.sessionDir);
|
|
62
|
+
configureDevActivity(config.sessionDir, { version: pkg.version, role: "auth" });
|
|
63
63
|
console.error(`\n=== Brightspace Authentication v${pkg.version} ===\n`);
|
|
64
64
|
console.error(config.headless
|
|
65
65
|
? "Authentication runs headlessly. MFA numbers and code prompts appear here."
|
package/build/index.js
CHANGED
|
@@ -96,7 +96,7 @@ else {
|
|
|
96
96
|
const { setActiveShortcuts } = await import("./utils/two-factor-shortcuts.js");
|
|
97
97
|
const { startUpdateChecks } = await import("./utils/update-checker.js");
|
|
98
98
|
const { createRuntime } = await import("./server.js");
|
|
99
|
-
configureDevActivity(config.sessionDir);
|
|
99
|
+
configureDevActivity(config.sessionDir, { version: PKG_VERSION, role: "server" });
|
|
100
100
|
// Lets the MFA approval message mention the setup options only to users
|
|
101
101
|
// who have neither turned on. Booleans only; the key is never passed.
|
|
102
102
|
setActiveShortcuts({ rememberMfa: config.rememberMfa === true, keySaved: Boolean(config.totpUri) });
|
|
@@ -44,27 +44,40 @@ export function httpOperation(path) {
|
|
|
44
44
|
const bare = path.split(/[?#]/, 1)[0];
|
|
45
45
|
return OPERATION_PATTERNS.find(([, pattern]) => pattern.test(bare))?.[0] ?? "other";
|
|
46
46
|
}
|
|
47
|
+
/** Names one background recovery; the parent passes it to its auth child so their events join. */
|
|
48
|
+
export const RECOVERY_ID_ENV = "D2L_RECOVERY_ID";
|
|
49
|
+
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
47
50
|
const calls = new AsyncLocalStorage();
|
|
48
51
|
const runId = randomUUID();
|
|
49
52
|
const MAX_BYTES = 5 * 1024 * 1024;
|
|
50
53
|
let directory;
|
|
51
54
|
let warned = false;
|
|
55
|
+
/** Set in an auth child: the parent recovery every event of this process belongs to. */
|
|
56
|
+
let processRecoveryId;
|
|
52
57
|
let lastStart;
|
|
53
58
|
let lastSuccess;
|
|
54
59
|
/** Explicit opt-in; separate from ordinary stderr debugging. */
|
|
55
|
-
export function configureDevActivity(sessionDir) {
|
|
60
|
+
export function configureDevActivity(sessionDir, run) {
|
|
56
61
|
directory = process.env.D2L_DEV_MODE === "true" ? join(sessionDir, "dev-activity") : undefined;
|
|
57
62
|
lastStart = lastSuccess = undefined;
|
|
58
63
|
warned = false;
|
|
64
|
+
const inherited = process.env[RECOVERY_ID_ENV];
|
|
65
|
+
processRecoveryId = inherited && ID.test(inherited) ? inherited : undefined;
|
|
66
|
+
if (run)
|
|
67
|
+
write("run_started", {}, undefined, undefined, undefined, run);
|
|
68
|
+
}
|
|
69
|
+
/** A fresh, non-secret id for one recovery; see RECOVERY_ID_ENV. */
|
|
70
|
+
export function newRecoveryId() {
|
|
71
|
+
return randomUUID();
|
|
59
72
|
}
|
|
60
73
|
/** No arbitrary text, errors, URLs, request arguments or response content. */
|
|
61
74
|
export function devActivity(event, fields = {}) {
|
|
62
75
|
write(event, fields);
|
|
63
76
|
}
|
|
64
|
-
function write(event, fields, context = calls.getStore(), idleMs, sinceSuccessMs) {
|
|
77
|
+
function write(event, fields, context = calls.getStore(), idleMs, sinceSuccessMs, run) {
|
|
65
78
|
if (!directory)
|
|
66
79
|
return;
|
|
67
|
-
if (!["tool_started", "tool_finished", "http_response", "auth_required", "token_mint_started",
|
|
80
|
+
if (!["run_started", "tool_started", "tool_finished", "http_response", "auth_required", "token_mint_started",
|
|
68
81
|
"token_mint_finished", "recovery_started", "mfa_observed", "recovery_finished", "auth_phase",
|
|
69
82
|
"mfa_method_selected", "mfa_code_submitted"].includes(event))
|
|
70
83
|
return;
|
|
@@ -72,6 +85,17 @@ function write(event, fields, context = calls.getStore(), idleMs, sinceSuccessMs
|
|
|
72
85
|
const record = { at, runId, pid: process.pid, event };
|
|
73
86
|
if (context)
|
|
74
87
|
Object.assign(record, context);
|
|
88
|
+
// A child's inherited id wins: it is the one its parent recorded.
|
|
89
|
+
const recoveryId = processRecoveryId ?? fields.recoveryId;
|
|
90
|
+
if (recoveryId && ID.test(recoveryId))
|
|
91
|
+
record.recoveryId = recoveryId;
|
|
92
|
+
if (run) {
|
|
93
|
+
if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,40})?$/.test(run.version))
|
|
94
|
+
record.version = run.version;
|
|
95
|
+
record.node = process.versions.node;
|
|
96
|
+
if (run.role === "server" || run.role === "auth")
|
|
97
|
+
record.role = run.role;
|
|
98
|
+
}
|
|
75
99
|
for (const [key, value] of Object.entries({ elapsedMs: fields.elapsedMs, status: fields.status, idleMs, sinceSuccessMs })) {
|
|
76
100
|
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
77
101
|
record[key] = value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.6",
|
|
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",
|