infinitecampus-mcp 2.4.4 → 2.4.5
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/bundle.js +481 -73
- package/dist/index.js +1 -1
- package/package.json +3 -3
- 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.4.
|
|
10
|
+
"version": "2.4.5"
|
|
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.
|
|
18
|
+
"version": "2.4.5",
|
|
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.
|
|
4
|
+
"version": "2.4.5",
|
|
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
|
@@ -3651,7 +3651,12 @@ var require_fast_uri = __commonJS({
|
|
|
3651
3651
|
}
|
|
3652
3652
|
function resolve(baseURI, relativeURI, options) {
|
|
3653
3653
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3654
|
-
const
|
|
3654
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3655
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3656
|
+
if (baseMalformed || relativeMalformed) {
|
|
3657
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3658
|
+
}
|
|
3659
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3655
3660
|
schemelessOptions.skipEscape = true;
|
|
3656
3661
|
return serialize(resolved, schemelessOptions);
|
|
3657
3662
|
}
|
|
@@ -3777,6 +3782,7 @@ var require_fast_uri = __commonJS({
|
|
|
3777
3782
|
}
|
|
3778
3783
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3779
3784
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3785
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3780
3786
|
function getParseError(parsed, matches) {
|
|
3781
3787
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3782
3788
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3811,6 +3817,20 @@ var require_fast_uri = __commonJS({
|
|
|
3811
3817
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3812
3818
|
malformedAuthorityOrPort = true;
|
|
3813
3819
|
}
|
|
3820
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3821
|
+
if (introducerMatch !== null) {
|
|
3822
|
+
const region = introducerMatch[1];
|
|
3823
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3824
|
+
if (normalizedRegion.length >= 2) {
|
|
3825
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3826
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3827
|
+
malformedAuthorityOrPort = true;
|
|
3828
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3829
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3830
|
+
malformedAuthorityOrPort = true;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3814
3834
|
const matches = uri.match(URI_PARSE);
|
|
3815
3835
|
if (matches) {
|
|
3816
3836
|
parsed.scheme = matches[1];
|
|
@@ -10573,7 +10593,7 @@ var require_websocket_server = __commonJS({
|
|
|
10573
10593
|
});
|
|
10574
10594
|
|
|
10575
10595
|
// src/index.ts
|
|
10576
|
-
import { dirname as dirname2, join as
|
|
10596
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
10577
10597
|
import { fileURLToPath } from "url";
|
|
10578
10598
|
|
|
10579
10599
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -34854,8 +34874,15 @@ var pageSchema = {
|
|
|
34854
34874
|
};
|
|
34855
34875
|
|
|
34856
34876
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
34857
|
-
var PROTOCOL_VERSION =
|
|
34877
|
+
var PROTOCOL_VERSION = 3;
|
|
34858
34878
|
var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
34879
|
+
function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub) {
|
|
34880
|
+
const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
|
|
34881
|
+
out.set(mcpHelloNonce, 0);
|
|
34882
|
+
out.set(extHelloNonce, mcpHelloNonce.length);
|
|
34883
|
+
out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
|
|
34884
|
+
return out;
|
|
34885
|
+
}
|
|
34859
34886
|
var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
34860
34887
|
"fetch",
|
|
34861
34888
|
"read_cookies",
|
|
@@ -34866,7 +34893,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
34866
34893
|
"read_indexed_db",
|
|
34867
34894
|
"read_dom",
|
|
34868
34895
|
"download",
|
|
34869
|
-
"graphql"
|
|
34896
|
+
"graphql",
|
|
34897
|
+
"write_cookies"
|
|
34870
34898
|
]);
|
|
34871
34899
|
|
|
34872
34900
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
@@ -35439,6 +35467,34 @@ function validateInnerRequest(raw) {
|
|
|
35439
35467
|
}
|
|
35440
35468
|
return raw;
|
|
35441
35469
|
}
|
|
35470
|
+
if (raw.op === "write_cookies") {
|
|
35471
|
+
assertObject(raw.init, "inner.init");
|
|
35472
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
35473
|
+
if (!Array.isArray(raw.init.cookies) || raw.init.cookies.length === 0) {
|
|
35474
|
+
throw new ProtocolError("inner.init.cookies: must be a non-empty array");
|
|
35475
|
+
}
|
|
35476
|
+
for (const [i, entry] of raw.init.cookies.entries()) {
|
|
35477
|
+
assertObject(entry, `inner.init.cookies[${i}]`);
|
|
35478
|
+
assertString(entry.name, `inner.init.cookies[${i}].name`);
|
|
35479
|
+
if (!SCOPE_KEY_RE.test(entry.name)) {
|
|
35480
|
+
throw new ProtocolError(`inner.init.cookies[${i}].name: invalid key ${JSON.stringify(entry.name)}`);
|
|
35481
|
+
}
|
|
35482
|
+
assertString(entry.value, `inner.init.cookies[${i}].value`);
|
|
35483
|
+
for (const k of Object.keys(entry)) {
|
|
35484
|
+
if (k !== "name" && k !== "value") {
|
|
35485
|
+
throw new ProtocolError(`inner.init.cookies[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
35486
|
+
}
|
|
35487
|
+
}
|
|
35488
|
+
}
|
|
35489
|
+
if (raw.init.path !== void 0)
|
|
35490
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
35491
|
+
for (const k of Object.keys(raw.init)) {
|
|
35492
|
+
if (k !== "origin" && k !== "cookies" && k !== "path") {
|
|
35493
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on write_cookies`);
|
|
35494
|
+
}
|
|
35495
|
+
}
|
|
35496
|
+
return raw;
|
|
35497
|
+
}
|
|
35442
35498
|
if (raw.op === "read_local_storage" || raw.op === "read_session_storage") {
|
|
35443
35499
|
assertObject(raw.init, "inner.init");
|
|
35444
35500
|
if (raw.init.origin === void 0) {
|
|
@@ -35625,7 +35681,7 @@ function validateInnerRequest(raw) {
|
|
|
35625
35681
|
}
|
|
35626
35682
|
return raw;
|
|
35627
35683
|
}
|
|
35628
|
-
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)}`);
|
|
35684
|
+
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
35629
35685
|
}
|
|
35630
35686
|
function assertNonEmptyKeyArray(value, label) {
|
|
35631
35687
|
if (!Array.isArray(value)) {
|
|
@@ -35686,6 +35742,18 @@ function validateInnerResponse(raw) {
|
|
|
35686
35742
|
}
|
|
35687
35743
|
return raw;
|
|
35688
35744
|
}
|
|
35745
|
+
if (op === "write_cookies") {
|
|
35746
|
+
if (raw.written === void 0) {
|
|
35747
|
+
throw new ProtocolError("inner.written: missing on write_cookies response");
|
|
35748
|
+
}
|
|
35749
|
+
if (!Array.isArray(raw.written)) {
|
|
35750
|
+
throw new ProtocolError("inner.written: must be an array");
|
|
35751
|
+
}
|
|
35752
|
+
for (const [i, name] of raw.written.entries()) {
|
|
35753
|
+
assertString(name, `inner.written[${i}]`);
|
|
35754
|
+
}
|
|
35755
|
+
return raw;
|
|
35756
|
+
}
|
|
35689
35757
|
if (op === "read_local_storage" || op === "read_session_storage") {
|
|
35690
35758
|
if (raw.values === void 0) {
|
|
35691
35759
|
throw new ProtocolError(`inner.values: missing on ${String(op)} response`);
|
|
@@ -36163,6 +36231,148 @@ async function awaitSessionReady(ready, opts) {
|
|
|
36163
36231
|
}
|
|
36164
36232
|
}
|
|
36165
36233
|
|
|
36234
|
+
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36235
|
+
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
36236
|
+
import { join as join2 } from "node:path";
|
|
36237
|
+
|
|
36238
|
+
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36239
|
+
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36240
|
+
import { join } from "node:path";
|
|
36241
|
+
import { homedir } from "node:os";
|
|
36242
|
+
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36243
|
+
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36244
|
+
function defaultIdentityDir() {
|
|
36245
|
+
return join(homedir(), ".fetchproxy", "identity");
|
|
36246
|
+
}
|
|
36247
|
+
function safeIdentityFileBase(serverName) {
|
|
36248
|
+
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
36249
|
+
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
36250
|
+
}
|
|
36251
|
+
return serverName.replace(/\//g, "_");
|
|
36252
|
+
}
|
|
36253
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36254
|
+
const safeFile = safeIdentityFileBase(serverName);
|
|
36255
|
+
const path = join(dir, `${safeFile}.json`);
|
|
36256
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36257
|
+
try {
|
|
36258
|
+
const raw = await readFile(path, "utf8");
|
|
36259
|
+
const j2 = JSON.parse(raw);
|
|
36260
|
+
return {
|
|
36261
|
+
x25519Priv: fromB64(j2.x25519Priv),
|
|
36262
|
+
x25519Pub: fromB64(j2.x25519Pub),
|
|
36263
|
+
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
36264
|
+
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
36265
|
+
createdAt: j2.createdAt
|
|
36266
|
+
};
|
|
36267
|
+
} catch (e) {
|
|
36268
|
+
if (e.code !== "ENOENT")
|
|
36269
|
+
throw e;
|
|
36270
|
+
}
|
|
36271
|
+
const x = await generateX25519();
|
|
36272
|
+
const ed = await generateEd25519();
|
|
36273
|
+
const id = {
|
|
36274
|
+
x25519Priv: x.privateKey,
|
|
36275
|
+
x25519Pub: x.publicKey,
|
|
36276
|
+
ed25519Priv: ed.privateKey,
|
|
36277
|
+
ed25519Pub: ed.publicKey,
|
|
36278
|
+
createdAt: Date.now()
|
|
36279
|
+
};
|
|
36280
|
+
const j = {
|
|
36281
|
+
x25519Priv: toB64(id.x25519Priv),
|
|
36282
|
+
x25519Pub: toB64(id.x25519Pub),
|
|
36283
|
+
ed25519Priv: toB64(id.ed25519Priv),
|
|
36284
|
+
ed25519Pub: toB64(id.ed25519Pub),
|
|
36285
|
+
createdAt: id.createdAt
|
|
36286
|
+
};
|
|
36287
|
+
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
36288
|
+
await chmod(path, 384);
|
|
36289
|
+
return id;
|
|
36290
|
+
}
|
|
36291
|
+
|
|
36292
|
+
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36293
|
+
function fileExtensionTrust(args) {
|
|
36294
|
+
return {
|
|
36295
|
+
allowNew: args.allowNew,
|
|
36296
|
+
location: extensionTrustPath(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
36297
|
+
read: () => readExtensionPin(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
36298
|
+
write: (pin) => writeExtensionPin(args.serverName, pin, args.dir ?? defaultIdentityDir())
|
|
36299
|
+
};
|
|
36300
|
+
}
|
|
36301
|
+
var TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
|
|
36302
|
+
function allowNewExtensionIdentity(explicit, env = process.env) {
|
|
36303
|
+
if (explicit !== void 0)
|
|
36304
|
+
return explicit;
|
|
36305
|
+
return env[TRUST_NEW_EXTENSION_ENV] === "1";
|
|
36306
|
+
}
|
|
36307
|
+
function decideExtensionTrust(args) {
|
|
36308
|
+
const { pin, hello, allowNew, serverName } = args;
|
|
36309
|
+
if (!pin)
|
|
36310
|
+
return { decision: "first-use" };
|
|
36311
|
+
if (pin.identityX25519Pub === hello.identityX25519Pub && pin.identityEd25519Pub === hello.identityEd25519Pub) {
|
|
36312
|
+
return { decision: "pinned" };
|
|
36313
|
+
}
|
|
36314
|
+
const trustPath = args.location ?? extensionTrustPathHint(serverName);
|
|
36315
|
+
if (allowNew) {
|
|
36316
|
+
return {
|
|
36317
|
+
decision: "replace",
|
|
36318
|
+
message: `[fetchproxy] ${serverName}: accepting a NEW extension identity because ${TRUST_NEW_EXTENSION_ENV}=1 \u2014 re-pinning. Unset it once the browser you expect is connected.`
|
|
36319
|
+
};
|
|
36320
|
+
}
|
|
36321
|
+
return {
|
|
36322
|
+
decision: "refused",
|
|
36323
|
+
message: `[fetchproxy] ${serverName}: refusing an extension whose identity is not the one this MCP paired with. If you re-installed the extension or moved to another browser, re-pair deliberately: run this MCP once with ${TRUST_NEW_EXTENSION_ENV}=1, or delete ${trustPath}. If you did neither, something else is answering as your browser.`
|
|
36324
|
+
};
|
|
36325
|
+
}
|
|
36326
|
+
function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
|
|
36327
|
+
return join2(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
36328
|
+
}
|
|
36329
|
+
function extensionTrustPathHint(serverName) {
|
|
36330
|
+
try {
|
|
36331
|
+
return extensionTrustPath(serverName);
|
|
36332
|
+
} catch {
|
|
36333
|
+
return join2(defaultIdentityDir(), "<server-name>.extension-trust.json");
|
|
36334
|
+
}
|
|
36335
|
+
}
|
|
36336
|
+
function isPin(x) {
|
|
36337
|
+
if (!x || typeof x !== "object")
|
|
36338
|
+
return false;
|
|
36339
|
+
const r = x;
|
|
36340
|
+
return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
|
|
36341
|
+
}
|
|
36342
|
+
async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
|
|
36343
|
+
const path = extensionTrustPath(serverName, dir);
|
|
36344
|
+
let raw;
|
|
36345
|
+
try {
|
|
36346
|
+
raw = await readFile2(path, "utf8");
|
|
36347
|
+
} catch (e) {
|
|
36348
|
+
if (e.code === "ENOENT")
|
|
36349
|
+
return null;
|
|
36350
|
+
throw e;
|
|
36351
|
+
}
|
|
36352
|
+
let parsed;
|
|
36353
|
+
try {
|
|
36354
|
+
parsed = JSON.parse(raw);
|
|
36355
|
+
} catch {
|
|
36356
|
+
throw new Error(`unreadable extension pin at ${path} (not JSON) \u2014 delete it to re-pair`);
|
|
36357
|
+
}
|
|
36358
|
+
if (!isPin(parsed)) {
|
|
36359
|
+
throw new Error(`unreadable extension pin at ${path} (wrong shape) \u2014 delete it to re-pair`);
|
|
36360
|
+
}
|
|
36361
|
+
return {
|
|
36362
|
+
identityX25519Pub: parsed.identityX25519Pub,
|
|
36363
|
+
identityEd25519Pub: parsed.identityEd25519Pub,
|
|
36364
|
+
pinnedAt: parsed.pinnedAt
|
|
36365
|
+
};
|
|
36366
|
+
}
|
|
36367
|
+
async function writeExtensionPin(serverName, pin, dir = defaultIdentityDir()) {
|
|
36368
|
+
const path = extensionTrustPath(serverName, dir);
|
|
36369
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
36370
|
+
const tmp = `${path}.tmp`;
|
|
36371
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
36372
|
+
await chmod2(tmp, 384);
|
|
36373
|
+
await rename(tmp, path);
|
|
36374
|
+
}
|
|
36375
|
+
|
|
36166
36376
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
36167
36377
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
36168
36378
|
var enc2 = new TextEncoder();
|
|
@@ -36216,9 +36426,12 @@ async function startHost(opts) {
|
|
|
36216
36426
|
}
|
|
36217
36427
|
resetSessionPromise();
|
|
36218
36428
|
let extensionHello = null;
|
|
36429
|
+
let extensionClaim = null;
|
|
36219
36430
|
wss.on("connection", (ws) => {
|
|
36220
36431
|
let identified = null;
|
|
36221
36432
|
let peerMcpId = null;
|
|
36433
|
+
let closed = false;
|
|
36434
|
+
let pinOnReady = false;
|
|
36222
36435
|
ws.on("message", async (data) => {
|
|
36223
36436
|
try {
|
|
36224
36437
|
let frame;
|
|
@@ -36230,10 +36443,43 @@ async function startHost(opts) {
|
|
|
36230
36443
|
return;
|
|
36231
36444
|
}
|
|
36232
36445
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
36233
|
-
if (extensionWs) {
|
|
36446
|
+
if (extensionWs || extensionClaim) {
|
|
36234
36447
|
ws.close(1008, "extension already connected");
|
|
36235
36448
|
return;
|
|
36236
36449
|
}
|
|
36450
|
+
extensionClaim = ws;
|
|
36451
|
+
let pin;
|
|
36452
|
+
try {
|
|
36453
|
+
pin = await opts.extensionTrust.read();
|
|
36454
|
+
} catch (e) {
|
|
36455
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
36456
|
+
if (extensionClaim === ws)
|
|
36457
|
+
extensionClaim = null;
|
|
36458
|
+
ws.close(1008, "extension pin unreadable");
|
|
36459
|
+
return;
|
|
36460
|
+
}
|
|
36461
|
+
const outcome = decideExtensionTrust({
|
|
36462
|
+
pin,
|
|
36463
|
+
hello: frame,
|
|
36464
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
36465
|
+
serverName: opts.ownServerName,
|
|
36466
|
+
location: opts.extensionTrust.location
|
|
36467
|
+
});
|
|
36468
|
+
if (outcome.decision === "refused") {
|
|
36469
|
+
console.warn(outcome.message);
|
|
36470
|
+
if (extensionClaim === ws)
|
|
36471
|
+
extensionClaim = null;
|
|
36472
|
+
ws.close(1008, "extension identity is not the pinned one");
|
|
36473
|
+
return;
|
|
36474
|
+
}
|
|
36475
|
+
if (outcome.decision === "replace")
|
|
36476
|
+
console.warn(outcome.message);
|
|
36477
|
+
if (closed || ws.readyState !== import_websocket.default.OPEN) {
|
|
36478
|
+
if (extensionClaim === ws)
|
|
36479
|
+
extensionClaim = null;
|
|
36480
|
+
return;
|
|
36481
|
+
}
|
|
36482
|
+
pinOnReady = outcome.decision !== "pinned";
|
|
36237
36483
|
identified = "extension";
|
|
36238
36484
|
extensionWs = ws;
|
|
36239
36485
|
extensionHello = frame;
|
|
@@ -36245,6 +36491,8 @@ async function startHost(opts) {
|
|
|
36245
36491
|
console.error("[fetchproxy] onPairCode threw:", e);
|
|
36246
36492
|
}
|
|
36247
36493
|
}
|
|
36494
|
+
for (const slot of peers.values())
|
|
36495
|
+
slot.ws.send(JSON.stringify(frame));
|
|
36248
36496
|
ws.send(JSON.stringify(ownHello));
|
|
36249
36497
|
for (const slot of peers.values()) {
|
|
36250
36498
|
ws.send(JSON.stringify(slot.helloFrame));
|
|
@@ -36280,6 +36528,8 @@ async function startHost(opts) {
|
|
|
36280
36528
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
36281
36529
|
if (extensionWs)
|
|
36282
36530
|
extensionWs.send(JSON.stringify(frame));
|
|
36531
|
+
if (extensionHello)
|
|
36532
|
+
ws.send(JSON.stringify(extensionHello));
|
|
36283
36533
|
return;
|
|
36284
36534
|
}
|
|
36285
36535
|
if (frame.type === "ready") {
|
|
@@ -36291,7 +36541,7 @@ async function startHost(opts) {
|
|
|
36291
36541
|
}
|
|
36292
36542
|
const extEdPub = fromB64(extensionHello.identityEd25519Pub);
|
|
36293
36543
|
const extNonce = fromB64(extensionHello.sessionNonce);
|
|
36294
|
-
const msg =
|
|
36544
|
+
const msg = readySignaturePayload(ownSessionNonce, extNonce, fromB64(frame.extensionSessionPub));
|
|
36295
36545
|
const sig = fromB64(frame.sessionSig);
|
|
36296
36546
|
let sigOk = false;
|
|
36297
36547
|
try {
|
|
@@ -36304,6 +36554,18 @@ async function startHost(opts) {
|
|
|
36304
36554
|
ws.close(1008, "extension session signature invalid");
|
|
36305
36555
|
return;
|
|
36306
36556
|
}
|
|
36557
|
+
if (pinOnReady) {
|
|
36558
|
+
pinOnReady = false;
|
|
36559
|
+
try {
|
|
36560
|
+
await opts.extensionTrust.write({
|
|
36561
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
36562
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
36563
|
+
pinnedAt: Date.now()
|
|
36564
|
+
});
|
|
36565
|
+
} catch (e) {
|
|
36566
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
36567
|
+
}
|
|
36568
|
+
}
|
|
36307
36569
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
36308
36570
|
const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
|
|
36309
36571
|
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -36357,6 +36619,9 @@ async function startHost(opts) {
|
|
|
36357
36619
|
}
|
|
36358
36620
|
});
|
|
36359
36621
|
ws.on("close", () => {
|
|
36622
|
+
closed = true;
|
|
36623
|
+
if (extensionClaim === ws)
|
|
36624
|
+
extensionClaim = null;
|
|
36360
36625
|
if (identified === "extension" && extensionWs === ws) {
|
|
36361
36626
|
extensionWs = null;
|
|
36362
36627
|
extensionHello = null;
|
|
@@ -36447,11 +36712,84 @@ async function startPeer(opts) {
|
|
|
36447
36712
|
resolveFirstReady = resolve;
|
|
36448
36713
|
rejectFirstReady = reject;
|
|
36449
36714
|
});
|
|
36715
|
+
let extensionHello = null;
|
|
36716
|
+
let warnedUnverifiable = false;
|
|
36717
|
+
let cachedPin = void 0;
|
|
36718
|
+
const authenticateExtension = async (sessionSig, extensionSessionPub) => {
|
|
36719
|
+
if (!extensionHello) {
|
|
36720
|
+
if (opts.requireExtensionIdentity) {
|
|
36721
|
+
console.error(`[fetchproxy] ${opts.serverName}: the concentrator does not forward the extension's identity, so this session cannot be verified \u2014 refusing. Upgrade the MCP holding the bridge port to 1.12.0 or later.`);
|
|
36722
|
+
return false;
|
|
36723
|
+
}
|
|
36724
|
+
if (!warnedUnverifiable) {
|
|
36725
|
+
warnedUnverifiable = true;
|
|
36726
|
+
console.warn(`[fetchproxy] ${opts.serverName}: the concentrator does not forward the extension's identity (pre-1.12.0), so this peer cannot verify which browser it is talking to. Upgrade the MCP holding the bridge port to close this.`);
|
|
36727
|
+
}
|
|
36728
|
+
return true;
|
|
36729
|
+
}
|
|
36730
|
+
const payload = readySignaturePayload(sessionNonce, fromB64(extensionHello.sessionNonce), fromB64(extensionSessionPub));
|
|
36731
|
+
let sigOk = false;
|
|
36732
|
+
try {
|
|
36733
|
+
sigOk = await ed25519Verify(fromB64(extensionHello.identityEd25519Pub), payload, fromB64(sessionSig));
|
|
36734
|
+
} catch {
|
|
36735
|
+
sigOk = false;
|
|
36736
|
+
}
|
|
36737
|
+
if (!sigOk) {
|
|
36738
|
+
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
36739
|
+
return false;
|
|
36740
|
+
}
|
|
36741
|
+
if (cachedPin === void 0) {
|
|
36742
|
+
try {
|
|
36743
|
+
cachedPin = await opts.extensionTrust.read();
|
|
36744
|
+
} catch (e) {
|
|
36745
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
36746
|
+
return false;
|
|
36747
|
+
}
|
|
36748
|
+
}
|
|
36749
|
+
const pin = cachedPin;
|
|
36750
|
+
const outcome = decideExtensionTrust({
|
|
36751
|
+
pin,
|
|
36752
|
+
hello: extensionHello,
|
|
36753
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
36754
|
+
serverName: opts.serverName,
|
|
36755
|
+
location: opts.extensionTrust.location
|
|
36756
|
+
});
|
|
36757
|
+
if (outcome.decision === "refused") {
|
|
36758
|
+
console.warn(outcome.message);
|
|
36759
|
+
return false;
|
|
36760
|
+
}
|
|
36761
|
+
if (outcome.decision === "replace")
|
|
36762
|
+
console.warn(outcome.message);
|
|
36763
|
+
if (outcome.decision !== "pinned") {
|
|
36764
|
+
try {
|
|
36765
|
+
const written = {
|
|
36766
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
36767
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
36768
|
+
pinnedAt: Date.now()
|
|
36769
|
+
};
|
|
36770
|
+
await opts.extensionTrust.write(written);
|
|
36771
|
+
cachedPin = written;
|
|
36772
|
+
} catch (e) {
|
|
36773
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
36774
|
+
}
|
|
36775
|
+
}
|
|
36776
|
+
return true;
|
|
36777
|
+
};
|
|
36450
36778
|
const onMessage = async (data) => {
|
|
36451
36779
|
try {
|
|
36452
36780
|
const raw = JSON.parse(data.toString());
|
|
36453
36781
|
const frame = validateFrame(raw);
|
|
36782
|
+
if (frame.type === "hello" && frame.role === "extension") {
|
|
36783
|
+
extensionHello = frame;
|
|
36784
|
+
return;
|
|
36785
|
+
}
|
|
36454
36786
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
36787
|
+
const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
|
|
36788
|
+
if (!authorised) {
|
|
36789
|
+
ws.close(1008, "extension identity refused");
|
|
36790
|
+
rejectFirstReady(new Error("peer: extension identity refused"));
|
|
36791
|
+
return;
|
|
36792
|
+
}
|
|
36455
36793
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
36456
36794
|
const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
|
|
36457
36795
|
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -36532,57 +36870,6 @@ async function startPeer(opts) {
|
|
|
36532
36870
|
return handle;
|
|
36533
36871
|
}
|
|
36534
36872
|
|
|
36535
|
-
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36536
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36537
|
-
import { join } from "node:path";
|
|
36538
|
-
import { homedir } from "node:os";
|
|
36539
|
-
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36540
|
-
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36541
|
-
function defaultIdentityDir() {
|
|
36542
|
-
return join(homedir(), ".fetchproxy", "identity");
|
|
36543
|
-
}
|
|
36544
|
-
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36545
|
-
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
36546
|
-
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
36547
|
-
}
|
|
36548
|
-
const safeFile = serverName.replace(/\//g, "_");
|
|
36549
|
-
const path = join(dir, `${safeFile}.json`);
|
|
36550
|
-
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36551
|
-
try {
|
|
36552
|
-
const raw = await readFile(path, "utf8");
|
|
36553
|
-
const j2 = JSON.parse(raw);
|
|
36554
|
-
return {
|
|
36555
|
-
x25519Priv: fromB64(j2.x25519Priv),
|
|
36556
|
-
x25519Pub: fromB64(j2.x25519Pub),
|
|
36557
|
-
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
36558
|
-
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
36559
|
-
createdAt: j2.createdAt
|
|
36560
|
-
};
|
|
36561
|
-
} catch (e) {
|
|
36562
|
-
if (e.code !== "ENOENT")
|
|
36563
|
-
throw e;
|
|
36564
|
-
}
|
|
36565
|
-
const x = await generateX25519();
|
|
36566
|
-
const ed = await generateEd25519();
|
|
36567
|
-
const id = {
|
|
36568
|
-
x25519Priv: x.privateKey,
|
|
36569
|
-
x25519Pub: x.publicKey,
|
|
36570
|
-
ed25519Priv: ed.privateKey,
|
|
36571
|
-
ed25519Pub: ed.publicKey,
|
|
36572
|
-
createdAt: Date.now()
|
|
36573
|
-
};
|
|
36574
|
-
const j = {
|
|
36575
|
-
x25519Priv: toB64(id.x25519Priv),
|
|
36576
|
-
x25519Pub: toB64(id.x25519Pub),
|
|
36577
|
-
ed25519Priv: toB64(id.ed25519Priv),
|
|
36578
|
-
ed25519Pub: toB64(id.ed25519Pub),
|
|
36579
|
-
createdAt: id.createdAt
|
|
36580
|
-
};
|
|
36581
|
-
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
36582
|
-
await chmod(path, 384);
|
|
36583
|
-
return id;
|
|
36584
|
-
}
|
|
36585
|
-
|
|
36586
36873
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
36587
36874
|
function classifyFetchError(error51) {
|
|
36588
36875
|
if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
|
|
@@ -36664,21 +36951,38 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
36664
36951
|
this.hint = hint;
|
|
36665
36952
|
}
|
|
36666
36953
|
};
|
|
36667
|
-
var
|
|
36954
|
+
var FetchproxyHintedError = class extends FetchproxyProtocolError {
|
|
36668
36955
|
/** The extension's raw rejection, unmodified. */
|
|
36669
36956
|
originalError;
|
|
36957
|
+
/** What the user should actually do, in prose. */
|
|
36670
36958
|
hint;
|
|
36671
|
-
constructor(originalError) {
|
|
36672
|
-
const hint = "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the Transporter extension popup, then re-run \u2014 you will be asked to approve the new scope. This is not a version problem and does not need an update.";
|
|
36959
|
+
constructor(originalError, hint) {
|
|
36673
36960
|
super(`${originalError} \u2014 ${hint}`);
|
|
36674
|
-
this.name = "
|
|
36961
|
+
this.name = "FetchproxyHintedError";
|
|
36675
36962
|
this.originalError = originalError;
|
|
36676
36963
|
this.hint = hint;
|
|
36677
36964
|
}
|
|
36678
36965
|
};
|
|
36966
|
+
var FetchproxyScopeError = class extends FetchproxyHintedError {
|
|
36967
|
+
constructor(originalError) {
|
|
36968
|
+
super(originalError, "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the Transporter extension popup, then re-run \u2014 you will be asked to approve the new scope. This is not a version problem and does not need an update.");
|
|
36969
|
+
this.name = "FetchproxyScopeError";
|
|
36970
|
+
}
|
|
36971
|
+
};
|
|
36972
|
+
var FetchproxyNoTabError = class extends FetchproxyHintedError {
|
|
36973
|
+
constructor(originalError) {
|
|
36974
|
+
super(originalError, "open a tab on that host and sign in, then re-run. This is not a version problem and does not need an update.");
|
|
36975
|
+
this.name = "FetchproxyNoTabError";
|
|
36976
|
+
}
|
|
36977
|
+
};
|
|
36679
36978
|
var SCOPE_REJECTION = /not in declared/;
|
|
36979
|
+
var NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
|
|
36680
36980
|
function protocolErrorFrom(error51) {
|
|
36681
|
-
|
|
36981
|
+
if (SCOPE_REJECTION.test(error51))
|
|
36982
|
+
return new FetchproxyScopeError(error51);
|
|
36983
|
+
if (NO_TAB_REJECTION.test(error51))
|
|
36984
|
+
return new FetchproxyNoTabError(error51);
|
|
36985
|
+
return new FetchproxyProtocolError(error51);
|
|
36682
36986
|
}
|
|
36683
36987
|
var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
36684
36988
|
url;
|
|
@@ -36784,6 +37088,9 @@ var FetchproxyServer = class {
|
|
|
36784
37088
|
// them off from `pending` (fetch) and `pendingReadCookies` (legacy
|
|
36785
37089
|
// string-shape) so the response routing in `onInner` stays linear.
|
|
36786
37090
|
pendingStorage = /* @__PURE__ */ new Map();
|
|
37091
|
+
// 1.12.0+: write-cookies awaiters resolve the list of names actually
|
|
37092
|
+
// written, so a caller can confirm rather than assume.
|
|
37093
|
+
pendingWriteCookies = /* @__PURE__ */ new Map();
|
|
36787
37094
|
// 0.3.0+: capture-header awaiters resolve a single string.
|
|
36788
37095
|
pendingCapture = /* @__PURE__ */ new Map();
|
|
36789
37096
|
// capture_redirect awaiters resolve the captured redirect URL string.
|
|
@@ -36910,6 +37217,8 @@ var FetchproxyServer = class {
|
|
|
36910
37217
|
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
36911
37218
|
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
36912
37219
|
identityDir: opts.identityDir,
|
|
37220
|
+
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
37221
|
+
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
36913
37222
|
onPairCode: opts.onPairCode
|
|
36914
37223
|
};
|
|
36915
37224
|
}
|
|
@@ -37009,7 +37318,8 @@ var FetchproxyServer = class {
|
|
|
37009
37318
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37010
37319
|
ownDomSelectors: this.opts.domSelectors,
|
|
37011
37320
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
37012
|
-
onPairCode: this.opts.onPairCode
|
|
37321
|
+
onPairCode: this.opts.onPairCode,
|
|
37322
|
+
extensionTrust: this.extensionTrust()
|
|
37013
37323
|
});
|
|
37014
37324
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
37015
37325
|
this.hostHandle.onExtensionDisconnect(() => {
|
|
@@ -37038,7 +37348,9 @@ var FetchproxyServer = class {
|
|
|
37038
37348
|
localStoragePointers: this.opts.localStoragePointers,
|
|
37039
37349
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37040
37350
|
domSelectors: this.opts.domSelectors,
|
|
37041
|
-
graphqlOps: this.opts.graphqlOps
|
|
37351
|
+
graphqlOps: this.opts.graphqlOps,
|
|
37352
|
+
extensionTrust: this.extensionTrust(),
|
|
37353
|
+
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
37042
37354
|
});
|
|
37043
37355
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
37044
37356
|
this.peerHandle.onRenegotiate(() => {
|
|
@@ -37177,6 +37489,23 @@ var FetchproxyServer = class {
|
|
|
37177
37489
|
markActive() {
|
|
37178
37490
|
this.noteActivityForKeepalive();
|
|
37179
37491
|
}
|
|
37492
|
+
/**
|
|
37493
|
+
* #208: this MCP's pin on the extension's identity, stored beside its own
|
|
37494
|
+
* identity key and so following `identityDir` wherever the caller put it.
|
|
37495
|
+
*
|
|
37496
|
+
* `allowNewExtensionIdentity` falls back to an environment variable when the
|
|
37497
|
+
* caller expressed no opinion, because the thirteen MCPs that construct this
|
|
37498
|
+
* class are separate packages: an operator whose extension re-install has
|
|
37499
|
+
* just locked all of them out needs one lever that does not require patching
|
|
37500
|
+
* every one of them.
|
|
37501
|
+
*/
|
|
37502
|
+
extensionTrust() {
|
|
37503
|
+
return fileExtensionTrust({
|
|
37504
|
+
serverName: this.opts.serverName,
|
|
37505
|
+
dir: this.opts.identityDir,
|
|
37506
|
+
allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
|
|
37507
|
+
});
|
|
37508
|
+
}
|
|
37180
37509
|
noteActivityForKeepalive() {
|
|
37181
37510
|
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
37182
37511
|
if (intervalMs <= 0)
|
|
@@ -37240,6 +37569,7 @@ var FetchproxyServer = class {
|
|
|
37240
37569
|
this.pending.delete(id);
|
|
37241
37570
|
this.pendingReadCookies.delete(id);
|
|
37242
37571
|
this.pendingStorage.delete(id);
|
|
37572
|
+
this.pendingWriteCookies.delete(id);
|
|
37243
37573
|
this.pendingCapture.delete(id);
|
|
37244
37574
|
this.pendingRedirect.delete(id);
|
|
37245
37575
|
this.pendingDownload.delete(id);
|
|
@@ -37397,10 +37727,20 @@ var FetchproxyServer = class {
|
|
|
37397
37727
|
}
|
|
37398
37728
|
const url2 = isAbsolute ? path : `https://${host}${path}`;
|
|
37399
37729
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
37730
|
+
let tabUrl = `https://${host}/`;
|
|
37731
|
+
if (opts.viaTab !== void 0) {
|
|
37732
|
+
try {
|
|
37733
|
+
new URL(opts.viaTab);
|
|
37734
|
+
} catch {
|
|
37735
|
+
throw new Error(`FetchproxyServer.request: viaTab is not a valid URL: ${JSON.stringify(opts.viaTab)}`);
|
|
37736
|
+
}
|
|
37737
|
+
assertUrlInDomains("viaTab", opts.viaTab, this.opts.domains);
|
|
37738
|
+
tabUrl = opts.viaTab;
|
|
37739
|
+
}
|
|
37400
37740
|
const init = {
|
|
37401
37741
|
url: url2,
|
|
37402
37742
|
method,
|
|
37403
|
-
tabUrl
|
|
37743
|
+
tabUrl,
|
|
37404
37744
|
headers: opts.headers,
|
|
37405
37745
|
body: opts.body
|
|
37406
37746
|
};
|
|
@@ -37636,6 +37976,61 @@ var FetchproxyServer = class {
|
|
|
37636
37976
|
}
|
|
37637
37977
|
return result.cookies;
|
|
37638
37978
|
}
|
|
37979
|
+
/**
|
|
37980
|
+
* 1.12.0+: overwrite the value of cookies this MCP already declares.
|
|
37981
|
+
*
|
|
37982
|
+
* The bridge's only write verb, and it exists for one failure class. Sites
|
|
37983
|
+
* that ROTATE a credential cookie hand back a new value on every refresh; if
|
|
37984
|
+
* the MCP refreshes and keeps the result to itself, the copy in the browser's
|
|
37985
|
+
* cookie jar is dead, and the user gets signed out of a tab they never
|
|
37986
|
+
* touched — usually reported to them as "inactivity". Writing the rotated
|
|
37987
|
+
* value back is the only thing that repairs it.
|
|
37988
|
+
*
|
|
37989
|
+
* Requires `'write_cookies'` in capabilities, which the user approves at pair
|
|
37990
|
+
* time as its own line. Every name must ALSO be in declared `cookieKeys`: a
|
|
37991
|
+
* write can never reach a cookie the MCP was not already trusted to read, so
|
|
37992
|
+
* granting it cannot widen which cookies are in play — only what may be done
|
|
37993
|
+
* to the ones already listed.
|
|
37994
|
+
*
|
|
37995
|
+
* The extension refuses the whole request unless every named cookie already
|
|
37996
|
+
* exists; this refreshes a value in place and deliberately cannot author new
|
|
37997
|
+
* cookies. Returns the names actually written.
|
|
37998
|
+
*/
|
|
37999
|
+
async writeCookies(opts) {
|
|
38000
|
+
if (!this.opts.capabilities.includes("write_cookies")) {
|
|
38001
|
+
throw new Error('FetchproxyServer.writeCookies(): MCP did not declare "write_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
|
|
38002
|
+
}
|
|
38003
|
+
const names = Object.keys(opts.cookies);
|
|
38004
|
+
if (names.length === 0) {
|
|
38005
|
+
throw new Error("FetchproxyServer.writeCookies(): no cookies given");
|
|
38006
|
+
}
|
|
38007
|
+
await this.ensureConnected();
|
|
38008
|
+
this.throwIfPendingPair();
|
|
38009
|
+
if (opts.subdomain !== void 0)
|
|
38010
|
+
assertSubdomainLabel(opts.subdomain);
|
|
38011
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
38012
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
38013
|
+
this.assertScopeSubset(names, this.opts.cookieKeys, "cookieKeys");
|
|
38014
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
38015
|
+
const id = this.nextRequestId++;
|
|
38016
|
+
const inner = {
|
|
38017
|
+
type: "request",
|
|
38018
|
+
id,
|
|
38019
|
+
op: "write_cookies",
|
|
38020
|
+
init: {
|
|
38021
|
+
// Bare origin, same invariant as the read path: a path must never be
|
|
38022
|
+
// able to move the request past the domain gate.
|
|
38023
|
+
origin: `https://${host}`,
|
|
38024
|
+
cookies: Object.entries(opts.cookies).map(([name, value]) => ({ name, value })),
|
|
38025
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
38026
|
+
}
|
|
38027
|
+
};
|
|
38028
|
+
const pending = new Promise((resolve, reject) => {
|
|
38029
|
+
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
38030
|
+
});
|
|
38031
|
+
await this.sendInnerFrame(inner);
|
|
38032
|
+
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
38033
|
+
}
|
|
37639
38034
|
/**
|
|
37640
38035
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
37641
38036
|
* tab. Requires `'read_local_storage'` in capabilities AND each key
|
|
@@ -38256,6 +38651,16 @@ var FetchproxyServer = class {
|
|
|
38256
38651
|
}
|
|
38257
38652
|
return;
|
|
38258
38653
|
}
|
|
38654
|
+
const writeCookiesCb = this.pendingWriteCookies.get(inner.id);
|
|
38655
|
+
if (writeCookiesCb) {
|
|
38656
|
+
this.pendingWriteCookies.delete(inner.id);
|
|
38657
|
+
if (inner.ok && inner.op === "write_cookies") {
|
|
38658
|
+
writeCookiesCb.resolve([...inner.written]);
|
|
38659
|
+
} else {
|
|
38660
|
+
writeCookiesCb.reject(protocolErrorFrom(inner.ok ? "write_cookies response had the wrong op" : inner.error));
|
|
38661
|
+
}
|
|
38662
|
+
return;
|
|
38663
|
+
}
|
|
38259
38664
|
const cookiesCb = this.pendingReadCookies.get(inner.id);
|
|
38260
38665
|
if (cookiesCb) {
|
|
38261
38666
|
this.pendingReadCookies.delete(inner.id);
|
|
@@ -38295,6 +38700,9 @@ var FetchproxyServer = class {
|
|
|
38295
38700
|
for (const { reject } of this.pendingStorage.values())
|
|
38296
38701
|
reject(err);
|
|
38297
38702
|
this.pendingStorage.clear();
|
|
38703
|
+
for (const { reject } of this.pendingWriteCookies.values())
|
|
38704
|
+
reject(err);
|
|
38705
|
+
this.pendingWriteCookies.clear();
|
|
38298
38706
|
for (const { reject } of this.pendingCapture.values())
|
|
38299
38707
|
reject(err);
|
|
38300
38708
|
this.pendingCapture.clear();
|
|
@@ -38577,7 +38985,7 @@ function loadAccount(env = process.env) {
|
|
|
38577
38985
|
// package.json
|
|
38578
38986
|
var package_default = {
|
|
38579
38987
|
name: "infinitecampus-mcp",
|
|
38580
|
-
version: "2.4.
|
|
38988
|
+
version: "2.4.5",
|
|
38581
38989
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
38582
38990
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
38583
38991
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38620,8 +39028,8 @@ var package_default = {
|
|
|
38620
39028
|
},
|
|
38621
39029
|
dependencies: {
|
|
38622
39030
|
"@chrischall/mcp-utils": "^0.14.0",
|
|
38623
|
-
"@fetchproxy/bootstrap": "^
|
|
38624
|
-
"@fetchproxy/server": "^
|
|
39031
|
+
"@fetchproxy/bootstrap": "^2.0.0",
|
|
39032
|
+
"@fetchproxy/server": "^2.0.0",
|
|
38625
39033
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38626
39034
|
dotenv: "^17.4.2",
|
|
38627
39035
|
"node-html-parser": "^9.0.0",
|
|
@@ -38729,7 +39137,7 @@ async function liftBrowserSession(account2) {
|
|
|
38729
39137
|
}
|
|
38730
39138
|
|
|
38731
39139
|
// src/client.ts
|
|
38732
|
-
import { writeFile as
|
|
39140
|
+
import { writeFile as writeFile3, stat } from "fs/promises";
|
|
38733
39141
|
import { dirname } from "path";
|
|
38734
39142
|
|
|
38735
39143
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
@@ -39189,7 +39597,7 @@ var ICClient = class {
|
|
|
39189
39597
|
});
|
|
39190
39598
|
if (!res.ok) throw new Error(`IC download ${res.status} for ${path}`);
|
|
39191
39599
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
39192
|
-
await
|
|
39600
|
+
await writeFile3(destinationPath, buf);
|
|
39193
39601
|
return {
|
|
39194
39602
|
path: destinationPath,
|
|
39195
39603
|
bytes: buf.byteLength,
|
|
@@ -40197,7 +40605,7 @@ function registerFeaturesTools(server, client) {
|
|
|
40197
40605
|
|
|
40198
40606
|
// src/index.ts
|
|
40199
40607
|
await loadDotenvSafely({
|
|
40200
|
-
path:
|
|
40608
|
+
path: join3(dirname2(fileURLToPath(import.meta.url)), "..", ".env"),
|
|
40201
40609
|
override: false
|
|
40202
40610
|
});
|
|
40203
40611
|
var AI_NOTICE = "[infinitecampus-mcp] Developed and maintained by AI (Claude). Use at your own discretion.";
|
|
@@ -40215,7 +40623,7 @@ try {
|
|
|
40215
40623
|
}
|
|
40216
40624
|
var COMMON = {
|
|
40217
40625
|
name: "infinitecampus",
|
|
40218
|
-
version: "2.4.
|
|
40626
|
+
version: "2.4.5"
|
|
40219
40627
|
// x-release-please-version
|
|
40220
40628
|
};
|
|
40221
40629
|
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.
|
|
60
|
+
version: '2.4.5', // x-release-please-version
|
|
61
61
|
};
|
|
62
62
|
if (account) {
|
|
63
63
|
const client = new ICClient(account, { refreshSession });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinitecampus-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
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>",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@chrischall/mcp-utils": "^0.14.0",
|
|
46
|
-
"@fetchproxy/bootstrap": "^
|
|
47
|
-
"@fetchproxy/server": "^
|
|
46
|
+
"@fetchproxy/bootstrap": "^2.0.0",
|
|
47
|
+
"@fetchproxy/server": "^2.0.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.
|
|
9
|
+
"version": "2.4.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "infinitecampus-mcp",
|
|
14
|
-
"version": "2.4.
|
|
14
|
+
"version": "2.4.5",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|