@leadbay/mcp 0.23.13 → 0.23.14

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,11 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.23.14 — 2026-07-03
4
+
5
+ Registry: publish the hosted remote endpoint so GitHub Copilot / VS Code can offer Leadbay as a one-click remote connector (product#3861).
6
+
7
+ - **`server.json` `remotes`** — the registry entry now advertises the hosted Streamable HTTP endpoint (`https://mcp.leadbay.app/mcp`) alongside the npm package. Landed in code in 0.23.13 but needs this version bump to actually re-publish to the MCP Registry (the registry won't re-publish an already-published version). Auth is standard MCP OAuth (discovered via `.well-known`), so no header/API-key declaration.
8
+
3
9
  ## 0.23.13 — 2026-07-03
4
10
 
5
11
  Directory-submission schema fix.
package/dist/bin.js CHANGED
@@ -5931,7 +5931,7 @@ WHEN NOT TO USE: as a substitute for leadbay_research_lead_by_id \u2014 that alr
5931
5931
 
5932
5932
  ## CREDIT COST \u2014 show the balance, discreetly
5933
5933
 
5934
- Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
5934
+ Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. If \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account), also omit the line \u2014 there is no finite balance to show; do NOT print "unlimited". Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
5935
5935
  `;
5936
5936
  leadbay_bulk_qualify_leads = `Pick the next N unqualified leads in the active lens and qualify them (run AI rescore + web fetch). Pass \`wait_for_completion:false\` to return quickly with \`{status:'running', qualify_id}\`; poll leadbay_qualify_status with that id. With \`wait_for_completion\` omitted/true, the legacy behavior polls until the answers are populated or a budget is exhausted. Already-qualified leads (those with a non-null \`ai_agent_lead_score\`) are silently no-ops on the backend, so this composite paginates past them to find fresh candidates. On 429 mid-fanout, stops launching but keeps polling already-launched leads.
5937
5937
 
@@ -6411,7 +6411,7 @@ WHEN NOT TO USE: for bulk enrichment by job title across many leads \u2014 use l
6411
6411
 
6412
6412
  ## CREDIT COST \u2014 discreet
6413
6413
 
6414
- This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero.
6414
+ This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero. If \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account), also omit the line and proceed freely \u2014 do NOT print "unlimited" and never tell the user they're out of credits.
6415
6415
 
6416
6416
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
6417
6417
  `;
@@ -6425,9 +6425,9 @@ WHEN NOT TO USE: to enrich a single contact \u2014 that's leadbay_enrich_contact
6425
6425
 
6426
6426
  Enrichment is the main PAID operation. Surface cost both before and after.
6427
6427
 
6428
- **BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited.
6428
+ **BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited. If \`credits_remaining\` is the string \`"unlimited"\`, this is an internal/unlimited account: proceed freely and say NOTHING about credits \u2014 drop the "You have {credits_remaining} credits" sentence entirely (still name the count and still confirm before launching); do NOT announce "unlimited" and never tell the user they're out of credits.
6429
6429
 
6430
- **AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
6430
+ **AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Omit it too if \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account) \u2014 do NOT print \`_(unlimited credits remaining)_\` or otherwise announce unlimited. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
6431
6431
 
6432
6432
  ## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
6433
6433
 
@@ -9962,11 +9962,34 @@ var init_qualify_lead = __esm({
9962
9962
  }
9963
9963
  });
9964
9964
 
9965
+ // ../core/dist/composite/_credits-helpers.js
9966
+ function isUnlimitedAccount(me) {
9967
+ return me.email?.toLowerCase().trim().endsWith("@leadbay.ai") ?? false;
9968
+ }
9969
+ async function readCreditsRemaining(client, force = false) {
9970
+ try {
9971
+ const me = await client.resolveMe(force);
9972
+ if (isUnlimitedAccount(me))
9973
+ return UNLIMITED;
9974
+ return me.organization.billing?.ai_credits ?? null;
9975
+ } catch {
9976
+ return null;
9977
+ }
9978
+ }
9979
+ var UNLIMITED;
9980
+ var init_credits_helpers = __esm({
9981
+ "../core/dist/composite/_credits-helpers.js"() {
9982
+ "use strict";
9983
+ UNLIMITED = "unlimited";
9984
+ }
9985
+ });
9986
+
9965
9987
  // ../core/dist/tools/enrich-contacts.js
9966
9988
  var enrichContacts;
9967
9989
  var init_enrich_contacts = __esm({
9968
9990
  "../core/dist/tools/enrich-contacts.js"() {
9969
9991
  "use strict";
9992
+ init_credits_helpers();
9970
9993
  init_tool_descriptions_generated();
9971
9994
  enrichContacts = {
9972
9995
  name: "leadbay_enrich_contacts",
@@ -10011,9 +10034,13 @@ var init_enrich_contacts = __esm({
10011
10034
  let creditsRemaining = null;
10012
10035
  try {
10013
10036
  const me = await client.request("GET", "/users/me");
10014
- creditsRemaining = me.organization.billing?.ai_credits ?? null;
10015
- if (creditsRemaining !== null && creditsRemaining <= 0) {
10016
- throw client.makeError("QUOTA_EXCEEDED", "No enrichment credits remaining", "Contact Leadbay support to extend your credit quota");
10037
+ if (isUnlimitedAccount(me)) {
10038
+ creditsRemaining = UNLIMITED;
10039
+ } else {
10040
+ creditsRemaining = me.organization.billing?.ai_credits ?? null;
10041
+ if (typeof creditsRemaining === "number" && creditsRemaining <= 0) {
10042
+ throw client.makeError("QUOTA_EXCEEDED", "No enrichment credits remaining", "Contact Leadbay support to extend your credit quota");
10043
+ }
10017
10044
  }
10018
10045
  } catch (e) {
10019
10046
  if (e?.code === "QUOTA_EXCEEDED")
@@ -17421,6 +17448,7 @@ var init_account_status = __esm({
17421
17448
  "../core/dist/composite/account-status.js"() {
17422
17449
  "use strict";
17423
17450
  init_agent_memory();
17451
+ init_credits_helpers();
17424
17452
  init_tool_descriptions_generated();
17425
17453
  accountStatus = {
17426
17454
  name: "leadbay_account_status",
@@ -17458,7 +17486,11 @@ var init_account_status = __esm({
17458
17486
  type: "boolean",
17459
17487
  description: "True if Leadbay is mid-regenerating intelligence after a refine_prompt; new leads will reflect it shortly."
17460
17488
  },
17461
- plan: { type: ["string", "null"] }
17489
+ plan: { type: ["string", "null"] },
17490
+ unlimited_credits: {
17491
+ type: "boolean",
17492
+ description: "True for internal/unlimited accounts (billing disabled server-side). When true, DO NOT read the null `plan` or absent quota as 'no credits' \u2014 the account has unlimited credits; enrichment/qualify proceed freely. Stay SILENT on credits: don't announce 'unlimited', don't show a balance, and never tell the user they're out of credits. False for normal accounts, where the usual quota/credit story applies."
17493
+ }
17462
17494
  }
17463
17495
  },
17464
17496
  last_requested_lens: {
@@ -17562,7 +17594,11 @@ var init_account_status = __esm({
17562
17594
  name: me.organization.name,
17563
17595
  ai_agent_enabled: me.organization.ai_agent_enabled ?? false,
17564
17596
  computing_intelligence: me.organization.computing_intelligence ?? false,
17565
- plan: quota?.plan ?? me.organization.quota_plan ?? null
17597
+ plan: quota?.plan ?? me.organization.quota_plan ?? null,
17598
+ // Internal/unlimited account (billing disabled). The backend zeroes
17599
+ // plan/quota for these, so without this flag the agent misreads
17600
+ // plan:null as "no credits" and refuses enrichment. product#3851.
17601
+ unlimited_credits: isUnlimitedAccount(me)
17566
17602
  },
17567
17603
  // Lens is withheld unless the user asked (lensAsked, above). When present,
17568
17604
  // the id is normalized to the STRING form (my-lenses.ts) so it matches the
@@ -19976,21 +20012,6 @@ var init_qualify_status = __esm({
19976
20012
  }
19977
20013
  });
19978
20014
 
19979
- // ../core/dist/composite/_credits-helpers.js
19980
- async function readCreditsRemaining(client, force = false) {
19981
- try {
19982
- const me = await client.resolveMe(force);
19983
- return me.organization.billing?.ai_credits ?? null;
19984
- } catch {
19985
- return null;
19986
- }
19987
- }
19988
- var init_credits_helpers = __esm({
19989
- "../core/dist/composite/_credits-helpers.js"() {
19990
- "use strict";
19991
- }
19992
- });
19993
-
19994
20015
  // ../core/dist/composite/enrich-titles.js
19995
20016
  var DEFAULT_CANDIDATE_COUNT, enrichTitles;
19996
20017
  var init_enrich_titles = __esm({
@@ -20081,8 +20102,8 @@ var init_enrich_titles = __esm({
20081
20102
  description: "Count of enrichable contacts at preview time."
20082
20103
  },
20083
20104
  credits_remaining: {
20084
- type: ["number", "null"],
20085
- description: "AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable."
20105
+ type: ["number", "string", "null"],
20106
+ description: `AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable. The string "unlimited" = an internal/unlimited account: proceed with enrichment freely and say NOTHING about credits (no balance, no 'unlimited', no 'no credits').`
20086
20107
  },
20087
20108
  selected_lead_count: {
20088
20109
  type: "number",
@@ -20466,8 +20487,8 @@ var init_bulk_enrich_status = __esm({
20466
20487
  description: "True when overall_progress.done === total AND no partial_failures."
20467
20488
  },
20468
20489
  credits_remaining: {
20469
- type: ["number", "null"],
20470
- description: "AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments."
20490
+ type: ["number", "string", "null"],
20491
+ description: `AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). The string "unlimited" = an internal/unlimited account: omit the _(N credits remaining)_ line entirely (there is no finite balance to show). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments.`
20471
20492
  },
20472
20493
  partial_failures: {
20473
20494
  type: "array",
@@ -23260,7 +23281,7 @@ If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retr
23260
23281
 
23261
23282
  # PHASE 0 \u2014 STATE + AUDIENCE
23262
23283
 
23263
- Call \`leadbay_account_status\` to see my remaining quota, my **enrichment credits**, and my **active lens**. Note \`web_fetch_remaining\` (qualification budget) and the enrichment credit balance \u2014 Phase 3 spends credits.
23284
+ Call \`leadbay_account_status\` to see my remaining quota, my **enrichment credits**, and my **active lens**. Note \`web_fetch_remaining\` (qualification budget) and the enrichment credit balance \u2014 Phase 3 spends credits. If \`organization.unlimited_credits\` is true, this is an internal/unlimited account: skip every credit-balance mention below and proceed with enrichment freely \u2014 never tell me I'm out of credits.
23264
23285
 
23265
23286
  Resolve the audience:
23266
23287
 
@@ -23379,9 +23400,9 @@ Call \`leadbay_recall_ordered_titles({leadIds, lensId})\` and \`leadbay_enrich_t
23379
23400
 
23380
23401
  Tell me what you swapped in one line ("dropped Corbett + RBS \u2014 ops-only; swapped in Acme + Globex which have Sales VPs"). The goal is a final cohort where EVERY lead has a real buyer to call.
23381
23402
 
23382
- **Step C \u2014 show the spend + persona and confirm.** State the persona, the chosen titles, and "You have {credits_remaining} credits; this enriches {enrichable_contacts} contacts." Confirm via \`ask_user_input_v0\` ("Enrich these {enrichable_contacts} <persona> contacts now?" \u2192 ["Yes, enrich", "No, skip", "Change the persona/titles"]). Never launch a paid run without this. Enrich up to the campaign size (default 20) best persona-matching contacts.
23403
+ **Step C \u2014 show the spend + persona and confirm.** State the persona, the chosen titles, and "You have {credits_remaining} credits; this enriches {enrichable_contacts} contacts." Confirm via \`ask_user_input_v0\` ("Enrich these {enrichable_contacts} <persona> contacts now?" \u2192 ["Yes, enrich", "No, skip", "Change the persona/titles"]). Never launch a paid run without this. Enrich up to the campaign size (default 20) best persona-matching contacts. (Unlimited account \u2014 \`credits_remaining\` is \`"unlimited"\` or \`unlimited_credits\` was true: drop the "You have {credits_remaining} credits" clause and just state the persona + count; still confirm before launching.)
23383
23404
 
23384
- **Step D \u2014 launch + poll.** On yes: \`leadbay_enrich_titles({leadIds, lensId, titles:[...chosen], email:true, phone:true})\` to launch, then poll \`leadbay_bulk_enrich_status\` until done (enrichment can take several minutes \u2014 keep polling, don't render an empty sheet prematurely). Append one small line at the very end of your reply: \`_(N credits remaining)_\`.
23405
+ **Step D \u2014 launch + poll.** On yes: \`leadbay_enrich_titles({leadIds, lensId, titles:[...chosen], email:true, phone:true})\` to launch, then poll \`leadbay_bulk_enrich_status\` until done (enrichment can take several minutes \u2014 keep polling, don't render an empty sheet prematurely). Append one small line at the very end of your reply: \`_(N credits remaining)_\` \u2014 but omit it entirely when \`credits_remaining\` is \`"unlimited"\` (unlimited account has no finite balance to show).
23385
23406
 
23386
23407
  If I skip enrichment, continue \u2014 the campaign can be enriched later from the call sheet.
23387
23408
 
@@ -23411,7 +23432,7 @@ Then STOP. Building a campaign is NOT outreaching \u2014 do not send anything an
23411
23432
  - Enrichment targets MY buyer persona \u2014 the people who would actually buy what *I* sell, derived from my product/ICP \u2014 NOT generic seniority. For a sales/prospecting tool that means the revenue org (sales / BD / growth / marketing leaders); a Director of Operations, COO, or logistics manager is useless no matter how senior. Get the persona right or the campaign is worthless.
23412
23433
  - Selection is DATA-DRIVEN (\`leadbay_recall_ordered_titles\` + \`leadbay_enrich_titles\` discovery) but FILTERED to the persona \u2014 never blindly repeat past-enriched or suggested titles that don't match who buys my product.
23413
23434
  - The FINAL cohort must be all buyer-ready: drop/swap any lead with no enrichable buyer-persona contact (Phase 3 Step B.5) rather than shipping it empty. A campaign where half the leads have no buyer to call is a failed campaign. Pick a generous pool in Phase 2 so swaps are possible.
23414
- - NEVER launch paid enrichment without showing \`credits_remaining\` + \`enrichable_contacts\`, naming the persona, and getting a yes.
23435
+ - NEVER launch paid enrichment without showing \`credits_remaining\` + \`enrichable_contacts\`, naming the persona, and getting a yes. (Exception on an unlimited account \u2014 \`credits_remaining\` is \`"unlimited"\` / \`unlimited_credits\` true: skip the \`credits_remaining\` display only; still show \`enrichable_contacts\`, name the persona, and get a yes.)
23415
23436
  - Qualify / pick BEFORE \`leadbay_create_campaign\` \u2014 never seed a campaign with unvetted leads.
23416
23437
  - Carry the captured \`lensId\` on every call. A lens shift loses the cohort.
23417
23438
  - End at the rendered call sheet, then hand off to \`leadbay_work_campaign\`. Do NOT re-implement the calling / follow-up loop here, and do NOT call \`leadbay_report_outreach\`.
@@ -27812,7 +27833,7 @@ var OAUTH_BASE_URLS = {
27812
27833
  fr: "https://staging.api.leadbay.app"
27813
27834
  }
27814
27835
  };
27815
- var VERSION = "0.23.13";
27836
+ var VERSION = "0.23.14";
27816
27837
  var HELP = `
27817
27838
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
27818
27839
 
@@ -75,7 +75,7 @@ If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retr
75
75
 
76
76
  # PHASE 0 \u2014 STATE + AUDIENCE
77
77
 
78
- Call \`leadbay_account_status\` to see my remaining quota, my **enrichment credits**, and my **active lens**. Note \`web_fetch_remaining\` (qualification budget) and the enrichment credit balance \u2014 Phase 3 spends credits.
78
+ Call \`leadbay_account_status\` to see my remaining quota, my **enrichment credits**, and my **active lens**. Note \`web_fetch_remaining\` (qualification budget) and the enrichment credit balance \u2014 Phase 3 spends credits. If \`organization.unlimited_credits\` is true, this is an internal/unlimited account: skip every credit-balance mention below and proceed with enrichment freely \u2014 never tell me I'm out of credits.
79
79
 
80
80
  Resolve the audience:
81
81
 
@@ -194,9 +194,9 @@ Call \`leadbay_recall_ordered_titles({leadIds, lensId})\` and \`leadbay_enrich_t
194
194
 
195
195
  Tell me what you swapped in one line ("dropped Corbett + RBS \u2014 ops-only; swapped in Acme + Globex which have Sales VPs"). The goal is a final cohort where EVERY lead has a real buyer to call.
196
196
 
197
- **Step C \u2014 show the spend + persona and confirm.** State the persona, the chosen titles, and "You have {credits_remaining} credits; this enriches {enrichable_contacts} contacts." Confirm via \`ask_user_input_v0\` ("Enrich these {enrichable_contacts} <persona> contacts now?" \u2192 ["Yes, enrich", "No, skip", "Change the persona/titles"]). Never launch a paid run without this. Enrich up to the campaign size (default 20) best persona-matching contacts.
197
+ **Step C \u2014 show the spend + persona and confirm.** State the persona, the chosen titles, and "You have {credits_remaining} credits; this enriches {enrichable_contacts} contacts." Confirm via \`ask_user_input_v0\` ("Enrich these {enrichable_contacts} <persona> contacts now?" \u2192 ["Yes, enrich", "No, skip", "Change the persona/titles"]). Never launch a paid run without this. Enrich up to the campaign size (default 20) best persona-matching contacts. (Unlimited account \u2014 \`credits_remaining\` is \`"unlimited"\` or \`unlimited_credits\` was true: drop the "You have {credits_remaining} credits" clause and just state the persona + count; still confirm before launching.)
198
198
 
199
- **Step D \u2014 launch + poll.** On yes: \`leadbay_enrich_titles({leadIds, lensId, titles:[...chosen], email:true, phone:true})\` to launch, then poll \`leadbay_bulk_enrich_status\` until done (enrichment can take several minutes \u2014 keep polling, don't render an empty sheet prematurely). Append one small line at the very end of your reply: \`_(N credits remaining)_\`.
199
+ **Step D \u2014 launch + poll.** On yes: \`leadbay_enrich_titles({leadIds, lensId, titles:[...chosen], email:true, phone:true})\` to launch, then poll \`leadbay_bulk_enrich_status\` until done (enrichment can take several minutes \u2014 keep polling, don't render an empty sheet prematurely). Append one small line at the very end of your reply: \`_(N credits remaining)_\` \u2014 but omit it entirely when \`credits_remaining\` is \`"unlimited"\` (unlimited account has no finite balance to show).
200
200
 
201
201
  If I skip enrichment, continue \u2014 the campaign can be enriched later from the call sheet.
202
202
 
@@ -226,7 +226,7 @@ Then STOP. Building a campaign is NOT outreaching \u2014 do not send anything an
226
226
  - Enrichment targets MY buyer persona \u2014 the people who would actually buy what *I* sell, derived from my product/ICP \u2014 NOT generic seniority. For a sales/prospecting tool that means the revenue org (sales / BD / growth / marketing leaders); a Director of Operations, COO, or logistics manager is useless no matter how senior. Get the persona right or the campaign is worthless.
227
227
  - Selection is DATA-DRIVEN (\`leadbay_recall_ordered_titles\` + \`leadbay_enrich_titles\` discovery) but FILTERED to the persona \u2014 never blindly repeat past-enriched or suggested titles that don't match who buys my product.
228
228
  - The FINAL cohort must be all buyer-ready: drop/swap any lead with no enrichable buyer-persona contact (Phase 3 Step B.5) rather than shipping it empty. A campaign where half the leads have no buyer to call is a failed campaign. Pick a generous pool in Phase 2 so swaps are possible.
229
- - NEVER launch paid enrichment without showing \`credits_remaining\` + \`enrichable_contacts\`, naming the persona, and getting a yes.
229
+ - NEVER launch paid enrichment without showing \`credits_remaining\` + \`enrichable_contacts\`, naming the persona, and getting a yes. (Exception on an unlimited account \u2014 \`credits_remaining\` is \`"unlimited"\` / \`unlimited_credits\` true: skip the \`credits_remaining\` display only; still show \`enrichable_contacts\`, name the persona, and get a yes.)
230
230
  - Qualify / pick BEFORE \`leadbay_create_campaign\` \u2014 never seed a campaign with unvetted leads.
231
231
  - Carry the captured \`lensId\` on every call. A lens shift loses the cohort.
232
232
  - End at the rendered call sheet, then hand off to \`leadbay_work_campaign\`. Do NOT re-implement the calling / follow-up loop here, and do NOT call \`leadbay_report_outreach\`.
@@ -7061,7 +7061,7 @@ WHEN NOT TO USE: as a substitute for leadbay_research_lead_by_id \u2014 that alr
7061
7061
 
7062
7062
  ## CREDIT COST \u2014 show the balance, discreetly
7063
7063
 
7064
- Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
7064
+ Once \`all_done\` is true the result carries \`credits_remaining\` (the post-spend AI-credit balance). Don't make a fuss \u2014 no sentence, no callout. Just append ONE small italic line in parentheses at the very END of your reply: \`_(N credits remaining)_\`. If \`credits_remaining\` is null (billing unavailable), omit the line \u2014 don't print 0. If \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account), also omit the line \u2014 there is no finite balance to show; do NOT print "unlimited". Do NOT report a "credits used" figure for this run: the per-contact cost can't be scoped to this specific enrichment (a lead's contact list mixes in earlier runs), so any "X used" number would be misleading.
7065
7065
  `;
7066
7066
  var leadbay_bulk_qualify_leads = `Pick the next N unqualified leads in the active lens and qualify them (run AI rescore + web fetch). Pass \`wait_for_completion:false\` to return quickly with \`{status:'running', qualify_id}\`; poll leadbay_qualify_status with that id. With \`wait_for_completion\` omitted/true, the legacy behavior polls until the answers are populated or a budget is exhausted. Already-qualified leads (those with a non-null \`ai_agent_lead_score\`) are silently no-ops on the backend, so this composite paginates past them to find fresh candidates. On 429 mid-fanout, stops launching but keeps polling already-launched leads.
7067
7067
 
@@ -7541,7 +7541,7 @@ WHEN NOT TO USE: for bulk enrichment by job title across many leads \u2014 use l
7541
7541
 
7542
7542
  ## CREDIT COST \u2014 discreet
7543
7543
 
7544
- This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero.
7544
+ This is a paid call. The result returns \`credits_remaining\` (billing.ai_credits, read before the spend). Don't make a fuss about credits: only flag the balance if it's low (e.g. \u2264 a few credits) so the user can decide. Otherwise append it quietly as a small italic parenthetical at the END of your reply \u2014 \`_(N credits remaining)_\`. Don't quote an exact per-contact cost (the rate is backend-only). The actual per-contact cost (enrichment.credits_used) appears on the contact via leadbay_get_contacts after enrichment. If \`credits_remaining\` is null, omit the line \u2014 don't assume zero. If \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account), also omit the line and proceed freely \u2014 do NOT print "unlimited" and never tell the user they're out of credits.
7545
7545
 
7546
7546
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
7547
7547
  `;
@@ -7555,9 +7555,9 @@ WHEN NOT TO USE: to enrich a single contact \u2014 that's leadbay_enrich_contact
7555
7555
 
7556
7556
  Enrichment is the main PAID operation. Surface cost both before and after.
7557
7557
 
7558
- **BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited.
7558
+ **BEFORE (confirm before launching).** The discover / preview_only / dry_run modes return \`credits_remaining\` (the balance) and \`enrichable_contacts\` (the volume that would be enriched). Tell the user plainly: **"You have {credits_remaining} credits. This will enrich {enrichable_contacts} contacts."** then ask them to confirm before you launch the paid run. Route that confirmation through \`ask_user_input_v0\` ("Enrich {enrichable_contacts} contacts now?" \u2192 ["Yes, enrich", "No, cancel"]). Do NOT state an exact estimated cost \u2014 the per-contact credit rate lives backend-side and is not in the preview; show the balance and the count, never a fabricated "will cost N credits". If \`credits_remaining\` is null, billing is unavailable \u2014 say the balance is unknown, don't assume zero or unlimited. If \`credits_remaining\` is the string \`"unlimited"\`, this is an internal/unlimited account: proceed freely and say NOTHING about credits \u2014 drop the "You have {credits_remaining} credits" sentence entirely (still name the count and still confirm before launching); do NOT announce "unlimited" and never tell the user they're out of credits.
7559
7559
 
7560
- **AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
7560
+ **AFTER (show the balance, discreetly).** Once the job finishes \u2014 poll \`leadbay_bulk_enrich_status\`, which returns \`credits_remaining\` (the post-spend balance). Don't make a fuss: append ONE small italic line in parentheses at the very END of your reply \u2014 \`_(N credits remaining)_\`. Omit it if \`credits_remaining\` is null. Omit it too if \`credits_remaining\` is the string \`"unlimited"\` (internal/unlimited account) \u2014 do NOT print \`_(unlimited credits remaining)_\` or otherwise announce unlimited. Do NOT report a "credits used" figure: per-run cost can't be scoped reliably (a lead's contacts mix earlier enrichments), so only the balance is shown.
7561
7561
 
7562
7562
  ## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
7563
7563
 
@@ -11033,6 +11033,22 @@ var qualifyLead = {
11033
11033
  }
11034
11034
  };
11035
11035
 
11036
+ // ../core/dist/composite/_credits-helpers.js
11037
+ var UNLIMITED = "unlimited";
11038
+ function isUnlimitedAccount(me) {
11039
+ return me.email?.toLowerCase().trim().endsWith("@leadbay.ai") ?? false;
11040
+ }
11041
+ async function readCreditsRemaining(client, force = false) {
11042
+ try {
11043
+ const me = await client.resolveMe(force);
11044
+ if (isUnlimitedAccount(me))
11045
+ return UNLIMITED;
11046
+ return me.organization.billing?.ai_credits ?? null;
11047
+ } catch {
11048
+ return null;
11049
+ }
11050
+ }
11051
+
11036
11052
  // ../core/dist/tools/enrich-contacts.js
11037
11053
  var enrichContacts = {
11038
11054
  name: "leadbay_enrich_contacts",
@@ -11077,9 +11093,13 @@ var enrichContacts = {
11077
11093
  let creditsRemaining = null;
11078
11094
  try {
11079
11095
  const me = await client.request("GET", "/users/me");
11080
- creditsRemaining = me.organization.billing?.ai_credits ?? null;
11081
- if (creditsRemaining !== null && creditsRemaining <= 0) {
11082
- throw client.makeError("QUOTA_EXCEEDED", "No enrichment credits remaining", "Contact Leadbay support to extend your credit quota");
11096
+ if (isUnlimitedAccount(me)) {
11097
+ creditsRemaining = UNLIMITED;
11098
+ } else {
11099
+ creditsRemaining = me.organization.billing?.ai_credits ?? null;
11100
+ if (typeof creditsRemaining === "number" && creditsRemaining <= 0) {
11101
+ throw client.makeError("QUOTA_EXCEEDED", "No enrichment credits remaining", "Contact Leadbay support to extend your credit quota");
11102
+ }
11083
11103
  }
11084
11104
  } catch (e) {
11085
11105
  if (e?.code === "QUOTA_EXCEEDED")
@@ -17975,7 +17995,11 @@ var accountStatus = {
17975
17995
  type: "boolean",
17976
17996
  description: "True if Leadbay is mid-regenerating intelligence after a refine_prompt; new leads will reflect it shortly."
17977
17997
  },
17978
- plan: { type: ["string", "null"] }
17998
+ plan: { type: ["string", "null"] },
17999
+ unlimited_credits: {
18000
+ type: "boolean",
18001
+ description: "True for internal/unlimited accounts (billing disabled server-side). When true, DO NOT read the null `plan` or absent quota as 'no credits' \u2014 the account has unlimited credits; enrichment/qualify proceed freely. Stay SILENT on credits: don't announce 'unlimited', don't show a balance, and never tell the user they're out of credits. False for normal accounts, where the usual quota/credit story applies."
18002
+ }
17979
18003
  }
17980
18004
  },
17981
18005
  last_requested_lens: {
@@ -18079,7 +18103,11 @@ var accountStatus = {
18079
18103
  name: me.organization.name,
18080
18104
  ai_agent_enabled: me.organization.ai_agent_enabled ?? false,
18081
18105
  computing_intelligence: me.organization.computing_intelligence ?? false,
18082
- plan: quota?.plan ?? me.organization.quota_plan ?? null
18106
+ plan: quota?.plan ?? me.organization.quota_plan ?? null,
18107
+ // Internal/unlimited account (billing disabled). The backend zeroes
18108
+ // plan/quota for these, so without this flag the agent misreads
18109
+ // plan:null as "no credits" and refuses enrichment. product#3851.
18110
+ unlimited_credits: isUnlimitedAccount(me)
18083
18111
  },
18084
18112
  // Lens is withheld unless the user asked (lensAsked, above). When present,
18085
18113
  // the id is normalized to the STRING form (my-lenses.ts) so it matches the
@@ -19835,16 +19863,6 @@ var qualifyStatus = {
19835
19863
  }
19836
19864
  };
19837
19865
 
19838
- // ../core/dist/composite/_credits-helpers.js
19839
- async function readCreditsRemaining(client, force = false) {
19840
- try {
19841
- const me = await client.resolveMe(force);
19842
- return me.organization.billing?.ai_credits ?? null;
19843
- } catch {
19844
- return null;
19845
- }
19846
- }
19847
-
19848
19866
  // ../core/dist/composite/enrich-titles.js
19849
19867
  var DEFAULT_CANDIDATE_COUNT = 25;
19850
19868
  var enrichTitles = {
@@ -19929,8 +19947,8 @@ var enrichTitles = {
19929
19947
  description: "Count of enrichable contacts at preview time."
19930
19948
  },
19931
19949
  credits_remaining: {
19932
- type: ["number", "null"],
19933
- description: "AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable."
19950
+ type: ["number", "string", "null"],
19951
+ description: `AI-credit balance BEFORE launching (billing.ai_credits). Present in discover / preview_only / dry_run modes. Pair with enrichable_contacts to tell the user 'you have N credits, this will enrich M contacts' \u2014 do NOT estimate an exact cost (the per-contact rate is backend-only). Null = billing unavailable. The string "unlimited" = an internal/unlimited account: proceed with enrichment freely and say NOTHING about credits (no balance, no 'unlimited', no 'no credits').`
19934
19952
  },
19935
19953
  selected_lead_count: {
19936
19954
  type: "number",
@@ -20304,8 +20322,8 @@ var bulkEnrichStatus = {
20304
20322
  description: "True when overall_progress.done === total AND no partial_failures."
20305
20323
  },
20306
20324
  credits_remaining: {
20307
- type: ["number", "null"],
20308
- description: "AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments."
20325
+ type: ["number", "string", "null"],
20326
+ description: `AI-credit balance re-read after the spend (force-refreshed /users/me \u2192 billing.ai_credits). Present only when all_done. Null = billing unavailable (don't read as zero). The string "unlimited" = an internal/unlimited account: omit the _(N credits remaining)_ line entirely (there is no finite balance to show). NOTE: a per-run 'credits used' figure is intentionally NOT returned \u2014 getContacts can't scope cost to this bulk, so any sum would conflate historical enrichments.`
20309
20327
  },
20310
20328
  partial_failures: {
20311
20329
  type: "array",
@@ -24067,7 +24085,7 @@ function parseWriteEnv(env = process.env) {
24067
24085
  }
24068
24086
 
24069
24087
  // src/http-server.ts
24070
- var VERSION = true ? "0.23.13" : "0.0.0-dev";
24088
+ var VERSION = true ? "0.23.14" : "0.0.0-dev";
24071
24089
  var PORT = Number(process.env.PORT ?? 8080);
24072
24090
  var HOST = process.env.HOST ?? "0.0.0.0";
24073
24091
  var sseSessions = /* @__PURE__ */ new Map();
@@ -1804,7 +1804,7 @@ var init_installer_gui = __esm({
1804
1804
  init_install_dxt();
1805
1805
  init_install_shared();
1806
1806
  init_oauth();
1807
- VERSION = true ? "0.23.13" : "0.0.0-dev";
1807
+ VERSION = true ? "0.23.14" : "0.0.0-dev";
1808
1808
  MESSAGES = {
1809
1809
  en: {
1810
1810
  installer: {
@@ -1067,7 +1067,7 @@ async function oauthLogin(opts) {
1067
1067
  }
1068
1068
 
1069
1069
  // installer/installer-gui.ts
1070
- var VERSION = true ? "0.23.13" : "0.0.0-dev";
1070
+ var VERSION = true ? "0.23.14" : "0.0.0-dev";
1071
1071
  var MESSAGES = {
1072
1072
  en: {
1073
1073
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.23.13",
3
+ "version": "0.23.14",
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",