brightspace-mcp-server 3.12.1 → 3.12.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/build/auth/auth-runner.js +51 -15
- package/build/tools/tool-helpers.js +7 -0
- package/package.json +1 -1
|
@@ -53,6 +53,14 @@ const CALL_BUDGET_MS = 55000;
|
|
|
53
53
|
* from another one) means nobody is approving.
|
|
54
54
|
*/
|
|
55
55
|
const ABANDON_MS = MFA_POLL_MS;
|
|
56
|
+
/**
|
|
57
|
+
* Calls arriving within this long of a batch's first call count as that batch
|
|
58
|
+
* (issue #212). A client running tool calls in parallel sends them within
|
|
59
|
+
* milliseconds; a model retrying after reading an answer takes a whole round
|
|
60
|
+
* trip, and each call is held up to CALL_BUDGET_MS anyway, so a retry always
|
|
61
|
+
* lands in a fresh batch and gets the number again.
|
|
62
|
+
*/
|
|
63
|
+
const WAVE_MS = 5000;
|
|
56
64
|
/**
|
|
57
65
|
* Milliseconds until a background sign-in counts as abandoned; zero or less
|
|
58
66
|
* means it already is. A caller still waiting keeps it alive outright;
|
|
@@ -84,6 +92,7 @@ function mfaPendingFailure(numberMatch) {
|
|
|
84
92
|
export class AuthProcessError extends AuthError {
|
|
85
93
|
kind;
|
|
86
94
|
numberMatch;
|
|
95
|
+
duplicate;
|
|
87
96
|
constructor(kind, message,
|
|
88
97
|
/**
|
|
89
98
|
* Entra number-match digits, when the failure is "mfaPending". Crossed
|
|
@@ -91,10 +100,17 @@ export class AuthProcessError extends AuthError {
|
|
|
91
100
|
* MFA_NUMBER_MARKER below) — already bounded to 1-3 digits at the point
|
|
92
101
|
* it was scraped from the page, so it is safe to surface verbatim.
|
|
93
102
|
*/
|
|
94
|
-
numberMatch
|
|
103
|
+
numberMatch,
|
|
104
|
+
/**
|
|
105
|
+
* Another call in the same parallel batch already carried this exact
|
|
106
|
+
* challenge to the user (issue #212), so this one should answer briefly
|
|
107
|
+
* instead of repeating the number and the instructions.
|
|
108
|
+
*/
|
|
109
|
+
duplicate = false) {
|
|
95
110
|
super(message);
|
|
96
111
|
this.kind = kind;
|
|
97
112
|
this.numberMatch = numberMatch;
|
|
113
|
+
this.duplicate = duplicate;
|
|
98
114
|
this.name = "AuthProcessError";
|
|
99
115
|
}
|
|
100
116
|
}
|
|
@@ -157,20 +173,21 @@ function forwardLines(stream, emit) {
|
|
|
157
173
|
buffered = "";
|
|
158
174
|
});
|
|
159
175
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
function dedupeChallenge(error, wave) {
|
|
177
|
+
if (!(error instanceof AuthProcessError) || error.kind !== "mfaPending" || error.duplicate)
|
|
178
|
+
return error;
|
|
179
|
+
const key = error.numberMatch ?? "";
|
|
180
|
+
if (!wave.delivered.has(key)) {
|
|
181
|
+
wave.delivered.add(key);
|
|
182
|
+
// Once a challenge has gone back to the user, the batch is over: every
|
|
183
|
+
// call already in it is a sibling, but a call arriving from now on is a
|
|
184
|
+
// RETRY -- quite possibly because the client hid this very answer -- and
|
|
185
|
+
// must get the digits in full. This is what keeps #201's guarantee.
|
|
186
|
+
wave.closed = true;
|
|
187
|
+
return error;
|
|
188
|
+
}
|
|
189
|
+
return new AuthProcessError(error.kind, error.message, error.numberMatch, true);
|
|
190
|
+
}
|
|
174
191
|
export class AuthRunner {
|
|
175
192
|
/**
|
|
176
193
|
* The login this process already started, if one is still running.
|
|
@@ -203,6 +220,12 @@ export class AuthRunner {
|
|
|
203
220
|
waiting = 0;
|
|
204
221
|
/** When the last caller left run(). */
|
|
205
222
|
lastAttendedAt = 0;
|
|
223
|
+
/**
|
|
224
|
+
* The current parallel batch and the challenges already handed to one of
|
|
225
|
+
* its calls (issue #212). The sign-in itself is shared through inFlight and
|
|
226
|
+
* childDone; this only stops every call in the batch repeating the number.
|
|
227
|
+
*/
|
|
228
|
+
wave = null;
|
|
206
229
|
scriptPath;
|
|
207
230
|
timeoutMs;
|
|
208
231
|
onProgress;
|
|
@@ -232,15 +255,27 @@ export class AuthRunner {
|
|
|
232
255
|
* the next call to join.
|
|
233
256
|
*/
|
|
234
257
|
async run(onChallenge) {
|
|
258
|
+
// Synchronous, before any await: calls in one tick cannot split a batch.
|
|
259
|
+
const wave = this.joinWave();
|
|
235
260
|
this.waiting += 1;
|
|
236
261
|
try {
|
|
237
262
|
return await this.attend(onChallenge);
|
|
238
263
|
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
throw dedupeChallenge(error, wave);
|
|
266
|
+
}
|
|
239
267
|
finally {
|
|
240
268
|
this.waiting -= 1;
|
|
241
269
|
this.lastAttendedAt = Date.now();
|
|
242
270
|
}
|
|
243
271
|
}
|
|
272
|
+
joinWave() {
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
if (!this.wave || this.wave.closed || now - this.wave.openedAt > WAVE_MS) {
|
|
275
|
+
this.wave = { openedAt: now, delivered: new Set(), closed: false };
|
|
276
|
+
}
|
|
277
|
+
return this.wave;
|
|
278
|
+
}
|
|
244
279
|
async attend(onChallenge) {
|
|
245
280
|
const startedAt = Date.now();
|
|
246
281
|
try {
|
|
@@ -427,6 +462,7 @@ export class AuthRunner {
|
|
|
427
462
|
this.childDone = null;
|
|
428
463
|
this.pendingChallenge = null;
|
|
429
464
|
this.challengeSignal = null;
|
|
465
|
+
this.wave = null;
|
|
430
466
|
}
|
|
431
467
|
});
|
|
432
468
|
trackedCompletion.catch(() => { });
|
|
@@ -122,7 +122,14 @@ const AUTH_FAILURE_GUIDANCE = {
|
|
|
122
122
|
* auth-runner.ts MFA_NUMBER_MARKER) — a bounded, pre-validated value, not
|
|
123
123
|
* arbitrary child output, so it is safe to interpolate here.
|
|
124
124
|
*/
|
|
125
|
+
const MFA_ALREADY_REPORTED = "Brightspace sign-in is already in progress; another call in this batch has the number to " +
|
|
126
|
+
"approve. Retry this call once the sign-in completes.";
|
|
125
127
|
function authFailureMessage(error) {
|
|
128
|
+
// Another call in this parallel batch already carries the number and the
|
|
129
|
+
// instructions (issue #212). Repeating them in every response made a batch
|
|
130
|
+
// unreadable; one short line is enough for this call.
|
|
131
|
+
if (error.kind === "mfaPending" && error.duplicate)
|
|
132
|
+
return MFA_ALREADY_REPORTED;
|
|
126
133
|
if (error.kind === "mfaPending" && error.numberMatch) {
|
|
127
134
|
return `Open Microsoft Authenticator and enter ${error.numberMatch} within 5 minutes. ` +
|
|
128
135
|
"Tell the user that number, then call this tool again right away without waiting for them " +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightspace-mcp-server",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.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",
|