honeybook-mcp 0.1.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +22 -0
- package/dist/auth.js +6 -0
- package/dist/bundle.js +682 -91
- package/dist/index.js +1 -1
- package/package.json +9 -8
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "HoneyBook client-portal tools for Claude Code",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.3.0"
|
|
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.
|
|
18
|
+
"version": "0.3.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Chall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -22,6 +22,28 @@ Ask Claude things like:
|
|
|
22
22
|
- Magic-link emails from the wedding vendors that use HoneyBook
|
|
23
23
|
- [fetchproxy 0.3.0 browser extension](https://github.com/chrischall/fetchproxy) — installed in Chrome (Web Store) or Safari (.dmg). Used by `use_magic_link` to snapshot the session out of your signed-in vendor portal tab.
|
|
24
24
|
|
|
25
|
+
## Acknowledgement of Terms
|
|
26
|
+
|
|
27
|
+
By using this MCP server, you acknowledge and agree to the following:
|
|
28
|
+
|
|
29
|
+
**1. This server accesses your own HoneyBook workspace.** Every request is dispatched through your own signed-in browser session via the fetchproxy extension. It does not — and cannot — access anyone else's workspace.
|
|
30
|
+
|
|
31
|
+
**2. [HoneyBook's Terms of Service](https://www.honeybook.com/legal/terms-of-service) govern your use of this server**, just as they govern your direct use of honeybook.com. The clauses most relevant here:
|
|
32
|
+
|
|
33
|
+
> In connection with your use of the Service you will not engage in or use any data mining, robots, scraping or similar data gathering or extraction methods.
|
|
34
|
+
|
|
35
|
+
And: users may not "modify, copy, frame, scrape, rent, lease, loan, sell, distribute or create derivative works based on the Service or the Service Content."
|
|
36
|
+
|
|
37
|
+
You are agreeing to those terms — read by the maintainer 2026-05-23 — every time you invoke a tool in this server. HoneyBook's ToS broadly prohibits data mining and scraping; this MCP is an unofficial automation tool and HoneyBook has not granted it an exception.
|
|
38
|
+
|
|
39
|
+
**3. Personal, single-business use only.** This project is not affiliated with, endorsed by, sponsored by, or in partnership with HoneyBook, Inc. It is a personal automation tool that an individual HoneyBook account holder can use to drive their own workspace. Do not use it on behalf of another business, do not bulk-extract HoneyBook's directory or template content, and do not create a derivative SaaS product on top of it.
|
|
40
|
+
|
|
41
|
+
**4. Stability is not guaranteed.** This server calls internal HoneyBook endpoints that HoneyBook may change without notice. It may break.
|
|
42
|
+
|
|
43
|
+
**5. You accept full responsibility** for any consequences of using this server in connection with your HoneyBook account — rate limiting, account warnings, suspension, or any enforcement action HoneyBook takes. HoneyBook can detect automated traffic and may block your IP or workspace. If HoneyBook objects to your use, stop using this server.
|
|
44
|
+
|
|
45
|
+
This section is the maintainer's good-faith summary of the terms — it is not legal advice and does not modify or supersede HoneyBook's actual ToS.
|
|
46
|
+
|
|
25
47
|
## Installation
|
|
26
48
|
|
|
27
49
|
### From source
|
package/dist/auth.js
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
// - Persistence (sessionStore.add) is the existing side effect; tests
|
|
57
57
|
// verify the captured session round-trips through sessionStore.get.
|
|
58
58
|
import { bootstrap } from '@fetchproxy/bootstrap';
|
|
59
|
+
import { classifyBridgeError } from '@fetchproxy/server';
|
|
59
60
|
import pkg from '../package.json' with { type: 'json' };
|
|
60
61
|
import { sessionStore, normalizeOrigin } from './sessions.js';
|
|
61
62
|
/**
|
|
@@ -167,6 +168,11 @@ export async function captureSessionViaFetchproxy(opts) {
|
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
170
|
catch (e) {
|
|
171
|
+
// SW eviction retry exhausted — surface library's typed `.hint` instead of the generic "open magic link" message.
|
|
172
|
+
if (classifyBridgeError(e) === 'bridge_down') {
|
|
173
|
+
const downErr = e;
|
|
174
|
+
throw new Error(`HoneyBook auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`);
|
|
175
|
+
}
|
|
170
176
|
const msg = e instanceof Error ? e.message : String(e);
|
|
171
177
|
throw new Error(`HoneyBook auth: fetchproxy capture failed: ${msg} — ` +
|
|
172
178
|
'open the vendor magic-link URL in Chrome (with the fetchproxy 0.3.0 extension installed), ' +
|
package/dist/bundle.js
CHANGED
|
@@ -7571,6 +7571,10 @@ var require_receiver = __commonJS({
|
|
|
7571
7571
|
* extensions
|
|
7572
7572
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
|
7573
7573
|
* client or server mode
|
|
7574
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
7575
|
+
* buffered data chunks
|
|
7576
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
7577
|
+
* fragments
|
|
7574
7578
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
7575
7579
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
7576
7580
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -7581,6 +7585,8 @@ var require_receiver = __commonJS({
|
|
|
7581
7585
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
|
7582
7586
|
this._extensions = options.extensions || {};
|
|
7583
7587
|
this._isServer = !!options.isServer;
|
|
7588
|
+
this._maxBufferedChunks = options.maxBufferedChunks | 0;
|
|
7589
|
+
this._maxFragments = options.maxFragments | 0;
|
|
7584
7590
|
this._maxPayload = options.maxPayload | 0;
|
|
7585
7591
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
|
7586
7592
|
this[kWebSocket] = void 0;
|
|
@@ -7610,6 +7616,18 @@ var require_receiver = __commonJS({
|
|
|
7610
7616
|
*/
|
|
7611
7617
|
_write(chunk, encoding, cb) {
|
|
7612
7618
|
if (this._opcode === 8 && this._state == GET_INFO) return cb();
|
|
7619
|
+
if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) {
|
|
7620
|
+
cb(
|
|
7621
|
+
this.createError(
|
|
7622
|
+
RangeError,
|
|
7623
|
+
"Too many buffered chunks",
|
|
7624
|
+
false,
|
|
7625
|
+
1008,
|
|
7626
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
7627
|
+
)
|
|
7628
|
+
);
|
|
7629
|
+
return;
|
|
7630
|
+
}
|
|
7613
7631
|
this._bufferedBytes += chunk.length;
|
|
7614
7632
|
this._buffers.push(chunk);
|
|
7615
7633
|
this.startLoop(cb);
|
|
@@ -7939,6 +7957,17 @@ var require_receiver = __commonJS({
|
|
|
7939
7957
|
return;
|
|
7940
7958
|
}
|
|
7941
7959
|
if (data.length) {
|
|
7960
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
7961
|
+
const error51 = this.createError(
|
|
7962
|
+
RangeError,
|
|
7963
|
+
"Too many message fragments",
|
|
7964
|
+
false,
|
|
7965
|
+
1008,
|
|
7966
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
7967
|
+
);
|
|
7968
|
+
cb(error51);
|
|
7969
|
+
return;
|
|
7970
|
+
}
|
|
7942
7971
|
this._messageLength = this._totalPayloadLength;
|
|
7943
7972
|
this._fragments.push(data);
|
|
7944
7973
|
}
|
|
@@ -7968,6 +7997,17 @@ var require_receiver = __commonJS({
|
|
|
7968
7997
|
cb(error51);
|
|
7969
7998
|
return;
|
|
7970
7999
|
}
|
|
8000
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
8001
|
+
const error51 = this.createError(
|
|
8002
|
+
RangeError,
|
|
8003
|
+
"Too many message fragments",
|
|
8004
|
+
false,
|
|
8005
|
+
1008,
|
|
8006
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
8007
|
+
);
|
|
8008
|
+
cb(error51);
|
|
8009
|
+
return;
|
|
8010
|
+
}
|
|
7971
8011
|
this._fragments.push(buf);
|
|
7972
8012
|
}
|
|
7973
8013
|
this.dataMessage(cb);
|
|
@@ -9174,6 +9214,10 @@ var require_websocket = __commonJS({
|
|
|
9174
9214
|
* multiple times in the same tick
|
|
9175
9215
|
* @param {Function} [options.generateMask] The function used to generate the
|
|
9176
9216
|
* masking key
|
|
9217
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
9218
|
+
* buffered data chunks
|
|
9219
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
9220
|
+
* fragments
|
|
9177
9221
|
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
|
9178
9222
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
9179
9223
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -9185,6 +9229,8 @@ var require_websocket = __commonJS({
|
|
|
9185
9229
|
binaryType: this.binaryType,
|
|
9186
9230
|
extensions: this._extensions,
|
|
9187
9231
|
isServer: this._isServer,
|
|
9232
|
+
maxBufferedChunks: options.maxBufferedChunks,
|
|
9233
|
+
maxFragments: options.maxFragments,
|
|
9188
9234
|
maxPayload: options.maxPayload,
|
|
9189
9235
|
skipUTF8Validation: options.skipUTF8Validation
|
|
9190
9236
|
});
|
|
@@ -9484,6 +9530,8 @@ var require_websocket = __commonJS({
|
|
|
9484
9530
|
autoPong: true,
|
|
9485
9531
|
closeTimeout: CLOSE_TIMEOUT,
|
|
9486
9532
|
protocolVersion: protocolVersions[1],
|
|
9533
|
+
maxBufferedChunks: 1024 * 1024,
|
|
9534
|
+
maxFragments: 128 * 1024,
|
|
9487
9535
|
maxPayload: 100 * 1024 * 1024,
|
|
9488
9536
|
skipUTF8Validation: false,
|
|
9489
9537
|
perMessageDeflate: true,
|
|
@@ -9726,6 +9774,8 @@ var require_websocket = __commonJS({
|
|
|
9726
9774
|
websocket.setSocket(socket, head, {
|
|
9727
9775
|
allowSynchronousEvents: opts.allowSynchronousEvents,
|
|
9728
9776
|
generateMask: opts.generateMask,
|
|
9777
|
+
maxBufferedChunks: opts.maxBufferedChunks,
|
|
9778
|
+
maxFragments: opts.maxFragments,
|
|
9729
9779
|
maxPayload: opts.maxPayload,
|
|
9730
9780
|
skipUTF8Validation: opts.skipUTF8Validation
|
|
9731
9781
|
});
|
|
@@ -10068,6 +10118,10 @@ var require_websocket_server = __commonJS({
|
|
|
10068
10118
|
* called
|
|
10069
10119
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
10070
10120
|
* @param {String} [options.host] The hostname where to bind the server
|
|
10121
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
10122
|
+
* buffered data chunks
|
|
10123
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
10124
|
+
* fragments
|
|
10071
10125
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
10072
10126
|
* size
|
|
10073
10127
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
@@ -10089,6 +10143,8 @@ var require_websocket_server = __commonJS({
|
|
|
10089
10143
|
options = {
|
|
10090
10144
|
allowSynchronousEvents: true,
|
|
10091
10145
|
autoPong: true,
|
|
10146
|
+
maxBufferedChunks: 1024 * 1024,
|
|
10147
|
+
maxFragments: 128 * 1024,
|
|
10092
10148
|
maxPayload: 100 * 1024 * 1024,
|
|
10093
10149
|
skipUTF8Validation: false,
|
|
10094
10150
|
perMessageDeflate: false,
|
|
@@ -10368,6 +10424,8 @@ var require_websocket_server = __commonJS({
|
|
|
10368
10424
|
socket.removeListener("error", socketOnError);
|
|
10369
10425
|
ws.setSocket(socket, head, {
|
|
10370
10426
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
10427
|
+
maxBufferedChunks: this.options.maxBufferedChunks,
|
|
10428
|
+
maxFragments: this.options.maxFragments,
|
|
10371
10429
|
maxPayload: this.options.maxPayload,
|
|
10372
10430
|
skipUTF8Validation: this.options.skipUTF8Validation
|
|
10373
10431
|
});
|
|
@@ -34701,6 +34759,7 @@ async function getActiveClient(origin) {
|
|
|
34701
34759
|
|
|
34702
34760
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
34703
34761
|
var PROTOCOL_VERSION = 2;
|
|
34762
|
+
var HKDF_SESSION_INFO = "fetchproxy/1.0.0/session";
|
|
34704
34763
|
var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
34705
34764
|
"fetch",
|
|
34706
34765
|
"read_cookies",
|
|
@@ -34775,7 +34834,7 @@ var ProtocolError = class extends Error {
|
|
|
34775
34834
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
34776
34835
|
var BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
34777
34836
|
var SCOPE_KEY_RE = /^[A-Za-z0-9_.\-]{1,256}$/;
|
|
34778
|
-
var SCOPE_KEY_GLOB_RE = /^[A-Za-z0-9_.\-]{1,
|
|
34837
|
+
var SCOPE_KEY_GLOB_RE = /^[A-Za-z0-9_.\-]{1,256}\*?$/;
|
|
34779
34838
|
var HEADER_NAME_RE = /^[A-Za-z0-9_\-]{1,128}$/;
|
|
34780
34839
|
var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
34781
34840
|
function assertObject(x, label) {
|
|
@@ -34904,7 +34963,7 @@ function assertStoragePointersArray(value, label, declaredKeys) {
|
|
|
34904
34963
|
if (entry.jsonPointer === void 0) {
|
|
34905
34964
|
throw new ProtocolError(`${label}[${i}].jsonPointer: missing`);
|
|
34906
34965
|
}
|
|
34907
|
-
if (typeof entry.key !== "string" || !
|
|
34966
|
+
if (typeof entry.key !== "string" || !SCOPE_KEY_RE.test(entry.key)) {
|
|
34908
34967
|
throw new ProtocolError(`${label}[${i}].key: invalid key ${JSON.stringify(entry.key)}`);
|
|
34909
34968
|
}
|
|
34910
34969
|
if (typeof entry.jsonPointer !== "string" || !isValidJsonPointer(entry.jsonPointer)) {
|
|
@@ -35003,6 +35062,8 @@ function validateFrame(raw) {
|
|
|
35003
35062
|
return validateReady(raw);
|
|
35004
35063
|
if (t === "frame")
|
|
35005
35064
|
return validateEncrypted(raw);
|
|
35065
|
+
if (t === "pair-pending")
|
|
35066
|
+
return validatePairPending(raw);
|
|
35006
35067
|
throw new ProtocolError(`unknown frame type: ${String(t)}`);
|
|
35007
35068
|
}
|
|
35008
35069
|
function validateHello(raw) {
|
|
@@ -35104,6 +35165,17 @@ function validateEncrypted(raw) {
|
|
|
35104
35165
|
assertBase64(raw.ciphertext, "frame.ciphertext");
|
|
35105
35166
|
return raw;
|
|
35106
35167
|
}
|
|
35168
|
+
var PAIR_CODE_RE = /^\d{3}-\d{3}$/;
|
|
35169
|
+
function validatePairPending(raw) {
|
|
35170
|
+
assertString(raw.mcpId, "pair-pending.mcpId");
|
|
35171
|
+
if (!isValidMcpId(raw.mcpId))
|
|
35172
|
+
throw new ProtocolError("pair-pending.mcpId: invalid format");
|
|
35173
|
+
assertString(raw.pairCode, "pair-pending.pairCode");
|
|
35174
|
+
if (!PAIR_CODE_RE.test(raw.pairCode)) {
|
|
35175
|
+
throw new ProtocolError(`pair-pending.pairCode: must match XXX-XXX, got ${String(raw.pairCode)}`);
|
|
35176
|
+
}
|
|
35177
|
+
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
35178
|
+
}
|
|
35107
35179
|
function validateInnerFrame(raw) {
|
|
35108
35180
|
assertObject(raw, "inner");
|
|
35109
35181
|
const t = raw.type;
|
|
@@ -35686,15 +35758,22 @@ async function startHost(opts) {
|
|
|
35686
35758
|
let extensionWs = null;
|
|
35687
35759
|
const peers = /* @__PURE__ */ new Map();
|
|
35688
35760
|
const ownInnerListeners = [];
|
|
35761
|
+
const disconnectListeners = [];
|
|
35762
|
+
const pendingPairListeners = [];
|
|
35689
35763
|
let ownSession = null;
|
|
35764
|
+
let ownPendingPairCode = null;
|
|
35690
35765
|
let resolveOwnSession;
|
|
35691
35766
|
let rejectOwnSession;
|
|
35692
|
-
|
|
35693
|
-
|
|
35694
|
-
|
|
35695
|
-
|
|
35696
|
-
|
|
35697
|
-
|
|
35767
|
+
let ownSessionReady;
|
|
35768
|
+
function resetSessionPromise() {
|
|
35769
|
+
ownSessionReady = new Promise((resolve, reject) => {
|
|
35770
|
+
resolveOwnSession = resolve;
|
|
35771
|
+
rejectOwnSession = reject;
|
|
35772
|
+
});
|
|
35773
|
+
ownSessionReady.catch(() => {
|
|
35774
|
+
});
|
|
35775
|
+
}
|
|
35776
|
+
resetSessionPromise();
|
|
35698
35777
|
let extensionHello = null;
|
|
35699
35778
|
wss.on("connection", (ws) => {
|
|
35700
35779
|
let identified = null;
|
|
@@ -35763,11 +35842,12 @@ async function startHost(opts) {
|
|
|
35763
35842
|
}
|
|
35764
35843
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
35765
35844
|
const shared = await ecdhX25519(opts.ownIdentity.x25519Priv, extPub);
|
|
35766
|
-
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(
|
|
35767
|
-
if (
|
|
35768
|
-
|
|
35769
|
-
|
|
35770
|
-
|
|
35845
|
+
const key = await hkdfSha256(shared, ownSessionNonce, enc2.encode(HKDF_SESSION_INFO), 32);
|
|
35846
|
+
if (extensionWs !== ws)
|
|
35847
|
+
return;
|
|
35848
|
+
ownSession = new SessionState(key);
|
|
35849
|
+
ownPendingPairCode = null;
|
|
35850
|
+
resolveOwnSession(ownSession);
|
|
35771
35851
|
} else {
|
|
35772
35852
|
const slot = peers.get(frame.mcpId);
|
|
35773
35853
|
if (slot)
|
|
@@ -35794,6 +35874,16 @@ async function startHost(opts) {
|
|
|
35794
35874
|
extensionWs.send(JSON.stringify(frame));
|
|
35795
35875
|
}
|
|
35796
35876
|
}
|
|
35877
|
+
if (frame.type === "pair-pending" && identified === "extension") {
|
|
35878
|
+
if (frame.mcpId === opts.ownMcpId) {
|
|
35879
|
+
ownPendingPairCode = frame.pairCode;
|
|
35880
|
+
pendingPairListeners.forEach((cb) => cb(frame.pairCode));
|
|
35881
|
+
} else {
|
|
35882
|
+
const slot = peers.get(frame.mcpId);
|
|
35883
|
+
if (slot)
|
|
35884
|
+
slot.ws.send(JSON.stringify(frame));
|
|
35885
|
+
}
|
|
35886
|
+
}
|
|
35797
35887
|
} catch (e) {
|
|
35798
35888
|
console.error("[fetchproxy] host: message handler error:", e);
|
|
35799
35889
|
try {
|
|
@@ -35809,6 +35899,9 @@ async function startHost(opts) {
|
|
|
35809
35899
|
if (!ownSession) {
|
|
35810
35900
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
35811
35901
|
}
|
|
35902
|
+
ownSession = null;
|
|
35903
|
+
resetSessionPromise();
|
|
35904
|
+
disconnectListeners.forEach((cb) => cb());
|
|
35812
35905
|
}
|
|
35813
35906
|
if (identified === "peer" && peerMcpId)
|
|
35814
35907
|
peers.delete(peerMcpId);
|
|
@@ -35833,7 +35926,14 @@ async function startHost(opts) {
|
|
|
35833
35926
|
},
|
|
35834
35927
|
onOwnInner: (cb) => {
|
|
35835
35928
|
ownInnerListeners.push(cb);
|
|
35836
|
-
}
|
|
35929
|
+
},
|
|
35930
|
+
onExtensionDisconnect: (cb) => {
|
|
35931
|
+
disconnectListeners.push(cb);
|
|
35932
|
+
},
|
|
35933
|
+
onPendingPair: (cb) => {
|
|
35934
|
+
pendingPairListeners.push(cb);
|
|
35935
|
+
},
|
|
35936
|
+
pendingPairCode: () => ownPendingPairCode
|
|
35837
35937
|
};
|
|
35838
35938
|
}
|
|
35839
35939
|
|
|
@@ -35863,36 +35963,57 @@ async function startPeer(opts) {
|
|
|
35863
35963
|
const sessionNonce = fromB64(hello.sessionNonce);
|
|
35864
35964
|
ws.send(JSON.stringify(hello));
|
|
35865
35965
|
const innerListeners = [];
|
|
35966
|
+
const renegotiateListeners = [];
|
|
35967
|
+
const pendingPairListeners = [];
|
|
35866
35968
|
let session = null;
|
|
35969
|
+
let pendingPairCode = null;
|
|
35970
|
+
let resolveFirstReady;
|
|
35971
|
+
let rejectFirstReady;
|
|
35867
35972
|
const sessionPromise = new Promise((resolve, reject) => {
|
|
35868
|
-
|
|
35869
|
-
|
|
35870
|
-
|
|
35871
|
-
|
|
35872
|
-
|
|
35873
|
-
|
|
35874
|
-
|
|
35875
|
-
|
|
35876
|
-
|
|
35877
|
-
|
|
35878
|
-
|
|
35973
|
+
resolveFirstReady = resolve;
|
|
35974
|
+
rejectFirstReady = reject;
|
|
35975
|
+
});
|
|
35976
|
+
const onMessage = async (data) => {
|
|
35977
|
+
try {
|
|
35978
|
+
const raw = JSON.parse(data.toString());
|
|
35979
|
+
const frame = validateFrame(raw);
|
|
35980
|
+
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
35981
|
+
const extPub = fromB64(frame.extensionSessionPub);
|
|
35982
|
+
const shared = await ecdhX25519(opts.identity.x25519Priv, extPub);
|
|
35983
|
+
const sessionKey = await hkdfSha256(shared, sessionNonce, enc3.encode(HKDF_SESSION_INFO), 32);
|
|
35984
|
+
const isRenegotiation = session !== null;
|
|
35985
|
+
session = new SessionState(sessionKey);
|
|
35986
|
+
pendingPairCode = null;
|
|
35987
|
+
if (isRenegotiation) {
|
|
35988
|
+
renegotiateListeners.forEach((cb) => cb());
|
|
35989
|
+
} else {
|
|
35990
|
+
resolveFirstReady(session);
|
|
35879
35991
|
}
|
|
35880
|
-
|
|
35881
|
-
|
|
35882
|
-
|
|
35883
|
-
|
|
35884
|
-
|
|
35992
|
+
return;
|
|
35993
|
+
}
|
|
35994
|
+
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
35995
|
+
pendingPairCode = frame.pairCode;
|
|
35996
|
+
pendingPairListeners.forEach((cb) => cb(frame.pairCode));
|
|
35997
|
+
return;
|
|
35998
|
+
}
|
|
35999
|
+
if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
|
|
36000
|
+
if (!session)
|
|
36001
|
+
return;
|
|
36002
|
+
if (!session.acceptInboundSeq(frame.seq))
|
|
36003
|
+
return;
|
|
36004
|
+
try {
|
|
35885
36005
|
const inner = await openEncryptedFrame(session.sessionKey, frame);
|
|
35886
36006
|
innerListeners.forEach((cb) => cb(inner));
|
|
36007
|
+
} catch {
|
|
35887
36008
|
}
|
|
35888
|
-
} catch (e) {
|
|
35889
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
35890
36009
|
}
|
|
35891
|
-
}
|
|
35892
|
-
|
|
35893
|
-
|
|
35894
|
-
|
|
35895
|
-
|
|
36010
|
+
} catch (e) {
|
|
36011
|
+
rejectFirstReady(e instanceof Error ? e : new Error(String(e)));
|
|
36012
|
+
}
|
|
36013
|
+
};
|
|
36014
|
+
ws.on("message", onMessage);
|
|
36015
|
+
ws.once("close", () => {
|
|
36016
|
+
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
35896
36017
|
});
|
|
35897
36018
|
sessionPromise.catch(() => {
|
|
35898
36019
|
});
|
|
@@ -35900,13 +36021,21 @@ async function startPeer(opts) {
|
|
|
35900
36021
|
ws,
|
|
35901
36022
|
session: sessionPromise,
|
|
35902
36023
|
sendInner: async (inner) => {
|
|
35903
|
-
|
|
36024
|
+
await sessionPromise;
|
|
36025
|
+
const s = session;
|
|
35904
36026
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
35905
36027
|
ws.send(JSON.stringify(sealed));
|
|
35906
36028
|
},
|
|
35907
36029
|
onInner: (cb) => {
|
|
35908
36030
|
innerListeners.push(cb);
|
|
35909
36031
|
},
|
|
36032
|
+
onRenegotiate: (cb) => {
|
|
36033
|
+
renegotiateListeners.push(cb);
|
|
36034
|
+
},
|
|
36035
|
+
onPendingPair: (cb) => {
|
|
36036
|
+
pendingPairListeners.push(cb);
|
|
36037
|
+
},
|
|
36038
|
+
pendingPairCode: () => pendingPairCode,
|
|
35910
36039
|
close: () => ws.close()
|
|
35911
36040
|
};
|
|
35912
36041
|
return handle;
|
|
@@ -35963,6 +36092,32 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
35963
36092
|
return id;
|
|
35964
36093
|
}
|
|
35965
36094
|
|
|
36095
|
+
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
36096
|
+
function classifyFetchError(error51) {
|
|
36097
|
+
if (/Could not establish connection/i.test(error51) || /Receiving end does not exist/i.test(error51)) {
|
|
36098
|
+
return "content_script_unreachable";
|
|
36099
|
+
}
|
|
36100
|
+
if (/^tab fetch failed:/.test(error51)) {
|
|
36101
|
+
return "tab_fetch_failed";
|
|
36102
|
+
}
|
|
36103
|
+
if (/^fetch threw:/.test(error51)) {
|
|
36104
|
+
return "tab_fetch_failed";
|
|
36105
|
+
}
|
|
36106
|
+
if (/^no tab matching /.test(error51)) {
|
|
36107
|
+
return "no_tab";
|
|
36108
|
+
}
|
|
36109
|
+
if (/not in domains \[/.test(error51)) {
|
|
36110
|
+
return "domain_denied";
|
|
36111
|
+
}
|
|
36112
|
+
if (/^capability .+ not granted/.test(error51)) {
|
|
36113
|
+
return "capability_denied";
|
|
36114
|
+
}
|
|
36115
|
+
if (/^(request|response) body too large:/.test(error51)) {
|
|
36116
|
+
return "body_too_large";
|
|
36117
|
+
}
|
|
36118
|
+
return "other";
|
|
36119
|
+
}
|
|
36120
|
+
|
|
35966
36121
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
35967
36122
|
var FetchproxyProtocolError = class extends Error {
|
|
35968
36123
|
constructor(message) {
|
|
@@ -35978,6 +36133,52 @@ var FetchproxyHttpError = class extends Error {
|
|
|
35978
36133
|
this.name = "FetchproxyHttpError";
|
|
35979
36134
|
}
|
|
35980
36135
|
};
|
|
36136
|
+
var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
36137
|
+
originalError;
|
|
36138
|
+
retryAttempted;
|
|
36139
|
+
op;
|
|
36140
|
+
url;
|
|
36141
|
+
/** 0.8.0+: bridge role at throw time; `null` if listen() hadn't bound yet. */
|
|
36142
|
+
role;
|
|
36143
|
+
/** 0.8.0+: bridge port at throw time (the same port `listen()` bound to). */
|
|
36144
|
+
port;
|
|
36145
|
+
hint;
|
|
36146
|
+
constructor(args) {
|
|
36147
|
+
const retryAttempted = args.retryAttempted ?? false;
|
|
36148
|
+
const op = args.op ?? "fetch";
|
|
36149
|
+
const retryClause = retryAttempted ? `Server already burned a one-shot lazy-revive retry; SW is still down. ` : `Server lazy-revive retry was disabled (bridgeReviveDelayMs unset/0). `;
|
|
36150
|
+
const hint = `the fetchproxy extension's service worker is not responding ("${args.originalError}"). Chrome evicts extension service workers after ~30s idle by default. ${retryClause}Wake it by clicking the fetchproxy extension toolbar icon, then retry. If it keeps happening, reload the extension from chrome://extensions.`;
|
|
36151
|
+
super(`fetchproxy bridge down during ${op}${args.url ? ` (${args.url})` : ""}. ${hint}`);
|
|
36152
|
+
this.name = "FetchproxyBridgeDownError";
|
|
36153
|
+
this.originalError = args.originalError;
|
|
36154
|
+
this.retryAttempted = retryAttempted;
|
|
36155
|
+
this.op = op;
|
|
36156
|
+
if (args.url !== void 0)
|
|
36157
|
+
this.url = args.url;
|
|
36158
|
+
this.role = args.role ?? null;
|
|
36159
|
+
this.port = args.port ?? 0;
|
|
36160
|
+
this.hint = hint;
|
|
36161
|
+
}
|
|
36162
|
+
};
|
|
36163
|
+
var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
36164
|
+
url;
|
|
36165
|
+
timeoutMs;
|
|
36166
|
+
/** 0.8.0+: bridge role at throw time; `null` if listen() hadn't bound yet. */
|
|
36167
|
+
role;
|
|
36168
|
+
/** 0.8.0+: bridge port at throw time. */
|
|
36169
|
+
port;
|
|
36170
|
+
/** 0.8.0+: actual elapsed milliseconds when the timer won the race. */
|
|
36171
|
+
elapsedMs;
|
|
36172
|
+
constructor(args) {
|
|
36173
|
+
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms`);
|
|
36174
|
+
this.name = "FetchproxyTimeoutError";
|
|
36175
|
+
this.url = args.url;
|
|
36176
|
+
this.timeoutMs = args.timeoutMs;
|
|
36177
|
+
this.role = args.role ?? null;
|
|
36178
|
+
this.port = args.port ?? 0;
|
|
36179
|
+
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
36180
|
+
}
|
|
36181
|
+
};
|
|
35981
36182
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
35982
36183
|
function assertSubdomainLabel(label) {
|
|
35983
36184
|
if (!SUBDOMAIN_LABEL_RE.test(label)) {
|
|
@@ -36005,12 +36206,28 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
36005
36206
|
}
|
|
36006
36207
|
var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
|
|
36007
36208
|
var FetchproxyServer = class {
|
|
36008
|
-
/**
|
|
36209
|
+
/**
|
|
36210
|
+
* Bridge role. `null` until the first verb call (or an explicit
|
|
36211
|
+
* `connect()`) — `listen()` no longer triggers the role election
|
|
36212
|
+
* as of 0.5.3+. Reset to `null` on `close()`.
|
|
36213
|
+
*/
|
|
36009
36214
|
role = null;
|
|
36010
36215
|
opts;
|
|
36011
36216
|
hostHandle = null;
|
|
36012
36217
|
peerHandle = null;
|
|
36013
36218
|
nextRequestId = 1;
|
|
36219
|
+
// 0.8.0+: process-wide freshness counters surfaced via bridgeHealth().
|
|
36220
|
+
// Replaces the local copies every downstream MCP was rolling on top
|
|
36221
|
+
// of its own transport adapter — see realty-mcp cohort drift notes.
|
|
36222
|
+
// Updated by recordSuccess / recordFailure from fetch + capture paths.
|
|
36223
|
+
// `lastExtensionMessageAt` (#23 ask 4) is updated whenever any inner
|
|
36224
|
+
// frame from the extension arrives — gives extension-side liveness
|
|
36225
|
+
// distinct from per-call success/failure.
|
|
36226
|
+
lastSuccessAt = null;
|
|
36227
|
+
lastFailureAt = null;
|
|
36228
|
+
lastFailureReason = null;
|
|
36229
|
+
consecutiveFailures = 0;
|
|
36230
|
+
lastExtensionMessageAt = null;
|
|
36014
36231
|
pending = /* @__PURE__ */ new Map();
|
|
36015
36232
|
// Separate pending map for read_cookies so the response shape (cookies
|
|
36016
36233
|
// string vs status/body) doesn't have to share a union type with fetch.
|
|
@@ -36027,6 +36244,12 @@ var FetchproxyServer = class {
|
|
|
36027
36244
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
36028
36245
|
mcpId = null;
|
|
36029
36246
|
identity = null;
|
|
36247
|
+
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
36248
|
+
// first time a verb call runs `ensureConnected`, awaited by concurrent
|
|
36249
|
+
// callers, cleared once the connection is up. Single source of truth
|
|
36250
|
+
// for "we're connecting right now" so two parallel first-calls don't
|
|
36251
|
+
// race the port bind.
|
|
36252
|
+
connectingPromise = null;
|
|
36030
36253
|
constructor(opts) {
|
|
36031
36254
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
36032
36255
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
@@ -36073,28 +36296,98 @@ var FetchproxyServer = class {
|
|
|
36073
36296
|
key: d.key,
|
|
36074
36297
|
jsonPointer: d.jsonPointer
|
|
36075
36298
|
})),
|
|
36299
|
+
// 0.8.0+: timer + lazy-revive default to ON. Every realty MCP
|
|
36300
|
+
// adapter was about to set these to the same numbers anyway; the
|
|
36301
|
+
// back-door is `0` (explicit opt-out) if a caller genuinely wants
|
|
36302
|
+
// the legacy hang-forever / fail-once-on-SW-eviction behavior.
|
|
36303
|
+
fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
|
|
36304
|
+
bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
|
|
36076
36305
|
identityDir: opts.identityDir,
|
|
36077
36306
|
onPairCode: opts.onPairCode
|
|
36078
36307
|
};
|
|
36079
36308
|
}
|
|
36080
36309
|
/**
|
|
36081
|
-
*
|
|
36082
|
-
* from disk (creating it on first call)
|
|
36083
|
-
*
|
|
36084
|
-
*
|
|
36085
|
-
* `
|
|
36086
|
-
*
|
|
36310
|
+
* Prepare the bridge for use. Loads the long-term identity keypair
|
|
36311
|
+
* from disk (creating it on first call) and computes this instance's
|
|
36312
|
+
* `mcpId`. Does NOT bind the bridge port or dial any WebSocket — the
|
|
36313
|
+
* connection is established lazily on the first verb call (see
|
|
36314
|
+
* `ensureConnected` / `getOrConnect`).
|
|
36315
|
+
*
|
|
36316
|
+
* Pre-0.5.3 behavior: `listen()` also did role election and started
|
|
36317
|
+
* the host/peer immediately, which meant every configured-but-unused
|
|
36318
|
+
* MCP claimed bridge resources at MCP-client boot. Several MCPs
|
|
36319
|
+
* starting in parallel under Claude Desktop also produced noisy
|
|
36320
|
+
* `ERR_CONNECTION_REFUSED` errors in the extension if it raced ahead
|
|
36321
|
+
* of the first MCP's port bind. Deferring keeps boot quiet and
|
|
36322
|
+
* leaves the port unowned until something actually needs it.
|
|
36323
|
+
*
|
|
36324
|
+
* Calling `listen()` twice without an intervening `close()` is a
|
|
36325
|
+
* no-op (the second call's identity load is idempotent).
|
|
36087
36326
|
*/
|
|
36088
36327
|
async listen() {
|
|
36089
|
-
|
|
36090
|
-
|
|
36328
|
+
if (!this.identity) {
|
|
36329
|
+
this.identity = await loadOrCreateIdentity(this.opts.serverName, this.opts.identityDir);
|
|
36330
|
+
}
|
|
36331
|
+
if (!this.mcpId) {
|
|
36332
|
+
this.mcpId = generateMcpId(this.opts.serverName, this.opts.version);
|
|
36333
|
+
}
|
|
36334
|
+
}
|
|
36335
|
+
/**
|
|
36336
|
+
* Force an eager bridge connection (role-election + host/peer handle
|
|
36337
|
+
* start + listener wiring) without waiting for the first verb call.
|
|
36338
|
+
* Useful for callers that want to surface the role / connection
|
|
36339
|
+
* outcome at boot, or for tests whose harness dials a mock extension
|
|
36340
|
+
* immediately after server construction. Production MCPs that just
|
|
36341
|
+
* answer tool calls should NOT call this — the lazy connect via
|
|
36342
|
+
* `ensureConnected` will do the right thing on first use, keeping
|
|
36343
|
+
* boot cheap and avoiding port-bind contention for MCPs that never
|
|
36344
|
+
* actually get invoked.
|
|
36345
|
+
*
|
|
36346
|
+
* Idempotent: a second call after the first has resolved is a no-op
|
|
36347
|
+
* (the existing handle is reused). Throws if `listen()` was never
|
|
36348
|
+
* called.
|
|
36349
|
+
*/
|
|
36350
|
+
async connect() {
|
|
36351
|
+
await this.ensureConnected();
|
|
36352
|
+
}
|
|
36353
|
+
/**
|
|
36354
|
+
* Establish the bridge connection (role-election + host/peer handle
|
|
36355
|
+
* start + listener wiring) the first time a verb is invoked.
|
|
36356
|
+
* Idempotent after the connection is up; concurrent first-callers
|
|
36357
|
+
* share the same in-flight promise so only one election happens.
|
|
36358
|
+
*
|
|
36359
|
+
* Throws if `listen()` was never called — the contract is that the
|
|
36360
|
+
* MCP author still must wire `transport.start()` at boot to load
|
|
36361
|
+
* identity / set mcpId, even though the WS doesn't open until a
|
|
36362
|
+
* verb runs.
|
|
36363
|
+
*/
|
|
36364
|
+
async ensureConnected() {
|
|
36365
|
+
if (this.hostHandle || this.peerHandle)
|
|
36366
|
+
return;
|
|
36367
|
+
if (this.connectingPromise) {
|
|
36368
|
+
await this.connectingPromise;
|
|
36369
|
+
return;
|
|
36370
|
+
}
|
|
36371
|
+
if (!this.identity || !this.mcpId) {
|
|
36372
|
+
throw new Error("FetchproxyServer: ensureConnected called before listen() \u2014 call listen() at MCP boot to load identity");
|
|
36373
|
+
}
|
|
36374
|
+
this.connectingPromise = this.doConnect();
|
|
36375
|
+
try {
|
|
36376
|
+
await this.connectingPromise;
|
|
36377
|
+
} finally {
|
|
36378
|
+
this.connectingPromise = null;
|
|
36379
|
+
}
|
|
36380
|
+
}
|
|
36381
|
+
async doConnect() {
|
|
36382
|
+
const identity = this.identity;
|
|
36383
|
+
const mcpId = this.mcpId;
|
|
36091
36384
|
const el = await electRole({ host: this.opts.host, port: this.opts.port });
|
|
36092
36385
|
if (el.role === "host") {
|
|
36093
36386
|
this.role = "host";
|
|
36094
36387
|
this.hostHandle = await startHost({
|
|
36095
36388
|
httpServer: el.server,
|
|
36096
|
-
ownIdentity:
|
|
36097
|
-
ownMcpId:
|
|
36389
|
+
ownIdentity: identity,
|
|
36390
|
+
ownMcpId: mcpId,
|
|
36098
36391
|
ownServerName: this.opts.serverName,
|
|
36099
36392
|
ownVersion: this.opts.version,
|
|
36100
36393
|
ownDomains: this.opts.domains,
|
|
@@ -36109,13 +36402,17 @@ var FetchproxyServer = class {
|
|
|
36109
36402
|
onPairCode: this.opts.onPairCode
|
|
36110
36403
|
});
|
|
36111
36404
|
this.hostHandle.onOwnInner((inner) => this.onInner(inner));
|
|
36405
|
+
this.hostHandle.onExtensionDisconnect(() => this.rejectAllPending());
|
|
36406
|
+
this.hostHandle.onPendingPair((code) => {
|
|
36407
|
+
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36408
|
+
});
|
|
36112
36409
|
} else {
|
|
36113
36410
|
this.role = "peer";
|
|
36114
36411
|
this.peerHandle = await startPeer({
|
|
36115
36412
|
host: this.opts.host,
|
|
36116
36413
|
port: this.opts.port,
|
|
36117
|
-
identity
|
|
36118
|
-
mcpId
|
|
36414
|
+
identity,
|
|
36415
|
+
mcpId,
|
|
36119
36416
|
serverName: this.opts.serverName,
|
|
36120
36417
|
version: this.opts.version,
|
|
36121
36418
|
domains: this.opts.domains,
|
|
@@ -36129,8 +36426,19 @@ var FetchproxyServer = class {
|
|
|
36129
36426
|
sessionStoragePointers: this.opts.sessionStoragePointers
|
|
36130
36427
|
});
|
|
36131
36428
|
this.peerHandle.onInner((inner) => this.onInner(inner));
|
|
36429
|
+
this.peerHandle.onRenegotiate(() => this.rejectAllPending());
|
|
36430
|
+
this.peerHandle.onPendingPair((code) => {
|
|
36431
|
+
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
36432
|
+
});
|
|
36433
|
+
if (this.opts.onPairCode) {
|
|
36434
|
+
const cb = this.opts.onPairCode;
|
|
36435
|
+
this.peerHandle.onPendingPair((code) => cb(code));
|
|
36436
|
+
}
|
|
36132
36437
|
}
|
|
36133
36438
|
}
|
|
36439
|
+
pairingErrorMessage(code) {
|
|
36440
|
+
return `fetchproxy transport error: pairing required for ${this.opts.serverName}. Tell the user to open the Transporter browser extension popup and approve the pair request. The pair code is: ${code} \u2014 display this code to the user so they can verify it matches.`;
|
|
36441
|
+
}
|
|
36134
36442
|
/**
|
|
36135
36443
|
* Raw single-shot fetch through the bridge. Most callers should prefer
|
|
36136
36444
|
* the verb shortcuts (`get` / `post` / `getJson` / `postJson` / `getHtml`)
|
|
@@ -36146,9 +36454,75 @@ var FetchproxyServer = class {
|
|
|
36146
36454
|
* offline, etc.).
|
|
36147
36455
|
*/
|
|
36148
36456
|
async fetch(init) {
|
|
36149
|
-
|
|
36150
|
-
|
|
36457
|
+
await this.ensureConnected();
|
|
36458
|
+
const pendingCode = this.currentPendingPairCode();
|
|
36459
|
+
if (pendingCode !== null) {
|
|
36460
|
+
const error51 = this.pairingErrorMessage(pendingCode);
|
|
36461
|
+
return {
|
|
36462
|
+
ok: false,
|
|
36463
|
+
error: error51,
|
|
36464
|
+
kind: classifyFetchError(error51),
|
|
36465
|
+
retryAttempted: false
|
|
36466
|
+
};
|
|
36467
|
+
}
|
|
36468
|
+
const first = await this._fetchOnceWithTimeout(init);
|
|
36469
|
+
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
36470
|
+
let final = first;
|
|
36471
|
+
if (!first.ok && first.kind === "content_script_unreachable" && reviveMs !== void 0 && reviveMs > 0) {
|
|
36472
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36473
|
+
const second = await this._fetchOnceWithTimeout(init);
|
|
36474
|
+
if (second.ok)
|
|
36475
|
+
this.recordSuccess();
|
|
36476
|
+
else
|
|
36477
|
+
this.recordFailure(`${second.kind ?? "other"}: ${second.error}`);
|
|
36478
|
+
return { ...second, retryAttempted: true };
|
|
36151
36479
|
}
|
|
36480
|
+
if (first.ok)
|
|
36481
|
+
this.recordSuccess();
|
|
36482
|
+
else
|
|
36483
|
+
this.recordFailure(`${first.kind ?? "other"}: ${first.error}`);
|
|
36484
|
+
return { ...first, retryAttempted: false };
|
|
36485
|
+
}
|
|
36486
|
+
/**
|
|
36487
|
+
* 0.8.0+: snapshot of the bridge's process-wide freshness counters,
|
|
36488
|
+
* suitable for surfacing through a downstream MCP's healthcheck tool.
|
|
36489
|
+
* Counters reset on a success (consecutiveFailures), accumulate
|
|
36490
|
+
* across the process lifetime otherwise. Replaces the per-MCP
|
|
36491
|
+
* duplication the realty cohort had been rolling in their adapters.
|
|
36492
|
+
* `lastExtensionMessageAt` is updated whenever ANY inner frame
|
|
36493
|
+
* arrives from the extension — gives extension-side liveness
|
|
36494
|
+
* distinct from server-side success/failure of the user-visible
|
|
36495
|
+
* call (addresses #23 ask 4).
|
|
36496
|
+
*/
|
|
36497
|
+
bridgeHealth() {
|
|
36498
|
+
return {
|
|
36499
|
+
role: this.role,
|
|
36500
|
+
port: this.opts.port,
|
|
36501
|
+
serverVersion: this.opts.version,
|
|
36502
|
+
fetchTimeoutMs: this.opts.fetchTimeoutMs ?? 0,
|
|
36503
|
+
bridgeReviveDelayMs: this.opts.bridgeReviveDelayMs ?? 0,
|
|
36504
|
+
lastSuccessAt: this.lastSuccessAt,
|
|
36505
|
+
lastFailureAt: this.lastFailureAt,
|
|
36506
|
+
lastFailureReason: this.lastFailureReason,
|
|
36507
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
36508
|
+
lastExtensionMessageAt: this.lastExtensionMessageAt
|
|
36509
|
+
};
|
|
36510
|
+
}
|
|
36511
|
+
recordSuccess() {
|
|
36512
|
+
this.lastSuccessAt = Date.now();
|
|
36513
|
+
this.consecutiveFailures = 0;
|
|
36514
|
+
}
|
|
36515
|
+
recordFailure(reason) {
|
|
36516
|
+
this.lastFailureAt = Date.now();
|
|
36517
|
+
this.lastFailureReason = reason;
|
|
36518
|
+
this.consecutiveFailures += 1;
|
|
36519
|
+
}
|
|
36520
|
+
/**
|
|
36521
|
+
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
36522
|
+
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
36523
|
+
* the throwing surface is the convenience methods.
|
|
36524
|
+
*/
|
|
36525
|
+
async _fetchOnceWithTimeout(init) {
|
|
36152
36526
|
const id = this.nextRequestId++;
|
|
36153
36527
|
const inner = { type: "request", id, op: "fetch", init };
|
|
36154
36528
|
const pending = new Promise((resolve) => {
|
|
@@ -36159,7 +36533,61 @@ var FetchproxyServer = class {
|
|
|
36159
36533
|
} else if (this.peerHandle) {
|
|
36160
36534
|
await this.peerHandle.sendInner(inner);
|
|
36161
36535
|
}
|
|
36162
|
-
|
|
36536
|
+
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
36537
|
+
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
36538
|
+
return pending;
|
|
36539
|
+
let timer;
|
|
36540
|
+
const start = Date.now();
|
|
36541
|
+
try {
|
|
36542
|
+
return await Promise.race([
|
|
36543
|
+
pending,
|
|
36544
|
+
new Promise((resolve) => {
|
|
36545
|
+
timer = setTimeout(() => {
|
|
36546
|
+
this.pending.delete(id);
|
|
36547
|
+
const elapsedMs = Date.now() - start;
|
|
36548
|
+
const error51 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
36549
|
+
resolve({
|
|
36550
|
+
ok: false,
|
|
36551
|
+
error: error51,
|
|
36552
|
+
kind: "timeout",
|
|
36553
|
+
retryAttempted: false,
|
|
36554
|
+
elapsedMs
|
|
36555
|
+
});
|
|
36556
|
+
}, timeoutMs);
|
|
36557
|
+
})
|
|
36558
|
+
]);
|
|
36559
|
+
} finally {
|
|
36560
|
+
if (timer)
|
|
36561
|
+
clearTimeout(timer);
|
|
36562
|
+
}
|
|
36563
|
+
}
|
|
36564
|
+
/**
|
|
36565
|
+
* Map an `ok:false` fetch result to its typed throwable. Centralizes
|
|
36566
|
+
* the kind-to-error-class switch so `request()` and (via the same
|
|
36567
|
+
* logic re-implemented inline) `captureRequestHeader()` agree on what
|
|
36568
|
+
* to throw.
|
|
36569
|
+
*/
|
|
36570
|
+
_typedErrorFor(result, url2, op, retryAttempted) {
|
|
36571
|
+
if (result.kind === "timeout") {
|
|
36572
|
+
return new FetchproxyTimeoutError({
|
|
36573
|
+
url: url2,
|
|
36574
|
+
timeoutMs: this.opts.fetchTimeoutMs ?? 0,
|
|
36575
|
+
role: this.role,
|
|
36576
|
+
port: this.opts.port,
|
|
36577
|
+
elapsedMs: result.elapsedMs
|
|
36578
|
+
});
|
|
36579
|
+
}
|
|
36580
|
+
if (result.kind === "content_script_unreachable") {
|
|
36581
|
+
return new FetchproxyBridgeDownError({
|
|
36582
|
+
originalError: result.error,
|
|
36583
|
+
retryAttempted,
|
|
36584
|
+
op,
|
|
36585
|
+
url: url2,
|
|
36586
|
+
role: this.role,
|
|
36587
|
+
port: this.opts.port
|
|
36588
|
+
});
|
|
36589
|
+
}
|
|
36590
|
+
return new FetchproxyProtocolError(result.error);
|
|
36163
36591
|
}
|
|
36164
36592
|
/**
|
|
36165
36593
|
* Convenience wrapper around `fetch()`. Builds the URL from a path
|
|
@@ -36182,8 +36610,18 @@ var FetchproxyServer = class {
|
|
|
36182
36610
|
if (opts.subdomain !== void 0)
|
|
36183
36611
|
assertSubdomainLabel(opts.subdomain);
|
|
36184
36612
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
36185
|
-
const
|
|
36186
|
-
|
|
36613
|
+
const isAbsolute = path.startsWith("http://") || path.startsWith("https://");
|
|
36614
|
+
let host;
|
|
36615
|
+
if (isAbsolute) {
|
|
36616
|
+
try {
|
|
36617
|
+
host = new URL(path).host;
|
|
36618
|
+
} catch {
|
|
36619
|
+
throw new Error(`FetchproxyServer.request: absolute path is not a valid URL: ${JSON.stringify(path)}`);
|
|
36620
|
+
}
|
|
36621
|
+
} else {
|
|
36622
|
+
host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
36623
|
+
}
|
|
36624
|
+
const url2 = isAbsolute ? path : `https://${host}${path}`;
|
|
36187
36625
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
36188
36626
|
const init = {
|
|
36189
36627
|
url: url2,
|
|
@@ -36194,7 +36632,7 @@ var FetchproxyServer = class {
|
|
|
36194
36632
|
};
|
|
36195
36633
|
const result = await this.fetch(init);
|
|
36196
36634
|
if (!result.ok) {
|
|
36197
|
-
throw
|
|
36635
|
+
throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false);
|
|
36198
36636
|
}
|
|
36199
36637
|
const response = {
|
|
36200
36638
|
status: result.status,
|
|
@@ -36288,9 +36726,8 @@ var FetchproxyServer = class {
|
|
|
36288
36726
|
if (!this.opts.capabilities.includes("read_cookies")) {
|
|
36289
36727
|
throw new Error('FetchproxyServer.readCookies(): MCP did not declare "read_cookies" in capabilities \u2014 add it to FetchproxyServerOpts.capabilities to enable this verb');
|
|
36290
36728
|
}
|
|
36291
|
-
|
|
36292
|
-
|
|
36293
|
-
}
|
|
36729
|
+
await this.ensureConnected();
|
|
36730
|
+
this.throwIfPendingPair();
|
|
36294
36731
|
if (opts.subdomain !== void 0)
|
|
36295
36732
|
assertSubdomainLabel(opts.subdomain);
|
|
36296
36733
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
@@ -36347,9 +36784,8 @@ var FetchproxyServer = class {
|
|
|
36347
36784
|
if (!this.opts.capabilities.includes(op)) {
|
|
36348
36785
|
throw new Error(`FetchproxyServer.${op === "read_local_storage" ? "readLocalStorage" : "readSessionStorage"}(): MCP did not declare ${JSON.stringify(op)} in capabilities`);
|
|
36349
36786
|
}
|
|
36350
|
-
|
|
36351
|
-
|
|
36352
|
-
}
|
|
36787
|
+
await this.ensureConnected();
|
|
36788
|
+
this.throwIfPendingPair();
|
|
36353
36789
|
if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
|
|
36354
36790
|
throw new Error(`FetchproxyServer.${op}: opts.keys must be a non-empty array`);
|
|
36355
36791
|
}
|
|
@@ -36404,13 +36840,75 @@ var FetchproxyServer = class {
|
|
|
36404
36840
|
if (!this.opts.capabilities.includes("capture_request_header")) {
|
|
36405
36841
|
throw new Error('FetchproxyServer.captureRequestHeader(): MCP did not declare "capture_request_header" in capabilities');
|
|
36406
36842
|
}
|
|
36407
|
-
|
|
36408
|
-
|
|
36843
|
+
await this.ensureConnected();
|
|
36844
|
+
this.throwIfPendingPair();
|
|
36845
|
+
const decls = this.opts.captureHeaders;
|
|
36846
|
+
let resolved;
|
|
36847
|
+
if (opts?.urlPattern !== void 0 && opts?.headerName !== void 0) {
|
|
36848
|
+
const found = decls.find((d) => d.urlPattern === opts.urlPattern && d.headerName === opts.headerName);
|
|
36849
|
+
if (!found) {
|
|
36850
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: (urlPattern=${JSON.stringify(opts.urlPattern)}, headerName=${JSON.stringify(opts.headerName)}) not declared in captureHeaders`);
|
|
36851
|
+
}
|
|
36852
|
+
resolved = found;
|
|
36853
|
+
} else if (opts?.urlPattern === void 0 && opts?.headerName === void 0) {
|
|
36854
|
+
if (decls.length === 0) {
|
|
36855
|
+
throw new Error("FetchproxyServer.captureRequestHeader: no captureHeaders declared on this server \u2014 declare at least one entry in FetchproxyServerOpts.captureHeaders, or pass {urlPattern, headerName} explicitly");
|
|
36856
|
+
}
|
|
36857
|
+
if (decls.length > 1) {
|
|
36858
|
+
const list = decls.map((d) => `${JSON.stringify(d.urlPattern)}/${JSON.stringify(d.headerName)}`).join(", ");
|
|
36859
|
+
throw new Error(`FetchproxyServer.captureRequestHeader: multiple captureHeaders declared (${decls.length}: ${list}); pass {urlPattern, headerName} to disambiguate`);
|
|
36860
|
+
}
|
|
36861
|
+
resolved = decls[0];
|
|
36862
|
+
} else {
|
|
36863
|
+
throw new Error("FetchproxyServer.captureRequestHeader: pass both urlPattern AND headerName, or neither (which defaults to the single declared entry)");
|
|
36409
36864
|
}
|
|
36410
|
-
const
|
|
36411
|
-
|
|
36412
|
-
|
|
36865
|
+
const callOpts = { ...resolved, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
36866
|
+
try {
|
|
36867
|
+
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
36868
|
+
this.recordSuccess();
|
|
36869
|
+
return result;
|
|
36870
|
+
} catch (err) {
|
|
36871
|
+
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
36872
|
+
if (!swDown) {
|
|
36873
|
+
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
36874
|
+
throw err;
|
|
36875
|
+
}
|
|
36876
|
+
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
36877
|
+
if (reviveMs > 0) {
|
|
36878
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
36879
|
+
try {
|
|
36880
|
+
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
36881
|
+
this.recordSuccess();
|
|
36882
|
+
return result;
|
|
36883
|
+
} catch (retryErr) {
|
|
36884
|
+
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
36885
|
+
if (!stillDown) {
|
|
36886
|
+
this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
|
|
36887
|
+
throw retryErr;
|
|
36888
|
+
}
|
|
36889
|
+
this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
|
|
36890
|
+
throw new FetchproxyBridgeDownError({
|
|
36891
|
+
originalError: retryErr.message,
|
|
36892
|
+
retryAttempted: true,
|
|
36893
|
+
op: "capture_request_header",
|
|
36894
|
+
url: resolved.urlPattern,
|
|
36895
|
+
role: this.role,
|
|
36896
|
+
port: this.opts.port
|
|
36897
|
+
});
|
|
36898
|
+
}
|
|
36899
|
+
}
|
|
36900
|
+
this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
|
|
36901
|
+
throw new FetchproxyBridgeDownError({
|
|
36902
|
+
originalError: err.message,
|
|
36903
|
+
retryAttempted: false,
|
|
36904
|
+
op: "capture_request_header",
|
|
36905
|
+
url: resolved.urlPattern,
|
|
36906
|
+
role: this.role,
|
|
36907
|
+
port: this.opts.port
|
|
36908
|
+
});
|
|
36413
36909
|
}
|
|
36910
|
+
}
|
|
36911
|
+
async _captureRequestHeaderOnce(opts) {
|
|
36414
36912
|
const id = this.nextRequestId++;
|
|
36415
36913
|
const inner = {
|
|
36416
36914
|
type: "request",
|
|
@@ -36447,9 +36945,8 @@ var FetchproxyServer = class {
|
|
|
36447
36945
|
if (!this.opts.capabilities.includes("read_indexed_db")) {
|
|
36448
36946
|
throw new Error('FetchproxyServer.readIndexedDb(): MCP did not declare "read_indexed_db" in capabilities');
|
|
36449
36947
|
}
|
|
36450
|
-
|
|
36451
|
-
|
|
36452
|
-
}
|
|
36948
|
+
await this.ensureConnected();
|
|
36949
|
+
this.throwIfPendingPair();
|
|
36453
36950
|
if (!Array.isArray(opts.keys) || opts.keys.length === 0) {
|
|
36454
36951
|
throw new Error("FetchproxyServer.readIndexedDb: opts.keys must be a non-empty array");
|
|
36455
36952
|
}
|
|
@@ -36520,17 +37017,35 @@ var FetchproxyServer = class {
|
|
|
36520
37017
|
onInner(inner) {
|
|
36521
37018
|
if (inner.type !== "response")
|
|
36522
37019
|
return;
|
|
37020
|
+
this.lastExtensionMessageAt = Date.now();
|
|
36523
37021
|
const fetchCb = this.pending.get(inner.id);
|
|
36524
37022
|
if (fetchCb) {
|
|
36525
37023
|
this.pending.delete(inner.id);
|
|
36526
37024
|
if (inner.ok) {
|
|
36527
37025
|
if (inner.op === void 0 || inner.op === "fetch") {
|
|
36528
|
-
fetchCb({
|
|
37026
|
+
fetchCb({
|
|
37027
|
+
ok: true,
|
|
37028
|
+
status: inner.status,
|
|
37029
|
+
url: inner.url,
|
|
37030
|
+
body: inner.body,
|
|
37031
|
+
retryAttempted: false
|
|
37032
|
+
});
|
|
36529
37033
|
} else {
|
|
36530
|
-
|
|
37034
|
+
const error51 = `unexpected ${inner.op} response on fetch awaiter`;
|
|
37035
|
+
fetchCb({
|
|
37036
|
+
ok: false,
|
|
37037
|
+
error: error51,
|
|
37038
|
+
kind: classifyFetchError(error51),
|
|
37039
|
+
retryAttempted: false
|
|
37040
|
+
});
|
|
36531
37041
|
}
|
|
36532
37042
|
} else {
|
|
36533
|
-
fetchCb({
|
|
37043
|
+
fetchCb({
|
|
37044
|
+
ok: false,
|
|
37045
|
+
error: inner.error,
|
|
37046
|
+
kind: classifyFetchError(inner.error),
|
|
37047
|
+
retryAttempted: false
|
|
37048
|
+
});
|
|
36534
37049
|
}
|
|
36535
37050
|
return;
|
|
36536
37051
|
}
|
|
@@ -36597,6 +37112,57 @@ var FetchproxyServer = class {
|
|
|
36597
37112
|
}
|
|
36598
37113
|
}
|
|
36599
37114
|
}
|
|
37115
|
+
rejectAllPending(reason = "extension disconnected") {
|
|
37116
|
+
const err = new FetchproxyProtocolError(reason);
|
|
37117
|
+
for (const cb of this.pending.values()) {
|
|
37118
|
+
cb({
|
|
37119
|
+
ok: false,
|
|
37120
|
+
error: err.message,
|
|
37121
|
+
kind: classifyFetchError(err.message),
|
|
37122
|
+
retryAttempted: false
|
|
37123
|
+
});
|
|
37124
|
+
}
|
|
37125
|
+
this.pending.clear();
|
|
37126
|
+
for (const cb of this.pendingReadCookies.values()) {
|
|
37127
|
+
cb({ ok: false, error: err.message });
|
|
37128
|
+
}
|
|
37129
|
+
this.pendingReadCookies.clear();
|
|
37130
|
+
for (const { reject } of this.pendingStorage.values())
|
|
37131
|
+
reject(err);
|
|
37132
|
+
this.pendingStorage.clear();
|
|
37133
|
+
for (const { reject } of this.pendingCapture.values())
|
|
37134
|
+
reject(err);
|
|
37135
|
+
this.pendingCapture.clear();
|
|
37136
|
+
for (const { reject } of this.pendingIdb.values())
|
|
37137
|
+
reject(err);
|
|
37138
|
+
this.pendingIdb.clear();
|
|
37139
|
+
}
|
|
37140
|
+
/**
|
|
37141
|
+
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
37142
|
+
* is active, returning null when none is pending. Public verbs call this
|
|
37143
|
+
* at the top so that a tool invoked while the bridge is waiting on user
|
|
37144
|
+
* approval fails fast with the actionable error rather than hanging on a
|
|
37145
|
+
* sealed frame the extension will never process.
|
|
37146
|
+
*/
|
|
37147
|
+
currentPendingPairCode() {
|
|
37148
|
+
if (this.hostHandle)
|
|
37149
|
+
return this.hostHandle.pendingPairCode();
|
|
37150
|
+
if (this.peerHandle)
|
|
37151
|
+
return this.peerHandle.pendingPairCode();
|
|
37152
|
+
return null;
|
|
37153
|
+
}
|
|
37154
|
+
/**
|
|
37155
|
+
* 0.5.2+: throw `FetchproxyProtocolError` with the actionable pair-code
|
|
37156
|
+
* message if the bridge is waiting on user approval. Used by the verb
|
|
37157
|
+
* methods (readCookies, readLocalStorage, etc.) that surface errors via
|
|
37158
|
+
* thrown exceptions rather than `ok:false` discriminated unions.
|
|
37159
|
+
*/
|
|
37160
|
+
throwIfPendingPair() {
|
|
37161
|
+
const code = this.currentPendingPairCode();
|
|
37162
|
+
if (code !== null) {
|
|
37163
|
+
throw new FetchproxyProtocolError(this.pairingErrorMessage(code));
|
|
37164
|
+
}
|
|
37165
|
+
}
|
|
36600
37166
|
/**
|
|
36601
37167
|
* Shut down the bridge. Host: terminates the WebSocket server and any
|
|
36602
37168
|
* still-attached extension/peer clients. Peer: closes the upstream
|
|
@@ -36604,6 +37170,10 @@ var FetchproxyServer = class {
|
|
|
36604
37170
|
* twice in a row.
|
|
36605
37171
|
*/
|
|
36606
37172
|
async close() {
|
|
37173
|
+
this.rejectAllPending();
|
|
37174
|
+
if (this.connectingPromise) {
|
|
37175
|
+
await this.connectingPromise.catch(() => void 0);
|
|
37176
|
+
}
|
|
36607
37177
|
if (this.hostHandle)
|
|
36608
37178
|
await this.hostHandle.close();
|
|
36609
37179
|
if (this.peerHandle)
|
|
@@ -36611,9 +37181,23 @@ var FetchproxyServer = class {
|
|
|
36611
37181
|
this.hostHandle = null;
|
|
36612
37182
|
this.peerHandle = null;
|
|
36613
37183
|
this.role = null;
|
|
37184
|
+
this.connectingPromise = null;
|
|
36614
37185
|
}
|
|
36615
37186
|
};
|
|
36616
37187
|
|
|
37188
|
+
// node_modules/@fetchproxy/server/dist/classify-bridge-error.js
|
|
37189
|
+
function classifyBridgeError(err) {
|
|
37190
|
+
if (err instanceof FetchproxyTimeoutError)
|
|
37191
|
+
return "timeout";
|
|
37192
|
+
if (err instanceof FetchproxyBridgeDownError)
|
|
37193
|
+
return "bridge_down";
|
|
37194
|
+
if (err instanceof FetchproxyHttpError)
|
|
37195
|
+
return "http";
|
|
37196
|
+
if (err instanceof FetchproxyProtocolError)
|
|
37197
|
+
return "protocol";
|
|
37198
|
+
return "other";
|
|
37199
|
+
}
|
|
37200
|
+
|
|
36617
37201
|
// node_modules/@fetchproxy/bootstrap/dist/index.js
|
|
36618
37202
|
var defaultFactory = (opts) => new FetchproxyServer(opts);
|
|
36619
37203
|
async function bootstrap(opts) {
|
|
@@ -36668,7 +37252,11 @@ async function bootstrap(opts) {
|
|
|
36668
37252
|
key: p.storageKey,
|
|
36669
37253
|
jsonPointer: p.jsonPointer
|
|
36670
37254
|
})),
|
|
36671
|
-
onPairCode: opts.onPairCode
|
|
37255
|
+
onPairCode: opts.onPairCode,
|
|
37256
|
+
// 0.8.0+ pass-through. Only forwarded when the caller set them;
|
|
37257
|
+
// unset → server defaults apply (30000 / 2000 in 0.8.0).
|
|
37258
|
+
...opts.fetchTimeoutMs !== void 0 ? { fetchTimeoutMs: opts.fetchTimeoutMs } : {},
|
|
37259
|
+
...opts.bridgeReviveDelayMs !== void 0 ? { bridgeReviveDelayMs: opts.bridgeReviveDelayMs } : {}
|
|
36672
37260
|
});
|
|
36673
37261
|
const storageDomainOpts = {};
|
|
36674
37262
|
if (opts.storageDomain !== void 0)
|
|
@@ -36699,8 +37287,7 @@ async function bootstrap(opts) {
|
|
|
36699
37287
|
for (const p of localStoragePointers) {
|
|
36700
37288
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
36701
37289
|
}
|
|
36702
|
-
|
|
36703
|
-
localStorage = await stub.readLocalStorage({
|
|
37290
|
+
localStorage = await server2.readLocalStorage({
|
|
36704
37291
|
keys: allKeys,
|
|
36705
37292
|
...storageDomainOpts,
|
|
36706
37293
|
...localStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -36713,8 +37300,7 @@ async function bootstrap(opts) {
|
|
|
36713
37300
|
for (const p of sessionStoragePointers) {
|
|
36714
37301
|
pointers[p.outputKey] = { storageKey: p.storageKey, jsonPointer: p.jsonPointer };
|
|
36715
37302
|
}
|
|
36716
|
-
|
|
36717
|
-
sessionStorage = await stub.readSessionStorage({
|
|
37303
|
+
sessionStorage = await server2.readSessionStorage({
|
|
36718
37304
|
keys: allKeys,
|
|
36719
37305
|
...storageDomainOpts,
|
|
36720
37306
|
...sessionStoragePointers.length > 0 ? { pointers } : {}
|
|
@@ -36737,9 +37323,6 @@ async function bootstrap(opts) {
|
|
|
36737
37323
|
}
|
|
36738
37324
|
const indexedDbBucket = {};
|
|
36739
37325
|
for (const d of indexedDb) {
|
|
36740
|
-
if (!server2.readIndexedDb) {
|
|
36741
|
-
throw new Error("bootstrap: server factory does not implement readIndexedDb (declared indexedDb but server stub omits it)");
|
|
36742
|
-
}
|
|
36743
37326
|
const values = await server2.readIndexedDb({
|
|
36744
37327
|
database: d.database,
|
|
36745
37328
|
store: d.store,
|
|
@@ -36776,7 +37359,7 @@ var BootstrapDisabledError = class extends Error {
|
|
|
36776
37359
|
// package.json
|
|
36777
37360
|
var package_default = {
|
|
36778
37361
|
name: "honeybook-mcp",
|
|
36779
|
-
version: "0.
|
|
37362
|
+
version: "0.3.0",
|
|
36780
37363
|
mcpName: "io.github.chrischall/honeybook-mcp",
|
|
36781
37364
|
description: "HoneyBook client-portal MCP server for Claude",
|
|
36782
37365
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -36816,17 +37399,18 @@ var package_default = {
|
|
|
36816
37399
|
"test:watch": "vitest"
|
|
36817
37400
|
},
|
|
36818
37401
|
dependencies: {
|
|
36819
|
-
"@fetchproxy/bootstrap": "^0.
|
|
37402
|
+
"@fetchproxy/bootstrap": "^0.8.0",
|
|
37403
|
+
"@fetchproxy/server": "^0.8.0",
|
|
36820
37404
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36821
|
-
dotenv: "^17.4.
|
|
36822
|
-
zod: "^4.4.
|
|
37405
|
+
dotenv: "^17.4.2",
|
|
37406
|
+
zod: "^4.4.3"
|
|
36823
37407
|
},
|
|
36824
37408
|
devDependencies: {
|
|
36825
|
-
"@types/node": "^25.
|
|
36826
|
-
"@vitest/coverage-v8": "^4.1.
|
|
37409
|
+
"@types/node": "^25.9.1",
|
|
37410
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
36827
37411
|
esbuild: "^0.28.0",
|
|
36828
|
-
typescript: "^6.0.
|
|
36829
|
-
vitest: "^4.1.
|
|
37412
|
+
typescript: "^6.0.3",
|
|
37413
|
+
vitest: "^4.1.7"
|
|
36830
37414
|
}
|
|
36831
37415
|
};
|
|
36832
37416
|
|
|
@@ -36917,6 +37501,12 @@ async function captureSessionViaFetchproxy(opts) {
|
|
|
36917
37501
|
}
|
|
36918
37502
|
});
|
|
36919
37503
|
} catch (e) {
|
|
37504
|
+
if (classifyBridgeError(e) === "bridge_down") {
|
|
37505
|
+
const downErr = e;
|
|
37506
|
+
throw new Error(
|
|
37507
|
+
`HoneyBook auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
|
|
37508
|
+
);
|
|
37509
|
+
}
|
|
36920
37510
|
const msg = e instanceof Error ? e.message : String(e);
|
|
36921
37511
|
throw new Error(
|
|
36922
37512
|
`HoneyBook auth: fetchproxy capture failed: ${msg} \u2014 open the vendor magic-link URL in Chrome (with the fetchproxy 0.3.0 extension installed), then retry.`
|
|
@@ -37442,7 +38032,8 @@ try {
|
|
|
37442
38032
|
}
|
|
37443
38033
|
var server = new McpServer({
|
|
37444
38034
|
name: "honeybook-mcp",
|
|
37445
|
-
version: "0.
|
|
38035
|
+
version: "0.3.0"
|
|
38036
|
+
// x-release-please-version
|
|
37446
38037
|
});
|
|
37447
38038
|
registerSessionTools(server);
|
|
37448
38039
|
registerWorkspaceFileTools(server);
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { registerContractTools } from './tools/contracts.js';
|
|
|
18
18
|
import { registerInvoiceTools } from './tools/invoices.js';
|
|
19
19
|
const server = new McpServer({
|
|
20
20
|
name: 'honeybook-mcp',
|
|
21
|
-
version: '0.
|
|
21
|
+
version: '0.3.0', // x-release-please-version
|
|
22
22
|
});
|
|
23
23
|
registerSessionTools(server);
|
|
24
24
|
registerWorkspaceFileTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "honeybook-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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>",
|
|
@@ -40,16 +40,17 @@
|
|
|
40
40
|
"test:watch": "vitest"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@fetchproxy/bootstrap": "^0.
|
|
43
|
+
"@fetchproxy/bootstrap": "^0.8.0",
|
|
44
|
+
"@fetchproxy/server": "^0.8.0",
|
|
44
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
|
-
"dotenv": "^17.4.
|
|
46
|
-
"zod": "^4.4.
|
|
46
|
+
"dotenv": "^17.4.2",
|
|
47
|
+
"zod": "^4.4.3"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@types/node": "^25.
|
|
50
|
-
"@vitest/coverage-v8": "^4.1.
|
|
50
|
+
"@types/node": "^25.9.1",
|
|
51
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
51
52
|
"esbuild": "^0.28.0",
|
|
52
|
-
"typescript": "^6.0.
|
|
53
|
-
"vitest": "^4.1.
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.1.7"
|
|
54
55
|
}
|
|
55
56
|
}
|
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.
|
|
9
|
+
"version": "0.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "honeybook-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.3.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|