canvas-parent-mcp 1.1.2 → 1.1.3

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.1.2"
10
+ "version": "1.1.3"
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.2",
18
+ "version": "1.1.3",
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.2",
4
+ "version": "1.1.3",
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
@@ -34856,6 +34856,8 @@ function validateFrame(raw) {
34856
34856
  return validateReady(raw);
34857
34857
  if (t === "frame")
34858
34858
  return validateEncrypted(raw);
34859
+ if (t === "pair-pending")
34860
+ return validatePairPending(raw);
34859
34861
  throw new ProtocolError(`unknown frame type: ${String(t)}`);
34860
34862
  }
34861
34863
  function validateHello(raw) {
@@ -34957,6 +34959,17 @@ function validateEncrypted(raw) {
34957
34959
  assertBase64(raw.ciphertext, "frame.ciphertext");
34958
34960
  return raw;
34959
34961
  }
34962
+ var PAIR_CODE_RE = /^\d{3}-\d{3}$/;
34963
+ function validatePairPending(raw) {
34964
+ assertString(raw.mcpId, "pair-pending.mcpId");
34965
+ if (!isValidMcpId(raw.mcpId))
34966
+ throw new ProtocolError("pair-pending.mcpId: invalid format");
34967
+ assertString(raw.pairCode, "pair-pending.pairCode");
34968
+ if (!PAIR_CODE_RE.test(raw.pairCode)) {
34969
+ throw new ProtocolError(`pair-pending.pairCode: must match XXX-XXX, got ${String(raw.pairCode)}`);
34970
+ }
34971
+ return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
34972
+ }
34960
34973
  function validateInnerFrame(raw) {
34961
34974
  assertObject(raw, "inner");
34962
34975
  const t = raw.type;
@@ -35540,7 +35553,9 @@ async function startHost(opts) {
35540
35553
  const peers = /* @__PURE__ */ new Map();
35541
35554
  const ownInnerListeners = [];
35542
35555
  const disconnectListeners = [];
35556
+ const pendingPairListeners = [];
35543
35557
  let ownSession = null;
35558
+ let ownPendingPairCode = null;
35544
35559
  let resolveOwnSession;
35545
35560
  let rejectOwnSession;
35546
35561
  let ownSessionReady;
@@ -35625,6 +35640,7 @@ async function startHost(opts) {
35625
35640
  if (extensionWs !== ws)
35626
35641
  return;
35627
35642
  ownSession = new SessionState(key);
35643
+ ownPendingPairCode = null;
35628
35644
  resolveOwnSession(ownSession);
35629
35645
  } else {
35630
35646
  const slot = peers.get(frame.mcpId);
@@ -35652,6 +35668,16 @@ async function startHost(opts) {
35652
35668
  extensionWs.send(JSON.stringify(frame));
35653
35669
  }
35654
35670
  }
35671
+ if (frame.type === "pair-pending" && identified === "extension") {
35672
+ if (frame.mcpId === opts.ownMcpId) {
35673
+ ownPendingPairCode = frame.pairCode;
35674
+ pendingPairListeners.forEach((cb) => cb(frame.pairCode));
35675
+ } else {
35676
+ const slot = peers.get(frame.mcpId);
35677
+ if (slot)
35678
+ slot.ws.send(JSON.stringify(frame));
35679
+ }
35680
+ }
35655
35681
  } catch (e) {
35656
35682
  console.error("[fetchproxy] host: message handler error:", e);
35657
35683
  try {
@@ -35697,7 +35723,11 @@ async function startHost(opts) {
35697
35723
  },
35698
35724
  onExtensionDisconnect: (cb) => {
35699
35725
  disconnectListeners.push(cb);
35700
- }
35726
+ },
35727
+ onPendingPair: (cb) => {
35728
+ pendingPairListeners.push(cb);
35729
+ },
35730
+ pendingPairCode: () => ownPendingPairCode
35701
35731
  };
35702
35732
  }
35703
35733
 
@@ -35727,36 +35757,57 @@ async function startPeer(opts) {
35727
35757
  const sessionNonce = fromB64(hello.sessionNonce);
35728
35758
  ws.send(JSON.stringify(hello));
35729
35759
  const innerListeners = [];
35760
+ const renegotiateListeners = [];
35761
+ const pendingPairListeners = [];
35730
35762
  let session = null;
35763
+ let pendingPairCode = null;
35764
+ let resolveFirstReady;
35765
+ let rejectFirstReady;
35731
35766
  const sessionPromise = new Promise((resolve, reject) => {
35732
- const onMessage = async (data) => {
35733
- try {
35734
- const raw = JSON.parse(data.toString());
35735
- const frame = validateFrame(raw);
35736
- if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
35737
- const extPub = fromB64(frame.extensionSessionPub);
35738
- const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
35739
- const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
35740
- session = new SessionState(sessionKey);
35741
- resolve(session);
35742
- return;
35767
+ resolveFirstReady = resolve;
35768
+ rejectFirstReady = reject;
35769
+ });
35770
+ const onMessage = async (data) => {
35771
+ try {
35772
+ const raw = JSON.parse(data.toString());
35773
+ const frame = validateFrame(raw);
35774
+ if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
35775
+ const extPub = fromB64(frame.extensionSessionPub);
35776
+ const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
35777
+ const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
35778
+ const isRenegotiation = session !== null;
35779
+ session = new SessionState(sessionKey);
35780
+ pendingPairCode = null;
35781
+ if (isRenegotiation) {
35782
+ renegotiateListeners.forEach((cb) => cb());
35783
+ } else {
35784
+ resolveFirstReady(session);
35743
35785
  }
35744
- if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
35745
- if (!session)
35746
- return;
35747
- if (!session.acceptInboundSeq(frame.seq))
35748
- return;
35786
+ return;
35787
+ }
35788
+ if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
35789
+ pendingPairCode = frame.pairCode;
35790
+ pendingPairListeners.forEach((cb) => cb(frame.pairCode));
35791
+ return;
35792
+ }
35793
+ if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
35794
+ if (!session)
35795
+ return;
35796
+ if (!session.acceptInboundSeq(frame.seq))
35797
+ return;
35798
+ try {
35749
35799
  const inner = await openEncryptedFrame(session.sessionKey, frame);
35750
35800
  innerListeners.forEach((cb) => cb(inner));
35801
+ } catch {
35751
35802
  }
35752
- } catch (e) {
35753
- reject(e instanceof Error ? e : new Error(String(e)));
35754
35803
  }
35755
- };
35756
- ws.on("message", onMessage);
35757
- ws.once("close", () => {
35758
- reject(new Error("peer WS closed before ready"));
35759
- });
35804
+ } catch (e) {
35805
+ rejectFirstReady(e instanceof Error ? e : new Error(String(e)));
35806
+ }
35807
+ };
35808
+ ws.on("message", onMessage);
35809
+ ws.once("close", () => {
35810
+ rejectFirstReady(new Error("peer WS closed before ready"));
35760
35811
  });
35761
35812
  sessionPromise.catch(() => {
35762
35813
  });
@@ -35764,13 +35815,21 @@ async function startPeer(opts) {
35764
35815
  ws,
35765
35816
  session: sessionPromise,
35766
35817
  sendInner: async (inner) => {
35767
- const s = await sessionPromise;
35818
+ await sessionPromise;
35819
+ const s = session;
35768
35820
  const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
35769
35821
  ws.send(JSON.stringify(sealed));
35770
35822
  },
35771
35823
  onInner: (cb) => {
35772
35824
  innerListeners.push(cb);
35773
35825
  },
35826
+ onRenegotiate: (cb) => {
35827
+ renegotiateListeners.push(cb);
35828
+ },
35829
+ onPendingPair: (cb) => {
35830
+ pendingPairListeners.push(cb);
35831
+ },
35832
+ pendingPairCode: () => pendingPairCode,
35774
35833
  close: () => ws.close()
35775
35834
  };
35776
35835
  return handle;
@@ -35895,7 +35954,11 @@ function assertUrlInDomains(field, url2, domains) {
35895
35954
  }
35896
35955
  var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
35897
35956
  var FetchproxyServer = class {
35898
- /** Set after `listen()` succeeds. Null while not listening. */
35957
+ /**
35958
+ * Bridge role. `null` until the first verb call (or an explicit
35959
+ * `connect()`) — `listen()` no longer triggers the role election
35960
+ * as of 0.5.3+. Reset to `null` on `close()`.
35961
+ */
35899
35962
  role = null;
35900
35963
  opts;
35901
35964
  hostHandle = null;
@@ -35917,6 +35980,12 @@ var FetchproxyServer = class {
35917
35980
  pendingIdb = /* @__PURE__ */ new Map();
35918
35981
  mcpId = null;
35919
35982
  identity = null;
35983
+ // 0.5.3+: in-flight role-election / handle-start promise. Set the
35984
+ // first time a verb call runs `ensureConnected`, awaited by concurrent
35985
+ // callers, cleared once the connection is up. Single source of truth
35986
+ // for "we're connecting right now" so two parallel first-calls don't
35987
+ // race the port bind.
35988
+ connectingPromise = null;
35920
35989
  constructor(opts) {
35921
35990
  if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
35922
35991
  throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
@@ -35968,23 +36037,87 @@ var FetchproxyServer = class {
35968
36037
  };
35969
36038
  }
35970
36039
  /**
35971
- * Start the WebSocket bridge. Loads the long-term identity keypair
35972
- * from disk (creating it on first call), elects the host-vs-peer
35973
- * role by attempting to bind the configured port, and stands up the
35974
- * matching handshake machinery. Idempotent only insofar as it leaves
35975
- * `role` non-null on success; calling `listen()` twice without an
35976
- * intervening `close()` is a programming error.
36040
+ * Prepare the bridge for use. Loads the long-term identity keypair
36041
+ * from disk (creating it on first call) and computes this instance's
36042
+ * `mcpId`. Does NOT bind the bridge port or dial any WebSocket — the
36043
+ * connection is established lazily on the first verb call (see
36044
+ * `ensureConnected` / `getOrConnect`).
36045
+ *
36046
+ * Pre-0.5.3 behavior: `listen()` also did role election and started
36047
+ * the host/peer immediately, which meant every configured-but-unused
36048
+ * MCP claimed bridge resources at MCP-client boot. Several MCPs
36049
+ * starting in parallel under Claude Desktop also produced noisy
36050
+ * `ERR_CONNECTION_REFUSED` errors in the extension if it raced ahead
36051
+ * of the first MCP's port bind. Deferring keeps boot quiet and
36052
+ * leaves the port unowned until something actually needs it.
36053
+ *
36054
+ * Calling `listen()` twice without an intervening `close()` is a
36055
+ * no-op (the second call's identity load is idempotent).
35977
36056
  */
35978
36057
  async listen() {
35979
- this.identity = await loadOrCreateIdentity(this.opts.serverName, this.opts.identityDir);
35980
- this.mcpId = generateMcpId(this.opts.serverName, this.opts.version);
36058
+ if (!this.identity) {
36059
+ this.identity = await loadOrCreateIdentity(this.opts.serverName, this.opts.identityDir);
36060
+ }
36061
+ if (!this.mcpId) {
36062
+ this.mcpId = generateMcpId(this.opts.serverName, this.opts.version);
36063
+ }
36064
+ }
36065
+ /**
36066
+ * Force an eager bridge connection (role-election + host/peer handle
36067
+ * start + listener wiring) without waiting for the first verb call.
36068
+ * Useful for callers that want to surface the role / connection
36069
+ * outcome at boot, or for tests whose harness dials a mock extension
36070
+ * immediately after server construction. Production MCPs that just
36071
+ * answer tool calls should NOT call this — the lazy connect via
36072
+ * `ensureConnected` will do the right thing on first use, keeping
36073
+ * boot cheap and avoiding port-bind contention for MCPs that never
36074
+ * actually get invoked.
36075
+ *
36076
+ * Idempotent: a second call after the first has resolved is a no-op
36077
+ * (the existing handle is reused). Throws if `listen()` was never
36078
+ * called.
36079
+ */
36080
+ async connect() {
36081
+ await this.ensureConnected();
36082
+ }
36083
+ /**
36084
+ * Establish the bridge connection (role-election + host/peer handle
36085
+ * start + listener wiring) the first time a verb is invoked.
36086
+ * Idempotent after the connection is up; concurrent first-callers
36087
+ * share the same in-flight promise so only one election happens.
36088
+ *
36089
+ * Throws if `listen()` was never called — the contract is that the
36090
+ * MCP author still must wire `transport.start()` at boot to load
36091
+ * identity / set mcpId, even though the WS doesn't open until a
36092
+ * verb runs.
36093
+ */
36094
+ async ensureConnected() {
36095
+ if (this.hostHandle || this.peerHandle)
36096
+ return;
36097
+ if (this.connectingPromise) {
36098
+ await this.connectingPromise;
36099
+ return;
36100
+ }
36101
+ if (!this.identity || !this.mcpId) {
36102
+ throw new Error("FetchproxyServer: ensureConnected called before listen() \u2014 call listen() at MCP boot to load identity");
36103
+ }
36104
+ this.connectingPromise = this.doConnect();
36105
+ try {
36106
+ await this.connectingPromise;
36107
+ } finally {
36108
+ this.connectingPromise = null;
36109
+ }
36110
+ }
36111
+ async doConnect() {
36112
+ const identity = this.identity;
36113
+ const mcpId = this.mcpId;
35981
36114
  const el = await electRole({ host: this.opts.host, port: this.opts.port });
35982
36115
  if (el.role === "host") {
35983
36116
  this.role = "host";
35984
36117
  this.hostHandle = await startHost({
35985
36118
  httpServer: el.server,
35986
- ownIdentity: this.identity,
35987
- ownMcpId: this.mcpId,
36119
+ ownIdentity: identity,
36120
+ ownMcpId: mcpId,
35988
36121
  ownServerName: this.opts.serverName,
35989
36122
  ownVersion: this.opts.version,
35990
36123
  ownDomains: this.opts.domains,
@@ -36000,13 +36133,16 @@ var FetchproxyServer = class {
36000
36133
  });
36001
36134
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
36002
36135
  this.hostHandle.onExtensionDisconnect(() => this.rejectAllPending());
36136
+ this.hostHandle.onPendingPair((code) => {
36137
+ this.rejectAllPending(this.pairingErrorMessage(code));
36138
+ });
36003
36139
  } else {
36004
36140
  this.role = "peer";
36005
36141
  this.peerHandle = await startPeer({
36006
36142
  host: this.opts.host,
36007
36143
  port: this.opts.port,
36008
- identity: this.identity,
36009
- mcpId: this.mcpId,
36144
+ identity,
36145
+ mcpId,
36010
36146
  serverName: this.opts.serverName,
36011
36147
  version: this.opts.version,
36012
36148
  domains: this.opts.domains,
@@ -36020,8 +36156,19 @@ var FetchproxyServer = class {
36020
36156
  sessionStoragePointers: this.opts.sessionStoragePointers
36021
36157
  });
36022
36158
  this.peerHandle.onInner((inner) => this.onInner(inner));
36159
+ this.peerHandle.onRenegotiate(() => this.rejectAllPending());
36160
+ this.peerHandle.onPendingPair((code) => {
36161
+ this.rejectAllPending(this.pairingErrorMessage(code));
36162
+ });
36163
+ if (this.opts.onPairCode) {
36164
+ const cb = this.opts.onPairCode;
36165
+ this.peerHandle.onPendingPair((code) => cb(code));
36166
+ }
36023
36167
  }
36024
36168
  }
36169
+ pairingErrorMessage(code) {
36170
+ return `fetchproxy: pairing required for ${this.opts.serverName} \u2014 open the fetchproxy extension popup in Chrome and approve the pair request. Verify the pair code matches: ${code}`;
36171
+ }
36025
36172
  /**
36026
36173
  * Raw single-shot fetch through the bridge. Most callers should prefer
36027
36174
  * the verb shortcuts (`get` / `post` / `getJson` / `postJson` / `getHtml`)
@@ -36037,8 +36184,11 @@ var FetchproxyServer = class {
36037
36184
  * offline, etc.).
36038
36185
  */
36039
36186
  async fetch(init) {
36040
- if (!this.hostHandle && !this.peerHandle) {
36041
- throw new Error("FetchproxyServer.fetch called before listen() \u2014 not listening");
36187
+ await this.ensureConnected();
36188
+ const pendingCode = this.currentPendingPairCode();
36189
+ if (pendingCode !== null) {
36190
+ const error51 = this.pairingErrorMessage(pendingCode);
36191
+ return { ok: false, error: error51, kind: classifyFetchError(error51) };
36042
36192
  }
36043
36193
  const id = this.nextRequestId++;
36044
36194
  const inner = { type: "request", id, op: "fetch", init };
@@ -36179,9 +36329,8 @@ var FetchproxyServer = class {
36179
36329
  if (!this.opts.capabilities.includes("read_cookies")) {
36180
36330
  throw new Error('FetchproxyServer.readCookies(): MCP did not declare "read_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
36181
36331
  }
36182
- if (!this.hostHandle && !this.peerHandle) {
36183
- throw new Error("FetchproxyServer.readCookies called before listen() \u2014 not listening");
36184
- }
36332
+ await this.ensureConnected();
36333
+ this.throwIfPendingPair();
36185
36334
  if (opts.subdomain !== void 0)
36186
36335
  assertSubdomainLabel(opts.subdomain);
36187
36336
  const baseDomain = this.resolveBaseDomain(opts.domain);
@@ -36238,9 +36387,8 @@ var FetchproxyServer = class {
36238
36387
  if (!this.opts.capabilities.includes(op)) {
36239
36388
  throw new Error(`FetchproxyServer.${op === "read_local_storage" ? "readLocalStorage" : "readSessionStorage"}(): MCP did not declare ${JSON.stringify(op)} in capabilities`);
36240
36389
  }
36241
- if (!this.hostHandle && !this.peerHandle) {
36242
- throw new Error(`FetchproxyServer.${op} called before listen() \u2014 not listening`);
36243
- }
36390
+ await this.ensureConnected();
36391
+ this.throwIfPendingPair();
36244
36392
  if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
36245
36393
  throw new Error(`FetchproxyServer.${op}: opts.keys must be a non-empty array`);
36246
36394
  }
@@ -36295,9 +36443,8 @@ var FetchproxyServer = class {
36295
36443
  if (!this.opts.capabilities.includes("capture_request_header")) {
36296
36444
  throw new Error('FetchproxyServer.captureRequestHeader(): MCP did not declare "capture_request_header" in capabilities');
36297
36445
  }
36298
- if (!this.hostHandle && !this.peerHandle) {
36299
- throw new Error("FetchproxyServer.captureRequestHeader called before listen() \u2014 not listening");
36300
- }
36446
+ await this.ensureConnected();
36447
+ this.throwIfPendingPair();
36301
36448
  const declared = this.opts.captureHeaders.find((d) => d.urlPattern === opts.urlPattern && d.headerName === opts.headerName);
36302
36449
  if (!declared) {
36303
36450
  throw new Error(`FetchproxyServer.captureRequestHeader: (urlPattern=${JSON.stringify(opts.urlPattern)}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
@@ -36338,9 +36485,8 @@ var FetchproxyServer = class {
36338
36485
  if (!this.opts.capabilities.includes("read_indexed_db")) {
36339
36486
  throw new Error('FetchproxyServer.readIndexedDb(): MCP did not declare "read_indexed_db" in capabilities');
36340
36487
  }
36341
- if (!this.hostHandle && !this.peerHandle) {
36342
- throw new Error("FetchproxyServer.readIndexedDb called before listen() \u2014 not listening");
36343
- }
36488
+ await this.ensureConnected();
36489
+ this.throwIfPendingPair();
36344
36490
  if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
36345
36491
  throw new Error("FetchproxyServer.readIndexedDb: opts.keys must be a non-empty array");
36346
36492
  }
@@ -36489,8 +36635,8 @@ var FetchproxyServer = class {
36489
36635
  }
36490
36636
  }
36491
36637
  }
36492
- rejectAllPending() {
36493
- const err = new FetchproxyProtocolError("extension disconnected");
36638
+ rejectAllPending(reason = "extension disconnected") {
36639
+ const err = new FetchproxyProtocolError(reason);
36494
36640
  for (const cb of this.pending.values()) {
36495
36641
  cb({ ok: false, error: err.message, kind: classifyFetchError(err.message) });
36496
36642
  }
@@ -36509,6 +36655,32 @@ var FetchproxyServer = class {
36509
36655
  reject(err);
36510
36656
  this.pendingIdb.clear();
36511
36657
  }
36658
+ /**
36659
+ * 0.5.2+: read the current pair-pending pair code from whichever handle
36660
+ * is active, returning null when none is pending. Public verbs call this
36661
+ * at the top so that a tool invoked while the bridge is waiting on user
36662
+ * approval fails fast with the actionable error rather than hanging on a
36663
+ * sealed frame the extension will never process.
36664
+ */
36665
+ currentPendingPairCode() {
36666
+ if (this.hostHandle)
36667
+ return this.hostHandle.pendingPairCode();
36668
+ if (this.peerHandle)
36669
+ return this.peerHandle.pendingPairCode();
36670
+ return null;
36671
+ }
36672
+ /**
36673
+ * 0.5.2+: throw `FetchproxyProtocolError` with the actionable pair-code
36674
+ * message if the bridge is waiting on user approval. Used by the verb
36675
+ * methods (readCookies, readLocalStorage, etc.) that surface errors via
36676
+ * thrown exceptions rather than `ok:false` discriminated unions.
36677
+ */
36678
+ throwIfPendingPair() {
36679
+ const code = this.currentPendingPairCode();
36680
+ if (code !== null) {
36681
+ throw new FetchproxyProtocolError(this.pairingErrorMessage(code));
36682
+ }
36683
+ }
36512
36684
  /**
36513
36685
  * Shut down the bridge. Host: terminates the WebSocket server and any
36514
36686
  * still-attached extension/peer clients. Peer: closes the upstream
@@ -36517,6 +36689,9 @@ var FetchproxyServer = class {
36517
36689
  */
36518
36690
  async close() {
36519
36691
  this.rejectAllPending();
36692
+ if (this.connectingPromise) {
36693
+ await this.connectingPromise.catch(() => void 0);
36694
+ }
36520
36695
  if (this.hostHandle)
36521
36696
  await this.hostHandle.close();
36522
36697
  if (this.peerHandle)
@@ -36524,6 +36699,7 @@ var FetchproxyServer = class {
36524
36699
  this.hostHandle = null;
36525
36700
  this.peerHandle = null;
36526
36701
  this.role = null;
36702
+ this.connectingPromise = null;
36527
36703
  }
36528
36704
  };
36529
36705
 
@@ -36772,7 +36948,7 @@ function loadAccount(env = process.env) {
36772
36948
  // package.json
36773
36949
  var package_default = {
36774
36950
  name: "canvas-parent-mcp",
36775
- version: "1.1.2",
36951
+ version: "1.1.3",
36776
36952
  mcpName: "io.github.chrischall/canvas-parent-mcp",
36777
36953
  description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
36778
36954
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -36816,17 +36992,17 @@ var package_default = {
36816
36992
  "test:watch": "vitest"
36817
36993
  },
36818
36994
  dependencies: {
36819
- "@fetchproxy/bootstrap": "^0.5.1",
36995
+ "@fetchproxy/bootstrap": "^0.6.0",
36820
36996
  "@modelcontextprotocol/sdk": "^1.29.0",
36821
- dotenv: "^17.4.0",
36822
- zod: "^4.3.6"
36997
+ dotenv: "^17.4.2",
36998
+ zod: "^4.4.3"
36823
36999
  },
36824
37000
  devDependencies: {
36825
- "@types/node": "^25.5.2",
36826
- "@vitest/coverage-v8": "^4.1.2",
37001
+ "@types/node": "^25.9.1",
37002
+ "@vitest/coverage-v8": "^4.1.7",
36827
37003
  esbuild: "^0.28.0",
36828
- typescript: "^6.0.2",
36829
- vitest: "^4.1.2"
37004
+ typescript: "^6.0.3",
37005
+ vitest: "^4.1.7"
36830
37006
  }
36831
37007
  };
36832
37008
 
@@ -37618,7 +37794,7 @@ try {
37618
37794
  } catch (e) {
37619
37795
  configError = e;
37620
37796
  }
37621
- var server = new McpServer({ name: "canvas", version: "1.1.2" });
37797
+ var server = new McpServer({ name: "canvas", version: "1.1.3" });
37622
37798
  if (resolved) {
37623
37799
  const client = new CanvasClient(resolved.account, {
37624
37800
  preloaded: resolved.preloaded
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ try {
44
44
  catch (e) {
45
45
  configError = e;
46
46
  }
47
- const server = new McpServer({ name: 'canvas', version: '1.1.2' }); // x-release-please-version
47
+ const server = new McpServer({ name: 'canvas', version: '1.1.3' }); // x-release-please-version
48
48
  if (resolved) {
49
49
  const client = new CanvasClient(resolved.account, {
50
50
  preloaded: resolved.preloaded,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvas-parent-mcp",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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,16 +44,16 @@
44
44
  "test:watch": "vitest"
45
45
  },
46
46
  "dependencies": {
47
- "@fetchproxy/bootstrap": "^0.5.1",
47
+ "@fetchproxy/bootstrap": "^0.6.0",
48
48
  "@modelcontextprotocol/sdk": "^1.29.0",
49
- "dotenv": "^17.4.0",
50
- "zod": "^4.3.6"
49
+ "dotenv": "^17.4.2",
50
+ "zod": "^4.4.3"
51
51
  },
52
52
  "devDependencies": {
53
- "@types/node": "^25.5.2",
54
- "@vitest/coverage-v8": "^4.1.2",
53
+ "@types/node": "^25.9.1",
54
+ "@vitest/coverage-v8": "^4.1.7",
55
55
  "esbuild": "^0.28.0",
56
- "typescript": "^6.0.2",
57
- "vitest": "^4.1.2"
56
+ "typescript": "^6.0.3",
57
+ "vitest": "^4.1.7"
58
58
  }
59
59
  }
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.2",
9
+ "version": "1.1.3",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "canvas-parent-mcp",
14
- "version": "1.1.2",
14
+ "version": "1.1.3",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },