@leadbay/mcp 0.19.3 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.20.1 — 2026-06-15
4
+
5
+ - **Triage board stays the first next-step option on a poor-fit batch** (`leadbay_daily_check_in`): when today's batch is an ICP mismatch (every lead AI-scored off-profile), the agent was demoting the interactive triage board below "refine audience" in the NEXT STEPS widget — the plain ordering rule kept losing to the agent's own leverage judgment ("the whole batch is junk, so lead with fixing the lens"). The workflow contract requires the named artifact to be the FIRST option. The ordering rule now holds the triage board at position 1 even on a mismatched batch; the mismatch is surfaced in the prose nudge and offered as a *later* "refine the lens" option, never by displacing the artifact. Verified 5/5/5/5 across 3 consecutive eval runs on an all-off-ICP batch (the exact case that defeated the weaker rule).
6
+
7
+ ## 0.20.0 — 2026-06-15
8
+
9
+ - **Proactive update proposal on a fresh session** (product#3742): the auto-update check already ran at boot, but the resulting proposal only reached the user if the agent happened to call `leadbay_account_status` — which a fresh session rarely does, so the "newer version available" prompt was effectively invisible. The cached `update_available` block now also rides along on `_meta.update_available` of the **first ordinary tool result** of a session while an upgrade is pending, gated once-per-version so it surfaces exactly once. `leadbay_account_status` keeps carrying it as a top-level field. The server-instruction paragraph now tells the agent to surface the `ask_user_input_v0` prompt whenever it sees the field on *any* response.
10
+ - **Installer asset is now `.dxt`, not `.mcpb`**: the release-asset picker prefers the `.dxt` bundle (falling back to `.mcpb` only when a release ships no `.dxt`). The field is renamed `mcpb_url` → `install_url` across `update_available`, the `leadbay_acknowledge_update` result, and the persisted update-state — with forward-migration of the legacy `latest_known_mcpb_url` key so existing users don't lose their cache.
11
+
3
12
  ## 0.19.3 — 2026-06-15
4
13
 
5
14
  - **New tool `leadbay_send_feedback`**: delivers a user-authored message to the same destination as the web app's "Send feedback" form — the team's Sentry feedback inbox (the website form calls `Sentry.captureFeedback`; there is no Leadbay API endpoint, so the MCP reuses its already-initialized `@sentry/node`). User-initiated ("send feedback / report a bug / tell Leadbay…"), or offered on a tool error and sent only on explicit yes. Distinct from the silent, agent-detected, PostHog-only `leadbay_report_friction`: feedback is explicit, user-authored, and reaches the team's inbox. Honest delivery — if the Sentry transport isn't available it returns `sent:false`, never a false success; Sentry is flushed after capture so the event actually ships; identity is attached when it resolves (anonymous fallback rather than dropping the message). Write-gated (`LEADBAY_MCP_WRITE=1`) since it sends data outward.
package/dist/bin.js CHANGED
@@ -16524,16 +16524,16 @@ var init_account_status = __esm({
16524
16524
  properties: {
16525
16525
  current_version: { type: "string" },
16526
16526
  latest_version: { type: "string" },
16527
- mcpb_url: {
16527
+ install_url: {
16528
16528
  type: "string",
16529
- description: "Direct download URL for the .mcpb installer asset."
16529
+ description: "Direct download URL for the installer asset (.dxt, falling back to .mcpb)."
16530
16530
  },
16531
16531
  release_url: {
16532
16532
  type: "string",
16533
16533
  description: "GitHub release page (changelog)."
16534
16534
  }
16535
16535
  },
16536
- required: ["current_version", "latest_version", "mcpb_url", "release_url"]
16536
+ required: ["current_version", "latest_version", "install_url", "release_url"]
16537
16537
  }
16538
16538
  },
16539
16539
  required: ["user", "organization"]
@@ -22038,7 +22038,7 @@ Contact enrichment is offered in the NEXT STEPS widget below \u2014 do NOT emit
22038
22038
 
22039
22039
  **REQUIRED OPTIONS \u2014 triggers and position rules:**
22040
22040
  - **Recurring language** ("every day", "every morning", "I do this every", "remind me", "automate this", "recurring"): add "Schedule 'Daily prospecting check-in' as a recurring task" and place it **first**.
22041
- - **\u22655 leads returned**: add "Build an interactive lead triage board for this batch" and place it **first** (or second if the scheduling offer above also applies).
22041
+ - **\u22655 leads returned**: add "Build an interactive lead triage board for this batch" and place it **first** (or second if the scheduling offer above also applies). This holds **even when the batch is a poor fit** (e.g. every lead AI-scored as off-ICP / a vertical mismatch): the triage board is still the first artifact option because the user asked to see and act on *this batch*. When the batch is a mismatch, ALSO offer "Refine the audience / lens so future batches fit better" \u2014 but as a *later* option, never displacing the triage board from first. Leading with audience-refinement instead of the artifact is a contract violation: surface the mismatch in your prose nudge, not by demoting the triage board.
22042
22042
 
22043
22043
  ## NEXT STEPS \u2014 after rendering the pull_leads table
22044
22044
 
@@ -22079,7 +22079,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
22079
22079
  If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
22080
22080
 
22081
22081
 
22082
- **Final ordering check (do this before rendering):** Recurring offer \u2192 option 1; triage board \u2192 option 1 (or 2 if scheduling is also required). Swap if needed.
22082
+ **Final ordering check (do this before rendering):** Recurring offer \u2192 option 1; triage board \u2192 option 1 (or 2 if scheduling is also required). A poor-fit / mismatched batch does NOT change this \u2014 triage board stays first, refine-audience goes later in the list. Swap if needed.
22083
22083
 
22084
22084
  # GATE \u2014 STOP
22085
22085
 
@@ -23584,10 +23584,13 @@ function initTelemetry(opts) {
23584
23584
 
23585
23585
  // src/update-check.ts
23586
23586
  var cachedInfo = null;
23587
- var checkInFlight = false;
23587
+ var inFlightCheck = null;
23588
23588
  function getCachedUpdateInfo() {
23589
23589
  return cachedInfo;
23590
23590
  }
23591
+ function getInFlightCheck() {
23592
+ return inFlightCheck;
23593
+ }
23591
23594
  var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
23592
23595
  var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
23593
23596
  var FETCH_TIMEOUT_MS = 5e3;
@@ -23632,25 +23635,24 @@ function compareSemver(a, b) {
23632
23635
  }
23633
23636
  return 0;
23634
23637
  }
23635
- function pickMcpbAsset(rel) {
23638
+ function pickInstallAsset(rel) {
23636
23639
  if (!Array.isArray(rel.assets)) return void 0;
23637
- const mcpb = rel.assets.find(
23638
- (a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
23639
- );
23640
- if (mcpb?.browser_download_url) return mcpb.browser_download_url;
23641
23640
  const dxt = rel.assets.find(
23642
23641
  (a) => typeof a.name === "string" && a.name.endsWith(".dxt")
23643
23642
  );
23644
- return dxt?.browser_download_url;
23643
+ if (dxt?.browser_download_url) return dxt.browser_download_url;
23644
+ const mcpb = rel.assets.find(
23645
+ (a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
23646
+ );
23647
+ return mcpb?.browser_download_url;
23645
23648
  }
23646
- async function checkForUpdate(opts) {
23647
- if (checkInFlight) return cachedInfo;
23648
- checkInFlight = true;
23649
- try {
23650
- return await doCheck(opts);
23651
- } finally {
23652
- checkInFlight = false;
23653
- }
23649
+ function checkForUpdate(opts) {
23650
+ if (inFlightCheck) return inFlightCheck;
23651
+ const p = doCheck(opts).finally(() => {
23652
+ if (inFlightCheck === p) inFlightCheck = null;
23653
+ });
23654
+ inFlightCheck = p;
23655
+ return p;
23654
23656
  }
23655
23657
  async function doCheck(opts) {
23656
23658
  const now = opts.now ?? Date.now;
@@ -23659,11 +23661,11 @@ async function doCheck(opts) {
23659
23661
  const currentVersion = opts.currentVersion;
23660
23662
  const state = await opts.stateStore.read();
23661
23663
  const within = now() - state.last_check_time < CHECK_THROTTLE_MS;
23662
- if (!opts.force && within && state.latest_known_version && state.latest_known_mcpb_url && state.latest_known_release_url) {
23664
+ if (!opts.force && within && state.latest_known_version && state.latest_known_install_url && state.latest_known_release_url) {
23663
23665
  const cached = buildInfoIfUpgrade(
23664
23666
  currentVersion,
23665
23667
  state.latest_known_version,
23666
- state.latest_known_mcpb_url,
23668
+ state.latest_known_install_url,
23667
23669
  state.latest_known_release_url,
23668
23670
  state.suppressed_versions,
23669
23671
  state.remind_until,
@@ -23717,18 +23719,18 @@ async function doCheck(opts) {
23717
23719
  return null;
23718
23720
  }
23719
23721
  let latestVersion;
23720
- let mcpbUrl;
23722
+ let installUrl;
23721
23723
  let releaseUrl;
23722
23724
  if (status === 200 && body) {
23723
23725
  const parsed = body.tag_name ? parseTagName(body.tag_name) : null;
23724
23726
  if (parsed) {
23725
23727
  latestVersion = parsed;
23726
- mcpbUrl = pickMcpbAsset(body);
23728
+ installUrl = pickInstallAsset(body);
23727
23729
  releaseUrl = body.html_url;
23728
23730
  }
23729
23731
  } else {
23730
23732
  latestVersion = state.latest_known_version;
23731
- mcpbUrl = state.latest_known_mcpb_url;
23733
+ installUrl = state.latest_known_install_url;
23732
23734
  releaseUrl = state.latest_known_release_url;
23733
23735
  }
23734
23736
  const persisted = await opts.stateStore.update((cur) => ({
@@ -23736,7 +23738,7 @@ async function doCheck(opts) {
23736
23738
  last_check_time: now(),
23737
23739
  etag: nextEtag,
23738
23740
  latest_known_version: latestVersion ?? cur.latest_known_version,
23739
- latest_known_mcpb_url: mcpbUrl ?? cur.latest_known_mcpb_url,
23741
+ latest_known_install_url: installUrl ?? cur.latest_known_install_url,
23740
23742
  latest_known_release_url: releaseUrl ?? cur.latest_known_release_url
23741
23743
  }));
23742
23744
  opts.telemetry.captureUpdateCheck?.({
@@ -23746,7 +23748,7 @@ async function doCheck(opts) {
23746
23748
  const info = buildInfoIfUpgrade(
23747
23749
  currentVersion,
23748
23750
  persisted.latest_known_version,
23749
- persisted.latest_known_mcpb_url,
23751
+ persisted.latest_known_install_url,
23750
23752
  persisted.latest_known_release_url,
23751
23753
  persisted.suppressed_versions,
23752
23754
  persisted.remind_until,
@@ -23755,15 +23757,15 @@ async function doCheck(opts) {
23755
23757
  cachedInfo = info;
23756
23758
  return info;
23757
23759
  }
23758
- function buildInfoIfUpgrade(currentVersion, latestVersion, mcpbUrl, releaseUrl, suppressed, remindUntil, nowMs) {
23759
- if (!latestVersion || !mcpbUrl || !releaseUrl) return null;
23760
+ function buildInfoIfUpgrade(currentVersion, latestVersion, installUrl, releaseUrl, suppressed, remindUntil, nowMs) {
23761
+ if (!latestVersion || !installUrl || !releaseUrl) return null;
23760
23762
  if (compareSemver(latestVersion, currentVersion) <= 0) return null;
23761
23763
  if (suppressed.includes(latestVersion)) return null;
23762
23764
  if (remindUntil && remindUntil > nowMs) return null;
23763
23765
  return {
23764
23766
  current_version: currentVersion,
23765
23767
  latest_version: latestVersion,
23766
- mcpb_url: mcpbUrl,
23768
+ install_url: installUrl,
23767
23769
  release_url: releaseUrl
23768
23770
  };
23769
23771
  }
@@ -23792,7 +23794,7 @@ async function recordRunningVersion(currentVersion, stateStore, telemetry) {
23792
23794
 
23793
23795
  // src/update-tool.ts
23794
23796
  var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
23795
- var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ mcpb_url, release_url }` \u2014 show the user a clickable link to mcpb_url so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
23797
+ var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ install_url, release_url }` \u2014 show the user a clickable link to install_url (a `.dxt` bundle) so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
23796
23798
  function buildAcknowledgeUpdateTool(opts) {
23797
23799
  const now = opts.now ?? Date.now;
23798
23800
  return {
@@ -23828,7 +23830,7 @@ function buildAcknowledgeUpdateTool(opts) {
23828
23830
  action: { type: "string" },
23829
23831
  version: { type: "string" },
23830
23832
  message: { type: "string" },
23831
- mcpb_url: { type: ["string", "null"] },
23833
+ install_url: { type: ["string", "null"] },
23832
23834
  release_url: { type: ["string", "null"] }
23833
23835
  },
23834
23836
  required: ["ok", "action", "version", "message"]
@@ -23862,9 +23864,9 @@ function buildAcknowledgeUpdateTool(opts) {
23862
23864
  ok: true,
23863
23865
  action,
23864
23866
  version,
23865
- mcpb_url: state.latest_known_mcpb_url ?? null,
23867
+ install_url: state.latest_known_install_url ?? null,
23866
23868
  release_url: state.latest_known_release_url ?? null,
23867
- message: state.latest_known_mcpb_url ? "Show the user the mcpb_url as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No .mcpb URL is cached. Direct the user to the release_url to download manually."
23869
+ message: state.latest_known_install_url ? "Show the user the install_url (a .dxt bundle) as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No installer URL is cached. Direct the user to the release_url to download manually."
23868
23870
  };
23869
23871
  }
23870
23872
  if (action === "remind_tomorrow") {
@@ -23967,7 +23969,7 @@ function buildStartHereParagraph(has) {
23967
23969
  }
23968
23970
  function buildUpdateAvailableParagraph(has) {
23969
23971
  if (!has("leadbay_acknowledge_update")) return null;
23970
- return "MCP auto-update: when `leadbay_account_status` returns an `update_available` field (`{ current_version, latest_version, mcpb_url, release_url }`), a newer MCP server release is published and the user has NOT suppressed it. Surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `mcpb_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .mcpb extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
23972
+ return "MCP auto-update: an `update_available` field (`{ current_version, latest_version, install_url, release_url }`) means a newer MCP server release is published and the user has NOT suppressed it. It appears in TWO places: as a top-level field on `leadbay_account_status`, AND on `_meta.update_available` of the FIRST other tool result in a session while an update is pending (so a fresh session surfaces the proposal even without an account_status call). Whenever you see it on ANY response, surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `install_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .dxt extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
23971
23973
  }
23972
23974
  function buildRhythmParagraph(has) {
23973
23975
  if (has("leadbay_report_outreach")) {
@@ -24262,16 +24264,47 @@ function buildServer(client, opts = {}) {
24262
24264
  );
24263
24265
  });
24264
24266
  };
24265
- const maybeAttachUpdate = (toolName, result) => {
24266
- if (toolName !== "leadbay_account_status") return;
24267
+ const UPDATE_SURFACE_WAIT_MS = 1500;
24268
+ const maybeAttachUpdate = async (toolName, result) => {
24267
24269
  if (!opts.updateStateStore) return;
24268
24270
  if (result === null || typeof result !== "object" || Array.isArray(result)) {
24269
24271
  return;
24270
24272
  }
24271
- const info = getCachedUpdateInfo();
24273
+ if (result.error === true) {
24274
+ return;
24275
+ }
24276
+ let info = getCachedUpdateInfo();
24277
+ if (!info) {
24278
+ const inflight = getInFlightCheck();
24279
+ if (inflight) {
24280
+ const settled = inflight.catch((err) => {
24281
+ opts.logger?.warn?.(
24282
+ `update_check.surface_await_failed ${err?.message ?? err}`
24283
+ );
24284
+ return null;
24285
+ });
24286
+ info = await Promise.race([
24287
+ settled,
24288
+ new Promise(
24289
+ (resolve) => setTimeout(() => resolve(null), UPDATE_SURFACE_WAIT_MS)
24290
+ )
24291
+ ]);
24292
+ info = getCachedUpdateInfo() ?? info;
24293
+ }
24294
+ }
24272
24295
  if (!info) return;
24273
- result.update_available = info;
24274
- if (!promptedVersionsThisSession.has(info.latest_version)) {
24296
+ const isAccountStatus = toolName === "leadbay_account_status";
24297
+ const alreadyPrompted = promptedVersionsThisSession.has(info.latest_version);
24298
+ if (!isAccountStatus && alreadyPrompted) return;
24299
+ if (isAccountStatus) {
24300
+ result.update_available = info;
24301
+ } else {
24302
+ const envelope = result;
24303
+ const target = envelope.__markdown_envelope === true && envelope.structured !== null && typeof envelope.structured === "object" && !Array.isArray(envelope.structured) ? envelope.structured : envelope;
24304
+ const existingMeta = target._meta && typeof target._meta === "object" && !Array.isArray(target._meta) ? target._meta : {};
24305
+ target._meta = { ...existingMeta, update_available: info };
24306
+ }
24307
+ if (!alreadyPrompted) {
24275
24308
  promptedVersionsThisSession.add(info.latest_version);
24276
24309
  telemetry.captureUpdatePrompted?.({
24277
24310
  current_version: serverVersion,
@@ -24451,7 +24484,7 @@ function buildServer(client, opts = {}) {
24451
24484
  // tool reports honestly when telemetry is off.
24452
24485
  sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
24453
24486
  });
24454
- maybeAttachUpdate(name, result);
24487
+ await maybeAttachUpdate(name, result);
24455
24488
  maybeAttachNotifications(result);
24456
24489
  if (result && typeof result === "object" && result.error === true) {
24457
24490
  const envText = formatErrorForLLM(result);
@@ -24590,6 +24623,7 @@ function buildServer(client, opts = {}) {
24590
24623
  endpoint: err._meta?.endpoint
24591
24624
  });
24592
24625
  }
24626
+ const httpStatus2 = err._meta?.http_status;
24593
24627
  telemetry.captureToolCall({
24594
24628
  tool: name,
24595
24629
  ok: false,
@@ -24597,6 +24631,7 @@ function buildServer(client, opts = {}) {
24597
24631
  format: "error-envelope",
24598
24632
  bytes: errText.length,
24599
24633
  error_code: code,
24634
+ ...typeof httpStatus2 === "number" ? { http_status: httpStatus2 } : {},
24600
24635
  triggered_by
24601
24636
  });
24602
24637
  if (COMPOSITE_FILE_TOOL_NAMES.has(name)) {
@@ -24605,7 +24640,8 @@ function buildServer(client, opts = {}) {
24605
24640
  last_prompt: triggered_by ?? "",
24606
24641
  ok: false,
24607
24642
  duration_ms: errDur,
24608
- error_code: code
24643
+ error_code: code,
24644
+ ...typeof httpStatus2 === "number" ? { http_status: httpStatus2 } : {}
24609
24645
  });
24610
24646
  }
24611
24647
  telemetry.captureException(err, buildBusinessCtx(name, err, triggered_by));
@@ -25461,8 +25497,10 @@ var UpdateStateStore = class {
25461
25497
  if (typeof r.latest_known_version === "string") {
25462
25498
  out.latest_known_version = r.latest_known_version;
25463
25499
  }
25464
- if (typeof r.latest_known_mcpb_url === "string") {
25465
- out.latest_known_mcpb_url = r.latest_known_mcpb_url;
25500
+ if (typeof r.latest_known_install_url === "string") {
25501
+ out.latest_known_install_url = r.latest_known_install_url;
25502
+ } else if (typeof r.latest_known_mcpb_url === "string") {
25503
+ out.latest_known_install_url = r.latest_known_mcpb_url;
25466
25504
  }
25467
25505
  if (typeof r.latest_known_release_url === "string") {
25468
25506
  out.latest_known_release_url = r.latest_known_release_url;
@@ -25890,7 +25928,7 @@ var OAUTH_BASE_URLS = {
25890
25928
  fr: "https://staging.api.leadbay.app"
25891
25929
  }
25892
25930
  };
25893
- var VERSION = "0.19.3";
25931
+ var VERSION = "0.20.1";
25894
25932
  var HELP = `
25895
25933
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
25896
25934
 
@@ -180,7 +180,7 @@ Contact enrichment is offered in the NEXT STEPS widget below \u2014 do NOT emit
180
180
 
181
181
  **REQUIRED OPTIONS \u2014 triggers and position rules:**
182
182
  - **Recurring language** ("every day", "every morning", "I do this every", "remind me", "automate this", "recurring"): add "Schedule 'Daily prospecting check-in' as a recurring task" and place it **first**.
183
- - **\u22655 leads returned**: add "Build an interactive lead triage board for this batch" and place it **first** (or second if the scheduling offer above also applies).
183
+ - **\u22655 leads returned**: add "Build an interactive lead triage board for this batch" and place it **first** (or second if the scheduling offer above also applies). This holds **even when the batch is a poor fit** (e.g. every lead AI-scored as off-ICP / a vertical mismatch): the triage board is still the first artifact option because the user asked to see and act on *this batch*. When the batch is a mismatch, ALSO offer "Refine the audience / lens so future batches fit better" \u2014 but as a *later* option, never displacing the triage board from first. Leading with audience-refinement instead of the artifact is a contract violation: surface the mismatch in your prose nudge, not by demoting the triage board.
184
184
 
185
185
  ## NEXT STEPS \u2014 after rendering the pull_leads table
186
186
 
@@ -221,7 +221,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
221
221
  If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
222
222
 
223
223
 
224
- **Final ordering check (do this before rendering):** Recurring offer \u2192 option 1; triage board \u2192 option 1 (or 2 if scheduling is also required). Swap if needed.
224
+ **Final ordering check (do this before rendering):** Recurring offer \u2192 option 1; triage board \u2192 option 1 (or 2 if scheduling is also required). A poor-fit / mismatched batch does NOT change this \u2014 triage board stays first, refine-audience goes later in the list. Swap if needed.
225
225
 
226
226
  # GATE \u2014 STOP
227
227
 
@@ -16779,16 +16779,16 @@ var accountStatus = {
16779
16779
  properties: {
16780
16780
  current_version: { type: "string" },
16781
16781
  latest_version: { type: "string" },
16782
- mcpb_url: {
16782
+ install_url: {
16783
16783
  type: "string",
16784
- description: "Direct download URL for the .mcpb installer asset."
16784
+ description: "Direct download URL for the installer asset (.dxt, falling back to .mcpb)."
16785
16785
  },
16786
16786
  release_url: {
16787
16787
  type: "string",
16788
16788
  description: "GitHub release page (changelog)."
16789
16789
  }
16790
16790
  },
16791
- required: ["current_version", "latest_version", "mcpb_url", "release_url"]
16791
+ required: ["current_version", "latest_version", "install_url", "release_url"]
16792
16792
  }
16793
16793
  },
16794
16794
  required: ["user", "organization"]
@@ -21262,10 +21262,13 @@ var NOOP_TELEMETRY = {
21262
21262
 
21263
21263
  // src/update-check.ts
21264
21264
  var cachedInfo = null;
21265
- var checkInFlight = false;
21265
+ var inFlightCheck = null;
21266
21266
  function getCachedUpdateInfo() {
21267
21267
  return cachedInfo;
21268
21268
  }
21269
+ function getInFlightCheck() {
21270
+ return inFlightCheck;
21271
+ }
21269
21272
  var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
21270
21273
  var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
21271
21274
  var FETCH_TIMEOUT_MS = 5e3;
@@ -21310,25 +21313,24 @@ function compareSemver(a, b) {
21310
21313
  }
21311
21314
  return 0;
21312
21315
  }
21313
- function pickMcpbAsset(rel) {
21316
+ function pickInstallAsset(rel) {
21314
21317
  if (!Array.isArray(rel.assets)) return void 0;
21315
- const mcpb = rel.assets.find(
21316
- (a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
21317
- );
21318
- if (mcpb?.browser_download_url) return mcpb.browser_download_url;
21319
21318
  const dxt = rel.assets.find(
21320
21319
  (a) => typeof a.name === "string" && a.name.endsWith(".dxt")
21321
21320
  );
21322
- return dxt?.browser_download_url;
21321
+ if (dxt?.browser_download_url) return dxt.browser_download_url;
21322
+ const mcpb = rel.assets.find(
21323
+ (a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
21324
+ );
21325
+ return mcpb?.browser_download_url;
21323
21326
  }
21324
- async function checkForUpdate(opts) {
21325
- if (checkInFlight) return cachedInfo;
21326
- checkInFlight = true;
21327
- try {
21328
- return await doCheck(opts);
21329
- } finally {
21330
- checkInFlight = false;
21331
- }
21327
+ function checkForUpdate(opts) {
21328
+ if (inFlightCheck) return inFlightCheck;
21329
+ const p = doCheck(opts).finally(() => {
21330
+ if (inFlightCheck === p) inFlightCheck = null;
21331
+ });
21332
+ inFlightCheck = p;
21333
+ return p;
21332
21334
  }
21333
21335
  async function doCheck(opts) {
21334
21336
  const now = opts.now ?? Date.now;
@@ -21337,11 +21339,11 @@ async function doCheck(opts) {
21337
21339
  const currentVersion = opts.currentVersion;
21338
21340
  const state = await opts.stateStore.read();
21339
21341
  const within = now() - state.last_check_time < CHECK_THROTTLE_MS;
21340
- if (!opts.force && within && state.latest_known_version && state.latest_known_mcpb_url && state.latest_known_release_url) {
21342
+ if (!opts.force && within && state.latest_known_version && state.latest_known_install_url && state.latest_known_release_url) {
21341
21343
  const cached = buildInfoIfUpgrade(
21342
21344
  currentVersion,
21343
21345
  state.latest_known_version,
21344
- state.latest_known_mcpb_url,
21346
+ state.latest_known_install_url,
21345
21347
  state.latest_known_release_url,
21346
21348
  state.suppressed_versions,
21347
21349
  state.remind_until,
@@ -21395,18 +21397,18 @@ async function doCheck(opts) {
21395
21397
  return null;
21396
21398
  }
21397
21399
  let latestVersion;
21398
- let mcpbUrl;
21400
+ let installUrl;
21399
21401
  let releaseUrl;
21400
21402
  if (status === 200 && body) {
21401
21403
  const parsed = body.tag_name ? parseTagName(body.tag_name) : null;
21402
21404
  if (parsed) {
21403
21405
  latestVersion = parsed;
21404
- mcpbUrl = pickMcpbAsset(body);
21406
+ installUrl = pickInstallAsset(body);
21405
21407
  releaseUrl = body.html_url;
21406
21408
  }
21407
21409
  } else {
21408
21410
  latestVersion = state.latest_known_version;
21409
- mcpbUrl = state.latest_known_mcpb_url;
21411
+ installUrl = state.latest_known_install_url;
21410
21412
  releaseUrl = state.latest_known_release_url;
21411
21413
  }
21412
21414
  const persisted = await opts.stateStore.update((cur) => ({
@@ -21414,7 +21416,7 @@ async function doCheck(opts) {
21414
21416
  last_check_time: now(),
21415
21417
  etag: nextEtag,
21416
21418
  latest_known_version: latestVersion ?? cur.latest_known_version,
21417
- latest_known_mcpb_url: mcpbUrl ?? cur.latest_known_mcpb_url,
21419
+ latest_known_install_url: installUrl ?? cur.latest_known_install_url,
21418
21420
  latest_known_release_url: releaseUrl ?? cur.latest_known_release_url
21419
21421
  }));
21420
21422
  opts.telemetry.captureUpdateCheck?.({
@@ -21424,7 +21426,7 @@ async function doCheck(opts) {
21424
21426
  const info = buildInfoIfUpgrade(
21425
21427
  currentVersion,
21426
21428
  persisted.latest_known_version,
21427
- persisted.latest_known_mcpb_url,
21429
+ persisted.latest_known_install_url,
21428
21430
  persisted.latest_known_release_url,
21429
21431
  persisted.suppressed_versions,
21430
21432
  persisted.remind_until,
@@ -21433,22 +21435,22 @@ async function doCheck(opts) {
21433
21435
  cachedInfo = info;
21434
21436
  return info;
21435
21437
  }
21436
- function buildInfoIfUpgrade(currentVersion, latestVersion, mcpbUrl, releaseUrl, suppressed, remindUntil, nowMs) {
21437
- if (!latestVersion || !mcpbUrl || !releaseUrl) return null;
21438
+ function buildInfoIfUpgrade(currentVersion, latestVersion, installUrl, releaseUrl, suppressed, remindUntil, nowMs) {
21439
+ if (!latestVersion || !installUrl || !releaseUrl) return null;
21438
21440
  if (compareSemver(latestVersion, currentVersion) <= 0) return null;
21439
21441
  if (suppressed.includes(latestVersion)) return null;
21440
21442
  if (remindUntil && remindUntil > nowMs) return null;
21441
21443
  return {
21442
21444
  current_version: currentVersion,
21443
21445
  latest_version: latestVersion,
21444
- mcpb_url: mcpbUrl,
21446
+ install_url: installUrl,
21445
21447
  release_url: releaseUrl
21446
21448
  };
21447
21449
  }
21448
21450
 
21449
21451
  // src/update-tool.ts
21450
21452
  var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
21451
- var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ mcpb_url, release_url }` \u2014 show the user a clickable link to mcpb_url so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
21453
+ var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ install_url, release_url }` \u2014 show the user a clickable link to install_url (a `.dxt` bundle) so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
21452
21454
  function buildAcknowledgeUpdateTool(opts) {
21453
21455
  const now = opts.now ?? Date.now;
21454
21456
  return {
@@ -21484,7 +21486,7 @@ function buildAcknowledgeUpdateTool(opts) {
21484
21486
  action: { type: "string" },
21485
21487
  version: { type: "string" },
21486
21488
  message: { type: "string" },
21487
- mcpb_url: { type: ["string", "null"] },
21489
+ install_url: { type: ["string", "null"] },
21488
21490
  release_url: { type: ["string", "null"] }
21489
21491
  },
21490
21492
  required: ["ok", "action", "version", "message"]
@@ -21518,9 +21520,9 @@ function buildAcknowledgeUpdateTool(opts) {
21518
21520
  ok: true,
21519
21521
  action,
21520
21522
  version,
21521
- mcpb_url: state.latest_known_mcpb_url ?? null,
21523
+ install_url: state.latest_known_install_url ?? null,
21522
21524
  release_url: state.latest_known_release_url ?? null,
21523
- message: state.latest_known_mcpb_url ? "Show the user the mcpb_url as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No .mcpb URL is cached. Direct the user to the release_url to download manually."
21525
+ message: state.latest_known_install_url ? "Show the user the install_url (a .dxt bundle) as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No installer URL is cached. Direct the user to the release_url to download manually."
21524
21526
  };
21525
21527
  }
21526
21528
  if (action === "remind_tomorrow") {
@@ -21623,7 +21625,7 @@ function buildStartHereParagraph(has) {
21623
21625
  }
21624
21626
  function buildUpdateAvailableParagraph(has) {
21625
21627
  if (!has("leadbay_acknowledge_update")) return null;
21626
- return "MCP auto-update: when `leadbay_account_status` returns an `update_available` field (`{ current_version, latest_version, mcpb_url, release_url }`), a newer MCP server release is published and the user has NOT suppressed it. Surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `mcpb_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .mcpb extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
21628
+ return "MCP auto-update: an `update_available` field (`{ current_version, latest_version, install_url, release_url }`) means a newer MCP server release is published and the user has NOT suppressed it. It appears in TWO places: as a top-level field on `leadbay_account_status`, AND on `_meta.update_available` of the FIRST other tool result in a session while an update is pending (so a fresh session surfaces the proposal even without an account_status call). Whenever you see it on ANY response, surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `install_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .dxt extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
21627
21629
  }
21628
21630
  function buildRhythmParagraph(has) {
21629
21631
  if (has("leadbay_report_outreach")) {
@@ -21918,16 +21920,47 @@ function buildServer(client, opts = {}) {
21918
21920
  );
21919
21921
  });
21920
21922
  };
21921
- const maybeAttachUpdate = (toolName, result) => {
21922
- if (toolName !== "leadbay_account_status") return;
21923
+ const UPDATE_SURFACE_WAIT_MS = 1500;
21924
+ const maybeAttachUpdate = async (toolName, result) => {
21923
21925
  if (!opts.updateStateStore) return;
21924
21926
  if (result === null || typeof result !== "object" || Array.isArray(result)) {
21925
21927
  return;
21926
21928
  }
21927
- const info = getCachedUpdateInfo();
21929
+ if (result.error === true) {
21930
+ return;
21931
+ }
21932
+ let info = getCachedUpdateInfo();
21933
+ if (!info) {
21934
+ const inflight = getInFlightCheck();
21935
+ if (inflight) {
21936
+ const settled = inflight.catch((err) => {
21937
+ opts.logger?.warn?.(
21938
+ `update_check.surface_await_failed ${err?.message ?? err}`
21939
+ );
21940
+ return null;
21941
+ });
21942
+ info = await Promise.race([
21943
+ settled,
21944
+ new Promise(
21945
+ (resolve) => setTimeout(() => resolve(null), UPDATE_SURFACE_WAIT_MS)
21946
+ )
21947
+ ]);
21948
+ info = getCachedUpdateInfo() ?? info;
21949
+ }
21950
+ }
21928
21951
  if (!info) return;
21929
- result.update_available = info;
21930
- if (!promptedVersionsThisSession.has(info.latest_version)) {
21952
+ const isAccountStatus = toolName === "leadbay_account_status";
21953
+ const alreadyPrompted = promptedVersionsThisSession.has(info.latest_version);
21954
+ if (!isAccountStatus && alreadyPrompted) return;
21955
+ if (isAccountStatus) {
21956
+ result.update_available = info;
21957
+ } else {
21958
+ const envelope = result;
21959
+ const target = envelope.__markdown_envelope === true && envelope.structured !== null && typeof envelope.structured === "object" && !Array.isArray(envelope.structured) ? envelope.structured : envelope;
21960
+ const existingMeta = target._meta && typeof target._meta === "object" && !Array.isArray(target._meta) ? target._meta : {};
21961
+ target._meta = { ...existingMeta, update_available: info };
21962
+ }
21963
+ if (!alreadyPrompted) {
21931
21964
  promptedVersionsThisSession.add(info.latest_version);
21932
21965
  telemetry.captureUpdatePrompted?.({
21933
21966
  current_version: serverVersion,
@@ -22107,7 +22140,7 @@ function buildServer(client, opts = {}) {
22107
22140
  // tool reports honestly when telemetry is off.
22108
22141
  sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
22109
22142
  });
22110
- maybeAttachUpdate(name, result);
22143
+ await maybeAttachUpdate(name, result);
22111
22144
  maybeAttachNotifications(result);
22112
22145
  if (result && typeof result === "object" && result.error === true) {
22113
22146
  const envText = formatErrorForLLM(result);
@@ -22246,6 +22279,7 @@ function buildServer(client, opts = {}) {
22246
22279
  endpoint: err._meta?.endpoint
22247
22280
  });
22248
22281
  }
22282
+ const httpStatus2 = err._meta?.http_status;
22249
22283
  telemetry.captureToolCall({
22250
22284
  tool: name,
22251
22285
  ok: false,
@@ -22253,6 +22287,7 @@ function buildServer(client, opts = {}) {
22253
22287
  format: "error-envelope",
22254
22288
  bytes: errText.length,
22255
22289
  error_code: code,
22290
+ ...typeof httpStatus2 === "number" ? { http_status: httpStatus2 } : {},
22256
22291
  triggered_by
22257
22292
  });
22258
22293
  if (COMPOSITE_FILE_TOOL_NAMES.has(name)) {
@@ -22261,7 +22296,8 @@ function buildServer(client, opts = {}) {
22261
22296
  last_prompt: triggered_by ?? "",
22262
22297
  ok: false,
22263
22298
  duration_ms: errDur,
22264
- error_code: code
22299
+ error_code: code,
22300
+ ...typeof httpStatus2 === "number" ? { http_status: httpStatus2 } : {}
22265
22301
  });
22266
22302
  }
22267
22303
  telemetry.captureException(err, buildBusinessCtx(name, err, triggered_by));
@@ -22403,7 +22439,7 @@ function parseWriteEnv(env = process.env) {
22403
22439
  }
22404
22440
 
22405
22441
  // src/http-server.ts
22406
- var VERSION = true ? "0.19.3" : "0.0.0-dev";
22442
+ var VERSION = true ? "0.20.1" : "0.0.0-dev";
22407
22443
  var PORT = Number(process.env.PORT ?? 8080);
22408
22444
  var HOST = process.env.HOST ?? "0.0.0.0";
22409
22445
  var sseSessions = /* @__PURE__ */ new Map();
@@ -1466,7 +1466,7 @@ var init_installer_gui = __esm({
1466
1466
  init_install_dxt();
1467
1467
  init_install_shared();
1468
1468
  init_oauth();
1469
- VERSION = "0.19.3";
1469
+ VERSION = "0.20.1";
1470
1470
  PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
1471
1471
  sessions = /* @__PURE__ */ new Map();
1472
1472
  OAUTH_BASE_URLS = {
@@ -873,7 +873,7 @@ async function oauthLogin(opts) {
873
873
  }
874
874
 
875
875
  // installer/installer-gui.ts
876
- var VERSION = "0.19.3";
876
+ var VERSION = "0.20.1";
877
877
  var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
878
878
  var sessions = /* @__PURE__ */ new Map();
879
879
  var OAUTH_BASE_URLS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.19.3",
3
+ "version": "0.20.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",