@leadbay/mcp 0.32.0 → 0.32.1
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/README.md +2 -1
- package/dist/bin.js +112 -10
- package/dist/http-server.js +108 -9
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -634,7 +634,7 @@ Use `dry_run: true` to validate domain formatting and wizard reachability withou
|
|
|
634
634
|
| `LEADBAY_MOCK` | no | unset | `"1"` serves all reads from on-disk fixtures (dev only) |
|
|
635
635
|
| `LEADBAY_MOCK_DIR` | no | `./.context/leadbay-live-shapes/` | Fixture dir for mock mode |
|
|
636
636
|
| `LEADBAY_LOG_LEVEL` | no | `error` | `debug` \| `info` \| `error`, logs to stderr |
|
|
637
|
-
| `LEADBAY_TIMEOUT_MS` | no | (
|
|
637
|
+
| `LEADBAY_TIMEOUT_MS` | no | `600000` | Backstop deadline for a single outbound Leadbay request, for the case where nothing cancels it. Not a latency budget: long work (enrichment, bulk qualify, import) is launched and polled, and a cancelled tool call already closes its own requests. On expiry the socket is closed and the tool returns a `TIMEOUT` error. Set `0` to disable the backstop. |
|
|
638
638
|
|
|
639
639
|
> ⚠️ **Set `LEADBAY_REGION` explicitly.** If you don't, the server probes BOTH `api-us.leadbay.app` and `api-fr.leadbay.app` in parallel with your bearer token attached, sending the token to a backend that doesn't own your account. The `install` and `login` subcommands enforce `--region` for exactly this reason; the runtime auto-probe is a backwards-compat fallback, not a recommended setting.
|
|
640
640
|
|
|
@@ -657,6 +657,7 @@ Use `dry_run: true` to validate domain formatting and wizard reachability withou
|
|
|
657
657
|
| `mcp tool called` | Every tool invocation | `tool`, `ok`, `duration_ms`, `format`, `bytes`, `error_code` (if failed) |
|
|
658
658
|
| `mcp quota hit` | When the API returns `QUOTA_EXCEEDED` (HTTP 429/402) | `tool`, `retry_after_s`, `endpoint` |
|
|
659
659
|
| `mcp topup link created` | When `leadbay_create_topup_link` returns a checkout URL | `tool` (the URL itself is **never** captured) |
|
|
660
|
+
| `mcp tool timeout` | When an outbound Leadbay request exceeds `LEADBAY_TIMEOUT_MS` | `tool`, `timeout_ms`, `endpoint`, `region` |
|
|
660
661
|
|
|
661
662
|
After your first authenticated call, your PostHog `distinctId` is set to your Leadbay account email so MCP events consolidate with web-app events for the same person. Events also carry `$groups.organization` so org-level rollups work.
|
|
662
663
|
|
package/dist/bin.js
CHANGED
|
@@ -11,20 +11,47 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// ../core/dist/client.js
|
|
13
13
|
import https from "https";
|
|
14
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
14
15
|
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
15
16
|
import { join } from "path";
|
|
16
|
-
function
|
|
17
|
+
function defaultTimeoutMs() {
|
|
18
|
+
const raw = process.env.LEADBAY_TIMEOUT_MS;
|
|
19
|
+
if (raw === void 0 || raw.trim() === "")
|
|
20
|
+
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
21
|
+
const n = Number(raw);
|
|
22
|
+
return Number.isFinite(n) ? n : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
23
|
+
}
|
|
24
|
+
function runWithRequestSignal(signal, fn) {
|
|
25
|
+
return requestSignalStore.run(signal, fn);
|
|
26
|
+
}
|
|
27
|
+
function makeCancelledError(method, url) {
|
|
28
|
+
const err = new Error(`Request cancelled: ${method} ${url}`);
|
|
29
|
+
err.name = "AbortError";
|
|
30
|
+
err.code = "CANCELLED";
|
|
31
|
+
return err;
|
|
32
|
+
}
|
|
33
|
+
function httpsRequest(method, url, headers, body, timeoutMs, signal) {
|
|
34
|
+
const deadlineMs = timeoutMs ?? defaultTimeoutMs();
|
|
35
|
+
const abortSignal = signal ?? requestSignalStore.getStore();
|
|
36
|
+
const abortSafe = method.toUpperCase() === "GET";
|
|
17
37
|
return new Promise((resolve, reject) => {
|
|
18
38
|
const start = Date.now();
|
|
39
|
+
if (abortSignal?.aborted) {
|
|
40
|
+
reject(makeCancelledError(method, url));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
19
43
|
const parsed = new URL(url);
|
|
20
44
|
const reqHeaders = { ...headers };
|
|
21
45
|
if (body !== void 0) {
|
|
22
46
|
reqHeaders["Content-Length"] = Buffer.byteLength(body);
|
|
23
47
|
}
|
|
24
48
|
let deadline;
|
|
49
|
+
let onAbort;
|
|
25
50
|
const clearDeadline = () => {
|
|
26
51
|
if (deadline !== void 0)
|
|
27
52
|
clearTimeout(deadline);
|
|
53
|
+
if (onAbort)
|
|
54
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
28
55
|
};
|
|
29
56
|
const req = https.request({
|
|
30
57
|
hostname: parsed.hostname,
|
|
@@ -45,15 +72,24 @@ function httpsRequest(method, url, headers, body, timeoutMs) {
|
|
|
45
72
|
});
|
|
46
73
|
});
|
|
47
74
|
});
|
|
48
|
-
if (
|
|
75
|
+
if (deadlineMs > 0) {
|
|
49
76
|
deadline = setTimeout(() => {
|
|
50
77
|
req.destroy?.();
|
|
51
|
-
const err = new Error(`Request timed out after ${
|
|
78
|
+
const err = new Error(`Request timed out after ${deadlineMs}ms: ${method} ${url}`);
|
|
52
79
|
err.code = "TIMEOUT";
|
|
80
|
+
err.timeout_ms = deadlineMs;
|
|
53
81
|
reject(err);
|
|
54
|
-
},
|
|
82
|
+
}, deadlineMs);
|
|
55
83
|
deadline.unref?.();
|
|
56
84
|
}
|
|
85
|
+
if (abortSignal && abortSafe) {
|
|
86
|
+
onAbort = () => {
|
|
87
|
+
req.destroy?.();
|
|
88
|
+
clearDeadline();
|
|
89
|
+
reject(makeCancelledError(method, url));
|
|
90
|
+
};
|
|
91
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
}
|
|
57
93
|
req.on("error", (e) => {
|
|
58
94
|
clearDeadline();
|
|
59
95
|
reject(e);
|
|
@@ -177,7 +213,7 @@ function parseRetryAfter(value) {
|
|
|
177
213
|
}
|
|
178
214
|
return null;
|
|
179
215
|
}
|
|
180
|
-
var LENS_CACHE_TTL_MS, TASTE_CACHE_TTL_MS, ME_CACHE_TTL_MS, MAX_CONCURRENT, REGIONS, API_VERSION, API_PREFIX, _mockFixtures, _mockJournal, LeadbayClient;
|
|
216
|
+
var LENS_CACHE_TTL_MS, TASTE_CACHE_TTL_MS, ME_CACHE_TTL_MS, MAX_CONCURRENT, DEFAULT_REQUEST_TIMEOUT_MS, requestSignalStore, REGIONS, API_VERSION, API_PREFIX, _mockFixtures, _mockJournal, LeadbayClient;
|
|
181
217
|
var init_client = __esm({
|
|
182
218
|
"../core/dist/client.js"() {
|
|
183
219
|
"use strict";
|
|
@@ -185,6 +221,8 @@ var init_client = __esm({
|
|
|
185
221
|
TASTE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
186
222
|
ME_CACHE_TTL_MS = 60 * 1e3;
|
|
187
223
|
MAX_CONCURRENT = 5;
|
|
224
|
+
DEFAULT_REQUEST_TIMEOUT_MS = 6e5;
|
|
225
|
+
requestSignalStore = new AsyncLocalStorage();
|
|
188
226
|
REGIONS = {
|
|
189
227
|
us: "https://api-us.leadbay.app",
|
|
190
228
|
fr: "https://api-fr.leadbay.app"
|
|
@@ -408,6 +446,8 @@ var init_client = __esm({
|
|
|
408
446
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
409
447
|
}
|
|
410
448
|
return JSON.parse(res.body);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
411
451
|
} finally {
|
|
412
452
|
this.releaseSemaphore();
|
|
413
453
|
}
|
|
@@ -439,6 +479,8 @@ var init_client = __esm({
|
|
|
439
479
|
if (res.status < 200 || res.status >= 300) {
|
|
440
480
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
441
481
|
}
|
|
482
|
+
} catch (e) {
|
|
483
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
442
484
|
} finally {
|
|
443
485
|
this.releaseSemaphore();
|
|
444
486
|
}
|
|
@@ -476,6 +518,8 @@ var init_client = __esm({
|
|
|
476
518
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
477
519
|
}
|
|
478
520
|
return JSON.parse(res.body);
|
|
521
|
+
} catch (e) {
|
|
522
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
479
523
|
} finally {
|
|
480
524
|
this.releaseSemaphore();
|
|
481
525
|
}
|
|
@@ -536,6 +580,29 @@ var init_client = __esm({
|
|
|
536
580
|
would_call: { method, path: fullPath, body: journalBody }
|
|
537
581
|
};
|
|
538
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Turn httpsRequest's raw TIMEOUT rejection into the `{error:true, code, …}`
|
|
585
|
+
* envelope every other failure already speaks, so the agent gets something it
|
|
586
|
+
* can read out to the user and act on rather than a bare Error string. Any
|
|
587
|
+
* other rejection (ECONNRESET, DNS, a mapped 4xx/5xx) passes through untouched
|
|
588
|
+
* — this is a translation, not a catch-all.
|
|
589
|
+
*
|
|
590
|
+
* The code stays "TIMEOUT" so the hosted auth probe's existing branch
|
|
591
|
+
* (auth-http.ts) keeps classifying it as a transient fault and moves to the
|
|
592
|
+
* sibling region instead of declaring a live token expired.
|
|
593
|
+
*/
|
|
594
|
+
mapTransportError(e, endpoint) {
|
|
595
|
+
const err = e;
|
|
596
|
+
if (err?.code !== "TIMEOUT")
|
|
597
|
+
return e;
|
|
598
|
+
const ms = err.timeout_ms ?? defaultTimeoutMs();
|
|
599
|
+
const envelope = this.makeError("TIMEOUT", `Leadbay did not respond within ${ms}ms \u2014 the request was cancelled`, "The connection was accepted but no response came back, so this is a Leadbay-side stall, not a bad request. It is transient: retry the same call once. If it times out again, tell the user Leadbay is not responding right now and offer to report it with leadbay_report_friction.", endpoint);
|
|
600
|
+
if (envelope._meta) {
|
|
601
|
+
envelope._meta.timeout_ms = ms;
|
|
602
|
+
envelope._meta.latency_ms = ms;
|
|
603
|
+
}
|
|
604
|
+
return envelope;
|
|
605
|
+
}
|
|
539
606
|
mapErrorResponse(status, rawBody, endpoint, headers) {
|
|
540
607
|
let parsed;
|
|
541
608
|
try {
|
|
@@ -657,6 +724,8 @@ var init_client = __esm({
|
|
|
657
724
|
this.telemetryEnabledFromStamp = false;
|
|
658
725
|
}
|
|
659
726
|
return observed;
|
|
727
|
+
} catch (e) {
|
|
728
|
+
throw this.mapTransportError(e, "GET /users/me");
|
|
660
729
|
} finally {
|
|
661
730
|
this.releaseSemaphore();
|
|
662
731
|
}
|
|
@@ -26464,6 +26533,7 @@ __export(dist_exports, {
|
|
|
26464
26533
|
AgentMemorySourceSchema: () => AgentMemorySourceSchema,
|
|
26465
26534
|
AgentMemoryTombstoneSchema: () => AgentMemoryTombstoneSchema,
|
|
26466
26535
|
COMPOSITE_FILE_TOOL_NAMES: () => COMPOSITE_FILE_TOOL_NAMES,
|
|
26536
|
+
DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
|
|
26467
26537
|
GETTING_STARTED_MANIFEST: () => GETTING_STARTED_MANIFEST,
|
|
26468
26538
|
InMemoryBulkStore: () => InMemoryBulkStore,
|
|
26469
26539
|
LeadbayClient: () => LeadbayClient,
|
|
@@ -26587,6 +26657,7 @@ __export(dist_exports, {
|
|
|
26587
26657
|
resolveImportRows: () => resolveImportRows,
|
|
26588
26658
|
resolveRegion: () => resolveRegion,
|
|
26589
26659
|
reviseHintFor: () => reviseHintFor,
|
|
26660
|
+
runWithRequestSignal: () => runWithRequestSignal,
|
|
26590
26661
|
scanPortfolioSignals: () => scanPortfolioSignals,
|
|
26591
26662
|
seedCandidates: () => seedCandidates,
|
|
26592
26663
|
selectLeads: () => selectLeads,
|
|
@@ -29837,6 +29908,7 @@ var BUILTIN_WIDGETS_PARAGRAPH = 'Prefer host-native widgets over inline markdown
|
|
|
29837
29908
|
|
|
29838
29909
|
// src/server.ts
|
|
29839
29910
|
init_dist();
|
|
29911
|
+
init_dist();
|
|
29840
29912
|
|
|
29841
29913
|
// src/telemetry.ts
|
|
29842
29914
|
import { PostHog } from "posthog-node";
|
|
@@ -29850,6 +29922,7 @@ var EMBEDDED_SENTRY_DSN = "https://301f1c433433b76132956ed5415bea19@o45058744368
|
|
|
29850
29922
|
// src/telemetry-events.ts
|
|
29851
29923
|
var EV_TOOL_CALL = "mcp tool called";
|
|
29852
29924
|
var EV_QUOTA_HIT = "mcp quota hit";
|
|
29925
|
+
var EV_TOOL_TIMEOUT = "mcp tool timeout";
|
|
29853
29926
|
var EV_TOPUP_LINK = "mcp topup link created";
|
|
29854
29927
|
var EV_STARTUP = "mcp startup";
|
|
29855
29928
|
var EV_MCP_UPDATE_CHECK = "mcp update check";
|
|
@@ -29860,6 +29933,7 @@ var EV_MCP_VERSION_UPDATED = "mcp version updated";
|
|
|
29860
29933
|
var EV_AGENT_MEMORY_CAPTURED = "agent_memory_captured";
|
|
29861
29934
|
var EV_AGENT_MEMORY_RECALLED = "agent_memory_recalled";
|
|
29862
29935
|
var EV_AGENT_MEMORY_PRUNED = "agent_memory_pruned";
|
|
29936
|
+
var DURATION_PLAUSIBILITY_CEILING_MS = 6e5;
|
|
29863
29937
|
var EV_FRICTION_REPORTED = "mcp friction reported";
|
|
29864
29938
|
var EV_COMPOSITE_CALL = "mcp composite call";
|
|
29865
29939
|
|
|
@@ -29873,6 +29947,8 @@ var NOOP_TELEMETRY = {
|
|
|
29873
29947
|
},
|
|
29874
29948
|
captureQuotaHit: (_props, _identity) => {
|
|
29875
29949
|
},
|
|
29950
|
+
captureToolTimeout: (_props, _identity) => {
|
|
29951
|
+
},
|
|
29876
29952
|
captureTopupLink: (_props, _identity) => {
|
|
29877
29953
|
},
|
|
29878
29954
|
captureStartup: (_props, _identity) => {
|
|
@@ -29907,6 +29983,13 @@ function parseTelemetryEnv(raw) {
|
|
|
29907
29983
|
if (v === "false" || v === "0" || v === "no" || v === "off") return false;
|
|
29908
29984
|
return true;
|
|
29909
29985
|
}
|
|
29986
|
+
function withPlausibleDuration(props) {
|
|
29987
|
+
const { duration_ms, ...rest } = props;
|
|
29988
|
+
if (Number.isFinite(duration_ms) && duration_ms >= 0 && duration_ms <= DURATION_PLAUSIBILITY_CEILING_MS) {
|
|
29989
|
+
return { ...rest, duration_ms };
|
|
29990
|
+
}
|
|
29991
|
+
return { ...rest, duration_ms_raw: duration_ms, duration_implausible: true };
|
|
29992
|
+
}
|
|
29910
29993
|
function initTelemetry(opts) {
|
|
29911
29994
|
if (!parseTelemetryEnv(process.env.LEADBAY_TELEMETRY_ENABLED)) return NOOP_TELEMETRY;
|
|
29912
29995
|
if (process.env.NODE_ENV === "test") return NOOP_TELEMETRY;
|
|
@@ -30075,14 +30158,17 @@ function initTelemetry(opts) {
|
|
|
30075
30158
|
return identityPromise;
|
|
30076
30159
|
},
|
|
30077
30160
|
captureToolCall(props, identity) {
|
|
30078
|
-
emit(EV_TOOL_CALL,
|
|
30161
|
+
emit(EV_TOOL_CALL, withPlausibleDuration(props), identity);
|
|
30079
30162
|
},
|
|
30080
30163
|
captureCompositeCall(props, identity) {
|
|
30081
|
-
emit(EV_COMPOSITE_CALL,
|
|
30164
|
+
emit(EV_COMPOSITE_CALL, withPlausibleDuration(props), identity);
|
|
30082
30165
|
},
|
|
30083
30166
|
captureQuotaHit(props, identity) {
|
|
30084
30167
|
emit(EV_QUOTA_HIT, { ...props }, identity);
|
|
30085
30168
|
},
|
|
30169
|
+
captureToolTimeout(props, identity) {
|
|
30170
|
+
emit(EV_TOOL_TIMEOUT, { ...props }, identity);
|
|
30171
|
+
},
|
|
30086
30172
|
captureTopupLink(props, identity) {
|
|
30087
30173
|
emit(EV_TOPUP_LINK, { ...props }, identity);
|
|
30088
30174
|
},
|
|
@@ -30992,6 +31078,16 @@ function buildServer(client, opts = {}) {
|
|
|
30992
31078
|
source: "business"
|
|
30993
31079
|
};
|
|
30994
31080
|
};
|
|
31081
|
+
const captureTimeoutAlert = (toolName, envelope, triggeredBy) => {
|
|
31082
|
+
const ms = envelope._meta?.timeout_ms;
|
|
31083
|
+
telemetry.captureToolTimeout({
|
|
31084
|
+
tool: toolName,
|
|
31085
|
+
...typeof ms === "number" ? { timeout_ms: ms } : {},
|
|
31086
|
+
...envelope._meta?.endpoint ? { endpoint: envelope._meta.endpoint } : {},
|
|
31087
|
+
...envelope._meta?.region ? { region: envelope._meta.region } : {},
|
|
31088
|
+
...triggeredBy !== void 0 ? { triggered_by: triggeredBy } : {}
|
|
31089
|
+
});
|
|
31090
|
+
};
|
|
30995
31091
|
const captureAgentMemoryTelemetry = (toolName, result) => {
|
|
30996
31092
|
if (!result || typeof result !== "object") return;
|
|
30997
31093
|
const meta = result._meta ?? {};
|
|
@@ -31156,7 +31252,7 @@ ${url}
|
|
|
31156
31252
|
isError: true
|
|
31157
31253
|
};
|
|
31158
31254
|
}
|
|
31159
|
-
const result = await tool.execute(client, args, {
|
|
31255
|
+
const result = await runWithRequestSignal(extra.signal, () => tool.execute(client, args, {
|
|
31160
31256
|
logger: opts.logger,
|
|
31161
31257
|
bulkTracker: opts.bulkTracker,
|
|
31162
31258
|
notificationsInbox: opts.notificationsInbox,
|
|
@@ -31189,7 +31285,7 @@ ${url}
|
|
|
31189
31285
|
...report.tool_called ? { tool_called: report.tool_called } : {},
|
|
31190
31286
|
...report.severity ? { severity: report.severity } : {}
|
|
31191
31287
|
}) === true
|
|
31192
|
-
});
|
|
31288
|
+
}));
|
|
31193
31289
|
await maybeAttachUpdate(name, result);
|
|
31194
31290
|
maybeAttachNotifications(result);
|
|
31195
31291
|
if (result && typeof result === "object" && result.error === true) {
|
|
@@ -31205,6 +31301,9 @@ ${url}
|
|
|
31205
31301
|
endpoint: result._meta?.endpoint
|
|
31206
31302
|
});
|
|
31207
31303
|
}
|
|
31304
|
+
if (envCode === "TIMEOUT") {
|
|
31305
|
+
captureTimeoutAlert(name, result, triggered_by);
|
|
31306
|
+
}
|
|
31208
31307
|
telemetry.captureToolCall({
|
|
31209
31308
|
tool: name,
|
|
31210
31309
|
ok: false,
|
|
@@ -31335,6 +31434,9 @@ ${url}
|
|
|
31335
31434
|
endpoint: err._meta?.endpoint
|
|
31336
31435
|
});
|
|
31337
31436
|
}
|
|
31437
|
+
if (!skipAnalytics && err.code === "TIMEOUT") {
|
|
31438
|
+
captureTimeoutAlert(name, err, triggered_by);
|
|
31439
|
+
}
|
|
31338
31440
|
const httpStatus2 = err._meta?.http_status;
|
|
31339
31441
|
if (!skipAnalytics) {
|
|
31340
31442
|
telemetry.captureToolCall({
|
|
@@ -32838,7 +32940,7 @@ var OAUTH_BASE_URLS = {
|
|
|
32838
32940
|
fr: "https://staging.api.leadbay.app"
|
|
32839
32941
|
}
|
|
32840
32942
|
};
|
|
32841
|
-
var VERSION = "0.32.
|
|
32943
|
+
var VERSION = "0.32.1";
|
|
32842
32944
|
var HELP = `
|
|
32843
32945
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
32844
32946
|
|
package/dist/http-server.js
CHANGED
|
@@ -2792,30 +2792,59 @@ function getPrompt(name, args = {}) {
|
|
|
2792
2792
|
|
|
2793
2793
|
// ../core/dist/client.js
|
|
2794
2794
|
import https from "https";
|
|
2795
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2795
2796
|
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
2796
2797
|
import { join } from "path";
|
|
2797
2798
|
var LENS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2798
2799
|
var TASTE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
2799
2800
|
var ME_CACHE_TTL_MS = 60 * 1e3;
|
|
2800
2801
|
var MAX_CONCURRENT = 5;
|
|
2802
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 6e5;
|
|
2803
|
+
function defaultTimeoutMs() {
|
|
2804
|
+
const raw = process.env.LEADBAY_TIMEOUT_MS;
|
|
2805
|
+
if (raw === void 0 || raw.trim() === "")
|
|
2806
|
+
return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2807
|
+
const n = Number(raw);
|
|
2808
|
+
return Number.isFinite(n) ? n : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2809
|
+
}
|
|
2810
|
+
var requestSignalStore = new AsyncLocalStorage();
|
|
2811
|
+
function runWithRequestSignal(signal, fn) {
|
|
2812
|
+
return requestSignalStore.run(signal, fn);
|
|
2813
|
+
}
|
|
2814
|
+
function makeCancelledError(method, url) {
|
|
2815
|
+
const err = new Error(`Request cancelled: ${method} ${url}`);
|
|
2816
|
+
err.name = "AbortError";
|
|
2817
|
+
err.code = "CANCELLED";
|
|
2818
|
+
return err;
|
|
2819
|
+
}
|
|
2801
2820
|
var REGIONS = {
|
|
2802
2821
|
us: "https://api-us.leadbay.app",
|
|
2803
2822
|
fr: "https://api-fr.leadbay.app"
|
|
2804
2823
|
};
|
|
2805
2824
|
var API_VERSION = "1.6";
|
|
2806
2825
|
var API_PREFIX = `/${API_VERSION}`;
|
|
2807
|
-
function httpsRequest(method, url, headers, body, timeoutMs) {
|
|
2826
|
+
function httpsRequest(method, url, headers, body, timeoutMs, signal) {
|
|
2827
|
+
const deadlineMs = timeoutMs ?? defaultTimeoutMs();
|
|
2828
|
+
const abortSignal = signal ?? requestSignalStore.getStore();
|
|
2829
|
+
const abortSafe = method.toUpperCase() === "GET";
|
|
2808
2830
|
return new Promise((resolve, reject) => {
|
|
2809
2831
|
const start = Date.now();
|
|
2832
|
+
if (abortSignal?.aborted) {
|
|
2833
|
+
reject(makeCancelledError(method, url));
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2810
2836
|
const parsed = new URL(url);
|
|
2811
2837
|
const reqHeaders = { ...headers };
|
|
2812
2838
|
if (body !== void 0) {
|
|
2813
2839
|
reqHeaders["Content-Length"] = Buffer.byteLength(body);
|
|
2814
2840
|
}
|
|
2815
2841
|
let deadline;
|
|
2842
|
+
let onAbort;
|
|
2816
2843
|
const clearDeadline = () => {
|
|
2817
2844
|
if (deadline !== void 0)
|
|
2818
2845
|
clearTimeout(deadline);
|
|
2846
|
+
if (onAbort)
|
|
2847
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2819
2848
|
};
|
|
2820
2849
|
const req = https.request({
|
|
2821
2850
|
hostname: parsed.hostname,
|
|
@@ -2836,15 +2865,24 @@ function httpsRequest(method, url, headers, body, timeoutMs) {
|
|
|
2836
2865
|
});
|
|
2837
2866
|
});
|
|
2838
2867
|
});
|
|
2839
|
-
if (
|
|
2868
|
+
if (deadlineMs > 0) {
|
|
2840
2869
|
deadline = setTimeout(() => {
|
|
2841
2870
|
req.destroy?.();
|
|
2842
|
-
const err = new Error(`Request timed out after ${
|
|
2871
|
+
const err = new Error(`Request timed out after ${deadlineMs}ms: ${method} ${url}`);
|
|
2843
2872
|
err.code = "TIMEOUT";
|
|
2873
|
+
err.timeout_ms = deadlineMs;
|
|
2844
2874
|
reject(err);
|
|
2845
|
-
},
|
|
2875
|
+
}, deadlineMs);
|
|
2846
2876
|
deadline.unref?.();
|
|
2847
2877
|
}
|
|
2878
|
+
if (abortSignal && abortSafe) {
|
|
2879
|
+
onAbort = () => {
|
|
2880
|
+
req.destroy?.();
|
|
2881
|
+
clearDeadline();
|
|
2882
|
+
reject(makeCancelledError(method, url));
|
|
2883
|
+
};
|
|
2884
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
2885
|
+
}
|
|
2848
2886
|
req.on("error", (e) => {
|
|
2849
2887
|
clearDeadline();
|
|
2850
2888
|
reject(e);
|
|
@@ -3166,6 +3204,8 @@ var LeadbayClient = class _LeadbayClient {
|
|
|
3166
3204
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
3167
3205
|
}
|
|
3168
3206
|
return JSON.parse(res.body);
|
|
3207
|
+
} catch (e) {
|
|
3208
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
3169
3209
|
} finally {
|
|
3170
3210
|
this.releaseSemaphore();
|
|
3171
3211
|
}
|
|
@@ -3197,6 +3237,8 @@ var LeadbayClient = class _LeadbayClient {
|
|
|
3197
3237
|
if (res.status < 200 || res.status >= 300) {
|
|
3198
3238
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
3199
3239
|
}
|
|
3240
|
+
} catch (e) {
|
|
3241
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
3200
3242
|
} finally {
|
|
3201
3243
|
this.releaseSemaphore();
|
|
3202
3244
|
}
|
|
@@ -3234,6 +3276,8 @@ var LeadbayClient = class _LeadbayClient {
|
|
|
3234
3276
|
throw this.mapErrorResponse(res.status, res.body, path, res.headers);
|
|
3235
3277
|
}
|
|
3236
3278
|
return JSON.parse(res.body);
|
|
3279
|
+
} catch (e) {
|
|
3280
|
+
throw this.mapTransportError(e, `${method} ${path}`);
|
|
3237
3281
|
} finally {
|
|
3238
3282
|
this.releaseSemaphore();
|
|
3239
3283
|
}
|
|
@@ -3294,6 +3338,29 @@ var LeadbayClient = class _LeadbayClient {
|
|
|
3294
3338
|
would_call: { method, path: fullPath, body: journalBody }
|
|
3295
3339
|
};
|
|
3296
3340
|
}
|
|
3341
|
+
/**
|
|
3342
|
+
* Turn httpsRequest's raw TIMEOUT rejection into the `{error:true, code, …}`
|
|
3343
|
+
* envelope every other failure already speaks, so the agent gets something it
|
|
3344
|
+
* can read out to the user and act on rather than a bare Error string. Any
|
|
3345
|
+
* other rejection (ECONNRESET, DNS, a mapped 4xx/5xx) passes through untouched
|
|
3346
|
+
* — this is a translation, not a catch-all.
|
|
3347
|
+
*
|
|
3348
|
+
* The code stays "TIMEOUT" so the hosted auth probe's existing branch
|
|
3349
|
+
* (auth-http.ts) keeps classifying it as a transient fault and moves to the
|
|
3350
|
+
* sibling region instead of declaring a live token expired.
|
|
3351
|
+
*/
|
|
3352
|
+
mapTransportError(e, endpoint) {
|
|
3353
|
+
const err = e;
|
|
3354
|
+
if (err?.code !== "TIMEOUT")
|
|
3355
|
+
return e;
|
|
3356
|
+
const ms = err.timeout_ms ?? defaultTimeoutMs();
|
|
3357
|
+
const envelope = this.makeError("TIMEOUT", `Leadbay did not respond within ${ms}ms \u2014 the request was cancelled`, "The connection was accepted but no response came back, so this is a Leadbay-side stall, not a bad request. It is transient: retry the same call once. If it times out again, tell the user Leadbay is not responding right now and offer to report it with leadbay_report_friction.", endpoint);
|
|
3358
|
+
if (envelope._meta) {
|
|
3359
|
+
envelope._meta.timeout_ms = ms;
|
|
3360
|
+
envelope._meta.latency_ms = ms;
|
|
3361
|
+
}
|
|
3362
|
+
return envelope;
|
|
3363
|
+
}
|
|
3297
3364
|
mapErrorResponse(status, rawBody, endpoint, headers) {
|
|
3298
3365
|
let parsed;
|
|
3299
3366
|
try {
|
|
@@ -3415,6 +3482,8 @@ var LeadbayClient = class _LeadbayClient {
|
|
|
3415
3482
|
this.telemetryEnabledFromStamp = false;
|
|
3416
3483
|
}
|
|
3417
3484
|
return observed;
|
|
3485
|
+
} catch (e) {
|
|
3486
|
+
throw this.mapTransportError(e, "GET /users/me");
|
|
3418
3487
|
} finally {
|
|
3419
3488
|
this.releaseSemaphore();
|
|
3420
3489
|
}
|
|
@@ -27663,6 +27732,7 @@ var EMBEDDED_SENTRY_DSN = "https://301f1c433433b76132956ed5415bea19@o45058744368
|
|
|
27663
27732
|
// src/telemetry-events.ts
|
|
27664
27733
|
var EV_TOOL_CALL = "mcp tool called";
|
|
27665
27734
|
var EV_QUOTA_HIT = "mcp quota hit";
|
|
27735
|
+
var EV_TOOL_TIMEOUT = "mcp tool timeout";
|
|
27666
27736
|
var EV_TOPUP_LINK = "mcp topup link created";
|
|
27667
27737
|
var EV_STARTUP = "mcp startup";
|
|
27668
27738
|
var EV_MCP_UPDATE_CHECK = "mcp update check";
|
|
@@ -27673,6 +27743,7 @@ var EV_MCP_VERSION_UPDATED = "mcp version updated";
|
|
|
27673
27743
|
var EV_AGENT_MEMORY_CAPTURED = "agent_memory_captured";
|
|
27674
27744
|
var EV_AGENT_MEMORY_RECALLED = "agent_memory_recalled";
|
|
27675
27745
|
var EV_AGENT_MEMORY_PRUNED = "agent_memory_pruned";
|
|
27746
|
+
var DURATION_PLAUSIBILITY_CEILING_MS = 6e5;
|
|
27676
27747
|
var EV_FRICTION_REPORTED = "mcp friction reported";
|
|
27677
27748
|
var EV_COMPOSITE_CALL = "mcp composite call";
|
|
27678
27749
|
|
|
@@ -27686,6 +27757,8 @@ var NOOP_TELEMETRY = {
|
|
|
27686
27757
|
},
|
|
27687
27758
|
captureQuotaHit: (_props, _identity) => {
|
|
27688
27759
|
},
|
|
27760
|
+
captureToolTimeout: (_props, _identity) => {
|
|
27761
|
+
},
|
|
27689
27762
|
captureTopupLink: (_props, _identity) => {
|
|
27690
27763
|
},
|
|
27691
27764
|
captureStartup: (_props, _identity) => {
|
|
@@ -27720,6 +27793,13 @@ function parseTelemetryEnv(raw) {
|
|
|
27720
27793
|
if (v === "false" || v === "0" || v === "no" || v === "off") return false;
|
|
27721
27794
|
return true;
|
|
27722
27795
|
}
|
|
27796
|
+
function withPlausibleDuration(props) {
|
|
27797
|
+
const { duration_ms, ...rest } = props;
|
|
27798
|
+
if (Number.isFinite(duration_ms) && duration_ms >= 0 && duration_ms <= DURATION_PLAUSIBILITY_CEILING_MS) {
|
|
27799
|
+
return { ...rest, duration_ms };
|
|
27800
|
+
}
|
|
27801
|
+
return { ...rest, duration_ms_raw: duration_ms, duration_implausible: true };
|
|
27802
|
+
}
|
|
27723
27803
|
function initTelemetry(opts) {
|
|
27724
27804
|
if (!parseTelemetryEnv(process.env.LEADBAY_TELEMETRY_ENABLED)) return NOOP_TELEMETRY;
|
|
27725
27805
|
if (process.env.NODE_ENV === "test") return NOOP_TELEMETRY;
|
|
@@ -27888,14 +27968,17 @@ function initTelemetry(opts) {
|
|
|
27888
27968
|
return identityPromise;
|
|
27889
27969
|
},
|
|
27890
27970
|
captureToolCall(props, identity) {
|
|
27891
|
-
emit(EV_TOOL_CALL,
|
|
27971
|
+
emit(EV_TOOL_CALL, withPlausibleDuration(props), identity);
|
|
27892
27972
|
},
|
|
27893
27973
|
captureCompositeCall(props, identity) {
|
|
27894
|
-
emit(EV_COMPOSITE_CALL,
|
|
27974
|
+
emit(EV_COMPOSITE_CALL, withPlausibleDuration(props), identity);
|
|
27895
27975
|
},
|
|
27896
27976
|
captureQuotaHit(props, identity) {
|
|
27897
27977
|
emit(EV_QUOTA_HIT, { ...props }, identity);
|
|
27898
27978
|
},
|
|
27979
|
+
captureToolTimeout(props, identity) {
|
|
27980
|
+
emit(EV_TOOL_TIMEOUT, { ...props }, identity);
|
|
27981
|
+
},
|
|
27899
27982
|
captureTopupLink(props, identity) {
|
|
27900
27983
|
emit(EV_TOPUP_LINK, { ...props }, identity);
|
|
27901
27984
|
},
|
|
@@ -28783,6 +28866,16 @@ function buildServer(client, opts = {}) {
|
|
|
28783
28866
|
source: "business"
|
|
28784
28867
|
};
|
|
28785
28868
|
};
|
|
28869
|
+
const captureTimeoutAlert = (toolName, envelope, triggeredBy) => {
|
|
28870
|
+
const ms = envelope._meta?.timeout_ms;
|
|
28871
|
+
telemetry2.captureToolTimeout({
|
|
28872
|
+
tool: toolName,
|
|
28873
|
+
...typeof ms === "number" ? { timeout_ms: ms } : {},
|
|
28874
|
+
...envelope._meta?.endpoint ? { endpoint: envelope._meta.endpoint } : {},
|
|
28875
|
+
...envelope._meta?.region ? { region: envelope._meta.region } : {},
|
|
28876
|
+
...triggeredBy !== void 0 ? { triggered_by: triggeredBy } : {}
|
|
28877
|
+
});
|
|
28878
|
+
};
|
|
28786
28879
|
const captureAgentMemoryTelemetry = (toolName, result) => {
|
|
28787
28880
|
if (!result || typeof result !== "object") return;
|
|
28788
28881
|
const meta = result._meta ?? {};
|
|
@@ -28947,7 +29040,7 @@ ${url}
|
|
|
28947
29040
|
isError: true
|
|
28948
29041
|
};
|
|
28949
29042
|
}
|
|
28950
|
-
const result = await tool.execute(client, args, {
|
|
29043
|
+
const result = await runWithRequestSignal(extra.signal, () => tool.execute(client, args, {
|
|
28951
29044
|
logger: opts.logger,
|
|
28952
29045
|
bulkTracker: opts.bulkTracker,
|
|
28953
29046
|
notificationsInbox: opts.notificationsInbox,
|
|
@@ -28980,7 +29073,7 @@ ${url}
|
|
|
28980
29073
|
...report.tool_called ? { tool_called: report.tool_called } : {},
|
|
28981
29074
|
...report.severity ? { severity: report.severity } : {}
|
|
28982
29075
|
}) === true
|
|
28983
|
-
});
|
|
29076
|
+
}));
|
|
28984
29077
|
await maybeAttachUpdate(name, result);
|
|
28985
29078
|
maybeAttachNotifications(result);
|
|
28986
29079
|
if (result && typeof result === "object" && result.error === true) {
|
|
@@ -28996,6 +29089,9 @@ ${url}
|
|
|
28996
29089
|
endpoint: result._meta?.endpoint
|
|
28997
29090
|
});
|
|
28998
29091
|
}
|
|
29092
|
+
if (envCode === "TIMEOUT") {
|
|
29093
|
+
captureTimeoutAlert(name, result, triggered_by);
|
|
29094
|
+
}
|
|
28999
29095
|
telemetry2.captureToolCall({
|
|
29000
29096
|
tool: name,
|
|
29001
29097
|
ok: false,
|
|
@@ -29126,6 +29222,9 @@ ${url}
|
|
|
29126
29222
|
endpoint: err._meta?.endpoint
|
|
29127
29223
|
});
|
|
29128
29224
|
}
|
|
29225
|
+
if (!skipAnalytics && err.code === "TIMEOUT") {
|
|
29226
|
+
captureTimeoutAlert(name, err, triggered_by);
|
|
29227
|
+
}
|
|
29129
29228
|
const httpStatus2 = err._meta?.http_status;
|
|
29130
29229
|
if (!skipAnalytics) {
|
|
29131
29230
|
telemetry2.captureToolCall({
|
|
@@ -29374,7 +29473,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
29374
29473
|
}
|
|
29375
29474
|
|
|
29376
29475
|
// src/http-server.ts
|
|
29377
|
-
var VERSION = true ? "0.32.
|
|
29476
|
+
var VERSION = true ? "0.32.1" : "0.0.0-dev";
|
|
29378
29477
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
29379
29478
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
29380
29479
|
var logger = {
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.1",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|