canvas-parent-mcp 1.1.6 → 1.1.7
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/auth.js +10 -7
- package/dist/bundle.js +198 -51
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Canvas LMS (Instructure) — courses, grades, assignments, announcements, planner, conversations, files",
|
|
10
|
-
"version": "1.1.
|
|
10
|
+
"version": "1.1.7"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Canvas LMS",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Canvas LMS (Instructure) MCP server for Claude — student/observer access via natural language",
|
|
18
|
-
"version": "1.1.
|
|
18
|
+
"version": "1.1.7",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
3
|
"displayName": "Canvas LMS",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.7",
|
|
5
5
|
"description": "Canvas LMS (Instructure) MCP server for Claude — courses, grades, assignments, announcements, planner, conversations, files",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Chris Hall",
|
package/dist/auth.js
CHANGED
|
@@ -14,17 +14,20 @@
|
|
|
14
14
|
// most reliable mode but most schools have disabled token creation
|
|
15
15
|
// for non-admins. Unchanged from pre-fetchproxy behavior.
|
|
16
16
|
//
|
|
17
|
-
// 2.
|
|
18
|
-
// CANVAS_CLIENT_ID + CANVAS_CLIENT_SECRET + CANVAS_REFRESH_TOKEN →
|
|
19
|
-
// `grant_type=refresh_token` against `/login/oauth2/token`, bootstrap
|
|
20
|
-
// via the bundled QR-login helper. Unchanged.
|
|
21
|
-
//
|
|
22
|
-
// 3. Username/password session-scrape (existing)
|
|
17
|
+
// 2. Username/password session-scrape (existing)
|
|
23
18
|
// CANVAS_USERNAME + CANVAS_PASSWORD → scrape `authenticity_token`
|
|
24
19
|
// from `/login/canvas`, POST creds, capture `canvas_session` +
|
|
25
20
|
// `pseudonym_credentials` cookies. Brittle (breaks on SSO/2FA and
|
|
26
21
|
// every Canvas login-page restyling) but works for direct Canvas
|
|
27
|
-
// accounts.
|
|
22
|
+
// accounts. Takes precedence over OAuth (path 3) — see the
|
|
23
|
+
// precedence order in `loadAccount()` (config.ts), which returns the
|
|
24
|
+
// username/password account before checking the OAuth triple.
|
|
25
|
+
// Unchanged from pre-fetchproxy behavior.
|
|
26
|
+
//
|
|
27
|
+
// 3. OAuth refresh token (existing)
|
|
28
|
+
// CANVAS_CLIENT_ID + CANVAS_CLIENT_SECRET + CANVAS_REFRESH_TOKEN →
|
|
29
|
+
// `grant_type=refresh_token` against `/login/oauth2/token`, bootstrap
|
|
30
|
+
// via the bundled QR-login helper. Unchanged.
|
|
28
31
|
//
|
|
29
32
|
// 4. fetchproxy fallback (new)
|
|
30
33
|
// When no env vars are set, lift the user's session out of their
|
package/dist/bundle.js
CHANGED
|
@@ -13,7 +13,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
13
13
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
14
|
});
|
|
15
15
|
var __commonJS = (cb, mod) => function __require2() {
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
throw mod = 0, e;
|
|
20
|
+
}
|
|
17
21
|
};
|
|
18
22
|
var __export = (target, all) => {
|
|
19
23
|
for (var name in all)
|
|
@@ -35743,10 +35747,20 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
35743
35747
|
|
|
35744
35748
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
35745
35749
|
import { createServer, Server as HttpServer } from "node:http";
|
|
35750
|
+
var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
35746
35751
|
async function electRole(opts) {
|
|
35747
35752
|
const server = createServer();
|
|
35753
|
+
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
35748
35754
|
return new Promise((resolve, reject) => {
|
|
35755
|
+
let timer;
|
|
35756
|
+
const clearBindTimer = () => {
|
|
35757
|
+
if (timer) {
|
|
35758
|
+
clearTimeout(timer);
|
|
35759
|
+
timer = void 0;
|
|
35760
|
+
}
|
|
35761
|
+
};
|
|
35749
35762
|
const onError = (e) => {
|
|
35763
|
+
clearBindTimer();
|
|
35750
35764
|
server.removeListener("listening", onListening);
|
|
35751
35765
|
if (e.code === "EADDRINUSE") {
|
|
35752
35766
|
try {
|
|
@@ -35759,11 +35773,25 @@ async function electRole(opts) {
|
|
|
35759
35773
|
}
|
|
35760
35774
|
};
|
|
35761
35775
|
const onListening = () => {
|
|
35776
|
+
clearBindTimer();
|
|
35762
35777
|
server.removeListener("error", onError);
|
|
35763
35778
|
resolve({ role: "host", server });
|
|
35764
35779
|
};
|
|
35765
35780
|
server.once("error", onError);
|
|
35766
35781
|
server.once("listening", onListening);
|
|
35782
|
+
if (bindTimeoutMs > 0) {
|
|
35783
|
+
timer = setTimeout(() => {
|
|
35784
|
+
server.removeListener("error", onError);
|
|
35785
|
+
server.removeListener("listening", onListening);
|
|
35786
|
+
try {
|
|
35787
|
+
server.close();
|
|
35788
|
+
} catch {
|
|
35789
|
+
}
|
|
35790
|
+
reject(new Error(`fetchproxy: bind to ${opts.host}:${opts.port} timed out after ${bindTimeoutMs}ms (port may be in a bad state)`));
|
|
35791
|
+
}, bindTimeoutMs);
|
|
35792
|
+
if (typeof timer.unref === "function")
|
|
35793
|
+
timer.unref();
|
|
35794
|
+
}
|
|
35767
35795
|
server.listen(opts.port, opts.host);
|
|
35768
35796
|
});
|
|
35769
35797
|
}
|
|
@@ -35856,6 +35884,44 @@ var SessionState = class {
|
|
|
35856
35884
|
}
|
|
35857
35885
|
};
|
|
35858
35886
|
|
|
35887
|
+
// node_modules/@fetchproxy/server/dist/session-ready.js
|
|
35888
|
+
var SESSION_READY_TIMEOUT_MS = 3e4;
|
|
35889
|
+
var FetchproxySessionNotReadyError = class extends Error {
|
|
35890
|
+
reason;
|
|
35891
|
+
pairCode;
|
|
35892
|
+
mcpId;
|
|
35893
|
+
hint;
|
|
35894
|
+
constructor(info) {
|
|
35895
|
+
const pairing = info.pairCode !== null && info.pairCode !== "";
|
|
35896
|
+
const hint = pairing ? `Open the Transporter extension popup and approve pair code ${info.pairCode} for "${info.mcpId}", then retry.` : `The extension is connected but hasn't confirmed a session for "${info.mcpId}" \u2014 sign in to the target site in that browser (and approve the requested scope if it changed), then retry.`;
|
|
35897
|
+
super(`fetchproxy: ${pairing ? "pairing not yet approved" : "no confirmed browser session"} for "${info.mcpId}". ${hint}`);
|
|
35898
|
+
this.name = "FetchproxySessionNotReadyError";
|
|
35899
|
+
this.reason = pairing ? "pair-required" : "not-ready";
|
|
35900
|
+
this.pairCode = pairing ? info.pairCode : null;
|
|
35901
|
+
this.mcpId = info.mcpId;
|
|
35902
|
+
this.hint = hint;
|
|
35903
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
35904
|
+
}
|
|
35905
|
+
};
|
|
35906
|
+
async function awaitSessionReady(ready, opts) {
|
|
35907
|
+
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
35908
|
+
if (ms <= 0)
|
|
35909
|
+
return ready;
|
|
35910
|
+
let timer;
|
|
35911
|
+
const timeout = new Promise((_, reject) => {
|
|
35912
|
+
timer = setTimeout(() => {
|
|
35913
|
+
reject(new FetchproxySessionNotReadyError({ mcpId: opts.mcpId, pairCode: opts.pendingPairCode() }));
|
|
35914
|
+
}, ms);
|
|
35915
|
+
timer.unref?.();
|
|
35916
|
+
});
|
|
35917
|
+
try {
|
|
35918
|
+
return await Promise.race([ready, timeout]);
|
|
35919
|
+
} finally {
|
|
35920
|
+
if (timer)
|
|
35921
|
+
clearTimeout(timer);
|
|
35922
|
+
}
|
|
35923
|
+
}
|
|
35924
|
+
|
|
35859
35925
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
35860
35926
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
35861
35927
|
var enc2 = new TextEncoder();
|
|
@@ -35943,6 +36009,29 @@ async function startHost(opts) {
|
|
|
35943
36009
|
return;
|
|
35944
36010
|
}
|
|
35945
36011
|
if (frame.type === "hello" && frame.role === "server") {
|
|
36012
|
+
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
36013
|
+
const peerSigMsg = concatBytes(enc2.encode(frame.mcpId), fromB64(frame.sessionNonce));
|
|
36014
|
+
const peerSig = fromB64(frame.sessionSig);
|
|
36015
|
+
let peerSigOk = false;
|
|
36016
|
+
try {
|
|
36017
|
+
peerSigOk = await ed25519Verify(peerEdPub, peerSigMsg, peerSig);
|
|
36018
|
+
} catch {
|
|
36019
|
+
peerSigOk = false;
|
|
36020
|
+
}
|
|
36021
|
+
if (!peerSigOk) {
|
|
36022
|
+
console.warn("[fetchproxy] peer hello signature invalid \u2014 refusing registration (possible squatter)");
|
|
36023
|
+
ws.close(1008, "peer hello signature invalid");
|
|
36024
|
+
return;
|
|
36025
|
+
}
|
|
36026
|
+
const existing = peers.get(frame.mcpId);
|
|
36027
|
+
if (existing && existing.ws !== ws) {
|
|
36028
|
+
const existingEdPub = existing.helloFrame.identityEd25519Pub;
|
|
36029
|
+
if (existingEdPub !== frame.identityEd25519Pub) {
|
|
36030
|
+
console.warn("[fetchproxy] peer mcpId already mapped to a different identity \u2014 refusing (mcpId squatting)");
|
|
36031
|
+
ws.close(1008, "mcpId already registered to another identity");
|
|
36032
|
+
return;
|
|
36033
|
+
}
|
|
36034
|
+
}
|
|
35946
36035
|
identified = "peer";
|
|
35947
36036
|
peerMcpId = frame.mcpId;
|
|
35948
36037
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
@@ -36035,8 +36124,10 @@ async function startHost(opts) {
|
|
|
36035
36124
|
resetSessionPromise();
|
|
36036
36125
|
disconnectListeners.forEach((cb) => cb());
|
|
36037
36126
|
}
|
|
36038
|
-
if (identified === "peer" && peerMcpId)
|
|
36039
|
-
peers.
|
|
36127
|
+
if (identified === "peer" && peerMcpId) {
|
|
36128
|
+
if (peers.get(peerMcpId)?.ws === ws)
|
|
36129
|
+
peers.delete(peerMcpId);
|
|
36130
|
+
}
|
|
36040
36131
|
});
|
|
36041
36132
|
});
|
|
36042
36133
|
return {
|
|
@@ -36052,7 +36143,10 @@ async function startHost(opts) {
|
|
|
36052
36143
|
});
|
|
36053
36144
|
}),
|
|
36054
36145
|
sendOwnInner: async (inner) => {
|
|
36055
|
-
const session = await ownSessionReady
|
|
36146
|
+
const session = await awaitSessionReady(ownSessionReady, {
|
|
36147
|
+
mcpId: opts.ownMcpId,
|
|
36148
|
+
pendingPairCode: () => ownPendingPairCode
|
|
36149
|
+
});
|
|
36056
36150
|
if (!extensionWs)
|
|
36057
36151
|
throw new Error("host: no extension connected");
|
|
36058
36152
|
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), inner);
|
|
@@ -36157,7 +36251,10 @@ async function startPeer(opts) {
|
|
|
36157
36251
|
ws,
|
|
36158
36252
|
session: sessionPromise,
|
|
36159
36253
|
sendInner: async (inner) => {
|
|
36160
|
-
await sessionPromise
|
|
36254
|
+
await awaitSessionReady(sessionPromise, {
|
|
36255
|
+
mcpId: opts.mcpId,
|
|
36256
|
+
pendingPairCode: () => pendingPairCode
|
|
36257
|
+
});
|
|
36161
36258
|
const s = session;
|
|
36162
36259
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
36163
36260
|
ws.send(JSON.stringify(sealed));
|
|
@@ -36823,6 +36920,35 @@ var FetchproxyServer = class {
|
|
|
36823
36920
|
this.keepAliveTimer = null;
|
|
36824
36921
|
}
|
|
36825
36922
|
}
|
|
36923
|
+
/**
|
|
36924
|
+
* Send an inner request frame via whichever bridge handle is active. If the
|
|
36925
|
+
* send throws (e.g. `FetchproxySessionNotReadyError` — the session never
|
|
36926
|
+
* confirmed), the frame never reached the bridge, so no reply will arrive:
|
|
36927
|
+
* drop the just-registered pending resolver for this id (it lives in exactly
|
|
36928
|
+
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
36929
|
+
* server closes, then rethrow.
|
|
36930
|
+
*/
|
|
36931
|
+
async sendInnerFrame(inner) {
|
|
36932
|
+
try {
|
|
36933
|
+
if (this.hostHandle) {
|
|
36934
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
36935
|
+
} else if (this.peerHandle) {
|
|
36936
|
+
await this.peerHandle.sendInner(inner);
|
|
36937
|
+
}
|
|
36938
|
+
} catch (err) {
|
|
36939
|
+
if ("id" in inner && typeof inner.id === "number") {
|
|
36940
|
+
const { id } = inner;
|
|
36941
|
+
this.pending.delete(id);
|
|
36942
|
+
this.pendingReadCookies.delete(id);
|
|
36943
|
+
this.pendingStorage.delete(id);
|
|
36944
|
+
this.pendingCapture.delete(id);
|
|
36945
|
+
this.pendingRedirect.delete(id);
|
|
36946
|
+
this.pendingDownload.delete(id);
|
|
36947
|
+
this.pendingIdb.delete(id);
|
|
36948
|
+
}
|
|
36949
|
+
throw err;
|
|
36950
|
+
}
|
|
36951
|
+
}
|
|
36826
36952
|
/**
|
|
36827
36953
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
36828
36954
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
@@ -36834,11 +36960,7 @@ var FetchproxyServer = class {
|
|
|
36834
36960
|
const pending = new Promise((resolve) => {
|
|
36835
36961
|
this.pending.set(id, resolve);
|
|
36836
36962
|
});
|
|
36837
|
-
|
|
36838
|
-
await this.hostHandle.sendOwnInner(inner);
|
|
36839
|
-
} else if (this.peerHandle) {
|
|
36840
|
-
await this.peerHandle.sendInner(inner);
|
|
36841
|
-
}
|
|
36963
|
+
await this.sendInnerFrame(inner);
|
|
36842
36964
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
36843
36965
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
36844
36966
|
return pending;
|
|
@@ -36867,6 +36989,51 @@ var FetchproxyServer = class {
|
|
|
36867
36989
|
clearTimeout(timer);
|
|
36868
36990
|
}
|
|
36869
36991
|
}
|
|
36992
|
+
/**
|
|
36993
|
+
* FP-B2: bound a non-`fetch` verb's reply wait by `fetchTimeoutMs`.
|
|
36994
|
+
*
|
|
36995
|
+
* Before this, only `fetch()` raced its `pending` reply against a timer
|
|
36996
|
+
* (`_fetchOnceWithTimeout`); `readCookies`, the storage reads,
|
|
36997
|
+
* `capture_request_header`, `capture_redirect`, `download`, and
|
|
36998
|
+
* `read_indexed_db` awaited their `pending` promise with no race, so a
|
|
36999
|
+
* wedged extension hung the tool call indefinitely.
|
|
37000
|
+
*
|
|
37001
|
+
* `pending` is the already-registered reply promise. `pendingMap`/`id`
|
|
37002
|
+
* point at the op-specific map entry so we can drop it on expiry exactly
|
|
37003
|
+
* as `_fetchOnceWithTimeout` does — otherwise a late bridge reply would
|
|
37004
|
+
* resolve into a stale resolver / leak. On timeout we reject with a
|
|
37005
|
+
* `FetchproxyTimeoutError` (the same throwable the convenience methods
|
|
37006
|
+
* already surface for fetch timeouts). `0`/unset opts out (unbounded),
|
|
37007
|
+
* matching the fetch path.
|
|
37008
|
+
*/
|
|
37009
|
+
async _withVerbTimeout(pending, pendingMap, id, url2) {
|
|
37010
|
+
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37011
|
+
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37012
|
+
return pending;
|
|
37013
|
+
let timer;
|
|
37014
|
+
const start = Date.now();
|
|
37015
|
+
try {
|
|
37016
|
+
return await Promise.race([
|
|
37017
|
+
pending,
|
|
37018
|
+
new Promise((_resolve, reject) => {
|
|
37019
|
+
timer = setTimeout(() => {
|
|
37020
|
+
pendingMap.delete(id);
|
|
37021
|
+
reject(new FetchproxyTimeoutError({
|
|
37022
|
+
url: url2,
|
|
37023
|
+
timeoutMs,
|
|
37024
|
+
role: this.role,
|
|
37025
|
+
port: this.opts.port,
|
|
37026
|
+
elapsedMs: Date.now() - start,
|
|
37027
|
+
retryAttempted: false
|
|
37028
|
+
}));
|
|
37029
|
+
}, timeoutMs);
|
|
37030
|
+
})
|
|
37031
|
+
]);
|
|
37032
|
+
} finally {
|
|
37033
|
+
if (timer)
|
|
37034
|
+
clearTimeout(timer);
|
|
37035
|
+
}
|
|
37036
|
+
}
|
|
36870
37037
|
/**
|
|
36871
37038
|
* Map an `ok:false` fetch result to its typed throwable. Centralizes
|
|
36872
37039
|
* the kind-to-error-class switch so `request()` and (via the same
|
|
@@ -37157,12 +37324,8 @@ var FetchproxyServer = class {
|
|
|
37157
37324
|
const pending = new Promise((resolve) => {
|
|
37158
37325
|
this.pendingReadCookies.set(id, resolve);
|
|
37159
37326
|
});
|
|
37160
|
-
|
|
37161
|
-
|
|
37162
|
-
} else if (this.peerHandle) {
|
|
37163
|
-
await this.peerHandle.sendInner(inner);
|
|
37164
|
-
}
|
|
37165
|
-
const result = await pending;
|
|
37327
|
+
await this.sendInnerFrame(inner);
|
|
37328
|
+
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37166
37329
|
if (!result.ok) {
|
|
37167
37330
|
throw new FetchproxyProtocolError(result.error);
|
|
37168
37331
|
}
|
|
@@ -37228,12 +37391,8 @@ var FetchproxyServer = class {
|
|
|
37228
37391
|
const pending = new Promise((resolve, reject) => {
|
|
37229
37392
|
this.pendingStorage.set(id, { resolve, reject });
|
|
37230
37393
|
});
|
|
37231
|
-
|
|
37232
|
-
|
|
37233
|
-
} else if (this.peerHandle) {
|
|
37234
|
-
await this.peerHandle.sendInner(inner);
|
|
37235
|
-
}
|
|
37236
|
-
return pending;
|
|
37394
|
+
await this.sendInnerFrame(inner);
|
|
37395
|
+
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
37237
37396
|
}
|
|
37238
37397
|
/**
|
|
37239
37398
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
@@ -37337,12 +37496,8 @@ var FetchproxyServer = class {
|
|
|
37337
37496
|
const pending = new Promise((resolve, reject) => {
|
|
37338
37497
|
this.pendingCapture.set(id, { resolve, reject });
|
|
37339
37498
|
});
|
|
37340
|
-
|
|
37341
|
-
|
|
37342
|
-
} else if (this.peerHandle) {
|
|
37343
|
-
await this.peerHandle.sendInner(inner);
|
|
37344
|
-
}
|
|
37345
|
-
return pending;
|
|
37499
|
+
await this.sendInnerFrame(inner);
|
|
37500
|
+
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37346
37501
|
}
|
|
37347
37502
|
/**
|
|
37348
37503
|
* Snapshot the redirect target URL of the next request the browser
|
|
@@ -37426,12 +37581,8 @@ var FetchproxyServer = class {
|
|
|
37426
37581
|
const pending = new Promise((resolve, reject) => {
|
|
37427
37582
|
this.pendingRedirect.set(id, { resolve, reject });
|
|
37428
37583
|
});
|
|
37429
|
-
|
|
37430
|
-
|
|
37431
|
-
} else if (this.peerHandle) {
|
|
37432
|
-
await this.peerHandle.sendInner(inner);
|
|
37433
|
-
}
|
|
37434
|
-
return pending;
|
|
37584
|
+
await this.sendInnerFrame(inner);
|
|
37585
|
+
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37435
37586
|
}
|
|
37436
37587
|
/**
|
|
37437
37588
|
* Download `url` through the BROWSER's own network stack via
|
|
@@ -37512,12 +37663,8 @@ var FetchproxyServer = class {
|
|
|
37512
37663
|
const pending = new Promise((resolve, reject) => {
|
|
37513
37664
|
this.pendingDownload.set(id, { resolve, reject });
|
|
37514
37665
|
});
|
|
37515
|
-
|
|
37516
|
-
|
|
37517
|
-
} else if (this.peerHandle) {
|
|
37518
|
-
await this.peerHandle.sendInner(inner);
|
|
37519
|
-
}
|
|
37520
|
-
return pending;
|
|
37666
|
+
await this.sendInnerFrame(inner);
|
|
37667
|
+
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
37521
37668
|
}
|
|
37522
37669
|
/**
|
|
37523
37670
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
@@ -37564,12 +37711,8 @@ var FetchproxyServer = class {
|
|
|
37564
37711
|
const pending = new Promise((resolve, reject) => {
|
|
37565
37712
|
this.pendingIdb.set(id, { resolve, reject });
|
|
37566
37713
|
});
|
|
37567
|
-
|
|
37568
|
-
|
|
37569
|
-
} else if (this.peerHandle) {
|
|
37570
|
-
await this.peerHandle.sendInner(inner);
|
|
37571
|
-
}
|
|
37572
|
-
return pending;
|
|
37714
|
+
await this.sendInnerFrame(inner);
|
|
37715
|
+
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
37573
37716
|
}
|
|
37574
37717
|
assertScopeSubset(requested, declared, label) {
|
|
37575
37718
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -38049,7 +38192,7 @@ function loadAccount(env = process.env) {
|
|
|
38049
38192
|
// package.json
|
|
38050
38193
|
var package_default = {
|
|
38051
38194
|
name: "canvas-parent-mcp",
|
|
38052
|
-
version: "1.1.
|
|
38195
|
+
version: "1.1.7",
|
|
38053
38196
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
38054
38197
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
38055
38198
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38093,7 +38236,7 @@ var package_default = {
|
|
|
38093
38236
|
"test:watch": "vitest"
|
|
38094
38237
|
},
|
|
38095
38238
|
dependencies: {
|
|
38096
|
-
"@chrischall/mcp-utils": "^0.
|
|
38239
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
38097
38240
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
38098
38241
|
"@fetchproxy/server": "^1.0.0",
|
|
38099
38242
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38101,7 +38244,7 @@ var package_default = {
|
|
|
38101
38244
|
zod: "^4.4.3"
|
|
38102
38245
|
},
|
|
38103
38246
|
devDependencies: {
|
|
38104
|
-
"@types/node": "^
|
|
38247
|
+
"@types/node": "^26.0.0",
|
|
38105
38248
|
"@vitest/coverage-v8": "^4.1.7",
|
|
38106
38249
|
esbuild: "^0.28.0",
|
|
38107
38250
|
typescript: "^6.0.3",
|
|
@@ -38200,7 +38343,7 @@ var CookieSessionManager = class {
|
|
|
38200
38343
|
isPermanentErrorFn;
|
|
38201
38344
|
constructor(opts) {
|
|
38202
38345
|
this.loginFn = opts.login;
|
|
38203
|
-
this.isExpiredFn = opts.isExpired;
|
|
38346
|
+
this.isExpiredFn = opts.isExpired ?? (() => false);
|
|
38204
38347
|
this.isPermanentErrorFn = opts.isPermanentError ?? (() => false);
|
|
38205
38348
|
}
|
|
38206
38349
|
/** The current session, or `undefined` before the first successful login. */
|
|
@@ -38270,6 +38413,10 @@ var CookieSessionManager = class {
|
|
|
38270
38413
|
* rather than looping. If the re-login itself fails, the original
|
|
38271
38414
|
* (expired-looking) response is returned so the caller can surface a clean
|
|
38272
38415
|
* sign-in error rather than the login failure.
|
|
38416
|
+
*
|
|
38417
|
+
* `call` resolves to `R` (the generic response type, default {@link Response});
|
|
38418
|
+
* the manager passes it untouched to {@link CookieSessionManagerOptions.isExpired}
|
|
38419
|
+
* and returns it untouched, so a custom transport type flows through cleanly.
|
|
38273
38420
|
*/
|
|
38274
38421
|
async withSession(call) {
|
|
38275
38422
|
const session = await this.ensure();
|
|
@@ -39005,7 +39152,7 @@ try {
|
|
|
39005
39152
|
}
|
|
39006
39153
|
await runMcp({
|
|
39007
39154
|
name: "canvas",
|
|
39008
|
-
version: "1.1.
|
|
39155
|
+
version: "1.1.7",
|
|
39009
39156
|
// x-release-please-version
|
|
39010
39157
|
tools: [
|
|
39011
39158
|
(server) => {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"mcpName": "io.github.chrischall/canvas-parent-mcp",
|
|
5
5
|
"description": "Canvas LMS (Instructure) MCP server — student/observer read access with optional file download",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"test:watch": "vitest"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
47
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
48
48
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
49
49
|
"@fetchproxy/server": "^1.0.0",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"zod": "^4.4.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@types/node": "^
|
|
55
|
+
"@types/node": "^26.0.0",
|
|
56
56
|
"@vitest/coverage-v8": "^4.1.7",
|
|
57
57
|
"esbuild": "^0.28.0",
|
|
58
58
|
"typescript": "^6.0.3",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/canvas-parent-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.7",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "canvas-parent-mcp",
|
|
14
|
-
"version": "1.1.
|
|
14
|
+
"version": "1.1.7",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|