honeybook-mcp 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/auth.js +18 -0
- package/dist/bundle.js +206 -52
- package/dist/index.js +1 -1
- package/dist/tools/sessions.js +1 -1
- package/package.json +3 -3
- 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.6"
|
|
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.6",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Chall"
|
|
21
21
|
},
|
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)
|
|
@@ -34706,8 +34710,12 @@ var API_KEY_RE = new RegExp([
|
|
|
34706
34710
|
// webhook signing secret (Stripe-style)
|
|
34707
34711
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
34708
34712
|
var QUERY_SECRET_RE = /([?&](?:access_token|refresh_token|client_secret|api_?key|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
34713
|
+
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
34714
|
+
var JSON_SECRET_KEYS = "access_token|refresh_token|client_secret|api_?key|password|passwd|secret|token";
|
|
34715
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")[^"]*(")`, "gi");
|
|
34716
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')[^']*(')`, "gi");
|
|
34709
34717
|
function redactSecrets(text) {
|
|
34710
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
34718
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
34711
34719
|
}
|
|
34712
34720
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
34713
34721
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -34790,6 +34798,8 @@ var NonNegInt = external_exports.number().int().nonnegative();
|
|
|
34790
34798
|
var NonEmptyString = external_exports.string().min(1);
|
|
34791
34799
|
var IsoDate = external_exports.iso.date();
|
|
34792
34800
|
var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
34801
|
+
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.");
|
|
34802
|
+
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
34803
|
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
34804
|
var schemaConfirm = external_exports.boolean().optional().describe("Must be true to proceed. Without this, the tool returns a preview.");
|
|
34795
34805
|
var paginationSchema = {
|
|
@@ -36006,10 +36016,20 @@ async function openEncryptedFrame(sessionKey, frame) {
|
|
|
36006
36016
|
|
|
36007
36017
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
36008
36018
|
import { createServer, Server as HttpServer } from "node:http";
|
|
36019
|
+
var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
36009
36020
|
async function electRole(opts) {
|
|
36010
36021
|
const server = createServer();
|
|
36022
|
+
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
36011
36023
|
return new Promise((resolve, reject) => {
|
|
36024
|
+
let timer;
|
|
36025
|
+
const clearBindTimer = () => {
|
|
36026
|
+
if (timer) {
|
|
36027
|
+
clearTimeout(timer);
|
|
36028
|
+
timer = void 0;
|
|
36029
|
+
}
|
|
36030
|
+
};
|
|
36012
36031
|
const onError = (e) => {
|
|
36032
|
+
clearBindTimer();
|
|
36013
36033
|
server.removeListener("listening", onListening);
|
|
36014
36034
|
if (e.code === "EADDRINUSE") {
|
|
36015
36035
|
try {
|
|
@@ -36022,11 +36042,25 @@ async function electRole(opts) {
|
|
|
36022
36042
|
}
|
|
36023
36043
|
};
|
|
36024
36044
|
const onListening = () => {
|
|
36045
|
+
clearBindTimer();
|
|
36025
36046
|
server.removeListener("error", onError);
|
|
36026
36047
|
resolve({ role: "host", server });
|
|
36027
36048
|
};
|
|
36028
36049
|
server.once("error", onError);
|
|
36029
36050
|
server.once("listening", onListening);
|
|
36051
|
+
if (bindTimeoutMs > 0) {
|
|
36052
|
+
timer = setTimeout(() => {
|
|
36053
|
+
server.removeListener("error", onError);
|
|
36054
|
+
server.removeListener("listening", onListening);
|
|
36055
|
+
try {
|
|
36056
|
+
server.close();
|
|
36057
|
+
} catch {
|
|
36058
|
+
}
|
|
36059
|
+
reject(new Error(`fetchproxy: bind to ${opts.host}:${opts.port} timed out after ${bindTimeoutMs}ms (port may be in a bad state)`));
|
|
36060
|
+
}, bindTimeoutMs);
|
|
36061
|
+
if (typeof timer.unref === "function")
|
|
36062
|
+
timer.unref();
|
|
36063
|
+
}
|
|
36030
36064
|
server.listen(opts.port, opts.host);
|
|
36031
36065
|
});
|
|
36032
36066
|
}
|
|
@@ -36119,6 +36153,44 @@ var SessionState = class {
|
|
|
36119
36153
|
}
|
|
36120
36154
|
};
|
|
36121
36155
|
|
|
36156
|
+
// node_modules/@fetchproxy/server/dist/session-ready.js
|
|
36157
|
+
var SESSION_READY_TIMEOUT_MS = 3e4;
|
|
36158
|
+
var FetchproxySessionNotReadyError = class extends Error {
|
|
36159
|
+
reason;
|
|
36160
|
+
pairCode;
|
|
36161
|
+
mcpId;
|
|
36162
|
+
hint;
|
|
36163
|
+
constructor(info) {
|
|
36164
|
+
const pairing = info.pairCode !== null && info.pairCode !== "";
|
|
36165
|
+
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.`;
|
|
36166
|
+
super(`fetchproxy: ${pairing ? "pairing not yet approved" : "no confirmed browser session"} for "${info.mcpId}". ${hint}`);
|
|
36167
|
+
this.name = "FetchproxySessionNotReadyError";
|
|
36168
|
+
this.reason = pairing ? "pair-required" : "not-ready";
|
|
36169
|
+
this.pairCode = pairing ? info.pairCode : null;
|
|
36170
|
+
this.mcpId = info.mcpId;
|
|
36171
|
+
this.hint = hint;
|
|
36172
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
36173
|
+
}
|
|
36174
|
+
};
|
|
36175
|
+
async function awaitSessionReady(ready, opts) {
|
|
36176
|
+
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
36177
|
+
if (ms <= 0)
|
|
36178
|
+
return ready;
|
|
36179
|
+
let timer;
|
|
36180
|
+
const timeout = new Promise((_, reject) => {
|
|
36181
|
+
timer = setTimeout(() => {
|
|
36182
|
+
reject(new FetchproxySessionNotReadyError({ mcpId: opts.mcpId, pairCode: opts.pendingPairCode() }));
|
|
36183
|
+
}, ms);
|
|
36184
|
+
timer.unref?.();
|
|
36185
|
+
});
|
|
36186
|
+
try {
|
|
36187
|
+
return await Promise.race([ready, timeout]);
|
|
36188
|
+
} finally {
|
|
36189
|
+
if (timer)
|
|
36190
|
+
clearTimeout(timer);
|
|
36191
|
+
}
|
|
36192
|
+
}
|
|
36193
|
+
|
|
36122
36194
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
36123
36195
|
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost)(:|$))/i;
|
|
36124
36196
|
var enc2 = new TextEncoder();
|
|
@@ -36206,6 +36278,29 @@ async function startHost(opts) {
|
|
|
36206
36278
|
return;
|
|
36207
36279
|
}
|
|
36208
36280
|
if (frame.type === "hello" && frame.role === "server") {
|
|
36281
|
+
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
36282
|
+
const peerSigMsg = concatBytes(enc2.encode(frame.mcpId), fromB64(frame.sessionNonce));
|
|
36283
|
+
const peerSig = fromB64(frame.sessionSig);
|
|
36284
|
+
let peerSigOk = false;
|
|
36285
|
+
try {
|
|
36286
|
+
peerSigOk = await ed25519Verify(peerEdPub, peerSigMsg, peerSig);
|
|
36287
|
+
} catch {
|
|
36288
|
+
peerSigOk = false;
|
|
36289
|
+
}
|
|
36290
|
+
if (!peerSigOk) {
|
|
36291
|
+
console.warn("[fetchproxy] peer hello signature invalid \u2014 refusing registration (possible squatter)");
|
|
36292
|
+
ws.close(1008, "peer hello signature invalid");
|
|
36293
|
+
return;
|
|
36294
|
+
}
|
|
36295
|
+
const existing = peers.get(frame.mcpId);
|
|
36296
|
+
if (existing && existing.ws !== ws) {
|
|
36297
|
+
const existingEdPub = existing.helloFrame.identityEd25519Pub;
|
|
36298
|
+
if (existingEdPub !== frame.identityEd25519Pub) {
|
|
36299
|
+
console.warn("[fetchproxy] peer mcpId already mapped to a different identity \u2014 refusing (mcpId squatting)");
|
|
36300
|
+
ws.close(1008, "mcpId already registered to another identity");
|
|
36301
|
+
return;
|
|
36302
|
+
}
|
|
36303
|
+
}
|
|
36209
36304
|
identified = "peer";
|
|
36210
36305
|
peerMcpId = frame.mcpId;
|
|
36211
36306
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
@@ -36298,8 +36393,10 @@ async function startHost(opts) {
|
|
|
36298
36393
|
resetSessionPromise();
|
|
36299
36394
|
disconnectListeners.forEach((cb) => cb());
|
|
36300
36395
|
}
|
|
36301
|
-
if (identified === "peer" && peerMcpId)
|
|
36302
|
-
peers.
|
|
36396
|
+
if (identified === "peer" && peerMcpId) {
|
|
36397
|
+
if (peers.get(peerMcpId)?.ws === ws)
|
|
36398
|
+
peers.delete(peerMcpId);
|
|
36399
|
+
}
|
|
36303
36400
|
});
|
|
36304
36401
|
});
|
|
36305
36402
|
return {
|
|
@@ -36315,7 +36412,10 @@ async function startHost(opts) {
|
|
|
36315
36412
|
});
|
|
36316
36413
|
}),
|
|
36317
36414
|
sendOwnInner: async (inner) => {
|
|
36318
|
-
const session = await ownSessionReady
|
|
36415
|
+
const session = await awaitSessionReady(ownSessionReady, {
|
|
36416
|
+
mcpId: opts.ownMcpId,
|
|
36417
|
+
pendingPairCode: () => ownPendingPairCode
|
|
36418
|
+
});
|
|
36319
36419
|
if (!extensionWs)
|
|
36320
36420
|
throw new Error("host: no extension connected");
|
|
36321
36421
|
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), inner);
|
|
@@ -36420,7 +36520,10 @@ async function startPeer(opts) {
|
|
|
36420
36520
|
ws,
|
|
36421
36521
|
session: sessionPromise,
|
|
36422
36522
|
sendInner: async (inner) => {
|
|
36423
|
-
await sessionPromise
|
|
36523
|
+
await awaitSessionReady(sessionPromise, {
|
|
36524
|
+
mcpId: opts.mcpId,
|
|
36525
|
+
pendingPairCode: () => pendingPairCode
|
|
36526
|
+
});
|
|
36424
36527
|
const s = session;
|
|
36425
36528
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), inner);
|
|
36426
36529
|
ws.send(JSON.stringify(sealed));
|
|
@@ -37086,6 +37189,35 @@ var FetchproxyServer = class {
|
|
|
37086
37189
|
this.keepAliveTimer = null;
|
|
37087
37190
|
}
|
|
37088
37191
|
}
|
|
37192
|
+
/**
|
|
37193
|
+
* Send an inner request frame via whichever bridge handle is active. If the
|
|
37194
|
+
* send throws (e.g. `FetchproxySessionNotReadyError` — the session never
|
|
37195
|
+
* confirmed), the frame never reached the bridge, so no reply will arrive:
|
|
37196
|
+
* drop the just-registered pending resolver for this id (it lives in exactly
|
|
37197
|
+
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
37198
|
+
* server closes, then rethrow.
|
|
37199
|
+
*/
|
|
37200
|
+
async sendInnerFrame(inner) {
|
|
37201
|
+
try {
|
|
37202
|
+
if (this.hostHandle) {
|
|
37203
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
37204
|
+
} else if (this.peerHandle) {
|
|
37205
|
+
await this.peerHandle.sendInner(inner);
|
|
37206
|
+
}
|
|
37207
|
+
} catch (err) {
|
|
37208
|
+
if ("id" in inner && typeof inner.id === "number") {
|
|
37209
|
+
const { id } = inner;
|
|
37210
|
+
this.pending.delete(id);
|
|
37211
|
+
this.pendingReadCookies.delete(id);
|
|
37212
|
+
this.pendingStorage.delete(id);
|
|
37213
|
+
this.pendingCapture.delete(id);
|
|
37214
|
+
this.pendingRedirect.delete(id);
|
|
37215
|
+
this.pendingDownload.delete(id);
|
|
37216
|
+
this.pendingIdb.delete(id);
|
|
37217
|
+
}
|
|
37218
|
+
throw err;
|
|
37219
|
+
}
|
|
37220
|
+
}
|
|
37089
37221
|
/**
|
|
37090
37222
|
* Single bridge round-trip, wrapped by `fetchTimeoutMs` when set.
|
|
37091
37223
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
@@ -37097,11 +37229,7 @@ var FetchproxyServer = class {
|
|
|
37097
37229
|
const pending = new Promise((resolve) => {
|
|
37098
37230
|
this.pending.set(id, resolve);
|
|
37099
37231
|
});
|
|
37100
|
-
|
|
37101
|
-
await this.hostHandle.sendOwnInner(inner);
|
|
37102
|
-
} else if (this.peerHandle) {
|
|
37103
|
-
await this.peerHandle.sendInner(inner);
|
|
37104
|
-
}
|
|
37232
|
+
await this.sendInnerFrame(inner);
|
|
37105
37233
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37106
37234
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37107
37235
|
return pending;
|
|
@@ -37130,6 +37258,51 @@ var FetchproxyServer = class {
|
|
|
37130
37258
|
clearTimeout(timer);
|
|
37131
37259
|
}
|
|
37132
37260
|
}
|
|
37261
|
+
/**
|
|
37262
|
+
* FP-B2: bound a non-`fetch` verb's reply wait by `fetchTimeoutMs`.
|
|
37263
|
+
*
|
|
37264
|
+
* Before this, only `fetch()` raced its `pending` reply against a timer
|
|
37265
|
+
* (`_fetchOnceWithTimeout`); `readCookies`, the storage reads,
|
|
37266
|
+
* `capture_request_header`, `capture_redirect`, `download`, and
|
|
37267
|
+
* `read_indexed_db` awaited their `pending` promise with no race, so a
|
|
37268
|
+
* wedged extension hung the tool call indefinitely.
|
|
37269
|
+
*
|
|
37270
|
+
* `pending` is the already-registered reply promise. `pendingMap`/`id`
|
|
37271
|
+
* point at the op-specific map entry so we can drop it on expiry exactly
|
|
37272
|
+
* as `_fetchOnceWithTimeout` does — otherwise a late bridge reply would
|
|
37273
|
+
* resolve into a stale resolver / leak. On timeout we reject with a
|
|
37274
|
+
* `FetchproxyTimeoutError` (the same throwable the convenience methods
|
|
37275
|
+
* already surface for fetch timeouts). `0`/unset opts out (unbounded),
|
|
37276
|
+
* matching the fetch path.
|
|
37277
|
+
*/
|
|
37278
|
+
async _withVerbTimeout(pending, pendingMap, id, url2) {
|
|
37279
|
+
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
37280
|
+
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
37281
|
+
return pending;
|
|
37282
|
+
let timer;
|
|
37283
|
+
const start = Date.now();
|
|
37284
|
+
try {
|
|
37285
|
+
return await Promise.race([
|
|
37286
|
+
pending,
|
|
37287
|
+
new Promise((_resolve, reject) => {
|
|
37288
|
+
timer = setTimeout(() => {
|
|
37289
|
+
pendingMap.delete(id);
|
|
37290
|
+
reject(new FetchproxyTimeoutError({
|
|
37291
|
+
url: url2,
|
|
37292
|
+
timeoutMs,
|
|
37293
|
+
role: this.role,
|
|
37294
|
+
port: this.opts.port,
|
|
37295
|
+
elapsedMs: Date.now() - start,
|
|
37296
|
+
retryAttempted: false
|
|
37297
|
+
}));
|
|
37298
|
+
}, timeoutMs);
|
|
37299
|
+
})
|
|
37300
|
+
]);
|
|
37301
|
+
} finally {
|
|
37302
|
+
if (timer)
|
|
37303
|
+
clearTimeout(timer);
|
|
37304
|
+
}
|
|
37305
|
+
}
|
|
37133
37306
|
/**
|
|
37134
37307
|
* Map an `ok:false` fetch result to its typed throwable. Centralizes
|
|
37135
37308
|
* the kind-to-error-class switch so `request()` and (via the same
|
|
@@ -37420,12 +37593,8 @@ var FetchproxyServer = class {
|
|
|
37420
37593
|
const pending = new Promise((resolve) => {
|
|
37421
37594
|
this.pendingReadCookies.set(id, resolve);
|
|
37422
37595
|
});
|
|
37423
|
-
|
|
37424
|
-
|
|
37425
|
-
} else if (this.peerHandle) {
|
|
37426
|
-
await this.peerHandle.sendInner(inner);
|
|
37427
|
-
}
|
|
37428
|
-
const result = await pending;
|
|
37596
|
+
await this.sendInnerFrame(inner);
|
|
37597
|
+
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
37429
37598
|
if (!result.ok) {
|
|
37430
37599
|
throw new FetchproxyProtocolError(result.error);
|
|
37431
37600
|
}
|
|
@@ -37491,12 +37660,8 @@ var FetchproxyServer = class {
|
|
|
37491
37660
|
const pending = new Promise((resolve, reject) => {
|
|
37492
37661
|
this.pendingStorage.set(id, { resolve, reject });
|
|
37493
37662
|
});
|
|
37494
|
-
|
|
37495
|
-
|
|
37496
|
-
} else if (this.peerHandle) {
|
|
37497
|
-
await this.peerHandle.sendInner(inner);
|
|
37498
|
-
}
|
|
37499
|
-
return pending;
|
|
37663
|
+
await this.sendInnerFrame(inner);
|
|
37664
|
+
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
37500
37665
|
}
|
|
37501
37666
|
/**
|
|
37502
37667
|
* 0.3.0+: snapshot the next outgoing request's named header. Single-
|
|
@@ -37600,12 +37765,8 @@ var FetchproxyServer = class {
|
|
|
37600
37765
|
const pending = new Promise((resolve, reject) => {
|
|
37601
37766
|
this.pendingCapture.set(id, { resolve, reject });
|
|
37602
37767
|
});
|
|
37603
|
-
|
|
37604
|
-
|
|
37605
|
-
} else if (this.peerHandle) {
|
|
37606
|
-
await this.peerHandle.sendInner(inner);
|
|
37607
|
-
}
|
|
37608
|
-
return pending;
|
|
37768
|
+
await this.sendInnerFrame(inner);
|
|
37769
|
+
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37609
37770
|
}
|
|
37610
37771
|
/**
|
|
37611
37772
|
* Snapshot the redirect target URL of the next request the browser
|
|
@@ -37689,12 +37850,8 @@ var FetchproxyServer = class {
|
|
|
37689
37850
|
const pending = new Promise((resolve, reject) => {
|
|
37690
37851
|
this.pendingRedirect.set(id, { resolve, reject });
|
|
37691
37852
|
});
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
} else if (this.peerHandle) {
|
|
37695
|
-
await this.peerHandle.sendInner(inner);
|
|
37696
|
-
}
|
|
37697
|
-
return pending;
|
|
37853
|
+
await this.sendInnerFrame(inner);
|
|
37854
|
+
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
37698
37855
|
}
|
|
37699
37856
|
/**
|
|
37700
37857
|
* Download `url` through the BROWSER's own network stack via
|
|
@@ -37775,12 +37932,8 @@ var FetchproxyServer = class {
|
|
|
37775
37932
|
const pending = new Promise((resolve, reject) => {
|
|
37776
37933
|
this.pendingDownload.set(id, { resolve, reject });
|
|
37777
37934
|
});
|
|
37778
|
-
|
|
37779
|
-
|
|
37780
|
-
} else if (this.peerHandle) {
|
|
37781
|
-
await this.peerHandle.sendInner(inner);
|
|
37782
|
-
}
|
|
37783
|
-
return pending;
|
|
37935
|
+
await this.sendInnerFrame(inner);
|
|
37936
|
+
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
37784
37937
|
}
|
|
37785
37938
|
/**
|
|
37786
37939
|
* 0.4.0+: read declared IndexedDB keys from the user's signed-in
|
|
@@ -37827,12 +37980,8 @@ var FetchproxyServer = class {
|
|
|
37827
37980
|
const pending = new Promise((resolve, reject) => {
|
|
37828
37981
|
this.pendingIdb.set(id, { resolve, reject });
|
|
37829
37982
|
});
|
|
37830
|
-
|
|
37831
|
-
|
|
37832
|
-
} else if (this.peerHandle) {
|
|
37833
|
-
await this.peerHandle.sendInner(inner);
|
|
37834
|
-
}
|
|
37835
|
-
return pending;
|
|
37983
|
+
await this.sendInnerFrame(inner);
|
|
37984
|
+
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
37836
37985
|
}
|
|
37837
37986
|
assertScopeSubset(requested, declared, label) {
|
|
37838
37987
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -38263,7 +38412,7 @@ function bridgeErrorInfo(err) {
|
|
|
38263
38412
|
// package.json
|
|
38264
38413
|
var package_default = {
|
|
38265
38414
|
name: "honeybook-mcp",
|
|
38266
|
-
version: "0.3.
|
|
38415
|
+
version: "0.3.6",
|
|
38267
38416
|
mcpName: "io.github.chrischall/honeybook-mcp",
|
|
38268
38417
|
description: "HoneyBook client-portal MCP server for Claude",
|
|
38269
38418
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -38303,7 +38452,7 @@ var package_default = {
|
|
|
38303
38452
|
"test:watch": "vitest"
|
|
38304
38453
|
},
|
|
38305
38454
|
dependencies: {
|
|
38306
|
-
"@chrischall/mcp-utils": "^0.
|
|
38455
|
+
"@chrischall/mcp-utils": "^0.12.0",
|
|
38307
38456
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
38308
38457
|
"@fetchproxy/server": "^1.0.0",
|
|
38309
38458
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38311,7 +38460,7 @@ var package_default = {
|
|
|
38311
38460
|
zod: "^4.4.3"
|
|
38312
38461
|
},
|
|
38313
38462
|
devDependencies: {
|
|
38314
|
-
"@types/node": "^
|
|
38463
|
+
"@types/node": "^26.0.0",
|
|
38315
38464
|
"@vitest/coverage-v8": "^4.1.7",
|
|
38316
38465
|
esbuild: "^0.28.0",
|
|
38317
38466
|
typescript: "^6.0.3",
|
|
@@ -38402,6 +38551,11 @@ async function captureSessionViaFetchproxy(opts) {
|
|
|
38402
38551
|
);
|
|
38403
38552
|
}
|
|
38404
38553
|
const msg = e instanceof Error ? e.message : String(e);
|
|
38554
|
+
if (bridgeError.type === "timeout" || /timeout/i.test(msg)) {
|
|
38555
|
+
throw new Error(
|
|
38556
|
+
"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."
|
|
38557
|
+
);
|
|
38558
|
+
}
|
|
38405
38559
|
throw new Error(
|
|
38406
38560
|
`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
38561
|
);
|
|
@@ -38472,7 +38626,7 @@ function registerSessionTools(server) {
|
|
|
38472
38626
|
server.registerTool(
|
|
38473
38627
|
"use_magic_link",
|
|
38474
38628
|
{
|
|
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.`,
|
|
38629
|
+
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
38630
|
inputSchema: {
|
|
38477
38631
|
magic_link_url: external_exports.string().url().describe(
|
|
38478
38632
|
"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 +39049,7 @@ var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
|
38895
39049
|
await loadDotenvSafely({ path: join3(__dirname, "..", ".env"), override: false });
|
|
38896
39050
|
await runMcp({
|
|
38897
39051
|
name: "honeybook-mcp",
|
|
38898
|
-
version: "0.3.
|
|
39052
|
+
version: "0.3.6",
|
|
38899
39053
|
// x-release-please-version
|
|
38900
39054
|
tools: [
|
|
38901
39055
|
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.6', // 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.6",
|
|
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.12.0",
|
|
44
44
|
"@fetchproxy/bootstrap": "^1.0.0",
|
|
45
45
|
"@fetchproxy/server": "^1.0.0",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"zod": "^4.4.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^
|
|
51
|
+
"@types/node": "^26.0.0",
|
|
52
52
|
"@vitest/coverage-v8": "^4.1.7",
|
|
53
53
|
"esbuild": "^0.28.0",
|
|
54
54
|
"typescript": "^6.0.3",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/honeybook-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.6",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "honeybook-mcp",
|
|
14
|
-
"version": "0.3.
|
|
14
|
+
"version": "0.3.6",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|