canvas-parent-mcp 1.1.3 → 1.1.4

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.3"
10
+ "version": "1.1.4"
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.3",
18
+ "version": "1.1.4",
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.3",
4
+ "version": "1.1.4",
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/auth.js CHANGED
@@ -52,6 +52,7 @@
52
52
  // - `loadAccount()` (the existing env-var resolver) is reused as-is so
53
53
  // the legacy paths keep working unchanged.
54
54
  import { bootstrap } from '@fetchproxy/bootstrap';
55
+ import { classifyBridgeError } from '@fetchproxy/server';
55
56
  import { loadAccount } from './config.js';
56
57
  import pkg from '../package.json' with { type: 'json' };
57
58
  function readEnv(key) {
@@ -153,6 +154,18 @@ export async function resolveAuth() {
153
154
  };
154
155
  }
155
156
  catch (e) {
157
+ // 0.8.0+ typed-error discrimination. The fetchproxy server already
158
+ // retries once on SW eviction (bridgeReviveDelayMs=2000 default), so
159
+ // a thrown FetchproxyBridgeDownError means the retry also failed —
160
+ // the extension's service worker is genuinely down and the user
161
+ // needs to wake it. The `.hint` is the actionable copy
162
+ // ("click the extension toolbar icon...") that we'd otherwise have
163
+ // to hand-write here. Surface it verbatim so users in path 4 get
164
+ // the same self-service guidance as path 5.
165
+ if (classifyBridgeError(e) === 'bridge_down') {
166
+ const downErr = e;
167
+ throw new Error(`Canvas auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
168
+ }
156
169
  const msg = e instanceof Error ? e.message : String(e);
157
170
  throw new Error(`Canvas auth: no CANVAS_TOKEN, CANVAS_CLIENT_*/CANVAS_REFRESH_TOKEN, or CANVAS_USERNAME/CANVAS_PASSWORD set, ` +
158
171
  `and fetchproxy fallback failed: ${msg}`);
package/dist/bundle.js CHANGED
@@ -35927,6 +35927,52 @@ var FetchproxyHttpError = class extends Error {
35927
35927
  this.name = "FetchproxyHttpError";
35928
35928
  }
35929
35929
  };
35930
+ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
35931
+ originalError;
35932
+ retryAttempted;
35933
+ op;
35934
+ url;
35935
+ /** 0.8.0+: bridge role at throw time; `null` if listen() hadn't bound yet. */
35936
+ role;
35937
+ /** 0.8.0+: bridge port at throw time (the same port `listen()` bound to). */
35938
+ port;
35939
+ hint;
35940
+ constructor(args) {
35941
+ const retryAttempted = args.retryAttempted ?? false;
35942
+ const op = args.op ?? "fetch";
35943
+ const retryClause = retryAttempted ? `Server already burned a one-shot lazy-revive retry; SW is still down. ` : `Server lazy-revive retry was disabled (bridgeReviveDelayMs unset/0). `;
35944
+ const hint = `the fetchproxy extension's service worker is not responding ("${args.originalError}"). Chrome evicts extension service workers after ~30s idle by default. ${retryClause}Wake it by clicking the fetchproxy extension toolbar icon, then retry. If it keeps happening, reload the extension from chrome://extensions.`;
35945
+ super(`fetchproxy bridge down during ${op}${args.url ? ` (${args.url})` : ""}. ${hint}`);
35946
+ this.name = "FetchproxyBridgeDownError";
35947
+ this.originalError = args.originalError;
35948
+ this.retryAttempted = retryAttempted;
35949
+ this.op = op;
35950
+ if (args.url !== void 0)
35951
+ this.url = args.url;
35952
+ this.role = args.role ?? null;
35953
+ this.port = args.port ?? 0;
35954
+ this.hint = hint;
35955
+ }
35956
+ };
35957
+ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
35958
+ url;
35959
+ timeoutMs;
35960
+ /** 0.8.0+: bridge role at throw time; `null` if listen() hadn't bound yet. */
35961
+ role;
35962
+ /** 0.8.0+: bridge port at throw time. */
35963
+ port;
35964
+ /** 0.8.0+: actual elapsed milliseconds when the timer won the race. */
35965
+ elapsedMs;
35966
+ constructor(args) {
35967
+ super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms`);
35968
+ this.name = "FetchproxyTimeoutError";
35969
+ this.url = args.url;
35970
+ this.timeoutMs = args.timeoutMs;
35971
+ this.role = args.role ?? null;
35972
+ this.port = args.port ?? 0;
35973
+ this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
35974
+ }
35975
+ };
35930
35976
  var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
35931
35977
  function assertSubdomainLabel(label) {
35932
35978
  if (!SUBDOMAIN_LABEL_RE.test(label)) {
@@ -35964,6 +36010,18 @@ var FetchproxyServer = class {
35964
36010
  hostHandle = null;
35965
36011
  peerHandle = null;
35966
36012
  nextRequestId = 1;
36013
+ // 0.8.0+: process-wide freshness counters surfaced via bridgeHealth().
36014
+ // Replaces the local copies every downstream MCP was rolling on top
36015
+ // of its own transport adapter — see realty-mcp cohort drift notes.
36016
+ // Updated by recordSuccess / recordFailure from fetch + capture paths.
36017
+ // `lastExtensionMessageAt` (#23 ask 4) is updated whenever any inner
36018
+ // frame from the extension arrives — gives extension-side liveness
36019
+ // distinct from per-call success/failure.
36020
+ lastSuccessAt = null;
36021
+ lastFailureAt = null;
36022
+ lastFailureReason = null;
36023
+ consecutiveFailures = 0;
36024
+ lastExtensionMessageAt = null;
35967
36025
  pending = /* @__PURE__ */ new Map();
35968
36026
  // Separate pending map for read_cookies so the response shape (cookies
35969
36027
  // string vs status/body) doesn't have to share a union type with fetch.
@@ -36032,6 +36090,12 @@ var FetchproxyServer = class {
36032
36090
  key: d.key,
36033
36091
  jsonPointer: d.jsonPointer
36034
36092
  })),
36093
+ // 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
36094
+ // adapter was about to set these to the same numbers anyway; the
36095
+ // back-door is `0` (explicit opt-out) if a caller genuinely wants
36096
+ // the legacy hang-forever / fail-once-on-SW-eviction behavior.
36097
+ fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
36098
+ bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
36035
36099
  identityDir: opts.identityDir,
36036
36100
  onPairCode: opts.onPairCode
36037
36101
  };
@@ -36167,7 +36231,7 @@ var FetchproxyServer = class {
36167
36231
  }
36168
36232
  }
36169
36233
  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}`;
36234
+ return `fetchproxy transport error: pairing required for ${this.opts.serverName}. Tell the user to open the Transporter browser extension popup and approve the pair request. The pair code is: ${code} \u2014 display this code to the user so they can verify it matches.`;
36171
36235
  }
36172
36236
  /**
36173
36237
  * Raw single-shot fetch through the bridge. Most callers should prefer
@@ -36188,8 +36252,71 @@ var FetchproxyServer = class {
36188
36252
  const pendingCode = this.currentPendingPairCode();
36189
36253
  if (pendingCode !== null) {
36190
36254
  const error51 = this.pairingErrorMessage(pendingCode);
36191
- return { ok: false, error: error51, kind: classifyFetchError(error51) };
36255
+ return {
36256
+ ok: false,
36257
+ error: error51,
36258
+ kind: classifyFetchError(error51),
36259
+ retryAttempted: false
36260
+ };
36261
+ }
36262
+ const first = await this._fetchOnceWithTimeout(init);
36263
+ const reviveMs = this.opts.bridgeReviveDelayMs;
36264
+ let final = first;
36265
+ if (!first.ok && first.kind === "content_script_unreachable" && reviveMs !== void 0 && reviveMs > 0) {
36266
+ await new Promise((r) => setTimeout(r, reviveMs));
36267
+ const second = await this._fetchOnceWithTimeout(init);
36268
+ if (second.ok)
36269
+ this.recordSuccess();
36270
+ else
36271
+ this.recordFailure(`${second.kind ?? "other"}: ${second.error}`);
36272
+ return { ...second, retryAttempted: true };
36192
36273
  }
36274
+ if (first.ok)
36275
+ this.recordSuccess();
36276
+ else
36277
+ this.recordFailure(`${first.kind ?? "other"}: ${first.error}`);
36278
+ return { ...first, retryAttempted: false };
36279
+ }
36280
+ /**
36281
+ * 0.8.0+: snapshot of the bridge's process-wide freshness counters,
36282
+ * suitable for surfacing through a downstream MCP's healthcheck tool.
36283
+ * Counters reset on a success (consecutiveFailures), accumulate
36284
+ * across the process lifetime otherwise. Replaces the per-MCP
36285
+ * duplication the realty cohort had been rolling in their adapters.
36286
+ * `lastExtensionMessageAt` is updated whenever ANY inner frame
36287
+ * arrives from the extension — gives extension-side liveness
36288
+ * distinct from server-side success/failure of the user-visible
36289
+ * call (addresses #23 ask 4).
36290
+ */
36291
+ bridgeHealth() {
36292
+ return {
36293
+ role: this.role,
36294
+ port: this.opts.port,
36295
+ serverVersion: this.opts.version,
36296
+ fetchTimeoutMs: this.opts.fetchTimeoutMs ?? 0,
36297
+ bridgeReviveDelayMs: this.opts.bridgeReviveDelayMs ?? 0,
36298
+ lastSuccessAt: this.lastSuccessAt,
36299
+ lastFailureAt: this.lastFailureAt,
36300
+ lastFailureReason: this.lastFailureReason,
36301
+ consecutiveFailures: this.consecutiveFailures,
36302
+ lastExtensionMessageAt: this.lastExtensionMessageAt
36303
+ };
36304
+ }
36305
+ recordSuccess() {
36306
+ this.lastSuccessAt = Date.now();
36307
+ this.consecutiveFailures = 0;
36308
+ }
36309
+ recordFailure(reason) {
36310
+ this.lastFailureAt = Date.now();
36311
+ this.lastFailureReason = reason;
36312
+ this.consecutiveFailures += 1;
36313
+ }
36314
+ /**
36315
+ * Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
36316
+ * On timeout returns the `{ok:false, kind:'timeout'}` envelope —
36317
+ * the throwing surface is the convenience methods.
36318
+ */
36319
+ async _fetchOnceWithTimeout(init) {
36193
36320
  const id = this.nextRequestId++;
36194
36321
  const inner = { type: "request", id, op: "fetch", init };
36195
36322
  const pending = new Promise((resolve) => {
@@ -36200,7 +36327,61 @@ var FetchproxyServer = class {
36200
36327
  } else if (this.peerHandle) {
36201
36328
  await this.peerHandle.sendInner(inner);
36202
36329
  }
36203
- return pending;
36330
+ const timeoutMs = this.opts.fetchTimeoutMs;
36331
+ if (timeoutMs === void 0 || timeoutMs <= 0)
36332
+ return pending;
36333
+ let timer;
36334
+ const start = Date.now();
36335
+ try {
36336
+ return await Promise.race([
36337
+ pending,
36338
+ new Promise((resolve) => {
36339
+ timer = setTimeout(() => {
36340
+ this.pending.delete(id);
36341
+ const elapsedMs = Date.now() - start;
36342
+ const error51 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
36343
+ resolve({
36344
+ ok: false,
36345
+ error: error51,
36346
+ kind: "timeout",
36347
+ retryAttempted: false,
36348
+ elapsedMs
36349
+ });
36350
+ }, timeoutMs);
36351
+ })
36352
+ ]);
36353
+ } finally {
36354
+ if (timer)
36355
+ clearTimeout(timer);
36356
+ }
36357
+ }
36358
+ /**
36359
+ * Map an `ok:false` fetch result to its typed throwable. Centralizes
36360
+ * the kind-to-error-class switch so `request()` and (via the same
36361
+ * logic re-implemented inline) `captureRequestHeader()` agree on what
36362
+ * to throw.
36363
+ */
36364
+ _typedErrorFor(result, url2, op, retryAttempted) {
36365
+ if (result.kind === "timeout") {
36366
+ return new FetchproxyTimeoutError({
36367
+ url: url2,
36368
+ timeoutMs: this.opts.fetchTimeoutMs ?? 0,
36369
+ role: this.role,
36370
+ port: this.opts.port,
36371
+ elapsedMs: result.elapsedMs
36372
+ });
36373
+ }
36374
+ if (result.kind === "content_script_unreachable") {
36375
+ return new FetchproxyBridgeDownError({
36376
+ originalError: result.error,
36377
+ retryAttempted,
36378
+ op,
36379
+ url: url2,
36380
+ role: this.role,
36381
+ port: this.opts.port
36382
+ });
36383
+ }
36384
+ return new FetchproxyProtocolError(result.error);
36204
36385
  }
36205
36386
  /**
36206
36387
  * Convenience wrapper around `fetch()`. Builds the URL from a path
@@ -36223,8 +36404,18 @@ var FetchproxyServer = class {
36223
36404
  if (opts.subdomain !== void 0)
36224
36405
  assertSubdomainLabel(opts.subdomain);
36225
36406
  const baseDomain = this.resolveBaseDomain(opts.domain);
36226
- const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
36227
- const url2 = path.startsWith("http://") || path.startsWith("https://") ? path : `https://${host}${path}`;
36407
+ const isAbsolute = path.startsWith("http://") || path.startsWith("https://");
36408
+ let host;
36409
+ if (isAbsolute) {
36410
+ try {
36411
+ host = new URL(path).host;
36412
+ } catch {
36413
+ throw new Error(`FetchproxyServer.request: absolute path is not a valid URL: ${JSON.stringify(path)}`);
36414
+ }
36415
+ } else {
36416
+ host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
36417
+ }
36418
+ const url2 = isAbsolute ? path : `https://${host}${path}`;
36228
36419
  assertUrlInDomains("request url", url2, this.opts.domains);
36229
36420
  const init = {
36230
36421
  url: url2,
@@ -36235,7 +36426,7 @@ var FetchproxyServer = class {
36235
36426
  };
36236
36427
  const result = await this.fetch(init);
36237
36428
  if (!result.ok) {
36238
- throw new FetchproxyProtocolError(result.error);
36429
+ throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false);
36239
36430
  }
36240
36431
  const response = {
36241
36432
  status: result.status,
@@ -36445,10 +36636,73 @@ var FetchproxyServer = class {
36445
36636
  }
36446
36637
  await this.ensureConnected();
36447
36638
  this.throwIfPendingPair();
36448
- const declared = this.opts.captureHeaders.find((d) => d.urlPattern === opts.urlPattern && d.headerName === opts.headerName);
36449
- if (!declared) {
36450
- throw new Error(`FetchproxyServer.captureRequestHeader: (urlPattern=${JSON.stringify(opts.urlPattern)}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
36639
+ const decls = this.opts.captureHeaders;
36640
+ let resolved2;
36641
+ if (opts?.urlPattern !== void 0 && opts?.headerName !== void 0) {
36642
+ const found = decls.find((d) => d.urlPattern === opts.urlPattern && d.headerName === opts.headerName);
36643
+ if (!found) {
36644
+ throw new Error(`FetchproxyServer.captureRequestHeader: (urlPattern=${JSON.stringify(opts.urlPattern)}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
36645
+ }
36646
+ resolved2 = found;
36647
+ } else if (opts?.urlPattern === void 0 && opts?.headerName === void 0) {
36648
+ if (decls.length === 0) {
36649
+ throw new Error("FetchproxyServer.captureRequestHeader: no captureHeaders declared on this server \u2014 declare at least one entry in FetchproxyServerOpts.captureHeaders, or pass {urlPattern, headerName} explicitly");
36650
+ }
36651
+ if (decls.length > 1) {
36652
+ const list = decls.map((d) => `${JSON.stringify(d.urlPattern)}/${JSON.stringify(d.headerName)}`).join(", ");
36653
+ throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list}); pass {urlPattern, headerName} to disambiguate`);
36654
+ }
36655
+ resolved2 = decls[0];
36656
+ } else {
36657
+ throw new Error("FetchproxyServer.captureRequestHeader: pass both urlPattern AND headerName, or neither (which defaults to the single declared entry)");
36451
36658
  }
36659
+ const callOpts = { ...resolved2, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
36660
+ try {
36661
+ const result = await this._captureRequestHeaderOnce(callOpts);
36662
+ this.recordSuccess();
36663
+ return result;
36664
+ } catch (err) {
36665
+ const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
36666
+ if (!swDown) {
36667
+ this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
36668
+ throw err;
36669
+ }
36670
+ const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
36671
+ if (reviveMs > 0) {
36672
+ await new Promise((r) => setTimeout(r, reviveMs));
36673
+ try {
36674
+ const result = await this._captureRequestHeaderOnce(callOpts);
36675
+ this.recordSuccess();
36676
+ return result;
36677
+ } catch (retryErr) {
36678
+ const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
36679
+ if (!stillDown) {
36680
+ this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
36681
+ throw retryErr;
36682
+ }
36683
+ this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
36684
+ throw new FetchproxyBridgeDownError({
36685
+ originalError: retryErr.message,
36686
+ retryAttempted: true,
36687
+ op: "capture_request_header",
36688
+ url: resolved2.urlPattern,
36689
+ role: this.role,
36690
+ port: this.opts.port
36691
+ });
36692
+ }
36693
+ }
36694
+ this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
36695
+ throw new FetchproxyBridgeDownError({
36696
+ originalError: err.message,
36697
+ retryAttempted: false,
36698
+ op: "capture_request_header",
36699
+ url: resolved2.urlPattern,
36700
+ role: this.role,
36701
+ port: this.opts.port
36702
+ });
36703
+ }
36704
+ }
36705
+ async _captureRequestHeaderOnce(opts) {
36452
36706
  const id = this.nextRequestId++;
36453
36707
  const inner = {
36454
36708
  type: "request",
@@ -36557,18 +36811,35 @@ var FetchproxyServer = class {
36557
36811
  onInner(inner) {
36558
36812
  if (inner.type !== "response")
36559
36813
  return;
36814
+ this.lastExtensionMessageAt = Date.now();
36560
36815
  const fetchCb = this.pending.get(inner.id);
36561
36816
  if (fetchCb) {
36562
36817
  this.pending.delete(inner.id);
36563
36818
  if (inner.ok) {
36564
36819
  if (inner.op === void 0 || inner.op === "fetch") {
36565
- fetchCb({ ok: true, status: inner.status, url: inner.url, body: inner.body });
36820
+ fetchCb({
36821
+ ok: true,
36822
+ status: inner.status,
36823
+ url: inner.url,
36824
+ body: inner.body,
36825
+ retryAttempted: false
36826
+ });
36566
36827
  } else {
36567
36828
  const error51 = `unexpected ${inner.op} response on fetch awaiter`;
36568
- fetchCb({ ok: false, error: error51, kind: classifyFetchError(error51) });
36829
+ fetchCb({
36830
+ ok: false,
36831
+ error: error51,
36832
+ kind: classifyFetchError(error51),
36833
+ retryAttempted: false
36834
+ });
36569
36835
  }
36570
36836
  } else {
36571
- fetchCb({ ok: false, error: inner.error, kind: classifyFetchError(inner.error) });
36837
+ fetchCb({
36838
+ ok: false,
36839
+ error: inner.error,
36840
+ kind: classifyFetchError(inner.error),
36841
+ retryAttempted: false
36842
+ });
36572
36843
  }
36573
36844
  return;
36574
36845
  }
@@ -36638,7 +36909,12 @@ var FetchproxyServer = class {
36638
36909
  rejectAllPending(reason = "extension disconnected") {
36639
36910
  const err = new FetchproxyProtocolError(reason);
36640
36911
  for (const cb of this.pending.values()) {
36641
- cb({ ok: false, error: err.message, kind: classifyFetchError(err.message) });
36912
+ cb({
36913
+ ok: false,
36914
+ error: err.message,
36915
+ kind: classifyFetchError(err.message),
36916
+ retryAttempted: false
36917
+ });
36642
36918
  }
36643
36919
  this.pending.clear();
36644
36920
  for (const cb of this.pendingReadCookies.values()) {
@@ -36703,6 +36979,19 @@ var FetchproxyServer = class {
36703
36979
  }
36704
36980
  };
36705
36981
 
36982
+ // node_modules/@fetchproxy/server/dist/classify-bridge-error.js
36983
+ function classifyBridgeError(err) {
36984
+ if (err instanceof FetchproxyTimeoutError)
36985
+ return "timeout";
36986
+ if (err instanceof FetchproxyBridgeDownError)
36987
+ return "bridge_down";
36988
+ if (err instanceof FetchproxyHttpError)
36989
+ return "http";
36990
+ if (err instanceof FetchproxyProtocolError)
36991
+ return "protocol";
36992
+ return "other";
36993
+ }
36994
+
36706
36995
  // node_modules/@fetchproxy/bootstrap/dist/index.js
36707
36996
  var defaultFactory = (opts) => new FetchproxyServer(opts);
36708
36997
  async function bootstrap(opts) {
@@ -36757,7 +37046,11 @@ async function bootstrap(opts) {
36757
37046
  key: p.storageKey,
36758
37047
  jsonPointer: p.jsonPointer
36759
37048
  })),
36760
- onPairCode: opts.onPairCode
37049
+ onPairCode: opts.onPairCode,
37050
+ // 0.8.0+ pass-through. Only forwarded when the caller set them;
37051
+ // unset → server defaults apply (30000 / 2000 in 0.8.0).
37052
+ ...opts.fetchTimeoutMs !== void 0 ? { fetchTimeoutMs: opts.fetchTimeoutMs } : {},
37053
+ ...opts.bridgeReviveDelayMs !== void 0 ? { bridgeReviveDelayMs: opts.bridgeReviveDelayMs } : {}
36761
37054
  });
36762
37055
  const storageDomainOpts = {};
36763
37056
  if (opts.storageDomain !== void 0)
@@ -36948,7 +37241,7 @@ function loadAccount(env = process.env) {
36948
37241
  // package.json
36949
37242
  var package_default = {
36950
37243
  name: "canvas-parent-mcp",
36951
- version: "1.1.3",
37244
+ version: "1.1.4",
36952
37245
  mcpName: "io.github.chrischall/canvas-parent-mcp",
36953
37246
  description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
36954
37247
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -36992,7 +37285,8 @@ var package_default = {
36992
37285
  "test:watch": "vitest"
36993
37286
  },
36994
37287
  dependencies: {
36995
- "@fetchproxy/bootstrap": "^0.6.0",
37288
+ "@fetchproxy/bootstrap": "^0.8.0",
37289
+ "@fetchproxy/server": "^0.8.0",
36996
37290
  "@modelcontextprotocol/sdk": "^1.29.0",
36997
37291
  dotenv: "^17.4.2",
36998
37292
  zod: "^4.4.3"
@@ -37070,6 +37364,12 @@ async function resolveAuth() {
37070
37364
  source: "fetchproxy"
37071
37365
  };
37072
37366
  } catch (e) {
37367
+ if (classifyBridgeError(e) === "bridge_down") {
37368
+ const downErr = e;
37369
+ throw new Error(
37370
+ `Canvas auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
37371
+ );
37372
+ }
37073
37373
  const msg = e instanceof Error ? e.message : String(e);
37074
37374
  throw new Error(
37075
37375
  `Canvas auth: no CANVAS_TOKEN, CANVAS_CLIENT_*/CANVAS_REFRESH_TOKEN, or CANVAS_USERNAME/CANVAS_PASSWORD set, and fetchproxy fallback failed: ${msg}`
@@ -37794,7 +38094,7 @@ try {
37794
38094
  } catch (e) {
37795
38095
  configError = e;
37796
38096
  }
37797
- var server = new McpServer({ name: "canvas", version: "1.1.3" });
38097
+ var server = new McpServer({ name: "canvas", version: "1.1.4" });
37798
38098
  if (resolved) {
37799
38099
  const client = new CanvasClient(resolved.account, {
37800
38100
  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.3' }); // x-release-please-version
47
+ const server = new McpServer({ name: 'canvas', version: '1.1.4' }); // 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.3",
3
+ "version": "1.1.4",
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,7 +44,8 @@
44
44
  "test:watch": "vitest"
45
45
  },
46
46
  "dependencies": {
47
- "@fetchproxy/bootstrap": "^0.6.0",
47
+ "@fetchproxy/bootstrap": "^0.8.0",
48
+ "@fetchproxy/server": "^0.8.0",
48
49
  "@modelcontextprotocol/sdk": "^1.29.0",
49
50
  "dotenv": "^17.4.2",
50
51
  "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.1.3",
9
+ "version": "1.1.4",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "canvas-parent-mcp",
14
- "version": "1.1.3",
14
+ "version": "1.1.4",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },