honeybook-mcp 0.3.3 → 0.3.5
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/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/auth.js +18 -0
- package/dist/bundle.js +200 -50
- package/dist/index.js +1 -1
- package/dist/tools/sessions.js +1 -1
- package/package.json +2 -2
- 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.3.
|
|
10
|
+
"version": "0.3.5"
|
|
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.3.
|
|
18
|
+
"version": "0.3.5",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Chall"
|
|
21
21
|
},
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chris Hall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# HoneyBook MCP
|
|
2
2
|
|
|
3
|
+
[](https://github.com/chrischall/honeybook-mcp/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/honeybook-mcp)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
A [Model Context Protocol](https://modelcontextprotocol.io) server that connects Claude to the [HoneyBook](https://www.honeybook.com) client portal, giving you natural-language access to contracts and invoices sent by your wedding vendors.
|
|
4
8
|
|
|
5
9
|
> [!WARNING]
|
package/dist/auth.js
CHANGED
|
@@ -154,6 +154,24 @@ export async function captureSessionViaFetchproxy(opts) {
|
|
|
154
154
|
throw new Error(`HoneyBook auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${bridgeError.hint ?? ''}`.trimEnd());
|
|
155
155
|
}
|
|
156
156
|
const msg = e instanceof Error ? e.message : String(e);
|
|
157
|
+
// The hb-api-fingerprint capture rejects with a bare `timeout` when no
|
|
158
|
+
// api.honeybook.com/api/v2/* request fires before its one-shot listener
|
|
159
|
+
// window elapses. This is the common "tab already loaded and idle" case:
|
|
160
|
+
// a HoneyBook portal fires its API calls on load, then goes quiet, so by
|
|
161
|
+
// the time bootstrap has paired, read jStorage, and armed the capture
|
|
162
|
+
// listener there's no fresh request left to sniff the header off. The fix
|
|
163
|
+
// is to make the portal issue a *new* request (refresh the tab, open a
|
|
164
|
+
// workspace) WHILE the capture is waiting — NOT to (re)open the magic
|
|
165
|
+
// link, which is already open. classifyBridgeError keys off the error
|
|
166
|
+
// class, so the capture timeout surfaces as a FetchproxyProtocolError
|
|
167
|
+
// ('protocol'), not 'timeout' — match on the message too.
|
|
168
|
+
if (bridgeError.type === 'timeout' || /timeout/i.test(msg)) {
|
|
169
|
+
throw new Error('HoneyBook auth: fetchproxy capture timed out waiting for the hb-api-fingerprint header. ' +
|
|
170
|
+
'That header can only be read off a live api.honeybook.com request, and a portal tab that has ' +
|
|
171
|
+
'already finished loading sits idle. Keep the vendor magic-link URL open in Chrome (with the ' +
|
|
172
|
+
'fetchproxy extension installed), then re-run use_magic_link and — within ~30s, while it waits — ' +
|
|
173
|
+
'refresh the portal tab (or open a workspace/file) so the page issues a fresh API request.');
|
|
174
|
+
}
|
|
157
175
|
throw new Error(`HoneyBook auth: fetchproxy capture failed: ${msg} — ` +
|
|
158
176
|
'open the vendor magic-link URL in Chrome (with the fetchproxy 0.3.0 extension installed), ' +
|
|
159
177
|
'then retry.');
|
package/dist/bundle.js
CHANGED
|
@@ -12,7 +12,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
14
|
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
throw mod = 0, e;
|
|
19
|
+
}
|
|
16
20
|
};
|
|
17
21
|
var __export = (target, all) => {
|
|
18
22
|
for (var name in all)
|
|
@@ -34790,6 +34794,8 @@ var NonNegInt = external_exports.number().int().nonnegative();
|
|
|
34790
34794
|
var NonEmptyString = external_exports.string().min(1);
|
|
34791
34795
|
var IsoDate = external_exports.iso.date();
|
|
34792
34796
|
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
34797
|
+
var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
34798
|
+
var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
34793
34799
|
var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
34794
34800
|
var schemaConfirm = external_exports.boolean().optional().describe("Must be true to proceed. Without this, the tool returns a preview.");
|
|
34795
34801
|
var paginationSchema = {
|
|
@@ -36006,10 +36012,20 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
36006
36012
|
|
|
36007
36013
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
36008
36014
|
import { createServer, Server as HttpServer } from "node:http";
|
|
36015
|
+
var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
36009
36016
|
async function electRole(opts) {
|
|
36010
36017
|
const server = createServer();
|
|
36018
|
+
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
36011
36019
|
return new Promise((resolve, reject) => {
|
|
36020
|
+
let timer;
|
|
36021
|
+
const clearBindTimer = () => {
|
|
36022
|
+
if (timer) {
|
|
36023
|
+
clearTimeout(timer);
|
|
36024
|
+
timer = void 0;
|
|
36025
|
+
}
|
|
36026
|
+
};
|
|
36012
36027
|
const onError = (e) => {
|
|
36028
|
+
clearBindTimer();
|
|
36013
36029
|
server.removeListener("listening", onListening);
|
|
36014
36030
|
if (e.code === "EADDRINUSE") {
|
|
36015
36031
|
try {
|
|
@@ -36022,11 +36038,25 @@ async function electRole(opts) {
|
|
|
36022
36038
|
}
|
|
36023
36039
|
};
|
|
36024
36040
|
const onListening = () => {
|
|
36041
|
+
clearBindTimer();
|
|
36025
36042
|
server.removeListener("error", onError);
|
|
36026
36043
|
resolve({ role: "host", server });
|
|
36027
36044
|
};
|
|
36028
36045
|
server.once("error", onError);
|
|
36029
36046
|
server.once("listening", onListening);
|
|
36047
|
+
if (bindTimeoutMs > 0) {
|
|
36048
|
+
timer = setTimeout(() => {
|
|
36049
|
+
server.removeListener("error", onError);
|
|
36050
|
+
server.removeListener("listening", onListening);
|
|
36051
|
+
try {
|
|
36052
|
+
server.close();
|
|
36053
|
+
} catch {
|
|
36054
|
+
}
|
|
36055
|
+
reject(new Error(`fetchproxy: bind to ${opts.host}:${opts.port} timed out after ${bindTimeoutMs}ms (port may be in a bad state)`));
|
|
36056
|
+
}, bindTimeoutMs);
|
|
36057
|
+
if (typeof timer.unref === "function")
|
|
36058
|
+
timer.unref();
|
|
36059
|
+
}
|
|
36030
36060
|
server.listen(opts.port, opts.host);
|
|
36031
36061
|
});
|
|
36032
36062
|
}
|
|
@@ -36119,6 +36149,44 @@ var SessionState = class {
|
|
|
36119
36149
|
}
|
|
36120
36150
|
};
|
|
36121
36151
|
|
|
36152
|
+
// node_modules/@fetchproxy/server/dist/session-ready.js
|
|
36153
|
+
var SESSION_READY_TIMEOUT_MS = 3e4;
|
|
36154
|
+
var FetchproxySessionNotReadyError = class extends Error {
|
|
36155
|
+
reason;
|
|
36156
|
+
pairCode;
|
|
36157
|
+
mcpId;
|
|
36158
|
+
hint;
|
|
36159
|
+
constructor(info) {
|
|
36160
|
+
const pairing = info.pairCode !== null && info.pairCode !== "";
|
|
36161
|
+
const hint = pairing ? `Open the Transporter extension popup and approve pair code ${info.pairCode} for "${info.mcpId}", then retry.` : `The extension is connected but hasn't confirmed a session for "${info.mcpId}" \u2014 sign in to the target site in that browser (and approve the requested scope if it changed), then retry.`;
|
|
36162
|
+
super(`fetchproxy: ${pairing ? "pairing not yet approved" : "no confirmed browser session"} for "${info.mcpId}". ${hint}`);
|
|
36163
|
+
this.name = "FetchproxySessionNotReadyError";
|
|
36164
|
+
this.reason = pairing ? "pair-required" : "not-ready";
|
|
36165
|
+
this.pairCode = pairing ? info.pairCode : null;
|
|
36166
|
+
this.mcpId = info.mcpId;
|
|
36167
|
+
this.hint = hint;
|
|
36168
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
36169
|
+
}
|
|
36170
|
+
};
|
|
36171
|
+
async function awaitSessionReady(ready, opts) {
|
|
36172
|
+
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
36173
|
+
if (ms <= 0)
|
|
36174
|
+
return ready;
|
|
36175
|
+
let timer;
|
|
36176
|
+
const timeout = new Promise((_, reject) => {
|
|
36177
|
+
timer = setTimeout(() => {
|
|
36178
|
+
reject(new FetchproxySessionNotReadyError({ mcpId: opts.mcpId, pairCode: opts.pendingPairCode() }));
|
|
36179
|
+
}, ms);
|
|
36180
|
+
timer.unref?.();
|
|
36181
|
+
});
|
|
36182
|
+
try {
|
|
36183
|
+
return await Promise.race([ready, timeout]);
|
|
36184
|
+
} finally {
|
|
36185
|
+
if (timer)
|
|
36186
|
+
clearTimeout(timer);
|
|
36187
|
+
}
|
|
36188
|
+
}
|
|
36189
|
+
|
|
36122
36190
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
36123
36191
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
36124
36192
|
var enc2 = new TextEncoder();
|
|
@@ -36206,6 +36274,29 @@ async function startHost(opts) {
|
|
|
36206
36274
|
return;
|
|
36207
36275
|
}
|
|
36208
36276
|
if (frame.type === "hello" && frame.role === "server") {
|
|
36277
|
+
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
36278
|
+
const peerSigMsg = concatBytes(enc2.encode(frame.mcpId), fromB64(frame.sessionNonce));
|
|
36279
|
+
const peerSig = fromB64(frame.sessionSig);
|
|
36280
|
+
let peerSigOk = false;
|
|
36281
|
+
try {
|
|
36282
|
+
peerSigOk = await ed25519Verify(peerEdPub, peerSigMsg, peerSig);
|
|
36283
|
+
} catch {
|
|
36284
|
+
peerSigOk = false;
|
|
36285
|
+
}
|
|
36286
|
+
if (!peerSigOk) {
|
|
36287
|
+
console.warn("[fetchproxy] peer hello signature invalid \u2014 refusing registration (possible squatter)");
|
|
36288
|
+
ws.close(1008, "peer hello signature invalid");
|
|
36289
|
+
return;
|
|
36290
|
+
}
|
|
36291
|
+
const existing = peers.get(frame.mcpId);
|
|
36292
|
+
if (existing && existing.ws !== ws) {
|
|
36293
|
+
const existingEdPub = existing.helloFrame.identityEd25519Pub;
|
|
36294
|
+
if (existingEdPub !== frame.identityEd25519Pub) {
|
|
36295
|
+
console.warn("[fetchproxy] peer mcpId already mapped to a different identity \u2014 refusing (mcpId squatting)");
|
|
36296
|
+
ws.close(1008, "mcpId already registered to another identity");
|
|
36297
|
+
return;
|
|
36298
|
+
}
|
|
36299
|
+
}
|
|
36209
36300
|
identified = "peer";
|
|
36210
36301
|
peerMcpId = frame.mcpId;
|
|
36211
36302
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
@@ -36298,8 +36389,10 @@ async function startHost(opts) {
|
|
|
36298
36389
|
resetSessionPromise();
|
|
36299
36390
|
disconnectListeners.forEach((cb) => cb());
|
|
36300
36391
|
}
|
|
36301
|
-
if (identified === "peer" && peerMcpId)
|
|
36302
|
-
peers.
|
|
36392
|
+
if (identified === "peer" && peerMcpId) {
|
|
36393
|
+
if (peers.get(peerMcpId)?.ws === ws)
|
|
36394
|
+
peers.delete(peerMcpId);
|
|
36395
|
+
}
|
|
36303
36396
|
});
|
|
36304
36397
|
});
|
|
36305
36398
|
return {
|
|
@@ -36315,7 +36408,10 @@ async function startHost(opts) {
|
|
|
36315
36408
|
});
|
|
36316
36409
|
}),
|
|
36317
36410
|
sendOwnInner: async (inner) => {
|
|
36318
|
-
const session = await ownSessionReady
|
|
36411
|
+
const session = await awaitSessionReady(ownSessionReady, {
|
|
36412
|
+
mcpId: opts.ownMcpId,
|
|
36413
|
+
pendingPairCode: () => ownPendingPairCode
|
|
36414
|
+
});
|
|
36319
36415
|
if (!extensionWs)
|
|
36320
36416
|
throw new Error("host: no extension connected");
|
|
36321
36417
|
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), inner);
|
|
@@ -36420,7 +36516,10 @@ async function startPeer(opts) {
|
|
|
36420
36516
|
ws,
|
|
36421
36517
|
session: sessionPromise,
|
|
36422
36518
|
sendInner: async (inner) => {
|
|
36423
|
-
await sessionPromise
|
|
36519
|
+
await awaitSessionReady(sessionPromise, {
|
|
36520
|
+
mcpId: opts.mcpId,
|
|
36521
|
+
pendingPairCode: () => pendingPairCode
|
|
36522
|
+
});
|
|
36424
36523
|
const s = session;
|
|
36425
36524
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
36426
36525
|
ws.send(JSON.stringify(sealed));
|
|
@@ -37086,6 +37185,35 @@ var FetchproxyServer = class {
|
|
|
37086
37185
|
this.keepAliveTimer = null;
|
|
37087
37186
|
}
|
|
37088
37187
|
}
|
|
37188
|
+
/**
|
|
37189
|
+
* Send an inner request frame via whichever bridge handle is active. If the
|
|
37190
|
+
* send throws (e.g. `FetchproxySessionNotReadyError` — the session never
|
|
37191
|
+
* confirmed), the frame never reached the bridge, so no reply will arrive:
|
|
37192
|
+
* drop the just-registered pending resolver for this id (it lives in exactly
|
|
37193
|
+
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
37194
|
+
* server closes, then rethrow.
|
|
37195
|
+
*/
|
|
37196
|
+
async sendInnerFrame(inner) {
|
|
37197
|
+
try {
|
|
37198
|
+
if (this.hostHandle) {
|
|
37199
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
37200
|
+
} else if (this.peerHandle) {
|
|
37201
|
+
await this.peerHandle.sendInner(inner);
|
|
37202
|
+
}
|
|
37203
|
+
} catch (err) {
|
|
37204
|
+
if ("id" in inner && typeof inner.id === "number") {
|
|
37205
|
+
const { id } = inner;
|
|
37206
|
+
this.pending.delete(id);
|
|
37207
|
+
this.pendingReadCookies.delete(id);
|
|
37208
|
+
this.pendingStorage.delete(id);
|
|
37209
|
+
this.pendingCapture.delete(id);
|
|
37210
|
+
this.pendingRedirect.delete(id);
|
|
37211
|
+
this.pendingDownload.delete(id);
|
|
37212
|
+
this.pendingIdb.delete(id);
|
|
37213
|
+
}
|
|
37214
|
+
throw err;
|
|
37215
|
+
}
|
|
37216
|
+
}
|
|
37089
37217
|
/**
|
|
37090
37218
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
37091
37219
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
@@ -37097,11 +37225,7 @@ var FetchproxyServer = class {
|
|
|
37097
37225
|
const pending = new Promise((resolve) => {
|
|
37098
37226
|
this.pending.set(id, resolve);
|
|
37099
37227
|
});
|
|
37100
|
-
|
|
37101
|
-
await this.hostHandle.sendOwnInner(inner);
|
|
37102
|
-
} else if (this.peerHandle) {
|
|
37103
|
-
await this.peerHandle.sendInner(inner);
|
|
37104
|
-
}
|
|
37228
|
+
await this.sendInnerFrame(inner);
|
|
37105
37229
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37106
37230
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37107
37231
|
return pending;
|
|
@@ -37130,6 +37254,51 @@ var FetchproxyServer = class {
|
|
|
37130
37254
|
clearTimeout(timer);
|
|
37131
37255
|
}
|
|
37132
37256
|
}
|
|
37257
|
+
/**
|
|
37258
|
+
* FP-B2: bound a non-`fetch` verb's reply wait by `fetchTimeoutMs`.
|
|
37259
|
+
*
|
|
37260
|
+
* Before this, only `fetch()` raced its `pending` reply against a timer
|
|
37261
|
+
* (`_fetchOnceWithTimeout`); `readCookies`, the storage reads,
|
|
37262
|
+
* `capture_request_header`, `capture_redirect`, `download`, and
|
|
37263
|
+
* `read_indexed_db` awaited their `pending` promise with no race, so a
|
|
37264
|
+
* wedged extension hung the tool call indefinitely.
|
|
37265
|
+
*
|
|
37266
|
+
* `pending` is the already-registered reply promise. `pendingMap`/`id`
|
|
37267
|
+
* point at the op-specific map entry so we can drop it on expiry exactly
|
|
37268
|
+
* as `_fetchOnceWithTimeout` does — otherwise a late bridge reply would
|
|
37269
|
+
* resolve into a stale resolver / leak. On timeout we reject with a
|
|
37270
|
+
* `FetchproxyTimeoutError` (the same throwable the convenience methods
|
|
37271
|
+
* already surface for fetch timeouts). `0`/unset opts out (unbounded),
|
|
37272
|
+
* matching the fetch path.
|
|
37273
|
+
*/
|
|
37274
|
+
async _withVerbTimeout(pending, pendingMap, id, url2) {
|
|
37275
|
+
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37276
|
+
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37277
|
+
return pending;
|
|
37278
|
+
let timer;
|
|
37279
|
+
const start = Date.now();
|
|
37280
|
+
try {
|
|
37281
|
+
return await Promise.race([
|
|
37282
|
+
pending,
|
|
37283
|
+
new Promise((_resolve, reject) => {
|
|
37284
|
+
timer = setTimeout(() => {
|
|
37285
|
+
pendingMap.delete(id);
|
|
37286
|
+
reject(new FetchproxyTimeoutError({
|
|
37287
|
+
url: url2,
|
|
37288
|
+
timeoutMs,
|
|
37289
|
+
role: this.role,
|
|
37290
|
+
port: this.opts.port,
|
|
37291
|
+
elapsedMs: Date.now() - start,
|
|
37292
|
+
retryAttempted: false
|
|
37293
|
+
}));
|
|
37294
|
+
}, timeoutMs);
|
|
37295
|
+
})
|
|
37296
|
+
]);
|
|
37297
|
+
} finally {
|
|
37298
|
+
if (timer)
|
|
37299
|
+
clearTimeout(timer);
|
|
37300
|
+
}
|
|
37301
|
+
}
|
|
37133
37302
|
/**
|
|
37134
37303
|
* Map an `ok:false` fetch result to its typed throwable. Centralizes
|
|
37135
37304
|
* the kind-to-error-class switch so `request()` and (via the same
|
|
@@ -37420,12 +37589,8 @@ var FetchproxyServer = class {
|
|
|
37420
37589
|
const pending = new Promise((resolve) => {
|
|
37421
37590
|
this.pendingReadCookies.set(id, resolve);
|
|
37422
37591
|
});
|
|
37423
|
-
|
|
37424
|
-
|
|
37425
|
-
} else if (this.peerHandle) {
|
|
37426
|
-
await this.peerHandle.sendInner(inner);
|
|
37427
|
-
}
|
|
37428
|
-
const result = await pending;
|
|
37592
|
+
await this.sendInnerFrame(inner);
|
|
37593
|
+
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37429
37594
|
if (!result.ok) {
|
|
37430
37595
|
throw new FetchproxyProtocolError(result.error);
|
|
37431
37596
|
}
|
|
@@ -37491,12 +37656,8 @@ var FetchproxyServer = class {
|
|
|
37491
37656
|
const pending = new Promise((resolve, reject) => {
|
|
37492
37657
|
this.pendingStorage.set(id, { resolve, reject });
|
|
37493
37658
|
});
|
|
37494
|
-
|
|
37495
|
-
|
|
37496
|
-
} else if (this.peerHandle) {
|
|
37497
|
-
await this.peerHandle.sendInner(inner);
|
|
37498
|
-
}
|
|
37499
|
-
return pending;
|
|
37659
|
+
await this.sendInnerFrame(inner);
|
|
37660
|
+
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
37500
37661
|
}
|
|
37501
37662
|
/**
|
|
37502
37663
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
@@ -37600,12 +37761,8 @@ var FetchproxyServer = class {
|
|
|
37600
37761
|
const pending = new Promise((resolve, reject) => {
|
|
37601
37762
|
this.pendingCapture.set(id, { resolve, reject });
|
|
37602
37763
|
});
|
|
37603
|
-
|
|
37604
|
-
|
|
37605
|
-
} else if (this.peerHandle) {
|
|
37606
|
-
await this.peerHandle.sendInner(inner);
|
|
37607
|
-
}
|
|
37608
|
-
return pending;
|
|
37764
|
+
await this.sendInnerFrame(inner);
|
|
37765
|
+
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37609
37766
|
}
|
|
37610
37767
|
/**
|
|
37611
37768
|
* Snapshot the redirect target URL of the next request the browser
|
|
@@ -37689,12 +37846,8 @@ var FetchproxyServer = class {
|
|
|
37689
37846
|
const pending = new Promise((resolve, reject) => {
|
|
37690
37847
|
this.pendingRedirect.set(id, { resolve, reject });
|
|
37691
37848
|
});
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
} else if (this.peerHandle) {
|
|
37695
|
-
await this.peerHandle.sendInner(inner);
|
|
37696
|
-
}
|
|
37697
|
-
return pending;
|
|
37849
|
+
await this.sendInnerFrame(inner);
|
|
37850
|
+
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37698
37851
|
}
|
|
37699
37852
|
/**
|
|
37700
37853
|
* Download `url` through the BROWSER's own network stack via
|
|
@@ -37775,12 +37928,8 @@ var FetchproxyServer = class {
|
|
|
37775
37928
|
const pending = new Promise((resolve, reject) => {
|
|
37776
37929
|
this.pendingDownload.set(id, { resolve, reject });
|
|
37777
37930
|
});
|
|
37778
|
-
|
|
37779
|
-
|
|
37780
|
-
} else if (this.peerHandle) {
|
|
37781
|
-
await this.peerHandle.sendInner(inner);
|
|
37782
|
-
}
|
|
37783
|
-
return pending;
|
|
37931
|
+
await this.sendInnerFrame(inner);
|
|
37932
|
+
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
37784
37933
|
}
|
|
37785
37934
|
/**
|
|
37786
37935
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
@@ -37827,12 +37976,8 @@ var FetchproxyServer = class {
|
|
|
37827
37976
|
const pending = new Promise((resolve, reject) => {
|
|
37828
37977
|
this.pendingIdb.set(id, { resolve, reject });
|
|
37829
37978
|
});
|
|
37830
|
-
|
|
37831
|
-
|
|
37832
|
-
} else if (this.peerHandle) {
|
|
37833
|
-
await this.peerHandle.sendInner(inner);
|
|
37834
|
-
}
|
|
37835
|
-
return pending;
|
|
37979
|
+
await this.sendInnerFrame(inner);
|
|
37980
|
+
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
37836
37981
|
}
|
|
37837
37982
|
assertScopeSubset(requested, declared, label) {
|
|
37838
37983
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -38263,7 +38408,7 @@ function bridgeErrorInfo(err) {
|
|
|
38263
38408
|
// package.json
|
|
38264
38409
|
var package_default = {
|
|
38265
38410
|
name: "honeybook-mcp",
|
|
38266
|
-
version: "0.3.
|
|
38411
|
+
version: "0.3.5",
|
|
38267
38412
|
mcpName: "io.github.chrischall/honeybook-mcp",
|
|
38268
38413
|
description: "HoneyBook client-portal MCP server for Claude",
|
|
38269
38414
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38303,7 +38448,7 @@ var package_default = {
|
|
|
38303
38448
|
"test:watch": "vitest"
|
|
38304
38449
|
},
|
|
38305
38450
|
dependencies: {
|
|
38306
|
-
"@chrischall/mcp-utils": "^0.
|
|
38451
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
38307
38452
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
38308
38453
|
"@fetchproxy/server": "^1.0.0",
|
|
38309
38454
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38402,6 +38547,11 @@ async function captureSessionViaFetchproxy(opts) {
|
|
|
38402
38547
|
);
|
|
38403
38548
|
}
|
|
38404
38549
|
const msg = e instanceof Error ? e.message : String(e);
|
|
38550
|
+
if (bridgeError.type === "timeout" || /timeout/i.test(msg)) {
|
|
38551
|
+
throw new Error(
|
|
38552
|
+
"HoneyBook auth: fetchproxy capture timed out waiting for the hb-api-fingerprint header. That header can only be read off a live api.honeybook.com request, and a portal tab that has already finished loading sits idle. Keep the vendor magic-link URL open in Chrome (with the fetchproxy extension installed), then re-run use_magic_link and \u2014 within ~30s, while it waits \u2014 refresh the portal tab (or open a workspace/file) so the page issues a fresh API request."
|
|
38553
|
+
);
|
|
38554
|
+
}
|
|
38405
38555
|
throw new Error(
|
|
38406
38556
|
`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.`
|
|
38407
38557
|
);
|
|
@@ -38472,7 +38622,7 @@ function registerSessionTools(server) {
|
|
|
38472
38622
|
server.registerTool(
|
|
38473
38623
|
"use_magic_link",
|
|
38474
38624
|
{
|
|
38475
|
-
description: `Capture a HoneyBook client-portal session via the fetchproxy 0.3.0 browser extension. Prerequisites: install the fetchproxy extension in Chrome/Safari, then open the vendor's magic-link URL in that browser so you're signed into their portal. This tool then snapshots the page's localStorage["jStorage"] and the hb-api-fingerprint header into ~/.honeybook-mcp/sessions.json. All other tools use the most-recently-activated session by default. The magic_link_url arg is used only to derive the portalOrigin (cache key) \u2014 the tool does NOT open or navigate to it.`,
|
|
38625
|
+
description: `Capture a HoneyBook client-portal session via the fetchproxy 0.3.0 browser extension. Prerequisites: install the fetchproxy extension in Chrome/Safari, then open the vendor's magic-link URL in that browser so you're signed into their portal. This tool then snapshots the page's localStorage["jStorage"] and the hb-api-fingerprint header into ~/.honeybook-mcp/sessions.json. IMPORTANT: the hb-api-fingerprint header is sniffed off the next live api.honeybook.com request, so a portal tab that has already finished loading (and gone idle) yields nothing and the capture times out. While this tool is running, refresh the portal tab (or open a workspace/file) so the page issues a fresh API request. All other tools use the most-recently-activated session by default. The magic_link_url arg is used only to derive the portalOrigin (cache key) \u2014 the tool does NOT open or navigate to it.`,
|
|
38476
38626
|
inputSchema: {
|
|
38477
38627
|
magic_link_url: external_exports.string().url().describe(
|
|
38478
38628
|
"Full magic-link URL from the vendor's HoneyBook email, e.g. https://<vendor>.hbportal.co/app/workspace_file/<id>/... Used only to derive the portal origin; you must already have this URL open in a Chrome tab with the fetchproxy extension installed."
|
|
@@ -38895,7 +39045,7 @@ var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
|
38895
39045
|
await loadDotenvSafely({ path: join3(__dirname, "..", ".env"), override: false });
|
|
38896
39046
|
await runMcp({
|
|
38897
39047
|
name: "honeybook-mcp",
|
|
38898
|
-
version: "0.3.
|
|
39048
|
+
version: "0.3.5",
|
|
38899
39049
|
// x-release-please-version
|
|
38900
39050
|
tools: [
|
|
38901
39051
|
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.3.
|
|
14
|
+
version: '0.3.5', // x-release-please-version
|
|
15
15
|
tools: [
|
|
16
16
|
registerSessionTools,
|
|
17
17
|
registerWorkspaceFileTools,
|
package/dist/tools/sessions.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function listActiveSessions() {
|
|
|
42
42
|
}
|
|
43
43
|
export function registerSessionTools(server) {
|
|
44
44
|
server.registerTool('use_magic_link', {
|
|
45
|
-
description: "Capture a HoneyBook client-portal session via the fetchproxy 0.3.0 browser extension. Prerequisites: install the fetchproxy extension in Chrome/Safari, then open the vendor's magic-link URL in that browser so you're signed into their portal. This tool then snapshots the page's localStorage[\"jStorage\"] and the hb-api-fingerprint header into ~/.honeybook-mcp/sessions.json. All other tools use the most-recently-activated session by default. The magic_link_url arg is used only to derive the portalOrigin (cache key) — the tool does NOT open or navigate to it.",
|
|
45
|
+
description: "Capture a HoneyBook client-portal session via the fetchproxy 0.3.0 browser extension. Prerequisites: install the fetchproxy extension in Chrome/Safari, then open the vendor's magic-link URL in that browser so you're signed into their portal. This tool then snapshots the page's localStorage[\"jStorage\"] and the hb-api-fingerprint header into ~/.honeybook-mcp/sessions.json. IMPORTANT: the hb-api-fingerprint header is sniffed off the next live api.honeybook.com request, so a portal tab that has already finished loading (and gone idle) yields nothing and the capture times out. While this tool is running, refresh the portal tab (or open a workspace/file) so the page issues a fresh API request. All other tools use the most-recently-activated session by default. The magic_link_url arg is used only to derive the portalOrigin (cache key) — the tool does NOT open or navigate to it.",
|
|
46
46
|
inputSchema: {
|
|
47
47
|
magic_link_url: z
|
|
48
48
|
.string()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "honeybook-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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,7 +40,7 @@
|
|
|
40
40
|
"test:watch": "vitest"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@chrischall/mcp-utils": "^0.
|
|
43
|
+
"@chrischall/mcp-utils": "^0.10.3",
|
|
44
44
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
45
45
|
"@fetchproxy/server": "^1.0.0",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
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.3.
|
|
9
|
+
"version": "0.3.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "honeybook-mcp",
|
|
14
|
-
"version": "0.3.
|
|
14
|
+
"version": "0.3.5",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|