@chrischall/eventbrite-mcp 0.3.1 → 0.3.3
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 +1059 -340
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -7287,19 +7287,207 @@ var init_errors = __esm({
|
|
|
7287
7287
|
}
|
|
7288
7288
|
});
|
|
7289
7289
|
|
|
7290
|
+
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
7291
|
+
async function exportRaw(key, format) {
|
|
7292
|
+
const buf = await subtle.exportKey(format, key);
|
|
7293
|
+
return new Uint8Array(buf);
|
|
7294
|
+
}
|
|
7295
|
+
async function generateX25519() {
|
|
7296
|
+
const kp = await subtle.generateKey({ name: "X25519" }, true, [
|
|
7297
|
+
"deriveBits"
|
|
7298
|
+
]);
|
|
7299
|
+
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
7300
|
+
const pub = await exportRaw(kp.publicKey, "raw");
|
|
7301
|
+
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
7302
|
+
}
|
|
7303
|
+
async function generateEd25519() {
|
|
7304
|
+
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
7305
|
+
"sign",
|
|
7306
|
+
"verify"
|
|
7307
|
+
]);
|
|
7308
|
+
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
7309
|
+
const pub = await exportRaw(kp.publicKey, "raw");
|
|
7310
|
+
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
7311
|
+
}
|
|
7312
|
+
async function importX25519Priv(raw) {
|
|
7313
|
+
const prefix = new Uint8Array([
|
|
7314
|
+
48,
|
|
7315
|
+
46,
|
|
7316
|
+
2,
|
|
7317
|
+
1,
|
|
7318
|
+
0,
|
|
7319
|
+
48,
|
|
7320
|
+
5,
|
|
7321
|
+
6,
|
|
7322
|
+
3,
|
|
7323
|
+
43,
|
|
7324
|
+
101,
|
|
7325
|
+
110,
|
|
7326
|
+
4,
|
|
7327
|
+
34,
|
|
7328
|
+
4,
|
|
7329
|
+
32
|
|
7330
|
+
]);
|
|
7331
|
+
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
7332
|
+
pkcs8.set(prefix, 0);
|
|
7333
|
+
pkcs8.set(raw, prefix.length);
|
|
7334
|
+
return subtle.importKey("pkcs8", pkcs8, { name: "X25519" }, false, [
|
|
7335
|
+
"deriveBits"
|
|
7336
|
+
]);
|
|
7337
|
+
}
|
|
7338
|
+
async function importX25519Pub(raw) {
|
|
7339
|
+
return subtle.importKey("raw", raw, { name: "X25519" }, false, []);
|
|
7340
|
+
}
|
|
7341
|
+
async function importEd25519Priv(raw) {
|
|
7342
|
+
const prefix = new Uint8Array([
|
|
7343
|
+
48,
|
|
7344
|
+
46,
|
|
7345
|
+
2,
|
|
7346
|
+
1,
|
|
7347
|
+
0,
|
|
7348
|
+
48,
|
|
7349
|
+
5,
|
|
7350
|
+
6,
|
|
7351
|
+
3,
|
|
7352
|
+
43,
|
|
7353
|
+
101,
|
|
7354
|
+
112,
|
|
7355
|
+
4,
|
|
7356
|
+
34,
|
|
7357
|
+
4,
|
|
7358
|
+
32
|
|
7359
|
+
]);
|
|
7360
|
+
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
7361
|
+
pkcs8.set(prefix, 0);
|
|
7362
|
+
pkcs8.set(raw, prefix.length);
|
|
7363
|
+
return subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, false, ["sign"]);
|
|
7364
|
+
}
|
|
7365
|
+
async function importEd25519Pub(raw) {
|
|
7366
|
+
return subtle.importKey("raw", raw, { name: "Ed25519" }, false, ["verify"]);
|
|
7367
|
+
}
|
|
7368
|
+
async function ecdhX25519(privRaw, pubRaw) {
|
|
7369
|
+
const priv = await importX25519Priv(privRaw);
|
|
7370
|
+
const pub = await importX25519Pub(pubRaw);
|
|
7371
|
+
const bits = await subtle.deriveBits({ name: "X25519", public: pub }, priv, 256);
|
|
7372
|
+
return new Uint8Array(bits);
|
|
7373
|
+
}
|
|
7374
|
+
async function ed25519Sign(privRaw, msg) {
|
|
7375
|
+
const priv = await importEd25519Priv(privRaw);
|
|
7376
|
+
const sig = await subtle.sign({ name: "Ed25519" }, priv, msg);
|
|
7377
|
+
return new Uint8Array(sig);
|
|
7378
|
+
}
|
|
7379
|
+
async function ed25519Verify(pubRaw, msg, sig) {
|
|
7380
|
+
const pub = await importEd25519Pub(pubRaw);
|
|
7381
|
+
return subtle.verify({ name: "Ed25519" }, pub, sig, msg);
|
|
7382
|
+
}
|
|
7383
|
+
async function hkdfSha256(ikm, salt, info, lenBytes) {
|
|
7384
|
+
const baseKey = await subtle.importKey("raw", ikm, { name: "HKDF" }, false, [
|
|
7385
|
+
"deriveBits"
|
|
7386
|
+
]);
|
|
7387
|
+
const bits = await subtle.deriveBits({
|
|
7388
|
+
name: "HKDF",
|
|
7389
|
+
hash: "SHA-256",
|
|
7390
|
+
salt,
|
|
7391
|
+
info
|
|
7392
|
+
}, baseKey, lenBytes * 8);
|
|
7393
|
+
return new Uint8Array(bits);
|
|
7394
|
+
}
|
|
7395
|
+
async function aesGcmSeal(key, iv, plaintext, aad) {
|
|
7396
|
+
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
7397
|
+
"encrypt"
|
|
7398
|
+
]);
|
|
7399
|
+
const ct = await subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad }, k, plaintext);
|
|
7400
|
+
return new Uint8Array(ct);
|
|
7401
|
+
}
|
|
7402
|
+
async function aesGcmOpen(key, iv, ciphertext, aad) {
|
|
7403
|
+
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
7404
|
+
"decrypt"
|
|
7405
|
+
]);
|
|
7406
|
+
const pt = await subtle.decrypt({ name: "AES-GCM", iv, additionalData: aad }, k, ciphertext);
|
|
7407
|
+
return new Uint8Array(pt);
|
|
7408
|
+
}
|
|
7409
|
+
async function sha256(data) {
|
|
7410
|
+
const h = await subtle.digest("SHA-256", data);
|
|
7411
|
+
return new Uint8Array(h);
|
|
7412
|
+
}
|
|
7413
|
+
var subtle;
|
|
7414
|
+
var init_crypto = __esm({
|
|
7415
|
+
"node_modules/@fetchproxy/protocol/dist/crypto.js"() {
|
|
7416
|
+
subtle = globalThis.crypto.subtle;
|
|
7417
|
+
}
|
|
7418
|
+
});
|
|
7419
|
+
|
|
7420
|
+
// node_modules/@fetchproxy/protocol/dist/encoding.js
|
|
7421
|
+
function toB64(bytes) {
|
|
7422
|
+
let s = "";
|
|
7423
|
+
for (let i = 0; i < bytes.length; i++)
|
|
7424
|
+
s += String.fromCharCode(bytes[i]);
|
|
7425
|
+
return btoa(s);
|
|
7426
|
+
}
|
|
7427
|
+
function fromB64(s) {
|
|
7428
|
+
const bin = atob(s);
|
|
7429
|
+
const out = new Uint8Array(bin.length);
|
|
7430
|
+
for (let i = 0; i < bin.length; i++)
|
|
7431
|
+
out[i] = bin.charCodeAt(i);
|
|
7432
|
+
return out;
|
|
7433
|
+
}
|
|
7434
|
+
var init_encoding = __esm({
|
|
7435
|
+
"node_modules/@fetchproxy/protocol/dist/encoding.js"() {
|
|
7436
|
+
}
|
|
7437
|
+
});
|
|
7438
|
+
|
|
7290
7439
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
7291
|
-
function
|
|
7292
|
-
const
|
|
7440
|
+
function helloSignaturePayload(mcpId, sessionNonce, sessionPub, answersExtNonce) {
|
|
7441
|
+
const id = enc.encode(mcpId);
|
|
7442
|
+
const out = new Uint8Array(id.length + sessionNonce.length + sessionPub.length + answersExtNonce.length);
|
|
7443
|
+
out.set(id, 0);
|
|
7444
|
+
out.set(sessionNonce, id.length);
|
|
7445
|
+
out.set(sessionPub, id.length + sessionNonce.length);
|
|
7446
|
+
out.set(answersExtNonce, id.length + sessionNonce.length + sessionPub.length);
|
|
7447
|
+
return out;
|
|
7448
|
+
}
|
|
7449
|
+
function answersNoExtSession(answersExtNonce) {
|
|
7450
|
+
let bytes;
|
|
7451
|
+
try {
|
|
7452
|
+
bytes = fromB64(answersExtNonce);
|
|
7453
|
+
} catch {
|
|
7454
|
+
return false;
|
|
7455
|
+
}
|
|
7456
|
+
if (bytes.length !== ANSWERS_NO_EXT_SESSION.length)
|
|
7457
|
+
return false;
|
|
7458
|
+
for (const b of bytes)
|
|
7459
|
+
if (b !== 0)
|
|
7460
|
+
return false;
|
|
7461
|
+
return true;
|
|
7462
|
+
}
|
|
7463
|
+
function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub, mcpSessionPub) {
|
|
7464
|
+
const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length + mcpSessionPub.length);
|
|
7293
7465
|
out.set(mcpHelloNonce, 0);
|
|
7294
7466
|
out.set(extHelloNonce, mcpHelloNonce.length);
|
|
7295
7467
|
out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
|
|
7468
|
+
out.set(mcpSessionPub, mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
|
|
7296
7469
|
return out;
|
|
7297
7470
|
}
|
|
7298
|
-
|
|
7471
|
+
async function transcriptHash(mcpNonce, extNonce, mcpSessionPub, extSessionPub) {
|
|
7472
|
+
const out = new Uint8Array(mcpNonce.length + extNonce.length + mcpSessionPub.length + extSessionPub.length);
|
|
7473
|
+
out.set(mcpNonce, 0);
|
|
7474
|
+
out.set(extNonce, mcpNonce.length);
|
|
7475
|
+
out.set(mcpSessionPub, mcpNonce.length + extNonce.length);
|
|
7476
|
+
out.set(extSessionPub, mcpNonce.length + extNonce.length + mcpSessionPub.length);
|
|
7477
|
+
return sha256(out);
|
|
7478
|
+
}
|
|
7479
|
+
function frameAad(mcpId, seq, direction) {
|
|
7480
|
+
return enc.encode(`fetchproxy/4/frame\0${mcpId}\0${seq}\0${direction}`);
|
|
7481
|
+
}
|
|
7482
|
+
var PROTOCOL_VERSION, HKDF_SESSION_INFO, enc, ANSWERS_NO_EXT_SESSION, KNOWN_CAPABILITIES;
|
|
7299
7483
|
var init_frames = __esm({
|
|
7300
7484
|
"node_modules/@fetchproxy/protocol/dist/frames.js"() {
|
|
7301
|
-
|
|
7302
|
-
|
|
7485
|
+
init_crypto();
|
|
7486
|
+
init_encoding();
|
|
7487
|
+
PROTOCOL_VERSION = 4;
|
|
7488
|
+
HKDF_SESSION_INFO = "fetchproxy/4.0.0/session";
|
|
7489
|
+
enc = new TextEncoder();
|
|
7490
|
+
ANSWERS_NO_EXT_SESSION = new Uint8Array(32);
|
|
7303
7491
|
KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
7304
7492
|
"fetch",
|
|
7305
7493
|
"fetch_in_page",
|
|
@@ -7381,6 +7569,143 @@ var init_json_pointer = __esm({
|
|
|
7381
7569
|
}
|
|
7382
7570
|
});
|
|
7383
7571
|
|
|
7572
|
+
// node_modules/@fetchproxy/protocol/dist/public-suffix.js
|
|
7573
|
+
function isPublicSuffix(host) {
|
|
7574
|
+
const normalised = host.trim().toLowerCase().replace(/\.$/, "");
|
|
7575
|
+
if (normalised === "")
|
|
7576
|
+
return false;
|
|
7577
|
+
const labels = normalised.split(".");
|
|
7578
|
+
if (labels.some((l) => l === ""))
|
|
7579
|
+
return false;
|
|
7580
|
+
if (labels.length === 1)
|
|
7581
|
+
return true;
|
|
7582
|
+
if (LISTED_PUBLIC_SUFFIXES.has(normalised))
|
|
7583
|
+
return true;
|
|
7584
|
+
if (labels.length === 2) {
|
|
7585
|
+
const [secondLevel, tld] = labels;
|
|
7586
|
+
if (secondLevel && tld && /^[a-z]{2}$/.test(tld) && CCTLD_ADMIN_LABELS.has(secondLevel)) {
|
|
7587
|
+
return true;
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
return false;
|
|
7591
|
+
}
|
|
7592
|
+
var CCTLD_ADMIN_LABELS, LISTED_PUBLIC_SUFFIXES;
|
|
7593
|
+
var init_public_suffix = __esm({
|
|
7594
|
+
"node_modules/@fetchproxy/protocol/dist/public-suffix.js"() {
|
|
7595
|
+
CCTLD_ADMIN_LABELS = /* @__PURE__ */ new Set([
|
|
7596
|
+
"ac",
|
|
7597
|
+
"co",
|
|
7598
|
+
"com",
|
|
7599
|
+
"edu",
|
|
7600
|
+
"gob",
|
|
7601
|
+
"gov",
|
|
7602
|
+
"mil",
|
|
7603
|
+
"net",
|
|
7604
|
+
"nom",
|
|
7605
|
+
"org",
|
|
7606
|
+
"sch"
|
|
7607
|
+
]);
|
|
7608
|
+
LISTED_PUBLIC_SUFFIXES = /* @__PURE__ */ new Set([
|
|
7609
|
+
// --- ICANN, under ccTLDs, outside CCTLD_ADMIN_LABELS ---
|
|
7610
|
+
"ad.jp",
|
|
7611
|
+
"ed.jp",
|
|
7612
|
+
"gr.jp",
|
|
7613
|
+
"lg.jp",
|
|
7614
|
+
"ne.jp",
|
|
7615
|
+
"or.jp",
|
|
7616
|
+
"go.kr",
|
|
7617
|
+
"ne.kr",
|
|
7618
|
+
"or.kr",
|
|
7619
|
+
"pe.kr",
|
|
7620
|
+
"re.kr",
|
|
7621
|
+
"go.id",
|
|
7622
|
+
"my.id",
|
|
7623
|
+
"or.id",
|
|
7624
|
+
"web.id",
|
|
7625
|
+
"go.th",
|
|
7626
|
+
"in.th",
|
|
7627
|
+
"or.th",
|
|
7628
|
+
"me.uk",
|
|
7629
|
+
"ltd.uk",
|
|
7630
|
+
"plc.uk",
|
|
7631
|
+
"nhs.uk",
|
|
7632
|
+
"mod.uk",
|
|
7633
|
+
"police.uk",
|
|
7634
|
+
"parliament.uk",
|
|
7635
|
+
"asn.au",
|
|
7636
|
+
"id.au",
|
|
7637
|
+
"gen.in",
|
|
7638
|
+
"ind.in",
|
|
7639
|
+
"firm.in",
|
|
7640
|
+
"eu.org",
|
|
7641
|
+
// --- Vendor ("private") suffixes: one entry = every customer's site ---
|
|
7642
|
+
"github.io",
|
|
7643
|
+
"githubusercontent.com",
|
|
7644
|
+
"gitlab.io",
|
|
7645
|
+
"bitbucket.io",
|
|
7646
|
+
"pages.dev",
|
|
7647
|
+
"workers.dev",
|
|
7648
|
+
"r2.dev",
|
|
7649
|
+
"trycloudflare.com",
|
|
7650
|
+
"vercel.app",
|
|
7651
|
+
"now.sh",
|
|
7652
|
+
"netlify.app",
|
|
7653
|
+
"netlify.live",
|
|
7654
|
+
"herokuapp.com",
|
|
7655
|
+
"herokussl.com",
|
|
7656
|
+
"appspot.com",
|
|
7657
|
+
"web.app",
|
|
7658
|
+
"firebaseapp.com",
|
|
7659
|
+
"cloudfunctions.net",
|
|
7660
|
+
"blogspot.com",
|
|
7661
|
+
"azurewebsites.net",
|
|
7662
|
+
"azurestaticapps.net",
|
|
7663
|
+
"azureedge.net",
|
|
7664
|
+
"azurecontainer.io",
|
|
7665
|
+
"cloudapp.azure.com",
|
|
7666
|
+
"trafficmanager.net",
|
|
7667
|
+
"s3.amazonaws.com",
|
|
7668
|
+
"compute.amazonaws.com",
|
|
7669
|
+
"compute-1.amazonaws.com",
|
|
7670
|
+
"elasticbeanstalk.com",
|
|
7671
|
+
"cloudfront.net",
|
|
7672
|
+
"awsapprunner.com",
|
|
7673
|
+
"amplifyapp.com",
|
|
7674
|
+
"fly.dev",
|
|
7675
|
+
"onrender.com",
|
|
7676
|
+
"railway.app",
|
|
7677
|
+
"koyeb.app",
|
|
7678
|
+
"deno.dev",
|
|
7679
|
+
"replit.dev",
|
|
7680
|
+
"repl.co",
|
|
7681
|
+
"glitch.me",
|
|
7682
|
+
"surge.sh",
|
|
7683
|
+
"neocities.org",
|
|
7684
|
+
"pythonanywhere.com",
|
|
7685
|
+
"readthedocs.io",
|
|
7686
|
+
"gitbook.io",
|
|
7687
|
+
"myshopify.com",
|
|
7688
|
+
"wixsite.com",
|
|
7689
|
+
"editorx.io",
|
|
7690
|
+
"webflow.io",
|
|
7691
|
+
"bubbleapps.io",
|
|
7692
|
+
"notion.site",
|
|
7693
|
+
"framer.app",
|
|
7694
|
+
"framer.website",
|
|
7695
|
+
"ngrok.io",
|
|
7696
|
+
"ngrok.app",
|
|
7697
|
+
"ngrok-free.app",
|
|
7698
|
+
"loca.lt",
|
|
7699
|
+
"duckdns.org",
|
|
7700
|
+
"ddns.net",
|
|
7701
|
+
"no-ip.org",
|
|
7702
|
+
"dyndns.org",
|
|
7703
|
+
"hopto.org",
|
|
7704
|
+
"zapto.org"
|
|
7705
|
+
]);
|
|
7706
|
+
}
|
|
7707
|
+
});
|
|
7708
|
+
|
|
7384
7709
|
// node_modules/@fetchproxy/protocol/dist/validate.js
|
|
7385
7710
|
function assertObject(x, label) {
|
|
7386
7711
|
if (typeof x !== "object" || x === null || Array.isArray(x)) {
|
|
@@ -7405,6 +7730,18 @@ function assertBase64(x, label) {
|
|
|
7405
7730
|
if (!BASE64_RE.test(x))
|
|
7406
7731
|
throw new ProtocolError(`${label}: invalid base64`);
|
|
7407
7732
|
}
|
|
7733
|
+
function base64ByteLength(s) {
|
|
7734
|
+
if (s.length % 4 !== 0)
|
|
7735
|
+
return null;
|
|
7736
|
+
const pad = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0;
|
|
7737
|
+
return s.length / 4 * 3 - pad;
|
|
7738
|
+
}
|
|
7739
|
+
function assertBase64Bytes(x, label, bytes) {
|
|
7740
|
+
assertBase64(x, label);
|
|
7741
|
+
if (base64ByteLength(x) !== bytes) {
|
|
7742
|
+
throw new ProtocolError(`${label}: expected base64 of ${bytes} raw bytes`);
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7408
7745
|
function assertBoolean(x, label) {
|
|
7409
7746
|
if (typeof x !== "boolean") {
|
|
7410
7747
|
throw new ProtocolError(`${label}: must be boolean`);
|
|
@@ -7733,6 +8070,9 @@ function validateHello(raw) {
|
|
|
7733
8070
|
if (!HOSTNAME_RE.test(d)) {
|
|
7734
8071
|
throw new ProtocolError(`hello.domains: invalid hostname ${JSON.stringify(d)}`);
|
|
7735
8072
|
}
|
|
8073
|
+
if (isPublicSuffix(d)) {
|
|
8074
|
+
throw new ProtocolError(`hello.domains: ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
8075
|
+
}
|
|
7736
8076
|
}
|
|
7737
8077
|
if (raw.capabilities !== void 0) {
|
|
7738
8078
|
if (!Array.isArray(raw.capabilities)) {
|
|
@@ -7790,6 +8130,8 @@ function validateHello(raw) {
|
|
|
7790
8130
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
7791
8131
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
7792
8132
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
8133
|
+
assertBase64Bytes(raw.sessionPub, "hello.sessionPub", 32);
|
|
8134
|
+
assertBase64Bytes(raw.answersExtNonce, "hello.answersExtNonce", 32);
|
|
7793
8135
|
assertBase64(raw.sessionSig, "hello.sessionSig");
|
|
7794
8136
|
return raw;
|
|
7795
8137
|
}
|
|
@@ -7807,11 +8149,34 @@ function validateHello(raw) {
|
|
|
7807
8149
|
}
|
|
7808
8150
|
throw new ProtocolError(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
7809
8151
|
}
|
|
8152
|
+
function peekHelloVersion(raw) {
|
|
8153
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
8154
|
+
return null;
|
|
8155
|
+
const frame = raw;
|
|
8156
|
+
if (frame.type !== "hello")
|
|
8157
|
+
return null;
|
|
8158
|
+
const protocolVersion = frame.protocolVersion;
|
|
8159
|
+
if (typeof protocolVersion !== "number" || !Number.isInteger(protocolVersion))
|
|
8160
|
+
return null;
|
|
8161
|
+
const accepts = [];
|
|
8162
|
+
if (Array.isArray(frame.accepts)) {
|
|
8163
|
+
for (const a of frame.accepts) {
|
|
8164
|
+
if (typeof a === "string")
|
|
8165
|
+
accepts.push(a);
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
return {
|
|
8169
|
+
protocolVersion,
|
|
8170
|
+
mcpId: isValidMcpId(frame.mcpId) ? frame.mcpId : null,
|
|
8171
|
+
accepts
|
|
8172
|
+
};
|
|
8173
|
+
}
|
|
7810
8174
|
function validateReady(raw) {
|
|
7811
8175
|
assertString(raw.mcpId, "ready.mcpId");
|
|
7812
8176
|
if (!isValidMcpId(raw.mcpId))
|
|
7813
8177
|
throw new ProtocolError("ready.mcpId: invalid format");
|
|
7814
8178
|
assertBase64(raw.extensionSessionPub, "ready.extensionSessionPub");
|
|
8179
|
+
assertBase64Bytes(raw.mcpSessionPub, "ready.mcpSessionPub", 32);
|
|
7815
8180
|
assertBase64(raw.sessionSig, "ready.sessionSig");
|
|
7816
8181
|
return raw;
|
|
7817
8182
|
}
|
|
@@ -7830,7 +8195,7 @@ function validatePairPending(raw) {
|
|
|
7830
8195
|
throw new ProtocolError("pair-pending.mcpId: invalid format");
|
|
7831
8196
|
assertString(raw.pairCode, "pair-pending.pairCode");
|
|
7832
8197
|
if (!PAIR_CODE_RE.test(raw.pairCode)) {
|
|
7833
|
-
throw new ProtocolError(`pair-pending.pairCode: must match
|
|
8198
|
+
throw new ProtocolError(`pair-pending.pairCode: must match XXXX-XXXX, got ${String(raw.pairCode)}`);
|
|
7834
8199
|
}
|
|
7835
8200
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
7836
8201
|
}
|
|
@@ -8279,6 +8644,7 @@ var init_validate = __esm({
|
|
|
8279
8644
|
init_frames();
|
|
8280
8645
|
init_mcp_id();
|
|
8281
8646
|
init_json_pointer();
|
|
8647
|
+
init_public_suffix();
|
|
8282
8648
|
ProtocolError = class extends Error {
|
|
8283
8649
|
constructor(message) {
|
|
8284
8650
|
super(message);
|
|
@@ -8292,10 +8658,10 @@ var init_validate = __esm({
|
|
|
8292
8658
|
HEADER_NAME_RE = /^[A-Za-z0-9_\-]{1,128}$/;
|
|
8293
8659
|
HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
8294
8660
|
CAPTURE_PATH_RE = /^\/[A-Za-z0-9._~%\-/]*\*?$/;
|
|
8295
|
-
DOM_SELECTOR_RE = /^[
|
|
8661
|
+
DOM_SELECTOR_RE = /^[^\x00-\x1f\x7f]{1,512}$/;
|
|
8296
8662
|
DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
|
|
8297
8663
|
GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
|
|
8298
|
-
PAIR_CODE_RE = /^\d{
|
|
8664
|
+
PAIR_CODE_RE = /^\d{4}-\d{4}$/;
|
|
8299
8665
|
KNOWN_RESPONSE_OPS = /* @__PURE__ */ new Set([
|
|
8300
8666
|
...KNOWN_CAPABILITIES,
|
|
8301
8667
|
"graphql_query"
|
|
@@ -8303,180 +8669,33 @@ var init_validate = __esm({
|
|
|
8303
8669
|
}
|
|
8304
8670
|
});
|
|
8305
8671
|
|
|
8306
|
-
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
8307
|
-
async function exportRaw(key, format) {
|
|
8308
|
-
const buf = await subtle.exportKey(format, key);
|
|
8309
|
-
return new Uint8Array(buf);
|
|
8310
|
-
}
|
|
8311
|
-
async function generateX25519() {
|
|
8312
|
-
const kp = await subtle.generateKey({ name: "X25519" }, true, [
|
|
8313
|
-
"deriveBits"
|
|
8314
|
-
]);
|
|
8315
|
-
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
8316
|
-
const pub = await exportRaw(kp.publicKey, "raw");
|
|
8317
|
-
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
8318
|
-
}
|
|
8319
|
-
async function generateEd25519() {
|
|
8320
|
-
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
8321
|
-
"sign",
|
|
8322
|
-
"verify"
|
|
8323
|
-
]);
|
|
8324
|
-
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
8325
|
-
const pub = await exportRaw(kp.publicKey, "raw");
|
|
8326
|
-
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
8327
|
-
}
|
|
8328
|
-
async function importX25519Priv(raw) {
|
|
8329
|
-
const prefix = new Uint8Array([
|
|
8330
|
-
48,
|
|
8331
|
-
46,
|
|
8332
|
-
2,
|
|
8333
|
-
1,
|
|
8334
|
-
0,
|
|
8335
|
-
48,
|
|
8336
|
-
5,
|
|
8337
|
-
6,
|
|
8338
|
-
3,
|
|
8339
|
-
43,
|
|
8340
|
-
101,
|
|
8341
|
-
110,
|
|
8342
|
-
4,
|
|
8343
|
-
34,
|
|
8344
|
-
4,
|
|
8345
|
-
32
|
|
8346
|
-
]);
|
|
8347
|
-
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
8348
|
-
pkcs8.set(prefix, 0);
|
|
8349
|
-
pkcs8.set(raw, prefix.length);
|
|
8350
|
-
return subtle.importKey("pkcs8", pkcs8, { name: "X25519" }, false, [
|
|
8351
|
-
"deriveBits"
|
|
8352
|
-
]);
|
|
8353
|
-
}
|
|
8354
|
-
async function importX25519Pub(raw) {
|
|
8355
|
-
return subtle.importKey("raw", raw, { name: "X25519" }, false, []);
|
|
8356
|
-
}
|
|
8357
|
-
async function importEd25519Priv(raw) {
|
|
8358
|
-
const prefix = new Uint8Array([
|
|
8359
|
-
48,
|
|
8360
|
-
46,
|
|
8361
|
-
2,
|
|
8362
|
-
1,
|
|
8363
|
-
0,
|
|
8364
|
-
48,
|
|
8365
|
-
5,
|
|
8366
|
-
6,
|
|
8367
|
-
3,
|
|
8368
|
-
43,
|
|
8369
|
-
101,
|
|
8370
|
-
112,
|
|
8371
|
-
4,
|
|
8372
|
-
34,
|
|
8373
|
-
4,
|
|
8374
|
-
32
|
|
8375
|
-
]);
|
|
8376
|
-
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
8377
|
-
pkcs8.set(prefix, 0);
|
|
8378
|
-
pkcs8.set(raw, prefix.length);
|
|
8379
|
-
return subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, false, ["sign"]);
|
|
8380
|
-
}
|
|
8381
|
-
async function importEd25519Pub(raw) {
|
|
8382
|
-
return subtle.importKey("raw", raw, { name: "Ed25519" }, false, ["verify"]);
|
|
8383
|
-
}
|
|
8384
|
-
async function ecdhX25519(privRaw, pubRaw) {
|
|
8385
|
-
const priv = await importX25519Priv(privRaw);
|
|
8386
|
-
const pub = await importX25519Pub(pubRaw);
|
|
8387
|
-
const bits = await subtle.deriveBits({ name: "X25519", public: pub }, priv, 256);
|
|
8388
|
-
return new Uint8Array(bits);
|
|
8389
|
-
}
|
|
8390
|
-
async function ed25519Sign(privRaw, msg) {
|
|
8391
|
-
const priv = await importEd25519Priv(privRaw);
|
|
8392
|
-
const sig = await subtle.sign({ name: "Ed25519" }, priv, msg);
|
|
8393
|
-
return new Uint8Array(sig);
|
|
8394
|
-
}
|
|
8395
|
-
async function ed25519Verify(pubRaw, msg, sig) {
|
|
8396
|
-
const pub = await importEd25519Pub(pubRaw);
|
|
8397
|
-
return subtle.verify({ name: "Ed25519" }, pub, sig, msg);
|
|
8398
|
-
}
|
|
8399
|
-
async function hkdfSha256(ikm, salt, info, lenBytes) {
|
|
8400
|
-
const baseKey = await subtle.importKey("raw", ikm, { name: "HKDF" }, false, [
|
|
8401
|
-
"deriveBits"
|
|
8402
|
-
]);
|
|
8403
|
-
const bits = await subtle.deriveBits({
|
|
8404
|
-
name: "HKDF",
|
|
8405
|
-
hash: "SHA-256",
|
|
8406
|
-
salt,
|
|
8407
|
-
info
|
|
8408
|
-
}, baseKey, lenBytes * 8);
|
|
8409
|
-
return new Uint8Array(bits);
|
|
8410
|
-
}
|
|
8411
|
-
async function aesGcmSeal(key, iv, plaintext) {
|
|
8412
|
-
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
8413
|
-
"encrypt"
|
|
8414
|
-
]);
|
|
8415
|
-
const ct = await subtle.encrypt({ name: "AES-GCM", iv }, k, plaintext);
|
|
8416
|
-
return new Uint8Array(ct);
|
|
8417
|
-
}
|
|
8418
|
-
async function aesGcmOpen(key, iv, ciphertext) {
|
|
8419
|
-
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
8420
|
-
"decrypt"
|
|
8421
|
-
]);
|
|
8422
|
-
const pt = await subtle.decrypt({ name: "AES-GCM", iv }, k, ciphertext);
|
|
8423
|
-
return new Uint8Array(pt);
|
|
8424
|
-
}
|
|
8425
|
-
async function sha256(data) {
|
|
8426
|
-
const h = await subtle.digest("SHA-256", data);
|
|
8427
|
-
return new Uint8Array(h);
|
|
8428
|
-
}
|
|
8429
|
-
var subtle;
|
|
8430
|
-
var init_crypto = __esm({
|
|
8431
|
-
"node_modules/@fetchproxy/protocol/dist/crypto.js"() {
|
|
8432
|
-
subtle = globalThis.crypto.subtle;
|
|
8433
|
-
}
|
|
8434
|
-
});
|
|
8435
|
-
|
|
8436
|
-
// node_modules/@fetchproxy/protocol/dist/encoding.js
|
|
8437
|
-
function toB64(bytes) {
|
|
8438
|
-
let s = "";
|
|
8439
|
-
for (let i = 0; i < bytes.length; i++)
|
|
8440
|
-
s += String.fromCharCode(bytes[i]);
|
|
8441
|
-
return btoa(s);
|
|
8442
|
-
}
|
|
8443
|
-
function fromB64(s) {
|
|
8444
|
-
const bin = atob(s);
|
|
8445
|
-
const out = new Uint8Array(bin.length);
|
|
8446
|
-
for (let i = 0; i < bin.length; i++)
|
|
8447
|
-
out[i] = bin.charCodeAt(i);
|
|
8448
|
-
return out;
|
|
8449
|
-
}
|
|
8450
|
-
function concatBytes(a, b) {
|
|
8451
|
-
const out = new Uint8Array(a.length + b.length);
|
|
8452
|
-
out.set(a, 0);
|
|
8453
|
-
out.set(b, a.length);
|
|
8454
|
-
return out;
|
|
8455
|
-
}
|
|
8456
|
-
var init_encoding = __esm({
|
|
8457
|
-
"node_modules/@fetchproxy/protocol/dist/encoding.js"() {
|
|
8458
|
-
}
|
|
8459
|
-
});
|
|
8460
|
-
|
|
8461
8672
|
// node_modules/@fetchproxy/protocol/dist/pair-code.js
|
|
8462
|
-
async function
|
|
8463
|
-
const
|
|
8464
|
-
const
|
|
8465
|
-
const
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
const
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
}
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8673
|
+
async function pairTranscript(mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub) {
|
|
8674
|
+
const label = enc2.encode(PAIR_LABEL);
|
|
8675
|
+
const parts = [mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub];
|
|
8676
|
+
const out = new Uint8Array(label.length + 1 + parts.reduce((n, p) => n + p.length, 0));
|
|
8677
|
+
out.set(label, 0);
|
|
8678
|
+
out[label.length] = 0;
|
|
8679
|
+
let at = label.length + 1;
|
|
8680
|
+
for (const p of parts) {
|
|
8681
|
+
out.set(p, at);
|
|
8682
|
+
at += p.length;
|
|
8683
|
+
}
|
|
8684
|
+
return digits(await sha256(out));
|
|
8685
|
+
}
|
|
8686
|
+
function digits(hash2) {
|
|
8687
|
+
let n = 0n;
|
|
8688
|
+
for (let i = 0; i < 8; i++)
|
|
8689
|
+
n = n << 8n | BigInt(hash2[i] ?? 0);
|
|
8690
|
+
const s = (n % 100000000n).toString().padStart(8, "0");
|
|
8691
|
+
return `${s.slice(0, 4)}-${s.slice(4)}`;
|
|
8692
|
+
}
|
|
8693
|
+
var enc2, PAIR_LABEL;
|
|
8476
8694
|
var init_pair_code = __esm({
|
|
8477
8695
|
"node_modules/@fetchproxy/protocol/dist/pair-code.js"() {
|
|
8478
8696
|
init_crypto();
|
|
8479
|
-
|
|
8697
|
+
enc2 = new TextEncoder();
|
|
8698
|
+
PAIR_LABEL = "fetchproxy/4/pair";
|
|
8480
8699
|
}
|
|
8481
8700
|
});
|
|
8482
8701
|
|
|
@@ -8486,10 +8705,24 @@ function randomIv() {
|
|
|
8486
8705
|
globalThis.crypto.getRandomValues(iv);
|
|
8487
8706
|
return iv;
|
|
8488
8707
|
}
|
|
8489
|
-
|
|
8708
|
+
function base64Length(byteLength) {
|
|
8709
|
+
return Math.ceil(byteLength / 3) * 4;
|
|
8710
|
+
}
|
|
8711
|
+
function encodeInnerFrame(inner) {
|
|
8712
|
+
return enc3.encode(JSON.stringify(inner));
|
|
8713
|
+
}
|
|
8714
|
+
function plaintextOf(inner) {
|
|
8715
|
+
return inner instanceof Uint8Array ? inner : encodeInnerFrame(inner);
|
|
8716
|
+
}
|
|
8717
|
+
function sealedFrameWireBytes(mcpId, seq, inner) {
|
|
8718
|
+
const plaintext = plaintextOf(inner).length;
|
|
8719
|
+
const envelope = enc3.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
|
|
8720
|
+
return envelope + IV_B64_BYTES + base64Length(plaintext + AES_GCM_TAG_BYTES);
|
|
8721
|
+
}
|
|
8722
|
+
async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
|
|
8490
8723
|
const iv = randomIv();
|
|
8491
|
-
const pt =
|
|
8492
|
-
const ct = await aesGcmSeal(sessionKey, iv, pt);
|
|
8724
|
+
const pt = plaintextOf(inner);
|
|
8725
|
+
const ct = await aesGcmSeal(sessionKey, iv, pt, frameAad(mcpId, seq, direction));
|
|
8493
8726
|
return {
|
|
8494
8727
|
type: "frame",
|
|
8495
8728
|
mcpId,
|
|
@@ -8498,18 +8731,18 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
|
|
|
8498
8731
|
ciphertext: toB64(ct)
|
|
8499
8732
|
};
|
|
8500
8733
|
}
|
|
8501
|
-
async function openEncryptedFrame(sessionKey, frame) {
|
|
8502
|
-
const result = await openEncryptedFrameDetailed(sessionKey, frame);
|
|
8734
|
+
async function openEncryptedFrame(sessionKey, frame, direction) {
|
|
8735
|
+
const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
|
|
8503
8736
|
if (result.stage === "ok")
|
|
8504
8737
|
return result.inner;
|
|
8505
8738
|
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
8506
8739
|
}
|
|
8507
|
-
async function openEncryptedFrameDetailed(sessionKey, frame) {
|
|
8740
|
+
async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
8508
8741
|
let pt;
|
|
8509
8742
|
try {
|
|
8510
8743
|
const iv = fromB64(frame.iv);
|
|
8511
8744
|
const ct = fromB64(frame.ciphertext);
|
|
8512
|
-
pt = await aesGcmOpen(sessionKey, iv, ct);
|
|
8745
|
+
pt = await aesGcmOpen(sessionKey, iv, ct, frameAad(frame.mcpId, frame.seq, direction));
|
|
8513
8746
|
} catch (error61) {
|
|
8514
8747
|
return { stage: "decrypt-failed", error: error61 };
|
|
8515
8748
|
}
|
|
@@ -8527,14 +8760,18 @@ async function openEncryptedFrameDetailed(sessionKey, frame) {
|
|
|
8527
8760
|
return { stage: "validation-failed", error: error61, recoveredId };
|
|
8528
8761
|
}
|
|
8529
8762
|
}
|
|
8530
|
-
var
|
|
8763
|
+
var enc3, dec, AES_GCM_TAG_BYTES, IV_B64_BYTES, MAX_FRAME_BYTES;
|
|
8531
8764
|
var init_seal = __esm({
|
|
8532
8765
|
"node_modules/@fetchproxy/protocol/dist/seal.js"() {
|
|
8533
8766
|
init_crypto();
|
|
8534
8767
|
init_encoding();
|
|
8768
|
+
init_frames();
|
|
8535
8769
|
init_validate();
|
|
8536
|
-
|
|
8770
|
+
enc3 = new TextEncoder();
|
|
8537
8771
|
dec = new TextDecoder();
|
|
8772
|
+
AES_GCM_TAG_BYTES = 16;
|
|
8773
|
+
IV_B64_BYTES = 16;
|
|
8774
|
+
MAX_FRAME_BYTES = 42 * 1024 * 1024;
|
|
8538
8775
|
}
|
|
8539
8776
|
});
|
|
8540
8777
|
|
|
@@ -8548,6 +8785,7 @@ var init_dist = __esm({
|
|
|
8548
8785
|
init_pair_code();
|
|
8549
8786
|
init_seal();
|
|
8550
8787
|
init_encoding();
|
|
8788
|
+
init_public_suffix();
|
|
8551
8789
|
init_json_pointer();
|
|
8552
8790
|
}
|
|
8553
8791
|
});
|
|
@@ -10847,7 +11085,7 @@ var require_websocket = __commonJS({
|
|
|
10847
11085
|
var http = __require("http");
|
|
10848
11086
|
var net = __require("net");
|
|
10849
11087
|
var tls = __require("tls");
|
|
10850
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
11088
|
+
var { randomBytes: randomBytes2, createHash } = __require("crypto");
|
|
10851
11089
|
var { Duplex, Readable } = __require("stream");
|
|
10852
11090
|
var { URL: URL2 } = __require("url");
|
|
10853
11091
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -11385,7 +11623,7 @@ var require_websocket = __commonJS({
|
|
|
11385
11623
|
}
|
|
11386
11624
|
}
|
|
11387
11625
|
const defaultPort = isSecure ? 443 : 80;
|
|
11388
|
-
const key =
|
|
11626
|
+
const key = randomBytes2(16).toString("base64");
|
|
11389
11627
|
const request = isSecure ? https.request : http.request;
|
|
11390
11628
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
11391
11629
|
let perMessageDeflate;
|
|
@@ -12297,7 +12535,7 @@ var init_wrapper = __esm({
|
|
|
12297
12535
|
async function buildServerHello(opts) {
|
|
12298
12536
|
const sessionNonce = new Uint8Array(32);
|
|
12299
12537
|
globalThis.crypto.getRandomValues(sessionNonce);
|
|
12300
|
-
const sig = await ed25519Sign(opts.identity.ed25519Priv,
|
|
12538
|
+
const sig = await ed25519Sign(opts.identity.ed25519Priv, helloSignaturePayload(opts.mcpId, sessionNonce, opts.sessionPub, opts.answersExtNonce));
|
|
12301
12539
|
const hello = {
|
|
12302
12540
|
type: "hello",
|
|
12303
12541
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -12310,6 +12548,8 @@ async function buildServerHello(opts) {
|
|
|
12310
12548
|
identityX25519Pub: toB64(opts.identity.x25519Pub),
|
|
12311
12549
|
identityEd25519Pub: toB64(opts.identity.ed25519Pub),
|
|
12312
12550
|
sessionNonce: toB64(sessionNonce),
|
|
12551
|
+
sessionPub: toB64(opts.sessionPub),
|
|
12552
|
+
answersExtNonce: toB64(opts.answersExtNonce),
|
|
12313
12553
|
sessionSig: toB64(sig)
|
|
12314
12554
|
};
|
|
12315
12555
|
if (opts.accepts && opts.accepts.length > 0)
|
|
@@ -12371,32 +12611,109 @@ var init_build_server_hello = __esm({
|
|
|
12371
12611
|
}
|
|
12372
12612
|
});
|
|
12373
12613
|
|
|
12614
|
+
// node_modules/@fetchproxy/server/dist/frame-size.js
|
|
12615
|
+
function encodeOutboundInnerFrame(mcpId, inner) {
|
|
12616
|
+
const plaintext = encodeInnerFrame(inner);
|
|
12617
|
+
const wireBytes = sealedFrameWireBytes(mcpId, Number.MAX_SAFE_INTEGER, plaintext);
|
|
12618
|
+
if (wireBytes > MAX_FRAME_BYTES) {
|
|
12619
|
+
throw new Error(`fetchproxy: this request is too large for one bridge frame: ${wireBytes} bytes on the wire, cap ${MAX_FRAME_BYTES}. The request is refused; the bridge is not closed, so other calls and other MCPs on it are unaffected.`);
|
|
12620
|
+
}
|
|
12621
|
+
return plaintext;
|
|
12622
|
+
}
|
|
12623
|
+
var init_frame_size = __esm({
|
|
12624
|
+
"node_modules/@fetchproxy/server/dist/frame-size.js"() {
|
|
12625
|
+
init_dist();
|
|
12626
|
+
}
|
|
12627
|
+
});
|
|
12628
|
+
|
|
12374
12629
|
// node_modules/@fetchproxy/server/dist/session.js
|
|
12375
|
-
var SessionState;
|
|
12630
|
+
var MAX_INFLIGHT_INBOUND_SEQS, SessionState;
|
|
12376
12631
|
var init_session = __esm({
|
|
12377
12632
|
"node_modules/@fetchproxy/server/dist/session.js"() {
|
|
12633
|
+
MAX_INFLIGHT_INBOUND_SEQS = 1024;
|
|
12378
12634
|
SessionState = class {
|
|
12379
12635
|
sessionKey;
|
|
12380
12636
|
outboundSeq = 0;
|
|
12381
12637
|
lastInboundSeq = 0;
|
|
12638
|
+
inflightInbound = /* @__PURE__ */ new Set();
|
|
12639
|
+
saturationWarned = false;
|
|
12382
12640
|
constructor(sessionKey) {
|
|
12383
12641
|
this.sessionKey = sessionKey;
|
|
12384
12642
|
}
|
|
12643
|
+
/**
|
|
12644
|
+
* Whether the caller holding this claim verdict should log saturation now.
|
|
12645
|
+
* True only on the TRANSITION into it: a line per dropped frame turned a
|
|
12646
|
+
* flood — or a set that never drains — into a log flood amplifying the very
|
|
12647
|
+
* condition it reported (#376). An `'ok'` claim re-arms the latch, since it
|
|
12648
|
+
* is the one signal every caller sees that the set has drained below the
|
|
12649
|
+
* bound; a replay needs no capacity and leaves it alone. Latched per
|
|
12650
|
+
* session, so a renegotiated one starts armed. A set hovering at the bound
|
|
12651
|
+
* can still alternate ok/saturated, but that logs at the rate frames finish
|
|
12652
|
+
* opening, not the rate they arrive.
|
|
12653
|
+
*/
|
|
12654
|
+
saturationWarningDue(claim2) {
|
|
12655
|
+
if (claim2 === "ok")
|
|
12656
|
+
this.saturationWarned = false;
|
|
12657
|
+
if (claim2 !== "saturated" || this.saturationWarned)
|
|
12658
|
+
return false;
|
|
12659
|
+
this.saturationWarned = true;
|
|
12660
|
+
return true;
|
|
12661
|
+
}
|
|
12385
12662
|
nextOutboundSeq() {
|
|
12386
12663
|
this.outboundSeq += 1;
|
|
12387
12664
|
return this.outboundSeq;
|
|
12388
12665
|
}
|
|
12389
|
-
|
|
12666
|
+
/**
|
|
12667
|
+
* Take this seq out of circulation for the frame about to be opened, and
|
|
12668
|
+
* say whether it was available. SYNCHRONOUS and single-shot: call it before
|
|
12669
|
+
* the first `await` of the receive path, or a duplicate frame read in the
|
|
12670
|
+
* same pass will be judged against a counter neither frame has moved yet.
|
|
12671
|
+
*
|
|
12672
|
+
* Anything but `'ok'` means "not this frame's to process": `'replay'` for
|
|
12673
|
+
* a spent seq or a duplicate of one still in flight, `'saturated'` when the
|
|
12674
|
+
* in-flight bound is full. Replay is checked first, since refusing a stale
|
|
12675
|
+
* seq needs no capacity. Every `'ok'` MUST be answered by exactly one
|
|
12676
|
+
* {@link commitInboundSeq} or {@link releaseInboundSeq}.
|
|
12677
|
+
*/
|
|
12678
|
+
claimInboundSeq(seq) {
|
|
12390
12679
|
if (seq <= this.lastInboundSeq)
|
|
12391
|
-
return
|
|
12392
|
-
this.
|
|
12393
|
-
|
|
12680
|
+
return "replay";
|
|
12681
|
+
if (this.inflightInbound.has(seq))
|
|
12682
|
+
return "replay";
|
|
12683
|
+
if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
|
|
12684
|
+
return "saturated";
|
|
12685
|
+
this.inflightInbound.add(seq);
|
|
12686
|
+
return "ok";
|
|
12687
|
+
}
|
|
12688
|
+
/**
|
|
12689
|
+
* Give a claim back without spending the seq — for a frame that did not
|
|
12690
|
+
* authenticate, which is a frame that never happened. The counter does not
|
|
12691
|
+
* move, so the genuine frames behind it, carrying lower numbers, are still
|
|
12692
|
+
* accepted. Idempotent, so a caller may release in a `catch` after a commit
|
|
12693
|
+
* it is not sure ran.
|
|
12694
|
+
*/
|
|
12695
|
+
releaseInboundSeq(seq) {
|
|
12696
|
+
this.inflightInbound.delete(seq);
|
|
12697
|
+
}
|
|
12698
|
+
/**
|
|
12699
|
+
* Record a seq as spent. Call only for a frame that authenticated. Never
|
|
12700
|
+
* moves the counter backwards, so an out-of-order commit cannot reopen a
|
|
12701
|
+
* seq an earlier one already closed.
|
|
12702
|
+
*/
|
|
12703
|
+
commitInboundSeq(seq) {
|
|
12704
|
+
this.inflightInbound.delete(seq);
|
|
12705
|
+
if (seq > this.lastInboundSeq)
|
|
12706
|
+
this.lastInboundSeq = seq;
|
|
12394
12707
|
}
|
|
12395
12708
|
};
|
|
12396
12709
|
}
|
|
12397
12710
|
});
|
|
12398
12711
|
|
|
12399
12712
|
// node_modules/@fetchproxy/server/dist/session-ready.js
|
|
12713
|
+
function protocolVersionCloseReason(info) {
|
|
12714
|
+
const far = info.peer === "extension" ? "the extension" : "the other MCP";
|
|
12715
|
+
return `protocol version mismatch: this MCP speaks ${info.ourVersion}, ${far} speaks ${info.theirVersion}`;
|
|
12716
|
+
}
|
|
12400
12717
|
async function awaitSessionReady(ready, opts) {
|
|
12401
12718
|
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
12402
12719
|
if (ms <= 0)
|
|
@@ -12415,7 +12732,7 @@ async function awaitSessionReady(ready, opts) {
|
|
|
12415
12732
|
clearTimeout(timer);
|
|
12416
12733
|
}
|
|
12417
12734
|
}
|
|
12418
|
-
var SESSION_READY_TIMEOUT_MS, FetchproxySessionNotReadyError, FetchproxyHelloRejectedError;
|
|
12735
|
+
var SESSION_READY_TIMEOUT_MS, FetchproxySessionNotReadyError, FetchproxyHelloRejectedError, MIN_VERSION, FetchproxyProtocolVersionError;
|
|
12419
12736
|
var init_session_ready = __esm({
|
|
12420
12737
|
"node_modules/@fetchproxy/server/dist/session-ready.js"() {
|
|
12421
12738
|
SESSION_READY_TIMEOUT_MS = 3e4;
|
|
@@ -12447,11 +12764,29 @@ var init_session_ready = __esm({
|
|
|
12447
12764
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
12448
12765
|
}
|
|
12449
12766
|
};
|
|
12767
|
+
MIN_VERSION = "3.0.0";
|
|
12768
|
+
FetchproxyProtocolVersionError = class extends Error {
|
|
12769
|
+
/** The protocol version this process speaks. */
|
|
12770
|
+
ourVersion;
|
|
12771
|
+
/** The protocol version the far end announced in the hello we refused. */
|
|
12772
|
+
theirVersion;
|
|
12773
|
+
peer;
|
|
12774
|
+
constructor(info) {
|
|
12775
|
+
const far = info.peer === "extension" ? `the attached browser extension speaks ${info.theirVersion} \u2014 update Transporter (the fetchproxy extension) to ${MIN_VERSION} or later` : `the MCP holding the bridge port speaks ${info.theirVersion} \u2014 upgrade @fetchproxy/server to ${MIN_VERSION} or later in that MCP`;
|
|
12776
|
+
super(`protocol version mismatch: this MCP speaks fetchproxy protocol ${info.ourVersion}, ${far}`);
|
|
12777
|
+
this.name = "FetchproxyProtocolVersionError";
|
|
12778
|
+
this.ourVersion = info.ourVersion;
|
|
12779
|
+
this.theirVersion = info.theirVersion;
|
|
12780
|
+
this.peer = info.peer;
|
|
12781
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
12782
|
+
}
|
|
12783
|
+
};
|
|
12450
12784
|
}
|
|
12451
12785
|
});
|
|
12452
12786
|
|
|
12453
12787
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
12454
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
12788
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
12789
|
+
import { randomBytes } from "node:crypto";
|
|
12455
12790
|
import { isAbsolute, join as join2 } from "node:path";
|
|
12456
12791
|
import { homedir } from "node:os";
|
|
12457
12792
|
function envIdentityDir() {
|
|
@@ -12505,16 +12840,45 @@ function parseIdentity(text) {
|
|
|
12505
12840
|
createdAt: j.createdAt
|
|
12506
12841
|
};
|
|
12507
12842
|
}
|
|
12843
|
+
async function openIdentityDir(dir) {
|
|
12844
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
12845
|
+
try {
|
|
12846
|
+
await chmod(dir, 448);
|
|
12847
|
+
} catch (e) {
|
|
12848
|
+
const code = e.code;
|
|
12849
|
+
if (code !== "EROFS" && code !== "EPERM")
|
|
12850
|
+
throw e;
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
async function sweepStagingFiles(dir, serverName) {
|
|
12854
|
+
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
12855
|
+
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
12856
|
+
let names;
|
|
12857
|
+
try {
|
|
12858
|
+
names = await readdir(dir);
|
|
12859
|
+
} catch {
|
|
12860
|
+
return;
|
|
12861
|
+
}
|
|
12862
|
+
await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join2(dir, name)).catch(() => void 0)));
|
|
12863
|
+
}
|
|
12508
12864
|
async function writeIdentityFile(dir, serverName, id) {
|
|
12509
12865
|
const path = identityFilePath(dir, serverName);
|
|
12510
|
-
await
|
|
12511
|
-
await
|
|
12512
|
-
|
|
12866
|
+
await openIdentityDir(dir);
|
|
12867
|
+
await sweepStagingFiles(dir, serverName);
|
|
12868
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
12869
|
+
try {
|
|
12870
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
12871
|
+
await chmod(tmp, 384);
|
|
12872
|
+
await rename(tmp, path);
|
|
12873
|
+
} catch (e) {
|
|
12874
|
+
await rm(tmp, { force: true });
|
|
12875
|
+
throw e;
|
|
12876
|
+
}
|
|
12513
12877
|
return path;
|
|
12514
12878
|
}
|
|
12515
12879
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
12516
12880
|
const path = identityFilePath(dir, serverName);
|
|
12517
|
-
await
|
|
12881
|
+
await openIdentityDir(dir);
|
|
12518
12882
|
try {
|
|
12519
12883
|
return parseIdentity(await readFile(path, "utf8"));
|
|
12520
12884
|
} catch (e) {
|
|
@@ -12525,24 +12889,44 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
12525
12889
|
await writeIdentityFile(dir, serverName, id);
|
|
12526
12890
|
return id;
|
|
12527
12891
|
}
|
|
12528
|
-
var SAFE_PLAIN, SAFE_SCOPED;
|
|
12892
|
+
var SAFE_PLAIN, SAFE_SCOPED, STAGING_BYTES;
|
|
12529
12893
|
var init_identity = __esm({
|
|
12530
12894
|
"node_modules/@fetchproxy/server/dist/identity.js"() {
|
|
12531
12895
|
init_dist();
|
|
12532
12896
|
SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
12533
12897
|
SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
12898
|
+
STAGING_BYTES = 6;
|
|
12534
12899
|
}
|
|
12535
12900
|
});
|
|
12536
12901
|
|
|
12537
12902
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
12538
|
-
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
12539
|
-
import { join as join3 } from "node:path";
|
|
12903
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
12904
|
+
import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
12905
|
+
function envTrustDir(env = process.env) {
|
|
12906
|
+
const raw = env[TRUST_DIR_ENV];
|
|
12907
|
+
if (raw === void 0)
|
|
12908
|
+
return void 0;
|
|
12909
|
+
const dir = raw.trim();
|
|
12910
|
+
if (dir !== "" && isAbsolute2(dir))
|
|
12911
|
+
return dir;
|
|
12912
|
+
console.warn(`[fetchproxy] ignoring ${TRUST_DIR_ENV}=${JSON.stringify(raw)}: it is not an absolute path. The extension pin will be written beside the identity instead, which is not where you pointed it \u2014 if that directory is read-only, no pin is ever kept.`);
|
|
12913
|
+
return void 0;
|
|
12914
|
+
}
|
|
12915
|
+
function resolveTrustDir(explicit, identityDir) {
|
|
12916
|
+
if (explicit !== void 0)
|
|
12917
|
+
return explicit;
|
|
12918
|
+
return envTrustDir() ?? identityDir ?? defaultIdentityDir();
|
|
12919
|
+
}
|
|
12920
|
+
function defaultTrustDir() {
|
|
12921
|
+
return resolveTrustDir();
|
|
12922
|
+
}
|
|
12540
12923
|
function fileExtensionTrust(args) {
|
|
12924
|
+
const dir = resolveTrustDir(args.trustDir, args.dir);
|
|
12541
12925
|
return {
|
|
12542
12926
|
allowNew: args.allowNew,
|
|
12543
|
-
location: extensionTrustPath(args.serverName,
|
|
12544
|
-
read: () => readExtensionPin(args.serverName,
|
|
12545
|
-
write: (pin) => writeExtensionPin(args.serverName, pin,
|
|
12927
|
+
location: extensionTrustPath(args.serverName, dir),
|
|
12928
|
+
read: () => readExtensionPin(args.serverName, dir),
|
|
12929
|
+
write: (pin) => writeExtensionPin(args.serverName, pin, dir)
|
|
12546
12930
|
};
|
|
12547
12931
|
}
|
|
12548
12932
|
function allowNewExtensionIdentity(explicit, env = process.env) {
|
|
@@ -12569,14 +12953,14 @@ function decideExtensionTrust(args) {
|
|
|
12569
12953
|
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.`
|
|
12570
12954
|
};
|
|
12571
12955
|
}
|
|
12572
|
-
function extensionTrustPath(serverName, dir =
|
|
12956
|
+
function extensionTrustPath(serverName, dir = defaultTrustDir()) {
|
|
12573
12957
|
return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
12574
12958
|
}
|
|
12575
12959
|
function extensionTrustPathHint(serverName) {
|
|
12576
12960
|
try {
|
|
12577
12961
|
return extensionTrustPath(serverName);
|
|
12578
12962
|
} catch {
|
|
12579
|
-
return join3(
|
|
12963
|
+
return join3(defaultTrustDir(), "<server-name>.extension-trust.json");
|
|
12580
12964
|
}
|
|
12581
12965
|
}
|
|
12582
12966
|
function isPin(x) {
|
|
@@ -12585,7 +12969,7 @@ function isPin(x) {
|
|
|
12585
12969
|
const r = x;
|
|
12586
12970
|
return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
|
|
12587
12971
|
}
|
|
12588
|
-
async function readExtensionPin(serverName, dir =
|
|
12972
|
+
async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
12589
12973
|
const path = extensionTrustPath(serverName, dir);
|
|
12590
12974
|
let raw;
|
|
12591
12975
|
try {
|
|
@@ -12610,36 +12994,75 @@ async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
|
|
|
12610
12994
|
pinnedAt: parsed.pinnedAt
|
|
12611
12995
|
};
|
|
12612
12996
|
}
|
|
12613
|
-
async function writeExtensionPin(serverName, pin, dir =
|
|
12997
|
+
async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
12614
12998
|
const path = extensionTrustPath(serverName, dir);
|
|
12615
12999
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
12616
13000
|
const tmp = `${path}.tmp`;
|
|
12617
13001
|
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
12618
13002
|
await chmod2(tmp, 384);
|
|
12619
|
-
await
|
|
13003
|
+
await rename2(tmp, path);
|
|
12620
13004
|
}
|
|
12621
|
-
var TRUST_NEW_EXTENSION_ENV;
|
|
13005
|
+
var TRUST_DIR_ENV, TRUST_NEW_EXTENSION_ENV;
|
|
12622
13006
|
var init_extension_trust = __esm({
|
|
12623
13007
|
"node_modules/@fetchproxy/server/dist/extension-trust.js"() {
|
|
12624
13008
|
init_identity();
|
|
13009
|
+
TRUST_DIR_ENV = "FETCHPROXY_TRUST_DIR";
|
|
12625
13010
|
TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
|
|
12626
13011
|
}
|
|
12627
13012
|
});
|
|
12628
13013
|
|
|
12629
13014
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
13015
|
+
function envAllowsLocalOrigins(env = process.env) {
|
|
13016
|
+
const raw = env[ALLOW_LOCAL_ORIGINS_ENV];
|
|
13017
|
+
if (raw === void 0 || raw.trim() === "")
|
|
13018
|
+
return false;
|
|
13019
|
+
if (raw.trim() === "1")
|
|
13020
|
+
return true;
|
|
13021
|
+
console.warn(`[fetchproxy] ignoring ${ALLOW_LOCAL_ORIGINS_ENV}=${JSON.stringify(raw)}: the only value that turns it on is 1. Upgrades from a null or localhost origin stay refused.`);
|
|
13022
|
+
return false;
|
|
13023
|
+
}
|
|
13024
|
+
function originVerdict(origin, allowLocal) {
|
|
13025
|
+
if (origin === void 0)
|
|
13026
|
+
return { allow: true };
|
|
13027
|
+
if (HTTP_ORIGIN_RE.test(origin)) {
|
|
13028
|
+
if (PUBLIC_ORIGIN_RE.test(origin)) {
|
|
13029
|
+
return { allow: false, reason: "origin not allowed", escapable: false };
|
|
13030
|
+
}
|
|
13031
|
+
return allowLocal ? { allow: true } : { allow: false, reason: "localhost page origin not allowed", escapable: true };
|
|
13032
|
+
}
|
|
13033
|
+
if (origin.toLowerCase() === OPAQUE_ORIGIN) {
|
|
13034
|
+
return allowLocal ? { allow: true } : { allow: false, reason: "opaque (null) origin not allowed", escapable: true };
|
|
13035
|
+
}
|
|
13036
|
+
if (EXTENSION_ORIGIN_RE.test(origin))
|
|
13037
|
+
return { allow: true };
|
|
13038
|
+
return { allow: false, reason: "origin not allowed", escapable: false };
|
|
13039
|
+
}
|
|
12630
13040
|
async function startHost(opts) {
|
|
13041
|
+
const allowLocalOrigins = envAllowsLocalOrigins();
|
|
13042
|
+
if (allowLocalOrigins) {
|
|
13043
|
+
console.warn(`[fetchproxy] ${ALLOW_LOCAL_ORIGINS_ENV}=1: accepting WebSocket upgrades from null and localhost page origins. This is a development escape \u2014 any local page the browser has open can reach this concentrator while it is set.`);
|
|
13044
|
+
}
|
|
13045
|
+
const warnedOrigins = /* @__PURE__ */ new Set();
|
|
12631
13046
|
const wss = new import_websocket_server.default({
|
|
12632
13047
|
server: opts.httpServer,
|
|
13048
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES,
|
|
12633
13049
|
verifyClient: (info, cb) => {
|
|
12634
13050
|
const origin = info.req.headers.origin;
|
|
12635
|
-
|
|
12636
|
-
|
|
13051
|
+
const verdict = originVerdict(origin, allowLocalOrigins);
|
|
13052
|
+
if (verdict.allow) {
|
|
13053
|
+
cb(true);
|
|
12637
13054
|
return;
|
|
12638
13055
|
}
|
|
12639
|
-
|
|
13056
|
+
if (verdict.escapable && origin !== void 0 && warnedOrigins.size < 8) {
|
|
13057
|
+
if (!warnedOrigins.has(origin)) {
|
|
13058
|
+
warnedOrigins.add(origin);
|
|
13059
|
+
console.warn(`[fetchproxy] refused a WebSocket upgrade from ${JSON.stringify(origin)}: ${verdict.reason}. If that is you, developing against the bridge, set ${ALLOW_LOCAL_ORIGINS_ENV}=1 on this MCP \u2014 never on a deployed one.`);
|
|
13060
|
+
}
|
|
13061
|
+
}
|
|
13062
|
+
cb(false, 403, verdict.reason);
|
|
12640
13063
|
}
|
|
12641
13064
|
});
|
|
12642
|
-
const
|
|
13065
|
+
const ownHelloBase = {
|
|
12643
13066
|
// 2.6.0: tell the extension it can say WHY it refused a hello, instead of
|
|
12644
13067
|
// leaving us to time out and guess.
|
|
12645
13068
|
accepts: ["hello-rejected"],
|
|
@@ -12658,14 +13081,26 @@ async function startHost(opts) {
|
|
|
12658
13081
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
12659
13082
|
domSelectors: opts.ownDomSelectors,
|
|
12660
13083
|
graphqlOps: opts.ownGraphqlOps
|
|
12661
|
-
}
|
|
12662
|
-
const
|
|
13084
|
+
};
|
|
13085
|
+
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
12663
13086
|
let extensionWs = null;
|
|
12664
13087
|
const peers = /* @__PURE__ */ new Map();
|
|
12665
13088
|
const ownInnerListeners = [];
|
|
12666
13089
|
const disconnectListeners = [];
|
|
12667
13090
|
const pendingPairListeners = [];
|
|
12668
13091
|
let ownSession = null;
|
|
13092
|
+
let ownEphemeral = null;
|
|
13093
|
+
function dropOwnSessionAndEphemeral() {
|
|
13094
|
+
if (ownEphemeral)
|
|
13095
|
+
ownEphemeral.priv.fill(0);
|
|
13096
|
+
ownEphemeral = null;
|
|
13097
|
+
return null;
|
|
13098
|
+
}
|
|
13099
|
+
function installOwnEphemeral(next) {
|
|
13100
|
+
if (ownEphemeral)
|
|
13101
|
+
ownEphemeral.priv.fill(0);
|
|
13102
|
+
ownEphemeral = next;
|
|
13103
|
+
}
|
|
12669
13104
|
let ownPendingPairCode = null;
|
|
12670
13105
|
let resolveOwnSession;
|
|
12671
13106
|
let rejectOwnSession;
|
|
@@ -12679,21 +13114,74 @@ async function startHost(opts) {
|
|
|
12679
13114
|
});
|
|
12680
13115
|
}
|
|
12681
13116
|
resetSessionPromise();
|
|
13117
|
+
let ownSessionRefusal = null;
|
|
13118
|
+
function refuseVersionMismatch(ws, raw, identified) {
|
|
13119
|
+
const peek = peekHelloVersion(raw);
|
|
13120
|
+
if (!peek || peek.protocolVersion === PROTOCOL_VERSION)
|
|
13121
|
+
return false;
|
|
13122
|
+
const err = new FetchproxyProtocolVersionError({
|
|
13123
|
+
ourVersion: PROTOCOL_VERSION,
|
|
13124
|
+
theirVersion: peek.protocolVersion,
|
|
13125
|
+
peer: peek.mcpId === null ? "extension" : "mcp"
|
|
13126
|
+
});
|
|
13127
|
+
console.warn(`[fetchproxy] host: ${err.message}`);
|
|
13128
|
+
const nothingBetterAttached = extensionWs === null && extensionClaim === null && ownSession === null;
|
|
13129
|
+
if (err.peer === "extension" && identified !== "peer" && nothingBetterAttached) {
|
|
13130
|
+
ownSessionRefusal = err;
|
|
13131
|
+
rejectOwnSession(err);
|
|
13132
|
+
resetSessionPromise();
|
|
13133
|
+
rejectOwnSession(err);
|
|
13134
|
+
}
|
|
13135
|
+
try {
|
|
13136
|
+
ws.close(1002, protocolVersionCloseReason(err));
|
|
13137
|
+
} catch {
|
|
13138
|
+
}
|
|
13139
|
+
return true;
|
|
13140
|
+
}
|
|
12682
13141
|
let extensionHello = null;
|
|
12683
13142
|
let extensionClaim = null;
|
|
13143
|
+
const pairCodeFor = async (hello, mint) => {
|
|
13144
|
+
if (!mint)
|
|
13145
|
+
return null;
|
|
13146
|
+
try {
|
|
13147
|
+
return await pairTranscript(opts.ownIdentity.x25519Pub, fromB64(hello.identityX25519Pub), mint.nonce, fromB64(hello.sessionNonce), mint.pub);
|
|
13148
|
+
} catch (e) {
|
|
13149
|
+
console.error("[fetchproxy] could not derive the pair code:", e);
|
|
13150
|
+
return null;
|
|
13151
|
+
}
|
|
13152
|
+
};
|
|
12684
13153
|
wss.on("connection", (ws) => {
|
|
12685
13154
|
let identified = null;
|
|
12686
13155
|
let peerMcpId = null;
|
|
12687
13156
|
let closed = false;
|
|
12688
13157
|
let pinOnReady = false;
|
|
13158
|
+
const handshakeTimer = setTimeout(() => {
|
|
13159
|
+
if (identified || closed)
|
|
13160
|
+
return;
|
|
13161
|
+
try {
|
|
13162
|
+
ws.close(1008, "handshake timeout");
|
|
13163
|
+
} catch {
|
|
13164
|
+
}
|
|
13165
|
+
}, opts.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS);
|
|
13166
|
+
handshakeTimer.unref?.();
|
|
13167
|
+
ws.on("error", (e) => {
|
|
13168
|
+
console.warn(`[fetchproxy] host: socket error: ${String(e)}`);
|
|
13169
|
+
});
|
|
12689
13170
|
ws.on("message", async (data) => {
|
|
12690
13171
|
try {
|
|
13172
|
+
let raw;
|
|
13173
|
+
try {
|
|
13174
|
+
raw = JSON.parse(data.toString());
|
|
13175
|
+
} catch {
|
|
13176
|
+
ws.close(1002, "protocol error");
|
|
13177
|
+
return;
|
|
13178
|
+
}
|
|
12691
13179
|
let frame;
|
|
12692
13180
|
try {
|
|
12693
|
-
const raw = JSON.parse(data.toString());
|
|
12694
13181
|
frame = validateFrame(raw);
|
|
12695
13182
|
} catch {
|
|
12696
|
-
ws
|
|
13183
|
+
if (!refuseVersionMismatch(ws, raw, identified))
|
|
13184
|
+
ws.close(1002, "protocol error");
|
|
12697
13185
|
return;
|
|
12698
13186
|
}
|
|
12699
13187
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
@@ -12737,25 +13225,43 @@ async function startHost(opts) {
|
|
|
12737
13225
|
identified = "extension";
|
|
12738
13226
|
extensionWs = ws;
|
|
12739
13227
|
extensionHello = frame;
|
|
12740
|
-
if (
|
|
13228
|
+
if (ownSessionRefusal) {
|
|
13229
|
+
ownSessionRefusal = null;
|
|
13230
|
+
resetSessionPromise();
|
|
13231
|
+
}
|
|
13232
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
13233
|
+
const mintedHello = await buildServerHello({
|
|
13234
|
+
...ownHelloBase,
|
|
13235
|
+
sessionPub: mintedKeypair.publicKey,
|
|
13236
|
+
// The echo Rule B's gate reads, inside the signed payload: this
|
|
13237
|
+
// hello answers the extension session whose hello triggered it.
|
|
13238
|
+
answersExtNonce: fromB64(frame.sessionNonce)
|
|
13239
|
+
});
|
|
13240
|
+
if (extensionWs !== ws) {
|
|
13241
|
+
mintedKeypair.privateKey.fill(0);
|
|
13242
|
+
return;
|
|
13243
|
+
}
|
|
13244
|
+
const mint = {
|
|
13245
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
13246
|
+
pub: mintedKeypair.publicKey
|
|
13247
|
+
};
|
|
13248
|
+
installOwnEphemeral({ ...mint, priv: mintedKeypair.privateKey });
|
|
13249
|
+
for (const slot of peers.values())
|
|
13250
|
+
slot.ws.send(JSON.stringify(frame));
|
|
13251
|
+
ws.send(JSON.stringify(mintedHello));
|
|
13252
|
+
const helloPairCode = await pairCodeFor(frame, mint);
|
|
13253
|
+
if (opts.onPairCode && helloPairCode !== null) {
|
|
12741
13254
|
try {
|
|
12742
|
-
|
|
12743
|
-
opts.onPairCode(code);
|
|
13255
|
+
opts.onPairCode(helloPairCode);
|
|
12744
13256
|
} catch (e) {
|
|
12745
13257
|
console.error("[fetchproxy] onPairCode threw:", e);
|
|
12746
13258
|
}
|
|
12747
13259
|
}
|
|
12748
|
-
for (const slot of peers.values())
|
|
12749
|
-
slot.ws.send(JSON.stringify(frame));
|
|
12750
|
-
ws.send(JSON.stringify(ownHello));
|
|
12751
|
-
for (const slot of peers.values()) {
|
|
12752
|
-
ws.send(JSON.stringify(slot.helloFrame));
|
|
12753
|
-
}
|
|
12754
13260
|
return;
|
|
12755
13261
|
}
|
|
12756
13262
|
if (frame.type === "hello" && frame.role === "server") {
|
|
12757
13263
|
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
12758
|
-
const peerSigMsg =
|
|
13264
|
+
const peerSigMsg = helloSignaturePayload(frame.mcpId, fromB64(frame.sessionNonce), fromB64(frame.sessionPub), fromB64(frame.answersExtNonce));
|
|
12759
13265
|
const peerSig = fromB64(frame.sessionSig);
|
|
12760
13266
|
let peerSigOk = false;
|
|
12761
13267
|
try {
|
|
@@ -12780,10 +13286,12 @@ async function startHost(opts) {
|
|
|
12780
13286
|
identified = "peer";
|
|
12781
13287
|
peerMcpId = frame.mcpId;
|
|
12782
13288
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
12783
|
-
if (
|
|
12784
|
-
extensionWs.send(JSON.stringify(frame));
|
|
12785
|
-
if (extensionHello)
|
|
13289
|
+
if (extensionHello && answersNoExtSession(frame.answersExtNonce)) {
|
|
12786
13290
|
ws.send(JSON.stringify(extensionHello));
|
|
13291
|
+
}
|
|
13292
|
+
if (extensionWs && extensionHello && frame.answersExtNonce === extensionHello.sessionNonce) {
|
|
13293
|
+
extensionWs.send(JSON.stringify(frame));
|
|
13294
|
+
}
|
|
12787
13295
|
return;
|
|
12788
13296
|
}
|
|
12789
13297
|
if (frame.type === "ready") {
|
|
@@ -12793,9 +13301,14 @@ async function startHost(opts) {
|
|
|
12793
13301
|
ws.close(1002, "ready before extension hello");
|
|
12794
13302
|
return;
|
|
12795
13303
|
}
|
|
13304
|
+
const held = ownEphemeral;
|
|
13305
|
+
if (!held || frame.mcpSessionPub !== toB64(held.pub)) {
|
|
13306
|
+
console.warn("[fetchproxy] discarding a ready for a session ephemeral this host no longer holds (the extension reconnected while a hello was in flight)");
|
|
13307
|
+
return;
|
|
13308
|
+
}
|
|
12796
13309
|
const extEdPub = fromB64(extensionHello.identityEd25519Pub);
|
|
12797
13310
|
const extNonce = fromB64(extensionHello.sessionNonce);
|
|
12798
|
-
const msg = readySignaturePayload(
|
|
13311
|
+
const msg = readySignaturePayload(held.nonce, extNonce, fromB64(frame.extensionSessionPub), held.pub);
|
|
12799
13312
|
const sig = fromB64(frame.sessionSig);
|
|
12800
13313
|
let sigOk = false;
|
|
12801
13314
|
try {
|
|
@@ -12821,8 +13334,9 @@ async function startHost(opts) {
|
|
|
12821
13334
|
}
|
|
12822
13335
|
}
|
|
12823
13336
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
12824
|
-
const shared = await ecdhX25519(
|
|
12825
|
-
const
|
|
13337
|
+
const shared = await ecdhX25519(held.priv, extPub);
|
|
13338
|
+
const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
|
|
13339
|
+
const key = await hkdfSha256(shared, salt, enc4.encode(HKDF_SESSION_INFO), 32);
|
|
12826
13340
|
if (extensionWs !== ws)
|
|
12827
13341
|
return;
|
|
12828
13342
|
ownSession = new SessionState(key);
|
|
@@ -12838,11 +13352,23 @@ async function startHost(opts) {
|
|
|
12838
13352
|
if (frame.type === "frame") {
|
|
12839
13353
|
if (identified === "extension") {
|
|
12840
13354
|
if (frame.mcpId === opts.ownMcpId) {
|
|
12841
|
-
|
|
13355
|
+
const session = ownSession;
|
|
13356
|
+
if (!session)
|
|
12842
13357
|
return;
|
|
12843
|
-
|
|
13358
|
+
const claim2 = session.claimInboundSeq(frame.seq);
|
|
13359
|
+
if (session.saturationWarningDue(claim2)) {
|
|
13360
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
13361
|
+
}
|
|
13362
|
+
if (claim2 !== "ok")
|
|
12844
13363
|
return;
|
|
12845
|
-
|
|
13364
|
+
let inner;
|
|
13365
|
+
try {
|
|
13366
|
+
inner = await openEncryptedFrame(session.sessionKey, frame, "e2s");
|
|
13367
|
+
} catch (e) {
|
|
13368
|
+
session.releaseInboundSeq(frame.seq);
|
|
13369
|
+
throw e;
|
|
13370
|
+
}
|
|
13371
|
+
session.commitInboundSeq(frame.seq);
|
|
12846
13372
|
ownInnerListeners.forEach((cb) => cb(inner));
|
|
12847
13373
|
} else {
|
|
12848
13374
|
const slot = peers.get(frame.mcpId);
|
|
@@ -12866,8 +13392,16 @@ async function startHost(opts) {
|
|
|
12866
13392
|
}
|
|
12867
13393
|
if (frame.type === "pair-pending" && identified === "extension") {
|
|
12868
13394
|
if (frame.mcpId === opts.ownMcpId) {
|
|
12869
|
-
|
|
12870
|
-
|
|
13395
|
+
const hello = extensionHello;
|
|
13396
|
+
const mint = ownEphemeral;
|
|
13397
|
+
const derived = hello === null ? null : await pairCodeFor(hello, mint);
|
|
13398
|
+
if (derived === null || frame.pairCode !== derived) {
|
|
13399
|
+
console.error(`[fetchproxy] ${opts.ownServerName}: the extension's pair code (${frame.pairCode}) does not match the one this MCP derived from the pair transcript` + (derived === null ? " (none \u2014 this MCP could not derive its own)" : ` (${derived})`) + " \u2014 refusing to pair (possible MITM between this MCP and the extension)");
|
|
13400
|
+
ws.close(1008, "pair code mismatch");
|
|
13401
|
+
return;
|
|
13402
|
+
}
|
|
13403
|
+
ownPendingPairCode = derived;
|
|
13404
|
+
pendingPairListeners.forEach((cb) => cb(derived));
|
|
12871
13405
|
} else {
|
|
12872
13406
|
const slot = peers.get(frame.mcpId);
|
|
12873
13407
|
if (slot)
|
|
@@ -12884,6 +13418,7 @@ async function startHost(opts) {
|
|
|
12884
13418
|
});
|
|
12885
13419
|
ws.on("close", () => {
|
|
12886
13420
|
closed = true;
|
|
13421
|
+
clearTimeout(handshakeTimer);
|
|
12887
13422
|
if (extensionClaim === ws)
|
|
12888
13423
|
extensionClaim = null;
|
|
12889
13424
|
if (identified === "extension" && extensionWs === ws) {
|
|
@@ -12892,7 +13427,7 @@ async function startHost(opts) {
|
|
|
12892
13427
|
if (!ownSession) {
|
|
12893
13428
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
12894
13429
|
}
|
|
12895
|
-
ownSession =
|
|
13430
|
+
ownSession = dropOwnSessionAndEphemeral();
|
|
12896
13431
|
ownPendingPairCode = null;
|
|
12897
13432
|
resetSessionPromise();
|
|
12898
13433
|
disconnectListeners.forEach((cb) => cb());
|
|
@@ -12931,7 +13466,8 @@ async function startHost(opts) {
|
|
|
12931
13466
|
});
|
|
12932
13467
|
if (!extensionWs)
|
|
12933
13468
|
throw new Error("host: no extension connected");
|
|
12934
|
-
const
|
|
13469
|
+
const plaintext = encodeOutboundInnerFrame(opts.ownMcpId, inner);
|
|
13470
|
+
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), plaintext, "s2e");
|
|
12935
13471
|
extensionWs.send(JSON.stringify(sealed));
|
|
12936
13472
|
},
|
|
12937
13473
|
onOwnInner: (cb) => {
|
|
@@ -12948,28 +13484,53 @@ async function startHost(opts) {
|
|
|
12948
13484
|
sessionLinked: () => ownSession !== null
|
|
12949
13485
|
};
|
|
12950
13486
|
}
|
|
12951
|
-
var PUBLIC_ORIGIN_RE,
|
|
13487
|
+
var HTTP_ORIGIN_RE, PUBLIC_ORIGIN_RE, EXTENSION_ORIGIN_RE, OPAQUE_ORIGIN, ALLOW_LOCAL_ORIGINS_ENV, HANDSHAKE_TIMEOUT_MS, MAX_PAYLOAD_BYTES, enc4;
|
|
12952
13488
|
var init_host = __esm({
|
|
12953
13489
|
"node_modules/@fetchproxy/server/dist/host.js"() {
|
|
12954
13490
|
init_wrapper();
|
|
12955
13491
|
init_dist();
|
|
12956
13492
|
init_build_server_hello();
|
|
13493
|
+
init_frame_size();
|
|
12957
13494
|
init_session();
|
|
12958
13495
|
init_session_ready();
|
|
12959
13496
|
init_extension_trust();
|
|
12960
|
-
|
|
12961
|
-
|
|
13497
|
+
HTTP_ORIGIN_RE = /^https?:\/\//i;
|
|
13498
|
+
PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost|\[::1\])(:|$))/i;
|
|
13499
|
+
EXTENSION_ORIGIN_RE = /^(chrome-extension|moz-extension|safari-web-extension):\/\//i;
|
|
13500
|
+
OPAQUE_ORIGIN = "null";
|
|
13501
|
+
ALLOW_LOCAL_ORIGINS_ENV = "FETCHPROXY_ALLOW_LOCAL_ORIGINS";
|
|
13502
|
+
HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
13503
|
+
MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
|
|
13504
|
+
enc4 = new TextEncoder();
|
|
12962
13505
|
}
|
|
12963
13506
|
});
|
|
12964
13507
|
|
|
12965
13508
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
12966
13509
|
async function startPeer(opts) {
|
|
12967
|
-
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}
|
|
13510
|
+
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
13511
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
13512
|
+
});
|
|
12968
13513
|
await new Promise((resolve, reject) => {
|
|
12969
|
-
|
|
12970
|
-
|
|
13514
|
+
const onOpen = () => {
|
|
13515
|
+
cleanup();
|
|
13516
|
+
resolve();
|
|
13517
|
+
};
|
|
13518
|
+
const onError = (e) => {
|
|
13519
|
+
cleanup();
|
|
13520
|
+
reject(e);
|
|
13521
|
+
};
|
|
13522
|
+
const cleanup = () => {
|
|
13523
|
+
ws.off("open", onOpen);
|
|
13524
|
+
ws.off("error", onError);
|
|
13525
|
+
};
|
|
13526
|
+
ws.once("open", onOpen);
|
|
13527
|
+
ws.once("error", onError);
|
|
12971
13528
|
});
|
|
12972
|
-
|
|
13529
|
+
ws.on("error", (e) => {
|
|
13530
|
+
console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
|
|
13531
|
+
});
|
|
13532
|
+
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
13533
|
+
const helloBase = {
|
|
12973
13534
|
identity: opts.identity,
|
|
12974
13535
|
mcpId: opts.mcpId,
|
|
12975
13536
|
serverName: opts.serverName,
|
|
@@ -12989,15 +13550,39 @@ async function startPeer(opts) {
|
|
|
12989
13550
|
// `extensionConnected()` / `sessionLinked()` can go back to false.
|
|
12990
13551
|
// 2.6.0: `hello-rejected` for the same reason, from the extension.
|
|
12991
13552
|
accepts: ["extension-disconnected", "hello-rejected"]
|
|
13553
|
+
};
|
|
13554
|
+
const bootstrapKeypair = await generateSessionKeypair();
|
|
13555
|
+
let bootstrapPriv = bootstrapKeypair.privateKey;
|
|
13556
|
+
const hello = await buildServerHello({
|
|
13557
|
+
...helloBase,
|
|
13558
|
+
sessionPub: bootstrapKeypair.publicKey,
|
|
13559
|
+
// At dial this peer has been told of no extension session, and saying so
|
|
13560
|
+
// is what keeps the host from forwarding a hello whose ephemeral is not
|
|
13561
|
+
// one a session may be opened from.
|
|
13562
|
+
answersExtNonce: ANSWERS_NO_EXT_SESSION
|
|
12992
13563
|
});
|
|
12993
|
-
const sessionNonce = fromB64(hello.sessionNonce);
|
|
12994
13564
|
ws.send(JSON.stringify(hello));
|
|
12995
13565
|
const innerListeners = [];
|
|
12996
13566
|
const renegotiateListeners = [];
|
|
12997
13567
|
const pendingPairListeners = [];
|
|
12998
13568
|
const closeListeners = [];
|
|
12999
13569
|
let session = null;
|
|
13570
|
+
let sessionEphemeral = null;
|
|
13571
|
+
function dropSessionEphemeral() {
|
|
13572
|
+
if (sessionEphemeral)
|
|
13573
|
+
sessionEphemeral.priv.fill(0);
|
|
13574
|
+
sessionEphemeral = null;
|
|
13575
|
+
}
|
|
13576
|
+
function installSessionEphemeral(next) {
|
|
13577
|
+
dropSessionEphemeral();
|
|
13578
|
+
if (bootstrapPriv) {
|
|
13579
|
+
bootstrapPriv.fill(0);
|
|
13580
|
+
bootstrapPriv = null;
|
|
13581
|
+
}
|
|
13582
|
+
sessionEphemeral = next;
|
|
13583
|
+
}
|
|
13000
13584
|
let pendingPairCode = null;
|
|
13585
|
+
let warnedUnverifiablePairCode = false;
|
|
13001
13586
|
let resolveFirstReady;
|
|
13002
13587
|
let rejectFirstReady;
|
|
13003
13588
|
const sessionPromise = new Promise((resolve, reject) => {
|
|
@@ -13006,58 +13591,60 @@ async function startPeer(opts) {
|
|
|
13006
13591
|
});
|
|
13007
13592
|
let extensionHello = null;
|
|
13008
13593
|
let extensionGone = false;
|
|
13009
|
-
let warnedUnverifiable = false;
|
|
13010
13594
|
let cachedPin = void 0;
|
|
13011
|
-
const
|
|
13012
|
-
if (!
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
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.`);
|
|
13020
|
-
}
|
|
13021
|
-
return true;
|
|
13595
|
+
const pairCodeFor = async (hello2, mint) => {
|
|
13596
|
+
if (!mint)
|
|
13597
|
+
return null;
|
|
13598
|
+
try {
|
|
13599
|
+
return await pairTranscript(opts.identity.x25519Pub, fromB64(hello2.identityX25519Pub), mint.nonce, fromB64(hello2.sessionNonce), mint.pub);
|
|
13600
|
+
} catch (e) {
|
|
13601
|
+
console.error("[fetchproxy] could not derive the pair code:", e);
|
|
13602
|
+
return null;
|
|
13022
13603
|
}
|
|
13023
|
-
|
|
13604
|
+
};
|
|
13605
|
+
const authenticateExtension = async (sessionSig, extensionSessionPub, held, hello2) => {
|
|
13606
|
+
if (!hello2) {
|
|
13607
|
+
console.error(`[fetchproxy] ${opts.serverName}: no extension hello has been relayed to this peer, so there is nothing to derive a v4 session from \u2014 refusing. Upgrade the MCP holding the bridge port to 3.0.0 or later.`);
|
|
13608
|
+
return null;
|
|
13609
|
+
}
|
|
13610
|
+
const payload = readySignaturePayload(held.nonce, fromB64(hello2.sessionNonce), fromB64(extensionSessionPub), held.pub);
|
|
13024
13611
|
let sigOk = false;
|
|
13025
13612
|
try {
|
|
13026
|
-
sigOk = await ed25519Verify(fromB64(
|
|
13613
|
+
sigOk = await ed25519Verify(fromB64(hello2.identityEd25519Pub), payload, fromB64(sessionSig));
|
|
13027
13614
|
} catch {
|
|
13028
13615
|
sigOk = false;
|
|
13029
13616
|
}
|
|
13030
13617
|
if (!sigOk) {
|
|
13031
13618
|
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
13032
|
-
return
|
|
13619
|
+
return null;
|
|
13033
13620
|
}
|
|
13034
13621
|
if (cachedPin === void 0) {
|
|
13035
13622
|
try {
|
|
13036
13623
|
cachedPin = await opts.extensionTrust.read();
|
|
13037
13624
|
} catch (e) {
|
|
13038
13625
|
console.error(`[fetchproxy] ${String(e)}`);
|
|
13039
|
-
return
|
|
13626
|
+
return null;
|
|
13040
13627
|
}
|
|
13041
13628
|
}
|
|
13042
13629
|
const pin = cachedPin;
|
|
13043
13630
|
const outcome = decideExtensionTrust({
|
|
13044
13631
|
pin,
|
|
13045
|
-
hello:
|
|
13632
|
+
hello: hello2,
|
|
13046
13633
|
allowNew: opts.extensionTrust.allowNew,
|
|
13047
13634
|
serverName: opts.serverName,
|
|
13048
13635
|
location: opts.extensionTrust.location
|
|
13049
13636
|
});
|
|
13050
13637
|
if (outcome.decision === "refused") {
|
|
13051
13638
|
console.warn(outcome.message);
|
|
13052
|
-
return
|
|
13639
|
+
return null;
|
|
13053
13640
|
}
|
|
13054
13641
|
if (outcome.decision === "replace")
|
|
13055
13642
|
console.warn(outcome.message);
|
|
13056
13643
|
if (outcome.decision !== "pinned") {
|
|
13057
13644
|
try {
|
|
13058
13645
|
const written = {
|
|
13059
|
-
identityX25519Pub:
|
|
13060
|
-
identityEd25519Pub:
|
|
13646
|
+
identityX25519Pub: hello2.identityX25519Pub,
|
|
13647
|
+
identityEd25519Pub: hello2.identityEd25519Pub,
|
|
13061
13648
|
pinnedAt: Date.now()
|
|
13062
13649
|
};
|
|
13063
13650
|
await opts.extensionTrust.write(written);
|
|
@@ -13066,32 +13653,62 @@ async function startPeer(opts) {
|
|
|
13066
13653
|
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
13067
13654
|
}
|
|
13068
13655
|
}
|
|
13069
|
-
return
|
|
13656
|
+
return hello2;
|
|
13070
13657
|
};
|
|
13071
13658
|
const onMessage = async (data) => {
|
|
13659
|
+
let raw;
|
|
13072
13660
|
try {
|
|
13073
|
-
|
|
13661
|
+
raw = JSON.parse(data.toString());
|
|
13074
13662
|
const frame = validateFrame(raw);
|
|
13075
13663
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
13076
13664
|
extensionHello = frame;
|
|
13665
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
13666
|
+
const mintedHello = await buildServerHello({
|
|
13667
|
+
...helloBase,
|
|
13668
|
+
sessionPub: mintedKeypair.publicKey,
|
|
13669
|
+
answersExtNonce: fromB64(frame.sessionNonce)
|
|
13670
|
+
});
|
|
13671
|
+
if (extensionHello !== frame) {
|
|
13672
|
+
mintedKeypair.privateKey.fill(0);
|
|
13673
|
+
return;
|
|
13674
|
+
}
|
|
13675
|
+
installSessionEphemeral({
|
|
13676
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
13677
|
+
pub: mintedKeypair.publicKey,
|
|
13678
|
+
priv: mintedKeypair.privateKey
|
|
13679
|
+
});
|
|
13680
|
+
ws.send(JSON.stringify(mintedHello));
|
|
13077
13681
|
return;
|
|
13078
13682
|
}
|
|
13079
13683
|
if (frame.type === "extension-disconnected") {
|
|
13080
13684
|
extensionHello = null;
|
|
13081
13685
|
extensionGone = true;
|
|
13686
|
+
dropSessionEphemeral();
|
|
13082
13687
|
pendingPairCode = null;
|
|
13083
13688
|
return;
|
|
13084
13689
|
}
|
|
13085
13690
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
13086
|
-
const
|
|
13087
|
-
|
|
13691
|
+
const held = sessionEphemeral;
|
|
13692
|
+
const heldExtHello = extensionHello;
|
|
13693
|
+
if (!held || frame.mcpSessionPub !== toB64(held.pub)) {
|
|
13694
|
+
console.warn(`[fetchproxy] ${opts.serverName}: discarding a ready for a session ephemeral this peer no longer holds (the extension reconnected while a hello was in flight)`);
|
|
13695
|
+
return;
|
|
13696
|
+
}
|
|
13697
|
+
const authenticated = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub, held, heldExtHello);
|
|
13698
|
+
if (!authenticated) {
|
|
13088
13699
|
ws.close(1008, "extension identity refused");
|
|
13089
13700
|
rejectFirstReady(new Error("peer: extension identity refused"));
|
|
13090
13701
|
return;
|
|
13091
13702
|
}
|
|
13092
13703
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
13093
|
-
const
|
|
13094
|
-
const
|
|
13704
|
+
const extNonce = fromB64(authenticated.sessionNonce);
|
|
13705
|
+
const shared = await ecdhX25519(held.priv, extPub);
|
|
13706
|
+
const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
|
|
13707
|
+
const sessionKey = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
|
|
13708
|
+
if (sessionEphemeral !== held) {
|
|
13709
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropping a session derived against an ephemeral that was superseded while it was being derived`);
|
|
13710
|
+
return;
|
|
13711
|
+
}
|
|
13095
13712
|
const isRenegotiation = session !== null;
|
|
13096
13713
|
session = new SessionState(sessionKey);
|
|
13097
13714
|
extensionGone = false;
|
|
@@ -13107,16 +13724,46 @@ async function startPeer(opts) {
|
|
|
13107
13724
|
rejectFirstReady(new FetchproxyHelloRejectedError({ mcpId: frame.mcpId, reason: frame.reason }));
|
|
13108
13725
|
}
|
|
13109
13726
|
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
13110
|
-
|
|
13111
|
-
|
|
13727
|
+
const hello2 = extensionHello;
|
|
13728
|
+
const mint = sessionEphemeral;
|
|
13729
|
+
const derived = hello2 === null ? null : await pairCodeFor(hello2, mint);
|
|
13730
|
+
if (derived === null) {
|
|
13731
|
+
if (!warnedUnverifiablePairCode) {
|
|
13732
|
+
warnedUnverifiablePairCode = true;
|
|
13733
|
+
console.warn(`[fetchproxy] ${opts.serverName}: a pair code arrived that this peer cannot derive for itself (no extension identity has been relayed to it, or no session ephemeral has been minted in answer to one), so it is not being shown. If the MCP holding the bridge port is older than 1.12.0, upgrading it closes this.`);
|
|
13734
|
+
}
|
|
13735
|
+
return;
|
|
13736
|
+
}
|
|
13737
|
+
if (frame.pairCode !== derived) {
|
|
13738
|
+
console.error(`[fetchproxy] ${opts.serverName}: the extension's pair code (${frame.pairCode}) does not match the one this peer derived from the pair transcript (${derived}) \u2014 refusing to pair (possible MITM between this MCP and the extension)`);
|
|
13739
|
+
ws.close(1008, "pair code mismatch");
|
|
13740
|
+
return;
|
|
13741
|
+
}
|
|
13742
|
+
pendingPairCode = derived;
|
|
13743
|
+
pendingPairListeners.forEach((cb) => cb(derived));
|
|
13112
13744
|
return;
|
|
13113
13745
|
}
|
|
13114
13746
|
if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
|
|
13115
|
-
|
|
13747
|
+
const inboundSession = session;
|
|
13748
|
+
if (!inboundSession)
|
|
13116
13749
|
return;
|
|
13117
|
-
|
|
13750
|
+
const claim2 = inboundSession.claimInboundSeq(frame.seq);
|
|
13751
|
+
if (inboundSession.saturationWarningDue(claim2)) {
|
|
13752
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
13753
|
+
}
|
|
13754
|
+
if (claim2 !== "ok")
|
|
13118
13755
|
return;
|
|
13119
|
-
|
|
13756
|
+
let result;
|
|
13757
|
+
try {
|
|
13758
|
+
result = await openEncryptedFrameDetailed(inboundSession.sessionKey, frame, "e2s");
|
|
13759
|
+
} catch (e) {
|
|
13760
|
+
inboundSession.releaseInboundSeq(frame.seq);
|
|
13761
|
+
throw e;
|
|
13762
|
+
}
|
|
13763
|
+
if (result.stage !== "decrypt-failed")
|
|
13764
|
+
inboundSession.commitInboundSeq(frame.seq);
|
|
13765
|
+
else
|
|
13766
|
+
inboundSession.releaseInboundSeq(frame.seq);
|
|
13120
13767
|
if (result.stage === "ok") {
|
|
13121
13768
|
innerListeners.forEach((cb) => cb(result.inner));
|
|
13122
13769
|
} else if (result.stage === "decrypt-failed") {
|
|
@@ -13133,12 +13780,25 @@ async function startPeer(opts) {
|
|
|
13133
13780
|
}
|
|
13134
13781
|
}
|
|
13135
13782
|
} catch (e) {
|
|
13136
|
-
|
|
13783
|
+
const peek = peekHelloVersion(raw);
|
|
13784
|
+
const mismatch = peek && peek.protocolVersion !== PROTOCOL_VERSION ? new FetchproxyProtocolVersionError({
|
|
13785
|
+
ourVersion: PROTOCOL_VERSION,
|
|
13786
|
+
theirVersion: peek.protocolVersion,
|
|
13787
|
+
peer: peek.mcpId === null ? "extension" : "mcp"
|
|
13788
|
+
}) : null;
|
|
13789
|
+
if (mismatch)
|
|
13790
|
+
console.warn(`[fetchproxy] ${opts.serverName}: ${mismatch.message}`);
|
|
13791
|
+
rejectFirstReady(mismatch ?? (e instanceof Error ? e : new Error(String(e))));
|
|
13137
13792
|
}
|
|
13138
13793
|
};
|
|
13139
13794
|
ws.on("message", onMessage);
|
|
13140
13795
|
ws.once("close", () => {
|
|
13141
13796
|
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
13797
|
+
dropSessionEphemeral();
|
|
13798
|
+
if (bootstrapPriv) {
|
|
13799
|
+
bootstrapPriv.fill(0);
|
|
13800
|
+
bootstrapPriv = null;
|
|
13801
|
+
}
|
|
13142
13802
|
closeListeners.forEach((cb) => cb());
|
|
13143
13803
|
});
|
|
13144
13804
|
sessionPromise.catch(() => {
|
|
@@ -13152,7 +13812,8 @@ async function startPeer(opts) {
|
|
|
13152
13812
|
pendingPairCode: () => pendingPairCode
|
|
13153
13813
|
});
|
|
13154
13814
|
const s = session;
|
|
13155
|
-
const
|
|
13815
|
+
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
13816
|
+
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
13156
13817
|
ws.send(JSON.stringify(sealed));
|
|
13157
13818
|
},
|
|
13158
13819
|
onInner: (cb) => {
|
|
@@ -13174,16 +13835,18 @@ async function startPeer(opts) {
|
|
|
13174
13835
|
};
|
|
13175
13836
|
return handle;
|
|
13176
13837
|
}
|
|
13177
|
-
var
|
|
13838
|
+
var enc5;
|
|
13178
13839
|
var init_peer = __esm({
|
|
13179
13840
|
"node_modules/@fetchproxy/server/dist/peer.js"() {
|
|
13180
13841
|
init_wrapper();
|
|
13181
13842
|
init_dist();
|
|
13182
13843
|
init_build_server_hello();
|
|
13844
|
+
init_frame_size();
|
|
13845
|
+
init_host();
|
|
13183
13846
|
init_session();
|
|
13184
13847
|
init_session_ready();
|
|
13185
13848
|
init_extension_trust();
|
|
13186
|
-
|
|
13849
|
+
enc5 = new TextEncoder();
|
|
13187
13850
|
}
|
|
13188
13851
|
});
|
|
13189
13852
|
|
|
@@ -13225,6 +13888,8 @@ function classifyBridgeError(err) {
|
|
|
13225
13888
|
return "session_not_ready";
|
|
13226
13889
|
if (err instanceof FetchproxyTimeoutError)
|
|
13227
13890
|
return "timeout";
|
|
13891
|
+
if (err instanceof FetchproxyWaitedError)
|
|
13892
|
+
return "timeout";
|
|
13228
13893
|
if (err instanceof FetchproxyBridgeDownError)
|
|
13229
13894
|
return "bridge_down";
|
|
13230
13895
|
if (err instanceof FetchproxyHttpError)
|
|
@@ -13262,7 +13927,19 @@ function envWsHost() {
|
|
|
13262
13927
|
return void 0;
|
|
13263
13928
|
return host;
|
|
13264
13929
|
}
|
|
13265
|
-
function
|
|
13930
|
+
function waitedHint(op) {
|
|
13931
|
+
const tail = " This is not a version problem and does not need an update.";
|
|
13932
|
+
if (op === "capture" || op === "capture_redirect") {
|
|
13933
|
+
return "nothing matched before the window closed. Is a tab open on the declared domain and signed in? The wait resolves on the NEXT matching request the PAGE makes, so an idle tab times out however long you wait \u2014 interact with the page, or raise the window." + tail;
|
|
13934
|
+
}
|
|
13935
|
+
if (op === "download") {
|
|
13936
|
+
return "the download did not finish before the window closed. Is a tab open on the declared domain and signed in, and is the file still transferring?" + tail;
|
|
13937
|
+
}
|
|
13938
|
+
return "the extension waited and nothing arrived. Is a tab open on the declared domain and signed in?" + tail;
|
|
13939
|
+
}
|
|
13940
|
+
function protocolErrorFrom(error61, op) {
|
|
13941
|
+
if (error61 === "timeout")
|
|
13942
|
+
return new FetchproxyWaitedError(error61, op);
|
|
13266
13943
|
if (SCOPE_REJECTION.test(error61))
|
|
13267
13944
|
return new FetchproxyScopeError(error61);
|
|
13268
13945
|
if (TAB_OPENING_REJECTION.test(error61))
|
|
@@ -13271,6 +13948,11 @@ function protocolErrorFrom(error61) {
|
|
|
13271
13948
|
return new FetchproxyNoTabError(error61);
|
|
13272
13949
|
return new FetchproxyProtocolError(error61);
|
|
13273
13950
|
}
|
|
13951
|
+
function verbDeadlineMs(transportMs, requestedMs, graceMs = VERB_DEADLINE_GRACE_MS) {
|
|
13952
|
+
if (requestedMs === void 0)
|
|
13953
|
+
return transportMs;
|
|
13954
|
+
return Math.max(requestedMs + graceMs, transportMs);
|
|
13955
|
+
}
|
|
13274
13956
|
function normalizeCookiePath(path) {
|
|
13275
13957
|
if (path === void 0 || path === "")
|
|
13276
13958
|
return void 0;
|
|
@@ -13306,7 +13988,7 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
13306
13988
|
const declared = domains.map((d) => JSON.stringify(d)).join(", ");
|
|
13307
13989
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
13308
13990
|
}
|
|
13309
|
-
var FetchproxyProtocolError, FetchproxyHttpError, FetchproxyBridgeDownError, FetchproxyHintedError, FetchproxyScopeError, FetchproxyNoTabError, FetchproxyTabOpeningError, SCOPE_REJECTION, NO_TAB_REJECTION, TAB_OPENING_REJECTION, FetchproxyTimeoutError, SUBDOMAIN_LABEL_RE, DEFAULT_JSON_OK_STATUSES, FetchproxyServer;
|
|
13991
|
+
var FetchproxyProtocolError, FetchproxyHttpError, FetchproxyBridgeDownError, FetchproxyHintedError, FetchproxyScopeError, FetchproxyNoTabError, FetchproxyTabOpeningError, FetchproxyWaitedError, SCOPE_REJECTION, NO_TAB_REJECTION, TAB_OPENING_REJECTION, VERB_DEADLINE_GRACE_MS, FetchproxyTimeoutError, SUBDOMAIN_LABEL_RE, DEFAULT_JSON_OK_STATUSES, FetchproxyServer;
|
|
13310
13992
|
var init_ws_server = __esm({
|
|
13311
13993
|
"node_modules/@fetchproxy/server/dist/ws-server.js"() {
|
|
13312
13994
|
init_dist();
|
|
@@ -13388,9 +14070,16 @@ var init_ws_server = __esm({
|
|
|
13388
14070
|
this.name = "FetchproxyTabOpeningError";
|
|
13389
14071
|
}
|
|
13390
14072
|
};
|
|
14073
|
+
FetchproxyWaitedError = class extends FetchproxyHintedError {
|
|
14074
|
+
constructor(originalError, op) {
|
|
14075
|
+
super(originalError, waitedHint(op));
|
|
14076
|
+
this.name = "FetchproxyWaitedError";
|
|
14077
|
+
}
|
|
14078
|
+
};
|
|
13391
14079
|
SCOPE_REJECTION = /not in declared/;
|
|
13392
14080
|
NO_TAB_REJECTION = /no tab matching (?!.*(content script loaded|one is still opening))/;
|
|
13393
14081
|
TAB_OPENING_REJECTION = /no tab matching .*one is still opening/;
|
|
14082
|
+
VERB_DEADLINE_GRACE_MS = 15e3;
|
|
13394
14083
|
FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
13395
14084
|
url;
|
|
13396
14085
|
timeoutMs;
|
|
@@ -13409,16 +14098,20 @@ var init_ws_server = __esm({
|
|
|
13409
14098
|
*/
|
|
13410
14099
|
retryAttempted;
|
|
13411
14100
|
/**
|
|
13412
|
-
* 2.4.2+ (#277): what the CALL asked for,
|
|
13413
|
-
*
|
|
13414
|
-
*
|
|
13415
|
-
*
|
|
13416
|
-
*
|
|
14101
|
+
* 2.4.2+ (#277): what the CALL asked for, whenever it asked for anything.
|
|
14102
|
+
*
|
|
14103
|
+
* Until #237 this was recorded only when the call asked for MORE than the
|
|
14104
|
+
* deadline, because asking for more is what the cap silently ignored. The
|
|
14105
|
+
* cap is gone — the call's own `timeoutMs` now GOVERNS its deadline
|
|
14106
|
+
* (`verbDeadlineMs`) — so the interesting case inverted: a timeout here
|
|
14107
|
+
* means the bridge did not answer within the window the CALLER chose, and
|
|
14108
|
+
* the message says which number that was and where it came from.
|
|
13417
14109
|
*/
|
|
13418
14110
|
requestedTimeoutMs;
|
|
13419
14111
|
constructor(args) {
|
|
13420
|
-
const
|
|
13421
|
-
|
|
14112
|
+
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
14113
|
+
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
14114
|
+
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms` + (callerChose ? ` \u2014 the ${args.requestedTimeoutMs}ms this call asked for, plus ${graceMs}ms for the reply to reach this server. The extension runs its own timer on the value you passed, so a window that is genuinely too short reports what did not happen rather than this.` : ""));
|
|
13422
14115
|
this.name = "FetchproxyTimeoutError";
|
|
13423
14116
|
this.url = args.url;
|
|
13424
14117
|
this.timeoutMs = args.timeoutMs;
|
|
@@ -13516,6 +14209,11 @@ var init_ws_server = __esm({
|
|
|
13516
14209
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
13517
14210
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
13518
14211
|
}
|
|
14212
|
+
for (const d of opts.domains) {
|
|
14213
|
+
if (typeof d === "string" && isPublicSuffix(d)) {
|
|
14214
|
+
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
14215
|
+
}
|
|
14216
|
+
}
|
|
13519
14217
|
let capabilities;
|
|
13520
14218
|
if (opts.capabilities === void 0) {
|
|
13521
14219
|
capabilities = ["fetch"];
|
|
@@ -13581,6 +14279,7 @@ var init_ws_server = __esm({
|
|
|
13581
14279
|
// back-door is `0` (explicit opt-out) if a caller genuinely wants
|
|
13582
14280
|
// the legacy hang-forever / fail-once-on-SW-eviction behavior.
|
|
13583
14281
|
fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
|
|
14282
|
+
verbDeadlineGraceMs: opts.verbDeadlineGraceMs ?? VERB_DEADLINE_GRACE_MS,
|
|
13584
14283
|
bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
|
|
13585
14284
|
// 0.10.0+ (#72): keep-alive defaults to 25s — round-3 #71 cohort
|
|
13586
14285
|
// wave showed every Pattern A consumer was opting into this same
|
|
@@ -13599,6 +14298,7 @@ var init_ws_server = __esm({
|
|
|
13599
14298
|
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
13600
14299
|
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
13601
14300
|
identityDir: opts.identityDir,
|
|
14301
|
+
trustDir: opts.trustDir,
|
|
13602
14302
|
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
13603
14303
|
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
13604
14304
|
onPairCode: opts.onPairCode
|
|
@@ -13875,7 +14575,9 @@ var init_ws_server = __esm({
|
|
|
13875
14575
|
}
|
|
13876
14576
|
/**
|
|
13877
14577
|
* #208: this MCP's pin on the extension's identity, stored beside its own
|
|
13878
|
-
* identity key and so following `identityDir` wherever the caller put it
|
|
14578
|
+
* identity key and so following `identityDir` wherever the caller put it —
|
|
14579
|
+
* unless `trustDir` (or `FETCHPROXY_TRUST_DIR`) says otherwise, which is
|
|
14580
|
+
* what a host that provisions the identity read-only has to say.
|
|
13879
14581
|
*
|
|
13880
14582
|
* `allowNewExtensionIdentity` falls back to an environment variable when the
|
|
13881
14583
|
* caller expressed no opinion, because the thirteen MCPs that construct this
|
|
@@ -13887,6 +14589,7 @@ var init_ws_server = __esm({
|
|
|
13887
14589
|
return fileExtensionTrust({
|
|
13888
14590
|
serverName: this.opts.serverName,
|
|
13889
14591
|
dir: this.opts.identityDir,
|
|
14592
|
+
trustDir: this.opts.trustDir,
|
|
13890
14593
|
allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
|
|
13891
14594
|
});
|
|
13892
14595
|
}
|
|
@@ -14049,9 +14752,11 @@ var init_ws_server = __esm({
|
|
|
14049
14752
|
return pending;
|
|
14050
14753
|
}
|
|
14051
14754
|
async _withVerbTimeout(pending, pendingMap, id, url2, requestedTimeoutMs) {
|
|
14052
|
-
const
|
|
14053
|
-
if (
|
|
14755
|
+
const transportMs = this.opts.fetchTimeoutMs;
|
|
14756
|
+
if (transportMs === void 0 || transportMs <= 0)
|
|
14054
14757
|
return pending;
|
|
14758
|
+
const graceMs = this.opts.verbDeadlineGraceMs;
|
|
14759
|
+
const timeoutMs = verbDeadlineMs(transportMs, requestedTimeoutMs, graceMs);
|
|
14055
14760
|
let timer;
|
|
14056
14761
|
const start = Date.now();
|
|
14057
14762
|
try {
|
|
@@ -14067,6 +14772,7 @@ var init_ws_server = __esm({
|
|
|
14067
14772
|
port: this.opts.port,
|
|
14068
14773
|
elapsedMs: Date.now() - start,
|
|
14069
14774
|
retryAttempted: false,
|
|
14775
|
+
graceMs,
|
|
14070
14776
|
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
14071
14777
|
}));
|
|
14072
14778
|
}, timeoutMs);
|
|
@@ -14127,9 +14833,9 @@ var init_ws_server = __esm({
|
|
|
14127
14833
|
if (opts.subdomain !== void 0)
|
|
14128
14834
|
assertSubdomainLabel(opts.subdomain);
|
|
14129
14835
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
14130
|
-
const
|
|
14836
|
+
const isAbsolute3 = path.startsWith("http://") || path.startsWith("https://");
|
|
14131
14837
|
let host;
|
|
14132
|
-
if (
|
|
14838
|
+
if (isAbsolute3) {
|
|
14133
14839
|
try {
|
|
14134
14840
|
host = new URL(path).host;
|
|
14135
14841
|
} catch {
|
|
@@ -14138,7 +14844,7 @@ var init_ws_server = __esm({
|
|
|
14138
14844
|
} else {
|
|
14139
14845
|
host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
14140
14846
|
}
|
|
14141
|
-
const url2 =
|
|
14847
|
+
const url2 = isAbsolute3 ? path : `https://${host}${path}`;
|
|
14142
14848
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
14143
14849
|
let tabUrl = `https://${host}/`;
|
|
14144
14850
|
if (opts.viaTab !== void 0) {
|
|
@@ -14627,8 +15333,9 @@ var init_ws_server = __esm({
|
|
|
14627
15333
|
this.pendingCapture,
|
|
14628
15334
|
id,
|
|
14629
15335
|
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
14630
|
-
// #
|
|
14631
|
-
//
|
|
15336
|
+
// #237: this GOVERNS the deadline (window + reply grace, floored at the
|
|
15337
|
+
// transport bound). Under #277 it did not, and was passed only so the
|
|
15338
|
+
// timeout could name the number that actually fired.
|
|
14632
15339
|
opts.timeoutMs
|
|
14633
15340
|
);
|
|
14634
15341
|
}
|
|
@@ -14720,8 +15427,9 @@ var init_ws_server = __esm({
|
|
|
14720
15427
|
this.pendingRedirect,
|
|
14721
15428
|
id,
|
|
14722
15429
|
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
14723
|
-
// #
|
|
14724
|
-
//
|
|
15430
|
+
// #237: this GOVERNS the deadline (window + reply grace, floored at the
|
|
15431
|
+
// transport bound). Under #277 it did not, and was passed only so the
|
|
15432
|
+
// timeout could name the number that actually fired.
|
|
14725
15433
|
opts.timeoutMs
|
|
14726
15434
|
);
|
|
14727
15435
|
}
|
|
@@ -14810,10 +15518,11 @@ var init_ws_server = __esm({
|
|
|
14810
15518
|
this.pendingDownload,
|
|
14811
15519
|
id,
|
|
14812
15520
|
opts.url,
|
|
14813
|
-
//
|
|
14814
|
-
//
|
|
14815
|
-
//
|
|
14816
|
-
// counted off the two call sites in view
|
|
15521
|
+
// `download` takes a per-call timeoutMs like the two capture verbs, so
|
|
15522
|
+
// it is governed by it like them (#237). It used to hit the identical
|
|
15523
|
+
// CAP and need the identical explanation; missing it then was the point
|
|
15524
|
+
// of #279 — "both verbs" was counted off the two call sites in view
|
|
15525
|
+
// rather than off the type. Three is still the number.
|
|
14817
15526
|
opts.timeoutMs
|
|
14818
15527
|
);
|
|
14819
15528
|
}
|
|
@@ -15042,7 +15751,7 @@ var init_ws_server = __esm({
|
|
|
15042
15751
|
captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
|
|
15043
15752
|
}
|
|
15044
15753
|
} else {
|
|
15045
|
-
captureCb.reject(protocolErrorFrom(inner.error));
|
|
15754
|
+
captureCb.reject(protocolErrorFrom(inner.error, "capture"));
|
|
15046
15755
|
}
|
|
15047
15756
|
return;
|
|
15048
15757
|
}
|
|
@@ -15056,7 +15765,7 @@ var init_ws_server = __esm({
|
|
|
15056
15765
|
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
15057
15766
|
}
|
|
15058
15767
|
} else {
|
|
15059
|
-
redirectCb.reject(protocolErrorFrom(inner.error));
|
|
15768
|
+
redirectCb.reject(protocolErrorFrom(inner.error, "capture_redirect"));
|
|
15060
15769
|
}
|
|
15061
15770
|
return;
|
|
15062
15771
|
}
|
|
@@ -15084,7 +15793,7 @@ var init_ws_server = __esm({
|
|
|
15084
15793
|
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
15085
15794
|
}
|
|
15086
15795
|
} else {
|
|
15087
|
-
downloadCb.reject(protocolErrorFrom(inner.error));
|
|
15796
|
+
downloadCb.reject(protocolErrorFrom(inner.error, "download"));
|
|
15088
15797
|
}
|
|
15089
15798
|
return;
|
|
15090
15799
|
}
|
|
@@ -15187,11 +15896,21 @@ var init_ws_server = __esm({
|
|
|
15187
15896
|
/**
|
|
15188
15897
|
* 2.5.0: the extension link as {@link BridgeSessionState}, read off
|
|
15189
15898
|
* whichever handle is live. A pending pair code outranks "extension not
|
|
15190
|
-
* seen" because
|
|
15191
|
-
*
|
|
15192
|
-
* pair-pending frames. Where the extension is KNOWN to be gone (a host's
|
|
15899
|
+
* seen" because a code could only have come from a `pair-pending` frame
|
|
15900
|
+
* the extension sent. Where the extension is KNOWN to be gone (a host's
|
|
15193
15901
|
* socket closed, a peer told by a 2.5.0+ host) the handle has already
|
|
15194
15902
|
* dropped the code, so the precedence never lies there (#283).
|
|
15903
|
+
*
|
|
15904
|
+
* M1 (bridge review 2026-09-10): a code is now only ever present when the
|
|
15905
|
+
* handle DERIVED it from both identity pubs, and both handles set and clear
|
|
15906
|
+
* that derivation in lockstep with the extension hello — so the precedence
|
|
15907
|
+
* has nothing left to decide: a pair code implies the hello, which implies
|
|
15908
|
+
* `extensionConnected`. A peer behind a pre-1.12.0 host is relayed no
|
|
15909
|
+
* extension hello, cannot derive, and therefore reports
|
|
15910
|
+
* `extension_disconnected` with a null code where it used to report the
|
|
15911
|
+
* wire's number as `pair_pending` — the honest state for a peer that has
|
|
15912
|
+
* never been told an extension is there. That is the trade: a number nobody
|
|
15913
|
+
* can check is worse than no number.
|
|
15195
15914
|
*/
|
|
15196
15915
|
sessionSnapshot() {
|
|
15197
15916
|
const handle = this.hostHandle ?? this.peerHandle;
|
|
@@ -22806,12 +23525,12 @@ var $ZodE164 = /* @__PURE__ */ $constructor("$ZodE164", (inst, def) => {
|
|
|
22806
23525
|
$ZodStringFormat.init(inst, def);
|
|
22807
23526
|
});
|
|
22808
23527
|
var CC_SANITIZE = /[- ]/g;
|
|
22809
|
-
function isLuhnAlgo(
|
|
22810
|
-
let length =
|
|
23528
|
+
function isLuhnAlgo(digits2) {
|
|
23529
|
+
let length = digits2.length;
|
|
22811
23530
|
let bit = 1;
|
|
22812
23531
|
let sum = 0;
|
|
22813
23532
|
while (length) {
|
|
22814
|
-
const value = +
|
|
23533
|
+
const value = +digits2[--length];
|
|
22815
23534
|
bit ^= 1;
|
|
22816
23535
|
sum += bit ? [0, 2, 4, 6, 8, 1, 3, 5, 7, 9][value] : value;
|
|
22817
23536
|
}
|
|
@@ -37543,8 +38262,8 @@ function hex2(_params) {
|
|
|
37543
38262
|
return _stringFormat(ZodCustomStringFormat, "hex", regexes_exports.hex, _params);
|
|
37544
38263
|
}
|
|
37545
38264
|
function hash(alg, params) {
|
|
37546
|
-
const
|
|
37547
|
-
const format = `${alg}_${
|
|
38265
|
+
const enc7 = params?.enc ?? "hex";
|
|
38266
|
+
const format = `${alg}_${enc7}`;
|
|
37548
38267
|
const regex = regexes_exports[format];
|
|
37549
38268
|
if (!regex)
|
|
37550
38269
|
throw new Error(`Unrecognized hash format: ${format}`);
|
|
@@ -45018,7 +45737,7 @@ var pageSchema = {
|
|
|
45018
45737
|
init_errors();
|
|
45019
45738
|
|
|
45020
45739
|
// src/version.ts
|
|
45021
|
-
var VERSION = "0.3.
|
|
45740
|
+
var VERSION = "0.3.3";
|
|
45022
45741
|
|
|
45023
45742
|
// src/client.ts
|
|
45024
45743
|
import { dirname, join } from "path";
|
|
@@ -45577,7 +46296,7 @@ var schemaEventStatus = external_exports.enum(["all", "live", "draft", "started"
|
|
|
45577
46296
|
function qs(params) {
|
|
45578
46297
|
return params.size > 0 ? `?${params}` : "";
|
|
45579
46298
|
}
|
|
45580
|
-
function
|
|
46299
|
+
function enc6(id) {
|
|
45581
46300
|
if (/^\.+$/.test(id)) {
|
|
45582
46301
|
throw new McpToolError(`Invalid id '${id}'.`, {
|
|
45583
46302
|
hint: "Ids must be Eventbrite object ids (normally digits), not path segments."
|
|
@@ -45659,7 +46378,7 @@ function registerAccountTools(server, deps) {
|
|
|
45659
46378
|
if (continuation) params.set("continuation", continuation);
|
|
45660
46379
|
const data = await client2.request(
|
|
45661
46380
|
"GET",
|
|
45662
|
-
`/organizations/${
|
|
46381
|
+
`/organizations/${enc6(org_id)}/events/${qs(params)}`
|
|
45663
46382
|
);
|
|
45664
46383
|
return viewResponse(view, data);
|
|
45665
46384
|
}
|
|
@@ -45682,7 +46401,7 @@ function registerAccountTools(server, deps) {
|
|
|
45682
46401
|
if (continuation) params.set("continuation", continuation);
|
|
45683
46402
|
const data = await client2.request(
|
|
45684
46403
|
"GET",
|
|
45685
|
-
`/organizations/${
|
|
46404
|
+
`/organizations/${enc6(org_id)}/attendees/${qs(params)}`
|
|
45686
46405
|
);
|
|
45687
46406
|
return viewResponse(view, data);
|
|
45688
46407
|
}
|
|
@@ -45703,7 +46422,7 @@ function registerAccountTools(server, deps) {
|
|
|
45703
46422
|
if (continuation) params.set("continuation", continuation);
|
|
45704
46423
|
const data = await client2.request(
|
|
45705
46424
|
"GET",
|
|
45706
|
-
`/organizations/${
|
|
46425
|
+
`/organizations/${enc6(org_id)}/orders/${qs(params)}`
|
|
45707
46426
|
);
|
|
45708
46427
|
return viewResponse(view, data);
|
|
45709
46428
|
}
|
|
@@ -45743,7 +46462,7 @@ function registerAccountTools(server, deps) {
|
|
|
45743
46462
|
if (continuation) params.set("continuation", continuation);
|
|
45744
46463
|
return viewResponse(
|
|
45745
46464
|
view,
|
|
45746
|
-
await client2.request("GET", `/organizations/${
|
|
46465
|
+
await client2.request("GET", `/organizations/${enc6(org_id)}/${noun}/${qs(params)}`)
|
|
45747
46466
|
);
|
|
45748
46467
|
}
|
|
45749
46468
|
);
|
|
@@ -45773,7 +46492,7 @@ function registerAccountTools(server, deps) {
|
|
|
45773
46492
|
if (continuation) params.set("continuation", continuation);
|
|
45774
46493
|
return viewResponse(
|
|
45775
46494
|
view,
|
|
45776
|
-
await client2.request("GET", `/organizations/${
|
|
46495
|
+
await client2.request("GET", `/organizations/${enc6(org_id)}/reports/${kind}/${qs(params)}`)
|
|
45777
46496
|
);
|
|
45778
46497
|
}
|
|
45779
46498
|
);
|
|
@@ -45877,7 +46596,7 @@ function registerEventTools(server, deps) {
|
|
|
45877
46596
|
if (continuation) params.set("continuation", continuation);
|
|
45878
46597
|
return viewResponse(
|
|
45879
46598
|
view,
|
|
45880
|
-
await client2.request("GET", `/events/${
|
|
46599
|
+
await client2.request("GET", `/events/${enc6(event_id)}/attendees/${qs(params)}`)
|
|
45881
46600
|
);
|
|
45882
46601
|
}
|
|
45883
46602
|
);
|
|
@@ -45894,7 +46613,7 @@ function registerEventTools(server, deps) {
|
|
|
45894
46613
|
},
|
|
45895
46614
|
async ({ event_id, attendee_id, view }) => viewResponse(
|
|
45896
46615
|
view,
|
|
45897
|
-
await client2.request("GET", `/events/${
|
|
46616
|
+
await client2.request("GET", `/events/${enc6(event_id)}/attendees/${enc6(attendee_id)}/`)
|
|
45898
46617
|
)
|
|
45899
46618
|
);
|
|
45900
46619
|
server.registerTool(
|
|
@@ -45917,7 +46636,7 @@ function registerEventTools(server, deps) {
|
|
|
45917
46636
|
if (continuation) params.set("continuation", continuation);
|
|
45918
46637
|
return viewResponse(
|
|
45919
46638
|
view,
|
|
45920
|
-
await client2.request("GET", `/events/${
|
|
46639
|
+
await client2.request("GET", `/events/${enc6(event_id)}/orders/${qs(params)}`)
|
|
45921
46640
|
);
|
|
45922
46641
|
}
|
|
45923
46642
|
);
|
|
@@ -45936,7 +46655,7 @@ function registerEventTools(server, deps) {
|
|
|
45936
46655
|
view,
|
|
45937
46656
|
await client2.request(
|
|
45938
46657
|
"GET",
|
|
45939
|
-
`/events/${
|
|
46658
|
+
`/events/${enc6(event_id)}/ticket_classes/${enc6(ticket_class_id)}/`
|
|
45940
46659
|
)
|
|
45941
46660
|
)
|
|
45942
46661
|
);
|
|
@@ -45955,7 +46674,7 @@ function registerEventTools(server, deps) {
|
|
|
45955
46674
|
view,
|
|
45956
46675
|
await client2.request(
|
|
45957
46676
|
"GET",
|
|
45958
|
-
`/events/${
|
|
46677
|
+
`/events/${enc6(event_id)}/${canned ? "canned_questions" : "questions"}/`
|
|
45959
46678
|
)
|
|
45960
46679
|
)
|
|
45961
46680
|
);
|
|
@@ -45978,7 +46697,7 @@ function registerLookupTools(server, deps) {
|
|
|
45978
46697
|
async ({ order_id, expand, view }) => {
|
|
45979
46698
|
const params = new URLSearchParams();
|
|
45980
46699
|
if (expand) params.set("expand", expand);
|
|
45981
|
-
return viewResponse(view, await client2.request("GET", `/orders/${
|
|
46700
|
+
return viewResponse(view, await client2.request("GET", `/orders/${enc6(order_id)}/${qs(params)}`));
|
|
45982
46701
|
}
|
|
45983
46702
|
);
|
|
45984
46703
|
server.registerTool(
|
|
@@ -45991,7 +46710,7 @@ function registerLookupTools(server, deps) {
|
|
|
45991
46710
|
view: viewArg()
|
|
45992
46711
|
}
|
|
45993
46712
|
},
|
|
45994
|
-
async ({ venue_id, view }) => viewResponse(view, await client2.request("GET", `/venues/${
|
|
46713
|
+
async ({ venue_id, view }) => viewResponse(view, await client2.request("GET", `/venues/${enc6(venue_id)}/`))
|
|
45995
46714
|
);
|
|
45996
46715
|
server.registerTool(
|
|
45997
46716
|
"eb_venue_events",
|
|
@@ -46011,7 +46730,7 @@ function registerLookupTools(server, deps) {
|
|
|
46011
46730
|
if (continuation) params.set("continuation", continuation);
|
|
46012
46731
|
return viewResponse(
|
|
46013
46732
|
view,
|
|
46014
|
-
await client2.request("GET", `/venues/${
|
|
46733
|
+
await client2.request("GET", `/venues/${enc6(venue_id)}/events/${qs(params)}`)
|
|
46015
46734
|
);
|
|
46016
46735
|
}
|
|
46017
46736
|
);
|
|
@@ -46025,7 +46744,7 @@ function registerLookupTools(server, deps) {
|
|
|
46025
46744
|
view: viewArg()
|
|
46026
46745
|
}
|
|
46027
46746
|
},
|
|
46028
|
-
async ({ organizer_id, view }) => viewResponse(view, await client2.request("GET", `/organizers/${
|
|
46747
|
+
async ({ organizer_id, view }) => viewResponse(view, await client2.request("GET", `/organizers/${enc6(organizer_id)}/`))
|
|
46029
46748
|
);
|
|
46030
46749
|
server.registerTool(
|
|
46031
46750
|
"eb_organizer_events",
|
|
@@ -46047,7 +46766,7 @@ function registerLookupTools(server, deps) {
|
|
|
46047
46766
|
if (continuation) params.set("continuation", continuation);
|
|
46048
46767
|
return viewResponse(
|
|
46049
46768
|
view,
|
|
46050
|
-
await client2.request("GET", `/organizers/${
|
|
46769
|
+
await client2.request("GET", `/organizers/${enc6(organizer_id)}/events/${qs(params)}`)
|
|
46051
46770
|
);
|
|
46052
46771
|
}
|
|
46053
46772
|
);
|
|
@@ -46069,7 +46788,7 @@ function registerLookupTools(server, deps) {
|
|
|
46069
46788
|
if (continuation) params.set("continuation", continuation);
|
|
46070
46789
|
return viewResponse(
|
|
46071
46790
|
view,
|
|
46072
|
-
await client2.request("GET", `/series/${
|
|
46791
|
+
await client2.request("GET", `/series/${enc6(series_id)}/events/${qs(params)}`)
|
|
46073
46792
|
);
|
|
46074
46793
|
}
|
|
46075
46794
|
);
|
|
@@ -46083,7 +46802,7 @@ function registerLookupTools(server, deps) {
|
|
|
46083
46802
|
view: viewArg()
|
|
46084
46803
|
}
|
|
46085
46804
|
},
|
|
46086
|
-
async ({ user_id, view }) => viewResponse(view, await client2.request("GET", `/users/${
|
|
46805
|
+
async ({ user_id, view }) => viewResponse(view, await client2.request("GET", `/users/${enc6(user_id)}/`))
|
|
46087
46806
|
);
|
|
46088
46807
|
}
|
|
46089
46808
|
|