@chrischall/tripadvisor-mcp 0.3.2 → 0.3.4
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 +651 -139
- package/dist/version.js +1 -1
- package/mint.yaml +56 -0
- package/package.json +8 -6
- package/server.json +2 -2
- package/skills/tripadvisor/SKILL.md +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for the TripAdvisor Terra API — location search, details, photos, and reviews",
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.4"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "TripAdvisor",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "TripAdvisor travel data via the Terra API — search hotels, restaurants, and attractions, with details, photos, and reviews",
|
|
18
|
-
"version": "0.3.
|
|
18
|
+
"version": "0.3.4",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -3651,7 +3651,12 @@ var require_fast_uri = __commonJS({
|
|
|
3651
3651
|
}
|
|
3652
3652
|
function resolve(baseURI, relativeURI, options) {
|
|
3653
3653
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3654
|
-
const
|
|
3654
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3655
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3656
|
+
if (baseMalformed || relativeMalformed) {
|
|
3657
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3658
|
+
}
|
|
3659
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3655
3660
|
schemelessOptions.skipEscape = true;
|
|
3656
3661
|
return serialize(resolved, schemelessOptions);
|
|
3657
3662
|
}
|
|
@@ -3777,6 +3782,7 @@ var require_fast_uri = __commonJS({
|
|
|
3777
3782
|
}
|
|
3778
3783
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3779
3784
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3785
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3780
3786
|
function getParseError(parsed, matches) {
|
|
3781
3787
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3782
3788
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3811,6 +3817,20 @@ var require_fast_uri = __commonJS({
|
|
|
3811
3817
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3812
3818
|
malformedAuthorityOrPort = true;
|
|
3813
3819
|
}
|
|
3820
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3821
|
+
if (introducerMatch !== null) {
|
|
3822
|
+
const region = introducerMatch[1];
|
|
3823
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3824
|
+
if (normalizedRegion.length >= 2) {
|
|
3825
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3826
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3827
|
+
malformedAuthorityOrPort = true;
|
|
3828
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3829
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3830
|
+
malformedAuthorityOrPort = true;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3814
3834
|
const matches = uri.match(URI_PARSE);
|
|
3815
3835
|
if (matches) {
|
|
3816
3836
|
parsed.scheme = matches[1];
|
|
@@ -26725,17 +26745,33 @@ function normalizeObjectSchema(schema) {
|
|
|
26725
26745
|
}
|
|
26726
26746
|
return void 0;
|
|
26727
26747
|
}
|
|
26748
|
+
function getDotPath(path) {
|
|
26749
|
+
if (path.length === 0) {
|
|
26750
|
+
return "object root";
|
|
26751
|
+
}
|
|
26752
|
+
return path.reduce((acc, seg, index) => {
|
|
26753
|
+
if (index === 0) {
|
|
26754
|
+
return String(seg);
|
|
26755
|
+
}
|
|
26756
|
+
if (typeof seg === "number") {
|
|
26757
|
+
return `${acc}[${seg}]`;
|
|
26758
|
+
}
|
|
26759
|
+
return `${acc}.${seg}`;
|
|
26760
|
+
}, "");
|
|
26761
|
+
}
|
|
26728
26762
|
function getParseErrorMessage(error51) {
|
|
26729
26763
|
if (error51 && typeof error51 === "object") {
|
|
26764
|
+
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
26765
|
+
return error51.issues.map((i) => {
|
|
26766
|
+
if (!i.path?.length) {
|
|
26767
|
+
return i.message;
|
|
26768
|
+
}
|
|
26769
|
+
return `${i.message} at ${getDotPath(i.path)}`;
|
|
26770
|
+
}).join("\n");
|
|
26771
|
+
}
|
|
26730
26772
|
if ("message" in error51 && typeof error51.message === "string") {
|
|
26731
26773
|
return error51.message;
|
|
26732
26774
|
}
|
|
26733
|
-
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
26734
|
-
const firstIssue = error51.issues[0];
|
|
26735
|
-
if (firstIssue && typeof firstIssue === "object" && "message" in firstIssue) {
|
|
26736
|
-
return String(firstIssue.message);
|
|
26737
|
-
}
|
|
26738
|
-
}
|
|
26739
26775
|
try {
|
|
26740
26776
|
return JSON.stringify(error51);
|
|
26741
26777
|
} catch {
|
|
@@ -33350,16 +33386,7 @@ var Server = class extends Protocol {
|
|
|
33350
33386
|
if (!methodSchema) {
|
|
33351
33387
|
throw new Error("Schema is missing a method literal");
|
|
33352
33388
|
}
|
|
33353
|
-
|
|
33354
|
-
if (isZ4Schema(methodSchema)) {
|
|
33355
|
-
const v4Schema = methodSchema;
|
|
33356
|
-
const v4Def = v4Schema._zod?.def;
|
|
33357
|
-
methodValue = v4Def?.value ?? v4Schema.value;
|
|
33358
|
-
} else {
|
|
33359
|
-
const v3Schema = methodSchema;
|
|
33360
|
-
const legacyDef = v3Schema._def;
|
|
33361
|
-
methodValue = legacyDef?.value ?? v3Schema.value;
|
|
33362
|
-
}
|
|
33389
|
+
const methodValue = getLiteralValue(methodSchema);
|
|
33363
33390
|
if (typeof methodValue !== "string") {
|
|
33364
33391
|
throw new Error("Schema method literal must be a string");
|
|
33365
33392
|
}
|
|
@@ -34547,8 +34574,17 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
34547
34574
|
import process3 from "node:process";
|
|
34548
34575
|
|
|
34549
34576
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
34577
|
+
var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
34550
34578
|
var ReadBuffer = class {
|
|
34579
|
+
constructor(options) {
|
|
34580
|
+
this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
|
|
34581
|
+
}
|
|
34551
34582
|
append(chunk2) {
|
|
34583
|
+
const newSize = (this._buffer?.length ?? 0) + chunk2.length;
|
|
34584
|
+
if (newSize > this._maxBufferSize) {
|
|
34585
|
+
this.clear();
|
|
34586
|
+
throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
|
|
34587
|
+
}
|
|
34552
34588
|
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
|
|
34553
34589
|
}
|
|
34554
34590
|
readMessage() {
|
|
@@ -34576,18 +34612,24 @@ function serializeMessage(message) {
|
|
|
34576
34612
|
|
|
34577
34613
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
34578
34614
|
var StdioServerTransport = class {
|
|
34579
|
-
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
34615
|
+
constructor(_stdin = process3.stdin, _stdout = process3.stdout, options) {
|
|
34580
34616
|
this._stdin = _stdin;
|
|
34581
34617
|
this._stdout = _stdout;
|
|
34582
|
-
this._readBuffer = new ReadBuffer();
|
|
34583
34618
|
this._started = false;
|
|
34584
34619
|
this._ondata = (chunk2) => {
|
|
34585
|
-
|
|
34586
|
-
|
|
34620
|
+
try {
|
|
34621
|
+
this._readBuffer.append(chunk2);
|
|
34622
|
+
this.processReadBuffer();
|
|
34623
|
+
} catch (error51) {
|
|
34624
|
+
this.onerror?.(error51);
|
|
34625
|
+
this.close().catch(() => {
|
|
34626
|
+
});
|
|
34627
|
+
}
|
|
34587
34628
|
};
|
|
34588
34629
|
this._onerror = (error51) => {
|
|
34589
34630
|
this.onerror?.(error51);
|
|
34590
34631
|
};
|
|
34632
|
+
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
34591
34633
|
}
|
|
34592
34634
|
/**
|
|
34593
34635
|
* Starts listening for messages on stdin.
|
|
@@ -34635,53 +34677,6 @@ var StdioServerTransport = class {
|
|
|
34635
34677
|
}
|
|
34636
34678
|
};
|
|
34637
34679
|
|
|
34638
|
-
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34639
|
-
async function createMcpServer(opts) {
|
|
34640
|
-
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34641
|
-
if (opts.banner !== void 0) {
|
|
34642
|
-
console.error(opts.banner);
|
|
34643
|
-
}
|
|
34644
|
-
const deps = opts.deps;
|
|
34645
|
-
for (const register of opts.tools) {
|
|
34646
|
-
await register(server, deps);
|
|
34647
|
-
}
|
|
34648
|
-
return server;
|
|
34649
|
-
}
|
|
34650
|
-
function withGracefulShutdown(server, opts = {}) {
|
|
34651
|
-
const shouldExit = opts.exit ?? true;
|
|
34652
|
-
let shuttingDown = false;
|
|
34653
|
-
const handler = (signal) => {
|
|
34654
|
-
if (shuttingDown)
|
|
34655
|
-
return;
|
|
34656
|
-
shuttingDown = true;
|
|
34657
|
-
void (async () => {
|
|
34658
|
-
try {
|
|
34659
|
-
if (opts.onSignal)
|
|
34660
|
-
await opts.onSignal(signal);
|
|
34661
|
-
await server.close();
|
|
34662
|
-
} catch (err) {
|
|
34663
|
-
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34664
|
-
} finally {
|
|
34665
|
-
if (shouldExit)
|
|
34666
|
-
process.exit(0);
|
|
34667
|
-
}
|
|
34668
|
-
})();
|
|
34669
|
-
};
|
|
34670
|
-
process.on("SIGINT", () => handler("SIGINT"));
|
|
34671
|
-
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
34672
|
-
}
|
|
34673
|
-
async function runMcp(opts) {
|
|
34674
|
-
const server = await createMcpServer(opts);
|
|
34675
|
-
const shutdown = opts.shutdown ?? true;
|
|
34676
|
-
if (shutdown !== false) {
|
|
34677
|
-
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
34678
|
-
}
|
|
34679
|
-
const spec = opts.transport ?? "stdio";
|
|
34680
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
34681
|
-
await server.connect(transport);
|
|
34682
|
-
return server;
|
|
34683
|
-
}
|
|
34684
|
-
|
|
34685
34680
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
34686
34681
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
34687
34682
|
var McpToolError = class extends Error {
|
|
@@ -34741,6 +34736,81 @@ function textResult(data) {
|
|
|
34741
34736
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
34742
34737
|
};
|
|
34743
34738
|
}
|
|
34739
|
+
function errorResult(message) {
|
|
34740
|
+
return {
|
|
34741
|
+
content: [{ type: "text", text: redactSecrets(message) }],
|
|
34742
|
+
isError: true
|
|
34743
|
+
};
|
|
34744
|
+
}
|
|
34745
|
+
|
|
34746
|
+
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34747
|
+
function hintResultOrRethrow(err) {
|
|
34748
|
+
if (err instanceof McpToolError && err.hint) {
|
|
34749
|
+
return errorResult(`${err.message}
|
|
34750
|
+
|
|
34751
|
+
Hint: ${err.hint}`);
|
|
34752
|
+
}
|
|
34753
|
+
throw err;
|
|
34754
|
+
}
|
|
34755
|
+
function surfaceToolHints(server) {
|
|
34756
|
+
const register = server.registerTool.bind(server);
|
|
34757
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
34758
|
+
let result;
|
|
34759
|
+
try {
|
|
34760
|
+
result = cb(...args);
|
|
34761
|
+
} catch (err) {
|
|
34762
|
+
return hintResultOrRethrow(err);
|
|
34763
|
+
}
|
|
34764
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
34765
|
+
});
|
|
34766
|
+
}
|
|
34767
|
+
async function createMcpServer(opts) {
|
|
34768
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34769
|
+
if (opts.surfaceHints !== false)
|
|
34770
|
+
surfaceToolHints(server);
|
|
34771
|
+
if (opts.banner !== void 0) {
|
|
34772
|
+
console.error(opts.banner);
|
|
34773
|
+
}
|
|
34774
|
+
const deps = opts.deps;
|
|
34775
|
+
for (const register of opts.tools) {
|
|
34776
|
+
await register(server, deps);
|
|
34777
|
+
}
|
|
34778
|
+
return server;
|
|
34779
|
+
}
|
|
34780
|
+
function withGracefulShutdown(server, opts = {}) {
|
|
34781
|
+
const shouldExit = opts.exit ?? true;
|
|
34782
|
+
let shuttingDown = false;
|
|
34783
|
+
const handler = (signal) => {
|
|
34784
|
+
if (shuttingDown)
|
|
34785
|
+
return;
|
|
34786
|
+
shuttingDown = true;
|
|
34787
|
+
void (async () => {
|
|
34788
|
+
try {
|
|
34789
|
+
if (opts.onSignal)
|
|
34790
|
+
await opts.onSignal(signal);
|
|
34791
|
+
await server.close();
|
|
34792
|
+
} catch (err) {
|
|
34793
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34794
|
+
} finally {
|
|
34795
|
+
if (shouldExit)
|
|
34796
|
+
process.exit(0);
|
|
34797
|
+
}
|
|
34798
|
+
})();
|
|
34799
|
+
};
|
|
34800
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
34801
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
34802
|
+
}
|
|
34803
|
+
async function runMcp(opts) {
|
|
34804
|
+
const server = await createMcpServer(opts);
|
|
34805
|
+
const shutdown = opts.shutdown ?? true;
|
|
34806
|
+
if (shutdown !== false) {
|
|
34807
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
34808
|
+
}
|
|
34809
|
+
const spec = opts.transport ?? "stdio";
|
|
34810
|
+
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
34811
|
+
await server.connect(transport);
|
|
34812
|
+
return server;
|
|
34813
|
+
}
|
|
34744
34814
|
|
|
34745
34815
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34746
34816
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
@@ -34936,7 +35006,7 @@ var pageSchema = {
|
|
|
34936
35006
|
};
|
|
34937
35007
|
|
|
34938
35008
|
// src/version.ts
|
|
34939
|
-
var VERSION = "0.3.
|
|
35009
|
+
var VERSION = "0.3.4";
|
|
34940
35010
|
|
|
34941
35011
|
// src/client.ts
|
|
34942
35012
|
import { dirname, join } from "node:path";
|
|
@@ -35260,8 +35330,15 @@ function registerLocationTools(server) {
|
|
|
35260
35330
|
}
|
|
35261
35331
|
|
|
35262
35332
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
35263
|
-
var PROTOCOL_VERSION =
|
|
35333
|
+
var PROTOCOL_VERSION = 3;
|
|
35264
35334
|
var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
35335
|
+
function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub) {
|
|
35336
|
+
const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
|
|
35337
|
+
out.set(mcpHelloNonce, 0);
|
|
35338
|
+
out.set(extHelloNonce, mcpHelloNonce.length);
|
|
35339
|
+
out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
|
|
35340
|
+
return out;
|
|
35341
|
+
}
|
|
35265
35342
|
var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
35266
35343
|
"fetch",
|
|
35267
35344
|
"read_cookies",
|
|
@@ -35272,7 +35349,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
35272
35349
|
"read_indexed_db",
|
|
35273
35350
|
"read_dom",
|
|
35274
35351
|
"download",
|
|
35275
|
-
"graphql"
|
|
35352
|
+
"graphql",
|
|
35353
|
+
"write_cookies"
|
|
35276
35354
|
]);
|
|
35277
35355
|
|
|
35278
35356
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
@@ -35383,6 +35461,15 @@ function assertHttpUrl(x, label) {
|
|
|
35383
35461
|
throw new ProtocolError(`${label}: must be http(s), got ${u.protocol}`);
|
|
35384
35462
|
}
|
|
35385
35463
|
}
|
|
35464
|
+
function assertCookiePath(x, label) {
|
|
35465
|
+
assertString(x, label);
|
|
35466
|
+
if (!x.startsWith("/") || x.startsWith("//")) {
|
|
35467
|
+
throw new ProtocolError(`${label}: must be an absolute path like "/campus"`);
|
|
35468
|
+
}
|
|
35469
|
+
if (x.includes("?") || x.includes("#") || x.includes("\\")) {
|
|
35470
|
+
throw new ProtocolError(`${label}: must not contain a query, fragment, or backslash`);
|
|
35471
|
+
}
|
|
35472
|
+
}
|
|
35386
35473
|
function assertHttpsOriginOnly(x, label) {
|
|
35387
35474
|
assertString(x, label);
|
|
35388
35475
|
let u;
|
|
@@ -35827,13 +35914,43 @@ function validateInnerRequest(raw) {
|
|
|
35827
35914
|
}
|
|
35828
35915
|
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
35829
35916
|
assertNonEmptyKeyArray(raw.init.keys, "inner.init.keys");
|
|
35917
|
+
if (raw.init.path !== void 0)
|
|
35918
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
35830
35919
|
for (const k of Object.keys(raw.init)) {
|
|
35831
|
-
if (k !== "origin" && k !== "keys") {
|
|
35920
|
+
if (k !== "origin" && k !== "keys" && k !== "path") {
|
|
35832
35921
|
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_cookies`);
|
|
35833
35922
|
}
|
|
35834
35923
|
}
|
|
35835
35924
|
return raw;
|
|
35836
35925
|
}
|
|
35926
|
+
if (raw.op === "write_cookies") {
|
|
35927
|
+
assertObject(raw.init, "inner.init");
|
|
35928
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
35929
|
+
if (!Array.isArray(raw.init.cookies) || raw.init.cookies.length === 0) {
|
|
35930
|
+
throw new ProtocolError("inner.init.cookies: must be a non-empty array");
|
|
35931
|
+
}
|
|
35932
|
+
for (const [i, entry] of raw.init.cookies.entries()) {
|
|
35933
|
+
assertObject(entry, `inner.init.cookies[${i}]`);
|
|
35934
|
+
assertString(entry.name, `inner.init.cookies[${i}].name`);
|
|
35935
|
+
if (!SCOPE_KEY_RE.test(entry.name)) {
|
|
35936
|
+
throw new ProtocolError(`inner.init.cookies[${i}].name: invalid key ${JSON.stringify(entry.name)}`);
|
|
35937
|
+
}
|
|
35938
|
+
assertString(entry.value, `inner.init.cookies[${i}].value`);
|
|
35939
|
+
for (const k of Object.keys(entry)) {
|
|
35940
|
+
if (k !== "name" && k !== "value") {
|
|
35941
|
+
throw new ProtocolError(`inner.init.cookies[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
35942
|
+
}
|
|
35943
|
+
}
|
|
35944
|
+
}
|
|
35945
|
+
if (raw.init.path !== void 0)
|
|
35946
|
+
assertCookiePath(raw.init.path, "inner.init.path");
|
|
35947
|
+
for (const k of Object.keys(raw.init)) {
|
|
35948
|
+
if (k !== "origin" && k !== "cookies" && k !== "path") {
|
|
35949
|
+
throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on write_cookies`);
|
|
35950
|
+
}
|
|
35951
|
+
}
|
|
35952
|
+
return raw;
|
|
35953
|
+
}
|
|
35837
35954
|
if (raw.op === "read_local_storage" || raw.op === "read_session_storage") {
|
|
35838
35955
|
assertObject(raw.init, "inner.init");
|
|
35839
35956
|
if (raw.init.origin === void 0) {
|
|
@@ -36020,7 +36137,7 @@ function validateInnerRequest(raw) {
|
|
|
36020
36137
|
}
|
|
36021
36138
|
return raw;
|
|
36022
36139
|
}
|
|
36023
|
-
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)}`);
|
|
36140
|
+
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)}`);
|
|
36024
36141
|
}
|
|
36025
36142
|
function assertNonEmptyKeyArray(value, label) {
|
|
36026
36143
|
if (!Array.isArray(value)) {
|
|
@@ -36081,6 +36198,18 @@ function validateInnerResponse(raw) {
|
|
|
36081
36198
|
}
|
|
36082
36199
|
return raw;
|
|
36083
36200
|
}
|
|
36201
|
+
if (op === "write_cookies") {
|
|
36202
|
+
if (raw.written === void 0) {
|
|
36203
|
+
throw new ProtocolError("inner.written: missing on write_cookies response");
|
|
36204
|
+
}
|
|
36205
|
+
if (!Array.isArray(raw.written)) {
|
|
36206
|
+
throw new ProtocolError("inner.written: must be an array");
|
|
36207
|
+
}
|
|
36208
|
+
for (const [i, name] of raw.written.entries()) {
|
|
36209
|
+
assertString(name, `inner.written[${i}]`);
|
|
36210
|
+
}
|
|
36211
|
+
return raw;
|
|
36212
|
+
}
|
|
36084
36213
|
if (op === "read_local_storage" || op === "read_session_storage") {
|
|
36085
36214
|
if (raw.values === void 0) {
|
|
36086
36215
|
throw new ProtocolError(`inner.values: missing on ${String(op)} response`);
|
|
@@ -36558,6 +36687,148 @@ async function awaitSessionReady(ready, opts) {
|
|
|
36558
36687
|
}
|
|
36559
36688
|
}
|
|
36560
36689
|
|
|
36690
|
+
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36691
|
+
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
36692
|
+
import { join as join3 } from "node:path";
|
|
36693
|
+
|
|
36694
|
+
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36695
|
+
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36696
|
+
import { join as join2 } from "node:path";
|
|
36697
|
+
import { homedir } from "node:os";
|
|
36698
|
+
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36699
|
+
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36700
|
+
function defaultIdentityDir() {
|
|
36701
|
+
return join2(homedir(), ".fetchproxy", "identity");
|
|
36702
|
+
}
|
|
36703
|
+
function safeIdentityFileBase(serverName) {
|
|
36704
|
+
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
36705
|
+
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
36706
|
+
}
|
|
36707
|
+
return serverName.replace(/\//g, "_");
|
|
36708
|
+
}
|
|
36709
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36710
|
+
const safeFile = safeIdentityFileBase(serverName);
|
|
36711
|
+
const path = join2(dir, `${safeFile}.json`);
|
|
36712
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36713
|
+
try {
|
|
36714
|
+
const raw = await readFile(path, "utf8");
|
|
36715
|
+
const j2 = JSON.parse(raw);
|
|
36716
|
+
return {
|
|
36717
|
+
x25519Priv: fromB64(j2.x25519Priv),
|
|
36718
|
+
x25519Pub: fromB64(j2.x25519Pub),
|
|
36719
|
+
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
36720
|
+
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
36721
|
+
createdAt: j2.createdAt
|
|
36722
|
+
};
|
|
36723
|
+
} catch (e) {
|
|
36724
|
+
if (e.code !== "ENOENT")
|
|
36725
|
+
throw e;
|
|
36726
|
+
}
|
|
36727
|
+
const x = await generateX25519();
|
|
36728
|
+
const ed = await generateEd25519();
|
|
36729
|
+
const id = {
|
|
36730
|
+
x25519Priv: x.privateKey,
|
|
36731
|
+
x25519Pub: x.publicKey,
|
|
36732
|
+
ed25519Priv: ed.privateKey,
|
|
36733
|
+
ed25519Pub: ed.publicKey,
|
|
36734
|
+
createdAt: Date.now()
|
|
36735
|
+
};
|
|
36736
|
+
const j = {
|
|
36737
|
+
x25519Priv: toB64(id.x25519Priv),
|
|
36738
|
+
x25519Pub: toB64(id.x25519Pub),
|
|
36739
|
+
ed25519Priv: toB64(id.ed25519Priv),
|
|
36740
|
+
ed25519Pub: toB64(id.ed25519Pub),
|
|
36741
|
+
createdAt: id.createdAt
|
|
36742
|
+
};
|
|
36743
|
+
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
36744
|
+
await chmod(path, 384);
|
|
36745
|
+
return id;
|
|
36746
|
+
}
|
|
36747
|
+
|
|
36748
|
+
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36749
|
+
function fileExtensionTrust(args) {
|
|
36750
|
+
return {
|
|
36751
|
+
allowNew: args.allowNew,
|
|
36752
|
+
location: extensionTrustPath(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
36753
|
+
read: () => readExtensionPin(args.serverName, args.dir ?? defaultIdentityDir()),
|
|
36754
|
+
write: (pin) => writeExtensionPin(args.serverName, pin, args.dir ?? defaultIdentityDir())
|
|
36755
|
+
};
|
|
36756
|
+
}
|
|
36757
|
+
var TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
|
|
36758
|
+
function allowNewExtensionIdentity(explicit, env = process.env) {
|
|
36759
|
+
if (explicit !== void 0)
|
|
36760
|
+
return explicit;
|
|
36761
|
+
return env[TRUST_NEW_EXTENSION_ENV] === "1";
|
|
36762
|
+
}
|
|
36763
|
+
function decideExtensionTrust(args) {
|
|
36764
|
+
const { pin, hello, allowNew, serverName } = args;
|
|
36765
|
+
if (!pin)
|
|
36766
|
+
return { decision: "first-use" };
|
|
36767
|
+
if (pin.identityX25519Pub === hello.identityX25519Pub && pin.identityEd25519Pub === hello.identityEd25519Pub) {
|
|
36768
|
+
return { decision: "pinned" };
|
|
36769
|
+
}
|
|
36770
|
+
const trustPath = args.location ?? extensionTrustPathHint(serverName);
|
|
36771
|
+
if (allowNew) {
|
|
36772
|
+
return {
|
|
36773
|
+
decision: "replace",
|
|
36774
|
+
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.`
|
|
36775
|
+
};
|
|
36776
|
+
}
|
|
36777
|
+
return {
|
|
36778
|
+
decision: "refused",
|
|
36779
|
+
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.`
|
|
36780
|
+
};
|
|
36781
|
+
}
|
|
36782
|
+
function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
|
|
36783
|
+
return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
36784
|
+
}
|
|
36785
|
+
function extensionTrustPathHint(serverName) {
|
|
36786
|
+
try {
|
|
36787
|
+
return extensionTrustPath(serverName);
|
|
36788
|
+
} catch {
|
|
36789
|
+
return join3(defaultIdentityDir(), "<server-name>.extension-trust.json");
|
|
36790
|
+
}
|
|
36791
|
+
}
|
|
36792
|
+
function isPin(x) {
|
|
36793
|
+
if (!x || typeof x !== "object")
|
|
36794
|
+
return false;
|
|
36795
|
+
const r = x;
|
|
36796
|
+
return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
|
|
36797
|
+
}
|
|
36798
|
+
async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
|
|
36799
|
+
const path = extensionTrustPath(serverName, dir);
|
|
36800
|
+
let raw;
|
|
36801
|
+
try {
|
|
36802
|
+
raw = await readFile2(path, "utf8");
|
|
36803
|
+
} catch (e) {
|
|
36804
|
+
if (e.code === "ENOENT")
|
|
36805
|
+
return null;
|
|
36806
|
+
throw e;
|
|
36807
|
+
}
|
|
36808
|
+
let parsed;
|
|
36809
|
+
try {
|
|
36810
|
+
parsed = JSON.parse(raw);
|
|
36811
|
+
} catch {
|
|
36812
|
+
throw new Error(`unreadable extension pin at ${path} (not JSON) \u2014 delete it to re-pair`);
|
|
36813
|
+
}
|
|
36814
|
+
if (!isPin(parsed)) {
|
|
36815
|
+
throw new Error(`unreadable extension pin at ${path} (wrong shape) \u2014 delete it to re-pair`);
|
|
36816
|
+
}
|
|
36817
|
+
return {
|
|
36818
|
+
identityX25519Pub: parsed.identityX25519Pub,
|
|
36819
|
+
identityEd25519Pub: parsed.identityEd25519Pub,
|
|
36820
|
+
pinnedAt: parsed.pinnedAt
|
|
36821
|
+
};
|
|
36822
|
+
}
|
|
36823
|
+
async function writeExtensionPin(serverName, pin, dir = defaultIdentityDir()) {
|
|
36824
|
+
const path = extensionTrustPath(serverName, dir);
|
|
36825
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
36826
|
+
const tmp = `${path}.tmp`;
|
|
36827
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
36828
|
+
await chmod2(tmp, 384);
|
|
36829
|
+
await rename(tmp, path);
|
|
36830
|
+
}
|
|
36831
|
+
|
|
36561
36832
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
36562
36833
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
36563
36834
|
var enc2 = new TextEncoder();
|
|
@@ -36611,9 +36882,12 @@ async function startHost(opts) {
|
|
|
36611
36882
|
}
|
|
36612
36883
|
resetSessionPromise();
|
|
36613
36884
|
let extensionHello = null;
|
|
36885
|
+
let extensionClaim = null;
|
|
36614
36886
|
wss.on("connection", (ws) => {
|
|
36615
36887
|
let identified = null;
|
|
36616
36888
|
let peerMcpId = null;
|
|
36889
|
+
let closed = false;
|
|
36890
|
+
let pinOnReady = false;
|
|
36617
36891
|
ws.on("message", async (data) => {
|
|
36618
36892
|
try {
|
|
36619
36893
|
let frame;
|
|
@@ -36625,10 +36899,43 @@ async function startHost(opts) {
|
|
|
36625
36899
|
return;
|
|
36626
36900
|
}
|
|
36627
36901
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
36628
|
-
if (extensionWs) {
|
|
36902
|
+
if (extensionWs || extensionClaim) {
|
|
36629
36903
|
ws.close(1008, "extension already connected");
|
|
36630
36904
|
return;
|
|
36631
36905
|
}
|
|
36906
|
+
extensionClaim = ws;
|
|
36907
|
+
let pin;
|
|
36908
|
+
try {
|
|
36909
|
+
pin = await opts.extensionTrust.read();
|
|
36910
|
+
} catch (e) {
|
|
36911
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
36912
|
+
if (extensionClaim === ws)
|
|
36913
|
+
extensionClaim = null;
|
|
36914
|
+
ws.close(1008, "extension pin unreadable");
|
|
36915
|
+
return;
|
|
36916
|
+
}
|
|
36917
|
+
const outcome = decideExtensionTrust({
|
|
36918
|
+
pin,
|
|
36919
|
+
hello: frame,
|
|
36920
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
36921
|
+
serverName: opts.ownServerName,
|
|
36922
|
+
location: opts.extensionTrust.location
|
|
36923
|
+
});
|
|
36924
|
+
if (outcome.decision === "refused") {
|
|
36925
|
+
console.warn(outcome.message);
|
|
36926
|
+
if (extensionClaim === ws)
|
|
36927
|
+
extensionClaim = null;
|
|
36928
|
+
ws.close(1008, "extension identity is not the pinned one");
|
|
36929
|
+
return;
|
|
36930
|
+
}
|
|
36931
|
+
if (outcome.decision === "replace")
|
|
36932
|
+
console.warn(outcome.message);
|
|
36933
|
+
if (closed || ws.readyState !== import_websocket.default.OPEN) {
|
|
36934
|
+
if (extensionClaim === ws)
|
|
36935
|
+
extensionClaim = null;
|
|
36936
|
+
return;
|
|
36937
|
+
}
|
|
36938
|
+
pinOnReady = outcome.decision !== "pinned";
|
|
36632
36939
|
identified = "extension";
|
|
36633
36940
|
extensionWs = ws;
|
|
36634
36941
|
extensionHello = frame;
|
|
@@ -36640,6 +36947,8 @@ async function startHost(opts) {
|
|
|
36640
36947
|
console.error("[fetchproxy] onPairCode threw:", e);
|
|
36641
36948
|
}
|
|
36642
36949
|
}
|
|
36950
|
+
for (const slot of peers.values())
|
|
36951
|
+
slot.ws.send(JSON.stringify(frame));
|
|
36643
36952
|
ws.send(JSON.stringify(ownHello));
|
|
36644
36953
|
for (const slot of peers.values()) {
|
|
36645
36954
|
ws.send(JSON.stringify(slot.helloFrame));
|
|
@@ -36675,6 +36984,8 @@ async function startHost(opts) {
|
|
|
36675
36984
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
36676
36985
|
if (extensionWs)
|
|
36677
36986
|
extensionWs.send(JSON.stringify(frame));
|
|
36987
|
+
if (extensionHello)
|
|
36988
|
+
ws.send(JSON.stringify(extensionHello));
|
|
36678
36989
|
return;
|
|
36679
36990
|
}
|
|
36680
36991
|
if (frame.type === "ready") {
|
|
@@ -36686,7 +36997,7 @@ async function startHost(opts) {
|
|
|
36686
36997
|
}
|
|
36687
36998
|
const extEdPub = fromB64(extensionHello.identityEd25519Pub);
|
|
36688
36999
|
const extNonce = fromB64(extensionHello.sessionNonce);
|
|
36689
|
-
const msg =
|
|
37000
|
+
const msg = readySignaturePayload(ownSessionNonce, extNonce, fromB64(frame.extensionSessionPub));
|
|
36690
37001
|
const sig = fromB64(frame.sessionSig);
|
|
36691
37002
|
let sigOk = false;
|
|
36692
37003
|
try {
|
|
@@ -36699,6 +37010,18 @@ async function startHost(opts) {
|
|
|
36699
37010
|
ws.close(1008, "extension session signature invalid");
|
|
36700
37011
|
return;
|
|
36701
37012
|
}
|
|
37013
|
+
if (pinOnReady) {
|
|
37014
|
+
pinOnReady = false;
|
|
37015
|
+
try {
|
|
37016
|
+
await opts.extensionTrust.write({
|
|
37017
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
37018
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
37019
|
+
pinnedAt: Date.now()
|
|
37020
|
+
});
|
|
37021
|
+
} catch (e) {
|
|
37022
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
37023
|
+
}
|
|
37024
|
+
}
|
|
36702
37025
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
36703
37026
|
const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
|
|
36704
37027
|
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -36752,6 +37075,9 @@ async function startHost(opts) {
|
|
|
36752
37075
|
}
|
|
36753
37076
|
});
|
|
36754
37077
|
ws.on("close", () => {
|
|
37078
|
+
closed = true;
|
|
37079
|
+
if (extensionClaim === ws)
|
|
37080
|
+
extensionClaim = null;
|
|
36755
37081
|
if (identified === "extension" && extensionWs === ws) {
|
|
36756
37082
|
extensionWs = null;
|
|
36757
37083
|
extensionHello = null;
|
|
@@ -36842,11 +37168,84 @@ async function startPeer(opts) {
|
|
|
36842
37168
|
resolveFirstReady = resolve;
|
|
36843
37169
|
rejectFirstReady = reject;
|
|
36844
37170
|
});
|
|
37171
|
+
let extensionHello = null;
|
|
37172
|
+
let warnedUnverifiable = false;
|
|
37173
|
+
let cachedPin = void 0;
|
|
37174
|
+
const authenticateExtension = async (sessionSig, extensionSessionPub) => {
|
|
37175
|
+
if (!extensionHello) {
|
|
37176
|
+
if (opts.requireExtensionIdentity) {
|
|
37177
|
+
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.`);
|
|
37178
|
+
return false;
|
|
37179
|
+
}
|
|
37180
|
+
if (!warnedUnverifiable) {
|
|
37181
|
+
warnedUnverifiable = true;
|
|
37182
|
+
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.`);
|
|
37183
|
+
}
|
|
37184
|
+
return true;
|
|
37185
|
+
}
|
|
37186
|
+
const payload = readySignaturePayload(sessionNonce, fromB64(extensionHello.sessionNonce), fromB64(extensionSessionPub));
|
|
37187
|
+
let sigOk = false;
|
|
37188
|
+
try {
|
|
37189
|
+
sigOk = await ed25519Verify(fromB64(extensionHello.identityEd25519Pub), payload, fromB64(sessionSig));
|
|
37190
|
+
} catch {
|
|
37191
|
+
sigOk = false;
|
|
37192
|
+
}
|
|
37193
|
+
if (!sigOk) {
|
|
37194
|
+
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
37195
|
+
return false;
|
|
37196
|
+
}
|
|
37197
|
+
if (cachedPin === void 0) {
|
|
37198
|
+
try {
|
|
37199
|
+
cachedPin = await opts.extensionTrust.read();
|
|
37200
|
+
} catch (e) {
|
|
37201
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
37202
|
+
return false;
|
|
37203
|
+
}
|
|
37204
|
+
}
|
|
37205
|
+
const pin = cachedPin;
|
|
37206
|
+
const outcome = decideExtensionTrust({
|
|
37207
|
+
pin,
|
|
37208
|
+
hello: extensionHello,
|
|
37209
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
37210
|
+
serverName: opts.serverName,
|
|
37211
|
+
location: opts.extensionTrust.location
|
|
37212
|
+
});
|
|
37213
|
+
if (outcome.decision === "refused") {
|
|
37214
|
+
console.warn(outcome.message);
|
|
37215
|
+
return false;
|
|
37216
|
+
}
|
|
37217
|
+
if (outcome.decision === "replace")
|
|
37218
|
+
console.warn(outcome.message);
|
|
37219
|
+
if (outcome.decision !== "pinned") {
|
|
37220
|
+
try {
|
|
37221
|
+
const written = {
|
|
37222
|
+
identityX25519Pub: extensionHello.identityX25519Pub,
|
|
37223
|
+
identityEd25519Pub: extensionHello.identityEd25519Pub,
|
|
37224
|
+
pinnedAt: Date.now()
|
|
37225
|
+
};
|
|
37226
|
+
await opts.extensionTrust.write(written);
|
|
37227
|
+
cachedPin = written;
|
|
37228
|
+
} catch (e) {
|
|
37229
|
+
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
37230
|
+
}
|
|
37231
|
+
}
|
|
37232
|
+
return true;
|
|
37233
|
+
};
|
|
36845
37234
|
const onMessage = async (data) => {
|
|
36846
37235
|
try {
|
|
36847
37236
|
const raw = JSON.parse(data.toString());
|
|
36848
37237
|
const frame = validateFrame(raw);
|
|
37238
|
+
if (frame.type === "hello" && frame.role === "extension") {
|
|
37239
|
+
extensionHello = frame;
|
|
37240
|
+
return;
|
|
37241
|
+
}
|
|
36849
37242
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
37243
|
+
const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
|
|
37244
|
+
if (!authorised) {
|
|
37245
|
+
ws.close(1008, "extension identity refused");
|
|
37246
|
+
rejectFirstReady(new Error("peer: extension identity refused"));
|
|
37247
|
+
return;
|
|
37248
|
+
}
|
|
36850
37249
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
36851
37250
|
const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
|
|
36852
37251
|
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
@@ -36927,57 +37326,6 @@ async function startPeer(opts) {
|
|
|
36927
37326
|
return handle;
|
|
36928
37327
|
}
|
|
36929
37328
|
|
|
36930
|
-
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36931
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36932
|
-
import { join as join2 } from "node:path";
|
|
36933
|
-
import { homedir } from "node:os";
|
|
36934
|
-
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36935
|
-
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36936
|
-
function defaultIdentityDir() {
|
|
36937
|
-
return join2(homedir(), ".fetchproxy", "identity");
|
|
36938
|
-
}
|
|
36939
|
-
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36940
|
-
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
36941
|
-
throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
|
|
36942
|
-
}
|
|
36943
|
-
const safeFile = serverName.replace(/\//g, "_");
|
|
36944
|
-
const path = join2(dir, `${safeFile}.json`);
|
|
36945
|
-
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36946
|
-
try {
|
|
36947
|
-
const raw = await readFile(path, "utf8");
|
|
36948
|
-
const j2 = JSON.parse(raw);
|
|
36949
|
-
return {
|
|
36950
|
-
x25519Priv: fromB64(j2.x25519Priv),
|
|
36951
|
-
x25519Pub: fromB64(j2.x25519Pub),
|
|
36952
|
-
ed25519Priv: fromB64(j2.ed25519Priv),
|
|
36953
|
-
ed25519Pub: fromB64(j2.ed25519Pub),
|
|
36954
|
-
createdAt: j2.createdAt
|
|
36955
|
-
};
|
|
36956
|
-
} catch (e) {
|
|
36957
|
-
if (e.code !== "ENOENT")
|
|
36958
|
-
throw e;
|
|
36959
|
-
}
|
|
36960
|
-
const x = await generateX25519();
|
|
36961
|
-
const ed = await generateEd25519();
|
|
36962
|
-
const id = {
|
|
36963
|
-
x25519Priv: x.privateKey,
|
|
36964
|
-
x25519Pub: x.publicKey,
|
|
36965
|
-
ed25519Priv: ed.privateKey,
|
|
36966
|
-
ed25519Pub: ed.publicKey,
|
|
36967
|
-
createdAt: Date.now()
|
|
36968
|
-
};
|
|
36969
|
-
const j = {
|
|
36970
|
-
x25519Priv: toB64(id.x25519Priv),
|
|
36971
|
-
x25519Pub: toB64(id.x25519Pub),
|
|
36972
|
-
ed25519Priv: toB64(id.ed25519Priv),
|
|
36973
|
-
ed25519Pub: toB64(id.ed25519Pub),
|
|
36974
|
-
createdAt: id.createdAt
|
|
36975
|
-
};
|
|
36976
|
-
await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
|
|
36977
|
-
await chmod(path, 384);
|
|
36978
|
-
return id;
|
|
36979
|
-
}
|
|
36980
|
-
|
|
36981
37329
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
36982
37330
|
function classifyFetchError(error51) {
|
|
36983
37331
|
if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
|
|
@@ -37018,6 +37366,17 @@ function classifyBridgeError(err) {
|
|
|
37018
37366
|
}
|
|
37019
37367
|
|
|
37020
37368
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
37369
|
+
function envWsPort() {
|
|
37370
|
+
const raw = process.env.FETCHPROXY_WS_PORT;
|
|
37371
|
+
if (raw === void 0 || raw.trim() === "")
|
|
37372
|
+
return void 0;
|
|
37373
|
+
if (!/^\d+$/.test(raw.trim()))
|
|
37374
|
+
return void 0;
|
|
37375
|
+
const port = Number(raw.trim());
|
|
37376
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
37377
|
+
return void 0;
|
|
37378
|
+
return port;
|
|
37379
|
+
}
|
|
37021
37380
|
var FetchproxyProtocolError = class extends Error {
|
|
37022
37381
|
constructor(message) {
|
|
37023
37382
|
super(message);
|
|
@@ -37059,6 +37418,39 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
37059
37418
|
this.hint = hint;
|
|
37060
37419
|
}
|
|
37061
37420
|
};
|
|
37421
|
+
var FetchproxyHintedError = class extends FetchproxyProtocolError {
|
|
37422
|
+
/** The extension's raw rejection, unmodified. */
|
|
37423
|
+
originalError;
|
|
37424
|
+
/** What the user should actually do, in prose. */
|
|
37425
|
+
hint;
|
|
37426
|
+
constructor(originalError, hint) {
|
|
37427
|
+
super(`${originalError} \u2014 ${hint}`);
|
|
37428
|
+
this.name = "FetchproxyHintedError";
|
|
37429
|
+
this.originalError = originalError;
|
|
37430
|
+
this.hint = hint;
|
|
37431
|
+
}
|
|
37432
|
+
};
|
|
37433
|
+
var FetchproxyScopeError = class extends FetchproxyHintedError {
|
|
37434
|
+
constructor(originalError) {
|
|
37435
|
+
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.");
|
|
37436
|
+
this.name = "FetchproxyScopeError";
|
|
37437
|
+
}
|
|
37438
|
+
};
|
|
37439
|
+
var FetchproxyNoTabError = class extends FetchproxyHintedError {
|
|
37440
|
+
constructor(originalError) {
|
|
37441
|
+
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.");
|
|
37442
|
+
this.name = "FetchproxyNoTabError";
|
|
37443
|
+
}
|
|
37444
|
+
};
|
|
37445
|
+
var SCOPE_REJECTION = /not in declared/;
|
|
37446
|
+
var NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
|
|
37447
|
+
function protocolErrorFrom(error51) {
|
|
37448
|
+
if (SCOPE_REJECTION.test(error51))
|
|
37449
|
+
return new FetchproxyScopeError(error51);
|
|
37450
|
+
if (NO_TAB_REJECTION.test(error51))
|
|
37451
|
+
return new FetchproxyNoTabError(error51);
|
|
37452
|
+
return new FetchproxyProtocolError(error51);
|
|
37453
|
+
}
|
|
37062
37454
|
var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
37063
37455
|
url;
|
|
37064
37456
|
timeoutMs;
|
|
@@ -37088,6 +37480,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
37088
37480
|
}
|
|
37089
37481
|
};
|
|
37090
37482
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
37483
|
+
function normalizeCookiePath(path) {
|
|
37484
|
+
if (path === void 0 || path === "")
|
|
37485
|
+
return void 0;
|
|
37486
|
+
const trimmed = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
|
|
37487
|
+
try {
|
|
37488
|
+
assertCookiePath(trimmed, "path");
|
|
37489
|
+
} catch (e) {
|
|
37490
|
+
throw new Error(`FetchproxyServer: ${e instanceof Error ? e.message : String(e)} (got ${JSON.stringify(path)})`);
|
|
37491
|
+
}
|
|
37492
|
+
return trimmed;
|
|
37493
|
+
}
|
|
37091
37494
|
function assertSubdomainLabel(label) {
|
|
37092
37495
|
if (!SUBDOMAIN_LABEL_RE.test(label)) {
|
|
37093
37496
|
throw new Error(`FetchproxyServer: subdomain must be a DNS label like "www" or "api" (or dot-separated like "auth.api"), got ${JSON.stringify(label)}`);
|
|
@@ -37152,6 +37555,9 @@ var FetchproxyServer = class {
|
|
|
37152
37555
|
// them off from `pending` (fetch) and `pendingReadCookies` (legacy
|
|
37153
37556
|
// string-shape) so the response routing in `onInner` stays linear.
|
|
37154
37557
|
pendingStorage = /* @__PURE__ */ new Map();
|
|
37558
|
+
// 1.12.0+: write-cookies awaiters resolve the list of names actually
|
|
37559
|
+
// written, so a caller can confirm rather than assume.
|
|
37560
|
+
pendingWriteCookies = /* @__PURE__ */ new Map();
|
|
37155
37561
|
// 0.3.0+: capture-header awaiters resolve a single string.
|
|
37156
37562
|
pendingCapture = /* @__PURE__ */ new Map();
|
|
37157
37563
|
// capture_redirect awaiters resolve the captured redirect URL string.
|
|
@@ -37218,7 +37624,7 @@ var FetchproxyServer = class {
|
|
|
37218
37624
|
}
|
|
37219
37625
|
}
|
|
37220
37626
|
this.opts = {
|
|
37221
|
-
port: opts.port ?? 37149,
|
|
37627
|
+
port: opts.port ?? envWsPort() ?? 37149,
|
|
37222
37628
|
host: opts.host ?? "127.0.0.1",
|
|
37223
37629
|
serverName: opts.serverName,
|
|
37224
37630
|
version: opts.version,
|
|
@@ -37278,6 +37684,8 @@ var FetchproxyServer = class {
|
|
|
37278
37684
|
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
37279
37685
|
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
37280
37686
|
identityDir: opts.identityDir,
|
|
37687
|
+
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
37688
|
+
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
37281
37689
|
onPairCode: opts.onPairCode
|
|
37282
37690
|
};
|
|
37283
37691
|
}
|
|
@@ -37377,7 +37785,8 @@ var FetchproxyServer = class {
|
|
|
37377
37785
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37378
37786
|
ownDomSelectors: this.opts.domSelectors,
|
|
37379
37787
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
37380
|
-
onPairCode: this.opts.onPairCode
|
|
37788
|
+
onPairCode: this.opts.onPairCode,
|
|
37789
|
+
extensionTrust: this.extensionTrust()
|
|
37381
37790
|
});
|
|
37382
37791
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
37383
37792
|
this.hostHandle.onExtensionDisconnect(() => {
|
|
@@ -37406,7 +37815,9 @@ var FetchproxyServer = class {
|
|
|
37406
37815
|
localStoragePointers: this.opts.localStoragePointers,
|
|
37407
37816
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
37408
37817
|
domSelectors: this.opts.domSelectors,
|
|
37409
|
-
graphqlOps: this.opts.graphqlOps
|
|
37818
|
+
graphqlOps: this.opts.graphqlOps,
|
|
37819
|
+
extensionTrust: this.extensionTrust(),
|
|
37820
|
+
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
37410
37821
|
});
|
|
37411
37822
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
37412
37823
|
this.peerHandle.onRenegotiate(() => {
|
|
@@ -37545,6 +37956,23 @@ var FetchproxyServer = class {
|
|
|
37545
37956
|
markActive() {
|
|
37546
37957
|
this.noteActivityForKeepalive();
|
|
37547
37958
|
}
|
|
37959
|
+
/**
|
|
37960
|
+
* #208: this MCP's pin on the extension's identity, stored beside its own
|
|
37961
|
+
* identity key and so following `identityDir` wherever the caller put it.
|
|
37962
|
+
*
|
|
37963
|
+
* `allowNewExtensionIdentity` falls back to an environment variable when the
|
|
37964
|
+
* caller expressed no opinion, because the thirteen MCPs that construct this
|
|
37965
|
+
* class are separate packages: an operator whose extension re-install has
|
|
37966
|
+
* just locked all of them out needs one lever that does not require patching
|
|
37967
|
+
* every one of them.
|
|
37968
|
+
*/
|
|
37969
|
+
extensionTrust() {
|
|
37970
|
+
return fileExtensionTrust({
|
|
37971
|
+
serverName: this.opts.serverName,
|
|
37972
|
+
dir: this.opts.identityDir,
|
|
37973
|
+
allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
|
|
37974
|
+
});
|
|
37975
|
+
}
|
|
37548
37976
|
noteActivityForKeepalive() {
|
|
37549
37977
|
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
37550
37978
|
if (intervalMs <= 0)
|
|
@@ -37608,6 +38036,7 @@ var FetchproxyServer = class {
|
|
|
37608
38036
|
this.pending.delete(id);
|
|
37609
38037
|
this.pendingReadCookies.delete(id);
|
|
37610
38038
|
this.pendingStorage.delete(id);
|
|
38039
|
+
this.pendingWriteCookies.delete(id);
|
|
37611
38040
|
this.pendingCapture.delete(id);
|
|
37612
38041
|
this.pendingRedirect.delete(id);
|
|
37613
38042
|
this.pendingDownload.delete(id);
|
|
@@ -37729,7 +38158,7 @@ var FetchproxyServer = class {
|
|
|
37729
38158
|
port: this.opts.port
|
|
37730
38159
|
});
|
|
37731
38160
|
}
|
|
37732
|
-
return
|
|
38161
|
+
return protocolErrorFrom(result.error);
|
|
37733
38162
|
}
|
|
37734
38163
|
/**
|
|
37735
38164
|
* Convenience wrapper around `fetch()`. Builds the URL from a path
|
|
@@ -37765,10 +38194,20 @@ var FetchproxyServer = class {
|
|
|
37765
38194
|
}
|
|
37766
38195
|
const url2 = isAbsolute ? path : `https://${host}${path}`;
|
|
37767
38196
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
38197
|
+
let tabUrl = `https://${host}/`;
|
|
38198
|
+
if (opts.viaTab !== void 0) {
|
|
38199
|
+
try {
|
|
38200
|
+
new URL(opts.viaTab);
|
|
38201
|
+
} catch {
|
|
38202
|
+
throw new Error(`FetchproxyServer.request: viaTab is not a valid URL: ${JSON.stringify(opts.viaTab)}`);
|
|
38203
|
+
}
|
|
38204
|
+
assertUrlInDomains("viaTab", opts.viaTab, this.opts.domains);
|
|
38205
|
+
tabUrl = opts.viaTab;
|
|
38206
|
+
}
|
|
37768
38207
|
const init = {
|
|
37769
38208
|
url: url2,
|
|
37770
38209
|
method,
|
|
37771
|
-
tabUrl
|
|
38210
|
+
tabUrl,
|
|
37772
38211
|
headers: opts.headers,
|
|
37773
38212
|
body: opts.body
|
|
37774
38213
|
};
|
|
@@ -37980,9 +38419,14 @@ var FetchproxyServer = class {
|
|
|
37980
38419
|
let inner;
|
|
37981
38420
|
if (opts.keys !== void 0) {
|
|
37982
38421
|
this.assertScopeSubset(opts.keys, this.opts.cookieKeys, "cookieKeys");
|
|
38422
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
37983
38423
|
const initV3 = {
|
|
38424
|
+
// Origin stays BARE. The path travels as its own validated field —
|
|
38425
|
+
// `assertHttpsOriginOnly` deliberately refuses a path here so one
|
|
38426
|
+
// cannot be used to re-point the read past the domain gate.
|
|
37984
38427
|
origin: `https://${host}`,
|
|
37985
|
-
keys: [...opts.keys]
|
|
38428
|
+
keys: [...opts.keys],
|
|
38429
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
37986
38430
|
};
|
|
37987
38431
|
inner = { type: "request", id, op: "read_cookies", init: initV3 };
|
|
37988
38432
|
} else {
|
|
@@ -37995,10 +38439,65 @@ var FetchproxyServer = class {
|
|
|
37995
38439
|
await this.sendInnerFrame(inner);
|
|
37996
38440
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37997
38441
|
if (!result.ok) {
|
|
37998
|
-
throw
|
|
38442
|
+
throw protocolErrorFrom(result.error);
|
|
37999
38443
|
}
|
|
38000
38444
|
return result.cookies;
|
|
38001
38445
|
}
|
|
38446
|
+
/**
|
|
38447
|
+
* 1.12.0+: overwrite the value of cookies this MCP already declares.
|
|
38448
|
+
*
|
|
38449
|
+
* The bridge's only write verb, and it exists for one failure class. Sites
|
|
38450
|
+
* that ROTATE a credential cookie hand back a new value on every refresh; if
|
|
38451
|
+
* the MCP refreshes and keeps the result to itself, the copy in the browser's
|
|
38452
|
+
* cookie jar is dead, and the user gets signed out of a tab they never
|
|
38453
|
+
* touched — usually reported to them as "inactivity". Writing the rotated
|
|
38454
|
+
* value back is the only thing that repairs it.
|
|
38455
|
+
*
|
|
38456
|
+
* Requires `'write_cookies'` in capabilities, which the user approves at pair
|
|
38457
|
+
* time as its own line. Every name must ALSO be in declared `cookieKeys`: a
|
|
38458
|
+
* write can never reach a cookie the MCP was not already trusted to read, so
|
|
38459
|
+
* granting it cannot widen which cookies are in play — only what may be done
|
|
38460
|
+
* to the ones already listed.
|
|
38461
|
+
*
|
|
38462
|
+
* The extension refuses the whole request unless every named cookie already
|
|
38463
|
+
* exists; this refreshes a value in place and deliberately cannot author new
|
|
38464
|
+
* cookies. Returns the names actually written.
|
|
38465
|
+
*/
|
|
38466
|
+
async writeCookies(opts) {
|
|
38467
|
+
if (!this.opts.capabilities.includes("write_cookies")) {
|
|
38468
|
+
throw new Error('FetchproxyServer.writeCookies(): MCP did not declare "write_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
|
|
38469
|
+
}
|
|
38470
|
+
const names = Object.keys(opts.cookies);
|
|
38471
|
+
if (names.length === 0) {
|
|
38472
|
+
throw new Error("FetchproxyServer.writeCookies(): no cookies given");
|
|
38473
|
+
}
|
|
38474
|
+
await this.ensureConnected();
|
|
38475
|
+
this.throwIfPendingPair();
|
|
38476
|
+
if (opts.subdomain !== void 0)
|
|
38477
|
+
assertSubdomainLabel(opts.subdomain);
|
|
38478
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
38479
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
38480
|
+
this.assertScopeSubset(names, this.opts.cookieKeys, "cookieKeys");
|
|
38481
|
+
const cookiePath = normalizeCookiePath(opts.path);
|
|
38482
|
+
const id = this.nextRequestId++;
|
|
38483
|
+
const inner = {
|
|
38484
|
+
type: "request",
|
|
38485
|
+
id,
|
|
38486
|
+
op: "write_cookies",
|
|
38487
|
+
init: {
|
|
38488
|
+
// Bare origin, same invariant as the read path: a path must never be
|
|
38489
|
+
// able to move the request past the domain gate.
|
|
38490
|
+
origin: `https://${host}`,
|
|
38491
|
+
cookies: Object.entries(opts.cookies).map(([name, value]) => ({ name, value })),
|
|
38492
|
+
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
38493
|
+
}
|
|
38494
|
+
};
|
|
38495
|
+
const pending = new Promise((resolve, reject) => {
|
|
38496
|
+
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
38497
|
+
});
|
|
38498
|
+
await this.sendInnerFrame(inner);
|
|
38499
|
+
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
38500
|
+
}
|
|
38002
38501
|
/**
|
|
38003
38502
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
38004
38503
|
* tab. Requires `'read_local_storage'` in capabilities AND each key
|
|
@@ -38545,7 +39044,7 @@ var FetchproxyServer = class {
|
|
|
38545
39044
|
storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
|
|
38546
39045
|
}
|
|
38547
39046
|
} else {
|
|
38548
|
-
storageCb.reject(
|
|
39047
|
+
storageCb.reject(protocolErrorFrom(inner.error));
|
|
38549
39048
|
}
|
|
38550
39049
|
return;
|
|
38551
39050
|
}
|
|
@@ -38559,7 +39058,7 @@ var FetchproxyServer = class {
|
|
|
38559
39058
|
captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
|
|
38560
39059
|
}
|
|
38561
39060
|
} else {
|
|
38562
|
-
captureCb.reject(
|
|
39061
|
+
captureCb.reject(protocolErrorFrom(inner.error));
|
|
38563
39062
|
}
|
|
38564
39063
|
return;
|
|
38565
39064
|
}
|
|
@@ -38573,7 +39072,7 @@ var FetchproxyServer = class {
|
|
|
38573
39072
|
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
38574
39073
|
}
|
|
38575
39074
|
} else {
|
|
38576
|
-
redirectCb.reject(
|
|
39075
|
+
redirectCb.reject(protocolErrorFrom(inner.error));
|
|
38577
39076
|
}
|
|
38578
39077
|
return;
|
|
38579
39078
|
}
|
|
@@ -38587,7 +39086,7 @@ var FetchproxyServer = class {
|
|
|
38587
39086
|
idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
|
|
38588
39087
|
}
|
|
38589
39088
|
} else {
|
|
38590
|
-
idbCb.reject(
|
|
39089
|
+
idbCb.reject(protocolErrorFrom(inner.error));
|
|
38591
39090
|
}
|
|
38592
39091
|
return;
|
|
38593
39092
|
}
|
|
@@ -38601,7 +39100,7 @@ var FetchproxyServer = class {
|
|
|
38601
39100
|
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
38602
39101
|
}
|
|
38603
39102
|
} else {
|
|
38604
|
-
downloadCb.reject(
|
|
39103
|
+
downloadCb.reject(protocolErrorFrom(inner.error));
|
|
38605
39104
|
}
|
|
38606
39105
|
return;
|
|
38607
39106
|
}
|
|
@@ -38615,7 +39114,17 @@ var FetchproxyServer = class {
|
|
|
38615
39114
|
graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
|
|
38616
39115
|
}
|
|
38617
39116
|
} else {
|
|
38618
|
-
graphqlCb.reject(
|
|
39117
|
+
graphqlCb.reject(protocolErrorFrom(inner.error));
|
|
39118
|
+
}
|
|
39119
|
+
return;
|
|
39120
|
+
}
|
|
39121
|
+
const writeCookiesCb = this.pendingWriteCookies.get(inner.id);
|
|
39122
|
+
if (writeCookiesCb) {
|
|
39123
|
+
this.pendingWriteCookies.delete(inner.id);
|
|
39124
|
+
if (inner.ok && inner.op === "write_cookies") {
|
|
39125
|
+
writeCookiesCb.resolve([...inner.written]);
|
|
39126
|
+
} else {
|
|
39127
|
+
writeCookiesCb.reject(protocolErrorFrom(inner.ok ? "write_cookies response had the wrong op" : inner.error));
|
|
38619
39128
|
}
|
|
38620
39129
|
return;
|
|
38621
39130
|
}
|
|
@@ -38658,6 +39167,9 @@ var FetchproxyServer = class {
|
|
|
38658
39167
|
for (const { reject } of this.pendingStorage.values())
|
|
38659
39168
|
reject(err);
|
|
38660
39169
|
this.pendingStorage.clear();
|
|
39170
|
+
for (const { reject } of this.pendingWriteCookies.values())
|
|
39171
|
+
reject(err);
|
|
39172
|
+
this.pendingWriteCookies.clear();
|
|
38661
39173
|
for (const { reject } of this.pendingCapture.values())
|
|
38662
39174
|
reject(err);
|
|
38663
39175
|
this.pendingCapture.clear();
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Single source of the server version. release-please bumps the literal below. */
|
|
2
|
-
export const VERSION = '0.3.
|
|
2
|
+
export const VERSION = '0.3.4'; // x-release-please-version
|
package/mint.yaml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
name: TripAdvisor
|
|
3
|
+
slug: tripadvisor
|
|
4
|
+
summary: >-
|
|
5
|
+
TripAdvisor location search, details, photos, and reviews for Claude via
|
|
6
|
+
the Terra API
|
|
7
|
+
#
|
|
8
|
+
# Hosting note (a comment, not user-facing summary text): this is a
|
|
9
|
+
# BROWSER-BRIDGE MCP — it reaches its site through the user's signed-in
|
|
10
|
+
# tab via the fetchproxy bridge. A bridged registration also needs runtime
|
|
11
|
+
# `fly-shared`, `bridge: true` and a `bridgePortEnv`, which are registration
|
|
12
|
+
# fields this manifest has no schema for (set them over the control API).
|
|
13
|
+
# `state.dataDir` below is required for `bridge`.
|
|
14
|
+
env:
|
|
15
|
+
- name: TRIPADVISOR_API_KEY
|
|
16
|
+
secret: true
|
|
17
|
+
required: true
|
|
18
|
+
help: >-
|
|
19
|
+
Your TripAdvisor Terra API key (tripadvisor.com/developers)
|
|
20
|
+
- name: TRIPADVISOR_REQUEST_TIMEOUT_MS
|
|
21
|
+
required: false
|
|
22
|
+
help: >-
|
|
23
|
+
Per-request timeout in milliseconds. Raise it if calls time out on slow
|
|
24
|
+
upstream responses.
|
|
25
|
+
- name: TRIPADVISOR_WS_PORT
|
|
26
|
+
required: false
|
|
27
|
+
help: >-
|
|
28
|
+
Concentrator port for the fetchproxy browser bridge (defaults to the
|
|
29
|
+
fleet-shared 37149). The runner injects a per-registration port for a
|
|
30
|
+
hosted bridged child, so this is the name to give bridgePortEnv at
|
|
31
|
+
registration.
|
|
32
|
+
- name: TRIPADVISOR_CACHE_TTL
|
|
33
|
+
required: false
|
|
34
|
+
help: >-
|
|
35
|
+
Seconds to cache search responses (default 300; 0 disables).
|
|
36
|
+
- name: TRIPADVISOR_STATIC_CACHE_TTL
|
|
37
|
+
required: false
|
|
38
|
+
help: >-
|
|
39
|
+
Seconds to cache details, photos and reviews (default 3600; 0 disables).
|
|
40
|
+
- name: TRIPADVISOR_DEBUG_LOG
|
|
41
|
+
required: false
|
|
42
|
+
help: >-
|
|
43
|
+
Set to 1 to log browser-bridge requests to stderr.
|
|
44
|
+
state:
|
|
45
|
+
dataDir: true
|
|
46
|
+
reason: >-
|
|
47
|
+
The fetchproxy identity lives at $HOME/.fetchproxy/identity/<name>.json
|
|
48
|
+
and the pair code derives from it. Without a persistent $HOME every cold
|
|
49
|
+
start mints a fresh identity and re-prompts pairing in the browser; the
|
|
50
|
+
API refuses bridge without it.
|
|
51
|
+
egress:
|
|
52
|
+
allow:
|
|
53
|
+
# Only hosts the SERVER process actually fetches. Hosts that appear
|
|
54
|
+
# solely in a URL this server BUILDS and returns are excluded.
|
|
55
|
+
- terra.tripadvisor.com
|
|
56
|
+
- tripadvisor.com
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrischall/tripadvisor-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"mcpName": "io.github.chrischall/tripadvisor-mcp",
|
|
5
5
|
"description": "TripAdvisor Terra API MCP server for Claude — search locations, details, photos, and reviews. Developed and maintained by AI (Claude Code).",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -33,19 +33,21 @@
|
|
|
33
33
|
".claude-plugin",
|
|
34
34
|
"skills",
|
|
35
35
|
".mcp.json",
|
|
36
|
-
"server.json"
|
|
36
|
+
"server.json",
|
|
37
|
+
"mint.yaml"
|
|
37
38
|
],
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "tsc && npm run bundle",
|
|
40
41
|
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from \"module\"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js",
|
|
41
42
|
"dev": "node dist/index.js",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
+
"test": "npm run typecheck && vitest run",
|
|
43
44
|
"test:watch": "vitest",
|
|
44
|
-
"test:coverage": "vitest run --coverage"
|
|
45
|
+
"test:coverage": "npm run typecheck && vitest run --coverage",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
47
|
-
"@chrischall/mcp-utils": "^0.
|
|
48
|
-
"@fetchproxy/server": "^
|
|
49
|
+
"@chrischall/mcp-utils": "^0.15.0",
|
|
50
|
+
"@fetchproxy/server": "^2.0.0",
|
|
49
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
52
|
"dotenv": "^17.4.0",
|
|
51
53
|
"zod": "^4.4.2"
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/tripadvisor-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.4",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@chrischall/tripadvisor-mcp",
|
|
14
|
-
"version": "0.3.
|
|
14
|
+
"version": "0.3.4",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: tripadvisor
|
|
2
|
+
name: tripadvisor
|
|
3
3
|
description: TripAdvisor travel data via the Terra API through MCP. Use when the user asks to find hotels, restaurants, or attractions, look up a place's TripAdvisor rating/reviews/photos, compare places to stay or eat, or find what's near a location. Triggers on phrases like "find a hotel in", "best restaurants near", "TripAdvisor reviews for", "what's the rating of", "things to do in", or "attractions near me". Requires the @chrischall/tripadvisor-mcp package installed and the tripadvisor server registered (see Setup), plus a TripAdvisor Terra API key.
|
|
4
4
|
---
|
|
5
5
|
|