@leadbay/mcp 0.32.0 → 0.32.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.32.4 — 2026-09-01
4
+
5
+ `leadbay_account_status.notifications` was permanently `[]` on the hosted server
6
+ (product#4009). Same shape as product#4005: `buildServerFromClient` builds every
7
+ hosted server without a `notificationsInbox`, `BuildServerOptions` makes it
8
+ optional, so it compiled and failed silently.
9
+
10
+ **Not fixed by wiring the inbox, because the inbox cannot exist there.** It is
11
+ fed by a WS listener whose ticket (`GET /auth/ws`) is per-account, which is
12
+ meaningless on a multi-tenant process, and the streamable transport builds a
13
+ fresh `Server` per request so nothing would survive to be cached. Porting it
14
+ would be the same mistake in a third place.
15
+
16
+ `GET /notifications` is already per-account and already durable. On hosted the
17
+ ledger IS the inbox, so `account_status` reads it on demand when no inbox is
18
+ wired. Stdio is unchanged and still free — the WS listener already has the
19
+ answer, and the ledger is not called.
20
+
21
+ - `fetchTerminalNotifications(client)` in `notifications/catch-up.ts` returns
22
+ terminal, unseen entries directly. `isTerminalUnseen` is extracted so inbox
23
+ seeding and the inbox-less read cannot disagree about what counts.
24
+ - A notifications failure resolves to `[]` rather than throwing. `account_status`
25
+ is the daily entry point; a ledger hiccup must not take the check-in down.
26
+ - **Deliberately not done: `_meta.notifications` still does not ride hosted tool
27
+ responses.** Reviving it would mean a `GET /notifications` per tool call to
28
+ decorate every response. The cost lands on the check-in entry point only,
29
+ which is where the daily-rhythm channel is actually read.
30
+
3
31
  ## 0.32.0 — 2026-09-01
4
32
 
5
33
  A poll-budget timeout stops being an error (product#4007). The import wizard's
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 | (client default) | Per-request timeout override |
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 httpsRequest(method, url, headers, body, timeoutMs) {
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 (timeoutMs !== void 0 && timeoutMs > 0) {
75
+ if (deadlineMs > 0) {
49
76
  deadline = setTimeout(() => {
50
77
  req.destroy?.();
51
- const err = new Error(`Request timed out after ${timeoutMs}ms: ${method} ${url}`);
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
- }, timeoutMs);
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
  }
@@ -5499,6 +5568,28 @@ var init_inbox = __esm({
5499
5568
  });
5500
5569
 
5501
5570
  // ../core/dist/notifications/catch-up.js
5571
+ function isTerminalUnseen(n) {
5572
+ if (!n.bulk_progress)
5573
+ return false;
5574
+ if (n.in_progress)
5575
+ return false;
5576
+ if (n.first_seen_at)
5577
+ return false;
5578
+ return true;
5579
+ }
5580
+ async function fetchTerminalNotifications(client, opts = {}) {
5581
+ try {
5582
+ const page = await client.listNotifications({
5583
+ archived: false,
5584
+ page: 0,
5585
+ count: opts.count ?? DEFAULT_COUNT
5586
+ });
5587
+ return page.items.filter(isTerminalUnseen).map(toInboxEntry);
5588
+ } catch (err) {
5589
+ opts.logger?.warn?.(`notifications.fetch_terminal failed: ${err?.message ?? err?.code ?? err}`);
5590
+ return [];
5591
+ }
5592
+ }
5502
5593
  async function catchUpNotifications(client, inbox, opts = {}) {
5503
5594
  const count = opts.count ?? DEFAULT_COUNT;
5504
5595
  let added = 0;
@@ -5509,11 +5600,7 @@ async function catchUpNotifications(client, inbox, opts = {}) {
5509
5600
  count
5510
5601
  });
5511
5602
  for (const n of page.items) {
5512
- if (!n.bulk_progress)
5513
- continue;
5514
- if (n.in_progress)
5515
- continue;
5516
- if (n.first_seen_at)
5603
+ if (!isTerminalUnseen(n))
5517
5604
  continue;
5518
5605
  const sizeBefore = inbox.size();
5519
5606
  inbox.record(n);
@@ -5530,6 +5617,7 @@ var DEFAULT_COUNT;
5530
5617
  var init_catch_up = __esm({
5531
5618
  "../core/dist/notifications/catch-up.js"() {
5532
5619
  "use strict";
5620
+ init_revise_hint();
5533
5621
  DEFAULT_COUNT = 50;
5534
5622
  }
5535
5623
  });
@@ -20649,6 +20737,7 @@ var init_account_status = __esm({
20649
20737
  "../core/dist/composite/account-status.js"() {
20650
20738
  "use strict";
20651
20739
  init_agent_memory();
20740
+ init_catch_up();
20652
20741
  init_credits_helpers();
20653
20742
  init_tool_descriptions_generated();
20654
20743
  accountStatus = {
@@ -20770,6 +20859,8 @@ var init_account_status = __esm({
20770
20859
  ctx?.logger?.warn?.(`account_status: quota_status failed: ${err?.message ?? err?.code ?? err}`);
20771
20860
  }
20772
20861
  }
20862
+ const inbox = ctx?.notificationsInbox;
20863
+ const notifications = inbox ? inbox.list() : await fetchTerminalNotifications(client, { logger: ctx?.logger });
20773
20864
  const lensId = me.last_requested_lens ?? null;
20774
20865
  const lensAsked = typeof ctx?.triggered_by === "string" && /\b(lens|lenses|audience|targeting|segment|filter)\b/i.test(ctx.triggered_by);
20775
20866
  let last_requested_lens_name = null;
@@ -20810,13 +20901,12 @@ var init_account_status = __esm({
20810
20901
  // on /me are intentionally NOT surfaced — they're defunct (see
20811
20902
  // SHAPE-DRIFT.md probe round 4).
20812
20903
  quota,
20813
- // Inbox of terminal bulk-progress notifications. Same shape the MCP
20814
- // server attaches to `_meta.notifications` on every tool response —
20815
- // duplicated here as a top-level field so the agent's daily-rhythm
20816
- // check-in (this composite) sees them without having to read _meta.
20817
- // Empty array when the WS listener isn't wired (OpenClaw, tests) OR
20818
- // when nothing has completed since the last ack.
20819
- notifications: ctx?.notificationsInbox?.list() ?? [],
20904
+ // Terminal bulk-progress notifications. Same shape the MCP server
20905
+ // attaches to `_meta.notifications` on every tool response — carried
20906
+ // here as a top-level field so the agent's daily-rhythm check-in (this
20907
+ // composite) sees them without reading _meta. See the read above for
20908
+ // why hosted takes a different path to the same list.
20909
+ notifications,
20820
20910
  // Non-null ONLY when the quota_status call failed. The agent must treat
20821
20911
  // this as "could not read quota" — NOT as zero usage, and NOT as a broken
20822
20912
  // login (the token just authenticated /users/me above). product#3761.
@@ -26464,6 +26554,7 @@ __export(dist_exports, {
26464
26554
  AgentMemorySourceSchema: () => AgentMemorySourceSchema,
26465
26555
  AgentMemoryTombstoneSchema: () => AgentMemoryTombstoneSchema,
26466
26556
  COMPOSITE_FILE_TOOL_NAMES: () => COMPOSITE_FILE_TOOL_NAMES,
26557
+ DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
26467
26558
  GETTING_STARTED_MANIFEST: () => GETTING_STARTED_MANIFEST,
26468
26559
  InMemoryBulkStore: () => InMemoryBulkStore,
26469
26560
  LeadbayClient: () => LeadbayClient,
@@ -26587,6 +26678,7 @@ __export(dist_exports, {
26587
26678
  resolveImportRows: () => resolveImportRows,
26588
26679
  resolveRegion: () => resolveRegion,
26589
26680
  reviseHintFor: () => reviseHintFor,
26681
+ runWithRequestSignal: () => runWithRequestSignal,
26590
26682
  scanPortfolioSignals: () => scanPortfolioSignals,
26591
26683
  seedCandidates: () => seedCandidates,
26592
26684
  selectLeads: () => selectLeads,
@@ -29837,6 +29929,7 @@ var BUILTIN_WIDGETS_PARAGRAPH = 'Prefer host-native widgets over inline markdown
29837
29929
 
29838
29930
  // src/server.ts
29839
29931
  init_dist();
29932
+ init_dist();
29840
29933
 
29841
29934
  // src/telemetry.ts
29842
29935
  import { PostHog } from "posthog-node";
@@ -29850,6 +29943,7 @@ var EMBEDDED_SENTRY_DSN = "https://301f1c433433b76132956ed5415bea19@o45058744368
29850
29943
  // src/telemetry-events.ts
29851
29944
  var EV_TOOL_CALL = "mcp tool called";
29852
29945
  var EV_QUOTA_HIT = "mcp quota hit";
29946
+ var EV_TOOL_TIMEOUT = "mcp tool timeout";
29853
29947
  var EV_TOPUP_LINK = "mcp topup link created";
29854
29948
  var EV_STARTUP = "mcp startup";
29855
29949
  var EV_MCP_UPDATE_CHECK = "mcp update check";
@@ -29860,6 +29954,7 @@ var EV_MCP_VERSION_UPDATED = "mcp version updated";
29860
29954
  var EV_AGENT_MEMORY_CAPTURED = "agent_memory_captured";
29861
29955
  var EV_AGENT_MEMORY_RECALLED = "agent_memory_recalled";
29862
29956
  var EV_AGENT_MEMORY_PRUNED = "agent_memory_pruned";
29957
+ var DURATION_PLAUSIBILITY_CEILING_MS = 6e5;
29863
29958
  var EV_FRICTION_REPORTED = "mcp friction reported";
29864
29959
  var EV_COMPOSITE_CALL = "mcp composite call";
29865
29960
 
@@ -29873,6 +29968,8 @@ var NOOP_TELEMETRY = {
29873
29968
  },
29874
29969
  captureQuotaHit: (_props, _identity) => {
29875
29970
  },
29971
+ captureToolTimeout: (_props, _identity) => {
29972
+ },
29876
29973
  captureTopupLink: (_props, _identity) => {
29877
29974
  },
29878
29975
  captureStartup: (_props, _identity) => {
@@ -29907,6 +30004,13 @@ function parseTelemetryEnv(raw) {
29907
30004
  if (v === "false" || v === "0" || v === "no" || v === "off") return false;
29908
30005
  return true;
29909
30006
  }
30007
+ function withPlausibleDuration(props) {
30008
+ const { duration_ms, ...rest } = props;
30009
+ if (Number.isFinite(duration_ms) && duration_ms >= 0 && duration_ms <= DURATION_PLAUSIBILITY_CEILING_MS) {
30010
+ return { ...rest, duration_ms };
30011
+ }
30012
+ return { ...rest, duration_ms_raw: duration_ms, duration_implausible: true };
30013
+ }
29910
30014
  function initTelemetry(opts) {
29911
30015
  if (!parseTelemetryEnv(process.env.LEADBAY_TELEMETRY_ENABLED)) return NOOP_TELEMETRY;
29912
30016
  if (process.env.NODE_ENV === "test") return NOOP_TELEMETRY;
@@ -30075,14 +30179,17 @@ function initTelemetry(opts) {
30075
30179
  return identityPromise;
30076
30180
  },
30077
30181
  captureToolCall(props, identity) {
30078
- emit(EV_TOOL_CALL, { ...props }, identity);
30182
+ emit(EV_TOOL_CALL, withPlausibleDuration(props), identity);
30079
30183
  },
30080
30184
  captureCompositeCall(props, identity) {
30081
- emit(EV_COMPOSITE_CALL, { ...props }, identity);
30185
+ emit(EV_COMPOSITE_CALL, withPlausibleDuration(props), identity);
30082
30186
  },
30083
30187
  captureQuotaHit(props, identity) {
30084
30188
  emit(EV_QUOTA_HIT, { ...props }, identity);
30085
30189
  },
30190
+ captureToolTimeout(props, identity) {
30191
+ emit(EV_TOOL_TIMEOUT, { ...props }, identity);
30192
+ },
30086
30193
  captureTopupLink(props, identity) {
30087
30194
  emit(EV_TOPUP_LINK, { ...props }, identity);
30088
30195
  },
@@ -30992,6 +31099,16 @@ function buildServer(client, opts = {}) {
30992
31099
  source: "business"
30993
31100
  };
30994
31101
  };
31102
+ const captureTimeoutAlert = (toolName, envelope, triggeredBy) => {
31103
+ const ms = envelope._meta?.timeout_ms;
31104
+ telemetry.captureToolTimeout({
31105
+ tool: toolName,
31106
+ ...typeof ms === "number" ? { timeout_ms: ms } : {},
31107
+ ...envelope._meta?.endpoint ? { endpoint: envelope._meta.endpoint } : {},
31108
+ ...envelope._meta?.region ? { region: envelope._meta.region } : {},
31109
+ ...triggeredBy !== void 0 ? { triggered_by: triggeredBy } : {}
31110
+ });
31111
+ };
30995
31112
  const captureAgentMemoryTelemetry = (toolName, result) => {
30996
31113
  if (!result || typeof result !== "object") return;
30997
31114
  const meta = result._meta ?? {};
@@ -31156,7 +31273,7 @@ ${url}
31156
31273
  isError: true
31157
31274
  };
31158
31275
  }
31159
- const result = await tool.execute(client, args, {
31276
+ const result = await runWithRequestSignal(extra.signal, () => tool.execute(client, args, {
31160
31277
  logger: opts.logger,
31161
31278
  bulkTracker: opts.bulkTracker,
31162
31279
  notificationsInbox: opts.notificationsInbox,
@@ -31189,7 +31306,7 @@ ${url}
31189
31306
  ...report.tool_called ? { tool_called: report.tool_called } : {},
31190
31307
  ...report.severity ? { severity: report.severity } : {}
31191
31308
  }) === true
31192
- });
31309
+ }));
31193
31310
  await maybeAttachUpdate(name, result);
31194
31311
  maybeAttachNotifications(result);
31195
31312
  if (result && typeof result === "object" && result.error === true) {
@@ -31205,6 +31322,9 @@ ${url}
31205
31322
  endpoint: result._meta?.endpoint
31206
31323
  });
31207
31324
  }
31325
+ if (envCode === "TIMEOUT") {
31326
+ captureTimeoutAlert(name, result, triggered_by);
31327
+ }
31208
31328
  telemetry.captureToolCall({
31209
31329
  tool: name,
31210
31330
  ok: false,
@@ -31335,6 +31455,9 @@ ${url}
31335
31455
  endpoint: err._meta?.endpoint
31336
31456
  });
31337
31457
  }
31458
+ if (!skipAnalytics && err.code === "TIMEOUT") {
31459
+ captureTimeoutAlert(name, err, triggered_by);
31460
+ }
31338
31461
  const httpStatus2 = err._meta?.http_status;
31339
31462
  if (!skipAnalytics) {
31340
31463
  telemetry.captureToolCall({
@@ -32838,7 +32961,7 @@ var OAUTH_BASE_URLS = {
32838
32961
  fr: "https://staging.api.leadbay.app"
32839
32962
  }
32840
32963
  };
32841
- var VERSION = "0.32.0";
32964
+ var VERSION = "0.32.4";
32842
32965
  var HELP = `
32843
32966
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
32844
32967
 
@@ -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 (timeoutMs !== void 0 && timeoutMs > 0) {
2868
+ if (deadlineMs > 0) {
2840
2869
  deadline = setTimeout(() => {
2841
2870
  req.destroy?.();
2842
- const err = new Error(`Request timed out after ${timeoutMs}ms: ${method} ${url}`);
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
- }, timeoutMs);
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
  }
@@ -8032,9 +8101,82 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
8032
8101
  "leadbay_tour_plan"
8033
8102
  ]);
8034
8103
 
8104
+ // ../core/dist/notifications/revise-hint.js
8105
+ var HINT_BULK_ENRICH = "Contact enrichment just finished. Revise any prior output that named these leads' contacts (outreach drafts, contact lists, recommended-lead lists with contact_count, NEXT STEPS asking the user to wait for emails / phones). Re-fetch contacts via leadbay_get_contacts for the affected leads.";
8106
+ var HINT_BULK_QUALIFY = "Lead qualification just finished. Revise any prior lead list / ranking / outreach shortlist that depended on ai_agent_lead_score for these leads \u2014 today's leads, top-of-inbox, followups maps, prepare-outreach shortlists. Re-pull qualification answers via leadbay_research_lead_by_id or re-rank via leadbay_pull_leads.";
8107
+ var HINT_IMPORT = "CSV / CRM import just finished. Revise any prior output that referenced 'leads available' before the import landed \u2014 lead lists pulled from the affected lens, 'what's new today', followup planning. Re-pull the affected lens via leadbay_pull_leads / leadbay_pull_followups.";
8108
+ var HINT_OTHER = "Background work just completed. If you referenced its subject in prior output, re-fetch the affected data and revise.";
8109
+ function reviseHintFor(kind) {
8110
+ switch (kind) {
8111
+ case "bulk_enrich":
8112
+ return HINT_BULK_ENRICH;
8113
+ case "bulk_qualify":
8114
+ return HINT_BULK_QUALIFY;
8115
+ case "import":
8116
+ return HINT_IMPORT;
8117
+ default:
8118
+ return HINT_OTHER;
8119
+ }
8120
+ }
8121
+ function inferKind(n) {
8122
+ if (n.links.some((l) => l.type === "bulk_enrichment"))
8123
+ return "bulk_enrich";
8124
+ if (n.file_import_id)
8125
+ return "import";
8126
+ if (n.bulk_progress)
8127
+ return "bulk_qualify";
8128
+ return "other";
8129
+ }
8130
+ function anchorIdFor(n, kind) {
8131
+ if (kind === "bulk_enrich") {
8132
+ const link = n.links.find((l) => l.type === "bulk_enrichment");
8133
+ return link ? String(link.id) : null;
8134
+ }
8135
+ if (kind === "import")
8136
+ return n.file_import_id;
8137
+ return null;
8138
+ }
8139
+ function toInboxEntry(n) {
8140
+ const kind = inferKind(n);
8141
+ return {
8142
+ notification_id: n.id,
8143
+ kind,
8144
+ anchor_id: anchorIdFor(n, kind),
8145
+ title: n.title,
8146
+ bulk_progress: n.bulk_progress,
8147
+ completed_at: n.updated_at,
8148
+ revise_hint: reviseHintFor(kind)
8149
+ };
8150
+ }
8151
+
8035
8152
  // ../core/dist/notifications/inbox.js
8036
8153
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
8037
8154
 
8155
+ // ../core/dist/notifications/catch-up.js
8156
+ var DEFAULT_COUNT = 50;
8157
+ function isTerminalUnseen(n) {
8158
+ if (!n.bulk_progress)
8159
+ return false;
8160
+ if (n.in_progress)
8161
+ return false;
8162
+ if (n.first_seen_at)
8163
+ return false;
8164
+ return true;
8165
+ }
8166
+ async function fetchTerminalNotifications(client, opts = {}) {
8167
+ try {
8168
+ const page = await client.listNotifications({
8169
+ archived: false,
8170
+ page: 0,
8171
+ count: opts.count ?? DEFAULT_COUNT
8172
+ });
8173
+ return page.items.filter(isTerminalUnseen).map(toInboxEntry);
8174
+ } catch (err) {
8175
+ opts.logger?.warn?.(`notifications.fetch_terminal failed: ${err?.message ?? err?.code ?? err}`);
8176
+ return [];
8177
+ }
8178
+ }
8179
+
8038
8180
  // ../core/dist/tool-descriptions.generated.js
8039
8181
  var leadbay_account_history = `## WHEN TO USE
8040
8182
 
@@ -22421,6 +22563,8 @@ var accountStatus = {
22421
22563
  ctx?.logger?.warn?.(`account_status: quota_status failed: ${err?.message ?? err?.code ?? err}`);
22422
22564
  }
22423
22565
  }
22566
+ const inbox = ctx?.notificationsInbox;
22567
+ const notifications = inbox ? inbox.list() : await fetchTerminalNotifications(client, { logger: ctx?.logger });
22424
22568
  const lensId = me.last_requested_lens ?? null;
22425
22569
  const lensAsked = typeof ctx?.triggered_by === "string" && /\b(lens|lenses|audience|targeting|segment|filter)\b/i.test(ctx.triggered_by);
22426
22570
  let last_requested_lens_name = null;
@@ -22461,13 +22605,12 @@ var accountStatus = {
22461
22605
  // on /me are intentionally NOT surfaced — they're defunct (see
22462
22606
  // SHAPE-DRIFT.md probe round 4).
22463
22607
  quota,
22464
- // Inbox of terminal bulk-progress notifications. Same shape the MCP
22465
- // server attaches to `_meta.notifications` on every tool response —
22466
- // duplicated here as a top-level field so the agent's daily-rhythm
22467
- // check-in (this composite) sees them without having to read _meta.
22468
- // Empty array when the WS listener isn't wired (OpenClaw, tests) OR
22469
- // when nothing has completed since the last ack.
22470
- notifications: ctx?.notificationsInbox?.list() ?? [],
22608
+ // Terminal bulk-progress notifications. Same shape the MCP server
22609
+ // attaches to `_meta.notifications` on every tool response — carried
22610
+ // here as a top-level field so the agent's daily-rhythm check-in (this
22611
+ // composite) sees them without reading _meta. See the read above for
22612
+ // why hosted takes a different path to the same list.
22613
+ notifications,
22471
22614
  // Non-null ONLY when the quota_status call failed. The agent must treat
22472
22615
  // this as "could not read quota" — NOT as zero usage, and NOT as a broken
22473
22616
  // login (the token just authenticated /users/me above). product#3761.
@@ -22481,7 +22624,7 @@ var accountStatus = {
22481
22624
 
22482
22625
  // ../core/dist/composite/bulk-qualify-leads.js
22483
22626
  var PAGE_SIZE = 50;
22484
- var DEFAULT_COUNT = 10;
22627
+ var DEFAULT_COUNT2 = 10;
22485
22628
  var MAX_COUNT = 25;
22486
22629
  var DEFAULT_PER_LEAD_BUDGET_MS = 9e4;
22487
22630
  var DEFAULT_TOTAL_BUDGET_MS2 = 5 * 6e4;
@@ -22529,7 +22672,7 @@ var bulkQualifyLeads = {
22529
22672
  properties: {
22530
22673
  count: {
22531
22674
  type: "number",
22532
- description: `How many fresh leads to qualify (default ${DEFAULT_COUNT}, max ${MAX_COUNT})`
22675
+ description: `How many fresh leads to qualify (default ${DEFAULT_COUNT2}, max ${MAX_COUNT})`
22533
22676
  },
22534
22677
  leadIds: {
22535
22678
  type: "array",
@@ -22620,7 +22763,7 @@ var bulkQualifyLeads = {
22620
22763
  ]
22621
22764
  },
22622
22765
  execute: async (client, params, ctx) => {
22623
- const wantCount = Math.min(params.count ?? DEFAULT_COUNT, MAX_COUNT);
22766
+ const wantCount = Math.min(params.count ?? DEFAULT_COUNT2, MAX_COUNT);
22624
22767
  const perLeadBudget = params.per_lead_budget_ms ?? DEFAULT_PER_LEAD_BUDGET_MS;
22625
22768
  const totalBudget = params.total_budget_ms ?? DEFAULT_TOTAL_BUDGET_MS2;
22626
22769
  const totalDeadline = Date.now() + totalBudget;
@@ -27663,6 +27806,7 @@ var EMBEDDED_SENTRY_DSN = "https://301f1c433433b76132956ed5415bea19@o45058744368
27663
27806
  // src/telemetry-events.ts
27664
27807
  var EV_TOOL_CALL = "mcp tool called";
27665
27808
  var EV_QUOTA_HIT = "mcp quota hit";
27809
+ var EV_TOOL_TIMEOUT = "mcp tool timeout";
27666
27810
  var EV_TOPUP_LINK = "mcp topup link created";
27667
27811
  var EV_STARTUP = "mcp startup";
27668
27812
  var EV_MCP_UPDATE_CHECK = "mcp update check";
@@ -27673,6 +27817,7 @@ var EV_MCP_VERSION_UPDATED = "mcp version updated";
27673
27817
  var EV_AGENT_MEMORY_CAPTURED = "agent_memory_captured";
27674
27818
  var EV_AGENT_MEMORY_RECALLED = "agent_memory_recalled";
27675
27819
  var EV_AGENT_MEMORY_PRUNED = "agent_memory_pruned";
27820
+ var DURATION_PLAUSIBILITY_CEILING_MS = 6e5;
27676
27821
  var EV_FRICTION_REPORTED = "mcp friction reported";
27677
27822
  var EV_COMPOSITE_CALL = "mcp composite call";
27678
27823
 
@@ -27686,6 +27831,8 @@ var NOOP_TELEMETRY = {
27686
27831
  },
27687
27832
  captureQuotaHit: (_props, _identity) => {
27688
27833
  },
27834
+ captureToolTimeout: (_props, _identity) => {
27835
+ },
27689
27836
  captureTopupLink: (_props, _identity) => {
27690
27837
  },
27691
27838
  captureStartup: (_props, _identity) => {
@@ -27720,6 +27867,13 @@ function parseTelemetryEnv(raw) {
27720
27867
  if (v === "false" || v === "0" || v === "no" || v === "off") return false;
27721
27868
  return true;
27722
27869
  }
27870
+ function withPlausibleDuration(props) {
27871
+ const { duration_ms, ...rest } = props;
27872
+ if (Number.isFinite(duration_ms) && duration_ms >= 0 && duration_ms <= DURATION_PLAUSIBILITY_CEILING_MS) {
27873
+ return { ...rest, duration_ms };
27874
+ }
27875
+ return { ...rest, duration_ms_raw: duration_ms, duration_implausible: true };
27876
+ }
27723
27877
  function initTelemetry(opts) {
27724
27878
  if (!parseTelemetryEnv(process.env.LEADBAY_TELEMETRY_ENABLED)) return NOOP_TELEMETRY;
27725
27879
  if (process.env.NODE_ENV === "test") return NOOP_TELEMETRY;
@@ -27888,14 +28042,17 @@ function initTelemetry(opts) {
27888
28042
  return identityPromise;
27889
28043
  },
27890
28044
  captureToolCall(props, identity) {
27891
- emit(EV_TOOL_CALL, { ...props }, identity);
28045
+ emit(EV_TOOL_CALL, withPlausibleDuration(props), identity);
27892
28046
  },
27893
28047
  captureCompositeCall(props, identity) {
27894
- emit(EV_COMPOSITE_CALL, { ...props }, identity);
28048
+ emit(EV_COMPOSITE_CALL, withPlausibleDuration(props), identity);
27895
28049
  },
27896
28050
  captureQuotaHit(props, identity) {
27897
28051
  emit(EV_QUOTA_HIT, { ...props }, identity);
27898
28052
  },
28053
+ captureToolTimeout(props, identity) {
28054
+ emit(EV_TOOL_TIMEOUT, { ...props }, identity);
28055
+ },
27899
28056
  captureTopupLink(props, identity) {
27900
28057
  emit(EV_TOPUP_LINK, { ...props }, identity);
27901
28058
  },
@@ -28783,6 +28940,16 @@ function buildServer(client, opts = {}) {
28783
28940
  source: "business"
28784
28941
  };
28785
28942
  };
28943
+ const captureTimeoutAlert = (toolName, envelope, triggeredBy) => {
28944
+ const ms = envelope._meta?.timeout_ms;
28945
+ telemetry2.captureToolTimeout({
28946
+ tool: toolName,
28947
+ ...typeof ms === "number" ? { timeout_ms: ms } : {},
28948
+ ...envelope._meta?.endpoint ? { endpoint: envelope._meta.endpoint } : {},
28949
+ ...envelope._meta?.region ? { region: envelope._meta.region } : {},
28950
+ ...triggeredBy !== void 0 ? { triggered_by: triggeredBy } : {}
28951
+ });
28952
+ };
28786
28953
  const captureAgentMemoryTelemetry = (toolName, result) => {
28787
28954
  if (!result || typeof result !== "object") return;
28788
28955
  const meta = result._meta ?? {};
@@ -28947,7 +29114,7 @@ ${url}
28947
29114
  isError: true
28948
29115
  };
28949
29116
  }
28950
- const result = await tool.execute(client, args, {
29117
+ const result = await runWithRequestSignal(extra.signal, () => tool.execute(client, args, {
28951
29118
  logger: opts.logger,
28952
29119
  bulkTracker: opts.bulkTracker,
28953
29120
  notificationsInbox: opts.notificationsInbox,
@@ -28980,7 +29147,7 @@ ${url}
28980
29147
  ...report.tool_called ? { tool_called: report.tool_called } : {},
28981
29148
  ...report.severity ? { severity: report.severity } : {}
28982
29149
  }) === true
28983
- });
29150
+ }));
28984
29151
  await maybeAttachUpdate(name, result);
28985
29152
  maybeAttachNotifications(result);
28986
29153
  if (result && typeof result === "object" && result.error === true) {
@@ -28996,6 +29163,9 @@ ${url}
28996
29163
  endpoint: result._meta?.endpoint
28997
29164
  });
28998
29165
  }
29166
+ if (envCode === "TIMEOUT") {
29167
+ captureTimeoutAlert(name, result, triggered_by);
29168
+ }
28999
29169
  telemetry2.captureToolCall({
29000
29170
  tool: name,
29001
29171
  ok: false,
@@ -29126,6 +29296,9 @@ ${url}
29126
29296
  endpoint: err._meta?.endpoint
29127
29297
  });
29128
29298
  }
29299
+ if (!skipAnalytics && err.code === "TIMEOUT") {
29300
+ captureTimeoutAlert(name, err, triggered_by);
29301
+ }
29129
29302
  const httpStatus2 = err._meta?.http_status;
29130
29303
  if (!skipAnalytics) {
29131
29304
  telemetry2.captureToolCall({
@@ -29374,7 +29547,7 @@ function parseWriteEnv(env = process.env) {
29374
29547
  }
29375
29548
 
29376
29549
  // src/http-server.ts
29377
- var VERSION = true ? "0.32.0" : "0.0.0-dev";
29550
+ var VERSION = true ? "0.32.4" : "0.0.0-dev";
29378
29551
  var PORT = Number(process.env.PORT ?? 8080);
29379
29552
  var HOST = process.env.HOST ?? "0.0.0.0";
29380
29553
  var logger = {
@@ -1804,7 +1804,7 @@ var init_installer_gui = __esm({
1804
1804
  init_install_dxt();
1805
1805
  init_install_shared();
1806
1806
  init_oauth();
1807
- VERSION = true ? "0.32.0" : "0.0.0-dev";
1807
+ VERSION = true ? "0.32.4" : "0.0.0-dev";
1808
1808
  MESSAGES = {
1809
1809
  en: {
1810
1810
  installer: {
@@ -1067,7 +1067,7 @@ async function oauthLogin(opts) {
1067
1067
  }
1068
1068
 
1069
1069
  // installer/installer-gui.ts
1070
- var VERSION = true ? "0.32.0" : "0.0.0-dev";
1070
+ var VERSION = true ? "0.32.4" : "0.0.0-dev";
1071
1071
  var MESSAGES = {
1072
1072
  en: {
1073
1073
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.32.0",
3
+ "version": "0.32.4",
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",