canvas-parent-mcp 1.1.8 → 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.1.8"
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.1.8",
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.1.8",
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",
@@ -20,6 +20,6 @@
20
20
  "education",
21
21
  "mcp"
22
22
  ],
23
- "skills": "./SKILL.md",
23
+ "skills": "./skills/",
24
24
  "mcp": "./.mcp.json"
25
25
  }
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,
@@ -34989,7 +34986,9 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
34989
34986
  "capture_request_header",
34990
34987
  "capture_redirect",
34991
34988
  "read_indexed_db",
34992
- "download"
34989
+ "read_dom",
34990
+ "download",
34991
+ "graphql"
34993
34992
  ]);
34994
34993
 
34995
34994
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35290,6 +35289,76 @@ function assertIndexedDbScopesArray(value, label) {
35290
35289
  }
35291
35290
  }
35292
35291
  }
35292
+ var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
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}$/;
35295
+ function assertDomSelectorsArray(value, label) {
35296
+ if (!Array.isArray(value)) {
35297
+ throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
35298
+ }
35299
+ const seen = /* @__PURE__ */ new Set();
35300
+ for (let i = 0; i < value.length; i++) {
35301
+ const entry = value[i];
35302
+ assertObject(entry, `${label}[${i}]`);
35303
+ if (entry.name === void 0) {
35304
+ throw new ProtocolError(`${label}[${i}].name: missing`);
35305
+ }
35306
+ if (entry.selector === void 0) {
35307
+ throw new ProtocolError(`${label}[${i}].selector: missing`);
35308
+ }
35309
+ if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
35310
+ throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
35311
+ }
35312
+ if (typeof entry.selector !== "string" || !DOM_SELECTOR_RE.test(entry.selector)) {
35313
+ throw new ProtocolError(`${label}[${i}].selector: invalid ${JSON.stringify(entry.selector)}`);
35314
+ }
35315
+ if (entry.attribute !== void 0) {
35316
+ if (typeof entry.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(entry.attribute)) {
35317
+ throw new ProtocolError(`${label}[${i}].attribute: invalid ${JSON.stringify(entry.attribute)}`);
35318
+ }
35319
+ }
35320
+ if (seen.has(entry.name)) {
35321
+ throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
35322
+ }
35323
+ seen.add(entry.name);
35324
+ for (const k of Object.keys(entry)) {
35325
+ if (k !== "name" && k !== "selector" && k !== "attribute") {
35326
+ throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
35327
+ }
35328
+ }
35329
+ }
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
+ }
35293
35362
  function validateFrame(raw) {
35294
35363
  assertObject(raw, "frame");
35295
35364
  const t = raw.type;
@@ -35365,6 +35434,12 @@ function validateHello(raw) {
35365
35434
  if (raw.sessionStoragePointers !== void 0) {
35366
35435
  assertStoragePointersArray(raw.sessionStoragePointers, "hello.sessionStoragePointers", raw.sessionStorageKeys);
35367
35436
  }
35437
+ if (raw.domSelectors !== void 0) {
35438
+ assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
35439
+ }
35440
+ if (raw.graphqlOps !== void 0) {
35441
+ assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
35442
+ }
35368
35443
  assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
35369
35444
  assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
35370
35445
  assertBase64(raw.sessionNonce, "hello.sessionNonce");
@@ -35599,6 +35674,43 @@ function validateInnerRequest(raw) {
35599
35674
  }
35600
35675
  return raw;
35601
35676
  }
35677
+ if (raw.op === "read_dom") {
35678
+ assertObject(raw.init, "inner.init");
35679
+ if (raw.init.origin === void 0)
35680
+ throw new ProtocolError("inner.init.origin: missing");
35681
+ if (raw.init.names === void 0)
35682
+ throw new ProtocolError("inner.init.names: missing");
35683
+ assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
35684
+ assertNonEmptyKeyArray(raw.init.names, "inner.init.names");
35685
+ for (const k of Object.keys(raw.init)) {
35686
+ if (k !== "origin" && k !== "names") {
35687
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom`);
35688
+ }
35689
+ }
35690
+ return raw;
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
+ }
35602
35714
  if (raw.op === "download") {
35603
35715
  assertObject(raw.init, "inner.init");
35604
35716
  if (raw.init.url === void 0) {
@@ -35624,7 +35736,7 @@ function validateInnerRequest(raw) {
35624
35736
  }
35625
35737
  return raw;
35626
35738
  }
35627
- 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", "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)}`);
35628
35740
  }
35629
35741
  function assertNonEmptyKeyArray(value, label) {
35630
35742
  if (!Array.isArray(value)) {
@@ -35655,6 +35767,10 @@ function assertStringMap(value, label) {
35655
35767
  }
35656
35768
  }
35657
35769
  }
35770
+ var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
35771
+ ...KNOWN_CAPABILITIES,
35772
+ "graphql_query"
35773
+ ]);
35658
35774
  function validateInnerResponse(raw) {
35659
35775
  assertPositiveInt(raw.id, "inner.id");
35660
35776
  if (raw.ok === true) {
@@ -35709,6 +35825,20 @@ function validateInnerResponse(raw) {
35709
35825
  assertObject(raw.values, "inner.values");
35710
35826
  return raw;
35711
35827
  }
35828
+ if (op === "read_dom") {
35829
+ if (raw.values === void 0) {
35830
+ throw new ProtocolError("inner.values: missing on read_dom response");
35831
+ }
35832
+ assertStringMap(raw.values, "inner.values");
35833
+ return raw;
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
+ }
35712
35842
  if (op === "download") {
35713
35843
  assertObject(raw.value, "inner.value");
35714
35844
  assertString(raw.value.path, "inner.value.path");
@@ -35733,7 +35863,7 @@ function validateInnerResponse(raw) {
35733
35863
  if (raw.ok === false) {
35734
35864
  assertString(raw.error, "inner.error");
35735
35865
  if (raw.op !== void 0) {
35736
- if (typeof raw.op !== "string" || !KNOWN_CAPABILITIES.has(raw.op)) {
35866
+ if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
35737
35867
  throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
35738
35868
  }
35739
35869
  }
@@ -35925,11 +36055,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
35925
36055
  };
35926
36056
  }
35927
36057
  async function openEncryptedFrame(sessionKey, frame) {
35928
- const iv = fromB64(frame.iv);
35929
- const ct = fromB64(frame.ciphertext);
35930
- const pt = await aesGcmOpen(sessionKey, iv, ct);
35931
- const parsed = JSON.parse(dec.decode(pt));
35932
- 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
+ }
35933
36085
  }
35934
36086
 
35935
36087
  // node_modules/@fetchproxy/server/dist/election.js
@@ -36048,6 +36200,19 @@ async function buildServerHello(opts) {
36048
36200
  jsonPointer: d.jsonPointer
36049
36201
  }));
36050
36202
  }
36203
+ if (opts.domSelectors && opts.domSelectors.length > 0) {
36204
+ hello.domSelectors = opts.domSelectors.map((d) => ({
36205
+ name: d.name,
36206
+ selector: d.selector,
36207
+ ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36208
+ }));
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
+ }
36051
36216
  return hello;
36052
36217
  }
36053
36218
 
@@ -36137,7 +36302,9 @@ async function startHost(opts) {
36137
36302
  captureHeaders: opts.ownCaptureHeaders,
36138
36303
  indexedDbScopes: opts.ownIndexedDbScopes,
36139
36304
  localStoragePointers: opts.ownLocalStoragePointers,
36140
- sessionStoragePointers: opts.ownSessionStoragePointers
36305
+ sessionStoragePointers: opts.ownSessionStoragePointers,
36306
+ domSelectors: opts.ownDomSelectors,
36307
+ graphqlOps: opts.ownGraphqlOps
36141
36308
  });
36142
36309
  const ownSessionNonce = fromB64(ownHello.sessionNonce);
36143
36310
  let extensionWs = null;
@@ -36372,8 +36539,10 @@ async function startPeer(opts) {
36372
36539
  sessionStorageKeys: opts.sessionStorageKeys,
36373
36540
  captureHeaders: opts.captureHeaders,
36374
36541
  indexedDbScopes: opts.indexedDbScopes,
36542
+ domSelectors: opts.domSelectors,
36375
36543
  localStoragePointers: opts.localStoragePointers,
36376
- sessionStoragePointers: opts.sessionStoragePointers
36544
+ sessionStoragePointers: opts.sessionStoragePointers,
36545
+ graphqlOps: opts.graphqlOps
36377
36546
  });
36378
36547
  const sessionNonce = fromB64(hello.sessionNonce);
36379
36548
  ws.send(JSON.stringify(hello));
@@ -36417,10 +36586,20 @@ async function startPeer(opts) {
36417
36586
  return;
36418
36587
  if (!session.acceptInboundSeq(frame.seq))
36419
36588
  return;
36420
- try {
36421
- const inner = await openEncryptedFrame(session.sessionKey, frame);
36422
- innerListeners.forEach((cb) => cb(inner));
36423
- } 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
+ }
36424
36603
  }
36425
36604
  }
36426
36605
  } catch (e) {
@@ -36697,6 +36876,10 @@ var FetchproxyServer = class {
36697
36876
  pendingIdb = /* @__PURE__ */ new Map();
36698
36877
  // download awaiters resolve the saved-file metadata (path + size + mime).
36699
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();
36700
36883
  mcpId = null;
36701
36884
  identity = null;
36702
36885
  // 0.5.3+: in-flight role-election / handle-start promise. Set the
@@ -36779,6 +36962,15 @@ var FetchproxyServer = class {
36779
36962
  key: d.key,
36780
36963
  jsonPointer: d.jsonPointer
36781
36964
  })),
36965
+ domSelectors: (opts.domSelectors ?? []).map((d) => ({
36966
+ name: d.name,
36967
+ selector: d.selector,
36968
+ ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36969
+ })),
36970
+ graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
36971
+ name: d.name,
36972
+ operationName: d.operationName
36973
+ })),
36782
36974
  // 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
36783
36975
  // adapter was about to set these to the same numbers anyway; the
36784
36976
  // back-door is `0` (explicit opt-out) if a caller genuinely wants
@@ -36899,6 +37091,8 @@ var FetchproxyServer = class {
36899
37091
  ownIndexedDbScopes: this.opts.indexedDbScopes,
36900
37092
  ownLocalStoragePointers: this.opts.localStoragePointers,
36901
37093
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
37094
+ ownDomSelectors: this.opts.domSelectors,
37095
+ ownGraphqlOps: this.opts.graphqlOps,
36902
37096
  onPairCode: this.opts.onPairCode
36903
37097
  });
36904
37098
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
@@ -36926,7 +37120,9 @@ var FetchproxyServer = class {
36926
37120
  captureHeaders: this.opts.captureHeaders,
36927
37121
  indexedDbScopes: this.opts.indexedDbScopes,
36928
37122
  localStoragePointers: this.opts.localStoragePointers,
36929
- sessionStoragePointers: this.opts.sessionStoragePointers
37123
+ sessionStoragePointers: this.opts.sessionStoragePointers,
37124
+ domSelectors: this.opts.domSelectors,
37125
+ graphqlOps: this.opts.graphqlOps
36930
37126
  });
36931
37127
  this.peerHandle.onInner((inner) => this.onInner(inner));
36932
37128
  this.peerHandle.onRenegotiate(() => {
@@ -37132,6 +37328,7 @@ var FetchproxyServer = class {
37132
37328
  this.pendingRedirect.delete(id);
37133
37329
  this.pendingDownload.delete(id);
37134
37330
  this.pendingIdb.delete(id);
37331
+ this.pendingGraphql.delete(id);
37135
37332
  }
37136
37333
  throw err;
37137
37334
  }
@@ -37901,6 +38098,92 @@ var FetchproxyServer = class {
37901
38098
  await this.sendInnerFrame(inner);
37902
38099
  return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
37903
38100
  }
38101
+ /**
38102
+ * 1.4.0+: read declared DOM values from the user's signed-in tab.
38103
+ * Requires `'read_dom'` in capabilities AND every requested `name` to
38104
+ * match a declared `domSelectors` entry. The extension reads each
38105
+ * declared selector from the matched tab's DOM (isolated-world
38106
+ * `querySelector`, value or attribute) — no page-JS execution.
38107
+ *
38108
+ * Returns a `Record<string, string>` of `name → value`, with names
38109
+ * whose element (or attribute) was absent omitted. Throws
38110
+ * `FetchproxyProtocolError` on bridge failures and a plain `Error` on
38111
+ * developer mistakes (undeclared capability, undeclared name).
38112
+ */
38113
+ async readDom(opts) {
38114
+ if (!this.opts.capabilities.includes("read_dom")) {
38115
+ throw new Error('FetchproxyServer.readDom(): MCP did not declare "read_dom" in capabilities');
38116
+ }
38117
+ await this.ensureConnected();
38118
+ this.throwIfPendingPair();
38119
+ if (!Array.isArray(opts.names) || opts.names.length === 0) {
38120
+ throw new Error("FetchproxyServer.readDom: opts.names must be a non-empty array");
38121
+ }
38122
+ this.assertScopeSubset(opts.names, this.opts.domSelectors.map((d) => d.name), "domSelectors");
38123
+ if (opts.subdomain !== void 0)
38124
+ assertSubdomainLabel(opts.subdomain);
38125
+ const baseDomain = this.resolveBaseDomain(opts.domain);
38126
+ const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
38127
+ const origin = `https://${host}`;
38128
+ const id = this.nextRequestId++;
38129
+ const inner = {
38130
+ type: "request",
38131
+ id,
38132
+ op: "read_dom",
38133
+ init: { origin, names: [...opts.names] }
38134
+ };
38135
+ const pending = new Promise((resolve, reject) => {
38136
+ this.pendingStorage.set(id, { resolve, reject });
38137
+ });
38138
+ await this.sendInnerFrame(inner);
38139
+ return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
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
+ }
37904
38187
  assertScopeSubset(requested, declared, label) {
37905
38188
  const undeclared = undeclaredKeys(requested, declared);
37906
38189
  if (undeclared.length > 0) {
@@ -37972,7 +38255,7 @@ var FetchproxyServer = class {
37972
38255
  if (storageCb) {
37973
38256
  this.pendingStorage.delete(inner.id);
37974
38257
  if (inner.ok) {
37975
- if ((inner.op === "read_local_storage" || inner.op === "read_session_storage") && inner.values) {
38258
+ if ((inner.op === "read_local_storage" || inner.op === "read_session_storage" || inner.op === "read_dom") && inner.values) {
37976
38259
  storageCb.resolve({ ...inner.values });
37977
38260
  } else {
37978
38261
  storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
@@ -38038,6 +38321,20 @@ var FetchproxyServer = class {
38038
38321
  }
38039
38322
  return;
38040
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
+ }
38041
38338
  const cookiesCb = this.pendingReadCookies.get(inner.id);
38042
38339
  if (cookiesCb) {
38043
38340
  this.pendingReadCookies.delete(inner.id);
@@ -38089,6 +38386,9 @@ var FetchproxyServer = class {
38089
38386
  for (const { reject } of this.pendingDownload.values())
38090
38387
  reject(err);
38091
38388
  this.pendingDownload.clear();
38389
+ for (const { reject } of this.pendingGraphql.values())
38390
+ reject(err);
38391
+ this.pendingGraphql.clear();
38092
38392
  }
38093
38393
  /**
38094
38394
  * 0.5.2+: read the current pair-pending pair code from whichever handle
@@ -38379,7 +38679,7 @@ function loadAccount(env = process.env) {
38379
38679
  // package.json
38380
38680
  var package_default = {
38381
38681
  name: "canvas-parent-mcp",
38382
- version: "1.1.8",
38682
+ version: "1.2.2",
38383
38683
  mcpName: "io.github.chrischall/canvas-parent-mcp",
38384
38684
  description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
38385
38685
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -38423,9 +38723,9 @@ var package_default = {
38423
38723
  "test:watch": "vitest"
38424
38724
  },
38425
38725
  dependencies: {
38426
- "@chrischall/mcp-utils": "^0.12.0",
38427
- "@fetchproxy/bootstrap": "^1.0.0",
38428
- "@fetchproxy/server": "^1.0.0",
38726
+ "@chrischall/mcp-utils": "^0.14.0",
38727
+ "@fetchproxy/bootstrap": "^1.7.0",
38728
+ "@fetchproxy/server": "^1.7.0",
38429
38729
  "@modelcontextprotocol/sdk": "^1.29.0",
38430
38730
  dotenv: "^17.4.2",
38431
38731
  zod: "^4.4.3"
@@ -38434,7 +38734,7 @@ var package_default = {
38434
38734
  "@types/node": "^26.0.0",
38435
38735
  "@vitest/coverage-v8": "^4.1.7",
38436
38736
  esbuild: "^0.28.0",
38437
- typescript: "^6.0.3",
38737
+ typescript: "^7.0.2",
38438
38738
  vitest: "^4.1.7"
38439
38739
  }
38440
38740
  };
@@ -39355,7 +39655,7 @@ try {
39355
39655
  }
39356
39656
  await runMcp({
39357
39657
  name: "canvas",
39358
- version: "1.1.8",
39658
+ version: "1.2.2",
39359
39659
  // x-release-please-version
39360
39660
  tools: [
39361
39661
  (server) => {