canvas-parent-mcp 1.5.5 → 1.5.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +89 -26
- 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.5.
|
|
10
|
+
"version": "1.5.6"
|
|
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.5.
|
|
18
|
+
"version": "1.5.6",
|
|
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.5.
|
|
4
|
+
"version": "1.5.6",
|
|
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/bundle.js
CHANGED
|
@@ -9444,7 +9444,7 @@ var require_websocket = __commonJS({
|
|
|
9444
9444
|
var http = __require("http");
|
|
9445
9445
|
var net = __require("net");
|
|
9446
9446
|
var tls = __require("tls");
|
|
9447
|
-
var { randomBytes:
|
|
9447
|
+
var { randomBytes: randomBytes3, createHash } = __require("crypto");
|
|
9448
9448
|
var { Duplex, Readable } = __require("stream");
|
|
9449
9449
|
var { URL: URL2 } = __require("url");
|
|
9450
9450
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9982,7 +9982,7 @@ var require_websocket = __commonJS({
|
|
|
9982
9982
|
}
|
|
9983
9983
|
}
|
|
9984
9984
|
const defaultPort = isSecure ? 443 : 80;
|
|
9985
|
-
const key =
|
|
9985
|
+
const key = randomBytes3(16).toString("base64");
|
|
9986
9986
|
const request = isSecure ? https.request : http.request;
|
|
9987
9987
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
9988
9988
|
let perMessageDeflate;
|
|
@@ -42204,9 +42204,29 @@ var SessionState = class {
|
|
|
42204
42204
|
outboundSeq = 0;
|
|
42205
42205
|
lastInboundSeq = 0;
|
|
42206
42206
|
inflightInbound = /* @__PURE__ */ new Set();
|
|
42207
|
+
saturationWarned = false;
|
|
42207
42208
|
constructor(sessionKey) {
|
|
42208
42209
|
this.sessionKey = sessionKey;
|
|
42209
42210
|
}
|
|
42211
|
+
/**
|
|
42212
|
+
* Whether the caller holding this claim verdict should log saturation now.
|
|
42213
|
+
* True only on the TRANSITION into it: a line per dropped frame turned a
|
|
42214
|
+
* flood — or a set that never drains — into a log flood amplifying the very
|
|
42215
|
+
* condition it reported (#376). An `'ok'` claim re-arms the latch, since it
|
|
42216
|
+
* is the one signal every caller sees that the set has drained below the
|
|
42217
|
+
* bound; a replay needs no capacity and leaves it alone. Latched per
|
|
42218
|
+
* session, so a renegotiated one starts armed. A set hovering at the bound
|
|
42219
|
+
* can still alternate ok/saturated, but that logs at the rate frames finish
|
|
42220
|
+
* opening, not the rate they arrive.
|
|
42221
|
+
*/
|
|
42222
|
+
saturationWarningDue(claim2) {
|
|
42223
|
+
if (claim2 === "ok")
|
|
42224
|
+
this.saturationWarned = false;
|
|
42225
|
+
if (claim2 !== "saturated" || this.saturationWarned)
|
|
42226
|
+
return false;
|
|
42227
|
+
this.saturationWarned = true;
|
|
42228
|
+
return true;
|
|
42229
|
+
}
|
|
42210
42230
|
nextOutboundSeq() {
|
|
42211
42231
|
this.outboundSeq += 1;
|
|
42212
42232
|
return this.outboundSeq;
|
|
@@ -42217,19 +42237,21 @@ var SessionState = class {
|
|
|
42217
42237
|
* the first `await` of the receive path, or a duplicate frame read in the
|
|
42218
42238
|
* same pass will be judged against a counter neither frame has moved yet.
|
|
42219
42239
|
*
|
|
42220
|
-
*
|
|
42221
|
-
* duplicate of one still in flight
|
|
42222
|
-
*
|
|
42240
|
+
* Anything but `'ok'` means "not this frame's to process": `'replay'` for
|
|
42241
|
+
* a spent seq or a duplicate of one still in flight, `'saturated'` when the
|
|
42242
|
+
* in-flight bound is full. Replay is checked first, since refusing a stale
|
|
42243
|
+
* seq needs no capacity. Every `'ok'` MUST be answered by exactly one
|
|
42244
|
+
* {@link commitInboundSeq} or {@link releaseInboundSeq}.
|
|
42223
42245
|
*/
|
|
42224
42246
|
claimInboundSeq(seq) {
|
|
42225
42247
|
if (seq <= this.lastInboundSeq)
|
|
42226
|
-
return
|
|
42248
|
+
return "replay";
|
|
42227
42249
|
if (this.inflightInbound.has(seq))
|
|
42228
|
-
return
|
|
42250
|
+
return "replay";
|
|
42229
42251
|
if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
|
|
42230
|
-
return
|
|
42252
|
+
return "saturated";
|
|
42231
42253
|
this.inflightInbound.add(seq);
|
|
42232
|
-
return
|
|
42254
|
+
return "ok";
|
|
42233
42255
|
}
|
|
42234
42256
|
/**
|
|
42235
42257
|
* Give a claim back without spending the seq — for a frame that did not
|
|
@@ -42324,11 +42346,12 @@ async function awaitSessionReady(ready, opts) {
|
|
|
42324
42346
|
}
|
|
42325
42347
|
|
|
42326
42348
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
42327
|
-
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42349
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42328
42350
|
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
42329
42351
|
|
|
42330
42352
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
42331
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
42353
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
42354
|
+
import { randomBytes } from "node:crypto";
|
|
42332
42355
|
import { isAbsolute, join } from "node:path";
|
|
42333
42356
|
import { homedir } from "node:os";
|
|
42334
42357
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
@@ -42384,16 +42407,46 @@ function parseIdentity(text) {
|
|
|
42384
42407
|
createdAt: j.createdAt
|
|
42385
42408
|
};
|
|
42386
42409
|
}
|
|
42410
|
+
async function openIdentityDir(dir) {
|
|
42411
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
42412
|
+
try {
|
|
42413
|
+
await chmod(dir, 448);
|
|
42414
|
+
} catch (e) {
|
|
42415
|
+
const code = e.code;
|
|
42416
|
+
if (code !== "EROFS" && code !== "EPERM")
|
|
42417
|
+
throw e;
|
|
42418
|
+
}
|
|
42419
|
+
}
|
|
42420
|
+
var STAGING_BYTES = 6;
|
|
42421
|
+
async function sweepStagingFiles(dir, serverName) {
|
|
42422
|
+
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
42423
|
+
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
42424
|
+
let names;
|
|
42425
|
+
try {
|
|
42426
|
+
names = await readdir(dir);
|
|
42427
|
+
} catch {
|
|
42428
|
+
return;
|
|
42429
|
+
}
|
|
42430
|
+
await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join(dir, name)).catch(() => void 0)));
|
|
42431
|
+
}
|
|
42387
42432
|
async function writeIdentityFile(dir, serverName, id) {
|
|
42388
42433
|
const path = identityFilePath(dir, serverName);
|
|
42389
|
-
await
|
|
42390
|
-
await
|
|
42391
|
-
|
|
42434
|
+
await openIdentityDir(dir);
|
|
42435
|
+
await sweepStagingFiles(dir, serverName);
|
|
42436
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
42437
|
+
try {
|
|
42438
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
42439
|
+
await chmod(tmp, 384);
|
|
42440
|
+
await rename(tmp, path);
|
|
42441
|
+
} catch (e) {
|
|
42442
|
+
await rm(tmp, { force: true });
|
|
42443
|
+
throw e;
|
|
42444
|
+
}
|
|
42392
42445
|
return path;
|
|
42393
42446
|
}
|
|
42394
42447
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
42395
42448
|
const path = identityFilePath(dir, serverName);
|
|
42396
|
-
await
|
|
42449
|
+
await openIdentityDir(dir);
|
|
42397
42450
|
try {
|
|
42398
42451
|
return parseIdentity(await readFile(path, "utf8"));
|
|
42399
42452
|
} catch (e) {
|
|
@@ -42506,7 +42559,7 @@ async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
|
42506
42559
|
const tmp = `${path}.tmp`;
|
|
42507
42560
|
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
42508
42561
|
await chmod2(tmp, 384);
|
|
42509
|
-
await
|
|
42562
|
+
await rename2(tmp, path);
|
|
42510
42563
|
}
|
|
42511
42564
|
|
|
42512
42565
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
@@ -42861,7 +42914,11 @@ async function startHost(opts) {
|
|
|
42861
42914
|
const session = ownSession;
|
|
42862
42915
|
if (!session)
|
|
42863
42916
|
return;
|
|
42864
|
-
|
|
42917
|
+
const claim2 = session.claimInboundSeq(frame.seq);
|
|
42918
|
+
if (session.saturationWarningDue(claim2)) {
|
|
42919
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
42920
|
+
}
|
|
42921
|
+
if (claim2 !== "ok")
|
|
42865
42922
|
return;
|
|
42866
42923
|
let inner;
|
|
42867
42924
|
try {
|
|
@@ -42990,7 +43047,9 @@ async function startHost(opts) {
|
|
|
42990
43047
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
42991
43048
|
var enc5 = new TextEncoder();
|
|
42992
43049
|
async function startPeer(opts) {
|
|
42993
|
-
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}
|
|
43050
|
+
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
43051
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
43052
|
+
});
|
|
42994
43053
|
await new Promise((resolve2, reject) => {
|
|
42995
43054
|
const onOpen = () => {
|
|
42996
43055
|
cleanup();
|
|
@@ -43228,7 +43287,11 @@ async function startPeer(opts) {
|
|
|
43228
43287
|
const inboundSession = session;
|
|
43229
43288
|
if (!inboundSession)
|
|
43230
43289
|
return;
|
|
43231
|
-
|
|
43290
|
+
const claim2 = inboundSession.claimInboundSeq(frame.seq);
|
|
43291
|
+
if (inboundSession.saturationWarningDue(claim2)) {
|
|
43292
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
43293
|
+
}
|
|
43294
|
+
if (claim2 !== "ok")
|
|
43232
43295
|
return;
|
|
43233
43296
|
let result;
|
|
43234
43297
|
try {
|
|
@@ -45679,7 +45742,7 @@ function loadAccount(env = process.env) {
|
|
|
45679
45742
|
// package.json
|
|
45680
45743
|
var package_default = {
|
|
45681
45744
|
name: "canvas-parent-mcp",
|
|
45682
|
-
version: "1.5.
|
|
45745
|
+
version: "1.5.6",
|
|
45683
45746
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
45684
45747
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
45685
45748
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45727,8 +45790,8 @@ var package_default = {
|
|
|
45727
45790
|
},
|
|
45728
45791
|
dependencies: {
|
|
45729
45792
|
"@chrischall/mcp-utils": "^0.27.1",
|
|
45730
|
-
"@fetchproxy/bootstrap": "^3.0.
|
|
45731
|
-
"@fetchproxy/server": "^3.0.
|
|
45793
|
+
"@fetchproxy/bootstrap": "^3.0.1",
|
|
45794
|
+
"@fetchproxy/server": "^3.0.1",
|
|
45732
45795
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
45733
45796
|
dotenv: "^17.4.2",
|
|
45734
45797
|
zod: "^4.5.4"
|
|
@@ -45990,7 +46053,7 @@ import { writeFile as writeFile3, stat } from "fs/promises";
|
|
|
45990
46053
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
45991
46054
|
import { dirname, join as join3, resolve } from "node:path";
|
|
45992
46055
|
import { homedir as homedir2 } from "node:os";
|
|
45993
|
-
import { randomBytes, createHmac } from "node:crypto";
|
|
46056
|
+
import { randomBytes as randomBytes2, createHmac } from "node:crypto";
|
|
45994
46057
|
function bindingDigest(secret, salt) {
|
|
45995
46058
|
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
45996
46059
|
}
|
|
@@ -46042,7 +46105,7 @@ function createFileStatePersistence(opts) {
|
|
|
46042
46105
|
},
|
|
46043
46106
|
save(state) {
|
|
46044
46107
|
const dir = dirname(filePath);
|
|
46045
|
-
const tmp = `${filePath}.tmp-${
|
|
46108
|
+
const tmp = `${filePath}.tmp-${randomBytes2(6).toString("hex")}`;
|
|
46046
46109
|
const dirExisted = existsSync(dir);
|
|
46047
46110
|
try {
|
|
46048
46111
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -46050,7 +46113,7 @@ function createFileStatePersistence(opts) {
|
|
|
46050
46113
|
chmodSync(dir, 448);
|
|
46051
46114
|
let boundTo;
|
|
46052
46115
|
if (secret !== void 0) {
|
|
46053
|
-
const salt =
|
|
46116
|
+
const salt = randomBytes2(16).toString("hex");
|
|
46054
46117
|
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
46055
46118
|
}
|
|
46056
46119
|
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
@@ -47149,7 +47212,7 @@ try {
|
|
|
47149
47212
|
}
|
|
47150
47213
|
await runMcp({
|
|
47151
47214
|
name: "canvas",
|
|
47152
|
-
version: "1.5.
|
|
47215
|
+
version: "1.5.6",
|
|
47153
47216
|
// x-release-please-version
|
|
47154
47217
|
tools: [
|
|
47155
47218
|
// Outside the `if (resolved)` on purpose: an unconfigured server must
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ catch (e) {
|
|
|
47
47
|
// host's install-time `tools/list` succeeds.
|
|
48
48
|
await runMcp({
|
|
49
49
|
name: 'canvas',
|
|
50
|
-
version: '1.5.
|
|
50
|
+
version: '1.5.6', // x-release-please-version
|
|
51
51
|
tools: [
|
|
52
52
|
// Outside the `if (resolved)` on purpose: an unconfigured server must
|
|
53
53
|
// still expose something that can say WHY (see the tool's docblock).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvas-parent-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
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>",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@chrischall/mcp-utils": "^0.27.1",
|
|
51
|
-
"@fetchproxy/bootstrap": "^3.0.
|
|
52
|
-
"@fetchproxy/server": "^3.0.
|
|
51
|
+
"@fetchproxy/bootstrap": "^3.0.1",
|
|
52
|
+
"@fetchproxy/server": "^3.0.1",
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
54
54
|
"dotenv": "^17.4.2",
|
|
55
55
|
"zod": "^4.5.4"
|
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.5.
|
|
9
|
+
"version": "1.5.6",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "canvas-parent-mcp",
|
|
14
|
-
"version": "1.5.
|
|
14
|
+
"version": "1.5.6",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|