canvas-parent-mcp 1.2.0 → 1.2.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.
@@ -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.2.0"
10
+ "version": "1.2.2"
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.2.0",
18
+ "version": "1.2.2",
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.2.0",
4
+ "version": "1.2.2",
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
@@ -3776,6 +3776,7 @@ var require_fast_uri = __commonJS({
3776
3776
  return uriTokens.join("");
3777
3777
  }
3778
3778
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3779
+ var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3779
3780
  function getParseError(parsed, matches) {
3780
3781
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3781
3782
  return 'URI path must start with "/" when authority is present.';
@@ -3805,6 +3806,11 @@ var require_fast_uri = __commonJS({
3805
3806
  uri = "//" + uri;
3806
3807
  }
3807
3808
  }
3809
+ const authorityMatch = uri.match(AUTHORITY_PREFIX);
3810
+ if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
3811
+ parsed.error = "URI authority must not contain a literal backslash.";
3812
+ malformedAuthorityOrPort = true;
3813
+ }
3808
3814
  const matches = uri.match(URI_PARSE);
3809
3815
  if (matches) {
3810
3816
  parsed.scheme = matches[1];
@@ -3848,7 +3854,7 @@ var require_fast_uri = __commonJS({
3848
3854
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3849
3855
  if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3850
3856
  try {
3851
- parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
3857
+ parsed.host = new URL("http://" + parsed.host).hostname;
3852
3858
  } catch (e) {
3853
3859
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3854
3860
  }
@@ -7693,6 +7699,7 @@ var require_receiver = __commonJS({
7693
7699
  this._opcode = 0;
7694
7700
  this._totalPayloadLength = 0;
7695
7701
  this._messageLength = 0;
7702
+ this._numFragments = 0;
7696
7703
  this._fragments = [];
7697
7704
  this._errored = false;
7698
7705
  this._loop = false;
@@ -8043,23 +8050,23 @@ var require_receiver = __commonJS({
8043
8050
  this.controlMessage(data, cb);
8044
8051
  return;
8045
8052
  }
8053
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
8054
+ const error51 = this.createError(
8055
+ RangeError,
8056
+ "Too many message fragments",
8057
+ false,
8058
+ 1008,
8059
+ "WS_ERR_TOO_MANY_BUFFERED_PARTS"
8060
+ );
8061
+ cb(error51);
8062
+ return;
8063
+ }
8046
8064
  if (this._compressed) {
8047
8065
  this._state = INFLATING;
8048
8066
  this.decompress(data, cb);
8049
8067
  return;
8050
8068
  }
8051
8069
  if (data.length) {
8052
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
8053
- const error51 = this.createError(
8054
- RangeError,
8055
- "Too many message fragments",
8056
- false,
8057
- 1008,
8058
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
8059
- );
8060
- cb(error51);
8061
- return;
8062
- }
8063
8070
  this._messageLength = this._totalPayloadLength;
8064
8071
  this._fragments.push(data);
8065
8072
  }
@@ -8089,17 +8096,6 @@ var require_receiver = __commonJS({
8089
8096
  cb(error51);
8090
8097
  return;
8091
8098
  }
8092
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
8093
- const error51 = this.createError(
8094
- RangeError,
8095
- "Too many message fragments",
8096
- false,
8097
- 1008,
8098
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
8099
- );
8100
- cb(error51);
8101
- return;
8102
- }
8103
8099
  this._fragments.push(buf);
8104
8100
  }
8105
8101
  this.dataMessage(cb);
@@ -8122,6 +8118,7 @@ var require_receiver = __commonJS({
8122
8118
  this._totalPayloadLength = 0;
8123
8119
  this._messageLength = 0;
8124
8120
  this._fragmented = 0;
8121
+ this._numFragments = 0;
8125
8122
  this._fragments = [];
8126
8123
  if (this._opcode === 2) {
8127
8124
  let data;
@@ -9622,8 +9619,8 @@ var require_websocket = __commonJS({
9622
9619
  autoPong: true,
9623
9620
  closeTimeout: CLOSE_TIMEOUT,
9624
9621
  protocolVersion: protocolVersions[1],
9625
- maxBufferedChunks: 1024 * 1024,
9626
- maxFragments: 128 * 1024,
9622
+ maxBufferedChunks: 256 * 1024,
9623
+ maxFragments: 16 * 1024,
9627
9624
  maxPayload: 100 * 1024 * 1024,
9628
9625
  skipUTF8Validation: false,
9629
9626
  perMessageDeflate: true,
@@ -10210,9 +10207,9 @@ var require_websocket_server = __commonJS({
10210
10207
  * called
10211
10208
  * @param {Function} [options.handleProtocols] A hook to handle protocols
10212
10209
  * @param {String} [options.host] The hostname where to bind the server
10213
- * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
10210
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
10214
10211
  * buffered data chunks
10215
- * @param {Number} [options.maxFragments=131072] The maximum number of message
10212
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
10216
10213
  * fragments
10217
10214
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
10218
10215
  * size
@@ -10235,8 +10232,8 @@ var require_websocket_server = __commonJS({
10235
10232
  options = {
10236
10233
  allowSynchronousEvents: true,
10237
10234
  autoPong: true,
10238
- maxBufferedChunks: 1024 * 1024,
10239
- maxFragments: 128 * 1024,
10235
+ maxBufferedChunks: 256 * 1024,
10236
+ maxFragments: 16 * 1024,
10240
10237
  maxPayload: 100 * 1024 * 1024,
10241
10238
  skipUTF8Validation: false,
10242
10239
  perMessageDeflate: false,
@@ -34990,7 +34987,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
34990
34987
  "capture_redirect",
34991
34988
  "read_indexed_db",
34992
34989
  "read_dom",
34993
- "download"
34990
+ "download",
34991
+ "graphql"
34994
34992
  ]);
34995
34993
 
34996
34994
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35293,6 +35291,7 @@ function assertIndexedDbScopesArray(value, label) {
35293
35291
  }
35294
35292
  var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
35295
35293
  var DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
35294
+ var GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
35296
35295
  function assertDomSelectorsArray(value, label) {
35297
35296
  if (!Array.isArray(value)) {
35298
35297
  throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
@@ -35329,6 +35328,37 @@ function assertDomSelectorsArray(value, label) {
35329
35328
  }
35330
35329
  }
35331
35330
  }
35331
+ function assertGraphqlOpsArray(value, label) {
35332
+ if (!Array.isArray(value)) {
35333
+ throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
35334
+ }
35335
+ const seen = /* @__PURE__ */ new Set();
35336
+ for (let i = 0; i < value.length; i++) {
35337
+ const entry = value[i];
35338
+ assertObject(entry, `${label}[${i}]`);
35339
+ if (entry.name === void 0) {
35340
+ throw new ProtocolError(`${label}[${i}].name: missing`);
35341
+ }
35342
+ if (entry.operationName === void 0) {
35343
+ throw new ProtocolError(`${label}[${i}].operationName: missing`);
35344
+ }
35345
+ if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
35346
+ throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
35347
+ }
35348
+ if (typeof entry.operationName !== "string" || !GRAPHQL_OP_NAME_RE.test(entry.operationName)) {
35349
+ throw new ProtocolError(`${label}[${i}].operationName: invalid ${JSON.stringify(entry.operationName)}`);
35350
+ }
35351
+ if (seen.has(entry.name)) {
35352
+ throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
35353
+ }
35354
+ seen.add(entry.name);
35355
+ for (const k of Object.keys(entry)) {
35356
+ if (k !== "name" && k !== "operationName") {
35357
+ throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
35358
+ }
35359
+ }
35360
+ }
35361
+ }
35332
35362
  function validateFrame(raw) {
35333
35363
  assertObject(raw, "frame");
35334
35364
  const t = raw.type;
@@ -35407,6 +35437,9 @@ function validateHello(raw) {
35407
35437
  if (raw.domSelectors !== void 0) {
35408
35438
  assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
35409
35439
  }
35440
+ if (raw.graphqlOps !== void 0) {
35441
+ assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
35442
+ }
35410
35443
  assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
35411
35444
  assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
35412
35445
  assertBase64(raw.sessionNonce, "hello.sessionNonce");
@@ -35656,6 +35689,28 @@ function validateInnerRequest(raw) {
35656
35689
  }
35657
35690
  return raw;
35658
35691
  }
35692
+ if (raw.op === "graphql_query") {
35693
+ assertObject(raw.init, "inner.init");
35694
+ if (raw.init.name === void 0)
35695
+ throw new ProtocolError("inner.init.name: missing");
35696
+ if (raw.init.variables === void 0) {
35697
+ throw new ProtocolError("inner.init.variables: missing");
35698
+ }
35699
+ assertString(raw.init.name, "inner.init.name");
35700
+ if (raw.init.name.length === 0) {
35701
+ throw new ProtocolError("inner.init.name: must be non-empty");
35702
+ }
35703
+ assertObject(raw.init.variables, "inner.init.variables");
35704
+ if (raw.init.tabUrl !== void 0) {
35705
+ assertString(raw.init.tabUrl, "inner.init.tabUrl");
35706
+ }
35707
+ for (const k of Object.keys(raw.init)) {
35708
+ if (k !== "name" && k !== "variables" && k !== "tabUrl") {
35709
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on graphql_query`);
35710
+ }
35711
+ }
35712
+ return raw;
35713
+ }
35659
35714
  if (raw.op === "download") {
35660
35715
  assertObject(raw.init, "inner.init");
35661
35716
  if (raw.init.url === void 0) {
@@ -35681,7 +35736,7 @@ function validateInnerRequest(raw) {
35681
35736
  }
35682
35737
  return raw;
35683
35738
  }
35684
- throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download"; got ${JSON.stringify(raw.op)}`);
35739
+ throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query"; got ${JSON.stringify(raw.op)}`);
35685
35740
  }
35686
35741
  function assertNonEmptyKeyArray(value, label) {
35687
35742
  if (!Array.isArray(value)) {
@@ -35712,6 +35767,10 @@ function assertStringMap(value, label) {
35712
35767
  }
35713
35768
  }
35714
35769
  }
35770
+ var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
35771
+ ...KNOWN_CAPABILITIES,
35772
+ "graphql_query"
35773
+ ]);
35715
35774
  function validateInnerResponse(raw) {
35716
35775
  assertPositiveInt(raw.id, "inner.id");
35717
35776
  if (raw.ok === true) {
@@ -35773,6 +35832,13 @@ function validateInnerResponse(raw) {
35773
35832
  assertStringMap(raw.values, "inner.values");
35774
35833
  return raw;
35775
35834
  }
35835
+ if (op === "graphql_query") {
35836
+ if (raw.data === void 0) {
35837
+ throw new ProtocolError("inner.data: missing on graphql_query response");
35838
+ }
35839
+ assertObject(raw.data, "inner.data");
35840
+ return raw;
35841
+ }
35776
35842
  if (op === "download") {
35777
35843
  assertObject(raw.value, "inner.value");
35778
35844
  assertString(raw.value.path, "inner.value.path");
@@ -35797,7 +35863,7 @@ function validateInnerResponse(raw) {
35797
35863
  if (raw.ok === false) {
35798
35864
  assertString(raw.error, "inner.error");
35799
35865
  if (raw.op !== void 0) {
35800
- if (typeof raw.op !== "string" || !KNOWN_CAPABILITIES.has(raw.op)) {
35866
+ if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
35801
35867
  throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
35802
35868
  }
35803
35869
  }
@@ -35989,11 +36055,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
35989
36055
  };
35990
36056
  }
35991
36057
  async function openEncryptedFrame(sessionKey, frame) {
35992
- const iv = fromB64(frame.iv);
35993
- const ct = fromB64(frame.ciphertext);
35994
- const pt = await aesGcmOpen(sessionKey, iv, ct);
35995
- const parsed = JSON.parse(dec.decode(pt));
35996
- return validateInnerFrame(parsed);
36058
+ const result = await openEncryptedFrameDetailed(sessionKey, frame);
36059
+ if (result.stage === "ok")
36060
+ return result.inner;
36061
+ throw result.error instanceof Error ? result.error : new Error(String(result.error));
36062
+ }
36063
+ async function openEncryptedFrameDetailed(sessionKey, frame) {
36064
+ let pt;
36065
+ try {
36066
+ const iv = fromB64(frame.iv);
36067
+ const ct = fromB64(frame.ciphertext);
36068
+ pt = await aesGcmOpen(sessionKey, iv, ct);
36069
+ } catch (error51) {
36070
+ return { stage: "decrypt-failed", error: error51 };
36071
+ }
36072
+ let parsed;
36073
+ try {
36074
+ parsed = JSON.parse(dec.decode(pt));
36075
+ } catch (error51) {
36076
+ return { stage: "validation-failed", error: error51, recoveredId: void 0 };
36077
+ }
36078
+ try {
36079
+ const inner = validateInnerFrame(parsed);
36080
+ return { stage: "ok", inner };
36081
+ } catch (error51) {
36082
+ const recoveredId = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.id === "number" && Number.isInteger(parsed.id) && parsed.id > 0 ? parsed.id : void 0;
36083
+ return { stage: "validation-failed", error: error51, recoveredId };
36084
+ }
35997
36085
  }
35998
36086
 
35999
36087
  // node_modules/@fetchproxy/server/dist/election.js
@@ -36119,6 +36207,12 @@ async function buildServerHello(opts) {
36119
36207
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36120
36208
  }));
36121
36209
  }
36210
+ if (opts.graphqlOps && opts.graphqlOps.length > 0) {
36211
+ hello.graphqlOps = opts.graphqlOps.map((d) => ({
36212
+ name: d.name,
36213
+ operationName: d.operationName
36214
+ }));
36215
+ }
36122
36216
  return hello;
36123
36217
  }
36124
36218
 
@@ -36209,7 +36303,8 @@ async function startHost(opts) {
36209
36303
  indexedDbScopes: opts.ownIndexedDbScopes,
36210
36304
  localStoragePointers: opts.ownLocalStoragePointers,
36211
36305
  sessionStoragePointers: opts.ownSessionStoragePointers,
36212
- domSelectors: opts.ownDomSelectors
36306
+ domSelectors: opts.ownDomSelectors,
36307
+ graphqlOps: opts.ownGraphqlOps
36213
36308
  });
36214
36309
  const ownSessionNonce = fromB64(ownHello.sessionNonce);
36215
36310
  let extensionWs = null;
@@ -36446,7 +36541,8 @@ async function startPeer(opts) {
36446
36541
  indexedDbScopes: opts.indexedDbScopes,
36447
36542
  domSelectors: opts.domSelectors,
36448
36543
  localStoragePointers: opts.localStoragePointers,
36449
- sessionStoragePointers: opts.sessionStoragePointers
36544
+ sessionStoragePointers: opts.sessionStoragePointers,
36545
+ graphqlOps: opts.graphqlOps
36450
36546
  });
36451
36547
  const sessionNonce = fromB64(hello.sessionNonce);
36452
36548
  ws.send(JSON.stringify(hello));
@@ -36490,10 +36586,20 @@ async function startPeer(opts) {
36490
36586
  return;
36491
36587
  if (!session.acceptInboundSeq(frame.seq))
36492
36588
  return;
36493
- try {
36494
- const inner = await openEncryptedFrame(session.sessionKey, frame);
36495
- innerListeners.forEach((cb) => cb(inner));
36496
- } catch {
36589
+ const result = await openEncryptedFrameDetailed(session.sessionKey, frame);
36590
+ if (result.stage === "ok") {
36591
+ innerListeners.forEach((cb) => cb(result.inner));
36592
+ } else if (result.stage === "decrypt-failed") {
36593
+ } else {
36594
+ console.error("[fetchproxy] peer: received a frame that decrypted OK but failed validation:", result.error);
36595
+ if (result.recoveredId !== void 0) {
36596
+ innerListeners.forEach((cb) => cb({
36597
+ type: "response",
36598
+ id: result.recoveredId,
36599
+ ok: false,
36600
+ error: `malformed response failed protocol validation: ${String(result.error)}`
36601
+ }));
36602
+ }
36497
36603
  }
36498
36604
  }
36499
36605
  } catch (e) {
@@ -36770,6 +36876,10 @@ var FetchproxyServer = class {
36770
36876
  pendingIdb = /* @__PURE__ */ new Map();
36771
36877
  // download awaiters resolve the saved-file metadata (path + size + mime).
36772
36878
  pendingDownload = /* @__PURE__ */ new Map();
36879
+ // 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
36880
+ // shape is operation-specific, so the awaiter resolves `unknown` and the
36881
+ // caller narrows.
36882
+ pendingGraphql = /* @__PURE__ */ new Map();
36773
36883
  mcpId = null;
36774
36884
  identity = null;
36775
36885
  // 0.5.3+: in-flight role-election / handle-start promise. Set the
@@ -36857,6 +36967,10 @@ var FetchproxyServer = class {
36857
36967
  selector: d.selector,
36858
36968
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36859
36969
  })),
36970
+ graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
36971
+ name: d.name,
36972
+ operationName: d.operationName
36973
+ })),
36860
36974
  // 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
36861
36975
  // adapter was about to set these to the same numbers anyway; the
36862
36976
  // back-door is `0` (explicit opt-out) if a caller genuinely wants
@@ -36978,6 +37092,7 @@ var FetchproxyServer = class {
36978
37092
  ownLocalStoragePointers: this.opts.localStoragePointers,
36979
37093
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
36980
37094
  ownDomSelectors: this.opts.domSelectors,
37095
+ ownGraphqlOps: this.opts.graphqlOps,
36981
37096
  onPairCode: this.opts.onPairCode
36982
37097
  });
36983
37098
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
@@ -37006,7 +37121,8 @@ var FetchproxyServer = class {
37006
37121
  indexedDbScopes: this.opts.indexedDbScopes,
37007
37122
  localStoragePointers: this.opts.localStoragePointers,
37008
37123
  sessionStoragePointers: this.opts.sessionStoragePointers,
37009
- domSelectors: this.opts.domSelectors
37124
+ domSelectors: this.opts.domSelectors,
37125
+ graphqlOps: this.opts.graphqlOps
37010
37126
  });
37011
37127
  this.peerHandle.onInner((inner) => this.onInner(inner));
37012
37128
  this.peerHandle.onRenegotiate(() => {
@@ -37212,6 +37328,7 @@ var FetchproxyServer = class {
37212
37328
  this.pendingRedirect.delete(id);
37213
37329
  this.pendingDownload.delete(id);
37214
37330
  this.pendingIdb.delete(id);
37331
+ this.pendingGraphql.delete(id);
37215
37332
  }
37216
37333
  throw err;
37217
37334
  }
@@ -38021,6 +38138,52 @@ var FetchproxyServer = class {
38021
38138
  await this.sendInnerFrame(inner);
38022
38139
  return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
38023
38140
  }
38141
+ /**
38142
+ * 1.x+: run a declared GraphQL operation through the page's own Apollo
38143
+ * client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
38144
+ * Requires `'graphql'` in capabilities AND `name` to match a declared
38145
+ * `graphqlOps` entry. The extension resolves `name` → `operationName` →
38146
+ * the live DocumentNode the page already observed, then invokes
38147
+ * `client.query({ query, variables })` — the site's own request path, so
38148
+ * per-request bot telemetry (Akamai etc.) runs automatically.
38149
+ *
38150
+ * Returns the GraphQL `data` object on success (shape is
38151
+ * operation-specific; the caller narrows). Throws a plain `Error` on
38152
+ * developer mistakes (undeclared capability, undeclared name) and a
38153
+ * descriptive `Error` on the `ok:false` bridge path — which includes the
38154
+ * typed "operation not yet observed on this tab" case (open the site's
38155
+ * page and retry).
38156
+ */
38157
+ async graphqlQuery(opts) {
38158
+ if (!this.opts.capabilities.includes("graphql")) {
38159
+ throw new Error('FetchproxyServer.graphqlQuery(): MCP did not declare "graphql" in capabilities');
38160
+ }
38161
+ if (typeof opts.name !== "string" || opts.name.length === 0) {
38162
+ throw new Error("FetchproxyServer.graphqlQuery: opts.name must be a non-empty string");
38163
+ }
38164
+ const declaredNames = this.opts.graphqlOps.map((d) => d.name);
38165
+ if (!declaredNames.includes(opts.name)) {
38166
+ throw new Error(`FetchproxyServer.graphqlQuery: operation ${JSON.stringify(opts.name)} not in declared graphqlOps [${declaredNames.map((n) => JSON.stringify(n)).join(", ")}]`);
38167
+ }
38168
+ await this.ensureConnected();
38169
+ this.throwIfPendingPair();
38170
+ const id = this.nextRequestId++;
38171
+ const inner = {
38172
+ type: "request",
38173
+ id,
38174
+ op: "graphql_query",
38175
+ init: {
38176
+ name: opts.name,
38177
+ variables: opts.variables,
38178
+ ...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
38179
+ }
38180
+ };
38181
+ const pending = new Promise((resolve, reject) => {
38182
+ this.pendingGraphql.set(id, { resolve, reject });
38183
+ });
38184
+ await this.sendInnerFrame(inner);
38185
+ return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
38186
+ }
38024
38187
  assertScopeSubset(requested, declared, label) {
38025
38188
  const undeclared = undeclaredKeys(requested, declared);
38026
38189
  if (undeclared.length > 0) {
@@ -38158,6 +38321,20 @@ var FetchproxyServer = class {
38158
38321
  }
38159
38322
  return;
38160
38323
  }
38324
+ const graphqlCb = this.pendingGraphql.get(inner.id);
38325
+ if (graphqlCb) {
38326
+ this.pendingGraphql.delete(inner.id);
38327
+ if (inner.ok) {
38328
+ if (inner.op === "graphql_query") {
38329
+ graphqlCb.resolve(inner.data);
38330
+ } else {
38331
+ graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
38332
+ }
38333
+ } else {
38334
+ graphqlCb.reject(new FetchproxyProtocolError(inner.error));
38335
+ }
38336
+ return;
38337
+ }
38161
38338
  const cookiesCb = this.pendingReadCookies.get(inner.id);
38162
38339
  if (cookiesCb) {
38163
38340
  this.pendingReadCookies.delete(inner.id);
@@ -38209,6 +38386,9 @@ var FetchproxyServer = class {
38209
38386
  for (const { reject } of this.pendingDownload.values())
38210
38387
  reject(err);
38211
38388
  this.pendingDownload.clear();
38389
+ for (const { reject } of this.pendingGraphql.values())
38390
+ reject(err);
38391
+ this.pendingGraphql.clear();
38212
38392
  }
38213
38393
  /**
38214
38394
  * 0.5.2+: read the current pair-pending pair code from whichever handle
@@ -38499,7 +38679,7 @@ function loadAccount(env = process.env) {
38499
38679
  // package.json
38500
38680
  var package_default = {
38501
38681
  name: "canvas-parent-mcp",
38502
- version: "1.2.0",
38682
+ version: "1.2.2",
38503
38683
  mcpName: "io.github.chrischall/canvas-parent-mcp",
38504
38684
  description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
38505
38685
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -38543,9 +38723,9 @@ var package_default = {
38543
38723
  "test:watch": "vitest"
38544
38724
  },
38545
38725
  dependencies: {
38546
- "@chrischall/mcp-utils": "^0.13.0",
38547
- "@fetchproxy/bootstrap": "^1.0.0",
38548
- "@fetchproxy/server": "^1.0.0",
38726
+ "@chrischall/mcp-utils": "^0.14.0",
38727
+ "@fetchproxy/bootstrap": "^1.7.0",
38728
+ "@fetchproxy/server": "^1.7.0",
38549
38729
  "@modelcontextprotocol/sdk": "^1.29.0",
38550
38730
  dotenv: "^17.4.2",
38551
38731
  zod: "^4.4.3"
@@ -39475,7 +39655,7 @@ try {
39475
39655
  }
39476
39656
  await runMcp({
39477
39657
  name: "canvas",
39478
- version: "1.2.0",
39658
+ version: "1.2.2",
39479
39659
  // x-release-please-version
39480
39660
  tools: [
39481
39661
  (server) => {
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ catch (e) {
46
46
  // host's install-time `tools/list` succeeds.
47
47
  await runMcp({
48
48
  name: 'canvas',
49
- version: '1.2.0', // x-release-please-version
49
+ version: '1.2.2', // x-release-please-version
50
50
  tools: [
51
51
  (server) => {
52
52
  if (resolved) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvas-parent-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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,9 +44,9 @@
44
44
  "test:watch": "vitest"
45
45
  },
46
46
  "dependencies": {
47
- "@chrischall/mcp-utils": "^0.13.0",
48
- "@fetchproxy/bootstrap": "^1.0.0",
49
- "@fetchproxy/server": "^1.0.0",
47
+ "@chrischall/mcp-utils": "^0.14.0",
48
+ "@fetchproxy/bootstrap": "^1.7.0",
49
+ "@fetchproxy/server": "^1.7.0",
50
50
  "@modelcontextprotocol/sdk": "^1.29.0",
51
51
  "dotenv": "^17.4.2",
52
52
  "zod": "^4.4.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.2.0",
9
+ "version": "1.2.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "canvas-parent-mcp",
14
- "version": "1.2.0",
14
+ "version": "1.2.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },