@chrischall/tripadvisor-mcp 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.2"
10
+ "version": "0.3.3"
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.2",
18
+ "version": "0.3.3",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tripadvisor-mcp",
3
3
  "displayName": "TripAdvisor",
4
- "version": "0.3.2",
4
+ "version": "0.3.3",
5
5
  "description": "MCP server for the TripAdvisor Terra API — location search, details, photos, and reviews",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/bundle.js CHANGED
@@ -26725,17 +26725,33 @@ function normalizeObjectSchema(schema) {
26725
26725
  }
26726
26726
  return void 0;
26727
26727
  }
26728
+ function getDotPath(path) {
26729
+ if (path.length === 0) {
26730
+ return "object root";
26731
+ }
26732
+ return path.reduce((acc, seg, index) => {
26733
+ if (index === 0) {
26734
+ return String(seg);
26735
+ }
26736
+ if (typeof seg === "number") {
26737
+ return `${acc}[${seg}]`;
26738
+ }
26739
+ return `${acc}.${seg}`;
26740
+ }, "");
26741
+ }
26728
26742
  function getParseErrorMessage(error51) {
26729
26743
  if (error51 && typeof error51 === "object") {
26744
+ if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
26745
+ return error51.issues.map((i) => {
26746
+ if (!i.path?.length) {
26747
+ return i.message;
26748
+ }
26749
+ return `${i.message} at ${getDotPath(i.path)}`;
26750
+ }).join("\n");
26751
+ }
26730
26752
  if ("message" in error51 && typeof error51.message === "string") {
26731
26753
  return error51.message;
26732
26754
  }
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
26755
  try {
26740
26756
  return JSON.stringify(error51);
26741
26757
  } catch {
@@ -33350,16 +33366,7 @@ var Server = class extends Protocol {
33350
33366
  if (!methodSchema) {
33351
33367
  throw new Error("Schema is missing a method literal");
33352
33368
  }
33353
- let methodValue;
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
- }
33369
+ const methodValue = getLiteralValue(methodSchema);
33363
33370
  if (typeof methodValue !== "string") {
33364
33371
  throw new Error("Schema method literal must be a string");
33365
33372
  }
@@ -34547,8 +34554,17 @@ var EMPTY_COMPLETION_RESULT = {
34547
34554
  import process3 from "node:process";
34548
34555
 
34549
34556
  // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
34557
+ var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
34550
34558
  var ReadBuffer = class {
34559
+ constructor(options) {
34560
+ this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
34561
+ }
34551
34562
  append(chunk2) {
34563
+ const newSize = (this._buffer?.length ?? 0) + chunk2.length;
34564
+ if (newSize > this._maxBufferSize) {
34565
+ this.clear();
34566
+ throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
34567
+ }
34552
34568
  this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
34553
34569
  }
34554
34570
  readMessage() {
@@ -34576,18 +34592,24 @@ function serializeMessage(message) {
34576
34592
 
34577
34593
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
34578
34594
  var StdioServerTransport = class {
34579
- constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
34595
+ constructor(_stdin = process3.stdin, _stdout = process3.stdout, options) {
34580
34596
  this._stdin = _stdin;
34581
34597
  this._stdout = _stdout;
34582
- this._readBuffer = new ReadBuffer();
34583
34598
  this._started = false;
34584
34599
  this._ondata = (chunk2) => {
34585
- this._readBuffer.append(chunk2);
34586
- this.processReadBuffer();
34600
+ try {
34601
+ this._readBuffer.append(chunk2);
34602
+ this.processReadBuffer();
34603
+ } catch (error51) {
34604
+ this.onerror?.(error51);
34605
+ this.close().catch(() => {
34606
+ });
34607
+ }
34587
34608
  };
34588
34609
  this._onerror = (error51) => {
34589
34610
  this.onerror?.(error51);
34590
34611
  };
34612
+ this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
34591
34613
  }
34592
34614
  /**
34593
34615
  * Starts listening for messages on stdin.
@@ -34936,7 +34958,7 @@ var pageSchema = {
34936
34958
  };
34937
34959
 
34938
34960
  // src/version.ts
34939
- var VERSION = "0.3.2";
34961
+ var VERSION = "0.3.3";
34940
34962
 
34941
34963
  // src/client.ts
34942
34964
  import { dirname, join } from "node:path";
@@ -35260,8 +35282,15 @@ function registerLocationTools(server) {
35260
35282
  }
35261
35283
 
35262
35284
  // node_modules/@fetchproxy/protocol/dist/frames.js
35263
- var PROTOCOL_VERSION = 2;
35285
+ var PROTOCOL_VERSION = 3;
35264
35286
  var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
35287
+ function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub) {
35288
+ const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
35289
+ out.set(mcpHelloNonce, 0);
35290
+ out.set(extHelloNonce, mcpHelloNonce.length);
35291
+ out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
35292
+ return out;
35293
+ }
35265
35294
  var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
35266
35295
  "fetch",
35267
35296
  "read_cookies",
@@ -35272,7 +35301,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
35272
35301
  "read_indexed_db",
35273
35302
  "read_dom",
35274
35303
  "download",
35275
- "graphql"
35304
+ "graphql",
35305
+ "write_cookies"
35276
35306
  ]);
35277
35307
 
35278
35308
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35383,6 +35413,15 @@ function assertHttpUrl(x, label) {
35383
35413
  throw new ProtocolError(`${label}: must be http(s), got ${u.protocol}`);
35384
35414
  }
35385
35415
  }
35416
+ function assertCookiePath(x, label) {
35417
+ assertString(x, label);
35418
+ if (!x.startsWith("/") || x.startsWith("//")) {
35419
+ throw new ProtocolError(`${label}: must be an absolute path like "/campus"`);
35420
+ }
35421
+ if (x.includes("?") || x.includes("#") || x.includes("\\")) {
35422
+ throw new ProtocolError(`${label}: must not contain a query, fragment, or backslash`);
35423
+ }
35424
+ }
35386
35425
  function assertHttpsOriginOnly(x, label) {
35387
35426
  assertString(x, label);
35388
35427
  let u;
@@ -35827,13 +35866,43 @@ function validateInnerRequest(raw) {
35827
35866
  }
35828
35867
  assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
35829
35868
  assertNonEmptyKeyArray(raw.init.keys, "inner.init.keys");
35869
+ if (raw.init.path !== void 0)
35870
+ assertCookiePath(raw.init.path, "inner.init.path");
35830
35871
  for (const k of Object.keys(raw.init)) {
35831
- if (k !== "origin" && k !== "keys") {
35872
+ if (k !== "origin" && k !== "keys" && k !== "path") {
35832
35873
  throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_cookies`);
35833
35874
  }
35834
35875
  }
35835
35876
  return raw;
35836
35877
  }
35878
+ if (raw.op === "write_cookies") {
35879
+ assertObject(raw.init, "inner.init");
35880
+ assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
35881
+ if (!Array.isArray(raw.init.cookies) || raw.init.cookies.length === 0) {
35882
+ throw new ProtocolError("inner.init.cookies: must be a non-empty array");
35883
+ }
35884
+ for (const [i, entry] of raw.init.cookies.entries()) {
35885
+ assertObject(entry, `inner.init.cookies[${i}]`);
35886
+ assertString(entry.name, `inner.init.cookies[${i}].name`);
35887
+ if (!SCOPE_KEY_RE.test(entry.name)) {
35888
+ throw new ProtocolError(`inner.init.cookies[${i}].name: invalid key ${JSON.stringify(entry.name)}`);
35889
+ }
35890
+ assertString(entry.value, `inner.init.cookies[${i}].value`);
35891
+ for (const k of Object.keys(entry)) {
35892
+ if (k !== "name" && k !== "value") {
35893
+ throw new ProtocolError(`inner.init.cookies[${i}]: unexpected field ${JSON.stringify(k)}`);
35894
+ }
35895
+ }
35896
+ }
35897
+ if (raw.init.path !== void 0)
35898
+ assertCookiePath(raw.init.path, "inner.init.path");
35899
+ for (const k of Object.keys(raw.init)) {
35900
+ if (k !== "origin" && k !== "cookies" && k !== "path") {
35901
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on write_cookies`);
35902
+ }
35903
+ }
35904
+ return raw;
35905
+ }
35837
35906
  if (raw.op === "read_local_storage" || raw.op === "read_session_storage") {
35838
35907
  assertObject(raw.init, "inner.init");
35839
35908
  if (raw.init.origin === void 0) {
@@ -36020,7 +36089,7 @@ function validateInnerRequest(raw) {
36020
36089
  }
36021
36090
  return raw;
36022
36091
  }
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)}`);
36092
+ 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
36093
  }
36025
36094
  function assertNonEmptyKeyArray(value, label) {
36026
36095
  if (!Array.isArray(value)) {
@@ -36081,6 +36150,18 @@ function validateInnerResponse(raw) {
36081
36150
  }
36082
36151
  return raw;
36083
36152
  }
36153
+ if (op === "write_cookies") {
36154
+ if (raw.written === void 0) {
36155
+ throw new ProtocolError("inner.written: missing on write_cookies response");
36156
+ }
36157
+ if (!Array.isArray(raw.written)) {
36158
+ throw new ProtocolError("inner.written: must be an array");
36159
+ }
36160
+ for (const [i, name] of raw.written.entries()) {
36161
+ assertString(name, `inner.written[${i}]`);
36162
+ }
36163
+ return raw;
36164
+ }
36084
36165
  if (op === "read_local_storage" || op === "read_session_storage") {
36085
36166
  if (raw.values === void 0) {
36086
36167
  throw new ProtocolError(`inner.values: missing on ${String(op)} response`);
@@ -36558,6 +36639,148 @@ async function awaitSessionReady(ready, opts) {
36558
36639
  }
36559
36640
  }
36560
36641
 
36642
+ // node_modules/@fetchproxy/server/dist/extension-trust.js
36643
+ import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
36644
+ import { join as join3 } from "node:path";
36645
+
36646
+ // node_modules/@fetchproxy/server/dist/identity.js
36647
+ import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
36648
+ import { join as join2 } from "node:path";
36649
+ import { homedir } from "node:os";
36650
+ var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
36651
+ var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
36652
+ function defaultIdentityDir() {
36653
+ return join2(homedir(), ".fetchproxy", "identity");
36654
+ }
36655
+ function safeIdentityFileBase(serverName) {
36656
+ if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
36657
+ throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
36658
+ }
36659
+ return serverName.replace(/\//g, "_");
36660
+ }
36661
+ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
36662
+ const safeFile = safeIdentityFileBase(serverName);
36663
+ const path = join2(dir, `${safeFile}.json`);
36664
+ await mkdir(dir, { recursive: true, mode: 448 });
36665
+ try {
36666
+ const raw = await readFile(path, "utf8");
36667
+ const j2 = JSON.parse(raw);
36668
+ return {
36669
+ x25519Priv: fromB64(j2.x25519Priv),
36670
+ x25519Pub: fromB64(j2.x25519Pub),
36671
+ ed25519Priv: fromB64(j2.ed25519Priv),
36672
+ ed25519Pub: fromB64(j2.ed25519Pub),
36673
+ createdAt: j2.createdAt
36674
+ };
36675
+ } catch (e) {
36676
+ if (e.code !== "ENOENT")
36677
+ throw e;
36678
+ }
36679
+ const x = await generateX25519();
36680
+ const ed = await generateEd25519();
36681
+ const id = {
36682
+ x25519Priv: x.privateKey,
36683
+ x25519Pub: x.publicKey,
36684
+ ed25519Priv: ed.privateKey,
36685
+ ed25519Pub: ed.publicKey,
36686
+ createdAt: Date.now()
36687
+ };
36688
+ const j = {
36689
+ x25519Priv: toB64(id.x25519Priv),
36690
+ x25519Pub: toB64(id.x25519Pub),
36691
+ ed25519Priv: toB64(id.ed25519Priv),
36692
+ ed25519Pub: toB64(id.ed25519Pub),
36693
+ createdAt: id.createdAt
36694
+ };
36695
+ await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
36696
+ await chmod(path, 384);
36697
+ return id;
36698
+ }
36699
+
36700
+ // node_modules/@fetchproxy/server/dist/extension-trust.js
36701
+ function fileExtensionTrust(args) {
36702
+ return {
36703
+ allowNew: args.allowNew,
36704
+ location: extensionTrustPath(args.serverName, args.dir ?? defaultIdentityDir()),
36705
+ read: () => readExtensionPin(args.serverName, args.dir ?? defaultIdentityDir()),
36706
+ write: (pin) => writeExtensionPin(args.serverName, pin, args.dir ?? defaultIdentityDir())
36707
+ };
36708
+ }
36709
+ var TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
36710
+ function allowNewExtensionIdentity(explicit, env = process.env) {
36711
+ if (explicit !== void 0)
36712
+ return explicit;
36713
+ return env[TRUST_NEW_EXTENSION_ENV] === "1";
36714
+ }
36715
+ function decideExtensionTrust(args) {
36716
+ const { pin, hello, allowNew, serverName } = args;
36717
+ if (!pin)
36718
+ return { decision: "first-use" };
36719
+ if (pin.identityX25519Pub === hello.identityX25519Pub && pin.identityEd25519Pub === hello.identityEd25519Pub) {
36720
+ return { decision: "pinned" };
36721
+ }
36722
+ const trustPath = args.location ?? extensionTrustPathHint(serverName);
36723
+ if (allowNew) {
36724
+ return {
36725
+ decision: "replace",
36726
+ 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.`
36727
+ };
36728
+ }
36729
+ return {
36730
+ decision: "refused",
36731
+ 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.`
36732
+ };
36733
+ }
36734
+ function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
36735
+ return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
36736
+ }
36737
+ function extensionTrustPathHint(serverName) {
36738
+ try {
36739
+ return extensionTrustPath(serverName);
36740
+ } catch {
36741
+ return join3(defaultIdentityDir(), "<server-name>.extension-trust.json");
36742
+ }
36743
+ }
36744
+ function isPin(x) {
36745
+ if (!x || typeof x !== "object")
36746
+ return false;
36747
+ const r = x;
36748
+ return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
36749
+ }
36750
+ async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
36751
+ const path = extensionTrustPath(serverName, dir);
36752
+ let raw;
36753
+ try {
36754
+ raw = await readFile2(path, "utf8");
36755
+ } catch (e) {
36756
+ if (e.code === "ENOENT")
36757
+ return null;
36758
+ throw e;
36759
+ }
36760
+ let parsed;
36761
+ try {
36762
+ parsed = JSON.parse(raw);
36763
+ } catch {
36764
+ throw new Error(`unreadable extension pin at ${path} (not JSON) \u2014 delete it to re-pair`);
36765
+ }
36766
+ if (!isPin(parsed)) {
36767
+ throw new Error(`unreadable extension pin at ${path} (wrong shape) \u2014 delete it to re-pair`);
36768
+ }
36769
+ return {
36770
+ identityX25519Pub: parsed.identityX25519Pub,
36771
+ identityEd25519Pub: parsed.identityEd25519Pub,
36772
+ pinnedAt: parsed.pinnedAt
36773
+ };
36774
+ }
36775
+ async function writeExtensionPin(serverName, pin, dir = defaultIdentityDir()) {
36776
+ const path = extensionTrustPath(serverName, dir);
36777
+ await mkdir2(dir, { recursive: true, mode: 448 });
36778
+ const tmp = `${path}.tmp`;
36779
+ await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
36780
+ await chmod2(tmp, 384);
36781
+ await rename(tmp, path);
36782
+ }
36783
+
36561
36784
  // node_modules/@fetchproxy/server/dist/host.js
36562
36785
  var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
36563
36786
  var enc2 = new TextEncoder();
@@ -36611,9 +36834,12 @@ async function startHost(opts) {
36611
36834
  }
36612
36835
  resetSessionPromise();
36613
36836
  let extensionHello = null;
36837
+ let extensionClaim = null;
36614
36838
  wss.on("connection", (ws) => {
36615
36839
  let identified = null;
36616
36840
  let peerMcpId = null;
36841
+ let closed = false;
36842
+ let pinOnReady = false;
36617
36843
  ws.on("message", async (data) => {
36618
36844
  try {
36619
36845
  let frame;
@@ -36625,10 +36851,43 @@ async function startHost(opts) {
36625
36851
  return;
36626
36852
  }
36627
36853
  if (frame.type === "hello" && frame.role === "extension") {
36628
- if (extensionWs) {
36854
+ if (extensionWs || extensionClaim) {
36629
36855
  ws.close(1008, "extension already connected");
36630
36856
  return;
36631
36857
  }
36858
+ extensionClaim = ws;
36859
+ let pin;
36860
+ try {
36861
+ pin = await opts.extensionTrust.read();
36862
+ } catch (e) {
36863
+ console.error(`[fetchproxy] ${String(e)}`);
36864
+ if (extensionClaim === ws)
36865
+ extensionClaim = null;
36866
+ ws.close(1008, "extension pin unreadable");
36867
+ return;
36868
+ }
36869
+ const outcome = decideExtensionTrust({
36870
+ pin,
36871
+ hello: frame,
36872
+ allowNew: opts.extensionTrust.allowNew,
36873
+ serverName: opts.ownServerName,
36874
+ location: opts.extensionTrust.location
36875
+ });
36876
+ if (outcome.decision === "refused") {
36877
+ console.warn(outcome.message);
36878
+ if (extensionClaim === ws)
36879
+ extensionClaim = null;
36880
+ ws.close(1008, "extension identity is not the pinned one");
36881
+ return;
36882
+ }
36883
+ if (outcome.decision === "replace")
36884
+ console.warn(outcome.message);
36885
+ if (closed || ws.readyState !== import_websocket.default.OPEN) {
36886
+ if (extensionClaim === ws)
36887
+ extensionClaim = null;
36888
+ return;
36889
+ }
36890
+ pinOnReady = outcome.decision !== "pinned";
36632
36891
  identified = "extension";
36633
36892
  extensionWs = ws;
36634
36893
  extensionHello = frame;
@@ -36640,6 +36899,8 @@ async function startHost(opts) {
36640
36899
  console.error("[fetchproxy] onPairCode threw:", e);
36641
36900
  }
36642
36901
  }
36902
+ for (const slot of peers.values())
36903
+ slot.ws.send(JSON.stringify(frame));
36643
36904
  ws.send(JSON.stringify(ownHello));
36644
36905
  for (const slot of peers.values()) {
36645
36906
  ws.send(JSON.stringify(slot.helloFrame));
@@ -36675,6 +36936,8 @@ async function startHost(opts) {
36675
36936
  peers.set(frame.mcpId, { ws, helloFrame: frame });
36676
36937
  if (extensionWs)
36677
36938
  extensionWs.send(JSON.stringify(frame));
36939
+ if (extensionHello)
36940
+ ws.send(JSON.stringify(extensionHello));
36678
36941
  return;
36679
36942
  }
36680
36943
  if (frame.type === "ready") {
@@ -36686,7 +36949,7 @@ async function startHost(opts) {
36686
36949
  }
36687
36950
  const extEdPub = fromB64(extensionHello.identityEd25519Pub);
36688
36951
  const extNonce = fromB64(extensionHello.sessionNonce);
36689
- const msg = concatBytes(ownSessionNonce, extNonce);
36952
+ const msg = readySignaturePayload(ownSessionNonce, extNonce, fromB64(frame.extensionSessionPub));
36690
36953
  const sig = fromB64(frame.sessionSig);
36691
36954
  let sigOk = false;
36692
36955
  try {
@@ -36699,6 +36962,18 @@ async function startHost(opts) {
36699
36962
  ws.close(1008, "extension session signature invalid");
36700
36963
  return;
36701
36964
  }
36965
+ if (pinOnReady) {
36966
+ pinOnReady = false;
36967
+ try {
36968
+ await opts.extensionTrust.write({
36969
+ identityX25519Pub: extensionHello.identityX25519Pub,
36970
+ identityEd25519Pub: extensionHello.identityEd25519Pub,
36971
+ pinnedAt: Date.now()
36972
+ });
36973
+ } catch (e) {
36974
+ console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
36975
+ }
36976
+ }
36702
36977
  const extPub = fromB64(frame.extensionSessionPub);
36703
36978
  const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
36704
36979
  const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
@@ -36752,6 +37027,9 @@ async function startHost(opts) {
36752
37027
  }
36753
37028
  });
36754
37029
  ws.on("close", () => {
37030
+ closed = true;
37031
+ if (extensionClaim === ws)
37032
+ extensionClaim = null;
36755
37033
  if (identified === "extension" && extensionWs === ws) {
36756
37034
  extensionWs = null;
36757
37035
  extensionHello = null;
@@ -36842,11 +37120,84 @@ async function startPeer(opts) {
36842
37120
  resolveFirstReady = resolve;
36843
37121
  rejectFirstReady = reject;
36844
37122
  });
37123
+ let extensionHello = null;
37124
+ let warnedUnverifiable = false;
37125
+ let cachedPin = void 0;
37126
+ const authenticateExtension = async (sessionSig, extensionSessionPub) => {
37127
+ if (!extensionHello) {
37128
+ if (opts.requireExtensionIdentity) {
37129
+ 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.`);
37130
+ return false;
37131
+ }
37132
+ if (!warnedUnverifiable) {
37133
+ warnedUnverifiable = true;
37134
+ 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.`);
37135
+ }
37136
+ return true;
37137
+ }
37138
+ const payload = readySignaturePayload(sessionNonce, fromB64(extensionHello.sessionNonce), fromB64(extensionSessionPub));
37139
+ let sigOk = false;
37140
+ try {
37141
+ sigOk = await ed25519Verify(fromB64(extensionHello.identityEd25519Pub), payload, fromB64(sessionSig));
37142
+ } catch {
37143
+ sigOk = false;
37144
+ }
37145
+ if (!sigOk) {
37146
+ console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
37147
+ return false;
37148
+ }
37149
+ if (cachedPin === void 0) {
37150
+ try {
37151
+ cachedPin = await opts.extensionTrust.read();
37152
+ } catch (e) {
37153
+ console.error(`[fetchproxy] ${String(e)}`);
37154
+ return false;
37155
+ }
37156
+ }
37157
+ const pin = cachedPin;
37158
+ const outcome = decideExtensionTrust({
37159
+ pin,
37160
+ hello: extensionHello,
37161
+ allowNew: opts.extensionTrust.allowNew,
37162
+ serverName: opts.serverName,
37163
+ location: opts.extensionTrust.location
37164
+ });
37165
+ if (outcome.decision === "refused") {
37166
+ console.warn(outcome.message);
37167
+ return false;
37168
+ }
37169
+ if (outcome.decision === "replace")
37170
+ console.warn(outcome.message);
37171
+ if (outcome.decision !== "pinned") {
37172
+ try {
37173
+ const written = {
37174
+ identityX25519Pub: extensionHello.identityX25519Pub,
37175
+ identityEd25519Pub: extensionHello.identityEd25519Pub,
37176
+ pinnedAt: Date.now()
37177
+ };
37178
+ await opts.extensionTrust.write(written);
37179
+ cachedPin = written;
37180
+ } catch (e) {
37181
+ console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
37182
+ }
37183
+ }
37184
+ return true;
37185
+ };
36845
37186
  const onMessage = async (data) => {
36846
37187
  try {
36847
37188
  const raw = JSON.parse(data.toString());
36848
37189
  const frame = validateFrame(raw);
37190
+ if (frame.type === "hello" && frame.role === "extension") {
37191
+ extensionHello = frame;
37192
+ return;
37193
+ }
36849
37194
  if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
37195
+ const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
37196
+ if (!authorised) {
37197
+ ws.close(1008, "extension identity refused");
37198
+ rejectFirstReady(new Error("peer: extension identity refused"));
37199
+ return;
37200
+ }
36850
37201
  const extPub = fromB64(frame.extensionSessionPub);
36851
37202
  const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
36852
37203
  const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
@@ -36927,57 +37278,6 @@ async function startPeer(opts) {
36927
37278
  return handle;
36928
37279
  }
36929
37280
 
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
37281
  // node_modules/@fetchproxy/server/dist/error-kind.js
36982
37282
  function classifyFetchError(error51) {
36983
37283
  if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
@@ -37059,6 +37359,39 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
37059
37359
  this.hint = hint;
37060
37360
  }
37061
37361
  };
37362
+ var FetchproxyHintedError = class extends FetchproxyProtocolError {
37363
+ /** The extension's raw rejection, unmodified. */
37364
+ originalError;
37365
+ /** What the user should actually do, in prose. */
37366
+ hint;
37367
+ constructor(originalError, hint) {
37368
+ super(`${originalError} \u2014 ${hint}`);
37369
+ this.name = "FetchproxyHintedError";
37370
+ this.originalError = originalError;
37371
+ this.hint = hint;
37372
+ }
37373
+ };
37374
+ var FetchproxyScopeError = class extends FetchproxyHintedError {
37375
+ constructor(originalError) {
37376
+ 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.");
37377
+ this.name = "FetchproxyScopeError";
37378
+ }
37379
+ };
37380
+ var FetchproxyNoTabError = class extends FetchproxyHintedError {
37381
+ constructor(originalError) {
37382
+ 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.");
37383
+ this.name = "FetchproxyNoTabError";
37384
+ }
37385
+ };
37386
+ var SCOPE_REJECTION = /not in declared/;
37387
+ var NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
37388
+ function protocolErrorFrom(error51) {
37389
+ if (SCOPE_REJECTION.test(error51))
37390
+ return new FetchproxyScopeError(error51);
37391
+ if (NO_TAB_REJECTION.test(error51))
37392
+ return new FetchproxyNoTabError(error51);
37393
+ return new FetchproxyProtocolError(error51);
37394
+ }
37062
37395
  var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
37063
37396
  url;
37064
37397
  timeoutMs;
@@ -37088,6 +37421,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
37088
37421
  }
37089
37422
  };
37090
37423
  var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
37424
+ function normalizeCookiePath(path) {
37425
+ if (path === void 0 || path === "")
37426
+ return void 0;
37427
+ const trimmed = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
37428
+ try {
37429
+ assertCookiePath(trimmed, "path");
37430
+ } catch (e) {
37431
+ throw new Error(`FetchproxyServer: ${e instanceof Error ? e.message : String(e)} (got ${JSON.stringify(path)})`);
37432
+ }
37433
+ return trimmed;
37434
+ }
37091
37435
  function assertSubdomainLabel(label) {
37092
37436
  if (!SUBDOMAIN_LABEL_RE.test(label)) {
37093
37437
  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 +37496,9 @@ var FetchproxyServer = class {
37152
37496
  // them off from `pending` (fetch) and `pendingReadCookies` (legacy
37153
37497
  // string-shape) so the response routing in `onInner` stays linear.
37154
37498
  pendingStorage = /* @__PURE__ */ new Map();
37499
+ // 1.12.0+: write-cookies awaiters resolve the list of names actually
37500
+ // written, so a caller can confirm rather than assume.
37501
+ pendingWriteCookies = /* @__PURE__ */ new Map();
37155
37502
  // 0.3.0+: capture-header awaiters resolve a single string.
37156
37503
  pendingCapture = /* @__PURE__ */ new Map();
37157
37504
  // capture_redirect awaiters resolve the captured redirect URL string.
@@ -37278,6 +37625,8 @@ var FetchproxyServer = class {
37278
37625
  keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
37279
37626
  keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
37280
37627
  identityDir: opts.identityDir,
37628
+ allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
37629
+ requireExtensionIdentity: opts.requireExtensionIdentity,
37281
37630
  onPairCode: opts.onPairCode
37282
37631
  };
37283
37632
  }
@@ -37377,7 +37726,8 @@ var FetchproxyServer = class {
37377
37726
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
37378
37727
  ownDomSelectors: this.opts.domSelectors,
37379
37728
  ownGraphqlOps: this.opts.graphqlOps,
37380
- onPairCode: this.opts.onPairCode
37729
+ onPairCode: this.opts.onPairCode,
37730
+ extensionTrust: this.extensionTrust()
37381
37731
  });
37382
37732
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
37383
37733
  this.hostHandle.onExtensionDisconnect(() => {
@@ -37406,7 +37756,9 @@ var FetchproxyServer = class {
37406
37756
  localStoragePointers: this.opts.localStoragePointers,
37407
37757
  sessionStoragePointers: this.opts.sessionStoragePointers,
37408
37758
  domSelectors: this.opts.domSelectors,
37409
- graphqlOps: this.opts.graphqlOps
37759
+ graphqlOps: this.opts.graphqlOps,
37760
+ extensionTrust: this.extensionTrust(),
37761
+ requireExtensionIdentity: this.opts.requireExtensionIdentity
37410
37762
  });
37411
37763
  this.peerHandle.onInner((inner) => this.onInner(inner));
37412
37764
  this.peerHandle.onRenegotiate(() => {
@@ -37545,6 +37897,23 @@ var FetchproxyServer = class {
37545
37897
  markActive() {
37546
37898
  this.noteActivityForKeepalive();
37547
37899
  }
37900
+ /**
37901
+ * #208: this MCP's pin on the extension's identity, stored beside its own
37902
+ * identity key and so following `identityDir` wherever the caller put it.
37903
+ *
37904
+ * `allowNewExtensionIdentity` falls back to an environment variable when the
37905
+ * caller expressed no opinion, because the thirteen MCPs that construct this
37906
+ * class are separate packages: an operator whose extension re-install has
37907
+ * just locked all of them out needs one lever that does not require patching
37908
+ * every one of them.
37909
+ */
37910
+ extensionTrust() {
37911
+ return fileExtensionTrust({
37912
+ serverName: this.opts.serverName,
37913
+ dir: this.opts.identityDir,
37914
+ allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
37915
+ });
37916
+ }
37548
37917
  noteActivityForKeepalive() {
37549
37918
  const intervalMs = this.opts.keepAliveIntervalMs;
37550
37919
  if (intervalMs <= 0)
@@ -37608,6 +37977,7 @@ var FetchproxyServer = class {
37608
37977
  this.pending.delete(id);
37609
37978
  this.pendingReadCookies.delete(id);
37610
37979
  this.pendingStorage.delete(id);
37980
+ this.pendingWriteCookies.delete(id);
37611
37981
  this.pendingCapture.delete(id);
37612
37982
  this.pendingRedirect.delete(id);
37613
37983
  this.pendingDownload.delete(id);
@@ -37729,7 +38099,7 @@ var FetchproxyServer = class {
37729
38099
  port: this.opts.port
37730
38100
  });
37731
38101
  }
37732
- return new FetchproxyProtocolError(result.error);
38102
+ return protocolErrorFrom(result.error);
37733
38103
  }
37734
38104
  /**
37735
38105
  * Convenience wrapper around `fetch()`. Builds the URL from a path
@@ -37765,10 +38135,20 @@ var FetchproxyServer = class {
37765
38135
  }
37766
38136
  const url2 = isAbsolute ? path : `https://${host}${path}`;
37767
38137
  assertUrlInDomains("request url", url2, this.opts.domains);
38138
+ let tabUrl = `https://${host}/`;
38139
+ if (opts.viaTab !== void 0) {
38140
+ try {
38141
+ new URL(opts.viaTab);
38142
+ } catch {
38143
+ throw new Error(`FetchproxyServer.request: viaTab is not a valid URL: ${JSON.stringify(opts.viaTab)}`);
38144
+ }
38145
+ assertUrlInDomains("viaTab", opts.viaTab, this.opts.domains);
38146
+ tabUrl = opts.viaTab;
38147
+ }
37768
38148
  const init = {
37769
38149
  url: url2,
37770
38150
  method,
37771
- tabUrl: `https://${host}/`,
38151
+ tabUrl,
37772
38152
  headers: opts.headers,
37773
38153
  body: opts.body
37774
38154
  };
@@ -37980,9 +38360,14 @@ var FetchproxyServer = class {
37980
38360
  let inner;
37981
38361
  if (opts.keys !== void 0) {
37982
38362
  this.assertScopeSubset(opts.keys, this.opts.cookieKeys, "cookieKeys");
38363
+ const cookiePath = normalizeCookiePath(opts.path);
37983
38364
  const initV3 = {
38365
+ // Origin stays BARE. The path travels as its own validated field —
38366
+ // `assertHttpsOriginOnly` deliberately refuses a path here so one
38367
+ // cannot be used to re-point the read past the domain gate.
37984
38368
  origin: `https://${host}`,
37985
- keys: [...opts.keys]
38369
+ keys: [...opts.keys],
38370
+ ...cookiePath !== void 0 ? { path: cookiePath } : {}
37986
38371
  };
37987
38372
  inner = { type: "request", id, op: "read_cookies", init: initV3 };
37988
38373
  } else {
@@ -37995,10 +38380,65 @@ var FetchproxyServer = class {
37995
38380
  await this.sendInnerFrame(inner);
37996
38381
  const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
37997
38382
  if (!result.ok) {
37998
- throw new FetchproxyProtocolError(result.error);
38383
+ throw protocolErrorFrom(result.error);
37999
38384
  }
38000
38385
  return result.cookies;
38001
38386
  }
38387
+ /**
38388
+ * 1.12.0+: overwrite the value of cookies this MCP already declares.
38389
+ *
38390
+ * The bridge's only write verb, and it exists for one failure class. Sites
38391
+ * that ROTATE a credential cookie hand back a new value on every refresh; if
38392
+ * the MCP refreshes and keeps the result to itself, the copy in the browser's
38393
+ * cookie jar is dead, and the user gets signed out of a tab they never
38394
+ * touched — usually reported to them as "inactivity". Writing the rotated
38395
+ * value back is the only thing that repairs it.
38396
+ *
38397
+ * Requires `'write_cookies'` in capabilities, which the user approves at pair
38398
+ * time as its own line. Every name must ALSO be in declared `cookieKeys`: a
38399
+ * write can never reach a cookie the MCP was not already trusted to read, so
38400
+ * granting it cannot widen which cookies are in play — only what may be done
38401
+ * to the ones already listed.
38402
+ *
38403
+ * The extension refuses the whole request unless every named cookie already
38404
+ * exists; this refreshes a value in place and deliberately cannot author new
38405
+ * cookies. Returns the names actually written.
38406
+ */
38407
+ async writeCookies(opts) {
38408
+ if (!this.opts.capabilities.includes("write_cookies")) {
38409
+ throw new Error('FetchproxyServer.writeCookies(): MCP did not declare "write_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
38410
+ }
38411
+ const names = Object.keys(opts.cookies);
38412
+ if (names.length === 0) {
38413
+ throw new Error("FetchproxyServer.writeCookies(): no cookies given");
38414
+ }
38415
+ await this.ensureConnected();
38416
+ this.throwIfPendingPair();
38417
+ if (opts.subdomain !== void 0)
38418
+ assertSubdomainLabel(opts.subdomain);
38419
+ const baseDomain = this.resolveBaseDomain(opts.domain);
38420
+ const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
38421
+ this.assertScopeSubset(names, this.opts.cookieKeys, "cookieKeys");
38422
+ const cookiePath = normalizeCookiePath(opts.path);
38423
+ const id = this.nextRequestId++;
38424
+ const inner = {
38425
+ type: "request",
38426
+ id,
38427
+ op: "write_cookies",
38428
+ init: {
38429
+ // Bare origin, same invariant as the read path: a path must never be
38430
+ // able to move the request past the domain gate.
38431
+ origin: `https://${host}`,
38432
+ cookies: Object.entries(opts.cookies).map(([name, value]) => ({ name, value })),
38433
+ ...cookiePath !== void 0 ? { path: cookiePath } : {}
38434
+ }
38435
+ };
38436
+ const pending = new Promise((resolve, reject) => {
38437
+ this.pendingWriteCookies.set(id, { resolve, reject });
38438
+ });
38439
+ await this.sendInnerFrame(inner);
38440
+ return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
38441
+ }
38002
38442
  /**
38003
38443
  * 0.3.0+: read declared localStorage keys from the user's signed-in
38004
38444
  * tab. Requires `'read_local_storage'` in capabilities AND each key
@@ -38545,7 +38985,7 @@ var FetchproxyServer = class {
38545
38985
  storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
38546
38986
  }
38547
38987
  } else {
38548
- storageCb.reject(new FetchproxyProtocolError(inner.error));
38988
+ storageCb.reject(protocolErrorFrom(inner.error));
38549
38989
  }
38550
38990
  return;
38551
38991
  }
@@ -38559,7 +38999,7 @@ var FetchproxyServer = class {
38559
38999
  captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
38560
39000
  }
38561
39001
  } else {
38562
- captureCb.reject(new FetchproxyProtocolError(inner.error));
39002
+ captureCb.reject(protocolErrorFrom(inner.error));
38563
39003
  }
38564
39004
  return;
38565
39005
  }
@@ -38573,7 +39013,7 @@ var FetchproxyServer = class {
38573
39013
  redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
38574
39014
  }
38575
39015
  } else {
38576
- redirectCb.reject(new FetchproxyProtocolError(inner.error));
39016
+ redirectCb.reject(protocolErrorFrom(inner.error));
38577
39017
  }
38578
39018
  return;
38579
39019
  }
@@ -38587,7 +39027,7 @@ var FetchproxyServer = class {
38587
39027
  idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
38588
39028
  }
38589
39029
  } else {
38590
- idbCb.reject(new FetchproxyProtocolError(inner.error));
39030
+ idbCb.reject(protocolErrorFrom(inner.error));
38591
39031
  }
38592
39032
  return;
38593
39033
  }
@@ -38601,7 +39041,7 @@ var FetchproxyServer = class {
38601
39041
  downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
38602
39042
  }
38603
39043
  } else {
38604
- downloadCb.reject(new FetchproxyProtocolError(inner.error));
39044
+ downloadCb.reject(protocolErrorFrom(inner.error));
38605
39045
  }
38606
39046
  return;
38607
39047
  }
@@ -38615,7 +39055,17 @@ var FetchproxyServer = class {
38615
39055
  graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
38616
39056
  }
38617
39057
  } else {
38618
- graphqlCb.reject(new FetchproxyProtocolError(inner.error));
39058
+ graphqlCb.reject(protocolErrorFrom(inner.error));
39059
+ }
39060
+ return;
39061
+ }
39062
+ const writeCookiesCb = this.pendingWriteCookies.get(inner.id);
39063
+ if (writeCookiesCb) {
39064
+ this.pendingWriteCookies.delete(inner.id);
39065
+ if (inner.ok && inner.op === "write_cookies") {
39066
+ writeCookiesCb.resolve([...inner.written]);
39067
+ } else {
39068
+ writeCookiesCb.reject(protocolErrorFrom(inner.ok ? "write_cookies response had the wrong op" : inner.error));
38619
39069
  }
38620
39070
  return;
38621
39071
  }
@@ -38658,6 +39108,9 @@ var FetchproxyServer = class {
38658
39108
  for (const { reject } of this.pendingStorage.values())
38659
39109
  reject(err);
38660
39110
  this.pendingStorage.clear();
39111
+ for (const { reject } of this.pendingWriteCookies.values())
39112
+ reject(err);
39113
+ this.pendingWriteCookies.clear();
38661
39114
  for (const { reject } of this.pendingCapture.values())
38662
39115
  reject(err);
38663
39116
  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'; // x-release-please-version
2
+ export const VERSION = '0.3.3'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/tripadvisor-mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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>",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@chrischall/mcp-utils": "^0.14.0",
48
- "@fetchproxy/server": "^1.7.0",
48
+ "@fetchproxy/server": "^2.0.0",
49
49
  "@modelcontextprotocol/sdk": "^1.29.0",
50
50
  "dotenv": "^17.4.0",
51
51
  "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.2",
9
+ "version": "0.3.3",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/tripadvisor-mcp",
14
- "version": "0.3.2",
14
+ "version": "0.3.3",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },