infinitecampus-mcp 2.4.0 → 2.4.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 Infinite Campus (Campus Parent) — grades, attendance, assignments, messages, documents",
10
- "version": "2.4.0"
10
+ "version": "2.4.2"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Infinite Campus",
16
16
  "source": "./",
17
17
  "description": "Infinite Campus (Campus Parent) MCP server for Claude — grades, attendance, assignments, messages, and documents via natural language",
18
- "version": "2.4.0",
18
+ "version": "2.4.2",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infinitecampus-mcp",
3
3
  "displayName": "Infinite Campus",
4
- "version": "2.4.0",
4
+ "version": "2.4.2",
5
5
  "description": "Infinite Campus (Campus Parent) MCP server for Claude — grades, attendance, assignments, messages, and documents",
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,
@@ -34846,7 +34843,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
34846
34843
  "capture_redirect",
34847
34844
  "read_indexed_db",
34848
34845
  "read_dom",
34849
- "download"
34846
+ "download",
34847
+ "graphql"
34850
34848
  ]);
34851
34849
 
34852
34850
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35149,6 +35147,7 @@ function assertIndexedDbScopesArray(value, label) {
35149
35147
  }
35150
35148
  var DOM_SELECTOR_RE = /^[^-]{1,512}$/;
35151
35149
  var DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
35150
+ var GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
35152
35151
  function assertDomSelectorsArray(value, label) {
35153
35152
  if (!Array.isArray(value)) {
35154
35153
  throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
@@ -35185,6 +35184,37 @@ function assertDomSelectorsArray(value, label) {
35185
35184
  }
35186
35185
  }
35187
35186
  }
35187
+ function assertGraphqlOpsArray(value, label) {
35188
+ if (!Array.isArray(value)) {
35189
+ throw new ProtocolError(`${label}: expected array, got ${typeof value}`);
35190
+ }
35191
+ const seen = /* @__PURE__ */ new Set();
35192
+ for (let i = 0; i < value.length; i++) {
35193
+ const entry = value[i];
35194
+ assertObject(entry, `${label}[${i}]`);
35195
+ if (entry.name === void 0) {
35196
+ throw new ProtocolError(`${label}[${i}].name: missing`);
35197
+ }
35198
+ if (entry.operationName === void 0) {
35199
+ throw new ProtocolError(`${label}[${i}].operationName: missing`);
35200
+ }
35201
+ if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
35202
+ throw new ProtocolError(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
35203
+ }
35204
+ if (typeof entry.operationName !== "string" || !GRAPHQL_OP_NAME_RE.test(entry.operationName)) {
35205
+ throw new ProtocolError(`${label}[${i}].operationName: invalid ${JSON.stringify(entry.operationName)}`);
35206
+ }
35207
+ if (seen.has(entry.name)) {
35208
+ throw new ProtocolError(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
35209
+ }
35210
+ seen.add(entry.name);
35211
+ for (const k of Object.keys(entry)) {
35212
+ if (k !== "name" && k !== "operationName") {
35213
+ throw new ProtocolError(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
35214
+ }
35215
+ }
35216
+ }
35217
+ }
35188
35218
  function validateFrame(raw) {
35189
35219
  assertObject(raw, "frame");
35190
35220
  const t = raw.type;
@@ -35263,6 +35293,9 @@ function validateHello(raw) {
35263
35293
  if (raw.domSelectors !== void 0) {
35264
35294
  assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
35265
35295
  }
35296
+ if (raw.graphqlOps !== void 0) {
35297
+ assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
35298
+ }
35266
35299
  assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
35267
35300
  assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
35268
35301
  assertBase64(raw.sessionNonce, "hello.sessionNonce");
@@ -35512,6 +35545,28 @@ function validateInnerRequest(raw) {
35512
35545
  }
35513
35546
  return raw;
35514
35547
  }
35548
+ if (raw.op === "graphql_query") {
35549
+ assertObject(raw.init, "inner.init");
35550
+ if (raw.init.name === void 0)
35551
+ throw new ProtocolError("inner.init.name: missing");
35552
+ if (raw.init.variables === void 0) {
35553
+ throw new ProtocolError("inner.init.variables: missing");
35554
+ }
35555
+ assertString(raw.init.name, "inner.init.name");
35556
+ if (raw.init.name.length === 0) {
35557
+ throw new ProtocolError("inner.init.name: must be non-empty");
35558
+ }
35559
+ assertObject(raw.init.variables, "inner.init.variables");
35560
+ if (raw.init.tabUrl !== void 0) {
35561
+ assertString(raw.init.tabUrl, "inner.init.tabUrl");
35562
+ }
35563
+ for (const k of Object.keys(raw.init)) {
35564
+ if (k !== "name" && k !== "variables" && k !== "tabUrl") {
35565
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on graphql_query`);
35566
+ }
35567
+ }
35568
+ return raw;
35569
+ }
35515
35570
  if (raw.op === "download") {
35516
35571
  assertObject(raw.init, "inner.init");
35517
35572
  if (raw.init.url === void 0) {
@@ -35537,7 +35592,7 @@ function validateInnerRequest(raw) {
35537
35592
  }
35538
35593
  return raw;
35539
35594
  }
35540
- 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)}`);
35595
+ 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)}`);
35541
35596
  }
35542
35597
  function assertNonEmptyKeyArray(value, label) {
35543
35598
  if (!Array.isArray(value)) {
@@ -35568,6 +35623,10 @@ function assertStringMap(value, label) {
35568
35623
  }
35569
35624
  }
35570
35625
  }
35626
+ var KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
35627
+ ...KNOWN_CAPABILITIES,
35628
+ "graphql_query"
35629
+ ]);
35571
35630
  function validateInnerResponse(raw) {
35572
35631
  assertPositiveInt(raw.id, "inner.id");
35573
35632
  if (raw.ok === true) {
@@ -35629,6 +35688,13 @@ function validateInnerResponse(raw) {
35629
35688
  assertStringMap(raw.values, "inner.values");
35630
35689
  return raw;
35631
35690
  }
35691
+ if (op === "graphql_query") {
35692
+ if (raw.data === void 0) {
35693
+ throw new ProtocolError("inner.data: missing on graphql_query response");
35694
+ }
35695
+ assertObject(raw.data, "inner.data");
35696
+ return raw;
35697
+ }
35632
35698
  if (op === "download") {
35633
35699
  assertObject(raw.value, "inner.value");
35634
35700
  assertString(raw.value.path, "inner.value.path");
@@ -35653,7 +35719,7 @@ function validateInnerResponse(raw) {
35653
35719
  if (raw.ok === false) {
35654
35720
  assertString(raw.error, "inner.error");
35655
35721
  if (raw.op !== void 0) {
35656
- if (typeof raw.op !== "string" || !KNOWN_CAPABILITIES.has(raw.op)) {
35722
+ if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
35657
35723
  throw new ProtocolError(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
35658
35724
  }
35659
35725
  }
@@ -35845,11 +35911,33 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
35845
35911
  };
35846
35912
  }
35847
35913
  async function openEncryptedFrame(sessionKey, frame) {
35848
- const iv = fromB64(frame.iv);
35849
- const ct = fromB64(frame.ciphertext);
35850
- const pt = await aesGcmOpen(sessionKey, iv, ct);
35851
- const parsed = JSON.parse(dec.decode(pt));
35852
- return validateInnerFrame(parsed);
35914
+ const result = await openEncryptedFrameDetailed(sessionKey, frame);
35915
+ if (result.stage === "ok")
35916
+ return result.inner;
35917
+ throw result.error instanceof Error ? result.error : new Error(String(result.error));
35918
+ }
35919
+ async function openEncryptedFrameDetailed(sessionKey, frame) {
35920
+ let pt;
35921
+ try {
35922
+ const iv = fromB64(frame.iv);
35923
+ const ct = fromB64(frame.ciphertext);
35924
+ pt = await aesGcmOpen(sessionKey, iv, ct);
35925
+ } catch (error51) {
35926
+ return { stage: "decrypt-failed", error: error51 };
35927
+ }
35928
+ let parsed;
35929
+ try {
35930
+ parsed = JSON.parse(dec.decode(pt));
35931
+ } catch (error51) {
35932
+ return { stage: "validation-failed", error: error51, recoveredId: void 0 };
35933
+ }
35934
+ try {
35935
+ const inner = validateInnerFrame(parsed);
35936
+ return { stage: "ok", inner };
35937
+ } catch (error51) {
35938
+ 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;
35939
+ return { stage: "validation-failed", error: error51, recoveredId };
35940
+ }
35853
35941
  }
35854
35942
 
35855
35943
  // node_modules/@fetchproxy/server/dist/election.js
@@ -35975,6 +36063,12 @@ async function buildServerHello(opts) {
35975
36063
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
35976
36064
  }));
35977
36065
  }
36066
+ if (opts.graphqlOps && opts.graphqlOps.length > 0) {
36067
+ hello.graphqlOps = opts.graphqlOps.map((d) => ({
36068
+ name: d.name,
36069
+ operationName: d.operationName
36070
+ }));
36071
+ }
35978
36072
  return hello;
35979
36073
  }
35980
36074
 
@@ -36065,7 +36159,8 @@ async function startHost(opts) {
36065
36159
  indexedDbScopes: opts.ownIndexedDbScopes,
36066
36160
  localStoragePointers: opts.ownLocalStoragePointers,
36067
36161
  sessionStoragePointers: opts.ownSessionStoragePointers,
36068
- domSelectors: opts.ownDomSelectors
36162
+ domSelectors: opts.ownDomSelectors,
36163
+ graphqlOps: opts.ownGraphqlOps
36069
36164
  });
36070
36165
  const ownSessionNonce = fromB64(ownHello.sessionNonce);
36071
36166
  let extensionWs = null;
@@ -36302,7 +36397,8 @@ async function startPeer(opts) {
36302
36397
  indexedDbScopes: opts.indexedDbScopes,
36303
36398
  domSelectors: opts.domSelectors,
36304
36399
  localStoragePointers: opts.localStoragePointers,
36305
- sessionStoragePointers: opts.sessionStoragePointers
36400
+ sessionStoragePointers: opts.sessionStoragePointers,
36401
+ graphqlOps: opts.graphqlOps
36306
36402
  });
36307
36403
  const sessionNonce = fromB64(hello.sessionNonce);
36308
36404
  ws.send(JSON.stringify(hello));
@@ -36346,10 +36442,20 @@ async function startPeer(opts) {
36346
36442
  return;
36347
36443
  if (!session.acceptInboundSeq(frame.seq))
36348
36444
  return;
36349
- try {
36350
- const inner = await openEncryptedFrame(session.sessionKey, frame);
36351
- innerListeners.forEach((cb) => cb(inner));
36352
- } catch {
36445
+ const result = await openEncryptedFrameDetailed(session.sessionKey, frame);
36446
+ if (result.stage === "ok") {
36447
+ innerListeners.forEach((cb) => cb(result.inner));
36448
+ } else if (result.stage === "decrypt-failed") {
36449
+ } else {
36450
+ console.error("[fetchproxy] peer: received a frame that decrypted OK but failed validation:", result.error);
36451
+ if (result.recoveredId !== void 0) {
36452
+ innerListeners.forEach((cb) => cb({
36453
+ type: "response",
36454
+ id: result.recoveredId,
36455
+ ok: false,
36456
+ error: `malformed response failed protocol validation: ${String(result.error)}`
36457
+ }));
36458
+ }
36353
36459
  }
36354
36460
  }
36355
36461
  } catch (e) {
@@ -36626,6 +36732,10 @@ var FetchproxyServer = class {
36626
36732
  pendingIdb = /* @__PURE__ */ new Map();
36627
36733
  // download awaiters resolve the saved-file metadata (path + size + mime).
36628
36734
  pendingDownload = /* @__PURE__ */ new Map();
36735
+ // 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
36736
+ // shape is operation-specific, so the awaiter resolves `unknown` and the
36737
+ // caller narrows.
36738
+ pendingGraphql = /* @__PURE__ */ new Map();
36629
36739
  mcpId = null;
36630
36740
  identity = null;
36631
36741
  // 0.5.3+: in-flight role-election / handle-start promise. Set the
@@ -36713,6 +36823,10 @@ var FetchproxyServer = class {
36713
36823
  selector: d.selector,
36714
36824
  ...d.attribute !== void 0 ? { attribute: d.attribute } : {}
36715
36825
  })),
36826
+ graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
36827
+ name: d.name,
36828
+ operationName: d.operationName
36829
+ })),
36716
36830
  // 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
36717
36831
  // adapter was about to set these to the same numbers anyway; the
36718
36832
  // back-door is `0` (explicit opt-out) if a caller genuinely wants
@@ -36834,6 +36948,7 @@ var FetchproxyServer = class {
36834
36948
  ownLocalStoragePointers: this.opts.localStoragePointers,
36835
36949
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
36836
36950
  ownDomSelectors: this.opts.domSelectors,
36951
+ ownGraphqlOps: this.opts.graphqlOps,
36837
36952
  onPairCode: this.opts.onPairCode
36838
36953
  });
36839
36954
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
@@ -36862,7 +36977,8 @@ var FetchproxyServer = class {
36862
36977
  indexedDbScopes: this.opts.indexedDbScopes,
36863
36978
  localStoragePointers: this.opts.localStoragePointers,
36864
36979
  sessionStoragePointers: this.opts.sessionStoragePointers,
36865
- domSelectors: this.opts.domSelectors
36980
+ domSelectors: this.opts.domSelectors,
36981
+ graphqlOps: this.opts.graphqlOps
36866
36982
  });
36867
36983
  this.peerHandle.onInner((inner) => this.onInner(inner));
36868
36984
  this.peerHandle.onRenegotiate(() => {
@@ -37068,6 +37184,7 @@ var FetchproxyServer = class {
37068
37184
  this.pendingRedirect.delete(id);
37069
37185
  this.pendingDownload.delete(id);
37070
37186
  this.pendingIdb.delete(id);
37187
+ this.pendingGraphql.delete(id);
37071
37188
  }
37072
37189
  throw err;
37073
37190
  }
@@ -37877,6 +37994,52 @@ var FetchproxyServer = class {
37877
37994
  await this.sendInnerFrame(inner);
37878
37995
  return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
37879
37996
  }
37997
+ /**
37998
+ * 1.x+: run a declared GraphQL operation through the page's own Apollo
37999
+ * client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
38000
+ * Requires `'graphql'` in capabilities AND `name` to match a declared
38001
+ * `graphqlOps` entry. The extension resolves `name` → `operationName` →
38002
+ * the live DocumentNode the page already observed, then invokes
38003
+ * `client.query({ query, variables })` — the site's own request path, so
38004
+ * per-request bot telemetry (Akamai etc.) runs automatically.
38005
+ *
38006
+ * Returns the GraphQL `data` object on success (shape is
38007
+ * operation-specific; the caller narrows). Throws a plain `Error` on
38008
+ * developer mistakes (undeclared capability, undeclared name) and a
38009
+ * descriptive `Error` on the `ok:false` bridge path — which includes the
38010
+ * typed "operation not yet observed on this tab" case (open the site's
38011
+ * page and retry).
38012
+ */
38013
+ async graphqlQuery(opts) {
38014
+ if (!this.opts.capabilities.includes("graphql")) {
38015
+ throw new Error('FetchproxyServer.graphqlQuery(): MCP did not declare "graphql" in capabilities');
38016
+ }
38017
+ if (typeof opts.name !== "string" || opts.name.length === 0) {
38018
+ throw new Error("FetchproxyServer.graphqlQuery: opts.name must be a non-empty string");
38019
+ }
38020
+ const declaredNames = this.opts.graphqlOps.map((d) => d.name);
38021
+ if (!declaredNames.includes(opts.name)) {
38022
+ throw new Error(`FetchproxyServer.graphqlQuery: operation ${JSON.stringify(opts.name)} not in declared graphqlOps [${declaredNames.map((n) => JSON.stringify(n)).join(", ")}]`);
38023
+ }
38024
+ await this.ensureConnected();
38025
+ this.throwIfPendingPair();
38026
+ const id = this.nextRequestId++;
38027
+ const inner = {
38028
+ type: "request",
38029
+ id,
38030
+ op: "graphql_query",
38031
+ init: {
38032
+ name: opts.name,
38033
+ variables: opts.variables,
38034
+ ...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
38035
+ }
38036
+ };
38037
+ const pending = new Promise((resolve, reject) => {
38038
+ this.pendingGraphql.set(id, { resolve, reject });
38039
+ });
38040
+ await this.sendInnerFrame(inner);
38041
+ return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
38042
+ }
37880
38043
  assertScopeSubset(requested, declared, label) {
37881
38044
  const undeclared = undeclaredKeys(requested, declared);
37882
38045
  if (undeclared.length > 0) {
@@ -38014,6 +38177,20 @@ var FetchproxyServer = class {
38014
38177
  }
38015
38178
  return;
38016
38179
  }
38180
+ const graphqlCb = this.pendingGraphql.get(inner.id);
38181
+ if (graphqlCb) {
38182
+ this.pendingGraphql.delete(inner.id);
38183
+ if (inner.ok) {
38184
+ if (inner.op === "graphql_query") {
38185
+ graphqlCb.resolve(inner.data);
38186
+ } else {
38187
+ graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
38188
+ }
38189
+ } else {
38190
+ graphqlCb.reject(new FetchproxyProtocolError(inner.error));
38191
+ }
38192
+ return;
38193
+ }
38017
38194
  const cookiesCb = this.pendingReadCookies.get(inner.id);
38018
38195
  if (cookiesCb) {
38019
38196
  this.pendingReadCookies.delete(inner.id);
@@ -38065,6 +38242,9 @@ var FetchproxyServer = class {
38065
38242
  for (const { reject } of this.pendingDownload.values())
38066
38243
  reject(err);
38067
38244
  this.pendingDownload.clear();
38245
+ for (const { reject } of this.pendingGraphql.values())
38246
+ reject(err);
38247
+ this.pendingGraphql.clear();
38068
38248
  }
38069
38249
  /**
38070
38250
  * 0.5.2+: read the current pair-pending pair code from whichever handle
@@ -38311,7 +38491,7 @@ function loadAccount(env = process.env) {
38311
38491
  // package.json
38312
38492
  var package_default = {
38313
38493
  name: "infinitecampus-mcp",
38314
- version: "2.4.0",
38494
+ version: "2.4.2",
38315
38495
  mcpName: "io.github.chrischall/infinitecampus-mcp",
38316
38496
  description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
38317
38497
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -38353,9 +38533,9 @@ var package_default = {
38353
38533
  "test:watch": "vitest"
38354
38534
  },
38355
38535
  dependencies: {
38356
- "@chrischall/mcp-utils": "^0.13.0",
38357
- "@fetchproxy/bootstrap": "^1.0.0",
38358
- "@fetchproxy/server": "^1.0.0",
38536
+ "@chrischall/mcp-utils": "^0.14.0",
38537
+ "@fetchproxy/bootstrap": "^1.7.0",
38538
+ "@fetchproxy/server": "^1.7.0",
38359
38539
  "@modelcontextprotocol/sdk": "^1.29.0",
38360
38540
  dotenv: "^17.4.2",
38361
38541
  "node-html-parser": "^9.0.0",
@@ -39927,7 +40107,7 @@ try {
39927
40107
  }
39928
40108
  var COMMON = {
39929
40109
  name: "infinitecampus",
39930
- version: "2.4.0"
40110
+ version: "2.4.2"
39931
40111
  // x-release-please-version
39932
40112
  };
39933
40113
  if (account) {
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ catch (e) {
57
57
  // actionable stderr message (banner) instead of a crash loop.
58
58
  const COMMON = {
59
59
  name: 'infinitecampus',
60
- version: '2.4.0', // x-release-please-version
60
+ version: '2.4.2', // x-release-please-version
61
61
  };
62
62
  if (account) {
63
63
  const client = new ICClient(account, { preloaded });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinitecampus-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "mcpName": "io.github.chrischall/infinitecampus-mcp",
5
5
  "description": "Infinite Campus (Campus Parent) MCP server — multi-district read + message/document write",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -42,9 +42,9 @@
42
42
  "test:watch": "vitest"
43
43
  },
44
44
  "dependencies": {
45
- "@chrischall/mcp-utils": "^0.13.0",
46
- "@fetchproxy/bootstrap": "^1.0.0",
47
- "@fetchproxy/server": "^1.0.0",
45
+ "@chrischall/mcp-utils": "^0.14.0",
46
+ "@fetchproxy/bootstrap": "^1.7.0",
47
+ "@fetchproxy/server": "^1.7.0",
48
48
  "@modelcontextprotocol/sdk": "^1.29.0",
49
49
  "dotenv": "^17.4.2",
50
50
  "node-html-parser": "^9.0.0",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/infinitecampus-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.4.0",
9
+ "version": "2.4.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "infinitecampus-mcp",
14
- "version": "2.4.0",
14
+ "version": "2.4.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },