@chrischall/eventbrite-mcp 0.3.0 → 0.3.1
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 +525 -261
- package/dist/version.js +1 -1
- package/package.json +7 -7
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Eventbrite — your tickets and orders, organizer data, and public event discovery",
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Eventbrite",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Eventbrite — tickets, orders, organizer data, and public event search. Account tools use a personal API token; discovery search routes through the user's signed-in eventbrite.com tab via the fetchproxy bridge, reusing their authenticated session.",
|
|
18
|
-
"version": "0.3.
|
|
18
|
+
"version": "0.3.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -7302,6 +7302,7 @@ var init_frames = __esm({
|
|
|
7302
7302
|
HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
7303
7303
|
KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
7304
7304
|
"fetch",
|
|
7305
|
+
"fetch_in_page",
|
|
7305
7306
|
"read_cookies",
|
|
7306
7307
|
"read_local_storage",
|
|
7307
7308
|
"read_session_storage",
|
|
@@ -7404,6 +7405,11 @@ function assertBase64(x, label) {
|
|
|
7404
7405
|
if (!BASE64_RE.test(x))
|
|
7405
7406
|
throw new ProtocolError(`${label}: invalid base64`);
|
|
7406
7407
|
}
|
|
7408
|
+
function assertBoolean(x, label) {
|
|
7409
|
+
if (typeof x !== "boolean") {
|
|
7410
|
+
throw new ProtocolError(`${label}: must be boolean`);
|
|
7411
|
+
}
|
|
7412
|
+
}
|
|
7407
7413
|
function assertPositiveInt(x, label) {
|
|
7408
7414
|
if (typeof x !== "number" || !Number.isInteger(x) || x <= 0) {
|
|
7409
7415
|
throw new ProtocolError(`${label}: expected positive integer`);
|
|
@@ -7697,6 +7703,10 @@ function validateFrame(raw) {
|
|
|
7697
7703
|
return validateEncrypted(raw);
|
|
7698
7704
|
if (t === "pair-pending")
|
|
7699
7705
|
return validatePairPending(raw);
|
|
7706
|
+
if (t === "hello-rejected")
|
|
7707
|
+
return validateHelloRejected(raw);
|
|
7708
|
+
if (t === "extension-disconnected")
|
|
7709
|
+
return { type: "extension-disconnected" };
|
|
7700
7710
|
throw new ProtocolError(`unknown frame type: ${String(t)}`);
|
|
7701
7711
|
}
|
|
7702
7712
|
function validateHello(raw) {
|
|
@@ -7764,6 +7774,16 @@ function validateHello(raw) {
|
|
|
7764
7774
|
if (raw.domSelectors !== void 0) {
|
|
7765
7775
|
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
7766
7776
|
}
|
|
7777
|
+
if (raw.accepts !== void 0) {
|
|
7778
|
+
if (!Array.isArray(raw.accepts)) {
|
|
7779
|
+
throw new ProtocolError("hello.accepts: expected array");
|
|
7780
|
+
}
|
|
7781
|
+
for (const a of raw.accepts) {
|
|
7782
|
+
if (typeof a !== "string") {
|
|
7783
|
+
throw new ProtocolError(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7767
7787
|
if (raw.graphqlOps !== void 0) {
|
|
7768
7788
|
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
7769
7789
|
}
|
|
@@ -7814,6 +7834,16 @@ function validatePairPending(raw) {
|
|
|
7814
7834
|
}
|
|
7815
7835
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
7816
7836
|
}
|
|
7837
|
+
function validateHelloRejected(raw) {
|
|
7838
|
+
assertString(raw.mcpId, "hello-rejected.mcpId");
|
|
7839
|
+
if (!isValidMcpId(raw.mcpId))
|
|
7840
|
+
throw new ProtocolError("hello-rejected.mcpId: invalid format");
|
|
7841
|
+
assertString(raw.reason, "hello-rejected.reason");
|
|
7842
|
+
if (raw.reason.length > 200) {
|
|
7843
|
+
throw new ProtocolError("hello-rejected.reason: must be at most 200 characters");
|
|
7844
|
+
}
|
|
7845
|
+
return { type: "hello-rejected", mcpId: raw.mcpId, reason: raw.reason };
|
|
7846
|
+
}
|
|
7817
7847
|
function validateInnerFrame(raw) {
|
|
7818
7848
|
assertObject(raw, "inner");
|
|
7819
7849
|
const t = raw.type;
|
|
@@ -7845,6 +7875,14 @@ function validateInnerRequest(raw) {
|
|
|
7845
7875
|
if (raw.init.body !== void 0) {
|
|
7846
7876
|
assertString(raw.init.body, "inner.init.body");
|
|
7847
7877
|
}
|
|
7878
|
+
if (raw.init.inPage !== void 0) {
|
|
7879
|
+
assertBoolean(raw.init.inPage, "inner.init.inPage");
|
|
7880
|
+
}
|
|
7881
|
+
if (raw.init.credentials !== void 0) {
|
|
7882
|
+
if (raw.init.credentials !== "include" && raw.init.credentials !== "omit") {
|
|
7883
|
+
throw new ProtocolError("inner.init.credentials: must be 'include' or 'omit'");
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7848
7886
|
return raw;
|
|
7849
7887
|
}
|
|
7850
7888
|
if (raw.op === "read_cookies") {
|
|
@@ -12274,6 +12312,8 @@ async function buildServerHello(opts) {
|
|
|
12274
12312
|
sessionNonce: toB64(sessionNonce),
|
|
12275
12313
|
sessionSig: toB64(sig)
|
|
12276
12314
|
};
|
|
12315
|
+
if (opts.accepts && opts.accepts.length > 0)
|
|
12316
|
+
hello.accepts = [...opts.accepts];
|
|
12277
12317
|
if (opts.cookieKeys && opts.cookieKeys.length > 0) {
|
|
12278
12318
|
hello.cookieKeys = [...opts.cookieKeys];
|
|
12279
12319
|
}
|
|
@@ -12375,7 +12415,7 @@ async function awaitSessionReady(ready, opts) {
|
|
|
12375
12415
|
clearTimeout(timer);
|
|
12376
12416
|
}
|
|
12377
12417
|
}
|
|
12378
|
-
var SESSION_READY_TIMEOUT_MS, FetchproxySessionNotReadyError;
|
|
12418
|
+
var SESSION_READY_TIMEOUT_MS, FetchproxySessionNotReadyError, FetchproxyHelloRejectedError;
|
|
12379
12419
|
var init_session_ready = __esm({
|
|
12380
12420
|
"node_modules/@fetchproxy/server/dist/session-ready.js"() {
|
|
12381
12421
|
SESSION_READY_TIMEOUT_MS = 3e4;
|
|
@@ -12396,15 +12436,35 @@ var init_session_ready = __esm({
|
|
|
12396
12436
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
12397
12437
|
}
|
|
12398
12438
|
};
|
|
12439
|
+
FetchproxyHelloRejectedError = class extends Error {
|
|
12440
|
+
mcpId;
|
|
12441
|
+
reason;
|
|
12442
|
+
constructor(info) {
|
|
12443
|
+
super(`fetchproxy: the extension refused the connection for "${info.mcpId}": ${info.reason}. This is the extension's own reason \u2014 it is not a timeout, and retrying unchanged will be refused the same way.`);
|
|
12444
|
+
this.name = "FetchproxyHelloRejectedError";
|
|
12445
|
+
this.mcpId = info.mcpId;
|
|
12446
|
+
this.reason = info.reason;
|
|
12447
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
12448
|
+
}
|
|
12449
|
+
};
|
|
12399
12450
|
}
|
|
12400
12451
|
});
|
|
12401
12452
|
|
|
12402
12453
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
12403
12454
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
12404
|
-
import { join as join2 } from "node:path";
|
|
12455
|
+
import { isAbsolute, join as join2 } from "node:path";
|
|
12405
12456
|
import { homedir } from "node:os";
|
|
12457
|
+
function envIdentityDir() {
|
|
12458
|
+
const raw = process.env.FETCHPROXY_IDENTITY_DIR;
|
|
12459
|
+
if (raw === void 0)
|
|
12460
|
+
return void 0;
|
|
12461
|
+
const dir = raw.trim();
|
|
12462
|
+
if (dir === "" || !isAbsolute(dir))
|
|
12463
|
+
return void 0;
|
|
12464
|
+
return dir;
|
|
12465
|
+
}
|
|
12406
12466
|
function defaultIdentityDir() {
|
|
12407
|
-
return join2(homedir(), ".fetchproxy", "identity");
|
|
12467
|
+
return envIdentityDir() ?? join2(homedir(), ".fetchproxy", "identity");
|
|
12408
12468
|
}
|
|
12409
12469
|
function safeIdentityFileBase(serverName) {
|
|
12410
12470
|
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
@@ -12412,42 +12472,57 @@ function safeIdentityFileBase(serverName) {
|
|
|
12412
12472
|
}
|
|
12413
12473
|
return serverName.replace(/\//g, "_");
|
|
12414
12474
|
}
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
try {
|
|
12420
|
-
const raw = await readFile(path, "utf8");
|
|
12421
|
-
const j2 = JSON.parse(raw);
|
|
12422
|
-
return {
|
|
12423
|
-
x25519Priv: fromB64(j2.x25519Priv),
|
|
12424
|
-
x25519Pub: fromB64(j2.x25519Pub),
|
|
12425
|
-
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
12426
|
-
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
12427
|
-
createdAt: j2.createdAt
|
|
12428
|
-
};
|
|
12429
|
-
} catch (e) {
|
|
12430
|
-
if (e.code !== "ENOENT")
|
|
12431
|
-
throw e;
|
|
12432
|
-
}
|
|
12475
|
+
function identityFilePath(dir, serverName) {
|
|
12476
|
+
return join2(dir, `${safeIdentityFileBase(serverName)}.json`);
|
|
12477
|
+
}
|
|
12478
|
+
async function generateIdentity() {
|
|
12433
12479
|
const x = await generateX25519();
|
|
12434
12480
|
const ed = await generateEd25519();
|
|
12435
|
-
|
|
12481
|
+
return {
|
|
12436
12482
|
x25519Priv: x.privateKey,
|
|
12437
12483
|
x25519Pub: x.publicKey,
|
|
12438
12484
|
ed25519Priv: ed.privateKey,
|
|
12439
12485
|
ed25519Pub: ed.publicKey,
|
|
12440
12486
|
createdAt: Date.now()
|
|
12441
12487
|
};
|
|
12442
|
-
|
|
12488
|
+
}
|
|
12489
|
+
function serializeIdentity(id) {
|
|
12490
|
+
return JSON.stringify({
|
|
12443
12491
|
x25519Priv: toB64(id.x25519Priv),
|
|
12444
12492
|
x25519Pub: toB64(id.x25519Pub),
|
|
12445
12493
|
ed25519Priv: toB64(id.ed25519Priv),
|
|
12446
12494
|
ed25519Pub: toB64(id.ed25519Pub),
|
|
12447
12495
|
createdAt: id.createdAt
|
|
12496
|
+
}, null, 2);
|
|
12497
|
+
}
|
|
12498
|
+
function parseIdentity(text) {
|
|
12499
|
+
const j = JSON.parse(text);
|
|
12500
|
+
return {
|
|
12501
|
+
x25519Priv: fromB64(j.x25519Priv),
|
|
12502
|
+
x25519Pub: fromB64(j.x25519Pub),
|
|
12503
|
+
ed25519Priv: fromB64(j.ed25519Priv),
|
|
12504
|
+
ed25519Pub: fromB64(j.ed25519Pub),
|
|
12505
|
+
createdAt: j.createdAt
|
|
12448
12506
|
};
|
|
12449
|
-
|
|
12507
|
+
}
|
|
12508
|
+
async function writeIdentityFile(dir, serverName, id) {
|
|
12509
|
+
const path = identityFilePath(dir, serverName);
|
|
12510
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
12511
|
+
await writeFile(path, serializeIdentity(id), { mode: 384 });
|
|
12450
12512
|
await chmod(path, 384);
|
|
12513
|
+
return path;
|
|
12514
|
+
}
|
|
12515
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
12516
|
+
const path = identityFilePath(dir, serverName);
|
|
12517
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
12518
|
+
try {
|
|
12519
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
12520
|
+
} catch (e) {
|
|
12521
|
+
if (e.code !== "ENOENT")
|
|
12522
|
+
throw e;
|
|
12523
|
+
}
|
|
12524
|
+
const id = await generateIdentity();
|
|
12525
|
+
await writeIdentityFile(dir, serverName, id);
|
|
12451
12526
|
return id;
|
|
12452
12527
|
}
|
|
12453
12528
|
var SAFE_PLAIN, SAFE_SCOPED;
|
|
@@ -12565,6 +12640,9 @@ async function startHost(opts) {
|
|
|
12565
12640
|
}
|
|
12566
12641
|
});
|
|
12567
12642
|
const ownHello = await buildServerHello({
|
|
12643
|
+
// 2.6.0: tell the extension it can say WHY it refused a hello, instead of
|
|
12644
|
+
// leaving us to time out and guess.
|
|
12645
|
+
accepts: ["hello-rejected"],
|
|
12568
12646
|
identity: opts.ownIdentity,
|
|
12569
12647
|
mcpId: opts.ownMcpId,
|
|
12570
12648
|
serverName: opts.ownServerName,
|
|
@@ -12776,6 +12854,16 @@ async function startHost(opts) {
|
|
|
12776
12854
|
extensionWs.send(JSON.stringify(frame));
|
|
12777
12855
|
}
|
|
12778
12856
|
}
|
|
12857
|
+
if (frame.type === "hello-rejected" && identified === "extension") {
|
|
12858
|
+
if (frame.mcpId === opts.ownMcpId) {
|
|
12859
|
+
rejectOwnSession(new FetchproxyHelloRejectedError({ mcpId: frame.mcpId, reason: frame.reason }));
|
|
12860
|
+
} else {
|
|
12861
|
+
const slot = peers.get(frame.mcpId);
|
|
12862
|
+
if (slot?.helloFrame.accepts?.includes("hello-rejected")) {
|
|
12863
|
+
slot.ws.send(JSON.stringify(frame));
|
|
12864
|
+
}
|
|
12865
|
+
}
|
|
12866
|
+
}
|
|
12779
12867
|
if (frame.type === "pair-pending" && identified === "extension") {
|
|
12780
12868
|
if (frame.mcpId === opts.ownMcpId) {
|
|
12781
12869
|
ownPendingPairCode = frame.pairCode;
|
|
@@ -12805,8 +12893,18 @@ async function startHost(opts) {
|
|
|
12805
12893
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
12806
12894
|
}
|
|
12807
12895
|
ownSession = null;
|
|
12896
|
+
ownPendingPairCode = null;
|
|
12808
12897
|
resetSessionPromise();
|
|
12809
12898
|
disconnectListeners.forEach((cb) => cb());
|
|
12899
|
+
const notice = JSON.stringify({ type: "extension-disconnected" });
|
|
12900
|
+
for (const slot of peers.values()) {
|
|
12901
|
+
if (slot.helloFrame.accepts?.includes("extension-disconnected")) {
|
|
12902
|
+
try {
|
|
12903
|
+
slot.ws.send(notice);
|
|
12904
|
+
} catch {
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12810
12908
|
}
|
|
12811
12909
|
if (identified === "peer" && peerMcpId) {
|
|
12812
12910
|
if (peers.get(peerMcpId)?.ws === ws)
|
|
@@ -12845,7 +12943,9 @@ async function startHost(opts) {
|
|
|
12845
12943
|
onPendingPair: (cb) => {
|
|
12846
12944
|
pendingPairListeners.push(cb);
|
|
12847
12945
|
},
|
|
12848
|
-
pendingPairCode: () => ownPendingPairCode
|
|
12946
|
+
pendingPairCode: () => ownPendingPairCode,
|
|
12947
|
+
extensionConnected: () => extensionWs !== null,
|
|
12948
|
+
sessionLinked: () => ownSession !== null
|
|
12849
12949
|
};
|
|
12850
12950
|
}
|
|
12851
12951
|
var PUBLIC_ORIGIN_RE, enc2;
|
|
@@ -12884,7 +12984,11 @@ async function startPeer(opts) {
|
|
|
12884
12984
|
domSelectors: opts.domSelectors,
|
|
12885
12985
|
localStoragePointers: opts.localStoragePointers,
|
|
12886
12986
|
sessionStoragePointers: opts.sessionStoragePointers,
|
|
12887
|
-
graphqlOps: opts.graphqlOps
|
|
12987
|
+
graphqlOps: opts.graphqlOps,
|
|
12988
|
+
// 2.5.0: let a host ≥2.5.0 tell us when the extension leaves, so
|
|
12989
|
+
// `extensionConnected()` / `sessionLinked()` can go back to false.
|
|
12990
|
+
// 2.6.0: `hello-rejected` for the same reason, from the extension.
|
|
12991
|
+
accepts: ["extension-disconnected", "hello-rejected"]
|
|
12888
12992
|
});
|
|
12889
12993
|
const sessionNonce = fromB64(hello.sessionNonce);
|
|
12890
12994
|
ws.send(JSON.stringify(hello));
|
|
@@ -12901,6 +13005,7 @@ async function startPeer(opts) {
|
|
|
12901
13005
|
rejectFirstReady = reject;
|
|
12902
13006
|
});
|
|
12903
13007
|
let extensionHello = null;
|
|
13008
|
+
let extensionGone = false;
|
|
12904
13009
|
let warnedUnverifiable = false;
|
|
12905
13010
|
let cachedPin = void 0;
|
|
12906
13011
|
const authenticateExtension = async (sessionSig, extensionSessionPub) => {
|
|
@@ -12971,6 +13076,12 @@ async function startPeer(opts) {
|
|
|
12971
13076
|
extensionHello = frame;
|
|
12972
13077
|
return;
|
|
12973
13078
|
}
|
|
13079
|
+
if (frame.type === "extension-disconnected") {
|
|
13080
|
+
extensionHello = null;
|
|
13081
|
+
extensionGone = true;
|
|
13082
|
+
pendingPairCode = null;
|
|
13083
|
+
return;
|
|
13084
|
+
}
|
|
12974
13085
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
12975
13086
|
const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
|
|
12976
13087
|
if (!authorised) {
|
|
@@ -12983,6 +13094,7 @@ async function startPeer(opts) {
|
|
|
12983
13094
|
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
12984
13095
|
const isRenegotiation = session !== null;
|
|
12985
13096
|
session = new SessionState(sessionKey);
|
|
13097
|
+
extensionGone = false;
|
|
12986
13098
|
pendingPairCode = null;
|
|
12987
13099
|
if (isRenegotiation) {
|
|
12988
13100
|
renegotiateListeners.forEach((cb) => cb());
|
|
@@ -12991,6 +13103,9 @@ async function startPeer(opts) {
|
|
|
12991
13103
|
}
|
|
12992
13104
|
return;
|
|
12993
13105
|
}
|
|
13106
|
+
if (frame.type === "hello-rejected" && frame.mcpId === opts.mcpId) {
|
|
13107
|
+
rejectFirstReady(new FetchproxyHelloRejectedError({ mcpId: frame.mcpId, reason: frame.reason }));
|
|
13108
|
+
}
|
|
12994
13109
|
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
12995
13110
|
pendingPairCode = frame.pairCode;
|
|
12996
13111
|
pendingPairListeners.forEach((cb) => cb(frame.pairCode));
|
|
@@ -13050,6 +13165,8 @@ async function startPeer(opts) {
|
|
|
13050
13165
|
pendingPairListeners.push(cb);
|
|
13051
13166
|
},
|
|
13052
13167
|
pendingPairCode: () => pendingPairCode,
|
|
13168
|
+
extensionConnected: () => extensionHello !== null,
|
|
13169
|
+
sessionLinked: () => session !== null && !extensionGone,
|
|
13053
13170
|
onClose: (cb) => {
|
|
13054
13171
|
closeListeners.push(cb);
|
|
13055
13172
|
},
|
|
@@ -13078,7 +13195,7 @@ function classifyFetchError(error61) {
|
|
|
13078
13195
|
if (/^tab fetch failed:/.test(error61)) {
|
|
13079
13196
|
return "tab_fetch_failed";
|
|
13080
13197
|
}
|
|
13081
|
-
if (/^fetch threw:/.test(error61)) {
|
|
13198
|
+
if (/^(in-page )?fetch threw:/.test(error61)) {
|
|
13082
13199
|
return "tab_fetch_failed";
|
|
13083
13200
|
}
|
|
13084
13201
|
if (/^no tab matching /.test(error61)) {
|
|
@@ -13102,6 +13219,10 @@ var init_error_kind = __esm({
|
|
|
13102
13219
|
|
|
13103
13220
|
// node_modules/@fetchproxy/server/dist/classify-bridge-error.js
|
|
13104
13221
|
function classifyBridgeError(err) {
|
|
13222
|
+
if (err instanceof FetchproxyHelloRejectedError)
|
|
13223
|
+
return "hello_rejected";
|
|
13224
|
+
if (err instanceof FetchproxySessionNotReadyError)
|
|
13225
|
+
return "session_not_ready";
|
|
13105
13226
|
if (err instanceof FetchproxyTimeoutError)
|
|
13106
13227
|
return "timeout";
|
|
13107
13228
|
if (err instanceof FetchproxyBridgeDownError)
|
|
@@ -13115,6 +13236,7 @@ function classifyBridgeError(err) {
|
|
|
13115
13236
|
var init_classify_bridge_error = __esm({
|
|
13116
13237
|
"node_modules/@fetchproxy/server/dist/classify-bridge-error.js"() {
|
|
13117
13238
|
init_ws_server();
|
|
13239
|
+
init_session_ready();
|
|
13118
13240
|
}
|
|
13119
13241
|
});
|
|
13120
13242
|
|
|
@@ -13143,6 +13265,8 @@ function envWsHost() {
|
|
|
13143
13265
|
function protocolErrorFrom(error61) {
|
|
13144
13266
|
if (SCOPE_REJECTION.test(error61))
|
|
13145
13267
|
return new FetchproxyScopeError(error61);
|
|
13268
|
+
if (TAB_OPENING_REJECTION.test(error61))
|
|
13269
|
+
return new FetchproxyTabOpeningError(error61);
|
|
13146
13270
|
if (NO_TAB_REJECTION.test(error61))
|
|
13147
13271
|
return new FetchproxyNoTabError(error61);
|
|
13148
13272
|
return new FetchproxyProtocolError(error61);
|
|
@@ -13182,7 +13306,7 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
13182
13306
|
const declared = domains.map((d) => JSON.stringify(d)).join(", ");
|
|
13183
13307
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
13184
13308
|
}
|
|
13185
|
-
var FetchproxyProtocolError, FetchproxyHttpError, FetchproxyBridgeDownError, FetchproxyHintedError, FetchproxyScopeError, FetchproxyNoTabError, SCOPE_REJECTION, NO_TAB_REJECTION, FetchproxyTimeoutError, SUBDOMAIN_LABEL_RE, DEFAULT_JSON_OK_STATUSES, FetchproxyServer;
|
|
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;
|
|
13186
13310
|
var init_ws_server = __esm({
|
|
13187
13311
|
"node_modules/@fetchproxy/server/dist/ws-server.js"() {
|
|
13188
13312
|
init_dist();
|
|
@@ -13258,8 +13382,15 @@ var init_ws_server = __esm({
|
|
|
13258
13382
|
this.name = "FetchproxyNoTabError";
|
|
13259
13383
|
}
|
|
13260
13384
|
};
|
|
13385
|
+
FetchproxyTabOpeningError = class extends FetchproxyHintedError {
|
|
13386
|
+
constructor(originalError) {
|
|
13387
|
+
super(originalError, "the extension is opening that tab now \u2014 retry in a few seconds. You do not need to open one yourself, and this is not a version problem.");
|
|
13388
|
+
this.name = "FetchproxyTabOpeningError";
|
|
13389
|
+
}
|
|
13390
|
+
};
|
|
13261
13391
|
SCOPE_REJECTION = /not in declared/;
|
|
13262
|
-
NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
|
|
13392
|
+
NO_TAB_REJECTION = /no tab matching (?!.*(content script loaded|one is still opening))/;
|
|
13393
|
+
TAB_OPENING_REJECTION = /no tab matching .*one is still opening/;
|
|
13263
13394
|
FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
13264
13395
|
url;
|
|
13265
13396
|
timeoutMs;
|
|
@@ -13277,8 +13408,17 @@ var init_ws_server = __esm({
|
|
|
13277
13408
|
* the first attempt.
|
|
13278
13409
|
*/
|
|
13279
13410
|
retryAttempted;
|
|
13411
|
+
/**
|
|
13412
|
+
* 2.4.2+ (#277): what the CALL asked for, when that is more than the
|
|
13413
|
+
* deadline that actually fired. A per-call `timeoutMs` is forwarded to the
|
|
13414
|
+
* extension but does not raise this server's `fetchTimeoutMs`, so the
|
|
13415
|
+
* shorter deadline wins — and used to report only its own number, a value
|
|
13416
|
+
* the caller never supplied, from an option the message never named.
|
|
13417
|
+
*/
|
|
13418
|
+
requestedTimeoutMs;
|
|
13280
13419
|
constructor(args) {
|
|
13281
|
-
|
|
13420
|
+
const capped = args.requestedTimeoutMs !== void 0 && args.requestedTimeoutMs > args.timeoutMs;
|
|
13421
|
+
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms` + (capped ? ` \u2014 that is this server's fetchTimeoutMs, not the ${args.requestedTimeoutMs}ms this call asked for. A per-call timeoutMs cannot exceed it; raise fetchTimeoutMs on the transport to wait longer.` : ""));
|
|
13282
13422
|
this.name = "FetchproxyTimeoutError";
|
|
13283
13423
|
this.url = args.url;
|
|
13284
13424
|
this.timeoutMs = args.timeoutMs;
|
|
@@ -13286,6 +13426,7 @@ var init_ws_server = __esm({
|
|
|
13286
13426
|
this.port = args.port ?? 0;
|
|
13287
13427
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
13288
13428
|
this.retryAttempted = args.retryAttempted ?? false;
|
|
13429
|
+
this.requestedTimeoutMs = args.requestedTimeoutMs ?? null;
|
|
13289
13430
|
}
|
|
13290
13431
|
};
|
|
13291
13432
|
SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
@@ -13694,6 +13835,7 @@ var init_ws_server = __esm({
|
|
|
13694
13835
|
lastFailureReason: this.lastFailureReason,
|
|
13695
13836
|
consecutiveFailures: this.consecutiveFailures,
|
|
13696
13837
|
lastExtensionMessageAt: this.lastExtensionMessageAt,
|
|
13838
|
+
session: this.sessionSnapshot(),
|
|
13697
13839
|
keepAlive: {
|
|
13698
13840
|
enabled: intervalMs > 0,
|
|
13699
13841
|
intervalMs,
|
|
@@ -13829,9 +13971,9 @@ var init_ws_server = __esm({
|
|
|
13829
13971
|
async _fetchOnceWithTimeout(init) {
|
|
13830
13972
|
const id = this.nextRequestId++;
|
|
13831
13973
|
const inner = { type: "request", id, op: "fetch", init };
|
|
13832
|
-
const pending = new Promise((resolve) => {
|
|
13974
|
+
const pending = this.guardPending(new Promise((resolve) => {
|
|
13833
13975
|
this.pending.set(id, resolve);
|
|
13834
|
-
});
|
|
13976
|
+
}));
|
|
13835
13977
|
await this.sendInnerFrame(inner);
|
|
13836
13978
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
13837
13979
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
@@ -13878,7 +14020,35 @@ var init_ws_server = __esm({
|
|
|
13878
14020
|
* already surface for fetch timeouts). `0`/unset opts out (unbounded),
|
|
13879
14021
|
* matching the fetch path.
|
|
13880
14022
|
*/
|
|
13881
|
-
|
|
14023
|
+
/**
|
|
14024
|
+
* Register a verb's pending promise and keep an early rejection from being
|
|
14025
|
+
* fatal.
|
|
14026
|
+
*
|
|
14027
|
+
* Every verb stores its resolver, then AWAITS `sendInnerFrame` before the
|
|
14028
|
+
* caller attaches a handler — and that send does real work (sealing, a
|
|
14029
|
+
* socket write), so it yields across event-loop turns. Anything that drains
|
|
14030
|
+
* the pending maps inside that window rejects a promise nothing is
|
|
14031
|
+
* listening to yet: a refused pairing (`rejectAllPending(pairingErrorMessage
|
|
14032
|
+
* (code))`), an extension disconnect, a `close()`. Under Node's default
|
|
14033
|
+
* `--unhandled-rejections=throw` that ends the PROCESS, so an MCP that
|
|
14034
|
+
* should have reported "approve pair code NNN-NN" dies instead, and each
|
|
14035
|
+
* respawn mints a fresh code nobody can approve (#329).
|
|
14036
|
+
*
|
|
14037
|
+
* The no-op catch is a listener, not a swallow. `.catch()` derives a NEW
|
|
14038
|
+
* promise and leaves this one's rejection intact, so `_withVerbTimeout` and
|
|
14039
|
+
* the caller still see it — the only thing that changes is that the
|
|
14040
|
+
* rejection is never unhandled.
|
|
14041
|
+
*
|
|
14042
|
+
* This is the same hazard `_withVerbTimeout`'s timer already guards against
|
|
14043
|
+
* from the other side, where it drops the resolver "so a late bridge
|
|
14044
|
+
* response doesn't become an unhandled promise that crashes the host".
|
|
14045
|
+
*/
|
|
14046
|
+
guardPending(pending) {
|
|
14047
|
+
pending.catch(() => {
|
|
14048
|
+
});
|
|
14049
|
+
return pending;
|
|
14050
|
+
}
|
|
14051
|
+
async _withVerbTimeout(pending, pendingMap, id, url2, requestedTimeoutMs) {
|
|
13882
14052
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
13883
14053
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
13884
14054
|
return pending;
|
|
@@ -13896,7 +14066,8 @@ var init_ws_server = __esm({
|
|
|
13896
14066
|
role: this.role,
|
|
13897
14067
|
port: this.opts.port,
|
|
13898
14068
|
elapsedMs: Date.now() - start,
|
|
13899
|
-
retryAttempted: false
|
|
14069
|
+
retryAttempted: false,
|
|
14070
|
+
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
13900
14071
|
}));
|
|
13901
14072
|
}, timeoutMs);
|
|
13902
14073
|
})
|
|
@@ -13956,9 +14127,9 @@ var init_ws_server = __esm({
|
|
|
13956
14127
|
if (opts.subdomain !== void 0)
|
|
13957
14128
|
assertSubdomainLabel(opts.subdomain);
|
|
13958
14129
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
13959
|
-
const
|
|
14130
|
+
const isAbsolute2 = path.startsWith("http://") || path.startsWith("https://");
|
|
13960
14131
|
let host;
|
|
13961
|
-
if (
|
|
14132
|
+
if (isAbsolute2) {
|
|
13962
14133
|
try {
|
|
13963
14134
|
host = new URL(path).host;
|
|
13964
14135
|
} catch {
|
|
@@ -13967,7 +14138,7 @@ var init_ws_server = __esm({
|
|
|
13967
14138
|
} else {
|
|
13968
14139
|
host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
13969
14140
|
}
|
|
13970
|
-
const url2 =
|
|
14141
|
+
const url2 = isAbsolute2 ? path : `https://${host}${path}`;
|
|
13971
14142
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
13972
14143
|
let tabUrl = `https://${host}/`;
|
|
13973
14144
|
if (opts.viaTab !== void 0) {
|
|
@@ -13984,7 +14155,15 @@ var init_ws_server = __esm({
|
|
|
13984
14155
|
method,
|
|
13985
14156
|
tabUrl,
|
|
13986
14157
|
headers: opts.headers,
|
|
13987
|
-
body: opts.body
|
|
14158
|
+
body: opts.body,
|
|
14159
|
+
// Spread rather than `inPage: opts.inPage`: the wire's validator treats
|
|
14160
|
+
// the key's PRESENCE as the request, and a literal `undefined` would
|
|
14161
|
+
// both serialize away inconsistently and read as "asked for" here.
|
|
14162
|
+
...opts.inPage === true ? { inPage: true } : {},
|
|
14163
|
+
// Spread for the reason `inPage` is: only a deliberate 'omit' goes on
|
|
14164
|
+
// the wire, so an untouched request serialises byte-identically to
|
|
14165
|
+
// every version before this option existed.
|
|
14166
|
+
...opts.credentials === "omit" ? { credentials: "omit" } : {}
|
|
13988
14167
|
};
|
|
13989
14168
|
const result = await this.fetch(init);
|
|
13990
14169
|
if (!result.ok) {
|
|
@@ -14158,7 +14337,11 @@ var init_ws_server = __esm({
|
|
|
14158
14337
|
last_success_at: health.lastSuccessAt,
|
|
14159
14338
|
last_failure_at: health.lastFailureAt,
|
|
14160
14339
|
last_failure_reason: health.lastFailureReason,
|
|
14161
|
-
consecutive_failures: health.consecutiveFailures
|
|
14340
|
+
consecutive_failures: health.consecutiveFailures,
|
|
14341
|
+
session_state: health.session.state,
|
|
14342
|
+
pending_pair_code: health.session.pairCode,
|
|
14343
|
+
extension_connected: health.session.extensionConnected,
|
|
14344
|
+
last_extension_message_at: health.lastExtensionMessageAt
|
|
14162
14345
|
},
|
|
14163
14346
|
...error61 ? { error: error61 } : {}
|
|
14164
14347
|
};
|
|
@@ -14208,9 +14391,9 @@ var init_ws_server = __esm({
|
|
|
14208
14391
|
const tabUrl = `https://${host}/`;
|
|
14209
14392
|
inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
|
|
14210
14393
|
}
|
|
14211
|
-
const pending = new Promise((resolve) => {
|
|
14394
|
+
const pending = this.guardPending(new Promise((resolve) => {
|
|
14212
14395
|
this.pendingReadCookies.set(id, resolve);
|
|
14213
|
-
});
|
|
14396
|
+
}));
|
|
14214
14397
|
await this.sendInnerFrame(inner);
|
|
14215
14398
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
14216
14399
|
if (!result.ok) {
|
|
@@ -14267,9 +14450,9 @@ var init_ws_server = __esm({
|
|
|
14267
14450
|
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
14268
14451
|
}
|
|
14269
14452
|
};
|
|
14270
|
-
const pending = new Promise((resolve, reject) => {
|
|
14453
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14271
14454
|
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
14272
|
-
});
|
|
14455
|
+
}));
|
|
14273
14456
|
await this.sendInnerFrame(inner);
|
|
14274
14457
|
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
14275
14458
|
}
|
|
@@ -14330,9 +14513,9 @@ var init_ws_server = __esm({
|
|
|
14330
14513
|
...opts.pointers ? { pointers: { ...opts.pointers } } : {}
|
|
14331
14514
|
}
|
|
14332
14515
|
};
|
|
14333
|
-
const pending = new Promise((resolve, reject) => {
|
|
14516
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14334
14517
|
this.pendingStorage.set(id, { resolve, reject });
|
|
14335
|
-
});
|
|
14518
|
+
}));
|
|
14336
14519
|
await this.sendInnerFrame(inner);
|
|
14337
14520
|
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
14338
14521
|
}
|
|
@@ -14435,11 +14618,19 @@ var init_ws_server = __esm({
|
|
|
14435
14618
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
14436
14619
|
}
|
|
14437
14620
|
};
|
|
14438
|
-
const pending = new Promise((resolve, reject) => {
|
|
14621
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14439
14622
|
this.pendingCapture.set(id, { resolve, reject });
|
|
14440
|
-
});
|
|
14623
|
+
}));
|
|
14441
14624
|
await this.sendInnerFrame(inner);
|
|
14442
|
-
return this._withVerbTimeout(
|
|
14625
|
+
return this._withVerbTimeout(
|
|
14626
|
+
pending,
|
|
14627
|
+
this.pendingCapture,
|
|
14628
|
+
id,
|
|
14629
|
+
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
14630
|
+
// #277: not to extend the deadline — so a timeout can say the deadline,
|
|
14631
|
+
// and not the value asked for here, is what fired.
|
|
14632
|
+
opts.timeoutMs
|
|
14633
|
+
);
|
|
14443
14634
|
}
|
|
14444
14635
|
/**
|
|
14445
14636
|
* Snapshot the redirect target URL of the next request the browser
|
|
@@ -14520,11 +14711,19 @@ var init_ws_server = __esm({
|
|
|
14520
14711
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
14521
14712
|
}
|
|
14522
14713
|
};
|
|
14523
|
-
const pending = new Promise((resolve, reject) => {
|
|
14714
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14524
14715
|
this.pendingRedirect.set(id, { resolve, reject });
|
|
14525
|
-
});
|
|
14716
|
+
}));
|
|
14526
14717
|
await this.sendInnerFrame(inner);
|
|
14527
|
-
return this._withVerbTimeout(
|
|
14718
|
+
return this._withVerbTimeout(
|
|
14719
|
+
pending,
|
|
14720
|
+
this.pendingRedirect,
|
|
14721
|
+
id,
|
|
14722
|
+
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
14723
|
+
// #277: not to extend the deadline — so a timeout can say the deadline,
|
|
14724
|
+
// and not the value asked for here, is what fired.
|
|
14725
|
+
opts.timeoutMs
|
|
14726
|
+
);
|
|
14528
14727
|
}
|
|
14529
14728
|
/**
|
|
14530
14729
|
* Download `url` through the BROWSER's own network stack via
|
|
@@ -14602,11 +14801,21 @@ var init_ws_server = __esm({
|
|
|
14602
14801
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
14603
14802
|
}
|
|
14604
14803
|
};
|
|
14605
|
-
const pending = new Promise((resolve, reject) => {
|
|
14804
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14606
14805
|
this.pendingDownload.set(id, { resolve, reject });
|
|
14607
|
-
});
|
|
14806
|
+
}));
|
|
14608
14807
|
await this.sendInnerFrame(inner);
|
|
14609
|
-
return this._withVerbTimeout(
|
|
14808
|
+
return this._withVerbTimeout(
|
|
14809
|
+
pending,
|
|
14810
|
+
this.pendingDownload,
|
|
14811
|
+
id,
|
|
14812
|
+
opts.url,
|
|
14813
|
+
// #277, same as the two capture verbs: `download` takes a per-call
|
|
14814
|
+
// timeoutMs too, so it hits the identical cap and needs the identical
|
|
14815
|
+
// explanation. Missing it was the point of #279 — "both verbs" was
|
|
14816
|
+
// counted off the two call sites in view rather than off the type.
|
|
14817
|
+
opts.timeoutMs
|
|
14818
|
+
);
|
|
14610
14819
|
}
|
|
14611
14820
|
/**
|
|
14612
14821
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
@@ -14650,9 +14859,9 @@ var init_ws_server = __esm({
|
|
|
14650
14859
|
keys: [...opts.keys]
|
|
14651
14860
|
}
|
|
14652
14861
|
};
|
|
14653
|
-
const pending = new Promise((resolve, reject) => {
|
|
14862
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14654
14863
|
this.pendingIdb.set(id, { resolve, reject });
|
|
14655
|
-
});
|
|
14864
|
+
}));
|
|
14656
14865
|
await this.sendInnerFrame(inner);
|
|
14657
14866
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
14658
14867
|
}
|
|
@@ -14690,9 +14899,9 @@ var init_ws_server = __esm({
|
|
|
14690
14899
|
op: "read_dom",
|
|
14691
14900
|
init: { origin, names: [...opts.names] }
|
|
14692
14901
|
};
|
|
14693
|
-
const pending = new Promise((resolve, reject) => {
|
|
14902
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14694
14903
|
this.pendingStorage.set(id, { resolve, reject });
|
|
14695
|
-
});
|
|
14904
|
+
}));
|
|
14696
14905
|
await this.sendInnerFrame(inner);
|
|
14697
14906
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
14698
14907
|
}
|
|
@@ -14736,9 +14945,9 @@ var init_ws_server = __esm({
|
|
|
14736
14945
|
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
14737
14946
|
}
|
|
14738
14947
|
};
|
|
14739
|
-
const pending = new Promise((resolve, reject) => {
|
|
14948
|
+
const pending = this.guardPending(new Promise((resolve, reject) => {
|
|
14740
14949
|
this.pendingGraphql.set(id, { resolve, reject });
|
|
14741
|
-
});
|
|
14950
|
+
}));
|
|
14742
14951
|
await this.sendInnerFrame(inner);
|
|
14743
14952
|
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
|
|
14744
14953
|
}
|
|
@@ -14975,6 +15184,25 @@ var init_ws_server = __esm({
|
|
|
14975
15184
|
return this.peerHandle.pendingPairCode();
|
|
14976
15185
|
return null;
|
|
14977
15186
|
}
|
|
15187
|
+
/**
|
|
15188
|
+
* 2.5.0: the extension link as {@link BridgeSessionState}, read off
|
|
15189
|
+
* whichever handle is live. A pending pair code outranks "extension not
|
|
15190
|
+
* seen" because the code could only have come from the extension — a
|
|
15191
|
+
* peer behind an older host never sees the extension hello but does see
|
|
15192
|
+
* pair-pending frames. Where the extension is KNOWN to be gone (a host's
|
|
15193
|
+
* socket closed, a peer told by a 2.5.0+ host) the handle has already
|
|
15194
|
+
* dropped the code, so the precedence never lies there (#283).
|
|
15195
|
+
*/
|
|
15196
|
+
sessionSnapshot() {
|
|
15197
|
+
const handle = this.hostHandle ?? this.peerHandle;
|
|
15198
|
+
const pairCode = this.currentPendingPairCode();
|
|
15199
|
+
if (!handle || this.role === null) {
|
|
15200
|
+
return { state: "not_listening", pairCode, extensionConnected: false };
|
|
15201
|
+
}
|
|
15202
|
+
const extensionConnected = handle.extensionConnected();
|
|
15203
|
+
const state = handle.sessionLinked() ? "linked" : pairCode !== null ? "pair_pending" : !extensionConnected ? "extension_disconnected" : "no_session";
|
|
15204
|
+
return { state, pairCode, extensionConnected };
|
|
15205
|
+
}
|
|
14978
15206
|
/**
|
|
14979
15207
|
* 0.5.2+: throw `FetchproxyProtocolError` with the actionable pair-code
|
|
14980
15208
|
* message if the bridge is waiting on user approval. Used by the verb
|
|
@@ -15346,6 +15574,10 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
15346
15574
|
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
15347
15575
|
case "credential_rejected":
|
|
15348
15576
|
return `${hostLabel} rejected the credential from '${source}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
15577
|
+
case "session_expired":
|
|
15578
|
+
return `The credential from '${source}' is configured, but no session is live \u2014 ${hostLabel} served a sign-in page rather than the data. Sign in again; a cookie-session portal expires these on its own, so this recurs between uses.`;
|
|
15579
|
+
case "verification_pending":
|
|
15580
|
+
return `${hostLabel} is holding the sign-in on a second factor rather than refusing it. Supply the verification code the ACCOUNT HOLDER received \u2014 the credential itself is not the problem, so changing it will not help.`;
|
|
15349
15581
|
case "timeout":
|
|
15350
15582
|
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
15351
15583
|
case "http":
|
|
@@ -15356,12 +15588,102 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
15356
15588
|
return `Unexpected failure \u2014 see error.message.`;
|
|
15357
15589
|
}
|
|
15358
15590
|
}
|
|
15591
|
+
function credentialHealthcheckDescription(hostLabel) {
|
|
15592
|
+
return `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Read-only; never returns the credential itself.`;
|
|
15593
|
+
}
|
|
15594
|
+
async function runCredentialHealthcheck(args) {
|
|
15595
|
+
const { prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
|
|
15596
|
+
const probeUrl = probePath ? `https://${hostLabel}${probePath.startsWith("/") ? "" : "/"}${probePath}` : void 0;
|
|
15597
|
+
let probeStarted = 0;
|
|
15598
|
+
let state;
|
|
15599
|
+
try {
|
|
15600
|
+
state = await resolveCredential();
|
|
15601
|
+
} catch (e) {
|
|
15602
|
+
const classified = classifyThrown?.(e);
|
|
15603
|
+
const result2 = {
|
|
15604
|
+
ok: false,
|
|
15605
|
+
// Still false, and still no source: a classification explains WHY
|
|
15606
|
+
// nothing resolved, it does not invent a credential that did.
|
|
15607
|
+
credential: { source: null, resolved: false },
|
|
15608
|
+
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
15609
|
+
probe: { elapsed_ms: 0 },
|
|
15610
|
+
error: {
|
|
15611
|
+
kind: classified?.kind ?? "no_credential",
|
|
15612
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
15613
|
+
...classified?.detail !== void 0 ? { detail: classified.detail } : {}
|
|
15614
|
+
},
|
|
15615
|
+
// The hint must follow the KIND beside it. Falling back to
|
|
15616
|
+
// `no_credential`'s copy under a classified kind would state a cause
|
|
15617
|
+
// the kind contradicts — the same disagreement this path exists to
|
|
15618
|
+
// remove. So: an inline hint wins; else the classified arm's own
|
|
15619
|
+
// copy (consumer override first); else, for a kind this module has
|
|
15620
|
+
// no copy for, the neutral `unknown` text rather than one that
|
|
15621
|
+
// asserts a cause; else the unclassified `no_credential` default.
|
|
15622
|
+
hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
|
|
15623
|
+
};
|
|
15624
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15625
|
+
}
|
|
15626
|
+
const credential = {
|
|
15627
|
+
source: state.source,
|
|
15628
|
+
resolved: state.source !== null,
|
|
15629
|
+
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
15630
|
+
};
|
|
15631
|
+
if (!credential.resolved) {
|
|
15632
|
+
const result2 = {
|
|
15633
|
+
ok: false,
|
|
15634
|
+
credential,
|
|
15635
|
+
probe: { elapsed_ms: 0 },
|
|
15636
|
+
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
15637
|
+
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
15638
|
+
};
|
|
15639
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15640
|
+
}
|
|
15641
|
+
let arm = "ok";
|
|
15642
|
+
let error61;
|
|
15643
|
+
let status;
|
|
15644
|
+
let customHint;
|
|
15645
|
+
probeStarted = Date.now();
|
|
15646
|
+
try {
|
|
15647
|
+
await probeFn();
|
|
15648
|
+
} catch (e) {
|
|
15649
|
+
status = statusOf(e);
|
|
15650
|
+
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
15651
|
+
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
15652
|
+
let kind = arm;
|
|
15653
|
+
let detail;
|
|
15654
|
+
const custom2 = classifyThrown?.(e);
|
|
15655
|
+
if (custom2) {
|
|
15656
|
+
kind = custom2.kind;
|
|
15657
|
+
customHint = custom2.hint;
|
|
15658
|
+
detail = custom2.detail;
|
|
15659
|
+
arm = isArm(kind) ? kind : "unknown";
|
|
15660
|
+
}
|
|
15661
|
+
error61 = {
|
|
15662
|
+
kind,
|
|
15663
|
+
// Redacted AND bounded before it reaches the result: an upstream
|
|
15664
|
+
// failure routinely quotes what it was sent, and a healthcheck is
|
|
15665
|
+
// the tool people paste into a chat when something is broken.
|
|
15666
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
15667
|
+
...detail !== void 0 ? { detail } : {}
|
|
15668
|
+
};
|
|
15669
|
+
}
|
|
15670
|
+
const result = {
|
|
15671
|
+
ok: error61 === void 0,
|
|
15672
|
+
credential,
|
|
15673
|
+
probe: {
|
|
15674
|
+
...probeUrl ? { url: probeUrl } : {},
|
|
15675
|
+
elapsed_ms: Date.now() - probeStarted,
|
|
15676
|
+
...status !== void 0 ? { status } : {}
|
|
15677
|
+
},
|
|
15678
|
+
...error61 ? { error: error61 } : {},
|
|
15679
|
+
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
15680
|
+
};
|
|
15681
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
15682
|
+
}
|
|
15359
15683
|
function registerCredentialHealthcheckTool(args) {
|
|
15360
|
-
|
|
15361
|
-
const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
|
|
15362
|
-
server.registerTool(`${prefix}_healthcheck`, {
|
|
15684
|
+
args.server.registerTool(`${args.prefix}_healthcheck`, {
|
|
15363
15685
|
title: "Verify credentials and upstream reachability",
|
|
15364
|
-
description:
|
|
15686
|
+
description: `${credentialHealthcheckDescription(args.hostLabel)} Call this when a real tool fails and you want to know which hop broke.`,
|
|
15365
15687
|
annotations: {
|
|
15366
15688
|
title: "Verify credentials and upstream reachability",
|
|
15367
15689
|
readOnlyHint: true,
|
|
@@ -15369,92 +15691,7 @@ function registerCredentialHealthcheckTool(args) {
|
|
|
15369
15691
|
openWorldHint: true
|
|
15370
15692
|
},
|
|
15371
15693
|
inputSchema: {}
|
|
15372
|
-
}, async () =>
|
|
15373
|
-
let probeStarted = 0;
|
|
15374
|
-
let state;
|
|
15375
|
-
try {
|
|
15376
|
-
state = await resolveCredential();
|
|
15377
|
-
} catch (e) {
|
|
15378
|
-
const classified = classifyThrown?.(e);
|
|
15379
|
-
const result2 = {
|
|
15380
|
-
ok: false,
|
|
15381
|
-
// Still false, and still no source: a classification explains WHY
|
|
15382
|
-
// nothing resolved, it does not invent a credential that did.
|
|
15383
|
-
credential: { source: null, resolved: false },
|
|
15384
|
-
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
15385
|
-
probe: { elapsed_ms: 0 },
|
|
15386
|
-
error: {
|
|
15387
|
-
kind: classified?.kind ?? "no_credential",
|
|
15388
|
-
message: truncateErrorMessage(messageOf(e)),
|
|
15389
|
-
...classified?.detail !== void 0 ? { detail: classified.detail } : {}
|
|
15390
|
-
},
|
|
15391
|
-
// The hint must follow the KIND beside it. Falling back to
|
|
15392
|
-
// `no_credential`'s copy under a classified kind would state a cause
|
|
15393
|
-
// the kind contradicts — the same disagreement this path exists to
|
|
15394
|
-
// remove. So: an inline hint wins; else the classified arm's own
|
|
15395
|
-
// copy (consumer override first); else, for a kind this module has
|
|
15396
|
-
// no copy for, the neutral `unknown` text rather than one that
|
|
15397
|
-
// asserts a cause; else the unclassified `no_credential` default.
|
|
15398
|
-
hint: classified?.hint ?? (isArm(classified?.kind) ? hints?.[classified.kind] ?? credentialHint(classified.kind, prefix, hostLabel, null) : classified !== void 0 ? credentialHint("unknown", prefix, hostLabel, null) : hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null))
|
|
15399
|
-
};
|
|
15400
|
-
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15401
|
-
}
|
|
15402
|
-
const credential = {
|
|
15403
|
-
source: state.source,
|
|
15404
|
-
resolved: state.source !== null,
|
|
15405
|
-
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
15406
|
-
};
|
|
15407
|
-
if (!credential.resolved) {
|
|
15408
|
-
const result2 = {
|
|
15409
|
-
ok: false,
|
|
15410
|
-
credential,
|
|
15411
|
-
probe: { elapsed_ms: 0 },
|
|
15412
|
-
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
15413
|
-
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
15414
|
-
};
|
|
15415
|
-
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
15416
|
-
}
|
|
15417
|
-
let arm = "ok";
|
|
15418
|
-
let error61;
|
|
15419
|
-
let status;
|
|
15420
|
-
let customHint;
|
|
15421
|
-
probeStarted = Date.now();
|
|
15422
|
-
try {
|
|
15423
|
-
await probeFn();
|
|
15424
|
-
} catch (e) {
|
|
15425
|
-
status = statusOf(e);
|
|
15426
|
-
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
15427
|
-
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
15428
|
-
let kind = arm;
|
|
15429
|
-
let detail;
|
|
15430
|
-
const custom2 = classifyThrown?.(e);
|
|
15431
|
-
if (custom2) {
|
|
15432
|
-
kind = custom2.kind;
|
|
15433
|
-
customHint = custom2.hint;
|
|
15434
|
-
detail = custom2.detail;
|
|
15435
|
-
}
|
|
15436
|
-
error61 = {
|
|
15437
|
-
kind,
|
|
15438
|
-
// Redacted AND bounded before it reaches the result: an upstream
|
|
15439
|
-
// failure routinely quotes what it was sent, and a healthcheck is
|
|
15440
|
-
// the tool people paste into a chat when something is broken.
|
|
15441
|
-
message: truncateErrorMessage(messageOf(e)),
|
|
15442
|
-
...detail !== void 0 ? { detail } : {}
|
|
15443
|
-
};
|
|
15444
|
-
}
|
|
15445
|
-
const result = {
|
|
15446
|
-
ok: error61 === void 0,
|
|
15447
|
-
credential,
|
|
15448
|
-
probe: {
|
|
15449
|
-
...probeUrl ? { url: probeUrl } : {},
|
|
15450
|
-
elapsed_ms: Date.now() - probeStarted,
|
|
15451
|
-
...status !== void 0 ? { status } : {}
|
|
15452
|
-
},
|
|
15453
|
-
...error61 ? { error: error61 } : {},
|
|
15454
|
-
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
15455
|
-
};
|
|
15456
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
15457
|
-
});
|
|
15694
|
+
}, async () => runCredentialHealthcheck(args));
|
|
15458
15695
|
}
|
|
15459
15696
|
var CREDENTIAL_ARMS;
|
|
15460
15697
|
var init_healthcheck = __esm({
|
|
@@ -15464,6 +15701,8 @@ var init_healthcheck = __esm({
|
|
|
15464
15701
|
"ok",
|
|
15465
15702
|
"no_credential",
|
|
15466
15703
|
"credential_rejected",
|
|
15704
|
+
"session_expired",
|
|
15705
|
+
"verification_pending",
|
|
15467
15706
|
"timeout",
|
|
15468
15707
|
"http",
|
|
15469
15708
|
"transport",
|
|
@@ -15485,6 +15724,7 @@ __export(fetchproxy_exports, {
|
|
|
15485
15724
|
TokenBucket: () => TokenBucket,
|
|
15486
15725
|
backoffDelayMs: () => backoffDelayMs,
|
|
15487
15726
|
bridgeErrorInfo: () => bridgeErrorInfo,
|
|
15727
|
+
bridgeHealthcheckDescription: () => bridgeHealthcheckDescription,
|
|
15488
15728
|
chunk: () => chunk,
|
|
15489
15729
|
classifyBotWall: () => classifyBotWall,
|
|
15490
15730
|
classifyBridgeError: () => classifyBridgeError,
|
|
@@ -15497,9 +15737,11 @@ __export(fetchproxy_exports, {
|
|
|
15497
15737
|
extractImgTags: () => extractImgTags,
|
|
15498
15738
|
lastPathSegment: () => lastPathSegment,
|
|
15499
15739
|
mapWithConcurrency: () => mapWithConcurrency,
|
|
15740
|
+
registerAdaptiveHealthcheckTool: () => registerAdaptiveHealthcheckTool,
|
|
15500
15741
|
registerBridgeHealthcheckTool: () => registerBridgeHealthcheckTool,
|
|
15501
15742
|
registerCredentialHealthcheckTool: () => registerCredentialHealthcheckTool,
|
|
15502
15743
|
retryOnceOnTimeout: () => retryOnceOnTimeout,
|
|
15744
|
+
runBridgeHealthcheck: () => runBridgeHealthcheck,
|
|
15503
15745
|
sleep: () => sleep,
|
|
15504
15746
|
withDeadline: () => withDeadline
|
|
15505
15747
|
});
|
|
@@ -15535,9 +15777,9 @@ function createFetchproxyTransport(opts) {
|
|
|
15535
15777
|
async start() {
|
|
15536
15778
|
await server.listen();
|
|
15537
15779
|
if (logListening) {
|
|
15538
|
-
console.error(`[${serverOpts.serverName}:bridge]
|
|
15780
|
+
console.error(`[${serverOpts.serverName}:bridge] ready \u2014 127.0.0.1:${server.bridgeHealth().port} binds on the first request (version=${serverOpts.version})`);
|
|
15539
15781
|
} else if (debug) {
|
|
15540
|
-
console.error(`[${serverOpts.serverName}:bridge]
|
|
15782
|
+
console.error(`[${serverOpts.serverName}:bridge] ready \u2014 binds on the first request (version=${serverOpts.version})`);
|
|
15541
15783
|
}
|
|
15542
15784
|
},
|
|
15543
15785
|
async close() {
|
|
@@ -15716,13 +15958,122 @@ function projectBridgeStatus(health) {
|
|
|
15716
15958
|
} : {}
|
|
15717
15959
|
};
|
|
15718
15960
|
}
|
|
15719
|
-
function
|
|
15720
|
-
|
|
15721
|
-
|
|
15961
|
+
function probeDisplayUrl(hostLabel, probePath) {
|
|
15962
|
+
return `https://${hostLabel}${probePath.startsWith("/") ? "" : "/"}${probePath}`;
|
|
15963
|
+
}
|
|
15964
|
+
function bridgeHealthcheckDescription(hostLabel, probePath) {
|
|
15965
|
+
return `Round-trips a small public ${hostLabel} URL (${probePath}) through the fetchproxy bridge and returns diagnostics: the bridge's role (host/peer/null), port, version, the extension link (linked / pair pending / not attached / never answered), the elapsed round-trip time, and a plain-English hint distinguishing 'bridge never came up' from 'extension not connected' from 'real ${hostLabel}-side problem'. Read-only, no auth required.`;
|
|
15966
|
+
}
|
|
15967
|
+
async function runBridgeHealthcheck(args) {
|
|
15968
|
+
const { prefix, probePath, hostLabel, probeFn, classifyThrown, hints, path } = args;
|
|
15969
|
+
const probeUrl = probeDisplayUrl(hostLabel, probePath);
|
|
15722
15970
|
const resolveTransport = () => typeof args.transport === "function" ? args.transport() : args.transport;
|
|
15723
|
-
|
|
15971
|
+
let probeBody = "";
|
|
15972
|
+
let thrown;
|
|
15973
|
+
const wrappedProbe = async (p) => {
|
|
15974
|
+
try {
|
|
15975
|
+
probeBody = await probeFn(p);
|
|
15976
|
+
return probeBody;
|
|
15977
|
+
} catch (e) {
|
|
15978
|
+
thrown = e;
|
|
15979
|
+
throw e;
|
|
15980
|
+
}
|
|
15981
|
+
};
|
|
15982
|
+
let ok;
|
|
15983
|
+
let elapsedMs;
|
|
15984
|
+
let bridge;
|
|
15985
|
+
let rawError;
|
|
15986
|
+
let pathNow;
|
|
15987
|
+
if (path) {
|
|
15988
|
+
const start = Date.now();
|
|
15989
|
+
try {
|
|
15990
|
+
await wrappedProbe(probePath);
|
|
15991
|
+
ok = true;
|
|
15992
|
+
} catch (e) {
|
|
15993
|
+
ok = false;
|
|
15994
|
+
rawError = { kind: classifyBridgeError(e), message: truncateErrorMessage(messageOf(e)) };
|
|
15995
|
+
}
|
|
15996
|
+
elapsedMs = Date.now() - start;
|
|
15997
|
+
pathNow = path();
|
|
15998
|
+
const transport2 = resolveTransport();
|
|
15999
|
+
bridge = transport2 ? projectBridgeStatus(transport2.status()) : void 0;
|
|
16000
|
+
} else {
|
|
16001
|
+
const transport2 = resolveTransport();
|
|
16002
|
+
if (!transport2) {
|
|
16003
|
+
throw new Error("bridge healthcheck: transport() returned nothing and no `path` was supplied \u2014 a bridge-only healthcheck needs its bridge.");
|
|
16004
|
+
}
|
|
16005
|
+
const probeResult = await transport2.runProbe(wrappedProbe, probePath);
|
|
16006
|
+
ok = probeResult.ok;
|
|
16007
|
+
elapsedMs = probeResult.elapsed_ms;
|
|
16008
|
+
bridge = {
|
|
16009
|
+
...probeResult.bridge,
|
|
16010
|
+
last_extension_message_at: transport2.status().lastExtensionMessageAt
|
|
16011
|
+
};
|
|
16012
|
+
rawError = probeResult.error;
|
|
16013
|
+
}
|
|
16014
|
+
const probe = ok ? { url: probeUrl, elapsed_ms: elapsedMs, status: 200, body_length: probeBody.length } : { url: probeUrl, elapsed_ms: elapsedMs };
|
|
16015
|
+
let error61;
|
|
16016
|
+
let bridgeHint;
|
|
16017
|
+
let customHint;
|
|
16018
|
+
let customDetail;
|
|
16019
|
+
if (rawError) {
|
|
16020
|
+
let kind = rawError.kind === "other" ? "unknown" : rawError.kind;
|
|
16021
|
+
if (thrown instanceof FetchproxySessionNotReadyError) {
|
|
16022
|
+
kind = "session_not_ready";
|
|
16023
|
+
bridgeHint = thrown.hint;
|
|
16024
|
+
} else if (thrown instanceof FetchproxyBridgeDownError) {
|
|
16025
|
+
bridgeHint = thrown.hint;
|
|
16026
|
+
}
|
|
16027
|
+
if (thrown !== void 0 && classifyThrown) {
|
|
16028
|
+
const custom2 = classifyThrown(thrown);
|
|
16029
|
+
if (custom2) {
|
|
16030
|
+
kind = custom2.kind;
|
|
16031
|
+
customHint = custom2.hint;
|
|
16032
|
+
customDetail = custom2.detail;
|
|
16033
|
+
}
|
|
16034
|
+
}
|
|
16035
|
+
error61 = {
|
|
16036
|
+
kind,
|
|
16037
|
+
message: rawError.message,
|
|
16038
|
+
...bridgeHint !== void 0 ? { bridge_hint: bridgeHint } : {},
|
|
16039
|
+
...customDetail !== void 0 ? { detail: customDetail } : {}
|
|
16040
|
+
};
|
|
16041
|
+
}
|
|
16042
|
+
const direct = pathNow ? pathNow.transport === "direct" : bridge === void 0;
|
|
16043
|
+
const arm = ok ? "ok" : error61?.kind === "session_not_ready" ? "session_not_ready" : error61?.kind === "bridge_down" ? "bridge_down" : direct ? "direct" : bridge === void 0 || bridge.role === null ? "no_role" : error61?.kind === "timeout" ? "timeout" : error61?.kind === "protocol" || error61?.kind === "http" ? "protocol" : "unknown";
|
|
16044
|
+
const defaultHint = healthcheckHint({
|
|
16045
|
+
ok,
|
|
16046
|
+
role: bridge?.role ?? null,
|
|
16047
|
+
// The real configured port from bridgeHealth(), never a literal 37149.
|
|
16048
|
+
port: bridge?.port ?? null,
|
|
16049
|
+
hostLabel,
|
|
16050
|
+
prefix,
|
|
16051
|
+
probePath,
|
|
16052
|
+
errorKind: error61?.kind,
|
|
16053
|
+
bridgeHint: error61?.kind === "bridge_down" ? bridgeHint : void 0,
|
|
16054
|
+
session: {
|
|
16055
|
+
...bridge?.session_state !== void 0 ? { state: bridge.session_state } : {},
|
|
16056
|
+
pairCode: bridge?.pending_pair_code ?? (thrown instanceof FetchproxySessionNotReadyError ? thrown.pairCode : null)
|
|
16057
|
+
},
|
|
16058
|
+
direct
|
|
16059
|
+
});
|
|
16060
|
+
const result = {
|
|
16061
|
+
ok,
|
|
16062
|
+
...bridge ? { bridge } : {},
|
|
16063
|
+
...pathNow ? { transport: pathNow } : {},
|
|
16064
|
+
probe,
|
|
16065
|
+
...error61 ? { error: error61 } : {},
|
|
16066
|
+
// Precedence: classifyThrown's hint > per-arm override > default ladder.
|
|
16067
|
+
hint: customHint ?? hints?.[arm] ?? defaultHint
|
|
16068
|
+
};
|
|
16069
|
+
return {
|
|
16070
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
16071
|
+
};
|
|
16072
|
+
}
|
|
16073
|
+
function registerBridgeHealthcheckTool(args) {
|
|
16074
|
+
args.server.registerTool(`${args.prefix}_healthcheck`, {
|
|
15724
16075
|
title: "Verify the fetchproxy bridge end-to-end",
|
|
15725
|
-
description:
|
|
16076
|
+
description: `${bridgeHealthcheckDescription(args.hostLabel, args.probePath)} Call this when a real tool fails and you want to know which hop broke.`,
|
|
15726
16077
|
annotations: {
|
|
15727
16078
|
title: "Verify the fetchproxy bridge end-to-end",
|
|
15728
16079
|
readOnlyHint: true,
|
|
@@ -15730,109 +16081,21 @@ function registerBridgeHealthcheckTool(args) {
|
|
|
15730
16081
|
openWorldHint: true
|
|
15731
16082
|
},
|
|
15732
16083
|
inputSchema: {}
|
|
15733
|
-
}, async () =>
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
let rawError;
|
|
15749
|
-
let pathNow;
|
|
15750
|
-
if (path) {
|
|
15751
|
-
const start = Date.now();
|
|
15752
|
-
try {
|
|
15753
|
-
await wrappedProbe(probePath);
|
|
15754
|
-
ok = true;
|
|
15755
|
-
} catch (e) {
|
|
15756
|
-
ok = false;
|
|
15757
|
-
rawError = { kind: classifyBridgeError(e), message: truncateErrorMessage(messageOf(e)) };
|
|
15758
|
-
}
|
|
15759
|
-
elapsedMs = Date.now() - start;
|
|
15760
|
-
pathNow = path();
|
|
15761
|
-
const transport2 = resolveTransport();
|
|
15762
|
-
bridge = transport2 ? projectBridgeStatus(transport2.status()) : void 0;
|
|
15763
|
-
} else {
|
|
15764
|
-
const transport2 = resolveTransport();
|
|
15765
|
-
if (!transport2) {
|
|
15766
|
-
throw new Error("registerBridgeHealthcheckTool: transport() returned nothing and no `path` was supplied \u2014 a bridge-only healthcheck needs its bridge.");
|
|
15767
|
-
}
|
|
15768
|
-
const probeResult = await transport2.runProbe(wrappedProbe, probePath);
|
|
15769
|
-
ok = probeResult.ok;
|
|
15770
|
-
elapsedMs = probeResult.elapsed_ms;
|
|
15771
|
-
bridge = {
|
|
15772
|
-
...probeResult.bridge,
|
|
15773
|
-
last_extension_message_at: transport2.status().lastExtensionMessageAt
|
|
15774
|
-
};
|
|
15775
|
-
rawError = probeResult.error;
|
|
15776
|
-
}
|
|
15777
|
-
const probe = ok ? { url: probeUrl, elapsed_ms: elapsedMs, status: 200, body_length: probeBody.length } : { url: probeUrl, elapsed_ms: elapsedMs };
|
|
15778
|
-
let error61;
|
|
15779
|
-
let bridgeHint;
|
|
15780
|
-
let customHint;
|
|
15781
|
-
let customDetail;
|
|
15782
|
-
if (rawError) {
|
|
15783
|
-
let kind = rawError.kind === "other" ? "unknown" : rawError.kind;
|
|
15784
|
-
if (thrown instanceof FetchproxySessionNotReadyError) {
|
|
15785
|
-
kind = "session_not_ready";
|
|
15786
|
-
bridgeHint = thrown.hint;
|
|
15787
|
-
} else if (thrown instanceof FetchproxyBridgeDownError) {
|
|
15788
|
-
bridgeHint = thrown.hint;
|
|
15789
|
-
}
|
|
15790
|
-
if (thrown !== void 0 && classifyThrown) {
|
|
15791
|
-
const custom2 = classifyThrown(thrown);
|
|
15792
|
-
if (custom2) {
|
|
15793
|
-
kind = custom2.kind;
|
|
15794
|
-
customHint = custom2.hint;
|
|
15795
|
-
customDetail = custom2.detail;
|
|
15796
|
-
}
|
|
15797
|
-
}
|
|
15798
|
-
error61 = {
|
|
15799
|
-
kind,
|
|
15800
|
-
message: rawError.message,
|
|
15801
|
-
...bridgeHint !== void 0 ? { bridge_hint: bridgeHint } : {},
|
|
15802
|
-
...customDetail !== void 0 ? { detail: customDetail } : {}
|
|
15803
|
-
};
|
|
15804
|
-
}
|
|
15805
|
-
const direct = pathNow ? pathNow.transport === "direct" : bridge === void 0;
|
|
15806
|
-
const arm = ok ? "ok" : error61?.kind === "session_not_ready" ? "session_not_ready" : error61?.kind === "bridge_down" ? "bridge_down" : direct ? "direct" : bridge === void 0 || bridge.role === null ? "no_role" : error61?.kind === "timeout" ? "timeout" : error61?.kind === "protocol" || error61?.kind === "http" ? "protocol" : "unknown";
|
|
15807
|
-
const defaultHint = healthcheckHint({
|
|
15808
|
-
ok,
|
|
15809
|
-
role: bridge?.role ?? null,
|
|
15810
|
-
// The real configured port from bridgeHealth(), never a literal 37149.
|
|
15811
|
-
port: bridge?.port ?? null,
|
|
15812
|
-
hostLabel,
|
|
15813
|
-
prefix,
|
|
15814
|
-
probePath,
|
|
15815
|
-
errorKind: error61?.kind,
|
|
15816
|
-
bridgeHint: error61?.kind === "bridge_down" ? bridgeHint : void 0,
|
|
15817
|
-
session: {
|
|
15818
|
-
...bridge?.session_state !== void 0 ? { state: bridge.session_state } : {},
|
|
15819
|
-
pairCode: bridge?.pending_pair_code ?? (thrown instanceof FetchproxySessionNotReadyError ? thrown.pairCode : null)
|
|
15820
|
-
},
|
|
15821
|
-
direct
|
|
15822
|
-
});
|
|
15823
|
-
const result = {
|
|
15824
|
-
ok,
|
|
15825
|
-
...bridge ? { bridge } : {},
|
|
15826
|
-
...pathNow ? { transport: pathNow } : {},
|
|
15827
|
-
probe,
|
|
15828
|
-
...error61 ? { error: error61 } : {},
|
|
15829
|
-
// Precedence: classifyThrown's hint > per-arm override > default ladder.
|
|
15830
|
-
hint: customHint ?? hints?.[arm] ?? defaultHint
|
|
15831
|
-
};
|
|
15832
|
-
return {
|
|
15833
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
15834
|
-
};
|
|
15835
|
-
});
|
|
16084
|
+
}, async () => runBridgeHealthcheck(args));
|
|
16085
|
+
}
|
|
16086
|
+
function registerAdaptiveHealthcheckTool(args) {
|
|
16087
|
+
const { server, prefix, hostLabel, usingBridge, bridge, credential } = args;
|
|
16088
|
+
server.registerTool(`${prefix}_healthcheck`, {
|
|
16089
|
+
title: "Verify this server can reach its upstream",
|
|
16090
|
+
description: `Reports which hop is broken when a real tool fails, for whichever path this server is actually using. Relaying through your signed-in browser tab: ${bridgeHealthcheckDescription(hostLabel, bridge.probePath)} Signing in server-side with configured credentials: ${credentialHealthcheckDescription(hostLabel)}`,
|
|
16091
|
+
annotations: {
|
|
16092
|
+
title: "Verify this server can reach its upstream",
|
|
16093
|
+
readOnlyHint: true,
|
|
16094
|
+
idempotentHint: true,
|
|
16095
|
+
openWorldHint: true
|
|
16096
|
+
},
|
|
16097
|
+
inputSchema: {}
|
|
16098
|
+
}, async () => usingBridge() ? runBridgeHealthcheck({ server, prefix, hostLabel, ...bridge }) : runCredentialHealthcheck({ server, prefix, hostLabel, ...credential }));
|
|
15836
16099
|
}
|
|
15837
16100
|
var PLACEHOLDER_RE2;
|
|
15838
16101
|
var init_fetchproxy = __esm({
|
|
@@ -15841,6 +16104,7 @@ var init_fetchproxy = __esm({
|
|
|
15841
16104
|
init_errors();
|
|
15842
16105
|
init_dist2();
|
|
15843
16106
|
init_healthcheck();
|
|
16107
|
+
init_healthcheck();
|
|
15844
16108
|
PLACEHOLDER_RE2 = /^\$\{[^}]*\}$/;
|
|
15845
16109
|
}
|
|
15846
16110
|
});
|
|
@@ -44754,7 +45018,7 @@ var pageSchema = {
|
|
|
44754
45018
|
init_errors();
|
|
44755
45019
|
|
|
44756
45020
|
// src/version.ts
|
|
44757
|
-
var VERSION = "0.3.
|
|
45021
|
+
var VERSION = "0.3.1";
|
|
44758
45022
|
|
|
44759
45023
|
// src/client.ts
|
|
44760
45024
|
import { dirname, join } from "path";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.3.
|
|
1
|
+
export const VERSION = '0.3.1'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/eventbrite-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"mcpName": "io.github.chrischall/eventbrite-mcp",
|
|
5
5
|
"description": "Eventbrite MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45,18 +45,18 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^0.
|
|
49
|
-
"@fetchproxy/server": "^2.
|
|
50
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
48
|
+
"@chrischall/mcp-utils": "^0.26.1",
|
|
49
|
+
"@fetchproxy/server": "^2.10.0",
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
51
51
|
"dotenv": "^17.4.0",
|
|
52
|
-
"zod": "^4.4
|
|
52
|
+
"zod": "^4.5.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^26.0.0",
|
|
56
|
-
"@vitest/coverage-v8": "^
|
|
56
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
57
57
|
"esbuild": "^0.28.0",
|
|
58
58
|
"typescript": "^7.0.2",
|
|
59
|
-
"vitest": "^
|
|
59
|
+
"vitest": "^5.0.0"
|
|
60
60
|
},
|
|
61
61
|
"allowScripts": {
|
|
62
62
|
"esbuild@0.28.1": true
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/eventbrite-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/eventbrite-mcp",
|
|
14
|
-
"version": "0.3.
|
|
14
|
+
"version": "0.3.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|