@leadbay/mcp 0.23.9 → 0.23.11

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,20 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.23.11 — 2026-07-02
4
+
5
+ Windows `.dxt` sign-in now opens the browser reliably (product#3839).
6
+
7
+ - **`openInBrowser` (Windows)** — the auto-open resolved the moment `cmd.exe` was *created*, before its internal `start` builtin actually handed the URL to the default browser. So a silent no-op (no default-browser protocol association, a locked-down shell / AppLocker, a corrupt `HKCR\http\shell\open`) went undetected: `browserOpenFailedAtBootstrap` stayed false and the user was told "a browser may have opened" when nothing did. On Windows we now wait (bounded — 800ms for `cmd start`, 1200ms otherwise) for the launcher's **exit code**, treat a non-zero exit as failure, and fall through to `rundll32 url.dll,FileProtocolHandler` (no command interpreter — the same ShellExecute path Explorer uses, with an honest exit code) and finally PowerShell `Start-Process`. When every launcher fails, the `AUTH_REQUIRED` envelope honestly says the browser couldn't be opened and shows the clickable sign-in link. The #3801 `&`-quoting `cmd start` candidate is unchanged and still tried first. macOS/Linux keep resolve-on-`spawn` (the #3805 headless-hang fix — those launchers are the hand-off).
8
+ - **`bin.ts` shutdown** — the `browserOpenInFlight` teardown wait rose 1.5s → 3s so the multi-launcher Windows walk can finish dispatching before exit (the sibling bootstrap wait already allows 4s; the surfaced sign-in link is the fallback either way).
9
+
10
+ ## 0.23.10 — 2026-07-01
11
+
12
+ A freshly-created lens no longer reads as "empty" (product#3833).
13
+
14
+ - **`leadbay_new_lens`** — creating a lens with criteria queues an asynchronous backend wishlist rebuild, so an immediate `leadbay_pull_leads` can read empty for a few seconds while it computes. The `created` result now carries `computing_wishlist: true` and a "leads stream in — pull in ~30s" message so the agent waits instead of reporting an empty lens. A criteria-less clone inherits the base lens's leads immediately (`computing_wishlist: false`).
15
+ - **`leadbay_pull_leads`** — on an EMPTY page where the backend reports the lens is still computing (`computing_wishlist`/`computing_scores`), `next_steps` now carries a single "Re-pull in ~30s" option (`kind: repull_computing`) instead of `null`, so the agent renders a wait-and-retry widget rather than "no leads." An empty page with nothing computing still returns `null` (a genuinely empty / over-narrow lens — no fabricated leads).
16
+ - Confirmed against `leadbay/backend` (`POST /lenses/:id/filter` → `queueRefreshJob` → `RefreshLens`) and a live probe on `api-us`: a bare clone shows leads instantly; a filtered clone shows `computing_wishlist: true` then settles within ~1–30s.
17
+
3
18
  ## 0.23.9 — 2026-06-27
4
19
 
5
20
  MCP data plane now targets backend **/1.6** (was /1.5).
@@ -459,7 +474,7 @@ Every tool invocation now fires an `mcp tool called` event to PostHog (same proj
459
474
 
460
475
  ## 0.6.1 — 2026-05-10
461
476
 
462
- **Distribution**: listed in the official MCP Registry as `io.github.leadbay/leadbay-mcp` (auto-published from CI via GitHub OIDC); installable as a Claude Code plugin via `/plugin marketplace add leadbay/leadclaw` then `/plugin install leadbay@leadbay-leadclaw`; submission packets prepared for the Claude.ai connector directory and Anthropic's curated MCPB extension directory.
477
+ **Distribution**: listed in the official MCP Registry as `io.github.leadbay/leadbay-mcp` (auto-published from CI via GitHub OIDC); installable as a Claude Code plugin via `/plugin marketplace add leadbay/mcp` then `/plugin install leadbay@leadbay-mcp`; submission packets prepared for the Claude.ai connector directory and Anthropic's curated MCPB extension directory.
463
478
 
464
479
  **Registry verification**: adds `mcpName` to the npm package metadata so the MCP Registry can verify ownership of `io.github.leadbay/leadbay-mcp` against the published `@leadbay/mcp` package.
465
480
 
@@ -641,7 +656,7 @@ New `leadbay_import_leads` composite write tool.
641
656
  Claude Desktop 2026 compatibility + install UX polish. Also publishes the `refine_prompt` `/user_prompt` wire-key fix that landed on `main` in 0.2.3 but never reached npm.
642
657
 
643
658
  - **Fix [product#3504](https://github.com/leadbay/product/issues/3504)** — `install --target claude-desktop` no longer silently no-ops on Claude Desktop 2026. The app moved to the DXT (Desktop Extension) system; the legacy `claude_desktop_config.json` is UI-prefs-only there and gets overwritten on every prefs save, so a block written to it disappears a few minutes later. `install` now detects DXT (via `Claude Extensions/`, `extensions-installations.json`, or `dxt:*` keys in `config.json`), prints a loud warning pointing at the `.dxt` bundle, and default-skips the legacy write. Pass `--force-legacy` to override.
644
- - **New: shipping a `.dxt` bundle** — drag-drop into Claude Desktop 2026 → Settings → Extensions. Uploaded to each [GitHub Release](https://github.com/leadbay/leadclaw/releases). Dialog asks for token + region + write-toggle; no terminal required. Manifest is DXT 0.2 (`dxt_version: "0.2"`, `user_config.leadbay_token.sensitive: true`). Source for the build lives in `packages/dxt/`.
659
+ - **New: shipping a `.dxt` bundle** — drag-drop into Claude Desktop 2026 → Settings → Extensions. Uploaded to each [GitHub Release](https://github.com/leadbay/mcp/releases). Dialog asks for token + region + write-toggle; no terminal required. Manifest is DXT 0.2 (`dxt_version: "0.2"`, `user_config.leadbay_token.sensitive: true`). Source for the build lives in `packages/dxt/`.
645
660
  - **Fix**: `login` 401 errors no longer end with a dangling `:` when the backend returns an empty body. Messages now read `login failed (401) at <url> (wrong email or password?)`. 429/5xx get their own hints too. The core helper `formatLoginError` is exported from `@leadbay/core` so the MCP and ClawHub surfaces stay in sync.
646
661
  - **README**: new section on `npm install -g` EACCES (sudo / npx / nvm workarounds — common on the official nodejs.org `.pkg`), and a pointer to the `.dxt` install for Claude Desktop 2026.
647
662
  - **Also shipping** (previously merged but not yet published to npm) — `leadbay_refine_prompt` / `leadbay_set_user_prompt` now send `{ user_prompt }` instead of `{ prompt }` to `POST /user_prompt` ([product#3508](https://github.com/leadbay/product/issues/3508)). Fixes the JSON deserialization 400 Ludo hit during the 0.2.2 install session.
package/MIGRATION.md CHANGED
@@ -166,7 +166,7 @@ The legacy `leadbay-mcp install --include-write` flag is accepted but a no-op
166
166
 
167
167
  ---
168
168
 
169
- # Migration: leadclaw / leadbay-mcp 0.1.x → 0.2.0
169
+ # Migration: leadbay-mcp 0.1.x → 0.2.0
170
170
 
171
171
  This release is the autoplan-reviewed agent-experience overhaul. The OpenClaw
172
172
  plugin and MCP server gain a coherent composite-tool surface so an AI agent
@@ -277,7 +277,7 @@ additionally exposes the granular tools; most users don't need it.
277
277
 
278
278
  ### OpenClaw plugin
279
279
 
280
- In the plugin config (e.g. `openclaw config set plugins.entries.leadclaw.exposeWrite true`):
280
+ In the plugin config (e.g. `openclaw config set plugins.entries.leadbay-mcp.exposeWrite true`):
281
281
 
282
282
  ```json
283
283
  {
package/README.md CHANGED
@@ -143,11 +143,11 @@ To rotate the local MCP credential, re-run `npx -y @leadbay/mcp install --oauth`
143
143
  ### Install via the Claude Code plugin marketplace
144
144
 
145
145
  ```text
146
- /plugin marketplace add leadbay/leadclaw
146
+ /plugin marketplace add leadbay/mcp
147
147
  ```
148
148
 
149
149
  ```text
150
- /plugin install leadbay@leadbay-leadclaw
150
+ /plugin install leadbay@leadbay-mcp
151
151
  ```
152
152
 
153
153
  Claude Code prompts for Leadbay auth/config through the plugin's `userConfig`. This is equivalent to the npm/CLI install above.
@@ -157,13 +157,13 @@ The plugin install gives you **two surfaces in one shot**:
157
157
  1. **The MCP server** — registered via the plugin's `mcpServers.leadbay` block (boots `@leadbay/mcp` over stdio). This exposes the `leadbay_*` tools to the agent.
158
158
  2. **Six auto-discovered Claude Code skills** under `skills/` — `leadbay_daily_check_in`, `leadbay_research_a_domain`, `leadbay_import_file`, `leadbay_log_outreach`, `leadbay_qualify_top_n`, `leadbay_refine_audience`. These auto-trigger on natural-language matches against each skill's description ("get me leadbay leads today", "research acme.com", etc.) and dispatch to the MCP tools above by name. Each `SKILL.md` is generated by `@leadbay/promptforge` from the same `.md.tmpl` source as the MCP prompt, so the two surfaces never drift.
159
159
 
160
- You can verify the skills installed by running `/skill list` after install. To uninstall everything, `/plugin uninstall leadbay@leadbay-leadclaw` removes the MCP server registration **and** the skills together.
160
+ You can verify the skills installed by running `/skill list` after install. To uninstall everything, `/plugin uninstall leadbay@leadbay-mcp` removes the MCP server registration **and** the skills together.
161
161
 
162
162
  ### Claude Desktop
163
163
 
164
164
  The guided installer writes the local `mcpServers.leadbay` entry in `claude_desktop_config.json` with `npx -y @leadbay/mcp@latest`, like the Linux flow. Restart Claude Desktop after install.
165
165
 
166
- The `.dxt` / `.mcpb` bundle from [Releases](https://github.com/leadbay/leadclaw/releases/latest) remains available as an alternative install path.
166
+ The `.dxt` / `.mcpb` bundle from [Releases](https://github.com/leadbay/mcp/releases/latest) remains available as an alternative install path.
167
167
 
168
168
  ### `npm install -g` says "EACCES" / "permission denied"
169
169
 
@@ -551,7 +551,7 @@ The user's literal text replaces `verification.ref` in the outreach record, and
551
551
 
552
552
  ## 7. Upgrade & rotation
553
553
 
554
- **Upgrade**: the guided installer pins `@leadbay/mcp@latest`, so each MCP session already picks up the newest published version — just restart the client. (If you hand-pinned a specific version in your config, change it to `@latest` or the version you want, then restart.) **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/leadclaw/releases).
554
+ **Upgrade**: the guided installer pins `@leadbay/mcp@latest`, so each MCP session already picks up the newest published version — just restart the client. (If you hand-pinned a specific version in your config, change it to `@latest` or the version you want, then restart.) **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/mcp/releases).
555
555
 
556
556
  **Rotate local credential**: re-run `npx -y @leadbay/mcp@latest install --oauth` (or `login --oauth`) — the new credential replaces the old one in your MCP client config.
557
557
 
package/dist/bin.js CHANGED
@@ -7411,8 +7411,9 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
7411
7411
  created (name + resolved sectors/sizes as chips) and ASK the user to confirm
7412
7412
  via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
7413
7413
  "yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
7414
- On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the candidates to
7415
- pick from.
7414
+ (if \`computing_wishlist:true\`, add that leads stream in \u2014 pull in ~30s, don't
7415
+ report "empty"). On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the
7416
+ candidates to pick from.
7416
7417
 
7417
7418
  ---
7418
7419
 
@@ -7426,6 +7427,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
7426
7427
 
7427
7428
  **Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
7428
7429
 
7430
+ **Leads compute asynchronously.** When the lens has criteria, applying the filter kicks off a backend wishlist rebuild \u2014 the \`created\` result carries \`computing_wishlist:true\`. An immediate \`leadbay_pull_leads\` may read empty for a few seconds while it computes; that lens is *warming up*, not empty. Wait ~30s before the first pull (or offer the user a re-pull). A criteria-less clone inherits the base lens's leads right away (\`computing_wishlist:false\`).
7431
+
7429
7432
  WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
7430
7433
 
7431
7434
  WHEN NOT TO USE: to EDIT an existing lens \u2014 use \`leadbay_adjust_audience\` (pass \`lensName\` to target one by name). Not for listing/switching (\`leadbay_my_lenses\`) or topping up (\`leadbay_extend_lens\`).
@@ -7479,17 +7482,23 @@ User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutual
7479
7482
  Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
7480
7483
  follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
7481
7484
 
7482
- | Observation | Suggest | Calls |
7483
- |-----------------------------------|------------------------------------------|--------------------------------------------------------|
7484
- | \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
7485
- | \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
7486
- | Lens created | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
7487
- | Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
7488
- | Lens created | "Leave it; keep my current lens active" | (no call) |
7489
- | \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
7490
-
7491
- If nothing fits, default to "switch to the new lens and pull leads" \u2014 never
7492
- invent a tool that doesn't exist.
7485
+ **\`created\` with \`computing_wishlist: true\`** \u2014 the new lens's leads are being
7486
+ (re)computed asynchronously. Do NOT fire an immediate \`leadbay_pull_leads\` and
7487
+ report "empty" \u2014 the lens is warming up, not empty. Tell the user the lens was
7488
+ created and its leads are streaming in, and offer to pull in ~30s.
7489
+
7490
+ | Observation | Suggest | Calls |
7491
+ |-----------------------------------------|------------------------------------------|--------------------------------------------------------|
7492
+ | \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
7493
+ | \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
7494
+ | Lens created, \`computing_wishlist=true\` | "Give it ~30s, then pull leads (the wishlist is still computing)" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` after ~30s |
7495
+ | Lens created (no criteria) | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
7496
+ | Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
7497
+ | Lens created | "Leave it; keep my current lens active" | (no call) |
7498
+ | \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
7499
+
7500
+ If nothing fits, default to "switch to the new lens and pull leads in ~30s
7501
+ (the wishlist may still be computing)" \u2014 never invent a tool that doesn't exist.
7493
7502
  `;
7494
7503
  leadbay_open_billing_portal = `Generate a one-shot Stripe customer-portal URL. Wraps \`GET /1.6/stripe/portal\` \u2192 \`{url}\`. Returns a fresh Stripe-hosted URL the user can open to manage their existing Leadbay subscription: change plan tier, swap payment method, view invoices. The agent does NOT make subscription changes itself \u2014 it surfaces the URL and lets the user act.
7495
7504
 
@@ -7951,7 +7960,8 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
7951
7960
 
7952
7961
  ## RENDER (quick)
7953
7962
 
7954
- 3-col markdown table sorted by \`score\` desc \u2014 DO NOT print the numeric
7963
+ 3-col markdown table in the order the tool returns them (the Discover-tab
7964
+ order \u2014 do NOT re-sort by score) \u2014 DO NOT print the numeric
7955
7965
  score. Col 1 = inline-code 10-segment bar (\`\u25B0\` firmographic, \`\u2756\` AI
7956
7966
  booster cap at the right end of the filled run, \`\u25B1\` empty;
7957
7967
  filled=round(score/10), ai=round(avg_boost/3.3)) + \`<br>\` + linked
@@ -7978,7 +7988,7 @@ The active lens can change between calls (5-min cache + backend \`last_requested
7978
7988
 
7979
7989
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
7980
7990
 
7981
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
7991
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
7982
7992
 
7983
7993
  ## Score-bar (10-segment, inline-code wrapped)
7984
7994
 
@@ -8084,6 +8094,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
8084
8094
  | Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
8085
8095
  | Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
8086
8096
  | \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
8097
+ | Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
8087
8098
  | User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
8088
8099
  | Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
8089
8100
  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.
@@ -9396,12 +9407,12 @@ var init_login = __esm({
9396
9407
  execute: async (client, params, ctx) => {
9397
9408
  const cleanPassword = params.password.replace(/\\(.)/g, "$1");
9398
9409
  const startWith = client.region === "fr" ? "fr" : "us";
9399
- ctx?.logger?.info?.(`LeadClaw login: email=${params.email} startRegion=${startWith}`);
9410
+ ctx?.logger?.info?.(`Leadbay login: email=${params.email} startRegion=${startWith}`);
9400
9411
  try {
9401
9412
  const result = await resolveRegion(params.email, cleanPassword, startWith);
9402
9413
  if (client.baseUrl !== result.baseUrl) {
9403
9414
  client.setBaseUrl(result.baseUrl, result.region);
9404
- ctx?.logger?.info?.(`LeadClaw login: switched to region=${result.region} (account is in the ${result.region.toUpperCase()} backend)`);
9415
+ ctx?.logger?.info?.(`Leadbay login: switched to region=${result.region} (account is in the ${result.region.toUpperCase()} backend)`);
9405
9416
  }
9406
9417
  client.setToken(result.token);
9407
9418
  client.prefetchOrgData().catch(() => {
@@ -9413,7 +9424,7 @@ var init_login = __esm({
9413
9424
  verified: result.verified
9414
9425
  };
9415
9426
  } catch (err) {
9416
- ctx?.logger?.error?.(`LeadClaw login: failed: ${err?.message}`);
9427
+ ctx?.logger?.error?.(`Leadbay login: failed: ${err?.message}`);
9417
9428
  return {
9418
9429
  error: true,
9419
9430
  code: "LOGIN_FAILED",
@@ -11413,7 +11424,7 @@ async function pollRecordsToTerminal(client, importId, budgetMs, expectedRowCoun
11413
11424
  }
11414
11425
  }
11415
11426
  if (!exhaustedPagination) {
11416
- throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/leadclaw/issues.`, `GET /imports/${importId}/records`);
11427
+ throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/mcp/issues.`, `GET /imports/${importId}/records`);
11417
11428
  }
11418
11429
  const snapshot = { total, transient };
11419
11430
  const settled = transient === 0;
@@ -14474,9 +14485,27 @@ function summarise(responses) {
14474
14485
  return { answered, total, avg_qualification_boost: avg, best_response_excerpt: excerpt };
14475
14486
  }
14476
14487
  function buildPullLeadsNextSteps(args) {
14477
- const { leadCount, hasMore, nextPage } = args;
14478
- if (leadCount <= 0)
14488
+ const { leadCount, hasMore, nextPage, computingWishlist, computingScores } = args;
14489
+ if (leadCount <= 0) {
14490
+ if (computingWishlist || computingScores) {
14491
+ return {
14492
+ question: "This lens is still warming up. What next?",
14493
+ options: [
14494
+ {
14495
+ label: "Re-pull in ~30s",
14496
+ description: "Leads are still being computed for this lens \u2014 wait ~30s, then pull again to see them.",
14497
+ kind: "repull_computing"
14498
+ },
14499
+ {
14500
+ label: "Refine audience",
14501
+ description: "Adjust the lens audience / filters (sector, size, prompt) \u2014 useful if it's coming back sparse.",
14502
+ kind: "refine_audience"
14503
+ }
14504
+ ]
14505
+ };
14506
+ }
14479
14507
  return null;
14508
+ }
14480
14509
  const options = [];
14481
14510
  options.push({
14482
14511
  label: "Triage board",
@@ -14574,7 +14603,7 @@ var init_pull_leads = __esm({
14574
14603
  },
14575
14604
  next_steps: {
14576
14605
  type: ["object", "null"],
14577
- description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. null only when the batch is empty.",
14606
+ description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
14578
14607
  properties: {
14579
14608
  question: { type: "string" },
14580
14609
  options: {
@@ -14665,7 +14694,13 @@ var init_pull_leads = __esm({
14665
14694
  const hasMore = currentPage < totalPages - 1;
14666
14695
  const nextPage = hasMore ? currentPage + 1 : null;
14667
14696
  const leadCount = res.items.length;
14668
- const nextSteps = buildPullLeadsNextSteps({ leadCount, hasMore, nextPage });
14697
+ const nextSteps = buildPullLeadsNextSteps({
14698
+ leadCount,
14699
+ hasMore,
14700
+ nextPage,
14701
+ computingWishlist: res.computing_wishlist,
14702
+ computingScores: res.computing_scores
14703
+ });
14669
14704
  return withAgentMemoryMeta(client, {
14670
14705
  lens: { id: lensId },
14671
14706
  leads: res.items.map((lead) => ({
@@ -21818,6 +21853,10 @@ var init_new_lens = __esm({
21818
21853
  items: { type: "object" }
21819
21854
  },
21820
21855
  filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
21856
+ computing_wishlist: {
21857
+ type: "boolean",
21858
+ description: "On 'created': true when the lens has criteria and its wishlist is (re)computing asynchronously \u2014 an immediate leadbay_pull_leads may read empty; wait ~30s. False for a criteria-less clone (it inherits the base lens's leads immediately)."
21859
+ },
21821
21860
  message: { type: "string" },
21822
21861
  _meta: { type: "object" }
21823
21862
  },
@@ -21924,7 +21963,13 @@ var init_new_lens = __esm({
21924
21963
  status: "created",
21925
21964
  lens: { id: created.id, name: created.name },
21926
21965
  filter_applied: merged,
21927
- message: `Created "${created.name}".`,
21966
+ // Applying the filter queued a backend wishlist refresh (#3833). The lens
21967
+ // leads are (re)computed asynchronously, so an immediate pull_leads can
21968
+ // read empty for a few seconds. Signal that here (flag-independent — the
21969
+ // backend's own computing_wishlist can lag right after create) so the
21970
+ // agent waits ~30s instead of reporting an empty lens.
21971
+ computing_wishlist: hasCriteria,
21972
+ message: hasCriteria ? `Created "${created.name}". Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them (an immediate pull may show an empty lens while the wishlist computes).` : `Created "${created.name}".`,
21928
21973
  _meta: { region: client.region }
21929
21974
  };
21930
21975
  }
@@ -23223,7 +23268,7 @@ Call \`leadbay_pull_leads\` on the resolved lens. **Capture \`response.lens.id\`
23223
23268
 
23224
23269
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
23225
23270
 
23226
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
23271
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
23227
23272
 
23228
23273
  ## Score-bar (10-segment, inline-code wrapped)
23229
23274
 
@@ -23422,7 +23467,7 @@ If the prompt's body and the tool's RENDERING appear to conflict, the tool's REN
23422
23467
 
23423
23468
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
23424
23469
 
23425
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
23470
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
23426
23471
 
23427
23472
  ## Score-bar (10-segment, inline-code wrapped)
23428
23473
 
@@ -23552,6 +23597,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
23552
23597
  | Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
23553
23598
  | Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
23554
23599
  | \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
23600
+ | Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
23555
23601
  | User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
23556
23602
  | Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
23557
23603
  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.
@@ -24066,7 +24112,7 @@ When \`bulk_qualify_leads\` returns, surface results in two parts.
24066
24112
 
24067
24113
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
24068
24114
 
24069
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
24115
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
24070
24116
 
24071
24117
  ## Score-bar (10-segment, inline-code wrapped)
24072
24118
 
@@ -25174,7 +25220,7 @@ function getCachedUpdateInfo() {
25174
25220
  function getInFlightCheck() {
25175
25221
  return inFlightCheck;
25176
25222
  }
25177
- var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
25223
+ var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/mcp/releases/latest";
25178
25224
  var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
25179
25225
  var FETCH_TIMEOUT_MS = 5e3;
25180
25226
  var USER_AGENT = "leadbay-mcp-update-check";
@@ -25754,7 +25800,7 @@ function buildServer(client, opts = {}) {
25754
25800
  }
25755
25801
  const exposedNames = new Set(toolByName.keys());
25756
25802
  const server = new Server(
25757
- { name: "leadbay", version: opts.version ?? "0.0.0-dev" },
25803
+ { name: "leadbay-mcp", version: opts.version ?? "0.0.0-dev" },
25758
25804
  {
25759
25805
  capabilities: {
25760
25806
  tools: {},
@@ -27536,6 +27582,17 @@ function browserOpenCandidates(url) {
27536
27582
  { cmd: "xdg-open", args: [url] }
27537
27583
  ];
27538
27584
  }
27585
+ function windowsFallbackCandidates(url) {
27586
+ const sysRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
27587
+ const psLiteral = `'${url.replace(/'/g, "''")}'`;
27588
+ return [
27589
+ { cmd: `${sysRoot}\\System32\\rundll32.exe`, args: ["url.dll,FileProtocolHandler", url] },
27590
+ {
27591
+ cmd: `${sysRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`,
27592
+ args: ["-NoProfile", "-NonInteractive", "-Command", "Start-Process", psLiteral]
27593
+ }
27594
+ ];
27595
+ }
27539
27596
  function browserLaunchEnv(debug) {
27540
27597
  const env = { ...process.env };
27541
27598
  if (process.platform !== "linux") return env;
@@ -27600,7 +27657,7 @@ function browserLaunchEnv(debug) {
27600
27657
  return env;
27601
27658
  }
27602
27659
  async function openInBrowser(url, debug) {
27603
- const candidates = browserOpenCandidates(url);
27660
+ const candidates = process.platform === "win32" ? [...browserOpenCandidates(url), ...windowsFallbackCandidates(url)] : browserOpenCandidates(url);
27604
27661
  const launchEnv = browserLaunchEnv(debug);
27605
27662
  debug?.(
27606
27663
  `openInBrowser: platform=${process.platform} DISPLAY=${launchEnv.DISPLAY ?? "<unset>"} WAYLAND=${launchEnv.WAYLAND_DISPLAY ?? "<unset>"} DBUS=${launchEnv.DBUS_SESSION_BUS_ADDRESS ? "set" : "<unset>"} candidates=[${candidates.map((c) => c.cmd).join(", ")}]`
@@ -27609,14 +27666,43 @@ async function openInBrowser(url, debug) {
27609
27666
  for (const { cmd, args } of candidates) {
27610
27667
  try {
27611
27668
  await new Promise((resolve, reject) => {
27612
- const spawnOpts = { stdio: "ignore", detached: true, env: launchEnv };
27613
- if (process.platform === "win32") spawnOpts.windowsVerbatimArguments = true;
27669
+ const isWin = process.platform === "win32";
27670
+ const spawnOpts = { stdio: "ignore", detached: !isWin, env: launchEnv };
27671
+ if (isWin) spawnOpts.windowsVerbatimArguments = true;
27614
27672
  const child = spawn3(cmd, args, spawnOpts);
27615
27673
  child.on("error", reject);
27616
- child.on("spawn", () => {
27617
- debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
27674
+ if (!isWin) {
27675
+ child.on("spawn", () => {
27676
+ debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
27677
+ child.unref();
27678
+ resolve();
27679
+ });
27680
+ return;
27681
+ }
27682
+ const isCmd = /cmd(\.exe)?$/i.test(cmd);
27683
+ const budgetMs = isCmd ? 800 : 1200;
27684
+ let settled = false;
27685
+ const finish = (fn) => {
27686
+ if (settled) return;
27687
+ settled = true;
27688
+ fn();
27689
+ };
27690
+ const timer = setTimeout(() => {
27691
+ debug?.(`exit-wait timeout (${budgetMs}ms): ${cmd} \u2014 assuming launched`);
27618
27692
  child.unref();
27619
- resolve();
27693
+ finish(resolve);
27694
+ }, budgetMs);
27695
+ timer.unref?.();
27696
+ child.on("spawn", () => debug?.(`spawn OK: ${cmd} (pid=${child.pid}) \u2014 awaiting exit`));
27697
+ child.on("exit", (code) => {
27698
+ clearTimeout(timer);
27699
+ if (code === 0 || code === null) {
27700
+ debug?.(`exit ${code}: ${cmd} \u2014 launched OK`);
27701
+ finish(resolve);
27702
+ } else {
27703
+ debug?.(`exit ${code}: ${cmd} \u2014 treating as failed, next candidate`);
27704
+ finish(() => reject(new Error(`${cmd} exited ${code}`)));
27705
+ }
27620
27706
  });
27621
27707
  });
27622
27708
  return;
@@ -27721,7 +27807,7 @@ var OAUTH_BASE_URLS = {
27721
27807
  fr: "https://staging.api.leadbay.app"
27722
27808
  }
27723
27809
  };
27724
- var VERSION = "0.23.9";
27810
+ var VERSION = "0.23.11";
27725
27811
  var HELP = `
27726
27812
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
27727
27813
 
@@ -27788,7 +27874,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
27788
27874
  }
27789
27875
 
27790
27876
  DOCS
27791
- https://github.com/leadbay/leadclaw#readme
27877
+ https://github.com/leadbay/mcp#readme
27792
27878
  `.trim();
27793
27879
  function makeStderrLogger(level) {
27794
27880
  const rank = { debug: 0, info: 1, error: 2 };
@@ -28998,11 +29084,11 @@ async function main() {
28998
29084
  }
28999
29085
  }
29000
29086
  if (browserOpenInFlight) {
29001
- bootstrapDebug(`shutdown(code=${code}) browser-open still in flight \u2014 waiting up to 1.5s`);
29087
+ bootstrapDebug(`shutdown(code=${code}) browser-open still in flight \u2014 waiting up to 3s`);
29002
29088
  try {
29003
29089
  await Promise.race([
29004
29090
  browserOpenInFlight,
29005
- new Promise((r) => setTimeout(r, 1500))
29091
+ new Promise((r) => setTimeout(r, 3e3))
29006
29092
  ]);
29007
29093
  } catch {
29008
29094
  }
@@ -88,7 +88,7 @@ Call \`leadbay_pull_leads\` on the resolved lens. **Capture \`response.lens.id\`
88
88
 
89
89
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
90
90
 
91
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
91
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
92
92
 
93
93
  ## Score-bar (10-segment, inline-code wrapped)
94
94
 
@@ -287,7 +287,7 @@ If the prompt's body and the tool's RENDERING appear to conflict, the tool's REN
287
287
 
288
288
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
289
289
 
290
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
290
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
291
291
 
292
292
  ## Score-bar (10-segment, inline-code wrapped)
293
293
 
@@ -417,6 +417,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
417
417
  | Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
418
418
  | Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
419
419
  | \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
420
+ | Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
420
421
  | User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
421
422
  | Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
422
423
  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.
@@ -931,7 +932,7 @@ When \`bulk_qualify_leads\` returns, surface results in two parts.
931
932
 
932
933
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
933
934
 
934
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
935
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
935
936
 
936
937
  ## Score-bar (10-segment, inline-code wrapped)
937
938
 
@@ -8540,8 +8541,9 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
8540
8541
  created (name + resolved sectors/sizes as chips) and ASK the user to confirm
8541
8542
  via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
8542
8543
  "yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
8543
- On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the candidates to
8544
- pick from.
8544
+ (if \`computing_wishlist:true\`, add that leads stream in \u2014 pull in ~30s, don't
8545
+ report "empty"). On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the
8546
+ candidates to pick from.
8545
8547
 
8546
8548
  ---
8547
8549
 
@@ -8555,6 +8557,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
8555
8557
 
8556
8558
  **Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
8557
8559
 
8560
+ **Leads compute asynchronously.** When the lens has criteria, applying the filter kicks off a backend wishlist rebuild \u2014 the \`created\` result carries \`computing_wishlist:true\`. An immediate \`leadbay_pull_leads\` may read empty for a few seconds while it computes; that lens is *warming up*, not empty. Wait ~30s before the first pull (or offer the user a re-pull). A criteria-less clone inherits the base lens's leads right away (\`computing_wishlist:false\`).
8561
+
8558
8562
  WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
8559
8563
 
8560
8564
  WHEN NOT TO USE: to EDIT an existing lens \u2014 use \`leadbay_adjust_audience\` (pass \`lensName\` to target one by name). Not for listing/switching (\`leadbay_my_lenses\`) or topping up (\`leadbay_extend_lens\`).
@@ -8608,17 +8612,23 @@ User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutual
8608
8612
  Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
8609
8613
  follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
8610
8614
 
8611
- | Observation | Suggest | Calls |
8612
- |-----------------------------------|------------------------------------------|--------------------------------------------------------|
8613
- | \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
8614
- | \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
8615
- | Lens created | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
8616
- | Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
8617
- | Lens created | "Leave it; keep my current lens active" | (no call) |
8618
- | \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
8619
-
8620
- If nothing fits, default to "switch to the new lens and pull leads" \u2014 never
8621
- invent a tool that doesn't exist.
8615
+ **\`created\` with \`computing_wishlist: true\`** \u2014 the new lens's leads are being
8616
+ (re)computed asynchronously. Do NOT fire an immediate \`leadbay_pull_leads\` and
8617
+ report "empty" \u2014 the lens is warming up, not empty. Tell the user the lens was
8618
+ created and its leads are streaming in, and offer to pull in ~30s.
8619
+
8620
+ | Observation | Suggest | Calls |
8621
+ |-----------------------------------------|------------------------------------------|--------------------------------------------------------|
8622
+ | \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
8623
+ | \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
8624
+ | Lens created, \`computing_wishlist=true\` | "Give it ~30s, then pull leads (the wishlist is still computing)" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` after ~30s |
8625
+ | Lens created (no criteria) | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
8626
+ | Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
8627
+ | Lens created | "Leave it; keep my current lens active" | (no call) |
8628
+ | \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
8629
+
8630
+ If nothing fits, default to "switch to the new lens and pull leads in ~30s
8631
+ (the wishlist may still be computing)" \u2014 never invent a tool that doesn't exist.
8622
8632
  `;
8623
8633
  var leadbay_open_billing_portal = `Generate a one-shot Stripe customer-portal URL. Wraps \`GET /1.6/stripe/portal\` \u2192 \`{url}\`. Returns a fresh Stripe-hosted URL the user can open to manage their existing Leadbay subscription: change plan tier, swap payment method, view invoices. The agent does NOT make subscription changes itself \u2014 it surfaces the URL and lets the user act.
8624
8634
 
@@ -9080,7 +9090,8 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
9080
9090
 
9081
9091
  ## RENDER (quick)
9082
9092
 
9083
- 3-col markdown table sorted by \`score\` desc \u2014 DO NOT print the numeric
9093
+ 3-col markdown table in the order the tool returns them (the Discover-tab
9094
+ order \u2014 do NOT re-sort by score) \u2014 DO NOT print the numeric
9084
9095
  score. Col 1 = inline-code 10-segment bar (\`\u25B0\` firmographic, \`\u2756\` AI
9085
9096
  booster cap at the right end of the filled run, \`\u25B1\` empty;
9086
9097
  filled=round(score/10), ai=round(avg_boost/3.3)) + \`<br>\` + linked
@@ -9107,7 +9118,7 @@ The active lens can change between calls (5-min cache + backend \`last_requested
9107
9118
 
9108
9119
  ## RENDERING \u2014 markdown table, three columns, score-bar driven
9109
9120
 
9110
- Present the response as a markdown table sorted by \`score\` descending, with exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
9121
+ Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
9111
9122
 
9112
9123
  ## Score-bar (10-segment, inline-code wrapped)
9113
9124
 
@@ -9213,6 +9224,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
9213
9224
  | Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
9214
9225
  | Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
9215
9226
  | \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
9227
+ | Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
9216
9228
  | User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
9217
9229
  | Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
9218
9230
  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.
@@ -10517,12 +10529,12 @@ var login = {
10517
10529
  execute: async (client, params, ctx) => {
10518
10530
  const cleanPassword = params.password.replace(/\\(.)/g, "$1");
10519
10531
  const startWith = client.region === "fr" ? "fr" : "us";
10520
- ctx?.logger?.info?.(`LeadClaw login: email=${params.email} startRegion=${startWith}`);
10532
+ ctx?.logger?.info?.(`Leadbay login: email=${params.email} startRegion=${startWith}`);
10521
10533
  try {
10522
10534
  const result = await resolveRegion(params.email, cleanPassword, startWith);
10523
10535
  if (client.baseUrl !== result.baseUrl) {
10524
10536
  client.setBaseUrl(result.baseUrl, result.region);
10525
- ctx?.logger?.info?.(`LeadClaw login: switched to region=${result.region} (account is in the ${result.region.toUpperCase()} backend)`);
10537
+ ctx?.logger?.info?.(`Leadbay login: switched to region=${result.region} (account is in the ${result.region.toUpperCase()} backend)`);
10526
10538
  }
10527
10539
  client.setToken(result.token);
10528
10540
  client.prefetchOrgData().catch(() => {
@@ -10534,7 +10546,7 @@ var login = {
10534
10546
  verified: result.verified
10535
10547
  };
10536
10548
  } catch (err) {
10537
- ctx?.logger?.error?.(`LeadClaw login: failed: ${err?.message}`);
10549
+ ctx?.logger?.error?.(`Leadbay login: failed: ${err?.message}`);
10538
10550
  return {
10539
10551
  error: true,
10540
10552
  code: "LOGIN_FAILED",
@@ -12421,7 +12433,7 @@ async function pollRecordsToTerminal(client, importId, budgetMs, expectedRowCoun
12421
12433
  }
12422
12434
  }
12423
12435
  if (!exhaustedPagination) {
12424
- throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/leadclaw/issues.`, `GET /imports/${importId}/records`);
12436
+ throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/mcp/issues.`, `GET /imports/${importId}/records`);
12425
12437
  }
12426
12438
  const snapshot = { total, transient };
12427
12439
  const settled = transient === 0;
@@ -15151,9 +15163,27 @@ function summarise(responses) {
15151
15163
  return { answered, total, avg_qualification_boost: avg, best_response_excerpt: excerpt };
15152
15164
  }
15153
15165
  function buildPullLeadsNextSteps(args) {
15154
- const { leadCount, hasMore, nextPage } = args;
15155
- if (leadCount <= 0)
15166
+ const { leadCount, hasMore, nextPage, computingWishlist, computingScores } = args;
15167
+ if (leadCount <= 0) {
15168
+ if (computingWishlist || computingScores) {
15169
+ return {
15170
+ question: "This lens is still warming up. What next?",
15171
+ options: [
15172
+ {
15173
+ label: "Re-pull in ~30s",
15174
+ description: "Leads are still being computed for this lens \u2014 wait ~30s, then pull again to see them.",
15175
+ kind: "repull_computing"
15176
+ },
15177
+ {
15178
+ label: "Refine audience",
15179
+ description: "Adjust the lens audience / filters (sector, size, prompt) \u2014 useful if it's coming back sparse.",
15180
+ kind: "refine_audience"
15181
+ }
15182
+ ]
15183
+ };
15184
+ }
15156
15185
  return null;
15186
+ }
15157
15187
  const options = [];
15158
15188
  options.push({
15159
15189
  label: "Triage board",
@@ -15245,7 +15275,7 @@ var pullLeads = {
15245
15275
  },
15246
15276
  next_steps: {
15247
15277
  type: ["object", "null"],
15248
- description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. null only when the batch is empty.",
15278
+ description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
15249
15279
  properties: {
15250
15280
  question: { type: "string" },
15251
15281
  options: {
@@ -15336,7 +15366,13 @@ var pullLeads = {
15336
15366
  const hasMore = currentPage < totalPages - 1;
15337
15367
  const nextPage = hasMore ? currentPage + 1 : null;
15338
15368
  const leadCount = res.items.length;
15339
- const nextSteps = buildPullLeadsNextSteps({ leadCount, hasMore, nextPage });
15369
+ const nextSteps = buildPullLeadsNextSteps({
15370
+ leadCount,
15371
+ hasMore,
15372
+ nextPage,
15373
+ computingWishlist: res.computing_wishlist,
15374
+ computingScores: res.computing_scores
15375
+ });
15340
15376
  return withAgentMemoryMeta(client, {
15341
15377
  lens: { id: lensId },
15342
15378
  leads: res.items.map((lead) => ({
@@ -21610,6 +21646,10 @@ var newLens = {
21610
21646
  items: { type: "object" }
21611
21647
  },
21612
21648
  filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
21649
+ computing_wishlist: {
21650
+ type: "boolean",
21651
+ description: "On 'created': true when the lens has criteria and its wishlist is (re)computing asynchronously \u2014 an immediate leadbay_pull_leads may read empty; wait ~30s. False for a criteria-less clone (it inherits the base lens's leads immediately)."
21652
+ },
21613
21653
  message: { type: "string" },
21614
21654
  _meta: { type: "object" }
21615
21655
  },
@@ -21716,7 +21756,13 @@ var newLens = {
21716
21756
  status: "created",
21717
21757
  lens: { id: created.id, name: created.name },
21718
21758
  filter_applied: merged,
21719
- message: `Created "${created.name}".`,
21759
+ // Applying the filter queued a backend wishlist refresh (#3833). The lens
21760
+ // leads are (re)computed asynchronously, so an immediate pull_leads can
21761
+ // read empty for a few seconds. Signal that here (flag-independent — the
21762
+ // backend's own computing_wishlist can lag right after create) so the
21763
+ // agent waits ~30s instead of reporting an empty lens.
21764
+ computing_wishlist: hasCriteria,
21765
+ message: hasCriteria ? `Created "${created.name}". Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them (an immediate pull may show an empty lens while the wishlist computes).` : `Created "${created.name}".`,
21720
21766
  _meta: { region: client.region }
21721
21767
  };
21722
21768
  }
@@ -22771,7 +22817,7 @@ function getCachedUpdateInfo() {
22771
22817
  function getInFlightCheck() {
22772
22818
  return inFlightCheck;
22773
22819
  }
22774
- var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
22820
+ var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/mcp/releases/latest";
22775
22821
  var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
22776
22822
  var FETCH_TIMEOUT_MS = 5e3;
22777
22823
  var USER_AGENT = "leadbay-mcp-update-check";
@@ -23329,7 +23375,7 @@ function buildServer(client, opts = {}) {
23329
23375
  }
23330
23376
  const exposedNames = new Set(toolByName.keys());
23331
23377
  const server = new Server(
23332
- { name: "leadbay", version: opts.version ?? "0.0.0-dev" },
23378
+ { name: "leadbay-mcp", version: opts.version ?? "0.0.0-dev" },
23333
23379
  {
23334
23380
  capabilities: {
23335
23381
  tools: {},
@@ -24016,7 +24062,7 @@ function parseWriteEnv(env = process.env) {
24016
24062
  }
24017
24063
 
24018
24064
  // src/http-server.ts
24019
- var VERSION = true ? "0.23.9" : "0.0.0-dev";
24065
+ var VERSION = true ? "0.23.11" : "0.0.0-dev";
24020
24066
  var PORT = Number(process.env.PORT ?? 8080);
24021
24067
  var HOST = process.env.HOST ?? "0.0.0.0";
24022
24068
  var sseSessions = /* @__PURE__ */ new Map();
@@ -24058,6 +24104,24 @@ function applyCors(c) {
24058
24104
  c.header("Access-Control-Allow-Origin", "*");
24059
24105
  c.header("Access-Control-Expose-Headers", "WWW-Authenticate");
24060
24106
  }
24107
+ var DEFAULT_ALLOWED_ORIGINS = [
24108
+ "https://claude.ai",
24109
+ "https://claude.com",
24110
+ "https://chatgpt.com"
24111
+ ];
24112
+ function allowedOrigins() {
24113
+ const extra = (process.env.LEADBAY_MCP_ALLOWED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
24114
+ return /* @__PURE__ */ new Set([...DEFAULT_ALLOWED_ORIGINS, ...extra]);
24115
+ }
24116
+ function rejectForeignOrigin(c) {
24117
+ const origin = c.req.header("origin");
24118
+ if (!origin) return null;
24119
+ const allowed = allowedOrigins();
24120
+ allowed.add(requestOrigin(c));
24121
+ if (allowed.has(origin)) return null;
24122
+ applyCors(c);
24123
+ return c.json({ error: "forbidden origin" }, 403);
24124
+ }
24061
24125
  function servePrm(c, resourcePath) {
24062
24126
  applyCors(c);
24063
24127
  c.header("Cache-Control", "public, max-age=3600");
@@ -24096,6 +24160,8 @@ app.use("/mcp", MCP_BODY_LIMIT);
24096
24160
  app.use("/fr/mcp", MCP_BODY_LIMIT);
24097
24161
  app.use("/messages", MCP_BODY_LIMIT);
24098
24162
  async function handleStreamable(c, resourcePath) {
24163
+ const foreign = rejectForeignOrigin(c);
24164
+ if (foreign) return foreign;
24099
24165
  const token = extractBearer(c.req.header("authorization"));
24100
24166
  const resolved = await resolveClientFromToken(token);
24101
24167
  if (resolved.authState === "missing" || resolved.authState === "expired") {
@@ -24141,6 +24207,8 @@ async function handleStreamable(c, resourcePath) {
24141
24207
  app.all("/mcp", (c) => handleStreamable(c, "/mcp"));
24142
24208
  app.all("/fr/mcp", (c) => handleStreamable(c, "/fr/mcp"));
24143
24209
  async function handleSse(c, resourcePath) {
24210
+ const foreign = rejectForeignOrigin(c);
24211
+ if (foreign) return foreign;
24144
24212
  const token = extractBearer(c.req.header("authorization"));
24145
24213
  const resolved = await resolveClientFromToken(token);
24146
24214
  if (resolved.authState === "missing" || resolved.authState === "expired") {
@@ -24162,6 +24230,8 @@ async function handleSse(c, resourcePath) {
24162
24230
  app.get("/sse", (c) => handleSse(c, "/sse"));
24163
24231
  app.get("/fr/sse", (c) => handleSse(c, "/fr/sse"));
24164
24232
  app.post("/messages", async (c) => {
24233
+ const foreign = rejectForeignOrigin(c);
24234
+ if (foreign) return foreign;
24165
24235
  const sessionId = c.req.query("sessionId");
24166
24236
  if (!sessionId) {
24167
24237
  return c.json({ error: "missing sessionId" }, 400);
@@ -867,6 +867,17 @@ function browserOpenCandidates(url) {
867
867
  { cmd: "xdg-open", args: [url] }
868
868
  ];
869
869
  }
870
+ function windowsFallbackCandidates(url) {
871
+ const sysRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
872
+ const psLiteral = `'${url.replace(/'/g, "''")}'`;
873
+ return [
874
+ { cmd: `${sysRoot}\\System32\\rundll32.exe`, args: ["url.dll,FileProtocolHandler", url] },
875
+ {
876
+ cmd: `${sysRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`,
877
+ args: ["-NoProfile", "-NonInteractive", "-Command", "Start-Process", psLiteral]
878
+ }
879
+ ];
880
+ }
870
881
  function browserLaunchEnv(debug) {
871
882
  const env = { ...process.env };
872
883
  if (process.platform !== "linux") return env;
@@ -931,7 +942,7 @@ function browserLaunchEnv(debug) {
931
942
  return env;
932
943
  }
933
944
  async function openInBrowser(url, debug) {
934
- const candidates = browserOpenCandidates(url);
945
+ const candidates = process.platform === "win32" ? [...browserOpenCandidates(url), ...windowsFallbackCandidates(url)] : browserOpenCandidates(url);
935
946
  const launchEnv = browserLaunchEnv(debug);
936
947
  debug?.(
937
948
  `openInBrowser: platform=${process.platform} DISPLAY=${launchEnv.DISPLAY ?? "<unset>"} WAYLAND=${launchEnv.WAYLAND_DISPLAY ?? "<unset>"} DBUS=${launchEnv.DBUS_SESSION_BUS_ADDRESS ? "set" : "<unset>"} candidates=[${candidates.map((c) => c.cmd).join(", ")}]`
@@ -940,14 +951,43 @@ async function openInBrowser(url, debug) {
940
951
  for (const { cmd, args } of candidates) {
941
952
  try {
942
953
  await new Promise((resolve, reject) => {
943
- const spawnOpts = { stdio: "ignore", detached: true, env: launchEnv };
944
- if (process.platform === "win32") spawnOpts.windowsVerbatimArguments = true;
954
+ const isWin = process.platform === "win32";
955
+ const spawnOpts = { stdio: "ignore", detached: !isWin, env: launchEnv };
956
+ if (isWin) spawnOpts.windowsVerbatimArguments = true;
945
957
  const child = spawn3(cmd, args, spawnOpts);
946
958
  child.on("error", reject);
947
- child.on("spawn", () => {
948
- debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
959
+ if (!isWin) {
960
+ child.on("spawn", () => {
961
+ debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
962
+ child.unref();
963
+ resolve();
964
+ });
965
+ return;
966
+ }
967
+ const isCmd = /cmd(\.exe)?$/i.test(cmd);
968
+ const budgetMs = isCmd ? 800 : 1200;
969
+ let settled = false;
970
+ const finish = (fn) => {
971
+ if (settled) return;
972
+ settled = true;
973
+ fn();
974
+ };
975
+ const timer = setTimeout(() => {
976
+ debug?.(`exit-wait timeout (${budgetMs}ms): ${cmd} \u2014 assuming launched`);
949
977
  child.unref();
950
- resolve();
978
+ finish(resolve);
979
+ }, budgetMs);
980
+ timer.unref?.();
981
+ child.on("spawn", () => debug?.(`spawn OK: ${cmd} (pid=${child.pid}) \u2014 awaiting exit`));
982
+ child.on("exit", (code) => {
983
+ clearTimeout(timer);
984
+ if (code === 0 || code === null) {
985
+ debug?.(`exit ${code}: ${cmd} \u2014 launched OK`);
986
+ finish(resolve);
987
+ } else {
988
+ debug?.(`exit ${code}: ${cmd} \u2014 treating as failed, next candidate`);
989
+ finish(() => reject(new Error(`${cmd} exited ${code}`)));
990
+ }
951
991
  });
952
992
  });
953
993
  return;
@@ -1764,7 +1804,7 @@ var init_installer_gui = __esm({
1764
1804
  init_install_dxt();
1765
1805
  init_install_shared();
1766
1806
  init_oauth();
1767
- VERSION = true ? "0.23.9" : "0.0.0-dev";
1807
+ VERSION = true ? "0.23.11" : "0.0.0-dev";
1768
1808
  MESSAGES = {
1769
1809
  en: {
1770
1810
  installer: {
@@ -853,6 +853,17 @@ function browserOpenCandidates(url) {
853
853
  { cmd: "xdg-open", args: [url] }
854
854
  ];
855
855
  }
856
+ function windowsFallbackCandidates(url) {
857
+ const sysRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
858
+ const psLiteral = `'${url.replace(/'/g, "''")}'`;
859
+ return [
860
+ { cmd: `${sysRoot}\\System32\\rundll32.exe`, args: ["url.dll,FileProtocolHandler", url] },
861
+ {
862
+ cmd: `${sysRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`,
863
+ args: ["-NoProfile", "-NonInteractive", "-Command", "Start-Process", psLiteral]
864
+ }
865
+ ];
866
+ }
856
867
  function browserLaunchEnv(debug) {
857
868
  const env = { ...process.env };
858
869
  if (process.platform !== "linux") return env;
@@ -917,7 +928,7 @@ function browserLaunchEnv(debug) {
917
928
  return env;
918
929
  }
919
930
  async function openInBrowser(url, debug) {
920
- const candidates = browserOpenCandidates(url);
931
+ const candidates = process.platform === "win32" ? [...browserOpenCandidates(url), ...windowsFallbackCandidates(url)] : browserOpenCandidates(url);
921
932
  const launchEnv = browserLaunchEnv(debug);
922
933
  debug?.(
923
934
  `openInBrowser: platform=${process.platform} DISPLAY=${launchEnv.DISPLAY ?? "<unset>"} WAYLAND=${launchEnv.WAYLAND_DISPLAY ?? "<unset>"} DBUS=${launchEnv.DBUS_SESSION_BUS_ADDRESS ? "set" : "<unset>"} candidates=[${candidates.map((c) => c.cmd).join(", ")}]`
@@ -926,14 +937,43 @@ async function openInBrowser(url, debug) {
926
937
  for (const { cmd, args } of candidates) {
927
938
  try {
928
939
  await new Promise((resolve, reject) => {
929
- const spawnOpts = { stdio: "ignore", detached: true, env: launchEnv };
930
- if (process.platform === "win32") spawnOpts.windowsVerbatimArguments = true;
940
+ const isWin = process.platform === "win32";
941
+ const spawnOpts = { stdio: "ignore", detached: !isWin, env: launchEnv };
942
+ if (isWin) spawnOpts.windowsVerbatimArguments = true;
931
943
  const child = spawn3(cmd, args, spawnOpts);
932
944
  child.on("error", reject);
933
- child.on("spawn", () => {
934
- debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
945
+ if (!isWin) {
946
+ child.on("spawn", () => {
947
+ debug?.(`spawn OK: ${cmd} (pid=${child.pid})`);
948
+ child.unref();
949
+ resolve();
950
+ });
951
+ return;
952
+ }
953
+ const isCmd = /cmd(\.exe)?$/i.test(cmd);
954
+ const budgetMs = isCmd ? 800 : 1200;
955
+ let settled = false;
956
+ const finish = (fn) => {
957
+ if (settled) return;
958
+ settled = true;
959
+ fn();
960
+ };
961
+ const timer = setTimeout(() => {
962
+ debug?.(`exit-wait timeout (${budgetMs}ms): ${cmd} \u2014 assuming launched`);
935
963
  child.unref();
936
- resolve();
964
+ finish(resolve);
965
+ }, budgetMs);
966
+ timer.unref?.();
967
+ child.on("spawn", () => debug?.(`spawn OK: ${cmd} (pid=${child.pid}) \u2014 awaiting exit`));
968
+ child.on("exit", (code) => {
969
+ clearTimeout(timer);
970
+ if (code === 0 || code === null) {
971
+ debug?.(`exit ${code}: ${cmd} \u2014 launched OK`);
972
+ finish(resolve);
973
+ } else {
974
+ debug?.(`exit ${code}: ${cmd} \u2014 treating as failed, next candidate`);
975
+ finish(() => reject(new Error(`${cmd} exited ${code}`)));
976
+ }
937
977
  });
938
978
  });
939
979
  return;
@@ -1027,7 +1067,7 @@ async function oauthLogin(opts) {
1027
1067
  }
1028
1068
 
1029
1069
  // installer/installer-gui.ts
1030
- var VERSION = true ? "0.23.9" : "0.0.0-dev";
1070
+ var VERSION = true ? "0.23.11" : "0.0.0-dev";
1031
1071
  var MESSAGES = {
1032
1072
  en: {
1033
1073
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.23.9",
3
+ "version": "0.23.11",
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",
@@ -61,13 +61,13 @@
61
61
  ],
62
62
  "repository": {
63
63
  "type": "git",
64
- "url": "git+https://github.com/leadbay/leadclaw.git",
64
+ "url": "git+https://github.com/leadbay/mcp.git",
65
65
  "directory": "packages/mcp"
66
66
  },
67
67
  "bugs": {
68
- "url": "https://github.com/leadbay/leadclaw/issues"
68
+ "url": "https://github.com/leadbay/mcp/issues"
69
69
  },
70
- "homepage": "https://github.com/leadbay/leadclaw/tree/main/packages/mcp#readme",
70
+ "homepage": "https://github.com/leadbay/mcp/tree/main/packages/mcp#readme",
71
71
  "author": "Leadbay",
72
72
  "license": "MIT"
73
73
  }