@leadbay/mcp 0.23.9 → 0.23.10

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,13 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.23.10 — 2026-07-01
4
+
5
+ A freshly-created lens no longer reads as "empty" (product#3833).
6
+
7
+ - **`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`).
8
+ - **`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).
9
+ - 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.
10
+
3
11
  ## 0.23.9 — 2026-06-27
4
12
 
5
13
  MCP data plane now targets backend **/1.6** (was /1.5).
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.
@@ -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";
@@ -27721,7 +27767,7 @@ var OAUTH_BASE_URLS = {
27721
27767
  fr: "https://staging.api.leadbay.app"
27722
27768
  }
27723
27769
  };
27724
- var VERSION = "0.23.9";
27770
+ var VERSION = "0.23.10";
27725
27771
  var HELP = `
27726
27772
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
27727
27773
 
@@ -27788,7 +27834,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
27788
27834
  }
27789
27835
 
27790
27836
  DOCS
27791
- https://github.com/leadbay/leadclaw#readme
27837
+ https://github.com/leadbay/mcp#readme
27792
27838
  `.trim();
27793
27839
  function makeStderrLogger(level) {
27794
27840
  const rank = { debug: 0, info: 1, error: 2 };
@@ -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.
@@ -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";
@@ -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.10" : "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);
@@ -1764,7 +1764,7 @@ var init_installer_gui = __esm({
1764
1764
  init_install_dxt();
1765
1765
  init_install_shared();
1766
1766
  init_oauth();
1767
- VERSION = true ? "0.23.9" : "0.0.0-dev";
1767
+ VERSION = true ? "0.23.10" : "0.0.0-dev";
1768
1768
  MESSAGES = {
1769
1769
  en: {
1770
1770
  installer: {
@@ -1027,7 +1027,7 @@ async function oauthLogin(opts) {
1027
1027
  }
1028
1028
 
1029
1029
  // installer/installer-gui.ts
1030
- var VERSION = true ? "0.23.9" : "0.0.0-dev";
1030
+ var VERSION = true ? "0.23.10" : "0.0.0-dev";
1031
1031
  var MESSAGES = {
1032
1032
  en: {
1033
1033
  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.10",
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
  }