@leadbay/mcp 0.8.0 → 0.9.0

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,32 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.9.0 — 2026-05-16
4
+
5
+ **RENDERING + NEXT STEPS blocks in every composite tool description**: agents consuming composite tools today default to prose summaries when they don't know how to present the data. Tool descriptions now carry two new prescriptive blocks the agent reads verbatim — `RENDERING` (a recipe for how to present the response: table columns, glyph palette, link targets, fields to hide) and `NEXT STEPS` (an observation → suggestion table the agent picks 2–3 contextually relevant offers from, never reciting the whole menu). Lands on the seven highest-leverage composites: `leadbay_pull_leads`, `leadbay_research_company`, `leadbay_prepare_outreach`, `leadbay_bulk_qualify_leads`, `leadbay_import_leads`, `leadbay_import_and_qualify`, `leadbay_import_status`, `leadbay_list_mappable_fields`, `leadbay_resolve_import_rows`. The blocks add ~5–7k chars per description, so the audit's per-tool char budget was raised from 3500 → 12000 with a comment explaining the design tradeoff.
6
+
7
+ **Three new snippet categories in promptforge** — `snippets/rendering/` (response-shape recipes: `score-bar`, `pull-leads-table`, `research-company-card`, `prepare-outreach-brief`, `import-result`, `status-inline`), `snippets/next-steps/` (one observation→suggestion table per composite), and `snippets/linking/` (`contact-linkedin` for the priority chain → real `linkedin_page` → people-search fallback with `°`-flag; `company-socials` for the multi-platform `social_urls` pill row). Each composite's `.md.tmpl` now `{{include:rendering/...}}` + `{{include:next-steps/...}}` instead of inlining the rules.
8
+
9
+ **New iron-law `outcome-after-outreach`**: when the user reports outreach happened ("I sent it", "she didn't pick up", a forwarded email thread), the agent MUST (a) call `leadbay_report_outreach` with verification AND (b) ask about the outcome and set `epilogue_status` to one of the 4 canonical values. User-facing dialogue uses "outcome" not "epilogue"; "follow-ups" not "Monitor". Included from `prepare_outreach.md.tmpl` and the new prospecting-overview prompt. Closes the loop that was silently de-ranking every future follow-up suggestion.
10
+
11
+ **New `leadbay_prospecting_overview` prompt → Claude Code skill**: ships at `prompts/leadbay_prospecting_overview.md.tmpl` and auto-emits `SKILL.md` via the existing skills pipeline. Orients the agent to the two-entry-point workflow (discovery via `pull_leads` vs follow-up via the app's Monitor view), natural-language signal routing, the outreach loop, adaptive drafting based on connected outreach tools (Lemlist / Outreach.io / Salesloft / Apollo / HubSpot / Instantly / Attio / Amplemarket / generic), outcome-recording habit, snooze/pushback semantics, and the lens-pinning rule. Auto-triggers on Leadbay-related conversation; stays dormant otherwise.
12
+
13
+ **Composite output-shape fixes**:
14
+
15
+ - **B1 / B6 / B7 — contact `linkedin_page` is canonical and never the literal string `"null"`**: `pull_leads`, `research_lead`, `prepare_outreach`, and `get_lead_profile` all propagate `linkedin_page` on `recommended_contact` and every `contacts[]` entry, coercing the legacy `"null"` four-character string (a backend serialization bug) to real JSON null on the way out.
16
+ - **B4 — `leadbay_research_lead` outputSchema fix**: `firmographics.size` was declared as `string|null` while the composite returns `{min,max,low,high,label}`; `firmographics.location` was `string|null` while the composite returns `{city,state,country,full,pos}`; `firmographics.tags` items were `string` while the composite returns `{id,display_name,tag,score}`. All three corrected to match the actual `LeadSimplified` shape. Also tightened `social_presence` and `social_urls` declarations to typed objects (was `["object","string","null"]` etc.). The tool was unusable before this fix — every call rejected by MCP schema validation.
17
+ - **B8 — `recommended_contact_title` dropped**: this field duplicated `recommended_contact.job_title` everywhere it appeared (`pull_leads`, `research_lead`, `get_lead_profile`). Removed.
18
+ - **B12 / B15 — `leadbay_prepare_outreach` expanded `lead` block**: was a two-field stub (`{name, ai_summary, website}`); now includes `score`, `ai_agent_lead_score`, `split_ai_summary`, `location`, `size`, `phone_numbers`, `description`, `short_description`, `social_presence`, `social_urls`. The agent no longer needs a second `research_company` call to render basic context.
19
+ - **B13 — self-polling enrichment**: `enrichment.complete: boolean` added. The brief now re-fetches contacts ONCE after triggering enrichment, and exposes `complete: true` when the recommended contact has either email or phone. The agent just re-calls `leadbay_prepare_outreach(leadId)` (no `enrich`) to poll; no separate `leadbay_get_contacts` tool needed.
20
+ - **B16 — `additional_contacts_count`**: clearer name. `other_contacts_count` kept as a deprecated alias for one release.
21
+ - **B21 — recommended_contact shape standardized**: always emit the post-enrichment field shape (`contact_id`, `first_name`, `last_name`, `job_title`, `email`, `phone_number`, `linkedin_page`, `is_org_contact`) with nulls in un-enriched fields — no more shape-flipping between pre- and post-enrichment.
22
+ - **`pull_leads` augmented**: trimmed shape now includes `ai_summary`, `split_ai_summary`, `phone_numbers`, `social_presence`, `social_urls`. Agents can render rich tables without verbose mode.
23
+
24
+ **Audit budget raised**: tool description per-tool char cap raised from 3500 → 12000. The largest tool descriptions are now `leadbay_research_company` (~10.5k chars) and `leadbay_prepare_outreach` (~10.1k chars). The plan was designed knowing this cost — the new char budget is justified by what the agent now does without prompting (table layout, glyph palette, observation→suggestion menu).
25
+
26
+ **New composite `leadbay_pull_followups`** — the Monitor view (re-engagement workflow), distinct from `leadbay_pull_leads` (discovery). Wraps `GET /1.5/monitor?personal=&liked=&filtered=&count=&page=` plus `GET/POST /1.5/monitor/filter` (server-persisted FilterItem). Accepts a `set_filter: { criteria: FilterCriterion[] }` parameter that POSTs first, then re-pulls — the same store-then-apply mechanism the Leadbay app uses. The composite excludes leads with active pushback client-side (defense-in-depth — the backend likely already does this) and reports `total_excluded_by_pushback`. Status-badge derived in the rendering rule from existing fields (`epilogue_status` + `last_prospecting_action_at` + `new`) — no new backend field needed. Endpoints verified live via the discover-monitor-activate wiki page; backend handler is `MonitorRoutes.kt:getMonitor()` → `Database.monitor.findAll`.
27
+
28
+ **Two new granular tools — `leadbay_set_pushback` / `leadbay_remove_pushback`**: snooze a lead (or a bulk set, up to 1000) for 3 / 6 / 12 months. Wraps `POST /leads/pushback` and `POST /leads/remove_pushback` (mirrors the existing `/leads/epilogue` / `/leads/remove_epilogue` pattern). Accepts short labels (`"3"` / `"6"` / `"12"`) or the wire-format enum (`PUSHBACK_3` / `PUSHBACK_6` / `PUSHBACK_12`). User-facing dialogue says "snooze for N months" — never "pushback status". Pull_followups excludes leads with active pushback from its results until expiry. Available behind `LEADBAY_MCP_ADVANCED=1 + LEADBAY_MCP_WRITE=1` (granular write).
29
+
3
30
  ## 0.8.0 — 2026-05-15
4
31
 
5
32
  **Prompts ship as Claude Code skills + initialize advertises the prompt catalog**: the six MCP prompts (`leadbay_daily_check_in`, `leadbay_research_a_domain`, `leadbay_import_file`, `leadbay_log_outreach`, `leadbay_qualify_top_n`, `leadbay_refine_audience`) now also emit as auto-discovered Claude Code skills under `.claude-plugin/plugins/leadbay/skills/<name>/SKILL.md` from the same `.md.tmpl` source. `{{arg:NAME}}` placeholders are rewritten in-place as natural-language extraction instructions because skills have no structured-argument system. The MCP server's `initialize` response now splices a `PROMPT_CATALOG_INSTRUCTIONS` paragraph into the `instructions` payload so UI-blind clients (Cowork is the prototypical case) learn the prompt set; bullets that reference a tool gated off by the current config are suppressed (preserves the iter-12 invariant that the system prompt never names a tool the agent cannot call). Plugin manifest bumped `0.6.2` → `0.6.3`.
package/dist/bin.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  granularReadTools,
9
9
  granularWriteTools,
10
10
  resolveRegion
11
- } from "./chunk-NVGZ432E.js";
11
+ } from "./chunk-2EGNZRD7.js";
12
12
 
13
13
  // src/bin.ts
14
14
  import { realpathSync } from "fs";
@@ -41,7 +41,7 @@ These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads
41
41
 
42
42
  ## Rule 1 \u2014 Pin the lens
43
43
 
44
- After your first \`leadbay_pull_leads\` call, capture \`response.lensId\` into your working memory and **pass it explicitly as \`lensId\` to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
44
+ After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
45
45
 
46
46
  ## Rule 2 \u2014 Prefer async for bulk operations
47
47
 
@@ -70,7 +70,7 @@ If you're resuming an interrupted session (you see a previous Phase already comp
70
70
  Call \`leadbay_account_status\` to see what quota I have left and which lens is active. Note the remaining \`ai_rescore_remaining\` and \`web_fetch_remaining\` budgets \u2014 Phase 4 enrichment depends on them.
71
71
 
72
72
  # PHASE 2 \u2014 FRESH BATCH
73
- Call \`leadbay_pull_leads\` to get today's fresh batch. Capture \`lensId\` from the response. **Use it as an explicit \`lensId\` argument on every subsequent Leadbay call this session** \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (See Rule 1 above \u2014 a mid-session lens shift discards your top-10 work.)
73
+ Call \`leadbay_pull_leads\` to get today's fresh batch. Capture \`response.lens.id\` (the response nests it under \`lens\`). **Use it as an explicit \`lensId\` argument on every subsequent Leadbay call this session** \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (See Rule 1 above \u2014 a mid-session lens shift discards your top-10 work.)
74
74
 
75
75
  # PHASE 3 \u2014 TRIAGE (top 10, motivational framing)
76
76
 
@@ -276,6 +276,128 @@ After I answer, call \`leadbay_report_outreach({lead_id: '{{arg:lead_id}}', note
276
276
  # PHASE 3 \u2014 CONFIRM
277
277
  Tell me the outreach was logged, name the verification.source used, and surface the response's \`outreach_id\` if present so I can refer back to it.
278
278
  `;
279
+ var leadbay_prospecting_overview = `
280
+ # Leadbay Prospecting \u2014 Orientation
281
+
282
+ You are working with Leadbay through the \`leadbay_*\` MCP tools. This prompt orients you to the user's mental model so you don't re-discover the workflow each session.
283
+
284
+ # Resilience rules for Leadbay long-running tools
285
+
286
+ These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_research_lead\`, \`leadbay_import_and_qualify\`, or \`leadbay_enrich_titles\`. **Treat timeouts and stream-closed errors as transient, not as signals to replan.**
287
+
288
+ ## Rule 1 \u2014 Pin the lens
289
+
290
+ After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
291
+
292
+ ## Rule 2 \u2014 Prefer async for bulk operations
293
+
294
+ \`leadbay_bulk_qualify_leads\` and \`leadbay_import_and_qualify\` accept \`wait_for_completion:false\`, which returns \`{status:'running', qualify_id}\` immediately. Then poll \`leadbay_qualify_status\` (or \`leadbay_import_status\`) every ~10s until the job completes. **Use the async pattern by default** \u2014 the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading \`"Request timed out"\` even though the server is still working.
295
+
296
+ ## Rule 3 \u2014 Serialize \`leadbay_research_lead\` fan-out
297
+
298
+ \`leadbay_research_lead\` is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. **Call it sequentially**, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue \u2014 do not abandon the remaining leads.
299
+
300
+ ## Rule 4 \u2014 Retry, don't replan
301
+
302
+ If a Leadbay tool returns \`"Request timed out"\`, \`"stream closed"\`, or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
303
+
304
+ 1. For bulk tools \u2014 retry with \`wait_for_completion:false\` and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.
305
+ 2. For single-lead tools \u2014 retry the same call once. If it still fails, record the lead id and continue with the rest of the workflow.
306
+ 3. **Do not** switch strategies (e.g. "the endpoint is broken, let me re-pull from scratch"). The earlier work is still valid; the timeout was the wire.
307
+
308
+ If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retry it, explicitly pass the lensId you captured (if any), and continue.
309
+
310
+
311
+ ## The two entry points
312
+
313
+ Leadbay supports two parallel ways to find leads to act on. Detect which entry the user wants from their natural language, then route accordingly.
314
+
315
+ \`\`\`
316
+ Discovery entry Follow-up entry
317
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
318
+ leadbay_pull_leads (re-engagement on the Monitor view \u2014
319
+ (new from the Discover currently surfaced via the Leadbay app
320
+ wishlist, lens-driven) UI; MCP-side composite forthcoming)
321
+ \u2502 \u2502
322
+ \u2502 optional: \u2502 filters by user phrasing:
323
+ \u2502 research_company / \u2502 geo, sector, recency,
324
+ \u2502 research_lead \u2502 liked / pushback / outcome
325
+ \u2502 (deepen profile) \u2502
326
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
327
+ \u25BC
328
+ leadbay_prepare_outreach
329
+ (single-contact brief)
330
+ \u2502
331
+ \u25BC
332
+ user does outreach
333
+ (agent drafts email / call / DM)
334
+ \u2502
335
+ \u25BC
336
+ leadbay_report_outreach
337
+ (close the loop \u2014 verification + outcome)
338
+ \`\`\`
339
+
340
+ **Discovery signals**: "show me my leads", "what's new today", "any new prospects", "let's prospect", no mention of prior context. Route to \`leadbay_pull_leads\`.
341
+
342
+ **Follow-up signals**: "what should I follow up on", "leads I haven't contacted", "leads in [city]", "before my trip", "this week", "this month", "what's overdue", explicit mention of recent or pending actions. Route to \`leadbay_pull_followups\` \u2014 the Monitor view of known leads. Apply \`set_filter\` for geo / sector / recency / action-type refinement; the filter is server-persisted across sessions.
343
+
344
+ When in doubt, ask. The two paths return overlapping but differently-ranked data; presenting the wrong one wastes the user's time.
345
+
346
+ ## The outreach loop
347
+
348
+ After \`leadbay_prepare_outreach\` returns a brief, the agent drafts. Adapt to the user's connected outreach tools \u2014 these change the draft idiom:
349
+
350
+ | Tool | Channel strength | Draft idiom |
351
+ |---------------------|-------------------------------------------|---------------------------------------------------------|
352
+ | **Lemlist** | Email + LinkedIn + WhatsApp + cold call | Sequence step (subject + body + step-N timing) |
353
+ | **Outreach.io** | Email + call cadence | Sequence step; surface intent signals for forecasting |
354
+ | **Salesloft** | Email + call + LinkedIn cadence | Cadence step; pair with deal context if available |
355
+ | **Apollo** | Email-first | Clean cold email; include prospect signal references |
356
+ | **HubSpot Sales Hub** | Email + tasks | HubSpot sequence email; recommend a task type |
357
+ | **Instantly** | Email at scale | Deliverability-conscious email (<80 words, no link spam)|
358
+ | **Attio** | Email + LinkedIn from CRM | Outreach record on the Attio person; reference the deal |
359
+ | **Amplemarket** | 7 channels (email/LI/call/SMS/WA/voice/video) | Per-channel variants; suggest the strongest channel |
360
+ | **Generic / Gmail / Outlook** | Email | Clean copy-paste email; no tool-specific syntax |
361
+
362
+ **Detect the active tool** in this priority order:
363
+ 1. The host's installed-connector / installed-MCP inventory, when available (Claude Desktop, Cowork).
364
+ 2. The conversation \u2014 what tools has the user mentioned or used recently? ("I'll send via lemlist" \u2192 assume lemlist.)
365
+ 3. Ask the user when uncertain.
366
+
367
+ ## The outcome / closing-the-loop habit
368
+
369
+ IRON LAW \u2014 OUTCOME AFTER OUTREACH. The moment the user reports outreach happened ("I sent it", "she didn't pick up", "left a voicemail", "they replied", a forwarded email thread, a calendar invite), you MUST (1) call leadbay_report_outreach with verification (gmail_message_id, calendar_event_id, or the user's literal one-sentence confirmation as user_confirmed.ref) AND (2) ask the user about the outcome and set epilogue_status to one of the 4 canonical values: EPILOGUE_INTEREST_VALIDATED_OR_MEETING_PLANED ("Meeting booked"), EPILOGUE_COULD_NOT_REACH_STILL_TRYING ("Trying to reach"), EPILOGUE_NOT_INTERESTED_LOST ("Not interested"), EPILOGUE_STILL_CHASING ("In progress"). Use the user-facing labels in dialogue ("What's the outcome \u2014 meeting booked, trying to reach, not interested, or in progress?"); never say "epilogue" out loud. Skipping this step silently de-ranks every future follow-up suggestion because pull_followups depends on honest, current outcomes.
370
+
371
+
372
+ User-facing dialogue:
373
+
374
+ - **Always say "outcome", never "epilogue"** \u2014 the backend field is \`epilogue_status\` of type \`EpilogueStatusType\` but that's wire-format jargon. The 4 user-facing labels:
375
+ - \`EPILOGUE_INTEREST_VALIDATED_OR_MEETING_PLANED\` \u2192 **Meeting booked** \u{1F3AF}
376
+ - \`EPILOGUE_COULD_NOT_REACH_STILL_TRYING\` \u2192 **Trying to reach** \u26A1
377
+ - \`EPILOGUE_NOT_INTERESTED_LOST\` \u2192 **Not interested** \u2744
378
+ - \`EPILOGUE_STILL_CHASING\` \u2192 **In progress** \u{1F7E2}
379
+ - **Always say "follow-ups", never "Monitor"** \u2014 "Monitor" is internal app jargon; salespeople say "follow-ups".
380
+
381
+ ## "Not now" / snooze / pushback
382
+
383
+ When the user says "not now", "next quarter", "follow up in 3 / 6 / 12 months", "next year", etc., this is a **pushback** action (not a note). Call \`leadbay_set_pushback({lead_ids, status})\` where \`status\` is \`3\`, \`6\`, or \`12\` (months). The lead drops out of \`leadbay_pull_followups\` until the window expires. Use \`leadbay_remove_pushback\` to revive a lead ahead of expiry. User-facing dialogue: say "snooze for N months", not "pushback".
384
+
385
+ ## Imports
386
+
387
+ When the user mentions a CSV / list / their CRM, use the **\`leadbay_import_file\`** prompt \u2014 it walks through scan \u2192 resolve \u2192 preserve \u2192 commit. The single-shot tool \`leadbay_import_leads\` is for clean, mechanical imports; the prompt handles messy ones.
388
+
389
+ ## AI scoring on the daily batch
390
+
391
+ Roughly the **top 10** of every \`leadbay_pull_leads\` response carry full AI qualification (\`qualification_summary.answered > 0\`, \`ai_agent_lead_score\`, \u2756 caps in the rendered bar). Leads below the top ~10 are NOT worse \u2014 the system is saving resources. A healthy daily rhythm: bulk-qualify the rows WITHOUT \u2756 caps so tomorrow's top-10 is richer. Use \`leadbay_bulk_qualify_leads([leadIds])\` for this; default to \`wait_for_completion:false\` for any count > 5.
392
+
393
+ ## Lens pinning
394
+
395
+ After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` (the response nests it under \`lens\`) and pass it explicitly as the \`lensId\` argument to every subsequent Leadbay call this session. Lens shifts mid-workflow throw away your prior batch \u2014 see Rule 1 in the long-running-tools heuristics above.
396
+
397
+ ## What to read once you've matched intent
398
+
399
+ You don't need to memorize every tool here \u2014 each tool's own description carries a RENDERING block (how to present the response) and a NEXT STEPS block (observation \u2192 suggestion table). Read the relevant tool's description in full when the user picks an entry point. This overview just gets you to the right starting tool.
400
+ `;
279
401
  var leadbay_qualify_top_n = `
280
402
  Qualify the top {{arg:count_or_default}} un-qualified leads in the active Leadbay lens.
281
403
 
@@ -338,6 +460,13 @@ I work on", or anything resembling "show me the day's batch".
338
460
  `, "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_research_lead", "leadbay_bulk_qualify_leads", "leadbay_enrich_contacts"], "failure_modes": ["Calls leadbay_report_outreach without explicit user authorization", "Surfaces fewer than 10 leads when more are available, or fails to top up via leadbay_qualify_top_n when the batch is short", 'Writes flat one-line summaries instead of motivational "why prospect this today" framing', "Skips deep research on promising leads (Phase 4) \u2014 the agent must call leadbay_research_lead on each, not just one", "Triggers contact enrichment without asking the user first (it consumes quota)", "Skips the STOP byproduct and proposes next actions on its own", 'Fires 10 parallel leadbay_research_lead calls and treats "stream closed" errors as terminal \u2014 must serialize and retry singletons', "Re-pulls leadbay_pull_leads without passing the captured lensId, allowing a backend lens shift to discard the Phase 2 batch", 'Treats a "Request timed out" from leadbay_bulk_qualify_leads as terminal instead of retrying with wait_for_completion:false + qualify_status polling'] },
339
461
  leadbay_import_file: { "name": "leadbay_import_file", "short_description": "Import a user-supplied CSV/file into Leadbay through five phases with\nevidence gates \u2014 scan, derive, resolve identities, preserve & commit,\nthen optionally qualify and report. The job is to maximize how many\nrows the Leadbay system actually ingests and matches.\n", "arguments": [{ "name": "file", "description": "Path or user-visible name of the CSV/file to import. If omitted, use the file the user attached or referenced.", "required": false }, { "name": "instruction", "description": 'Additional user goal, e.g. "then qualify the leads", "preserve owner phone as a custom field", or "only import restaurants in Manhattan".', "required": false }], "expected_calls": ["leadbay_resolve_import_rows", "leadbay_list_mappable_fields", "leadbay_create_custom_field", "leadbay_import_leads", "leadbay_import_and_qualify", "leadbay_add_note", "leadbay_import_status"], "failure_modes": ["Picks LEADBAY_ID from score alone, name-only, fuzzy-name-only, root-domain-only, brand-only, postcode-only, or city-only evidence", "Drops meaningful business notes or CRM record links instead of preserving them as custom fields or lead notes", "Treats a consumer mailbox domain (gmail.com, hotmail.com, ...) as the company domain", "Skips deriving company_domain from a business email when no website column exists (this kills match rate)", "Skips the COLUMN PRESERVATION PLAN byproduct before importing", "Skips the DECISION LOG byproduct before writing LEADBAY_ID", "Returns the imported records WITHOUT writing LEADBAY_ID values back into the user's file (leaves the user no audit trail of what matched)", "Fabricates leadIds, contact emails, or mapping IDs not present in the file or a tool response"] },
340
462
  leadbay_log_outreach: { "name": "leadbay_log_outreach", "short_description": "Log outreach (an email I sent, a call I made, a meeting I had) on a\nspecific lead. Captures verification so the SDR pipeline trusts the entry.\n", "arguments": [{ "name": "lead_id", "description": "The lead UUID. Get it from leadbay_pull_leads or leadbay_research_lead.", "required": true }, { "name": "summary", "description": "1-2 sentences describing what I did (e.g. 'Sent intro email to CTO citing recent Hornsea contract').", "required": true }], "expected_calls": ["leadbay_report_outreach"], "failure_modes": ["Calls leadbay_report_outreach without first collecting a verification source", "Fabricates a gmail_message_id or calendar_event_id (the human team treats verification as canonical)", "Records outreach to a different lead_id than the one the user supplied", "Skips the dry_run step when the user is unsure what would be sent"] },
463
+ leadbay_prospecting_overview: { "name": "leadbay_prospecting_overview", "short_description": `Orientation for working with Leadbay from any host \u2014 discovery vs.
464
+ follow-up, the outreach loop, outcome recording, imports, pushback /
465
+ snooze, and the connected-outreach-tool registry. Trigger when the
466
+ conversation involves Leadbay leads, prospecting, pipeline, follow-up,
467
+ outreach, or lens / ICP \u2014 anything from "show me my leads" to "what
468
+ should I follow up on" to "I'll send via lemlist".
469
+ `, "arguments": [], "expected_calls": ["leadbay_account_status", "leadbay_pull_leads", "leadbay_pull_followups", "leadbay_research_company", "leadbay_research_lead", "leadbay_prepare_outreach", "leadbay_report_outreach", "leadbay_set_pushback", "leadbay_remove_pushback", "leadbay_bulk_qualify_leads", "leadbay_enrich_titles", "leadbay_import_leads", "leadbay_add_note", "leadbay_adjust_audience"], "failure_modes": ['Drives outreach without asking the user "how did it go?" afterwards \u2014 leaving prospecting_actions and epilogue_status stale', 'Says "epilogue" in user-facing dialogue instead of "outcome"', 'Says "Monitor" in user-facing dialogue instead of "follow-ups"', 'Treats a "not now / next quarter" reply as a note instead of routing through the pushback mechanism', "Drafts outreach in a generic format when the user has a connected sequencer (lemlist, Outreach.io, etc.) that has its own idiom", "Re-pulls leads without passing the captured lensId, allowing a backend lens shift to discard prior work", "Skips the STOP byproduct in any multi-step workflow it triggers"] },
341
470
  leadbay_qualify_top_n: { "name": "leadbay_qualify_top_n", "short_description": "Bulk-qualify the top N un-qualified leads in the active lens. Uses\nleadbay_bulk_qualify_leads with a sensible default budget.\n", "arguments": [{ "name": "count", "description": "How many leads to qualify (default 10, max 25). Higher counts may take 5+ minutes.", "required": false }], "expected_calls": ["leadbay_bulk_qualify_leads", "leadbay_research_lead"], "failure_modes": ["Picks a count larger than the user asked for (or larger than the max 25)", "Glosses over still-running leads in the summary instead of naming them", "Recommends a lead from the existing qualified pool instead of one from this batch's actual results"] },
342
471
  leadbay_refine_audience: { "name": "leadbay_refine_audience", "short_description": "Refine the kind of leads Leadbay surfaces beyond firmographics, with a\nfree-text instruction. Handles the clarification round-trip if the new\nprompt is ambiguous.\n", "arguments": [{ "name": "instruction", "description": "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.", "required": true }], "expected_calls": ["leadbay_refine_prompt", "leadbay_account_status"], "failure_modes": ["Calls leadbay_answer_clarification on the user's behalf instead of surfacing the clarification verbatim", "Glosses over the clarification options instead of presenting them as offered", "Promises immediate effect when status='applied' actually triggers an async intelligence recompute"] },
343
472
  leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Import a company by domain and run deep qualification + research in one\npass. Use when a colleague mentions a name and you want everything Leadbay\nknows about it.\n", "arguments": [{ "name": "domain", "description": "The company's primary domain (e.g. 'acme.com'). Protocol/path are stripped.", "required": true }], "expected_calls": ["leadbay_import_and_qualify", "leadbay_research_lead"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Reports certainty about fit when qualification didn't actually run (e.g. quota_blocked)", "Skips the research step after import completes"] }
@@ -347,6 +476,7 @@ var PROMPT_CATALOG_BULLETS = {
347
476
  leadbay_daily_check_in: `- \`leadbay_daily_check_in\`: Run the canonical daily check-in: see account state, pull a fresh batch, triage the top 10, deep-dive on every promising one, and offer contact enrichment. The user's typical morning workflow. Trigger when the user asks for "leadbay leads", "best leads to prospect today", "what should I work on", or anything resembling "show me the day's batch".`,
348
477
  leadbay_import_file: `- \`leadbay_import_file\` (optional args: file, instruction): Import a user-supplied CSV/file into Leadbay through five phases with evidence gates \u2014 scan, derive, resolve identities, preserve & commit, then optionally qualify and report. The job is to maximize how many rows the Leadbay system actually ingests and matches.`,
349
478
  leadbay_log_outreach: `- \`leadbay_log_outreach\` (required args: lead_id, summary): Log outreach (an email I sent, a call I made, a meeting I had) on a specific lead. Captures verification so the SDR pipeline trusts the entry.`,
479
+ leadbay_prospecting_overview: `- \`leadbay_prospecting_overview\`: Orientation for working with Leadbay from any host \u2014 discovery vs. follow-up, the outreach loop, outcome recording, imports, pushback / snooze, and the connected-outreach-tool registry. Trigger when the conversation involves Leadbay leads, prospecting, pipeline, follow-up, outreach, or lens / ICP \u2014 anything from "show me my leads" to "what should I follow up on" to "I'll send via lemlist".`,
350
480
  leadbay_qualify_top_n: `- \`leadbay_qualify_top_n\` (optional args: count): Bulk-qualify the top N un-qualified leads in the active lens. Uses leadbay_bulk_qualify_leads with a sensible default budget.`,
351
481
  leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
352
482
  leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Import a company by domain and run deep qualification + research in one pass. Use when a colleague mentions a name and you want everything Leadbay knows about it.`
@@ -370,6 +500,12 @@ var CATALOG = [
370
500
  arguments: [],
371
501
  render: () => [userMessage(leadbay_daily_check_in)]
372
502
  },
503
+ {
504
+ name: "leadbay_prospecting_overview",
505
+ description: PROMPT_META.leadbay_prospecting_overview.short_description,
506
+ arguments: [],
507
+ render: () => [userMessage(leadbay_prospecting_overview)]
508
+ },
373
509
  {
374
510
  name: "leadbay_research_a_domain",
375
511
  description: PROMPT_META.leadbay_research_a_domain.short_description,
@@ -725,7 +861,7 @@ function buildServer(client, opts = {}) {
725
861
  }
726
862
  const exposedNames = new Set(toolByName.keys());
727
863
  const server = new Server(
728
- { name: "leadbay", version: "0.6.2" },
864
+ { name: "leadbay", version: opts.version ?? "0.0.0-dev" },
729
865
  {
730
866
  capabilities: {
731
867
  tools: {},
@@ -925,7 +1061,7 @@ function buildServer(client, opts = {}) {
925
1061
 
926
1062
  // src/bin.ts
927
1063
  import { createRequire } from "module";
928
- var VERSION = "0.8.0";
1064
+ var VERSION = "0.9.0";
929
1065
  var HELP = `
930
1066
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
931
1067
 
@@ -1214,7 +1350,7 @@ async function runLogin(args) {
1214
1350
  let result;
1215
1351
  try {
1216
1352
  if (pinnedRegion && !allowFallback) {
1217
- const { REGIONS } = await import("./dist-HS5N4SIS.js");
1353
+ const { REGIONS } = await import("./dist-IIPWWDS5.js");
1218
1354
  const baseUrl = REGIONS[pinnedRegion];
1219
1355
  const c = createClient({ region: pinnedRegion });
1220
1356
  const token = await loginAt(baseUrl, email, password);
@@ -1706,7 +1842,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
1706
1842
  let region;
1707
1843
  try {
1708
1844
  if (pinnedRegion && !allowFallback) {
1709
- const { REGIONS } = await import("./dist-HS5N4SIS.js");
1845
+ const { REGIONS } = await import("./dist-IIPWWDS5.js");
1710
1846
  const baseUrl = REGIONS[pinnedRegion];
1711
1847
  token = await loginAt(baseUrl, email, password);
1712
1848
  region = pinnedRegion;
@@ -1850,7 +1986,8 @@ async function main() {
1850
1986
  includeAdvanced,
1851
1987
  includeWrite,
1852
1988
  logger,
1853
- bulkTracker
1989
+ bulkTracker,
1990
+ version: VERSION
1854
1991
  });
1855
1992
  const transport = new StdioServerTransport();
1856
1993
  logger.info?.(