honeybook-mcp 0.5.0 → 0.7.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/dist/bundle.js +234 -77
- package/dist/client.js +7 -1
- package/dist/index.js +3 -1
- package/dist/tools/healthcheck.js +61 -0
- package/package.json +4 -4
- 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.7.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.7.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Chall"
|
|
21
21
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -2990,7 +2990,7 @@ var require_compile = __commonJS({
|
|
|
2990
2990
|
const schOrFunc = root.refs[ref];
|
|
2991
2991
|
if (schOrFunc)
|
|
2992
2992
|
return schOrFunc;
|
|
2993
|
-
let _sch =
|
|
2993
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2994
2994
|
if (_sch === void 0) {
|
|
2995
2995
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2996
2996
|
const { schemaId } = this.opts;
|
|
@@ -3017,7 +3017,7 @@ var require_compile = __commonJS({
|
|
|
3017
3017
|
function sameSchemaEnv(s1, s2) {
|
|
3018
3018
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3019
3019
|
}
|
|
3020
|
-
function
|
|
3020
|
+
function resolve2(root, ref) {
|
|
3021
3021
|
let sch;
|
|
3022
3022
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3023
3023
|
ref = sch;
|
|
@@ -3313,8 +3313,8 @@ var require_utils = __commonJS({
|
|
|
3313
3313
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3314
3314
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3315
3315
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3316
|
-
function reescapeHostDelimiters(host,
|
|
3317
|
-
const re =
|
|
3316
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
3317
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3318
3318
|
re.lastIndex = 0;
|
|
3319
3319
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3320
3320
|
}
|
|
@@ -3648,7 +3648,7 @@ var require_fast_uri = __commonJS({
|
|
|
3648
3648
|
}
|
|
3649
3649
|
return uri;
|
|
3650
3650
|
}
|
|
3651
|
-
function
|
|
3651
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3652
3652
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3653
3653
|
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3654
3654
|
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
@@ -3803,7 +3803,7 @@ var require_fast_uri = __commonJS({
|
|
|
3803
3803
|
fragment: void 0
|
|
3804
3804
|
};
|
|
3805
3805
|
let malformedAuthorityOrPort = false;
|
|
3806
|
-
let
|
|
3806
|
+
let isIP2 = false;
|
|
3807
3807
|
if (options.reference === "suffix") {
|
|
3808
3808
|
if (options.scheme) {
|
|
3809
3809
|
uri = options.scheme + ":" + uri;
|
|
@@ -3852,9 +3852,9 @@ var require_fast_uri = __commonJS({
|
|
|
3852
3852
|
if (ipv4result === false) {
|
|
3853
3853
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
3854
3854
|
parsed.host = ipv6result.host.toLowerCase();
|
|
3855
|
-
|
|
3855
|
+
isIP2 = ipv6result.isIPV6;
|
|
3856
3856
|
} else {
|
|
3857
|
-
|
|
3857
|
+
isIP2 = true;
|
|
3858
3858
|
}
|
|
3859
3859
|
}
|
|
3860
3860
|
if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
|
|
@@ -3871,7 +3871,7 @@ var require_fast_uri = __commonJS({
|
|
|
3871
3871
|
}
|
|
3872
3872
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
3873
3873
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3874
|
-
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
3874
|
+
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
3875
3875
|
try {
|
|
3876
3876
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3877
3877
|
} catch (e) {
|
|
@@ -3885,7 +3885,7 @@ var require_fast_uri = __commonJS({
|
|
|
3885
3885
|
parsed.scheme = unescape(parsed.scheme);
|
|
3886
3886
|
}
|
|
3887
3887
|
if (parsed.host !== void 0) {
|
|
3888
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host),
|
|
3888
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP2);
|
|
3889
3889
|
}
|
|
3890
3890
|
}
|
|
3891
3891
|
if (parsed.path) {
|
|
@@ -3932,7 +3932,7 @@ var require_fast_uri = __commonJS({
|
|
|
3932
3932
|
var fastUri = {
|
|
3933
3933
|
SCHEMES,
|
|
3934
3934
|
normalize,
|
|
3935
|
-
resolve,
|
|
3935
|
+
resolve: resolve2,
|
|
3936
3936
|
resolveComponent,
|
|
3937
3937
|
equal,
|
|
3938
3938
|
serialize,
|
|
@@ -7198,7 +7198,7 @@ var require_permessage_deflate = __commonJS({
|
|
|
7198
7198
|
acceptAsServer(offers) {
|
|
7199
7199
|
const opts = this._options;
|
|
7200
7200
|
const accepted = offers.find((params) => {
|
|
7201
|
-
if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && !params.client_max_window_bits) {
|
|
7201
|
+
if (opts.serverNoContextTakeover === false && params.server_no_context_takeover || params.server_max_window_bits && (opts.serverMaxWindowBits === false || typeof opts.serverMaxWindowBits === "number" && opts.serverMaxWindowBits > params.server_max_window_bits) || typeof opts.clientMaxWindowBits === "number" && (typeof params.client_max_window_bits === "number" ? opts.clientMaxWindowBits > params.client_max_window_bits : !params.client_max_window_bits)) {
|
|
7202
7202
|
return false;
|
|
7203
7203
|
}
|
|
7204
7204
|
return true;
|
|
@@ -10592,7 +10592,7 @@ var require_websocket_server = __commonJS({
|
|
|
10592
10592
|
});
|
|
10593
10593
|
|
|
10594
10594
|
// src/index.ts
|
|
10595
|
-
import { dirname as dirname2, join as
|
|
10595
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
10596
10596
|
import { fileURLToPath } from "url";
|
|
10597
10597
|
|
|
10598
10598
|
// node_modules/zod/v3/helpers/util.js
|
|
@@ -32553,7 +32553,7 @@ var Protocol = class {
|
|
|
32553
32553
|
return;
|
|
32554
32554
|
}
|
|
32555
32555
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
32556
|
-
await new Promise((
|
|
32556
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
32557
32557
|
options?.signal?.throwIfAborted();
|
|
32558
32558
|
}
|
|
32559
32559
|
} catch (error51) {
|
|
@@ -32570,7 +32570,7 @@ var Protocol = class {
|
|
|
32570
32570
|
*/
|
|
32571
32571
|
request(request, resultSchema, options) {
|
|
32572
32572
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
32573
|
-
return new Promise((
|
|
32573
|
+
return new Promise((resolve2, reject) => {
|
|
32574
32574
|
const earlyReject = (error51) => {
|
|
32575
32575
|
reject(error51);
|
|
32576
32576
|
};
|
|
@@ -32648,7 +32648,7 @@ var Protocol = class {
|
|
|
32648
32648
|
if (!parseResult.success) {
|
|
32649
32649
|
reject(parseResult.error);
|
|
32650
32650
|
} else {
|
|
32651
|
-
|
|
32651
|
+
resolve2(parseResult.data);
|
|
32652
32652
|
}
|
|
32653
32653
|
} catch (error51) {
|
|
32654
32654
|
reject(error51);
|
|
@@ -32909,12 +32909,12 @@ var Protocol = class {
|
|
|
32909
32909
|
}
|
|
32910
32910
|
} catch {
|
|
32911
32911
|
}
|
|
32912
|
-
return new Promise((
|
|
32912
|
+
return new Promise((resolve2, reject) => {
|
|
32913
32913
|
if (signal.aborted) {
|
|
32914
32914
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
32915
32915
|
return;
|
|
32916
32916
|
}
|
|
32917
|
-
const timeoutId = setTimeout(
|
|
32917
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
32918
32918
|
signal.addEventListener("abort", () => {
|
|
32919
32919
|
clearTimeout(timeoutId);
|
|
32920
32920
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -34005,7 +34005,7 @@ var McpServer = class {
|
|
|
34005
34005
|
let task = createTaskResult.task;
|
|
34006
34006
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
34007
34007
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
34008
|
-
await new Promise((
|
|
34008
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
34009
34009
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
34010
34010
|
if (!updatedTask) {
|
|
34011
34011
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -34669,12 +34669,12 @@ var StdioServerTransport = class {
|
|
|
34669
34669
|
this.onclose?.();
|
|
34670
34670
|
}
|
|
34671
34671
|
send(message) {
|
|
34672
|
-
return new Promise((
|
|
34672
|
+
return new Promise((resolve2) => {
|
|
34673
34673
|
const json2 = serializeMessage(message);
|
|
34674
34674
|
if (this._stdout.write(json2)) {
|
|
34675
|
-
|
|
34675
|
+
resolve2();
|
|
34676
34676
|
} else {
|
|
34677
|
-
this._stdout.once("drain",
|
|
34677
|
+
this._stdout.once("drain", resolve2);
|
|
34678
34678
|
}
|
|
34679
34679
|
});
|
|
34680
34680
|
}
|
|
@@ -34891,11 +34891,11 @@ var pageSchema = {
|
|
|
34891
34891
|
|
|
34892
34892
|
// src/sessions.ts
|
|
34893
34893
|
import { homedir } from "node:os";
|
|
34894
|
-
import { join } from "node:path";
|
|
34894
|
+
import { join as join2 } from "node:path";
|
|
34895
34895
|
|
|
34896
34896
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
34897
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync } from "node:fs";
|
|
34898
|
-
import { dirname } from "node:path";
|
|
34897
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
34898
|
+
import { dirname, join, resolve } from "node:path";
|
|
34899
34899
|
function normalizeOrigin(input) {
|
|
34900
34900
|
try {
|
|
34901
34901
|
return new URL(input).origin.replace(/\/$/, "");
|
|
@@ -35031,9 +35031,9 @@ var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
|
35031
35031
|
|
|
35032
35032
|
// src/sessions.ts
|
|
35033
35033
|
var sessionsDir = readEnvVar("HONEYBOOK_SESSIONS_DIR", {
|
|
35034
|
-
default:
|
|
35034
|
+
default: join2(homedir(), ".honeybook-mcp")
|
|
35035
35035
|
});
|
|
35036
|
-
var sessionsFilePath =
|
|
35036
|
+
var sessionsFilePath = join2(sessionsDir, "sessions.json");
|
|
35037
35037
|
var sessionStore = new SessionStore({
|
|
35038
35038
|
filePath: sessionsFilePath,
|
|
35039
35039
|
keyOf: (session) => session.portalOrigin,
|
|
@@ -35116,14 +35116,13 @@ var HoneyBookClient = class {
|
|
|
35116
35116
|
function clearClientCache() {
|
|
35117
35117
|
clientCache.clear();
|
|
35118
35118
|
}
|
|
35119
|
+
var NO_ACTIVE_SESSION_MESSAGE = "No active HoneyBook session. Use the `use_magic_link` tool with a magic-link URL from a vendor's email to activate one.";
|
|
35119
35120
|
async function getActiveClient(origin) {
|
|
35120
35121
|
const session = sessionStore.get(origin);
|
|
35121
35122
|
if (!session) {
|
|
35122
35123
|
const active = sessionStore.list();
|
|
35123
35124
|
if (active.length === 0) {
|
|
35124
|
-
throw new Error(
|
|
35125
|
-
"No active HoneyBook session. Use the `use_magic_link` tool with a magic-link URL from a vendor's email to activate one."
|
|
35126
|
-
);
|
|
35125
|
+
throw new Error(NO_ACTIVE_SESSION_MESSAGE);
|
|
35127
35126
|
}
|
|
35128
35127
|
throw new Error(
|
|
35129
35128
|
`No active session for origin "${origin}". Active origins: ${active.map((s) => s.portalOrigin).join(", ")}`
|
|
@@ -36317,7 +36316,7 @@ var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
|
36317
36316
|
async function electRole(opts) {
|
|
36318
36317
|
const server = createServer();
|
|
36319
36318
|
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
36320
|
-
return new Promise((
|
|
36319
|
+
return new Promise((resolve2, reject) => {
|
|
36321
36320
|
let timer;
|
|
36322
36321
|
const clearBindTimer = () => {
|
|
36323
36322
|
if (timer) {
|
|
@@ -36333,7 +36332,7 @@ async function electRole(opts) {
|
|
|
36333
36332
|
server.close();
|
|
36334
36333
|
} catch {
|
|
36335
36334
|
}
|
|
36336
|
-
|
|
36335
|
+
resolve2({ role: "peer" });
|
|
36337
36336
|
} else {
|
|
36338
36337
|
reject(e);
|
|
36339
36338
|
}
|
|
@@ -36341,7 +36340,7 @@ async function electRole(opts) {
|
|
|
36341
36340
|
const onListening = () => {
|
|
36342
36341
|
clearBindTimer();
|
|
36343
36342
|
server.removeListener("error", onError);
|
|
36344
|
-
|
|
36343
|
+
resolve2({ role: "host", server });
|
|
36345
36344
|
};
|
|
36346
36345
|
server.once("error", onError);
|
|
36347
36346
|
server.once("listening", onListening);
|
|
@@ -36503,16 +36502,16 @@ async function awaitSessionReady(ready, opts) {
|
|
|
36503
36502
|
|
|
36504
36503
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36505
36504
|
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
36506
|
-
import { join as
|
|
36505
|
+
import { join as join4 } from "node:path";
|
|
36507
36506
|
|
|
36508
36507
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36509
36508
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36510
|
-
import { join as
|
|
36509
|
+
import { join as join3 } from "node:path";
|
|
36511
36510
|
import { homedir as homedir2 } from "node:os";
|
|
36512
36511
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36513
36512
|
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36514
36513
|
function defaultIdentityDir() {
|
|
36515
|
-
return
|
|
36514
|
+
return join3(homedir2(), ".fetchproxy", "identity");
|
|
36516
36515
|
}
|
|
36517
36516
|
function safeIdentityFileBase(serverName) {
|
|
36518
36517
|
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
@@ -36522,7 +36521,7 @@ function safeIdentityFileBase(serverName) {
|
|
|
36522
36521
|
}
|
|
36523
36522
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36524
36523
|
const safeFile = safeIdentityFileBase(serverName);
|
|
36525
|
-
const path =
|
|
36524
|
+
const path = join3(dir, `${safeFile}.json`);
|
|
36526
36525
|
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36527
36526
|
try {
|
|
36528
36527
|
const raw = await readFile(path, "utf8");
|
|
@@ -36594,13 +36593,13 @@ function decideExtensionTrust(args) {
|
|
|
36594
36593
|
};
|
|
36595
36594
|
}
|
|
36596
36595
|
function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
|
|
36597
|
-
return
|
|
36596
|
+
return join4(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
36598
36597
|
}
|
|
36599
36598
|
function extensionTrustPathHint(serverName) {
|
|
36600
36599
|
try {
|
|
36601
36600
|
return extensionTrustPath(serverName);
|
|
36602
36601
|
} catch {
|
|
36603
|
-
return
|
|
36602
|
+
return join4(defaultIdentityDir(), "<server-name>.extension-trust.json");
|
|
36604
36603
|
}
|
|
36605
36604
|
}
|
|
36606
36605
|
function isPin(x) {
|
|
@@ -36687,8 +36686,8 @@ async function startHost(opts) {
|
|
|
36687
36686
|
let rejectOwnSession;
|
|
36688
36687
|
let ownSessionReady;
|
|
36689
36688
|
function resetSessionPromise() {
|
|
36690
|
-
ownSessionReady = new Promise((
|
|
36691
|
-
resolveOwnSession =
|
|
36689
|
+
ownSessionReady = new Promise((resolve2, reject) => {
|
|
36690
|
+
resolveOwnSession = resolve2;
|
|
36692
36691
|
rejectOwnSession = reject;
|
|
36693
36692
|
});
|
|
36694
36693
|
ownSessionReady.catch(() => {
|
|
@@ -36909,7 +36908,7 @@ async function startHost(opts) {
|
|
|
36909
36908
|
});
|
|
36910
36909
|
});
|
|
36911
36910
|
return {
|
|
36912
|
-
close: () => new Promise((
|
|
36911
|
+
close: () => new Promise((resolve2) => {
|
|
36913
36912
|
for (const client of wss.clients) {
|
|
36914
36913
|
try {
|
|
36915
36914
|
client.terminate();
|
|
@@ -36917,7 +36916,7 @@ async function startHost(opts) {
|
|
|
36917
36916
|
}
|
|
36918
36917
|
}
|
|
36919
36918
|
wss.close(() => {
|
|
36920
|
-
opts.httpServer.close(() =>
|
|
36919
|
+
opts.httpServer.close(() => resolve2());
|
|
36921
36920
|
});
|
|
36922
36921
|
}),
|
|
36923
36922
|
sendOwnInner: async (inner) => {
|
|
@@ -36947,8 +36946,8 @@ async function startHost(opts) {
|
|
|
36947
36946
|
var enc3 = new TextEncoder();
|
|
36948
36947
|
async function startPeer(opts) {
|
|
36949
36948
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`);
|
|
36950
|
-
await new Promise((
|
|
36951
|
-
ws.once("open", () =>
|
|
36949
|
+
await new Promise((resolve2, reject) => {
|
|
36950
|
+
ws.once("open", () => resolve2());
|
|
36952
36951
|
ws.once("error", reject);
|
|
36953
36952
|
});
|
|
36954
36953
|
const hello = await buildServerHello({
|
|
@@ -36978,8 +36977,8 @@ async function startPeer(opts) {
|
|
|
36978
36977
|
let pendingPairCode = null;
|
|
36979
36978
|
let resolveFirstReady;
|
|
36980
36979
|
let rejectFirstReady;
|
|
36981
|
-
const sessionPromise = new Promise((
|
|
36982
|
-
resolveFirstReady =
|
|
36980
|
+
const sessionPromise = new Promise((resolve2, reject) => {
|
|
36981
|
+
resolveFirstReady = resolve2;
|
|
36983
36982
|
rejectFirstReady = reject;
|
|
36984
36983
|
});
|
|
36985
36984
|
let extensionHello = null;
|
|
@@ -37180,6 +37179,7 @@ function classifyBridgeError(err) {
|
|
|
37180
37179
|
}
|
|
37181
37180
|
|
|
37182
37181
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
37182
|
+
import { isIP } from "node:net";
|
|
37183
37183
|
function envWsPort() {
|
|
37184
37184
|
const raw = process.env.FETCHPROXY_WS_PORT;
|
|
37185
37185
|
if (raw === void 0 || raw.trim() === "")
|
|
@@ -37191,6 +37191,15 @@ function envWsPort() {
|
|
|
37191
37191
|
return void 0;
|
|
37192
37192
|
return port;
|
|
37193
37193
|
}
|
|
37194
|
+
function envWsHost() {
|
|
37195
|
+
const raw = process.env.FETCHPROXY_WS_HOST;
|
|
37196
|
+
if (raw === void 0)
|
|
37197
|
+
return void 0;
|
|
37198
|
+
const host = raw.trim();
|
|
37199
|
+
if (host === "" || isIP(host) === 0)
|
|
37200
|
+
return void 0;
|
|
37201
|
+
return host;
|
|
37202
|
+
}
|
|
37194
37203
|
var FetchproxyProtocolError = class extends Error {
|
|
37195
37204
|
constructor(message) {
|
|
37196
37205
|
super(message);
|
|
@@ -37439,7 +37448,7 @@ var FetchproxyServer = class {
|
|
|
37439
37448
|
}
|
|
37440
37449
|
this.opts = {
|
|
37441
37450
|
port: opts.port ?? envWsPort() ?? 37149,
|
|
37442
|
-
host: opts.host ?? "127.0.0.1",
|
|
37451
|
+
host: opts.host ?? envWsHost() ?? "127.0.0.1",
|
|
37443
37452
|
serverName: opts.serverName,
|
|
37444
37453
|
version: opts.version,
|
|
37445
37454
|
domains: [...opts.domains],
|
|
@@ -37725,6 +37734,7 @@ var FetchproxyServer = class {
|
|
|
37725
37734
|
return {
|
|
37726
37735
|
role: this.role,
|
|
37727
37736
|
port: this.opts.port,
|
|
37737
|
+
host: this.opts.host,
|
|
37728
37738
|
serverVersion: this.opts.version,
|
|
37729
37739
|
fetchTimeoutMs: this.opts.fetchTimeoutMs ?? 0,
|
|
37730
37740
|
bridgeReviveDelayMs: this.opts.bridgeReviveDelayMs ?? 0,
|
|
@@ -37868,8 +37878,8 @@ var FetchproxyServer = class {
|
|
|
37868
37878
|
async _fetchOnceWithTimeout(init) {
|
|
37869
37879
|
const id = this.nextRequestId++;
|
|
37870
37880
|
const inner = { type: "request", id, op: "fetch", init };
|
|
37871
|
-
const pending = new Promise((
|
|
37872
|
-
this.pending.set(id,
|
|
37881
|
+
const pending = new Promise((resolve2) => {
|
|
37882
|
+
this.pending.set(id, resolve2);
|
|
37873
37883
|
});
|
|
37874
37884
|
await this.sendInnerFrame(inner);
|
|
37875
37885
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
@@ -37880,12 +37890,12 @@ var FetchproxyServer = class {
|
|
|
37880
37890
|
try {
|
|
37881
37891
|
return await Promise.race([
|
|
37882
37892
|
pending,
|
|
37883
|
-
new Promise((
|
|
37893
|
+
new Promise((resolve2) => {
|
|
37884
37894
|
timer = setTimeout(() => {
|
|
37885
37895
|
this.pending.delete(id);
|
|
37886
37896
|
const elapsedMs = Date.now() - start;
|
|
37887
37897
|
const error51 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
37888
|
-
|
|
37898
|
+
resolve2({
|
|
37889
37899
|
ok: false,
|
|
37890
37900
|
error: error51,
|
|
37891
37901
|
kind: "timeout",
|
|
@@ -38247,8 +38257,8 @@ var FetchproxyServer = class {
|
|
|
38247
38257
|
const tabUrl = `https://${host}/`;
|
|
38248
38258
|
inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
|
|
38249
38259
|
}
|
|
38250
|
-
const pending = new Promise((
|
|
38251
|
-
this.pendingReadCookies.set(id,
|
|
38260
|
+
const pending = new Promise((resolve2) => {
|
|
38261
|
+
this.pendingReadCookies.set(id, resolve2);
|
|
38252
38262
|
});
|
|
38253
38263
|
await this.sendInnerFrame(inner);
|
|
38254
38264
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
@@ -38306,8 +38316,8 @@ var FetchproxyServer = class {
|
|
|
38306
38316
|
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
38307
38317
|
}
|
|
38308
38318
|
};
|
|
38309
|
-
const pending = new Promise((
|
|
38310
|
-
this.pendingWriteCookies.set(id, { resolve, reject });
|
|
38319
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38320
|
+
this.pendingWriteCookies.set(id, { resolve: resolve2, reject });
|
|
38311
38321
|
});
|
|
38312
38322
|
await this.sendInnerFrame(inner);
|
|
38313
38323
|
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
@@ -38369,8 +38379,8 @@ var FetchproxyServer = class {
|
|
|
38369
38379
|
...opts.pointers ? { pointers: { ...opts.pointers } } : {}
|
|
38370
38380
|
}
|
|
38371
38381
|
};
|
|
38372
|
-
const pending = new Promise((
|
|
38373
|
-
this.pendingStorage.set(id, { resolve, reject });
|
|
38382
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38383
|
+
this.pendingStorage.set(id, { resolve: resolve2, reject });
|
|
38374
38384
|
});
|
|
38375
38385
|
await this.sendInnerFrame(inner);
|
|
38376
38386
|
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
@@ -38474,8 +38484,8 @@ var FetchproxyServer = class {
|
|
|
38474
38484
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38475
38485
|
}
|
|
38476
38486
|
};
|
|
38477
|
-
const pending = new Promise((
|
|
38478
|
-
this.pendingCapture.set(id, { resolve, reject });
|
|
38487
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38488
|
+
this.pendingCapture.set(id, { resolve: resolve2, reject });
|
|
38479
38489
|
});
|
|
38480
38490
|
await this.sendInnerFrame(inner);
|
|
38481
38491
|
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38559,8 +38569,8 @@ var FetchproxyServer = class {
|
|
|
38559
38569
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38560
38570
|
}
|
|
38561
38571
|
};
|
|
38562
|
-
const pending = new Promise((
|
|
38563
|
-
this.pendingRedirect.set(id, { resolve, reject });
|
|
38572
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38573
|
+
this.pendingRedirect.set(id, { resolve: resolve2, reject });
|
|
38564
38574
|
});
|
|
38565
38575
|
await this.sendInnerFrame(inner);
|
|
38566
38576
|
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38641,8 +38651,8 @@ var FetchproxyServer = class {
|
|
|
38641
38651
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38642
38652
|
}
|
|
38643
38653
|
};
|
|
38644
|
-
const pending = new Promise((
|
|
38645
|
-
this.pendingDownload.set(id, { resolve, reject });
|
|
38654
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38655
|
+
this.pendingDownload.set(id, { resolve: resolve2, reject });
|
|
38646
38656
|
});
|
|
38647
38657
|
await this.sendInnerFrame(inner);
|
|
38648
38658
|
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
@@ -38689,8 +38699,8 @@ var FetchproxyServer = class {
|
|
|
38689
38699
|
keys: [...opts.keys]
|
|
38690
38700
|
}
|
|
38691
38701
|
};
|
|
38692
|
-
const pending = new Promise((
|
|
38693
|
-
this.pendingIdb.set(id, { resolve, reject });
|
|
38702
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38703
|
+
this.pendingIdb.set(id, { resolve: resolve2, reject });
|
|
38694
38704
|
});
|
|
38695
38705
|
await this.sendInnerFrame(inner);
|
|
38696
38706
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
@@ -38729,8 +38739,8 @@ var FetchproxyServer = class {
|
|
|
38729
38739
|
op: "read_dom",
|
|
38730
38740
|
init: { origin, names: [...opts.names] }
|
|
38731
38741
|
};
|
|
38732
|
-
const pending = new Promise((
|
|
38733
|
-
this.pendingStorage.set(id, { resolve, reject });
|
|
38742
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38743
|
+
this.pendingStorage.set(id, { resolve: resolve2, reject });
|
|
38734
38744
|
});
|
|
38735
38745
|
await this.sendInnerFrame(inner);
|
|
38736
38746
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
@@ -38775,8 +38785,8 @@ var FetchproxyServer = class {
|
|
|
38775
38785
|
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
38776
38786
|
}
|
|
38777
38787
|
};
|
|
38778
|
-
const pending = new Promise((
|
|
38779
|
-
this.pendingGraphql.set(id, { resolve, reject });
|
|
38788
|
+
const pending = new Promise((resolve2, reject) => {
|
|
38789
|
+
this.pendingGraphql.set(id, { resolve: resolve2, reject });
|
|
38780
38790
|
});
|
|
38781
38791
|
await this.sendInnerFrame(inner);
|
|
38782
38792
|
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
|
|
@@ -39215,6 +39225,118 @@ var BootstrapDisabledError = class extends Error {
|
|
|
39215
39225
|
}
|
|
39216
39226
|
};
|
|
39217
39227
|
|
|
39228
|
+
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
39229
|
+
function statusOf(err) {
|
|
39230
|
+
if (typeof err !== "object" || err === null)
|
|
39231
|
+
return void 0;
|
|
39232
|
+
const s = err.status ?? err.statusCode;
|
|
39233
|
+
return typeof s === "number" ? s : void 0;
|
|
39234
|
+
}
|
|
39235
|
+
function credentialHint(arm, prefix, hostLabel, source) {
|
|
39236
|
+
switch (arm) {
|
|
39237
|
+
case "ok":
|
|
39238
|
+
return `Credential from '${source}' works: ${hostLabel} accepted an authenticated request. If a real tool still fails, the problem is that tool, not auth.`;
|
|
39239
|
+
case "no_credential":
|
|
39240
|
+
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
39241
|
+
case "credential_rejected":
|
|
39242
|
+
return `${hostLabel} rejected the credential from '${source}'. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
39243
|
+
case "timeout":
|
|
39244
|
+
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
39245
|
+
case "http":
|
|
39246
|
+
return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
|
|
39247
|
+
case "transport":
|
|
39248
|
+
return `Could not reach ${hostLabel} at all. Check network egress; the credential itself was never judged.`;
|
|
39249
|
+
default:
|
|
39250
|
+
return `Unexpected failure \u2014 see error.message.`;
|
|
39251
|
+
}
|
|
39252
|
+
}
|
|
39253
|
+
function registerCredentialHealthcheckTool(args) {
|
|
39254
|
+
const { server, prefix, hostLabel, probePath, resolveCredential, probeFn, classifyThrown, hints } = args;
|
|
39255
|
+
const probeUrl = probePath ? `https://${hostLabel}${probePath}` : void 0;
|
|
39256
|
+
server.registerTool(`${prefix}_healthcheck`, {
|
|
39257
|
+
title: "Verify credentials and upstream reachability",
|
|
39258
|
+
description: `Resolves the credential the way real tools do, then makes one authenticated request to ${hostLabel}. Reports which source supplied the credential, whether ${hostLabel} accepted it, the round-trip time, and a plain-English hint distinguishing 'no credential' from 'credential rejected' from 'a ${hostLabel}-side problem'. Call this when a real tool fails and you want to know which hop broke. Read-only; never returns the credential itself.`,
|
|
39259
|
+
annotations: {
|
|
39260
|
+
title: "Verify credentials and upstream reachability",
|
|
39261
|
+
readOnlyHint: true,
|
|
39262
|
+
idempotentHint: true,
|
|
39263
|
+
openWorldHint: true
|
|
39264
|
+
},
|
|
39265
|
+
inputSchema: {}
|
|
39266
|
+
}, async () => {
|
|
39267
|
+
let probeStarted = 0;
|
|
39268
|
+
let state;
|
|
39269
|
+
try {
|
|
39270
|
+
state = await resolveCredential();
|
|
39271
|
+
} catch (e) {
|
|
39272
|
+
const result2 = {
|
|
39273
|
+
ok: false,
|
|
39274
|
+
credential: { source: null, resolved: false },
|
|
39275
|
+
// No `url`: nothing was probed, and naming one implies it was tried.
|
|
39276
|
+
probe: { elapsed_ms: 0 },
|
|
39277
|
+
error: { kind: "no_credential", message: truncateErrorMessage(messageOf(e)) },
|
|
39278
|
+
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
39279
|
+
};
|
|
39280
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
39281
|
+
}
|
|
39282
|
+
const credential = {
|
|
39283
|
+
source: state.source,
|
|
39284
|
+
resolved: state.source !== null,
|
|
39285
|
+
...state.detail !== void 0 ? { detail: state.detail } : {}
|
|
39286
|
+
};
|
|
39287
|
+
if (!credential.resolved) {
|
|
39288
|
+
const result2 = {
|
|
39289
|
+
ok: false,
|
|
39290
|
+
credential,
|
|
39291
|
+
probe: { elapsed_ms: 0 },
|
|
39292
|
+
error: { kind: "no_credential", message: "no credential source resolved" },
|
|
39293
|
+
hint: hints?.no_credential ?? credentialHint("no_credential", prefix, hostLabel, null)
|
|
39294
|
+
};
|
|
39295
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
39296
|
+
}
|
|
39297
|
+
let arm = "ok";
|
|
39298
|
+
let error51;
|
|
39299
|
+
let status;
|
|
39300
|
+
let customHint;
|
|
39301
|
+
probeStarted = Date.now();
|
|
39302
|
+
try {
|
|
39303
|
+
await probeFn();
|
|
39304
|
+
} catch (e) {
|
|
39305
|
+
status = statusOf(e);
|
|
39306
|
+
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
39307
|
+
arm = status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
39308
|
+
let kind = arm;
|
|
39309
|
+
let detail;
|
|
39310
|
+
const custom2 = classifyThrown?.(e);
|
|
39311
|
+
if (custom2) {
|
|
39312
|
+
kind = custom2.kind;
|
|
39313
|
+
customHint = custom2.hint;
|
|
39314
|
+
detail = custom2.detail;
|
|
39315
|
+
}
|
|
39316
|
+
error51 = {
|
|
39317
|
+
kind,
|
|
39318
|
+
// Redacted AND bounded before it reaches the result: an upstream
|
|
39319
|
+
// failure routinely quotes what it was sent, and a healthcheck is
|
|
39320
|
+
// the tool people paste into a chat when something is broken.
|
|
39321
|
+
message: truncateErrorMessage(messageOf(e)),
|
|
39322
|
+
...detail !== void 0 ? { detail } : {}
|
|
39323
|
+
};
|
|
39324
|
+
}
|
|
39325
|
+
const result = {
|
|
39326
|
+
ok: error51 === void 0,
|
|
39327
|
+
credential,
|
|
39328
|
+
probe: {
|
|
39329
|
+
...probeUrl ? { url: probeUrl } : {},
|
|
39330
|
+
elapsed_ms: Date.now() - probeStarted,
|
|
39331
|
+
...status !== void 0 ? { status } : {}
|
|
39332
|
+
},
|
|
39333
|
+
...error51 ? { error: error51 } : {},
|
|
39334
|
+
hint: customHint ?? hints?.[arm] ?? credentialHint(arm, prefix, hostLabel, state.source)
|
|
39335
|
+
};
|
|
39336
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39337
|
+
});
|
|
39338
|
+
}
|
|
39339
|
+
|
|
39218
39340
|
// node_modules/@chrischall/mcp-utils/dist/fetchproxy/index.js
|
|
39219
39341
|
function bridgeErrorInfo(err) {
|
|
39220
39342
|
const kind = classifyBridgeError(err);
|
|
@@ -39251,7 +39373,7 @@ function bridgeErrorInfo(err) {
|
|
|
39251
39373
|
// package.json
|
|
39252
39374
|
var package_default = {
|
|
39253
39375
|
name: "honeybook-mcp",
|
|
39254
|
-
version: "0.
|
|
39376
|
+
version: "0.7.0",
|
|
39255
39377
|
mcpName: "io.github.chrischall/honeybook-mcp",
|
|
39256
39378
|
description: "HoneyBook client-portal MCP server for Claude",
|
|
39257
39379
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -39293,9 +39415,9 @@ var package_default = {
|
|
|
39293
39415
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
39294
39416
|
},
|
|
39295
39417
|
dependencies: {
|
|
39296
|
-
"@chrischall/mcp-utils": "^0.
|
|
39297
|
-
"@fetchproxy/bootstrap": "^2.
|
|
39298
|
-
"@fetchproxy/server": "^2.
|
|
39418
|
+
"@chrischall/mcp-utils": "^0.19.2",
|
|
39419
|
+
"@fetchproxy/bootstrap": "^2.2.0",
|
|
39420
|
+
"@fetchproxy/server": "^2.2.0",
|
|
39299
39421
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39300
39422
|
dotenv: "^17.4.2",
|
|
39301
39423
|
zod: "^4.4.3"
|
|
@@ -39490,6 +39612,40 @@ function registerSessionTools(server) {
|
|
|
39490
39612
|
);
|
|
39491
39613
|
}
|
|
39492
39614
|
|
|
39615
|
+
// src/tools/healthcheck.ts
|
|
39616
|
+
function registerHealthcheckTools(server) {
|
|
39617
|
+
registerCredentialHealthcheckTool({
|
|
39618
|
+
server,
|
|
39619
|
+
prefix: "honeybook",
|
|
39620
|
+
hostLabel: "api.honeybook.com",
|
|
39621
|
+
probePath: "/api/v2/users/{id}/payment_methods",
|
|
39622
|
+
resolveCredential: async () => {
|
|
39623
|
+
const active = sessionStore.list();
|
|
39624
|
+
if (active.length === 0) {
|
|
39625
|
+
throw new Error(NO_ACTIVE_SESSION_MESSAGE);
|
|
39626
|
+
}
|
|
39627
|
+
return {
|
|
39628
|
+
source: "magic-link session",
|
|
39629
|
+
detail: { origins: active.map((s) => s.portalOrigin) }
|
|
39630
|
+
};
|
|
39631
|
+
},
|
|
39632
|
+
probeFn: async () => {
|
|
39633
|
+
const client = await getActiveClient();
|
|
39634
|
+
return client.request("GET", `/api/v2/users/${client.scope.userId}/payment_methods`);
|
|
39635
|
+
},
|
|
39636
|
+
classifyThrown: (err) => {
|
|
39637
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39638
|
+
if (/auth expired|use_magic_link|HBUnauthorizedError/.test(msg)) {
|
|
39639
|
+
return {
|
|
39640
|
+
kind: "credential_rejected",
|
|
39641
|
+
hint: "HoneyBook rejected the session \u2014 it has expired. Re-run `use_magic_link` with a fresh link from a vendor email. (HoneyBook signals this with 404 + HBUnauthorizedError rather than 401; the client normalises both.)"
|
|
39642
|
+
};
|
|
39643
|
+
}
|
|
39644
|
+
return void 0;
|
|
39645
|
+
}
|
|
39646
|
+
});
|
|
39647
|
+
}
|
|
39648
|
+
|
|
39493
39649
|
// src/types.ts
|
|
39494
39650
|
var FILE_TYPES = ["agreement", "invoice", "brochure", "proposal"];
|
|
39495
39651
|
|
|
@@ -39890,10 +40046,10 @@ function registerInvoiceTools(server) {
|
|
|
39890
40046
|
|
|
39891
40047
|
// src/index.ts
|
|
39892
40048
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
39893
|
-
await loadDotenvSafely({ path:
|
|
40049
|
+
await loadDotenvSafely({ path: join5(__dirname, "..", ".env"), override: false });
|
|
39894
40050
|
await runMcp({
|
|
39895
40051
|
name: "honeybook-mcp",
|
|
39896
|
-
version: "0.
|
|
40052
|
+
version: "0.7.0",
|
|
39897
40053
|
// x-release-please-version
|
|
39898
40054
|
tools: [
|
|
39899
40055
|
registerSessionTools,
|
|
@@ -39901,6 +40057,7 @@ await runMcp({
|
|
|
39901
40057
|
registerWorkspaceTools,
|
|
39902
40058
|
registerPaymentMethodTools,
|
|
39903
40059
|
registerContractTools,
|
|
39904
|
-
registerInvoiceTools
|
|
40060
|
+
registerInvoiceTools,
|
|
40061
|
+
registerHealthcheckTools
|
|
39905
40062
|
]
|
|
39906
40063
|
});
|
package/dist/client.js
CHANGED
|
@@ -96,12 +96,18 @@ export function resetClientsForTest() {
|
|
|
96
96
|
clientCache.clear();
|
|
97
97
|
moduleState.apiVersionPromise = null;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The one "no session yet" message, shared with `honeybook_healthcheck` so the
|
|
101
|
+
* two cannot drift into telling the user different things about the same
|
|
102
|
+
* state.
|
|
103
|
+
*/
|
|
104
|
+
export const NO_ACTIVE_SESSION_MESSAGE = 'No active HoneyBook session. Use the `use_magic_link` tool with a magic-link URL from a vendor\'s email to activate one.';
|
|
99
105
|
export async function getActiveClient(origin) {
|
|
100
106
|
const session = sessionStore.get(origin);
|
|
101
107
|
if (!session) {
|
|
102
108
|
const active = sessionStore.list();
|
|
103
109
|
if (active.length === 0) {
|
|
104
|
-
throw new Error(
|
|
110
|
+
throw new Error(NO_ACTIVE_SESSION_MESSAGE);
|
|
105
111
|
}
|
|
106
112
|
throw new Error(`No active session for origin "${origin}". Active origins: ${active.map((s) => s.portalOrigin).join(', ')}`);
|
|
107
113
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { runMcp, loadDotenvSafely } from '@chrischall/mcp-utils';
|
|
|
4
4
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
await loadDotenvSafely({ path: join(__dirname, '..', '.env'), override: false });
|
|
6
6
|
import { registerSessionTools } from './tools/sessions.js';
|
|
7
|
+
import { registerHealthcheckTools } from './tools/healthcheck.js';
|
|
7
8
|
import { registerWorkspaceFileTools } from './tools/workspace_files.js';
|
|
8
9
|
import { registerWorkspaceTools } from './tools/workspaces.js';
|
|
9
10
|
import { registerPaymentMethodTools } from './tools/payment_methods.js';
|
|
@@ -11,7 +12,7 @@ import { registerContractTools } from './tools/contracts.js';
|
|
|
11
12
|
import { registerInvoiceTools } from './tools/invoices.js';
|
|
12
13
|
await runMcp({
|
|
13
14
|
name: 'honeybook-mcp',
|
|
14
|
-
version: '0.
|
|
15
|
+
version: '0.7.0', // x-release-please-version
|
|
15
16
|
tools: [
|
|
16
17
|
registerSessionTools,
|
|
17
18
|
registerWorkspaceFileTools,
|
|
@@ -19,5 +20,6 @@ await runMcp({
|
|
|
19
20
|
registerPaymentMethodTools,
|
|
20
21
|
registerContractTools,
|
|
21
22
|
registerInvoiceTools,
|
|
23
|
+
registerHealthcheckTools,
|
|
22
24
|
],
|
|
23
25
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { registerCredentialHealthcheckTool } from '@chrischall/mcp-utils/healthcheck';
|
|
2
|
+
import { getActiveClient, NO_ACTIVE_SESSION_MESSAGE } from '../client.js';
|
|
3
|
+
import { sessionStore } from '../sessions.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register `honeybook_healthcheck` — checks for an active portal session, then
|
|
6
|
+
* makes one authenticated call.
|
|
7
|
+
*
|
|
8
|
+
* HoneyBook's credential is a session captured from a magic link, so its
|
|
9
|
+
* failures are: no session was ever activated, the session has lapsed and
|
|
10
|
+
* HoneyBook rejects it, or HoneyBook is down. Those need different responses
|
|
11
|
+
* and all three previously surfaced as the same opaque tool error.
|
|
12
|
+
*
|
|
13
|
+
* HoneyBook answers a revoked token with **404 + HBUnauthorizedError**, not
|
|
14
|
+
* 401, so `classifyThrown` re-kinds that as a rejection. Without it a lapsed
|
|
15
|
+
* session reads as "that resource does not exist", which sends people looking
|
|
16
|
+
* for a missing workspace instead of re-activating their link.
|
|
17
|
+
*/
|
|
18
|
+
export function registerHealthcheckTools(server) {
|
|
19
|
+
registerCredentialHealthcheckTool({
|
|
20
|
+
server,
|
|
21
|
+
prefix: 'honeybook',
|
|
22
|
+
hostLabel: 'api.honeybook.com',
|
|
23
|
+
probePath: '/api/v2/users/{id}/payment_methods',
|
|
24
|
+
resolveCredential: async () => {
|
|
25
|
+
const active = sessionStore.list();
|
|
26
|
+
if (active.length === 0) {
|
|
27
|
+
// The SAME constant getActiveClient throws, so the two cannot drift.
|
|
28
|
+
throw new Error(NO_ACTIVE_SESSION_MESSAGE);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
source: 'magic-link session',
|
|
32
|
+
detail: { origins: active.map((s) => s.portalOrigin) },
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
probeFn: async () => {
|
|
36
|
+
const client = await getActiveClient();
|
|
37
|
+
return client.request('GET', `/api/v2/users/${client.scope.userId}/payment_methods`);
|
|
38
|
+
},
|
|
39
|
+
classifyThrown: (err) => {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
// Match the message the CLIENT throws, not HoneyBook's wire error.
|
|
42
|
+
//
|
|
43
|
+
// `HoneyBookClient.request()` already converts both 401 and
|
|
44
|
+
// 404+`HBUnauthorizedError` into one "auth expired … use_magic_link"
|
|
45
|
+
// error (`authExpiredError`), so nothing carrying the literal
|
|
46
|
+
// `HBUnauthorizedError` ever reaches here. An earlier version matched
|
|
47
|
+
// that literal and therefore never fired in production — the mock in its
|
|
48
|
+
// test bypassed the very transformation that made it dead.
|
|
49
|
+
//
|
|
50
|
+
// `HBUnauthorizedError` is kept as a second alternative only for a raw
|
|
51
|
+
// error that has not been through `request()`.
|
|
52
|
+
if (/auth expired|use_magic_link|HBUnauthorizedError/.test(msg)) {
|
|
53
|
+
return {
|
|
54
|
+
kind: 'credential_rejected',
|
|
55
|
+
hint: 'HoneyBook rejected the session — it has expired. Re-run `use_magic_link` with a fresh link from a vendor email. (HoneyBook signals this with 404 + HBUnauthorizedError rather than 401; the client normalises both.)',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "honeybook-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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>",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@chrischall/mcp-utils": "^0.
|
|
46
|
-
"@fetchproxy/bootstrap": "^2.
|
|
47
|
-
"@fetchproxy/server": "^2.
|
|
45
|
+
"@chrischall/mcp-utils": "^0.19.2",
|
|
46
|
+
"@fetchproxy/bootstrap": "^2.2.0",
|
|
47
|
+
"@fetchproxy/server": "^2.2.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
49
|
"dotenv": "^17.4.2",
|
|
50
50
|
"zod": "^4.4.3"
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/honeybook-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.7.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "honeybook-mcp",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.7.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|