@leadbay/mcp 0.39.8 → 0.39.9

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/MIGRATION.md CHANGED
@@ -32,9 +32,9 @@ field. On the `notification_id` path the job is done when `in_progress` is false
32
32
  `still_running[]` is empty on that path from the first poll and must not be read
33
33
  as "done". On the `lead_ids` path, done means `still_running[]` is empty.
34
34
 
35
- `leadbay_bulk_enrich_status` answers `ENRICH_JOB_NO_COUNTERS` when the enrichment
36
- notification carries no per-contact counters (the common case) — re-call with the
37
- `lead_ids` the launch returned.
35
+ `leadbay_bulk_enrich_status` no longer answers `ENRICH_JOB_NO_COUNTERS`. An
36
+ enrichment notification never carries per-contact counters, so the tool reads the
37
+ job's lead set back from the backend and answers through the per-lead path.
38
38
 
39
39
  ## Errors removed
40
40
 
@@ -43,8 +43,8 @@ notification carries no per-contact counters (the common case) — re-call with
43
43
  no longer exist, and neither does `~/.leadbay/bulks.json` or the
44
44
  `LEADBAY_BULK_STORE_ALLOW_MEMORY` escape hatch. The replacements are
45
45
  `ENRICH_STATUS_INPUT_REQUIRED`, `ENRICH_JOB_NOT_FOUND`, `ENRICH_JOB_WRONG_KIND`,
46
- `ENRICH_JOB_NO_COUNTERS`, `QUALIFY_STATUS_INPUT_REQUIRED`, `QUALIFY_JOB_NOT_FOUND`
47
- and `QUALIFY_JOB_WRONG_KIND`. The 0.5.0 sections below describe the old store and
46
+ `QUALIFY_STATUS_INPUT_REQUIRED`, `QUALIFY_JOB_NOT_FOUND` and
47
+ `QUALIFY_JOB_WRONG_KIND`. The 0.5.0 sections below describe the old store and
48
48
  are kept as history.
49
49
 
50
50
  ---
package/dist/bin.js CHANGED
@@ -1982,7 +1982,7 @@ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or
1982
1982
 
1983
1983
  WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
1984
1984
  `;
1985
- leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\` for the job counters in one call, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned for per-lead progress. \`lead_ids\` alone is a valid call and is the reliable one: the job lookup is a scan of your recent notifications, so an archived job may not be found, and an enrichment notification does not always carry counters (then the tool answers \`ENRICH_JOB_NO_COUNTERS\` with the backend's running/finished flag and asks for \`lead_ids\`) \u2014 but the leads always answer. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
1985
+ leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\`, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned. Either one alone is a valid call: with \`notification_id\` the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still answers with per-lead progress. Pass \`lead_ids\` as well whenever you still have them \u2014 the job lookup is a scan of your recent notifications, so an archived job may not be found, but the leads always answer. \`titles\` / \`email\` / \`phone\` scope the count to the roles and channel THIS run asked for. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
1986
1986
 
1987
1987
  WHEN TO USE: poll this REPEATEDLY after leadbay_enrich_titles returns a \`notification_id\`, staying active until the job is done \u2014 don't stop after one check, and don't hand the turn back to the user while progress is still climbing. "Done" = \`all_done:true\`, OR \`overall_progress.done\` has held steady across several SPACED polls (~15\u201330s apart) over at least ~90s\u20132 min of elapsed time (a reservation the provider never answers stays \`done:false\`, so \`all_done\` can stay false forever \u2014 don't spin indefinitely; a contact with nothing found does flip, to \`done:true\` with \`credits_used:0\`). Do NOT declare a plateau from the first few back-to-back reads: right after launch, \`overall_progress.done\` can sit flat while the backend is still spinning the job up, so space your polls out and give it real elapsed time before treating a flat count as terminal. Also do NOT declare a plateau while the result carries \`partial_failures\` \u2014 a flat \`done\` there means a transient per-lead fetch error (e.g. a 429), NOT an unresolvable contact; keep polling (respecting any \`retry_after\`) or surface it as a temporary status failure, rather than reporting those leads as permanently unresolved. Default \`include_contacts=false\` for the cheap interim polls; set \`include_contacts=true\` on the read you report from to pull each lead's enriched contacts for the completion report.
1988
1988
 
@@ -21087,7 +21087,26 @@ async function pMap(items, fn, concurrency) {
21087
21087
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
21088
21088
  return out;
21089
21089
  }
21090
- var STATUS_FETCH_CONCURRENCY, bulkEnrichStatus;
21090
+ async function readBulkLeadIds(client, bulkId) {
21091
+ const ids = [];
21092
+ for (let page = 0; page < BULK_LEADS_MAX_PAGES; page += 1) {
21093
+ let res;
21094
+ try {
21095
+ res = await client.request("GET", `/leads/bulk_enrichments/${encodeURIComponent(bulkId)}/leads?contacts=false&count=${BULK_LEADS_PAGE_SIZE}&page=${page}`);
21096
+ } catch {
21097
+ return null;
21098
+ }
21099
+ for (const item of res?.items ?? []) {
21100
+ if (item?.id)
21101
+ ids.push(item.id);
21102
+ }
21103
+ const pages = res?.pagination?.pages ?? 1;
21104
+ if (page + 1 >= pages)
21105
+ return ids;
21106
+ }
21107
+ return null;
21108
+ }
21109
+ var STATUS_FETCH_CONCURRENCY, BULK_LEADS_PAGE_SIZE, BULK_LEADS_MAX_PAGES, bulkEnrichStatus;
21091
21110
  var init_bulk_enrich_status = __esm({
21092
21111
  "../core/dist/composite/bulk-enrich-status.js"() {
21093
21112
  "use strict";
@@ -21097,6 +21116,8 @@ var init_bulk_enrich_status = __esm({
21097
21116
  init_credits_helpers();
21098
21117
  init_tool_descriptions_generated();
21099
21118
  STATUS_FETCH_CONCURRENCY = 5;
21119
+ BULK_LEADS_PAGE_SIZE = 100;
21120
+ BULK_LEADS_MAX_PAGES = 3;
21100
21121
  bulkEnrichStatus = {
21101
21122
  name: "leadbay_bulk_enrich_status",
21102
21123
  annotations: {
@@ -21112,7 +21133,7 @@ var init_bulk_enrich_status = __esm({
21112
21133
  properties: {
21113
21134
  notification_id: {
21114
21135
  type: "string",
21115
- description: "The `notification_id` returned by leadbay_enrich_titles. Gives the job-level counters in one call."
21136
+ description: "The `notification_id` returned by leadbay_enrich_titles. Answers on its own: the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still gives per-lead progress."
21116
21137
  },
21117
21138
  lead_ids: {
21118
21139
  type: "array",
@@ -21176,7 +21197,7 @@ var init_bulk_enrich_status = __esm({
21176
21197
  },
21177
21198
  execute: async (client, params, ctx) => {
21178
21199
  const includeContacts = params.include_contacts ?? false;
21179
- const leadIds = params.lead_ids ?? [];
21200
+ let leadIds = params.lead_ids ?? [];
21180
21201
  const startMs = Date.now();
21181
21202
  if (!params.notification_id && leadIds.length === 0) {
21182
21203
  return {
@@ -21189,6 +21210,7 @@ var init_bulk_enrich_status = __esm({
21189
21210
  let bp = null;
21190
21211
  let inProgress = null;
21191
21212
  let launchedAt = null;
21213
+ let bulkAnchorId = null;
21192
21214
  if (params.notification_id) {
21193
21215
  const n = await readNotificationById(client, params.notification_id);
21194
21216
  if (n && inferKind(n) !== "bulk_enrich") {
@@ -21204,6 +21226,7 @@ var init_bulk_enrich_status = __esm({
21204
21226
  bp = n.bulk_progress;
21205
21227
  inProgress = n.in_progress;
21206
21228
  launchedAt = n.created_at;
21229
+ bulkAnchorId = anchorIdFor(n, "bulk_enrich");
21207
21230
  } else if (leadIds.length === 0) {
21208
21231
  return {
21209
21232
  error: true,
@@ -21213,6 +21236,9 @@ var init_bulk_enrich_status = __esm({
21213
21236
  };
21214
21237
  }
21215
21238
  }
21239
+ if (!bp && leadIds.length === 0 && bulkAnchorId !== null) {
21240
+ leadIds = await readBulkLeadIds(client, bulkAnchorId) ?? [];
21241
+ }
21216
21242
  if (leadIds.length > 0) {
21217
21243
  const wantTitles = new Set((params.titles ?? []).map((t) => t.trim().toLowerCase()));
21218
21244
  const inThisRun = (c) => {
@@ -21316,13 +21342,18 @@ var init_bulk_enrich_status = __esm({
21316
21342
  };
21317
21343
  }
21318
21344
  if (!bp) {
21345
+ const isReportRead2 = inProgress === false;
21346
+ const creditsRemaining2 = isReportRead2 ? await readCreditsRemaining(client, true) : null;
21319
21347
  return {
21320
- error: true,
21321
- code: "ENRICH_JOB_NO_COUNTERS",
21322
- message: `This enrichment notification carries no per-contact counters; the backend reports it as ${inProgress ? "still running" : "finished"}`,
21323
- hint: "Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) \u2014 that path counts contacts directly and works whether or not the notification has counters.",
21348
+ notification_id: params.notification_id,
21349
+ ...launchedAt ? { launched_at: launchedAt } : {},
21350
+ status: inProgress ? "launched" : "complete",
21351
+ leads: [],
21352
+ overall_progress: { done: 0, total: 0, done_ratio: 0 },
21324
21353
  ...inProgress !== null ? { in_progress: inProgress } : {},
21325
- ...launchedAt ? { launched_at: launchedAt } : {}
21354
+ all_done: isReportRead2,
21355
+ ...isReportRead2 ? { credits_remaining: creditsRemaining2 } : {},
21356
+ counts_hint: "This job's notification carries no per-contact counters and its lead set could not be read back, so done/total are 0. Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) to count contacts directly."
21326
21357
  };
21327
21358
  }
21328
21359
  const done = bp.success_count + bp.failure_count + bp.quota_hit_count;
@@ -32162,7 +32193,7 @@ var OAUTH_BASE_URLS = {
32162
32193
  fr: "https://staging.api.leadbay.app"
32163
32194
  }
32164
32195
  };
32165
- var VERSION = "0.39.8";
32196
+ var VERSION = "0.39.9";
32166
32197
  var HELP = `
32167
32198
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
32168
32199
 
@@ -4932,7 +4932,7 @@ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or
4932
4932
 
4933
4933
  WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
4934
4934
  `;
4935
- var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\` for the job counters in one call, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned for per-lead progress. \`lead_ids\` alone is a valid call and is the reliable one: the job lookup is a scan of your recent notifications, so an archived job may not be found, and an enrichment notification does not always carry counters (then the tool answers \`ENRICH_JOB_NO_COUNTERS\` with the backend's running/finished flag and asks for \`lead_ids\`) \u2014 but the leads always answer. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
4935
+ var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Pass the \`notification_id\`, and/or the \`lead_ids\` + \`titles\` + \`email\` / \`phone\` the launch returned. Either one alone is a valid call: with \`notification_id\` the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still answers with per-lead progress. Pass \`lead_ids\` as well whenever you still have them \u2014 the job lookup is a scan of your recent notifications, so an archived job may not be found, but the leads always answer. \`titles\` / \`email\` / \`phone\` scope the count to the roles and channel THIS run asked for. When \`include_contacts=true\` (opt-in), includes each contact's email/phone_number/job_title/enrichment.done.
4936
4936
 
4937
4937
  WHEN TO USE: poll this REPEATEDLY after leadbay_enrich_titles returns a \`notification_id\`, staying active until the job is done \u2014 don't stop after one check, and don't hand the turn back to the user while progress is still climbing. "Done" = \`all_done:true\`, OR \`overall_progress.done\` has held steady across several SPACED polls (~15\u201330s apart) over at least ~90s\u20132 min of elapsed time (a reservation the provider never answers stays \`done:false\`, so \`all_done\` can stay false forever \u2014 don't spin indefinitely; a contact with nothing found does flip, to \`done:true\` with \`credits_used:0\`). Do NOT declare a plateau from the first few back-to-back reads: right after launch, \`overall_progress.done\` can sit flat while the backend is still spinning the job up, so space your polls out and give it real elapsed time before treating a flat count as terminal. Also do NOT declare a plateau while the result carries \`partial_failures\` \u2014 a flat \`done\` there means a transient per-lead fetch error (e.g. a 429), NOT an unresolvable contact; keep polling (respecting any \`retry_after\`) or surface it as a temporary status failure, rather than reporting those leads as permanently unresolved. Default \`include_contacts=false\` for the cheap interim polls; set \`include_contacts=true\` on the read you report from to pull each lead's enriched contacts for the completion report.
4938
4938
 
@@ -23290,6 +23290,27 @@ async function pMap(items, fn, concurrency) {
23290
23290
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
23291
23291
  return out;
23292
23292
  }
23293
+ var BULK_LEADS_PAGE_SIZE = 100;
23294
+ var BULK_LEADS_MAX_PAGES = 3;
23295
+ async function readBulkLeadIds(client, bulkId) {
23296
+ const ids = [];
23297
+ for (let page = 0; page < BULK_LEADS_MAX_PAGES; page += 1) {
23298
+ let res;
23299
+ try {
23300
+ res = await client.request("GET", `/leads/bulk_enrichments/${encodeURIComponent(bulkId)}/leads?contacts=false&count=${BULK_LEADS_PAGE_SIZE}&page=${page}`);
23301
+ } catch {
23302
+ return null;
23303
+ }
23304
+ for (const item of res?.items ?? []) {
23305
+ if (item?.id)
23306
+ ids.push(item.id);
23307
+ }
23308
+ const pages = res?.pagination?.pages ?? 1;
23309
+ if (page + 1 >= pages)
23310
+ return ids;
23311
+ }
23312
+ return null;
23313
+ }
23293
23314
  var bulkEnrichStatus = {
23294
23315
  name: "leadbay_bulk_enrich_status",
23295
23316
  annotations: {
@@ -23305,7 +23326,7 @@ var bulkEnrichStatus = {
23305
23326
  properties: {
23306
23327
  notification_id: {
23307
23328
  type: "string",
23308
- description: "The `notification_id` returned by leadbay_enrich_titles. Gives the job-level counters in one call."
23329
+ description: "The `notification_id` returned by leadbay_enrich_titles. Answers on its own: the tool reads the job's lead set back from the backend, so an id kept from an earlier conversation still gives per-lead progress."
23309
23330
  },
23310
23331
  lead_ids: {
23311
23332
  type: "array",
@@ -23369,7 +23390,7 @@ var bulkEnrichStatus = {
23369
23390
  },
23370
23391
  execute: async (client, params, ctx) => {
23371
23392
  const includeContacts = params.include_contacts ?? false;
23372
- const leadIds = params.lead_ids ?? [];
23393
+ let leadIds = params.lead_ids ?? [];
23373
23394
  const startMs = Date.now();
23374
23395
  if (!params.notification_id && leadIds.length === 0) {
23375
23396
  return {
@@ -23382,6 +23403,7 @@ var bulkEnrichStatus = {
23382
23403
  let bp = null;
23383
23404
  let inProgress = null;
23384
23405
  let launchedAt = null;
23406
+ let bulkAnchorId = null;
23385
23407
  if (params.notification_id) {
23386
23408
  const n = await readNotificationById(client, params.notification_id);
23387
23409
  if (n && inferKind(n) !== "bulk_enrich") {
@@ -23397,6 +23419,7 @@ var bulkEnrichStatus = {
23397
23419
  bp = n.bulk_progress;
23398
23420
  inProgress = n.in_progress;
23399
23421
  launchedAt = n.created_at;
23422
+ bulkAnchorId = anchorIdFor(n, "bulk_enrich");
23400
23423
  } else if (leadIds.length === 0) {
23401
23424
  return {
23402
23425
  error: true,
@@ -23406,6 +23429,9 @@ var bulkEnrichStatus = {
23406
23429
  };
23407
23430
  }
23408
23431
  }
23432
+ if (!bp && leadIds.length === 0 && bulkAnchorId !== null) {
23433
+ leadIds = await readBulkLeadIds(client, bulkAnchorId) ?? [];
23434
+ }
23409
23435
  if (leadIds.length > 0) {
23410
23436
  const wantTitles = new Set((params.titles ?? []).map((t) => t.trim().toLowerCase()));
23411
23437
  const inThisRun = (c) => {
@@ -23509,13 +23535,18 @@ var bulkEnrichStatus = {
23509
23535
  };
23510
23536
  }
23511
23537
  if (!bp) {
23538
+ const isReportRead2 = inProgress === false;
23539
+ const creditsRemaining2 = isReportRead2 ? await readCreditsRemaining(client, true) : null;
23512
23540
  return {
23513
- error: true,
23514
- code: "ENRICH_JOB_NO_COUNTERS",
23515
- message: `This enrichment notification carries no per-contact counters; the backend reports it as ${inProgress ? "still running" : "finished"}`,
23516
- hint: "Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) \u2014 that path counts contacts directly and works whether or not the notification has counters.",
23541
+ notification_id: params.notification_id,
23542
+ ...launchedAt ? { launched_at: launchedAt } : {},
23543
+ status: inProgress ? "launched" : "complete",
23544
+ leads: [],
23545
+ overall_progress: { done: 0, total: 0, done_ratio: 0 },
23517
23546
  ...inProgress !== null ? { in_progress: inProgress } : {},
23518
- ...launchedAt ? { launched_at: launchedAt } : {}
23547
+ all_done: isReportRead2,
23548
+ ...isReportRead2 ? { credits_remaining: creditsRemaining2 } : {},
23549
+ counts_hint: "This job's notification carries no per-contact counters and its lead set could not be read back, so done/total are 0. Re-call with the `lead_ids` returned by leadbay_enrich_titles (plus titles/email/phone) to count contacts directly."
23519
23550
  };
23520
23551
  }
23521
23552
  const done = bp.success_count + bp.failure_count + bp.quota_hit_count;
@@ -29435,7 +29466,7 @@ function parseWriteEnv(env = process.env) {
29435
29466
  }
29436
29467
 
29437
29468
  // src/http-server.ts
29438
- var VERSION = true ? "0.39.8" : "0.0.0-dev";
29469
+ var VERSION = true ? "0.39.9" : "0.0.0-dev";
29439
29470
  var PORT = Number(process.env.PORT ?? 8080);
29440
29471
  var HOST = process.env.HOST ?? "0.0.0.0";
29441
29472
  var logger = {
@@ -1805,7 +1805,7 @@ var init_installer_gui = __esm({
1805
1805
  init_install_dxt();
1806
1806
  init_install_shared();
1807
1807
  init_oauth();
1808
- VERSION = true ? "0.39.8" : "0.0.0-dev";
1808
+ VERSION = true ? "0.39.9" : "0.0.0-dev";
1809
1809
  MESSAGES = {
1810
1810
  en: {
1811
1811
  installer: {
@@ -1068,7 +1068,7 @@ async function oauthLogin(opts) {
1068
1068
  }
1069
1069
 
1070
1070
  // installer/installer-gui.ts
1071
- var VERSION = true ? "0.39.8" : "0.0.0-dev";
1071
+ var VERSION = true ? "0.39.9" : "0.0.0-dev";
1072
1072
  var MESSAGES = {
1073
1073
  en: {
1074
1074
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.39.8",
3
+ "version": "0.39.9",
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",