@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 +27 -0
- package/dist/bin.js +145 -8
- package/dist/{chunk-NVGZ432E.js → chunk-2EGNZRD7.js} +1190 -51
- package/dist/{dist-HS5N4SIS.js → dist-IIPWWDS5.js} +7 -1
- package/package.json +1 -1
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-
|
|
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.
|
|
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 \`
|
|
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.
|
|
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.
|
|
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-
|
|
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-
|
|
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?.(
|