honeybook-mcp 0.4.3 → 0.4.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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "HoneyBook client-portal tools for Claude Code",
10
- "version": "0.4.3"
10
+ "version": "0.4.4"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "HoneyBook",
16
16
  "source": "./",
17
17
  "description": "HoneyBook client-portal MCP for Claude — view wedding-vendor contracts and invoices via MCP",
18
- "version": "0.4.3",
18
+ "version": "0.4.4",
19
19
  "author": {
20
20
  "name": "Chris Chall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "honeybook",
3
3
  "displayName": "HoneyBook",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "description": "HoneyBook client-portal MCP for Claude — view wedding-vendor contracts and invoices via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall",
package/dist/bundle.js CHANGED
@@ -3650,7 +3650,12 @@ var require_fast_uri = __commonJS({
3650
3650
  }
3651
3651
  function resolve(baseURI, relativeURI, options) {
3652
3652
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3653
- const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3653
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3654
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3655
+ if (baseMalformed || relativeMalformed) {
3656
+ throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3657
+ }
3658
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3654
3659
  schemelessOptions.skipEscape = true;
3655
3660
  return serialize(resolved, schemelessOptions);
3656
3661
  }
@@ -3776,6 +3781,7 @@ var require_fast_uri = __commonJS({
3776
3781
  }
3777
3782
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3778
3783
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3784
+ var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
3779
3785
  function getParseError(parsed, matches) {
3780
3786
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3781
3787
  return 'URI path must start with "/" when authority is present.';
@@ -3810,6 +3816,20 @@ var require_fast_uri = __commonJS({
3810
3816
  parsed.error = "URI authority must not contain a literal backslash.";
3811
3817
  malformedAuthorityOrPort = true;
3812
3818
  }
3819
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
3820
+ if (introducerMatch !== null) {
3821
+ const region = introducerMatch[1];
3822
+ const normalizedRegion = region.replace(/[\t\n\r]/g, "");
3823
+ if (normalizedRegion.length >= 2) {
3824
+ if (normalizedRegion.slice(0, 2) !== "//") {
3825
+ parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
3826
+ malformedAuthorityOrPort = true;
3827
+ } else if (region.length !== normalizedRegion.length) {
3828
+ parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
3829
+ malformedAuthorityOrPort = true;
3830
+ }
3831
+ }
3832
+ }
3813
3833
  const matches = uri.match(URI_PARSE);
3814
3834
  if (matches) {
3815
3835
  parsed.scheme = matches[1];
@@ -10572,7 +10592,7 @@ var require_websocket_server = __commonJS({
10572
10592
  });
10573
10593
 
10574
10594
  // src/index.ts
10575
- import { dirname as dirname2, join as join3 } from "path";
10595
+ import { dirname as dirname2, join as join4 } from "path";
10576
10596
  import { fileURLToPath } from "url";
10577
10597
 
10578
10598
  // node_modules/zod/v3/helpers/util.js
@@ -26728,17 +26748,33 @@ function normalizeObjectSchema(schema) {
26728
26748
  }
26729
26749
  return void 0;
26730
26750
  }
26751
+ function getDotPath(path) {
26752
+ if (path.length === 0) {
26753
+ return "object root";
26754
+ }
26755
+ return path.reduce((acc, seg, index) => {
26756
+ if (index === 0) {
26757
+ return String(seg);
26758
+ }
26759
+ if (typeof seg === "number") {
26760
+ return `${acc}[${seg}]`;
26761
+ }
26762
+ return `${acc}.${seg}`;
26763
+ }, "");
26764
+ }
26731
26765
  function getParseErrorMessage(error51) {
26732
26766
  if (error51 && typeof error51 === "object") {
26767
+ if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
26768
+ return error51.issues.map((i) => {
26769
+ if (!i.path?.length) {
26770
+ return i.message;
26771
+ }
26772
+ return `${i.message} at ${getDotPath(i.path)}`;
26773
+ }).join("\n");
26774
+ }
26733
26775
  if ("message" in error51 && typeof error51.message === "string") {
26734
26776
  return error51.message;
26735
26777
  }
26736
- if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
26737
- const firstIssue = error51.issues[0];
26738
- if (firstIssue && typeof firstIssue === "object" && "message" in firstIssue) {
26739
- return String(firstIssue.message);
26740
- }
26741
- }
26742
26778
  try {
26743
26779
  return JSON.stringify(error51);
26744
26780
  } catch {
@@ -33353,16 +33389,7 @@ var Server = class extends Protocol {
33353
33389
  if (!methodSchema) {
33354
33390
  throw new Error("Schema is missing a method literal");
33355
33391
  }
33356
- let methodValue;
33357
- if (isZ4Schema(methodSchema)) {
33358
- const v4Schema = methodSchema;
33359
- const v4Def = v4Schema._zod?.def;
33360
- methodValue = v4Def?.value ?? v4Schema.value;
33361
- } else {
33362
- const v3Schema = methodSchema;
33363
- const legacyDef = v3Schema._def;
33364
- methodValue = legacyDef?.value ?? v3Schema.value;
33365
- }
33392
+ const methodValue = getLiteralValue(methodSchema);
33366
33393
  if (typeof methodValue !== "string") {
33367
33394
  throw new Error("Schema method literal must be a string");
33368
33395
  }
@@ -34550,8 +34577,17 @@ var EMPTY_COMPLETION_RESULT = {
34550
34577
  import process3 from "node:process";
34551
34578
 
34552
34579
  // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
34580
+ var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
34553
34581
  var ReadBuffer = class {
34582
+ constructor(options) {
34583
+ this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
34584
+ }
34554
34585
  append(chunk2) {
34586
+ const newSize = (this._buffer?.length ?? 0) + chunk2.length;
34587
+ if (newSize > this._maxBufferSize) {
34588
+ this.clear();
34589
+ throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
34590
+ }
34555
34591
  this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk2]) : chunk2;
34556
34592
  }
34557
34593
  readMessage() {
@@ -34579,18 +34615,24 @@ function serializeMessage(message) {
34579
34615
 
34580
34616
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
34581
34617
  var StdioServerTransport = class {
34582
- constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
34618
+ constructor(_stdin = process3.stdin, _stdout = process3.stdout, options) {
34583
34619
  this._stdin = _stdin;
34584
34620
  this._stdout = _stdout;
34585
- this._readBuffer = new ReadBuffer();
34586
34621
  this._started = false;
34587
34622
  this._ondata = (chunk2) => {
34588
- this._readBuffer.append(chunk2);
34589
- this.processReadBuffer();
34623
+ try {
34624
+ this._readBuffer.append(chunk2);
34625
+ this.processReadBuffer();
34626
+ } catch (error51) {
34627
+ this.onerror?.(error51);
34628
+ this.close().catch(() => {
34629
+ });
34630
+ }
34590
34631
  };
34591
34632
  this._onerror = (error51) => {
34592
34633
  this.onerror?.(error51);
34593
34634
  };
34635
+ this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
34594
34636
  }
34595
34637
  /**
34596
34638
  * Starts listening for messages on stdin.
@@ -35058,8 +35100,15 @@ async function getActiveClient(origin) {
35058
35100
  }
35059
35101
 
35060
35102
  // node_modules/@fetchproxy/protocol/dist/frames.js
35061
- var PROTOCOL_VERSION = 2;
35103
+ var PROTOCOL_VERSION = 3;
35062
35104
  var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
35105
+ function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub) {
35106
+ const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
35107
+ out.set(mcpHelloNonce, 0);
35108
+ out.set(extHelloNonce, mcpHelloNonce.length);
35109
+ out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
35110
+ return out;
35111
+ }
35063
35112
  var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
35064
35113
  "fetch",
35065
35114
  "read_cookies",
@@ -35070,7 +35119,8 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
35070
35119
  "read_indexed_db",
35071
35120
  "read_dom",
35072
35121
  "download",
35073
- "graphql"
35122
+ "graphql",
35123
+ "write_cookies"
35074
35124
  ]);
35075
35125
 
35076
35126
  // node_modules/@fetchproxy/protocol/dist/mcp-id.js
@@ -35181,6 +35231,15 @@ function assertHttpUrl(x, label) {
35181
35231
  throw new ProtocolError(`${label}: must be http(s), got ${u.protocol}`);
35182
35232
  }
35183
35233
  }
35234
+ function assertCookiePath(x, label) {
35235
+ assertString(x, label);
35236
+ if (!x.startsWith("/") || x.startsWith("//")) {
35237
+ throw new ProtocolError(`${label}: must be an absolute path like "/campus"`);
35238
+ }
35239
+ if (x.includes("?") || x.includes("#") || x.includes("\\")) {
35240
+ throw new ProtocolError(`${label}: must not contain a query, fragment, or backslash`);
35241
+ }
35242
+ }
35184
35243
  function assertHttpsOriginOnly(x, label) {
35185
35244
  assertString(x, label);
35186
35245
  let u;
@@ -35625,13 +35684,43 @@ function validateInnerRequest(raw) {
35625
35684
  }
35626
35685
  assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
35627
35686
  assertNonEmptyKeyArray(raw.init.keys, "inner.init.keys");
35687
+ if (raw.init.path !== void 0)
35688
+ assertCookiePath(raw.init.path, "inner.init.path");
35628
35689
  for (const k of Object.keys(raw.init)) {
35629
- if (k !== "origin" && k !== "keys") {
35690
+ if (k !== "origin" && k !== "keys" && k !== "path") {
35630
35691
  throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on read_cookies`);
35631
35692
  }
35632
35693
  }
35633
35694
  return raw;
35634
35695
  }
35696
+ if (raw.op === "write_cookies") {
35697
+ assertObject(raw.init, "inner.init");
35698
+ assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
35699
+ if (!Array.isArray(raw.init.cookies) || raw.init.cookies.length === 0) {
35700
+ throw new ProtocolError("inner.init.cookies: must be a non-empty array");
35701
+ }
35702
+ for (const [i, entry] of raw.init.cookies.entries()) {
35703
+ assertObject(entry, `inner.init.cookies[${i}]`);
35704
+ assertString(entry.name, `inner.init.cookies[${i}].name`);
35705
+ if (!SCOPE_KEY_RE.test(entry.name)) {
35706
+ throw new ProtocolError(`inner.init.cookies[${i}].name: invalid key ${JSON.stringify(entry.name)}`);
35707
+ }
35708
+ assertString(entry.value, `inner.init.cookies[${i}].value`);
35709
+ for (const k of Object.keys(entry)) {
35710
+ if (k !== "name" && k !== "value") {
35711
+ throw new ProtocolError(`inner.init.cookies[${i}]: unexpected field ${JSON.stringify(k)}`);
35712
+ }
35713
+ }
35714
+ }
35715
+ if (raw.init.path !== void 0)
35716
+ assertCookiePath(raw.init.path, "inner.init.path");
35717
+ for (const k of Object.keys(raw.init)) {
35718
+ if (k !== "origin" && k !== "cookies" && k !== "path") {
35719
+ throw new ProtocolError(`inner.init: unexpected field ${JSON.stringify(k)} on write_cookies`);
35720
+ }
35721
+ }
35722
+ return raw;
35723
+ }
35635
35724
  if (raw.op === "read_local_storage" || raw.op === "read_session_storage") {
35636
35725
  assertObject(raw.init, "inner.init");
35637
35726
  if (raw.init.origin === void 0) {
@@ -35818,7 +35907,7 @@ function validateInnerRequest(raw) {
35818
35907
  }
35819
35908
  return raw;
35820
35909
  }
35821
- 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)}`);
35910
+ 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)}`);
35822
35911
  }
35823
35912
  function assertNonEmptyKeyArray(value, label) {
35824
35913
  if (!Array.isArray(value)) {
@@ -35879,6 +35968,18 @@ function validateInnerResponse(raw) {
35879
35968
  }
35880
35969
  return raw;
35881
35970
  }
35971
+ if (op === "write_cookies") {
35972
+ if (raw.written === void 0) {
35973
+ throw new ProtocolError("inner.written: missing on write_cookies response");
35974
+ }
35975
+ if (!Array.isArray(raw.written)) {
35976
+ throw new ProtocolError("inner.written: must be an array");
35977
+ }
35978
+ for (const [i, name] of raw.written.entries()) {
35979
+ assertString(name, `inner.written[${i}]`);
35980
+ }
35981
+ return raw;
35982
+ }
35882
35983
  if (op === "read_local_storage" || op === "read_session_storage") {
35883
35984
  if (raw.values === void 0) {
35884
35985
  throw new ProtocolError(`inner.values: missing on ${String(op)} response`);
@@ -36356,6 +36457,148 @@ async function awaitSessionReady(ready, opts) {
36356
36457
  }
36357
36458
  }
36358
36459
 
36460
+ // node_modules/@fetchproxy/server/dist/extension-trust.js
36461
+ import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
36462
+ import { join as join3 } from "node:path";
36463
+
36464
+ // node_modules/@fetchproxy/server/dist/identity.js
36465
+ import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
36466
+ import { join as join2 } from "node:path";
36467
+ import { homedir as homedir2 } from "node:os";
36468
+ var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
36469
+ var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
36470
+ function defaultIdentityDir() {
36471
+ return join2(homedir2(), ".fetchproxy", "identity");
36472
+ }
36473
+ function safeIdentityFileBase(serverName) {
36474
+ if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
36475
+ throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
36476
+ }
36477
+ return serverName.replace(/\//g, "_");
36478
+ }
36479
+ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
36480
+ const safeFile = safeIdentityFileBase(serverName);
36481
+ const path = join2(dir, `${safeFile}.json`);
36482
+ await mkdir(dir, { recursive: true, mode: 448 });
36483
+ try {
36484
+ const raw = await readFile(path, "utf8");
36485
+ const j2 = JSON.parse(raw);
36486
+ return {
36487
+ x25519Priv: fromB64(j2.x25519Priv),
36488
+ x25519Pub: fromB64(j2.x25519Pub),
36489
+ ed25519Priv: fromB64(j2.ed25519Priv),
36490
+ ed25519Pub: fromB64(j2.ed25519Pub),
36491
+ createdAt: j2.createdAt
36492
+ };
36493
+ } catch (e) {
36494
+ if (e.code !== "ENOENT")
36495
+ throw e;
36496
+ }
36497
+ const x = await generateX25519();
36498
+ const ed = await generateEd25519();
36499
+ const id = {
36500
+ x25519Priv: x.privateKey,
36501
+ x25519Pub: x.publicKey,
36502
+ ed25519Priv: ed.privateKey,
36503
+ ed25519Pub: ed.publicKey,
36504
+ createdAt: Date.now()
36505
+ };
36506
+ const j = {
36507
+ x25519Priv: toB64(id.x25519Priv),
36508
+ x25519Pub: toB64(id.x25519Pub),
36509
+ ed25519Priv: toB64(id.ed25519Priv),
36510
+ ed25519Pub: toB64(id.ed25519Pub),
36511
+ createdAt: id.createdAt
36512
+ };
36513
+ await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
36514
+ await chmod(path, 384);
36515
+ return id;
36516
+ }
36517
+
36518
+ // node_modules/@fetchproxy/server/dist/extension-trust.js
36519
+ function fileExtensionTrust(args) {
36520
+ return {
36521
+ allowNew: args.allowNew,
36522
+ location: extensionTrustPath(args.serverName, args.dir ?? defaultIdentityDir()),
36523
+ read: () => readExtensionPin(args.serverName, args.dir ?? defaultIdentityDir()),
36524
+ write: (pin) => writeExtensionPin(args.serverName, pin, args.dir ?? defaultIdentityDir())
36525
+ };
36526
+ }
36527
+ var TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
36528
+ function allowNewExtensionIdentity(explicit, env = process.env) {
36529
+ if (explicit !== void 0)
36530
+ return explicit;
36531
+ return env[TRUST_NEW_EXTENSION_ENV] === "1";
36532
+ }
36533
+ function decideExtensionTrust(args) {
36534
+ const { pin, hello, allowNew, serverName } = args;
36535
+ if (!pin)
36536
+ return { decision: "first-use" };
36537
+ if (pin.identityX25519Pub === hello.identityX25519Pub && pin.identityEd25519Pub === hello.identityEd25519Pub) {
36538
+ return { decision: "pinned" };
36539
+ }
36540
+ const trustPath = args.location ?? extensionTrustPathHint(serverName);
36541
+ if (allowNew) {
36542
+ return {
36543
+ decision: "replace",
36544
+ 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.`
36545
+ };
36546
+ }
36547
+ return {
36548
+ decision: "refused",
36549
+ 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.`
36550
+ };
36551
+ }
36552
+ function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
36553
+ return join3(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
36554
+ }
36555
+ function extensionTrustPathHint(serverName) {
36556
+ try {
36557
+ return extensionTrustPath(serverName);
36558
+ } catch {
36559
+ return join3(defaultIdentityDir(), "<server-name>.extension-trust.json");
36560
+ }
36561
+ }
36562
+ function isPin(x) {
36563
+ if (!x || typeof x !== "object")
36564
+ return false;
36565
+ const r = x;
36566
+ return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
36567
+ }
36568
+ async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
36569
+ const path = extensionTrustPath(serverName, dir);
36570
+ let raw;
36571
+ try {
36572
+ raw = await readFile2(path, "utf8");
36573
+ } catch (e) {
36574
+ if (e.code === "ENOENT")
36575
+ return null;
36576
+ throw e;
36577
+ }
36578
+ let parsed;
36579
+ try {
36580
+ parsed = JSON.parse(raw);
36581
+ } catch {
36582
+ throw new Error(`unreadable extension pin at ${path} (not JSON) \u2014 delete it to re-pair`);
36583
+ }
36584
+ if (!isPin(parsed)) {
36585
+ throw new Error(`unreadable extension pin at ${path} (wrong shape) \u2014 delete it to re-pair`);
36586
+ }
36587
+ return {
36588
+ identityX25519Pub: parsed.identityX25519Pub,
36589
+ identityEd25519Pub: parsed.identityEd25519Pub,
36590
+ pinnedAt: parsed.pinnedAt
36591
+ };
36592
+ }
36593
+ async function writeExtensionPin(serverName, pin, dir = defaultIdentityDir()) {
36594
+ const path = extensionTrustPath(serverName, dir);
36595
+ await mkdir2(dir, { recursive: true, mode: 448 });
36596
+ const tmp = `${path}.tmp`;
36597
+ await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
36598
+ await chmod2(tmp, 384);
36599
+ await rename(tmp, path);
36600
+ }
36601
+
36359
36602
  // node_modules/@fetchproxy/server/dist/host.js
36360
36603
  var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
36361
36604
  var enc2 = new TextEncoder();
@@ -36409,9 +36652,12 @@ async function startHost(opts) {
36409
36652
  }
36410
36653
  resetSessionPromise();
36411
36654
  let extensionHello = null;
36655
+ let extensionClaim = null;
36412
36656
  wss.on("connection", (ws) => {
36413
36657
  let identified = null;
36414
36658
  let peerMcpId = null;
36659
+ let closed = false;
36660
+ let pinOnReady = false;
36415
36661
  ws.on("message", async (data) => {
36416
36662
  try {
36417
36663
  let frame;
@@ -36423,10 +36669,43 @@ async function startHost(opts) {
36423
36669
  return;
36424
36670
  }
36425
36671
  if (frame.type === "hello" && frame.role === "extension") {
36426
- if (extensionWs) {
36672
+ if (extensionWs || extensionClaim) {
36427
36673
  ws.close(1008, "extension already connected");
36428
36674
  return;
36429
36675
  }
36676
+ extensionClaim = ws;
36677
+ let pin;
36678
+ try {
36679
+ pin = await opts.extensionTrust.read();
36680
+ } catch (e) {
36681
+ console.error(`[fetchproxy] ${String(e)}`);
36682
+ if (extensionClaim === ws)
36683
+ extensionClaim = null;
36684
+ ws.close(1008, "extension pin unreadable");
36685
+ return;
36686
+ }
36687
+ const outcome = decideExtensionTrust({
36688
+ pin,
36689
+ hello: frame,
36690
+ allowNew: opts.extensionTrust.allowNew,
36691
+ serverName: opts.ownServerName,
36692
+ location: opts.extensionTrust.location
36693
+ });
36694
+ if (outcome.decision === "refused") {
36695
+ console.warn(outcome.message);
36696
+ if (extensionClaim === ws)
36697
+ extensionClaim = null;
36698
+ ws.close(1008, "extension identity is not the pinned one");
36699
+ return;
36700
+ }
36701
+ if (outcome.decision === "replace")
36702
+ console.warn(outcome.message);
36703
+ if (closed || ws.readyState !== import_websocket.default.OPEN) {
36704
+ if (extensionClaim === ws)
36705
+ extensionClaim = null;
36706
+ return;
36707
+ }
36708
+ pinOnReady = outcome.decision !== "pinned";
36430
36709
  identified = "extension";
36431
36710
  extensionWs = ws;
36432
36711
  extensionHello = frame;
@@ -36438,6 +36717,8 @@ async function startHost(opts) {
36438
36717
  console.error("[fetchproxy] onPairCode threw:", e);
36439
36718
  }
36440
36719
  }
36720
+ for (const slot of peers.values())
36721
+ slot.ws.send(JSON.stringify(frame));
36441
36722
  ws.send(JSON.stringify(ownHello));
36442
36723
  for (const slot of peers.values()) {
36443
36724
  ws.send(JSON.stringify(slot.helloFrame));
@@ -36473,6 +36754,8 @@ async function startHost(opts) {
36473
36754
  peers.set(frame.mcpId, { ws, helloFrame: frame });
36474
36755
  if (extensionWs)
36475
36756
  extensionWs.send(JSON.stringify(frame));
36757
+ if (extensionHello)
36758
+ ws.send(JSON.stringify(extensionHello));
36476
36759
  return;
36477
36760
  }
36478
36761
  if (frame.type === "ready") {
@@ -36484,7 +36767,7 @@ async function startHost(opts) {
36484
36767
  }
36485
36768
  const extEdPub = fromB64(extensionHello.identityEd25519Pub);
36486
36769
  const extNonce = fromB64(extensionHello.sessionNonce);
36487
- const msg = concatBytes(ownSessionNonce, extNonce);
36770
+ const msg = readySignaturePayload(ownSessionNonce, extNonce, fromB64(frame.extensionSessionPub));
36488
36771
  const sig = fromB64(frame.sessionSig);
36489
36772
  let sigOk = false;
36490
36773
  try {
@@ -36497,6 +36780,18 @@ async function startHost(opts) {
36497
36780
  ws.close(1008, "extension session signature invalid");
36498
36781
  return;
36499
36782
  }
36783
+ if (pinOnReady) {
36784
+ pinOnReady = false;
36785
+ try {
36786
+ await opts.extensionTrust.write({
36787
+ identityX25519Pub: extensionHello.identityX25519Pub,
36788
+ identityEd25519Pub: extensionHello.identityEd25519Pub,
36789
+ pinnedAt: Date.now()
36790
+ });
36791
+ } catch (e) {
36792
+ console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
36793
+ }
36794
+ }
36500
36795
  const extPub = fromB64(frame.extensionSessionPub);
36501
36796
  const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
36502
36797
  const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
@@ -36550,6 +36845,9 @@ async function startHost(opts) {
36550
36845
  }
36551
36846
  });
36552
36847
  ws.on("close", () => {
36848
+ closed = true;
36849
+ if (extensionClaim === ws)
36850
+ extensionClaim = null;
36553
36851
  if (identified === "extension" && extensionWs === ws) {
36554
36852
  extensionWs = null;
36555
36853
  extensionHello = null;
@@ -36640,11 +36938,84 @@ async function startPeer(opts) {
36640
36938
  resolveFirstReady = resolve;
36641
36939
  rejectFirstReady = reject;
36642
36940
  });
36941
+ let extensionHello = null;
36942
+ let warnedUnverifiable = false;
36943
+ let cachedPin = void 0;
36944
+ const authenticateExtension = async (sessionSig, extensionSessionPub) => {
36945
+ if (!extensionHello) {
36946
+ if (opts.requireExtensionIdentity) {
36947
+ 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.`);
36948
+ return false;
36949
+ }
36950
+ if (!warnedUnverifiable) {
36951
+ warnedUnverifiable = true;
36952
+ 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.`);
36953
+ }
36954
+ return true;
36955
+ }
36956
+ const payload = readySignaturePayload(sessionNonce, fromB64(extensionHello.sessionNonce), fromB64(extensionSessionPub));
36957
+ let sigOk = false;
36958
+ try {
36959
+ sigOk = await ed25519Verify(fromB64(extensionHello.identityEd25519Pub), payload, fromB64(sessionSig));
36960
+ } catch {
36961
+ sigOk = false;
36962
+ }
36963
+ if (!sigOk) {
36964
+ console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
36965
+ return false;
36966
+ }
36967
+ if (cachedPin === void 0) {
36968
+ try {
36969
+ cachedPin = await opts.extensionTrust.read();
36970
+ } catch (e) {
36971
+ console.error(`[fetchproxy] ${String(e)}`);
36972
+ return false;
36973
+ }
36974
+ }
36975
+ const pin = cachedPin;
36976
+ const outcome = decideExtensionTrust({
36977
+ pin,
36978
+ hello: extensionHello,
36979
+ allowNew: opts.extensionTrust.allowNew,
36980
+ serverName: opts.serverName,
36981
+ location: opts.extensionTrust.location
36982
+ });
36983
+ if (outcome.decision === "refused") {
36984
+ console.warn(outcome.message);
36985
+ return false;
36986
+ }
36987
+ if (outcome.decision === "replace")
36988
+ console.warn(outcome.message);
36989
+ if (outcome.decision !== "pinned") {
36990
+ try {
36991
+ const written = {
36992
+ identityX25519Pub: extensionHello.identityX25519Pub,
36993
+ identityEd25519Pub: extensionHello.identityEd25519Pub,
36994
+ pinnedAt: Date.now()
36995
+ };
36996
+ await opts.extensionTrust.write(written);
36997
+ cachedPin = written;
36998
+ } catch (e) {
36999
+ console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
37000
+ }
37001
+ }
37002
+ return true;
37003
+ };
36643
37004
  const onMessage = async (data) => {
36644
37005
  try {
36645
37006
  const raw = JSON.parse(data.toString());
36646
37007
  const frame = validateFrame(raw);
37008
+ if (frame.type === "hello" && frame.role === "extension") {
37009
+ extensionHello = frame;
37010
+ return;
37011
+ }
36647
37012
  if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
37013
+ const authorised = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub);
37014
+ if (!authorised) {
37015
+ ws.close(1008, "extension identity refused");
37016
+ rejectFirstReady(new Error("peer: extension identity refused"));
37017
+ return;
37018
+ }
36648
37019
  const extPub = fromB64(frame.extensionSessionPub);
36649
37020
  const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
36650
37021
  const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
@@ -36725,57 +37096,6 @@ async function startPeer(opts) {
36725
37096
  return handle;
36726
37097
  }
36727
37098
 
36728
- // node_modules/@fetchproxy/server/dist/identity.js
36729
- import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
36730
- import { join as join2 } from "node:path";
36731
- import { homedir as homedir2 } from "node:os";
36732
- var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
36733
- var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
36734
- function defaultIdentityDir() {
36735
- return join2(homedir2(), ".fetchproxy", "identity");
36736
- }
36737
- async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
36738
- if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
36739
- throw new Error(`unsafe serverName for identity file: ${JSON.stringify(serverName)}`);
36740
- }
36741
- const safeFile = serverName.replace(/\//g, "_");
36742
- const path = join2(dir, `${safeFile}.json`);
36743
- await mkdir(dir, { recursive: true, mode: 448 });
36744
- try {
36745
- const raw = await readFile(path, "utf8");
36746
- const j2 = JSON.parse(raw);
36747
- return {
36748
- x25519Priv: fromB64(j2.x25519Priv),
36749
- x25519Pub: fromB64(j2.x25519Pub),
36750
- ed25519Priv: fromB64(j2.ed25519Priv),
36751
- ed25519Pub: fromB64(j2.ed25519Pub),
36752
- createdAt: j2.createdAt
36753
- };
36754
- } catch (e) {
36755
- if (e.code !== "ENOENT")
36756
- throw e;
36757
- }
36758
- const x = await generateX25519();
36759
- const ed = await generateEd25519();
36760
- const id = {
36761
- x25519Priv: x.privateKey,
36762
- x25519Pub: x.publicKey,
36763
- ed25519Priv: ed.privateKey,
36764
- ed25519Pub: ed.publicKey,
36765
- createdAt: Date.now()
36766
- };
36767
- const j = {
36768
- x25519Priv: toB64(id.x25519Priv),
36769
- x25519Pub: toB64(id.x25519Pub),
36770
- ed25519Priv: toB64(id.ed25519Priv),
36771
- ed25519Pub: toB64(id.ed25519Pub),
36772
- createdAt: id.createdAt
36773
- };
36774
- await writeFile(path, JSON.stringify(j, null, 2), { mode: 384 });
36775
- await chmod(path, 384);
36776
- return id;
36777
- }
36778
-
36779
37099
  // node_modules/@fetchproxy/server/dist/error-kind.js
36780
37100
  function classifyFetchError(error51) {
36781
37101
  if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
@@ -36857,6 +37177,39 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
36857
37177
  this.hint = hint;
36858
37178
  }
36859
37179
  };
37180
+ var FetchproxyHintedError = class extends FetchproxyProtocolError {
37181
+ /** The extension's raw rejection, unmodified. */
37182
+ originalError;
37183
+ /** What the user should actually do, in prose. */
37184
+ hint;
37185
+ constructor(originalError, hint) {
37186
+ super(`${originalError} \u2014 ${hint}`);
37187
+ this.name = "FetchproxyHintedError";
37188
+ this.originalError = originalError;
37189
+ this.hint = hint;
37190
+ }
37191
+ };
37192
+ var FetchproxyScopeError = class extends FetchproxyHintedError {
37193
+ constructor(originalError) {
37194
+ 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.");
37195
+ this.name = "FetchproxyScopeError";
37196
+ }
37197
+ };
37198
+ var FetchproxyNoTabError = class extends FetchproxyHintedError {
37199
+ constructor(originalError) {
37200
+ 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.");
37201
+ this.name = "FetchproxyNoTabError";
37202
+ }
37203
+ };
37204
+ var SCOPE_REJECTION = /not in declared/;
37205
+ var NO_TAB_REJECTION = /no tab matching (?!.*content script loaded)/;
37206
+ function protocolErrorFrom(error51) {
37207
+ if (SCOPE_REJECTION.test(error51))
37208
+ return new FetchproxyScopeError(error51);
37209
+ if (NO_TAB_REJECTION.test(error51))
37210
+ return new FetchproxyNoTabError(error51);
37211
+ return new FetchproxyProtocolError(error51);
37212
+ }
36860
37213
  var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
36861
37214
  url;
36862
37215
  timeoutMs;
@@ -36886,6 +37239,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
36886
37239
  }
36887
37240
  };
36888
37241
  var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
37242
+ function normalizeCookiePath(path) {
37243
+ if (path === void 0 || path === "")
37244
+ return void 0;
37245
+ const trimmed = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
37246
+ try {
37247
+ assertCookiePath(trimmed, "path");
37248
+ } catch (e) {
37249
+ throw new Error(`FetchproxyServer: ${e instanceof Error ? e.message : String(e)} (got ${JSON.stringify(path)})`);
37250
+ }
37251
+ return trimmed;
37252
+ }
36889
37253
  function assertSubdomainLabel(label) {
36890
37254
  if (!SUBDOMAIN_LABEL_RE.test(label)) {
36891
37255
  throw new Error(`FetchproxyServer: subdomain must be a DNS label like "www" or "api" (or dot-separated like "auth.api"), got ${JSON.stringify(label)}`);
@@ -36950,6 +37314,9 @@ var FetchproxyServer = class {
36950
37314
  // them off from `pending` (fetch) and `pendingReadCookies` (legacy
36951
37315
  // string-shape) so the response routing in `onInner` stays linear.
36952
37316
  pendingStorage = /* @__PURE__ */ new Map();
37317
+ // 1.12.0+: write-cookies awaiters resolve the list of names actually
37318
+ // written, so a caller can confirm rather than assume.
37319
+ pendingWriteCookies = /* @__PURE__ */ new Map();
36953
37320
  // 0.3.0+: capture-header awaiters resolve a single string.
36954
37321
  pendingCapture = /* @__PURE__ */ new Map();
36955
37322
  // capture_redirect awaiters resolve the captured redirect URL string.
@@ -37076,6 +37443,8 @@ var FetchproxyServer = class {
37076
37443
  keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
37077
37444
  keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
37078
37445
  identityDir: opts.identityDir,
37446
+ allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
37447
+ requireExtensionIdentity: opts.requireExtensionIdentity,
37079
37448
  onPairCode: opts.onPairCode
37080
37449
  };
37081
37450
  }
@@ -37175,7 +37544,8 @@ var FetchproxyServer = class {
37175
37544
  ownSessionStoragePointers: this.opts.sessionStoragePointers,
37176
37545
  ownDomSelectors: this.opts.domSelectors,
37177
37546
  ownGraphqlOps: this.opts.graphqlOps,
37178
- onPairCode: this.opts.onPairCode
37547
+ onPairCode: this.opts.onPairCode,
37548
+ extensionTrust: this.extensionTrust()
37179
37549
  });
37180
37550
  this.hostHandle.onOwnInner((inner) => this.onInner(inner));
37181
37551
  this.hostHandle.onExtensionDisconnect(() => {
@@ -37204,7 +37574,9 @@ var FetchproxyServer = class {
37204
37574
  localStoragePointers: this.opts.localStoragePointers,
37205
37575
  sessionStoragePointers: this.opts.sessionStoragePointers,
37206
37576
  domSelectors: this.opts.domSelectors,
37207
- graphqlOps: this.opts.graphqlOps
37577
+ graphqlOps: this.opts.graphqlOps,
37578
+ extensionTrust: this.extensionTrust(),
37579
+ requireExtensionIdentity: this.opts.requireExtensionIdentity
37208
37580
  });
37209
37581
  this.peerHandle.onInner((inner) => this.onInner(inner));
37210
37582
  this.peerHandle.onRenegotiate(() => {
@@ -37343,6 +37715,23 @@ var FetchproxyServer = class {
37343
37715
  markActive() {
37344
37716
  this.noteActivityForKeepalive();
37345
37717
  }
37718
+ /**
37719
+ * #208: this MCP's pin on the extension's identity, stored beside its own
37720
+ * identity key and so following `identityDir` wherever the caller put it.
37721
+ *
37722
+ * `allowNewExtensionIdentity` falls back to an environment variable when the
37723
+ * caller expressed no opinion, because the thirteen MCPs that construct this
37724
+ * class are separate packages: an operator whose extension re-install has
37725
+ * just locked all of them out needs one lever that does not require patching
37726
+ * every one of them.
37727
+ */
37728
+ extensionTrust() {
37729
+ return fileExtensionTrust({
37730
+ serverName: this.opts.serverName,
37731
+ dir: this.opts.identityDir,
37732
+ allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
37733
+ });
37734
+ }
37346
37735
  noteActivityForKeepalive() {
37347
37736
  const intervalMs = this.opts.keepAliveIntervalMs;
37348
37737
  if (intervalMs <= 0)
@@ -37406,6 +37795,7 @@ var FetchproxyServer = class {
37406
37795
  this.pending.delete(id);
37407
37796
  this.pendingReadCookies.delete(id);
37408
37797
  this.pendingStorage.delete(id);
37798
+ this.pendingWriteCookies.delete(id);
37409
37799
  this.pendingCapture.delete(id);
37410
37800
  this.pendingRedirect.delete(id);
37411
37801
  this.pendingDownload.delete(id);
@@ -37527,7 +37917,7 @@ var FetchproxyServer = class {
37527
37917
  port: this.opts.port
37528
37918
  });
37529
37919
  }
37530
- return new FetchproxyProtocolError(result.error);
37920
+ return protocolErrorFrom(result.error);
37531
37921
  }
37532
37922
  /**
37533
37923
  * Convenience wrapper around `fetch()`. Builds the URL from a path
@@ -37563,10 +37953,20 @@ var FetchproxyServer = class {
37563
37953
  }
37564
37954
  const url2 = isAbsolute ? path : `https://${host}${path}`;
37565
37955
  assertUrlInDomains("request url", url2, this.opts.domains);
37956
+ let tabUrl = `https://${host}/`;
37957
+ if (opts.viaTab !== void 0) {
37958
+ try {
37959
+ new URL(opts.viaTab);
37960
+ } catch {
37961
+ throw new Error(`FetchproxyServer.request: viaTab is not a valid URL: ${JSON.stringify(opts.viaTab)}`);
37962
+ }
37963
+ assertUrlInDomains("viaTab", opts.viaTab, this.opts.domains);
37964
+ tabUrl = opts.viaTab;
37965
+ }
37566
37966
  const init = {
37567
37967
  url: url2,
37568
37968
  method,
37569
- tabUrl: `https://${host}/`,
37969
+ tabUrl,
37570
37970
  headers: opts.headers,
37571
37971
  body: opts.body
37572
37972
  };
@@ -37778,9 +38178,14 @@ var FetchproxyServer = class {
37778
38178
  let inner;
37779
38179
  if (opts.keys !== void 0) {
37780
38180
  this.assertScopeSubset(opts.keys, this.opts.cookieKeys, "cookieKeys");
38181
+ const cookiePath = normalizeCookiePath(opts.path);
37781
38182
  const initV3 = {
38183
+ // Origin stays BARE. The path travels as its own validated field —
38184
+ // `assertHttpsOriginOnly` deliberately refuses a path here so one
38185
+ // cannot be used to re-point the read past the domain gate.
37782
38186
  origin: `https://${host}`,
37783
- keys: [...opts.keys]
38187
+ keys: [...opts.keys],
38188
+ ...cookiePath !== void 0 ? { path: cookiePath } : {}
37784
38189
  };
37785
38190
  inner = { type: "request", id, op: "read_cookies", init: initV3 };
37786
38191
  } else {
@@ -37793,10 +38198,65 @@ var FetchproxyServer = class {
37793
38198
  await this.sendInnerFrame(inner);
37794
38199
  const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
37795
38200
  if (!result.ok) {
37796
- throw new FetchproxyProtocolError(result.error);
38201
+ throw protocolErrorFrom(result.error);
37797
38202
  }
37798
38203
  return result.cookies;
37799
38204
  }
38205
+ /**
38206
+ * 1.12.0+: overwrite the value of cookies this MCP already declares.
38207
+ *
38208
+ * The bridge's only write verb, and it exists for one failure class. Sites
38209
+ * that ROTATE a credential cookie hand back a new value on every refresh; if
38210
+ * the MCP refreshes and keeps the result to itself, the copy in the browser's
38211
+ * cookie jar is dead, and the user gets signed out of a tab they never
38212
+ * touched — usually reported to them as "inactivity". Writing the rotated
38213
+ * value back is the only thing that repairs it.
38214
+ *
38215
+ * Requires `'write_cookies'` in capabilities, which the user approves at pair
38216
+ * time as its own line. Every name must ALSO be in declared `cookieKeys`: a
38217
+ * write can never reach a cookie the MCP was not already trusted to read, so
38218
+ * granting it cannot widen which cookies are in play — only what may be done
38219
+ * to the ones already listed.
38220
+ *
38221
+ * The extension refuses the whole request unless every named cookie already
38222
+ * exists; this refreshes a value in place and deliberately cannot author new
38223
+ * cookies. Returns the names actually written.
38224
+ */
38225
+ async writeCookies(opts) {
38226
+ if (!this.opts.capabilities.includes("write_cookies")) {
38227
+ throw new Error('FetchproxyServer.writeCookies(): MCP did not declare "write_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
38228
+ }
38229
+ const names = Object.keys(opts.cookies);
38230
+ if (names.length === 0) {
38231
+ throw new Error("FetchproxyServer.writeCookies(): no cookies given");
38232
+ }
38233
+ await this.ensureConnected();
38234
+ this.throwIfPendingPair();
38235
+ if (opts.subdomain !== void 0)
38236
+ assertSubdomainLabel(opts.subdomain);
38237
+ const baseDomain = this.resolveBaseDomain(opts.domain);
38238
+ const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
38239
+ this.assertScopeSubset(names, this.opts.cookieKeys, "cookieKeys");
38240
+ const cookiePath = normalizeCookiePath(opts.path);
38241
+ const id = this.nextRequestId++;
38242
+ const inner = {
38243
+ type: "request",
38244
+ id,
38245
+ op: "write_cookies",
38246
+ init: {
38247
+ // Bare origin, same invariant as the read path: a path must never be
38248
+ // able to move the request past the domain gate.
38249
+ origin: `https://${host}`,
38250
+ cookies: Object.entries(opts.cookies).map(([name, value]) => ({ name, value })),
38251
+ ...cookiePath !== void 0 ? { path: cookiePath } : {}
38252
+ }
38253
+ };
38254
+ const pending = new Promise((resolve, reject) => {
38255
+ this.pendingWriteCookies.set(id, { resolve, reject });
38256
+ });
38257
+ await this.sendInnerFrame(inner);
38258
+ return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
38259
+ }
37800
38260
  /**
37801
38261
  * 0.3.0+: read declared localStorage keys from the user's signed-in
37802
38262
  * tab. Requires `'read_local_storage'` in capabilities AND each key
@@ -38343,7 +38803,7 @@ var FetchproxyServer = class {
38343
38803
  storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
38344
38804
  }
38345
38805
  } else {
38346
- storageCb.reject(new FetchproxyProtocolError(inner.error));
38806
+ storageCb.reject(protocolErrorFrom(inner.error));
38347
38807
  }
38348
38808
  return;
38349
38809
  }
@@ -38357,7 +38817,7 @@ var FetchproxyServer = class {
38357
38817
  captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
38358
38818
  }
38359
38819
  } else {
38360
- captureCb.reject(new FetchproxyProtocolError(inner.error));
38820
+ captureCb.reject(protocolErrorFrom(inner.error));
38361
38821
  }
38362
38822
  return;
38363
38823
  }
@@ -38371,7 +38831,7 @@ var FetchproxyServer = class {
38371
38831
  redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
38372
38832
  }
38373
38833
  } else {
38374
- redirectCb.reject(new FetchproxyProtocolError(inner.error));
38834
+ redirectCb.reject(protocolErrorFrom(inner.error));
38375
38835
  }
38376
38836
  return;
38377
38837
  }
@@ -38385,7 +38845,7 @@ var FetchproxyServer = class {
38385
38845
  idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
38386
38846
  }
38387
38847
  } else {
38388
- idbCb.reject(new FetchproxyProtocolError(inner.error));
38848
+ idbCb.reject(protocolErrorFrom(inner.error));
38389
38849
  }
38390
38850
  return;
38391
38851
  }
@@ -38399,7 +38859,7 @@ var FetchproxyServer = class {
38399
38859
  downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
38400
38860
  }
38401
38861
  } else {
38402
- downloadCb.reject(new FetchproxyProtocolError(inner.error));
38862
+ downloadCb.reject(protocolErrorFrom(inner.error));
38403
38863
  }
38404
38864
  return;
38405
38865
  }
@@ -38413,7 +38873,17 @@ var FetchproxyServer = class {
38413
38873
  graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
38414
38874
  }
38415
38875
  } else {
38416
- graphqlCb.reject(new FetchproxyProtocolError(inner.error));
38876
+ graphqlCb.reject(protocolErrorFrom(inner.error));
38877
+ }
38878
+ return;
38879
+ }
38880
+ const writeCookiesCb = this.pendingWriteCookies.get(inner.id);
38881
+ if (writeCookiesCb) {
38882
+ this.pendingWriteCookies.delete(inner.id);
38883
+ if (inner.ok && inner.op === "write_cookies") {
38884
+ writeCookiesCb.resolve([...inner.written]);
38885
+ } else {
38886
+ writeCookiesCb.reject(protocolErrorFrom(inner.ok ? "write_cookies response had the wrong op" : inner.error));
38417
38887
  }
38418
38888
  return;
38419
38889
  }
@@ -38456,6 +38926,9 @@ var FetchproxyServer = class {
38456
38926
  for (const { reject } of this.pendingStorage.values())
38457
38927
  reject(err);
38458
38928
  this.pendingStorage.clear();
38929
+ for (const { reject } of this.pendingWriteCookies.values())
38930
+ reject(err);
38931
+ this.pendingWriteCookies.clear();
38459
38932
  for (const { reject } of this.pendingCapture.values())
38460
38933
  reject(err);
38461
38934
  this.pendingCapture.clear();
@@ -38525,6 +38998,9 @@ var FetchproxyServer = class {
38525
38998
  // node_modules/@fetchproxy/bootstrap/dist/index.js
38526
38999
  var defaultFactory = (opts) => new FetchproxyServer(opts);
38527
39000
  async function bootstrap(opts) {
39001
+ return runOneLift(opts);
39002
+ }
39003
+ async function runOneLift(opts) {
38528
39004
  const envVar = opts.serverName.toUpperCase().replace(/[^A-Z0-9]/g, "_").replace(/^_+/, "") + "_DISABLE_FETCHPROXY";
38529
39005
  const envVal = process.env[envVar];
38530
39006
  if (envVal !== void 0 && envVal !== "" && envVal !== "0" && envVal !== "false") {
@@ -38593,7 +39069,9 @@ async function bootstrap(opts) {
38593
39069
  if (opts.declare.cookies.length > 0) {
38594
39070
  const joined = await server.readCookies({
38595
39071
  keys: opts.declare.cookies,
38596
- ...storageDomainOpts
39072
+ ...storageDomainOpts,
39073
+ // Cookie-only: the other buckets are origin-scoped and ignore path.
39074
+ ...opts.storagePath !== void 0 ? { path: opts.storagePath } : {}
38597
39075
  });
38598
39076
  for (const piece of joined.split("; ")) {
38599
39077
  if (!piece)
@@ -38651,12 +39129,18 @@ async function bootstrap(opts) {
38651
39129
  });
38652
39130
  indexedDbBucket[`${d.database}/${d.store}`] = values;
38653
39131
  }
39132
+ const absent = (declared, got) => declared.filter((k) => !(k in got));
38654
39133
  return {
38655
39134
  cookies,
38656
39135
  localStorage,
38657
39136
  sessionStorage,
38658
39137
  capturedHeaders,
38659
- indexedDb: indexedDbBucket
39138
+ indexedDb: indexedDbBucket,
39139
+ missing: {
39140
+ cookies: absent(opts.declare.cookies, cookies),
39141
+ localStorage: absent(opts.declare.localStorage, localStorage),
39142
+ sessionStorage: absent(opts.declare.sessionStorage, sessionStorage)
39143
+ }
38660
39144
  };
38661
39145
  } finally {
38662
39146
  try {
@@ -38712,7 +39196,7 @@ function bridgeErrorInfo(err) {
38712
39196
  // package.json
38713
39197
  var package_default = {
38714
39198
  name: "honeybook-mcp",
38715
- version: "0.4.3",
39199
+ version: "0.4.4",
38716
39200
  mcpName: "io.github.chrischall/honeybook-mcp",
38717
39201
  description: "HoneyBook client-portal MCP server for Claude",
38718
39202
  author: "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -38753,8 +39237,8 @@ var package_default = {
38753
39237
  },
38754
39238
  dependencies: {
38755
39239
  "@chrischall/mcp-utils": "^0.14.0",
38756
- "@fetchproxy/bootstrap": "^1.7.0",
38757
- "@fetchproxy/server": "^1.7.0",
39240
+ "@fetchproxy/bootstrap": "^2.0.0",
39241
+ "@fetchproxy/server": "^2.0.0",
38758
39242
  "@modelcontextprotocol/sdk": "^1.29.0",
38759
39243
  dotenv: "^17.4.2",
38760
39244
  zod: "^4.4.3"
@@ -39346,10 +39830,10 @@ function registerInvoiceTools(server) {
39346
39830
 
39347
39831
  // src/index.ts
39348
39832
  var __dirname = dirname2(fileURLToPath(import.meta.url));
39349
- await loadDotenvSafely({ path: join3(__dirname, "..", ".env"), override: false });
39833
+ await loadDotenvSafely({ path: join4(__dirname, "..", ".env"), override: false });
39350
39834
  await runMcp({
39351
39835
  name: "honeybook-mcp",
39352
- version: "0.4.3",
39836
+ version: "0.4.4",
39353
39837
  // x-release-please-version
39354
39838
  tools: [
39355
39839
  registerSessionTools,
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { registerContractTools } from './tools/contracts.js';
11
11
  import { registerInvoiceTools } from './tools/invoices.js';
12
12
  await runMcp({
13
13
  name: 'honeybook-mcp',
14
- version: '0.4.3', // x-release-please-version
14
+ version: '0.4.4', // x-release-please-version
15
15
  tools: [
16
16
  registerSessionTools,
17
17
  registerWorkspaceFileTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honeybook-mcp",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "mcpName": "io.github.chrischall/honeybook-mcp",
5
5
  "description": "HoneyBook client-portal MCP server for Claude",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@chrischall/mcp-utils": "^0.14.0",
44
- "@fetchproxy/bootstrap": "^1.7.0",
45
- "@fetchproxy/server": "^1.7.0",
44
+ "@fetchproxy/bootstrap": "^2.0.0",
45
+ "@fetchproxy/server": "^2.0.0",
46
46
  "@modelcontextprotocol/sdk": "^1.29.0",
47
47
  "dotenv": "^17.4.2",
48
48
  "zod": "^4.4.3"
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/honeybook-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.4.3",
9
+ "version": "0.4.4",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "honeybook-mcp",
14
- "version": "0.4.3",
14
+ "version": "0.4.4",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },