@chrischall/eventbrite-mcp 0.1.0 → 0.1.2
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/README.md +0 -8
- package/dist/bundle.js +541 -81
- package/dist/client.js +4 -4
- package/dist/discovery.js +2 -2
- package/dist/index.js +1 -1
- package/dist/tools/account.js +1 -1
- package/dist/tools/discovery.js +3 -3
- package/dist/version.js +1 -1
- package/package.json +5 -15
- package/server.json +2 -2
- package/dist/eventbrite-auth.js +0 -18
|
@@ -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.1.
|
|
10
|
+
"version": "0.1.2"
|
|
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.1.
|
|
18
|
+
"version": "0.1.2",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -79,20 +79,12 @@ Search flow: `eb_resolve_place {location: "Charlotte, NC"}` →
|
|
|
79
79
|
`eb_resolve_place` also accepts a raw slug (`nc--charlotte`). A bare city with
|
|
80
80
|
no state or country is rejected rather than guessed.
|
|
81
81
|
|
|
82
|
-
## Hosted connector
|
|
83
|
-
|
|
84
|
-
`src/worker.ts` deploys the token-API tools as a Cloudflare Worker remote
|
|
85
|
-
connector for claude.ai (OAuth login collects your Eventbrite token). The
|
|
86
|
-
discovery tools are **excluded** there — the browser bridge doesn't exist in a
|
|
87
|
-
Worker. See `docs/DEPLOY-CONNECTOR.md`.
|
|
88
|
-
|
|
89
82
|
## Development
|
|
90
83
|
|
|
91
84
|
```sh
|
|
92
85
|
npm install
|
|
93
86
|
npm test # node suite
|
|
94
87
|
npm run build # tsc + esbuild bundle
|
|
95
|
-
npm run worker:test
|
|
96
88
|
```
|
|
97
89
|
|
|
98
90
|
API shape notes (captured + verified): `docs/EVENTBRITE-API.md`. A
|
package/dist/bundle.js
CHANGED
|
@@ -3659,7 +3659,12 @@ var require_fast_uri = __commonJS({
|
|
|
3659
3659
|
}
|
|
3660
3660
|
function resolve(baseURI, relativeURI, options) {
|
|
3661
3661
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3662
|
-
const
|
|
3662
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3663
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3664
|
+
if (baseMalformed || relativeMalformed) {
|
|
3665
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3666
|
+
}
|
|
3667
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3663
3668
|
schemelessOptions.skipEscape = true;
|
|
3664
3669
|
return serialize(resolved, schemelessOptions);
|
|
3665
3670
|
}
|
|
@@ -3785,6 +3790,7 @@ var require_fast_uri = __commonJS({
|
|
|
3785
3790
|
}
|
|
3786
3791
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3787
3792
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3793
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3788
3794
|
function getParseError(parsed, matches) {
|
|
3789
3795
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3790
3796
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3819,6 +3825,20 @@ var require_fast_uri = __commonJS({
|
|
|
3819
3825
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3820
3826
|
malformedAuthorityOrPort = true;
|
|
3821
3827
|
}
|
|
3828
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3829
|
+
if (introducerMatch !== null) {
|
|
3830
|
+
const region = introducerMatch[1];
|
|
3831
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3832
|
+
if (normalizedRegion.length >= 2) {
|
|
3833
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3834
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3835
|
+
malformedAuthorityOrPort = true;
|
|
3836
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3837
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3838
|
+
malformedAuthorityOrPort = true;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3822
3842
|
const matches = uri.match(URI_PARSE);
|
|
3823
3843
|
if (matches) {
|
|
3824
3844
|
parsed.scheme = matches[1];
|
|
@@ -6985,10 +7005,17 @@ var init_errors = __esm({
|
|
|
6985
7005
|
});
|
|
6986
7006
|
|
|
6987
7007
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
7008
|
+
function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub) {
|
|
7009
|
+
const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
|
|
7010
|
+
out.set(mcpHelloNonce, 0);
|
|
7011
|
+
out.set(extHelloNonce, mcpHelloNonce.length);
|
|
7012
|
+
out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
|
|
7013
|
+
return out;
|
|
7014
|
+
}
|
|
6988
7015
|
var PROTOCOL_VERSION, HKDF_SESSION_INFO, KNOWN_CAPABILITIES;
|
|
6989
7016
|
var init_frames = __esm({
|
|
6990
7017
|
"node_modules/@fetchproxy/protocol/dist/frames.js"() {
|
|
6991
|
-
PROTOCOL_VERSION =
|
|
7018
|
+
PROTOCOL_VERSION = 3;
|
|
6992
7019
|
HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
6993
7020
|
KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
6994
7021
|
"fetch",
|
|
@@ -7000,7 +7027,8 @@ var init_frames = __esm({
|
|
|
7000
7027
|
"read_indexed_db",
|
|
7001
7028
|
"read_dom",
|
|
7002
7029
|
"download",
|
|
7003
|
-
"graphql"
|
|
7030
|
+
"graphql",
|
|
7031
|
+
"write_cookies"
|
|
7004
7032
|
]);
|
|
7005
7033
|
}
|
|
7006
7034
|
});
|
|
@@ -7110,6 +7138,15 @@ function assertHttpUrl(x, label) {
|
|
|
7110
7138
|
throw new ProtocolError(`${label}: must be http(s), got ${u.protocol}`);
|
|
7111
7139
|
}
|
|
7112
7140
|
}
|
|
7141
|
+
function assertCookiePath(x, label) {
|
|
7142
|
+
assertString(x, label);
|
|
7143
|
+
if (!x.startsWith("/") || x.startsWith("//")) {
|
|
7144
|
+
throw new ProtocolError(`${label}: must be an absolute path like "/campus"`);
|
|
7145
|
+
}
|
|
7146
|
+
if (x.includes("?") || x.includes("#") || x.includes("\\")) {
|
|
7147
|
+
throw new ProtocolError(`${label}: must not contain a query, fragment, or backslash`);
|
|
7148
|
+
}
|
|
7149
|
+
}
|
|
7113
7150
|
function assertHttpsOriginOnly(x, label) {
|
|
7114
7151
|
assertString(x, label);
|
|
7115
7152
|
let u;
|
|
@@ -7549,13 +7586,43 @@ function validateInnerRequest(raw) {
|
|
|
7549
7586
|
}
|
|
7550
7587
|
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
7551
7588
|
assertNonEmptyKeyArray(raw.init.keys, "inner.init.keys");
|
|
7589
|
+
if (raw.init.path !== void 0)
|
|
7590
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
7552
7591
|
for (const k of Object.keys(raw.init)) {
|
|
7553
|
-
if (k !== "origin" && k !== "keys") {
|
|
7592
|
+
if (k !== "origin" && k !== "keys" && k !== "path") {
|
|
7554
7593
|
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_cookies`);
|
|
7555
7594
|
}
|
|
7556
7595
|
}
|
|
7557
7596
|
return raw;
|
|
7558
7597
|
}
|
|
7598
|
+
if (raw.op === "write_cookies") {
|
|
7599
|
+
assertObject(raw.init, "inner.init");
|
|
7600
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
7601
|
+
if (!Array.isArray(raw.init.cookies) || raw.init.cookies.length === 0) {
|
|
7602
|
+
throw new ProtocolError("inner.init.cookies: must be a non-empty array");
|
|
7603
|
+
}
|
|
7604
|
+
for (const [i, entry] of raw.init.cookies.entries()) {
|
|
7605
|
+
assertObject(entry, `inner.init.cookies[${i}]`);
|
|
7606
|
+
assertString(entry.name, `inner.init.cookies[${i}].name`);
|
|
7607
|
+
if (!SCOPE_KEY_RE.test(entry.name)) {
|
|
7608
|
+
throw new ProtocolError(`inner.init.cookies[${i}].name: invalid key ${JSON.stringify(entry.name)}`);
|
|
7609
|
+
}
|
|
7610
|
+
assertString(entry.value, `inner.init.cookies[${i}].value`);
|
|
7611
|
+
for (const k of Object.keys(entry)) {
|
|
7612
|
+
if (k !== "name" && k !== "value") {
|
|
7613
|
+
throw new ProtocolError(`inner.init.cookies[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
if (raw.init.path !== void 0)
|
|
7618
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
7619
|
+
for (const k of Object.keys(raw.init)) {
|
|
7620
|
+
if (k !== "origin" && k !== "cookies" && k !== "path") {
|
|
7621
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on write_cookies`);
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
return raw;
|
|
7625
|
+
}
|
|
7559
7626
|
if (raw.op === "read_local_storage" || raw.op === "read_session_storage") {
|
|
7560
7627
|
assertObject(raw.init, "inner.init");
|
|
7561
7628
|
if (raw.init.origin === void 0) {
|
|
@@ -7742,7 +7809,7 @@ function validateInnerRequest(raw) {
|
|
|
7742
7809
|
}
|
|
7743
7810
|
return raw;
|
|
7744
7811
|
}
|
|
7745
|
-
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query"; got ${JSON.stringify(raw.op)}`);
|
|
7812
|
+
throw new ProtocolError(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
7746
7813
|
}
|
|
7747
7814
|
function assertNonEmptyKeyArray(value, label) {
|
|
7748
7815
|
if (!Array.isArray(value)) {
|
|
@@ -7799,6 +7866,18 @@ function validateInnerResponse(raw) {
|
|
|
7799
7866
|
}
|
|
7800
7867
|
return raw;
|
|
7801
7868
|
}
|
|
7869
|
+
if (op === "write_cookies") {
|
|
7870
|
+
if (raw.written === void 0) {
|
|
7871
|
+
throw new ProtocolError("inner.written: missing on write_cookies response");
|
|
7872
|
+
}
|
|
7873
|
+
if (!Array.isArray(raw.written)) {
|
|
7874
|
+
throw new ProtocolError("inner.written: must be an array");
|
|
7875
|
+
}
|
|
7876
|
+
for (const [i, name] of raw.written.entries()) {
|
|
7877
|
+
assertString(name, `inner.written[${i}]`);
|
|
7878
|
+
}
|
|
7879
|
+
return raw;
|
|
7880
|
+
}
|
|
7802
7881
|
if (op === "read_local_storage" || op === "read_session_storage") {
|
|
7803
7882
|
if (raw.values === void 0) {
|
|
7804
7883
|
throw new ProtocolError(`inner.values: missing on ${String(op)} response`);
|
|
@@ -12037,6 +12116,158 @@ var init_session_ready = __esm({
|
|
|
12037
12116
|
}
|
|
12038
12117
|
});
|
|
12039
12118
|
|
|
12119
|
+
// node_modules/@fetchproxy/server/dist/identity.js
|
|
12120
|
+
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
12121
|
+
import { join as join2 } from "node:path";
|
|
12122
|
+
import { homedir } from "node:os";
|
|
12123
|
+
function defaultIdentityDir() {
|
|
12124
|
+
return join2(homedir(), ".fetchproxy", "identity");
|
|
12125
|
+
}
|
|
12126
|
+
function safeIdentityFileBase(serverName) {
|
|
12127
|
+
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
12128
|
+
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
12129
|
+
}
|
|
12130
|
+
return serverName.replace(/\//g, "_");
|
|
12131
|
+
}
|
|
12132
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
12133
|
+
const safeFile = safeIdentityFileBase(serverName);
|
|
12134
|
+
const path = join2(dir, `${safeFile}.json`);
|
|
12135
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
12136
|
+
try {
|
|
12137
|
+
const raw = await readFile(path, "utf8");
|
|
12138
|
+
const j2 = JSON.parse(raw);
|
|
12139
|
+
return {
|
|
12140
|
+
x25519Priv: fromB64(j2.x25519Priv),
|
|
12141
|
+
x25519Pub: fromB64(j2.x25519Pub),
|
|
12142
|
+
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
12143
|
+
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
12144
|
+
createdAt: j2.createdAt
|
|
12145
|
+
};
|
|
12146
|
+
} catch (e) {
|
|
12147
|
+
if (e.code !== "ENOENT")
|
|
12148
|
+
throw e;
|
|
12149
|
+
}
|
|
12150
|
+
const x = await generateX25519();
|
|
12151
|
+
const ed = await generateEd25519();
|
|
12152
|
+
const id = {
|
|
12153
|
+
x25519Priv: x.privateKey,
|
|
12154
|
+
x25519Pub: x.publicKey,
|
|
12155
|
+
ed25519Priv: ed.privateKey,
|
|
12156
|
+
ed25519Pub: ed.publicKey,
|
|
12157
|
+
createdAt: Date.now()
|
|
12158
|
+
};
|
|
12159
|
+
const j = {
|
|
12160
|
+
x25519Priv: toB64(id.x25519Priv),
|
|
12161
|
+
x25519Pub: toB64(id.x25519Pub),
|
|
12162
|
+
ed25519Priv: toB64(id.ed25519Priv),
|
|
12163
|
+
ed25519Pub: toB64(id.ed25519Pub),
|
|
12164
|
+
createdAt: id.createdAt
|
|
12165
|
+
};
|
|
12166
|
+
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
12167
|
+
await chmod(path, 384);
|
|
12168
|
+
return id;
|
|
12169
|
+
}
|
|
12170
|
+
var SAFE_PLAIN, SAFE_SCOPED;
|
|
12171
|
+
var init_identity = __esm({
|
|
12172
|
+
"node_modules/@fetchproxy/server/dist/identity.js"() {
|
|
12173
|
+
init_dist();
|
|
12174
|
+
SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
12175
|
+
SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
12176
|
+
}
|
|
12177
|
+
});
|
|
12178
|
+
|
|
12179
|
+
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
12180
|
+
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
12181
|
+
import { join as join3 } from "node:path";
|
|
12182
|
+
function fileExtensionTrust(args) {
|
|
12183
|
+
return {
|
|
12184
|
+
allowNew: args.allowNew,
|
|
12185
|
+
location: extensionTrustPath(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
12186
|
+
read: () => readExtensionPin(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
12187
|
+
write: (pin) => writeExtensionPin(args.serverName, pin, args.dir ?? defaultIdentityDir())
|
|
12188
|
+
};
|
|
12189
|
+
}
|
|
12190
|
+
function allowNewExtensionIdentity(explicit, env = process.env) {
|
|
12191
|
+
if (explicit !== void 0)
|
|
12192
|
+
return explicit;
|
|
12193
|
+
return env[TRUST_NEW_EXTENSION_ENV] === "1";
|
|
12194
|
+
}
|
|
12195
|
+
function decideExtensionTrust(args) {
|
|
12196
|
+
const { pin, hello, allowNew, serverName } = args;
|
|
12197
|
+
if (!pin)
|
|
12198
|
+
return { decision: "first-use" };
|
|
12199
|
+
if (pin.identityX25519Pub === hello.identityX25519Pub && pin.identityEd25519Pub === hello.identityEd25519Pub) {
|
|
12200
|
+
return { decision: "pinned" };
|
|
12201
|
+
}
|
|
12202
|
+
const trustPath = args.location ?? extensionTrustPathHint(serverName);
|
|
12203
|
+
if (allowNew) {
|
|
12204
|
+
return {
|
|
12205
|
+
decision: "replace",
|
|
12206
|
+
message: `[fetchproxy] ${serverName}: accepting a NEW extension identity because ${TRUST_NEW_EXTENSION_ENV}=1 \u2014 re-pinning. Unset it once the browser you expect is connected.`
|
|
12207
|
+
};
|
|
12208
|
+
}
|
|
12209
|
+
return {
|
|
12210
|
+
decision: "refused",
|
|
12211
|
+
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.`
|
|
12212
|
+
};
|
|
12213
|
+
}
|
|
12214
|
+
function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
|
|
12215
|
+
return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
12216
|
+
}
|
|
12217
|
+
function extensionTrustPathHint(serverName) {
|
|
12218
|
+
try {
|
|
12219
|
+
return extensionTrustPath(serverName);
|
|
12220
|
+
} catch {
|
|
12221
|
+
return join3(defaultIdentityDir(), "<server-name>.extension-trust.json");
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
function isPin(x) {
|
|
12225
|
+
if (!x || typeof x !== "object")
|
|
12226
|
+
return false;
|
|
12227
|
+
const r = x;
|
|
12228
|
+
return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
|
|
12229
|
+
}
|
|
12230
|
+
async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
|
|
12231
|
+
const path = extensionTrustPath(serverName, dir);
|
|
12232
|
+
let raw;
|
|
12233
|
+
try {
|
|
12234
|
+
raw = await readFile2(path, "utf8");
|
|
12235
|
+
} catch (e) {
|
|
12236
|
+
if (e.code === "ENOENT")
|
|
12237
|
+
return null;
|
|
12238
|
+
throw e;
|
|
12239
|
+
}
|
|
12240
|
+
let parsed;
|
|
12241
|
+
try {
|
|
12242
|
+
parsed = JSON.parse(raw);
|
|
12243
|
+
} catch {
|
|
12244
|
+
throw new Error(`unreadable extension pin at ${path} (not JSON) \u2014 delete it to re-pair`);
|
|
12245
|
+
}
|
|
12246
|
+
if (!isPin(parsed)) {
|
|
12247
|
+
throw new Error(`unreadable extension pin at ${path} (wrong shape) \u2014 delete it to re-pair`);
|
|
12248
|
+
}
|
|
12249
|
+
return {
|
|
12250
|
+
identityX25519Pub: parsed.identityX25519Pub,
|
|
12251
|
+
identityEd25519Pub: parsed.identityEd25519Pub,
|
|
12252
|
+
pinnedAt: parsed.pinnedAt
|
|
12253
|
+
};
|
|
12254
|
+
}
|
|
12255
|
+
async function writeExtensionPin(serverName, pin, dir = defaultIdentityDir()) {
|
|
12256
|
+
const path = extensionTrustPath(serverName, dir);
|
|
12257
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
12258
|
+
const tmp = `${path}.tmp`;
|
|
12259
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
12260
|
+
await chmod2(tmp, 384);
|
|
12261
|
+
await rename(tmp, path);
|
|
12262
|
+
}
|
|
12263
|
+
var TRUST_NEW_EXTENSION_ENV;
|
|
12264
|
+
var init_extension_trust = __esm({
|
|
12265
|
+
"node_modules/@fetchproxy/server/dist/extension-trust.js"() {
|
|
12266
|
+
init_identity();
|
|
12267
|
+
TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
|
|
12268
|
+
}
|
|
12269
|
+
});
|
|
12270
|
+
|
|
12040
12271
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
12041
12272
|
async function startHost(opts) {
|
|
12042
12273
|
const wss = new import_websocket_server.default({
|
|
@@ -12088,9 +12319,12 @@ async function startHost(opts) {
|
|
|
12088
12319
|
}
|
|
12089
12320
|
resetSessionPromise();
|
|
12090
12321
|
let extensionHello = null;
|
|
12322
|
+
let extensionClaim = null;
|
|
12091
12323
|
wss.on("connection", (ws) => {
|
|
12092
12324
|
let identified = null;
|
|
12093
12325
|
let peerMcpId = null;
|
|
12326
|
+
let closed = false;
|
|
12327
|
+
let pinOnReady = false;
|
|
12094
12328
|
ws.on("message", async (data) => {
|
|
12095
12329
|
try {
|
|
12096
12330
|
let frame;
|
|
@@ -12102,10 +12336,43 @@ async function startHost(opts) {
|
|
|
12102
12336
|
return;
|
|
12103
12337
|
}
|
|
12104
12338
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
12105
|
-
if (extensionWs) {
|
|
12339
|
+
if (extensionWs || extensionClaim) {
|
|
12106
12340
|
ws.close(1008, "extension already connected");
|
|
12107
12341
|
return;
|
|
12108
12342
|
}
|
|
12343
|
+
extensionClaim = ws;
|
|
12344
|
+
let pin;
|
|
12345
|
+
try {
|
|
12346
|
+
pin = await opts.extensionTrust.read();
|
|
12347
|
+
} catch (e) {
|
|
12348
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
12349
|
+
if (extensionClaim === ws)
|
|
12350
|
+
extensionClaim = null;
|
|
12351
|
+
ws.close(1008, "extension pin unreadable");
|
|
12352
|
+
return;
|
|
12353
|
+
}
|
|
12354
|
+
const outcome = decideExtensionTrust({
|
|
12355
|
+
pin,
|
|
12356
|
+
hello: frame,
|
|
12357
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
12358
|
+
serverName: opts.ownServerName,
|
|
12359
|
+
location: opts.extensionTrust.location
|
|
12360
|
+
});
|
|
12361
|
+
if (outcome.decision === "refused") {
|
|
12362
|
+
console.warn(outcome.message);
|
|
12363
|
+
if (extensionClaim === ws)
|
|
12364
|
+
extensionClaim = null;
|
|
12365
|
+
ws.close(1008, "extension identity is not the pinned one");
|
|
12366
|
+
return;
|
|
12367
|
+
}
|
|
12368
|
+
if (outcome.decision === "replace")
|
|
12369
|
+
console.warn(outcome.message);
|
|
12370
|
+
if (closed || ws.readyState !== import_websocket.default.OPEN) {
|
|
12371
|
+
if (extensionClaim === ws)
|
|
12372
|
+
extensionClaim = null;
|
|
12373
|
+
return;
|
|
12374
|
+
}
|
|
12375
|
+
pinOnReady = outcome.decision !== "pinned";
|
|
12109
12376
|
identified = "extension";
|
|
12110
12377
|
extensionWs = ws;
|
|
12111
12378
|
extensionHello = frame;
|
|
@@ -12117,6 +12384,8 @@ async function startHost(opts) {
|
|
|
12117
12384
|
console.error("[fetchproxy] onPairCode threw:", e);
|
|
12118
12385
|
}
|
|
12119
12386
|
}
|
|
12387
|
+
for (const slot of peers.values())
|
|
12388
|
+
slot.ws.send(JSON.stringify(frame));
|
|
12120
12389
|
ws.send(JSON.stringify(ownHello));
|
|
12121
12390
|
for (const slot of peers.values()) {
|
|
12122
12391
|
ws.send(JSON.stringify(slot.helloFrame));
|
|
@@ -12152,6 +12421,8 @@ async function startHost(opts) {
|
|
|
12152
12421
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
12153
12422
|
if (extensionWs)
|
|
12154
12423
|
extensionWs.send(JSON.stringify(frame));
|
|
12424
|
+
if (extensionHello)
|
|
12425
|
+
ws.send(JSON.stringify(extensionHello));
|
|
12155
12426
|
return;
|
|
12156
12427
|
}
|
|
12157
12428
|
if (frame.type === "ready") {
|
|
@@ -12163,7 +12434,7 @@ async function startHost(opts) {
|
|
|
12163
12434
|
}
|
|
12164
12435
|
const extEdPub = fromB64(extensionHello.identityEd25519Pub);
|
|
12165
12436
|
const extNonce = fromB64(extensionHello.sessionNonce);
|
|
12166
|
-
const msg =
|
|
12437
|
+
const msg = readySignaturePayload(ownSessionNonce, extNonce, fromB64(frame.extensionSessionPub));
|
|
12167
12438
|
const sig = fromB64(frame.sessionSig);
|
|
12168
12439
|
let sigOk = false;
|
|
12169
12440
|
try {
|
|
@@ -12176,6 +12447,18 @@ async function startHost(opts) {
|
|
|
12176
12447
|
ws.close(1008, "extension session signature invalid");
|
|
12177
12448
|
return;
|
|
12178
12449
|
}
|
|
12450
|
+
if (pinOnReady) {
|
|
12451
|
+
pinOnReady = false;
|
|
12452
|
+
try {
|
|
12453
|
+
await opts.extensionTrust.write({
|
|
12454
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
12455
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
12456
|
+
pinnedAt: Date.now()
|
|
12457
|
+
});
|
|
12458
|
+
} catch (e) {
|
|
12459
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
12460
|
+
}
|
|
12461
|
+
}
|
|
12179
12462
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
12180
12463
|
const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
|
|
12181
12464
|
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -12229,6 +12512,9 @@ async function startHost(opts) {
|
|
|
12229
12512
|
}
|
|
12230
12513
|
});
|
|
12231
12514
|
ws.on("close", () => {
|
|
12515
|
+
closed = true;
|
|
12516
|
+
if (extensionClaim === ws)
|
|
12517
|
+
extensionClaim = null;
|
|
12232
12518
|
if (identified === "extension" && extensionWs === ws) {
|
|
12233
12519
|
extensionWs = null;
|
|
12234
12520
|
extensionHello = null;
|
|
@@ -12287,6 +12573,7 @@ var init_host = __esm({
|
|
|
12287
12573
|
init_build_server_hello();
|
|
12288
12574
|
init_session();
|
|
12289
12575
|
init_session_ready();
|
|
12576
|
+
init_extension_trust();
|
|
12290
12577
|
PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
12291
12578
|
enc2 = new TextEncoder();
|
|
12292
12579
|
}
|
|
@@ -12330,11 +12617,84 @@ async function startPeer(opts) {
|
|
|
12330
12617
|
resolveFirstReady = resolve;
|
|
12331
12618
|
rejectFirstReady = reject;
|
|
12332
12619
|
});
|
|
12620
|
+
let extensionHello = null;
|
|
12621
|
+
let warnedUnverifiable = false;
|
|
12622
|
+
let cachedPin = void 0;
|
|
12623
|
+
const authenticateExtension = async (sessionSig, extensionSessionPub) => {
|
|
12624
|
+
if (!extensionHello) {
|
|
12625
|
+
if (opts.requireExtensionIdentity) {
|
|
12626
|
+
console.error(`[fetchproxy] ${opts.serverName}: the concentrator does not forward the extension's identity, so this session cannot be verified \u2014 refusing. Upgrade the MCP holding the bridge port to 1.12.0 or later.`);
|
|
12627
|
+
return false;
|
|
12628
|
+
}
|
|
12629
|
+
if (!warnedUnverifiable) {
|
|
12630
|
+
warnedUnverifiable = true;
|
|
12631
|
+
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.`);
|
|
12632
|
+
}
|
|
12633
|
+
return true;
|
|
12634
|
+
}
|
|
12635
|
+
const payload = readySignaturePayload(sessionNonce, fromB64(extensionHello.sessionNonce), fromB64(extensionSessionPub));
|
|
12636
|
+
let sigOk = false;
|
|
12637
|
+
try {
|
|
12638
|
+
sigOk = await ed25519Verify(fromB64(extensionHello.identityEd25519Pub), payload, fromB64(sessionSig));
|
|
12639
|
+
} catch {
|
|
12640
|
+
sigOk = false;
|
|
12641
|
+
}
|
|
12642
|
+
if (!sigOk) {
|
|
12643
|
+
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
12644
|
+
return false;
|
|
12645
|
+
}
|
|
12646
|
+
if (cachedPin === void 0) {
|
|
12647
|
+
try {
|
|
12648
|
+
cachedPin = await opts.extensionTrust.read();
|
|
12649
|
+
} catch (e) {
|
|
12650
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
12651
|
+
return false;
|
|
12652
|
+
}
|
|
12653
|
+
}
|
|
12654
|
+
const pin = cachedPin;
|
|
12655
|
+
const outcome = decideExtensionTrust({
|
|
12656
|
+
pin,
|
|
12657
|
+
hello: extensionHello,
|
|
12658
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
12659
|
+
serverName: opts.serverName,
|
|
12660
|
+
location: opts.extensionTrust.location
|
|
12661
|
+
});
|
|
12662
|
+
if (outcome.decision === "refused") {
|
|
12663
|
+
console.warn(outcome.message);
|
|
12664
|
+
return false;
|
|
12665
|
+
}
|
|
12666
|
+
if (outcome.decision === "replace")
|
|
12667
|
+
console.warn(outcome.message);
|
|
12668
|
+
if (outcome.decision !== "pinned") {
|
|
12669
|
+
try {
|
|
12670
|
+
const written = {
|
|
12671
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
12672
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
12673
|
+
pinnedAt: Date.now()
|
|
12674
|
+
};
|
|
12675
|
+
await opts.extensionTrust.write(written);
|
|
12676
|
+
cachedPin = written;
|
|
12677
|
+
} catch (e) {
|
|
12678
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
12679
|
+
}
|
|
12680
|
+
}
|
|
12681
|
+
return true;
|
|
12682
|
+
};
|
|
12333
12683
|
const onMessage = async (data) => {
|
|
12334
12684
|
try {
|
|
12335
12685
|
const raw = JSON.parse(data.toString());
|
|
12336
12686
|
const frame = validateFrame(raw);
|
|
12687
|
+
if (frame.type === "hello" && frame.role === "extension") {
|
|
12688
|
+
extensionHello = frame;
|
|
12689
|
+
return;
|
|
12690
|
+
}
|
|
12337
12691
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
12692
|
+
const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
|
|
12693
|
+
if (!authorised) {
|
|
12694
|
+
ws.close(1008, "extension identity refused");
|
|
12695
|
+
rejectFirstReady(new Error("peer: extension identity refused"));
|
|
12696
|
+
return;
|
|
12697
|
+
}
|
|
12338
12698
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
12339
12699
|
const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
|
|
12340
12700
|
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -12422,67 +12782,11 @@ var init_peer = __esm({
|
|
|
12422
12782
|
init_build_server_hello();
|
|
12423
12783
|
init_session();
|
|
12424
12784
|
init_session_ready();
|
|
12785
|
+
init_extension_trust();
|
|
12425
12786
|
enc3 = new TextEncoder();
|
|
12426
12787
|
}
|
|
12427
12788
|
});
|
|
12428
12789
|
|
|
12429
|
-
// node_modules/@fetchproxy/server/dist/identity.js
|
|
12430
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
12431
|
-
import { join as join2 } from "node:path";
|
|
12432
|
-
import { homedir } from "node:os";
|
|
12433
|
-
function defaultIdentityDir() {
|
|
12434
|
-
return join2(homedir(), ".fetchproxy", "identity");
|
|
12435
|
-
}
|
|
12436
|
-
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
12437
|
-
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
12438
|
-
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
12439
|
-
}
|
|
12440
|
-
const safeFile = serverName.replace(/\//g, "_");
|
|
12441
|
-
const path = join2(dir, `${safeFile}.json`);
|
|
12442
|
-
await mkdir(dir, { recursive: true, mode: 448 });
|
|
12443
|
-
try {
|
|
12444
|
-
const raw = await readFile(path, "utf8");
|
|
12445
|
-
const j2 = JSON.parse(raw);
|
|
12446
|
-
return {
|
|
12447
|
-
x25519Priv: fromB64(j2.x25519Priv),
|
|
12448
|
-
x25519Pub: fromB64(j2.x25519Pub),
|
|
12449
|
-
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
12450
|
-
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
12451
|
-
createdAt: j2.createdAt
|
|
12452
|
-
};
|
|
12453
|
-
} catch (e) {
|
|
12454
|
-
if (e.code !== "ENOENT")
|
|
12455
|
-
throw e;
|
|
12456
|
-
}
|
|
12457
|
-
const x = await generateX25519();
|
|
12458
|
-
const ed = await generateEd25519();
|
|
12459
|
-
const id = {
|
|
12460
|
-
x25519Priv: x.privateKey,
|
|
12461
|
-
x25519Pub: x.publicKey,
|
|
12462
|
-
ed25519Priv: ed.privateKey,
|
|
12463
|
-
ed25519Pub: ed.publicKey,
|
|
12464
|
-
createdAt: Date.now()
|
|
12465
|
-
};
|
|
12466
|
-
const j = {
|
|
12467
|
-
x25519Priv: toB64(id.x25519Priv),
|
|
12468
|
-
x25519Pub: toB64(id.x25519Pub),
|
|
12469
|
-
ed25519Priv: toB64(id.ed25519Priv),
|
|
12470
|
-
ed25519Pub: toB64(id.ed25519Pub),
|
|
12471
|
-
createdAt: id.createdAt
|
|
12472
|
-
};
|
|
12473
|
-
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
12474
|
-
await chmod(path, 384);
|
|
12475
|
-
return id;
|
|
12476
|
-
}
|
|
12477
|
-
var SAFE_PLAIN, SAFE_SCOPED;
|
|
12478
|
-
var init_identity = __esm({
|
|
12479
|
-
"node_modules/@fetchproxy/server/dist/identity.js"() {
|
|
12480
|
-
init_dist();
|
|
12481
|
-
SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
12482
|
-
SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
12483
|
-
}
|
|
12484
|
-
});
|
|
12485
|
-
|
|
12486
12790
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
12487
12791
|
function classifyFetchError(error51) {
|
|
12488
12792
|
if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
|
|
@@ -12532,6 +12836,24 @@ var init_classify_bridge_error = __esm({
|
|
|
12532
12836
|
});
|
|
12533
12837
|
|
|
12534
12838
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
12839
|
+
function protocolErrorFrom(error51) {
|
|
12840
|
+
if (SCOPE_REJECTION.test(error51))
|
|
12841
|
+
return new FetchproxyScopeError(error51);
|
|
12842
|
+
if (NO_TAB_REJECTION.test(error51))
|
|
12843
|
+
return new FetchproxyNoTabError(error51);
|
|
12844
|
+
return new FetchproxyProtocolError(error51);
|
|
12845
|
+
}
|
|
12846
|
+
function normalizeCookiePath(path) {
|
|
12847
|
+
if (path === void 0 || path === "")
|
|
12848
|
+
return void 0;
|
|
12849
|
+
const trimmed = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
|
|
12850
|
+
try {
|
|
12851
|
+
assertCookiePath(trimmed, "path");
|
|
12852
|
+
} catch (e) {
|
|
12853
|
+
throw new Error(`FetchproxyServer: ${e instanceof Error ? e.message : String(e)} (got ${JSON.stringify(path)})`);
|
|
12854
|
+
}
|
|
12855
|
+
return trimmed;
|
|
12856
|
+
}
|
|
12535
12857
|
function assertSubdomainLabel(label) {
|
|
12536
12858
|
if (!SUBDOMAIN_LABEL_RE.test(label)) {
|
|
12537
12859
|
throw new Error(`FetchproxyServer: subdomain must be a DNS label like "www" or "api" (or dot-separated like "auth.api"), got ${JSON.stringify(label)}`);
|
|
@@ -12556,7 +12878,7 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
12556
12878
|
const declared = domains.map((d) => JSON.stringify(d)).join(", ");
|
|
12557
12879
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
12558
12880
|
}
|
|
12559
|
-
var FetchproxyProtocolError, FetchproxyHttpError, FetchproxyBridgeDownError, FetchproxyTimeoutError, SUBDOMAIN_LABEL_RE, DEFAULT_JSON_OK_STATUSES, FetchproxyServer;
|
|
12881
|
+
var FetchproxyProtocolError, FetchproxyHttpError, FetchproxyBridgeDownError, FetchproxyHintedError, FetchproxyScopeError, FetchproxyNoTabError, SCOPE_REJECTION, NO_TAB_REJECTION, FetchproxyTimeoutError, SUBDOMAIN_LABEL_RE, DEFAULT_JSON_OK_STATUSES, FetchproxyServer;
|
|
12560
12882
|
var init_ws_server = __esm({
|
|
12561
12883
|
"node_modules/@fetchproxy/server/dist/ws-server.js"() {
|
|
12562
12884
|
init_dist();
|
|
@@ -12564,6 +12886,7 @@ var init_ws_server = __esm({
|
|
|
12564
12886
|
init_host();
|
|
12565
12887
|
init_peer();
|
|
12566
12888
|
init_identity();
|
|
12889
|
+
init_extension_trust();
|
|
12567
12890
|
init_error_kind();
|
|
12568
12891
|
init_classify_bridge_error();
|
|
12569
12892
|
FetchproxyProtocolError = class extends Error {
|
|
@@ -12607,6 +12930,32 @@ var init_ws_server = __esm({
|
|
|
12607
12930
|
this.hint = hint;
|
|
12608
12931
|
}
|
|
12609
12932
|
};
|
|
12933
|
+
FetchproxyHintedError = class extends FetchproxyProtocolError {
|
|
12934
|
+
/** The extension's raw rejection, unmodified. */
|
|
12935
|
+
originalError;
|
|
12936
|
+
/** What the user should actually do, in prose. */
|
|
12937
|
+
hint;
|
|
12938
|
+
constructor(originalError, hint) {
|
|
12939
|
+
super(`${originalError} \u2014 ${hint}`);
|
|
12940
|
+
this.name = "FetchproxyHintedError";
|
|
12941
|
+
this.originalError = originalError;
|
|
12942
|
+
this.hint = hint;
|
|
12943
|
+
}
|
|
12944
|
+
};
|
|
12945
|
+
FetchproxyScopeError = class extends FetchproxyHintedError {
|
|
12946
|
+
constructor(originalError) {
|
|
12947
|
+
super(originalError, "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the Transporter extension popup, then re-run \u2014 you will be asked to approve the new scope. This is not a version problem and does not need an update.");
|
|
12948
|
+
this.name = "FetchproxyScopeError";
|
|
12949
|
+
}
|
|
12950
|
+
};
|
|
12951
|
+
FetchproxyNoTabError = class extends FetchproxyHintedError {
|
|
12952
|
+
constructor(originalError) {
|
|
12953
|
+
super(originalError, "open a tab on that host and sign in, then re-run. This is not a version problem and does not need an update.");
|
|
12954
|
+
this.name = "FetchproxyNoTabError";
|
|
12955
|
+
}
|
|
12956
|
+
};
|
|
12957
|
+
SCOPE_REJECTION = /not in declared/;
|
|
12958
|
+
NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
|
|
12610
12959
|
FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
12611
12960
|
url;
|
|
12612
12961
|
timeoutMs;
|
|
@@ -12676,6 +13025,9 @@ var init_ws_server = __esm({
|
|
|
12676
13025
|
// them off from `pending` (fetch) and `pendingReadCookies` (legacy
|
|
12677
13026
|
// string-shape) so the response routing in `onInner` stays linear.
|
|
12678
13027
|
pendingStorage = /* @__PURE__ */ new Map();
|
|
13028
|
+
// 1.12.0+: write-cookies awaiters resolve the list of names actually
|
|
13029
|
+
// written, so a caller can confirm rather than assume.
|
|
13030
|
+
pendingWriteCookies = /* @__PURE__ */ new Map();
|
|
12679
13031
|
// 0.3.0+: capture-header awaiters resolve a single string.
|
|
12680
13032
|
pendingCapture = /* @__PURE__ */ new Map();
|
|
12681
13033
|
// capture_redirect awaiters resolve the captured redirect URL string.
|
|
@@ -12802,6 +13154,8 @@ var init_ws_server = __esm({
|
|
|
12802
13154
|
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
12803
13155
|
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
12804
13156
|
identityDir: opts.identityDir,
|
|
13157
|
+
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
13158
|
+
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
12805
13159
|
onPairCode: opts.onPairCode
|
|
12806
13160
|
};
|
|
12807
13161
|
}
|
|
@@ -12901,7 +13255,8 @@ var init_ws_server = __esm({
|
|
|
12901
13255
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
12902
13256
|
ownDomSelectors: this.opts.domSelectors,
|
|
12903
13257
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
12904
|
-
onPairCode: this.opts.onPairCode
|
|
13258
|
+
onPairCode: this.opts.onPairCode,
|
|
13259
|
+
extensionTrust: this.extensionTrust()
|
|
12905
13260
|
});
|
|
12906
13261
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
12907
13262
|
this.hostHandle.onExtensionDisconnect(() => {
|
|
@@ -12930,7 +13285,9 @@ var init_ws_server = __esm({
|
|
|
12930
13285
|
localStoragePointers: this.opts.localStoragePointers,
|
|
12931
13286
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
12932
13287
|
domSelectors: this.opts.domSelectors,
|
|
12933
|
-
graphqlOps: this.opts.graphqlOps
|
|
13288
|
+
graphqlOps: this.opts.graphqlOps,
|
|
13289
|
+
extensionTrust: this.extensionTrust(),
|
|
13290
|
+
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
12934
13291
|
});
|
|
12935
13292
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
12936
13293
|
this.peerHandle.onRenegotiate(() => {
|
|
@@ -13069,6 +13426,23 @@ var init_ws_server = __esm({
|
|
|
13069
13426
|
markActive() {
|
|
13070
13427
|
this.noteActivityForKeepalive();
|
|
13071
13428
|
}
|
|
13429
|
+
/**
|
|
13430
|
+
* #208: this MCP's pin on the extension's identity, stored beside its own
|
|
13431
|
+
* identity key and so following `identityDir` wherever the caller put it.
|
|
13432
|
+
*
|
|
13433
|
+
* `allowNewExtensionIdentity` falls back to an environment variable when the
|
|
13434
|
+
* caller expressed no opinion, because the thirteen MCPs that construct this
|
|
13435
|
+
* class are separate packages: an operator whose extension re-install has
|
|
13436
|
+
* just locked all of them out needs one lever that does not require patching
|
|
13437
|
+
* every one of them.
|
|
13438
|
+
*/
|
|
13439
|
+
extensionTrust() {
|
|
13440
|
+
return fileExtensionTrust({
|
|
13441
|
+
serverName: this.opts.serverName,
|
|
13442
|
+
dir: this.opts.identityDir,
|
|
13443
|
+
allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
|
|
13444
|
+
});
|
|
13445
|
+
}
|
|
13072
13446
|
noteActivityForKeepalive() {
|
|
13073
13447
|
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
13074
13448
|
if (intervalMs <= 0)
|
|
@@ -13132,6 +13506,7 @@ var init_ws_server = __esm({
|
|
|
13132
13506
|
this.pending.delete(id);
|
|
13133
13507
|
this.pendingReadCookies.delete(id);
|
|
13134
13508
|
this.pendingStorage.delete(id);
|
|
13509
|
+
this.pendingWriteCookies.delete(id);
|
|
13135
13510
|
this.pendingCapture.delete(id);
|
|
13136
13511
|
this.pendingRedirect.delete(id);
|
|
13137
13512
|
this.pendingDownload.delete(id);
|
|
@@ -13253,7 +13628,7 @@ var init_ws_server = __esm({
|
|
|
13253
13628
|
port: this.opts.port
|
|
13254
13629
|
});
|
|
13255
13630
|
}
|
|
13256
|
-
return
|
|
13631
|
+
return protocolErrorFrom(result.error);
|
|
13257
13632
|
}
|
|
13258
13633
|
/**
|
|
13259
13634
|
* Convenience wrapper around `fetch()`. Builds the URL from a path
|
|
@@ -13289,10 +13664,20 @@ var init_ws_server = __esm({
|
|
|
13289
13664
|
}
|
|
13290
13665
|
const url2 = isAbsolute ? path : `https://${host}${path}`;
|
|
13291
13666
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
13667
|
+
let tabUrl = `https://${host}/`;
|
|
13668
|
+
if (opts.viaTab !== void 0) {
|
|
13669
|
+
try {
|
|
13670
|
+
new URL(opts.viaTab);
|
|
13671
|
+
} catch {
|
|
13672
|
+
throw new Error(`FetchproxyServer.request: viaTab is not a valid URL: ${JSON.stringify(opts.viaTab)}`);
|
|
13673
|
+
}
|
|
13674
|
+
assertUrlInDomains("viaTab", opts.viaTab, this.opts.domains);
|
|
13675
|
+
tabUrl = opts.viaTab;
|
|
13676
|
+
}
|
|
13292
13677
|
const init = {
|
|
13293
13678
|
url: url2,
|
|
13294
13679
|
method,
|
|
13295
|
-
tabUrl
|
|
13680
|
+
tabUrl,
|
|
13296
13681
|
headers: opts.headers,
|
|
13297
13682
|
body: opts.body
|
|
13298
13683
|
};
|
|
@@ -13504,9 +13889,14 @@ var init_ws_server = __esm({
|
|
|
13504
13889
|
let inner;
|
|
13505
13890
|
if (opts.keys !== void 0) {
|
|
13506
13891
|
this.assertScopeSubset(opts.keys, this.opts.cookieKeys, "cookieKeys");
|
|
13892
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
13507
13893
|
const initV3 = {
|
|
13894
|
+
// Origin stays BARE. The path travels as its own validated field —
|
|
13895
|
+
// `assertHttpsOriginOnly` deliberately refuses a path here so one
|
|
13896
|
+
// cannot be used to re-point the read past the domain gate.
|
|
13508
13897
|
origin: `https://${host}`,
|
|
13509
|
-
keys: [...opts.keys]
|
|
13898
|
+
keys: [...opts.keys],
|
|
13899
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
13510
13900
|
};
|
|
13511
13901
|
inner = { type: "request", id, op: "read_cookies", init: initV3 };
|
|
13512
13902
|
} else {
|
|
@@ -13519,10 +13909,65 @@ var init_ws_server = __esm({
|
|
|
13519
13909
|
await this.sendInnerFrame(inner);
|
|
13520
13910
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
13521
13911
|
if (!result.ok) {
|
|
13522
|
-
throw
|
|
13912
|
+
throw protocolErrorFrom(result.error);
|
|
13523
13913
|
}
|
|
13524
13914
|
return result.cookies;
|
|
13525
13915
|
}
|
|
13916
|
+
/**
|
|
13917
|
+
* 1.12.0+: overwrite the value of cookies this MCP already declares.
|
|
13918
|
+
*
|
|
13919
|
+
* The bridge's only write verb, and it exists for one failure class. Sites
|
|
13920
|
+
* that ROTATE a credential cookie hand back a new value on every refresh; if
|
|
13921
|
+
* the MCP refreshes and keeps the result to itself, the copy in the browser's
|
|
13922
|
+
* cookie jar is dead, and the user gets signed out of a tab they never
|
|
13923
|
+
* touched — usually reported to them as "inactivity". Writing the rotated
|
|
13924
|
+
* value back is the only thing that repairs it.
|
|
13925
|
+
*
|
|
13926
|
+
* Requires `'write_cookies'` in capabilities, which the user approves at pair
|
|
13927
|
+
* time as its own line. Every name must ALSO be in declared `cookieKeys`: a
|
|
13928
|
+
* write can never reach a cookie the MCP was not already trusted to read, so
|
|
13929
|
+
* granting it cannot widen which cookies are in play — only what may be done
|
|
13930
|
+
* to the ones already listed.
|
|
13931
|
+
*
|
|
13932
|
+
* The extension refuses the whole request unless every named cookie already
|
|
13933
|
+
* exists; this refreshes a value in place and deliberately cannot author new
|
|
13934
|
+
* cookies. Returns the names actually written.
|
|
13935
|
+
*/
|
|
13936
|
+
async writeCookies(opts) {
|
|
13937
|
+
if (!this.opts.capabilities.includes("write_cookies")) {
|
|
13938
|
+
throw new Error('FetchproxyServer.writeCookies(): MCP did not declare "write_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
|
|
13939
|
+
}
|
|
13940
|
+
const names = Object.keys(opts.cookies);
|
|
13941
|
+
if (names.length === 0) {
|
|
13942
|
+
throw new Error("FetchproxyServer.writeCookies(): no cookies given");
|
|
13943
|
+
}
|
|
13944
|
+
await this.ensureConnected();
|
|
13945
|
+
this.throwIfPendingPair();
|
|
13946
|
+
if (opts.subdomain !== void 0)
|
|
13947
|
+
assertSubdomainLabel(opts.subdomain);
|
|
13948
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
13949
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
13950
|
+
this.assertScopeSubset(names, this.opts.cookieKeys, "cookieKeys");
|
|
13951
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
13952
|
+
const id = this.nextRequestId++;
|
|
13953
|
+
const inner = {
|
|
13954
|
+
type: "request",
|
|
13955
|
+
id,
|
|
13956
|
+
op: "write_cookies",
|
|
13957
|
+
init: {
|
|
13958
|
+
// Bare origin, same invariant as the read path: a path must never be
|
|
13959
|
+
// able to move the request past the domain gate.
|
|
13960
|
+
origin: `https://${host}`,
|
|
13961
|
+
cookies: Object.entries(opts.cookies).map(([name, value]) => ({ name, value })),
|
|
13962
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
13963
|
+
}
|
|
13964
|
+
};
|
|
13965
|
+
const pending = new Promise((resolve, reject) => {
|
|
13966
|
+
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
13967
|
+
});
|
|
13968
|
+
await this.sendInnerFrame(inner);
|
|
13969
|
+
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
13970
|
+
}
|
|
13526
13971
|
/**
|
|
13527
13972
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
13528
13973
|
* tab. Requires `'read_local_storage'` in capabilities AND each key
|
|
@@ -14069,7 +14514,7 @@ var init_ws_server = __esm({
|
|
|
14069
14514
|
storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
|
|
14070
14515
|
}
|
|
14071
14516
|
} else {
|
|
14072
|
-
storageCb.reject(
|
|
14517
|
+
storageCb.reject(protocolErrorFrom(inner.error));
|
|
14073
14518
|
}
|
|
14074
14519
|
return;
|
|
14075
14520
|
}
|
|
@@ -14083,7 +14528,7 @@ var init_ws_server = __esm({
|
|
|
14083
14528
|
captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
|
|
14084
14529
|
}
|
|
14085
14530
|
} else {
|
|
14086
|
-
captureCb.reject(
|
|
14531
|
+
captureCb.reject(protocolErrorFrom(inner.error));
|
|
14087
14532
|
}
|
|
14088
14533
|
return;
|
|
14089
14534
|
}
|
|
@@ -14097,7 +14542,7 @@ var init_ws_server = __esm({
|
|
|
14097
14542
|
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
14098
14543
|
}
|
|
14099
14544
|
} else {
|
|
14100
|
-
redirectCb.reject(
|
|
14545
|
+
redirectCb.reject(protocolErrorFrom(inner.error));
|
|
14101
14546
|
}
|
|
14102
14547
|
return;
|
|
14103
14548
|
}
|
|
@@ -14111,7 +14556,7 @@ var init_ws_server = __esm({
|
|
|
14111
14556
|
idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
|
|
14112
14557
|
}
|
|
14113
14558
|
} else {
|
|
14114
|
-
idbCb.reject(
|
|
14559
|
+
idbCb.reject(protocolErrorFrom(inner.error));
|
|
14115
14560
|
}
|
|
14116
14561
|
return;
|
|
14117
14562
|
}
|
|
@@ -14125,7 +14570,7 @@ var init_ws_server = __esm({
|
|
|
14125
14570
|
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
14126
14571
|
}
|
|
14127
14572
|
} else {
|
|
14128
|
-
downloadCb.reject(
|
|
14573
|
+
downloadCb.reject(protocolErrorFrom(inner.error));
|
|
14129
14574
|
}
|
|
14130
14575
|
return;
|
|
14131
14576
|
}
|
|
@@ -14139,7 +14584,17 @@ var init_ws_server = __esm({
|
|
|
14139
14584
|
graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
|
|
14140
14585
|
}
|
|
14141
14586
|
} else {
|
|
14142
|
-
graphqlCb.reject(
|
|
14587
|
+
graphqlCb.reject(protocolErrorFrom(inner.error));
|
|
14588
|
+
}
|
|
14589
|
+
return;
|
|
14590
|
+
}
|
|
14591
|
+
const writeCookiesCb = this.pendingWriteCookies.get(inner.id);
|
|
14592
|
+
if (writeCookiesCb) {
|
|
14593
|
+
this.pendingWriteCookies.delete(inner.id);
|
|
14594
|
+
if (inner.ok && inner.op === "write_cookies") {
|
|
14595
|
+
writeCookiesCb.resolve([...inner.written]);
|
|
14596
|
+
} else {
|
|
14597
|
+
writeCookiesCb.reject(protocolErrorFrom(inner.ok ? "write_cookies response had the wrong op" : inner.error));
|
|
14143
14598
|
}
|
|
14144
14599
|
return;
|
|
14145
14600
|
}
|
|
@@ -14182,6 +14637,9 @@ var init_ws_server = __esm({
|
|
|
14182
14637
|
for (const { reject } of this.pendingStorage.values())
|
|
14183
14638
|
reject(err);
|
|
14184
14639
|
this.pendingStorage.clear();
|
|
14640
|
+
for (const { reject } of this.pendingWriteCookies.values())
|
|
14641
|
+
reject(err);
|
|
14642
|
+
this.pendingWriteCookies.clear();
|
|
14185
14643
|
for (const { reject } of this.pendingCapture.values())
|
|
14186
14644
|
reject(err);
|
|
14187
14645
|
this.pendingCapture.clear();
|
|
@@ -14560,6 +15018,8 @@ var init_dist2 = __esm({
|
|
|
14560
15018
|
init_deadline();
|
|
14561
15019
|
init_parse_html();
|
|
14562
15020
|
init_batch();
|
|
15021
|
+
init_extension_trust();
|
|
15022
|
+
init_identity();
|
|
14563
15023
|
}
|
|
14564
15024
|
});
|
|
14565
15025
|
|
|
@@ -39265,7 +39725,7 @@ var pageSchema = {
|
|
|
39265
39725
|
init_errors();
|
|
39266
39726
|
|
|
39267
39727
|
// src/version.ts
|
|
39268
|
-
var VERSION = "0.1.
|
|
39728
|
+
var VERSION = "0.1.2";
|
|
39269
39729
|
|
|
39270
39730
|
// src/client.ts
|
|
39271
39731
|
import { dirname, join } from "path";
|
|
@@ -39286,10 +39746,10 @@ var EventbriteClient = class {
|
|
|
39286
39746
|
* host's install-time smoke test) when EVENTBRITE_TOKEN isn't set yet.
|
|
39287
39747
|
* Tool calls re-raise the error at request time.
|
|
39288
39748
|
*
|
|
39289
|
-
* Optional constructor seam:
|
|
39749
|
+
* Optional constructor seam: a hosted per-user deployment builds one
|
|
39290
39750
|
* client per request with that user's `token` injected. The stdio path
|
|
39291
39751
|
* passes no options, so the token resolves from the environment.
|
|
39292
|
-
* The constructor is PURE (no I/O, no randomness) — it
|
|
39752
|
+
* The constructor is PURE (no I/O, no randomness) — it may run in module
|
|
39293
39753
|
* global scope via the module singleton below.
|
|
39294
39754
|
*/
|
|
39295
39755
|
constructor(opts) {
|
|
@@ -39510,7 +39970,7 @@ var DiscoveryClient = class {
|
|
|
39510
39970
|
* 1. `api` — `POST eventbriteapi.com/v3/destination/search/` with a bearer
|
|
39511
39971
|
* token. Verified live 2026-07-30: works with a private OR public token,
|
|
39512
39972
|
* no WAF, no CSRF, no cookies. This is the default because it needs no
|
|
39513
|
-
* browser and therefore works
|
|
39973
|
+
* browser and therefore works without the bridge.
|
|
39514
39974
|
* 2. `transport` — the fetchproxy bridge through a signed-in tab. Retained as
|
|
39515
39975
|
* a fallback for when no token is configured, or the API route refuses.
|
|
39516
39976
|
*
|
package/dist/client.js
CHANGED
|
@@ -4,9 +4,9 @@ import { loadDotenvSafely, readEnvVar, createApiClient } from '@chrischall/mcp-u
|
|
|
4
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb
|
|
5
5
|
// bundle). `loadDotenvSafely` swallows a missing dotenv module and never lets
|
|
6
6
|
// .env override a host-provided value.
|
|
7
|
-
// The try/catch guards
|
|
7
|
+
// The try/catch guards a non-Node runtime, where `import.meta.url`
|
|
8
8
|
// is undefined and `fileURLToPath(undefined)` would throw at module init
|
|
9
|
-
//
|
|
9
|
+
// startup validation in such a runtime — and there is no .env to load there.
|
|
10
10
|
try {
|
|
11
11
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
await loadDotenvSafely({ path: join(dir, '..', '.env'), override: false });
|
|
@@ -32,10 +32,10 @@ export class EventbriteClient {
|
|
|
32
32
|
* host's install-time smoke test) when EVENTBRITE_TOKEN isn't set yet.
|
|
33
33
|
* Tool calls re-raise the error at request time.
|
|
34
34
|
*
|
|
35
|
-
* Optional constructor seam:
|
|
35
|
+
* Optional constructor seam: a hosted per-user deployment builds one
|
|
36
36
|
* client per request with that user's `token` injected. The stdio path
|
|
37
37
|
* passes no options, so the token resolves from the environment.
|
|
38
|
-
* The constructor is PURE (no I/O, no randomness) — it
|
|
38
|
+
* The constructor is PURE (no I/O, no randomness) — it may run in module
|
|
39
39
|
* global scope via the module singleton below.
|
|
40
40
|
*/
|
|
41
41
|
constructor(opts) {
|
package/dist/discovery.js
CHANGED
|
@@ -19,7 +19,7 @@ function isUsableBrowsePage(result) {
|
|
|
19
19
|
return false;
|
|
20
20
|
return typeof result.body === 'string' && PLACE_ID_RE.test(result.body);
|
|
21
21
|
}
|
|
22
|
-
/** `fetch` with an AbortController deadline
|
|
22
|
+
/** `fetch` with an AbortController deadline. */
|
|
23
23
|
async function fetchWithTimeout(url, ms) {
|
|
24
24
|
const controller = new AbortController();
|
|
25
25
|
const timer = setTimeout(() => controller.abort(), ms);
|
|
@@ -219,7 +219,7 @@ export class DiscoveryClient {
|
|
|
219
219
|
* 1. `api` — `POST eventbriteapi.com/v3/destination/search/` with a bearer
|
|
220
220
|
* token. Verified live 2026-07-30: works with a private OR public token,
|
|
221
221
|
* no WAF, no CSRF, no cookies. This is the default because it needs no
|
|
222
|
-
* browser and therefore works
|
|
222
|
+
* browser and therefore works without the bridge.
|
|
223
223
|
* 2. `transport` — the fetchproxy bridge through a signed-in tab. Retained as
|
|
224
224
|
* a fallback for when no token is configured, or the API route refuses.
|
|
225
225
|
*
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { registerDiscoveryTools } from './tools/discovery.js';
|
|
|
15
15
|
// documented host serves the consumer search at
|
|
16
16
|
// POST /destination/search/ with a plain bearer token — no WAF, no CSRF,
|
|
17
17
|
// no browser. That is now the primary route, so discovery works in a
|
|
18
|
-
//
|
|
18
|
+
// a bridge-less deployment too. The bridge (port 37149, bound lazily) is kept as
|
|
19
19
|
// a FALLBACK for when no token is configured or the API route refuses.
|
|
20
20
|
const transport = new FetchproxyTransport({
|
|
21
21
|
version: VERSION,
|
package/dist/tools/account.js
CHANGED
|
@@ -3,7 +3,7 @@ import { textResult } from '@chrischall/mcp-utils';
|
|
|
3
3
|
import { enc, qs, schemaContinuation } from './params.js';
|
|
4
4
|
/**
|
|
5
5
|
* Account-side tools on the documented API (`eventbriteapi.com/v3`, bearer
|
|
6
|
-
* token). Transport-neutral:
|
|
6
|
+
* token). Transport-neutral: a hosted deployment registers these with a
|
|
7
7
|
* per-user client.
|
|
8
8
|
*/
|
|
9
9
|
export function registerAccountTools(server, deps) {
|
package/dist/tools/discovery.js
CHANGED
|
@@ -5,7 +5,7 @@ import { toCompactEvent } from '../discovery.js';
|
|
|
5
5
|
* Public event discovery. Verified live 2026-07-30: the documented host serves
|
|
6
6
|
* the consumer search at POST /destination/search/ with a plain bearer token —
|
|
7
7
|
* no WAF, no CSRF, no cookies — so these tools no longer require a browser and
|
|
8
|
-
* ARE registered
|
|
8
|
+
* ARE registered without a bridge. The fetchproxy bridge remains a
|
|
9
9
|
* fallback on the stdio path.
|
|
10
10
|
*/
|
|
11
11
|
export async function registerDiscoveryTools(server, deps) {
|
|
@@ -99,13 +99,13 @@ export async function registerDiscoveryTools(server, deps) {
|
|
|
99
99
|
const data = await discovery.eventsByIds(event_ids, expand ? expand.split(',').map((s) => s.trim()) : undefined);
|
|
100
100
|
return textResult(data);
|
|
101
101
|
});
|
|
102
|
-
// eb_healthcheck diagnoses the BRIDGE. With no bridge
|
|
102
|
+
// eb_healthcheck diagnoses the BRIDGE. With no bridge
|
|
103
103
|
// there is nothing for it to report on, so it is not registered at all —
|
|
104
104
|
// better than a tool that always answers "no transport".
|
|
105
105
|
if (!transport)
|
|
106
106
|
return;
|
|
107
107
|
// Imported lazily, AFTER the guard: a static import would drag the fetchproxy
|
|
108
|
-
// helper into
|
|
108
|
+
// helper into a bundle where it can never run.
|
|
109
109
|
const { registerBridgeHealthcheckTool } = await import('@chrischall/mcp-utils/fetchproxy');
|
|
110
110
|
// The categories endpoint answers 200 JSON on the www host regardless of
|
|
111
111
|
// login state, so it isolates bridge problems from Eventbrite-side problems.
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
1
|
+
export const VERSION = '0.1.2'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/eventbrite-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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>",
|
|
@@ -40,33 +40,23 @@
|
|
|
40
40
|
"dev": "node dist/index.js",
|
|
41
41
|
"test": "vitest run",
|
|
42
42
|
"test:watch": "vitest",
|
|
43
|
-
"test:coverage": "vitest run --coverage"
|
|
44
|
-
"worker:dev": "wrangler dev",
|
|
45
|
-
"worker:deploy": "wrangler deploy",
|
|
46
|
-
"worker:test": "vitest run --config vitest.workers.config.ts"
|
|
43
|
+
"test:coverage": "vitest run --coverage"
|
|
47
44
|
},
|
|
48
45
|
"dependencies": {
|
|
49
46
|
"@chrischall/mcp-utils": "^0.14.0",
|
|
50
|
-
"@fetchproxy/server": "^
|
|
47
|
+
"@fetchproxy/server": "^2.0.0",
|
|
51
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
52
49
|
"dotenv": "^17.4.0",
|
|
53
50
|
"zod": "^4.4.2"
|
|
54
51
|
},
|
|
55
52
|
"devDependencies": {
|
|
56
|
-
"@chrischall/mcp-connector": "^1.1.1",
|
|
57
|
-
"@cloudflare/vitest-pool-workers": "^0.19.0",
|
|
58
|
-
"@cloudflare/workers-oauth-provider": "^0.8.1",
|
|
59
|
-
"@cloudflare/workers-types": "^5.20260708.1",
|
|
60
53
|
"@types/node": "^26.0.0",
|
|
61
54
|
"@vitest/coverage-v8": "^4.1.2",
|
|
62
|
-
"agents": "^0.19.0",
|
|
63
55
|
"esbuild": "^0.28.0",
|
|
64
56
|
"typescript": "^7.0.2",
|
|
65
|
-
"vitest": "^4.1.2"
|
|
66
|
-
"wrangler": "^4.110.0"
|
|
57
|
+
"vitest": "^4.1.2"
|
|
67
58
|
},
|
|
68
59
|
"allowScripts": {
|
|
69
|
-
"esbuild@0.28.1": true
|
|
70
|
-
"workerd@1.20260722.1": true
|
|
60
|
+
"esbuild@0.28.1": true
|
|
71
61
|
}
|
|
72
62
|
}
|
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.1.
|
|
9
|
+
"version": "0.1.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/eventbrite-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/dist/eventbrite-auth.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { EventbriteClient } from './client.js';
|
|
2
|
-
/**
|
|
3
|
-
* `ConnectorAuth` for the Eventbrite remote connector: the login page collects
|
|
4
|
-
* the user's personal Eventbrite token (eventbrite.com/platform/api-keys),
|
|
5
|
-
* verifies it against the current-user endpoint (a bad token throws, which
|
|
6
|
-
* the connector surfaces back on the login page), and stores `{ token }`.
|
|
7
|
-
*/
|
|
8
|
-
export const eventbriteAuth = {
|
|
9
|
-
service: 'Eventbrite',
|
|
10
|
-
accent: '#F05537',
|
|
11
|
-
privacyNote: 'Your Eventbrite private token is stored encrypted and used only to call the Eventbrite API on your behalf.',
|
|
12
|
-
fields: [{ name: 'token', label: 'Eventbrite private token', type: 'password' }],
|
|
13
|
-
async login(fields) {
|
|
14
|
-
const client = new EventbriteClient({ token: fields.token });
|
|
15
|
-
await client.request('GET', '/users/me/');
|
|
16
|
-
return { token: fields.token };
|
|
17
|
-
},
|
|
18
|
-
};
|