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.
@@ -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.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.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.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: randomBytes2, createHash } = __require("crypto");
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 = randomBytes2(16).toString("base64");
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
- * Answering false means "not this frame's to process" — replayed, or a
42221
- * duplicate of one still in flight. Every true MUST be answered by exactly
42222
- * one {@link commitInboundSeq} or {@link releaseInboundSeq}.
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 false;
42248
+ return "replay";
42227
42249
  if (this.inflightInbound.has(seq))
42228
- return false;
42250
+ return "replay";
42229
42251
  if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
42230
- return false;
42252
+ return "saturated";
42231
42253
  this.inflightInbound.add(seq);
42232
- return true;
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 mkdir(dir, { recursive: true, mode: 448 });
42390
- await writeFile(path, serializeIdentity(id), { mode: 384 });
42391
- await chmod(path, 384);
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 mkdir(dir, { recursive: true, mode: 448 });
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 rename(tmp, path);
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
- if (!session.claimInboundSeq(frame.seq))
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
- if (!inboundSession.claimInboundSeq(frame.seq))
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.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.0",
45731
- "@fetchproxy/server": "^3.0.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-${randomBytes(6).toString("hex")}`;
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 = randomBytes(16).toString("hex");
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.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.5', // x-release-please-version
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.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.0",
52
- "@fetchproxy/server": "^3.0.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.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.5",
14
+ "version": "1.5.6",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },