infinitecampus-mcp 2.2.3 → 2.3.0
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/auth.js +13 -0
- package/dist/bundle.js +317 -17
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/server.json +2 -2
|
@@ -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.
|
|
10
|
+
"version": "2.3.0"
|
|
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.
|
|
18
|
+
"version": "2.3.0",
|
|
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
|
+
"version": "2.3.0",
|
|
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/auth.js
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
// - `loadAccount()` (the existing env-var resolver) is reused as-is so the
|
|
48
48
|
// legacy paths keep working unchanged.
|
|
49
49
|
import { bootstrap } from '@fetchproxy/bootstrap';
|
|
50
|
+
import { classifyBridgeError } from '@fetchproxy/server';
|
|
50
51
|
import { loadAccount } from './config.js';
|
|
51
52
|
import pkg from '../package.json' with { type: 'json' };
|
|
52
53
|
function readEnv(key) {
|
|
@@ -123,6 +124,18 @@ export async function resolveAuth() {
|
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
catch (e) {
|
|
127
|
+
// 0.8.0+ typed-error discrimination. The fetchproxy server already
|
|
128
|
+
// retries once on SW eviction (bridgeReviveDelayMs=2000 default), so
|
|
129
|
+
// a thrown FetchproxyBridgeDownError means the retry also failed —
|
|
130
|
+
// the extension's service worker is genuinely down and the user
|
|
131
|
+
// needs to wake it. The `.hint` is the actionable copy
|
|
132
|
+
// ("click the extension toolbar icon...") that we'd otherwise have
|
|
133
|
+
// to hand-write here. Surface it verbatim so users in path 2 get
|
|
134
|
+
// the same self-service guidance as path 3.
|
|
135
|
+
if (classifyBridgeError(e) === 'bridge_down') {
|
|
136
|
+
const downErr = e;
|
|
137
|
+
throw new Error(`IC auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
|
|
138
|
+
}
|
|
126
139
|
const msg = e instanceof Error ? e.message : String(e);
|
|
127
140
|
throw new Error('IC auth: no IC_USERNAME/IC_PASSWORD set, and fetchproxy fallback failed: ' + msg);
|
|
128
141
|
}
|
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}
|
|
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 {
|
|
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
|
-
|
|
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
|
|
36227
|
-
|
|
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
|
|
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
|
|
36449
|
-
|
|
36450
|
-
|
|
36639
|
+
const decls = this.opts.captureHeaders;
|
|
36640
|
+
let resolved;
|
|
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
|
+
resolved = 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
|
+
resolved = 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 = { ...resolved, ...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: resolved.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: resolved.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({
|
|
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({
|
|
36829
|
+
fetchCb({
|
|
36830
|
+
ok: false,
|
|
36831
|
+
error: error51,
|
|
36832
|
+
kind: classifyFetchError(error51),
|
|
36833
|
+
retryAttempted: false
|
|
36834
|
+
});
|
|
36569
36835
|
}
|
|
36570
36836
|
} else {
|
|
36571
|
-
fetchCb({
|
|
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({
|
|
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)
|
|
@@ -36904,7 +37197,7 @@ function loadAccount(env = process.env) {
|
|
|
36904
37197
|
// package.json
|
|
36905
37198
|
var package_default = {
|
|
36906
37199
|
name: "infinitecampus-mcp",
|
|
36907
|
-
version: "2.
|
|
37200
|
+
version: "2.3.0",
|
|
36908
37201
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
36909
37202
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
36910
37203
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -36946,7 +37239,8 @@ var package_default = {
|
|
|
36946
37239
|
"test:watch": "vitest"
|
|
36947
37240
|
},
|
|
36948
37241
|
dependencies: {
|
|
36949
|
-
"@fetchproxy/bootstrap": "^0.
|
|
37242
|
+
"@fetchproxy/bootstrap": "^0.8.0",
|
|
37243
|
+
"@fetchproxy/server": "^0.8.0",
|
|
36950
37244
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36951
37245
|
dotenv: "^17.4.2",
|
|
36952
37246
|
zod: "^4.4.3"
|
|
@@ -37022,6 +37316,12 @@ async function resolveAuth() {
|
|
|
37022
37316
|
source: "fetchproxy"
|
|
37023
37317
|
};
|
|
37024
37318
|
} catch (e) {
|
|
37319
|
+
if (classifyBridgeError(e) === "bridge_down") {
|
|
37320
|
+
const downErr = e;
|
|
37321
|
+
throw new Error(
|
|
37322
|
+
`IC auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
|
|
37323
|
+
);
|
|
37324
|
+
}
|
|
37025
37325
|
const msg = e instanceof Error ? e.message : String(e);
|
|
37026
37326
|
throw new Error(
|
|
37027
37327
|
"IC auth: no IC_USERNAME/IC_PASSWORD set, and fetchproxy fallback failed: " + msg
|
|
@@ -38275,7 +38575,7 @@ try {
|
|
|
38275
38575
|
} catch (e) {
|
|
38276
38576
|
configError = e;
|
|
38277
38577
|
}
|
|
38278
|
-
var server = new McpServer({ name: "infinitecampus", version: "2.
|
|
38578
|
+
var server = new McpServer({ name: "infinitecampus", version: "2.3.0" });
|
|
38279
38579
|
if (account) {
|
|
38280
38580
|
const client = new ICClient(account, { preloaded });
|
|
38281
38581
|
registerDistrictTools(server, client);
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ try {
|
|
|
50
50
|
catch (e) {
|
|
51
51
|
configError = e;
|
|
52
52
|
}
|
|
53
|
-
const server = new McpServer({ name: 'infinitecampus', version: '2.
|
|
53
|
+
const server = new McpServer({ name: 'infinitecampus', version: '2.3.0' }); // x-release-please-version
|
|
54
54
|
if (account) {
|
|
55
55
|
const client = new ICClient(account, { preloaded });
|
|
56
56
|
registerDistrictTools(server, client);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinitecampus-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
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,7 +42,8 @@
|
|
|
42
42
|
"test:watch": "vitest"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@fetchproxy/bootstrap": "^0.
|
|
45
|
+
"@fetchproxy/bootstrap": "^0.8.0",
|
|
46
|
+
"@fetchproxy/server": "^0.8.0",
|
|
46
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
48
|
"dotenv": "^17.4.2",
|
|
48
49
|
"zod": "^4.4.3"
|
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.
|
|
9
|
+
"version": "2.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "infinitecampus-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.3.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|