@leadbay/mcp 0.7.1 → 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 +36 -1
- package/README.md +9 -0
- package/dist/bin.js +205 -27
- package/dist/{chunk-3WNCQ7MP.js → chunk-2EGNZRD7.js} +1195 -50
- package/dist/{dist-2RTYPHB3.js → dist-IIPWWDS5.js} +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
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
|
+
|
|
30
|
+
## 0.8.0 — 2026-05-15
|
|
31
|
+
|
|
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`.
|
|
33
|
+
|
|
34
|
+
**Daily check-in resilience against MCP per-call timeouts and lens shifts**: a real session of `leadbay_daily_check_in` failed in three correlated ways — a blocking `leadbay_bulk_qualify_leads` hit the MCP per-call timeout, the recovery re-pull silently shifted lens and discarded the EU batch, and ten parallel `leadbay_research_lead` calls produced `"Tool permission stream closed"` backpressure that the agent treated as terminal. All three are workflow-contract gaps. New reusable snippet `packages/promptforge/snippets/heuristics/long-running-tools.md` codifies four resilience rules: pin the captured `lensId` to every subsequent call, default `wait_for_completion:false` + `qualify_status` polling for bulk ops, serialize `leadbay_research_lead` fan-out (≤3 parallel), retry transient transport errors instead of replanning. `leadbay_daily_check_in` includes the snippet, adds `PHASE 0 — RESUME CHECK` so "continue from where you left off" does not restart, pins `lensId` in Phase 2, switches Phase 3's top-up to the async pattern with `lensId`, and serializes Phase 4. Three new `failure_modes` entries enforce the rules during evals. Belt-and-suspenders paragraphs added to three composite tool descriptions (`leadbay_pull_leads`, `leadbay_bulk_qualify_leads`, `leadbay_research_lead`) so ad-hoc tool use gets the same hints.
|
|
35
|
+
|
|
36
|
+
**Tests**: `packages/promptforge/test/skills.test.ts` enforces 1:1 `.md.tmpl` ↔ `SKILL.md` mapping with a byte-equal freshness gate, non-empty trigger-phrasing descriptions, no leftover `{{arg:…}}` in skill bodies, and that `PROMPT_CATALOG_INSTRUCTIONS` names every prompt with the direct-invoke fallback. `packages/mcp/test/server.test.ts` asserts default config's `instructions` mentions all six prompts and that read-only config drops `leadbay_qualify_top_n` (its description references the gated-off `leadbay_bulk_qualify_leads`).
|
|
37
|
+
|
|
38
|
+
## 0.7.1 — 2026-05-14
|
|
4
39
|
|
|
5
40
|
**Hotfix**: `packages/mcp/server.json` (the MCP Registry manifest) was still pinned at `0.6.3` and referenced `@leadbay/mcp@0.6` in three places. The 0.7.0 npm publish succeeded but the MCP Registry publish failed on a version-drift check. This release re-publishes 0.7.0's content with `server.json` bumped to `0.7.1` and the npm specifier updated to `@leadbay/mcp@0.7`. No functional change to the published package over 0.7.0.
|
|
6
41
|
|
package/README.md
CHANGED
|
@@ -52,6 +52,13 @@ The token is **session-scoped** (full account access, password-equivalent). Trea
|
|
|
52
52
|
|
|
53
53
|
Claude Code prompts for your token and region through the plugin's `userConfig`. This is equivalent to the npm/CLI install above; users who already ran `leadbay-mcp login` can reuse that token.
|
|
54
54
|
|
|
55
|
+
The plugin install gives you **two surfaces in one shot**:
|
|
56
|
+
|
|
57
|
+
1. **The MCP server** — registered via the plugin's `mcpServers.leadbay` block (boots `@leadbay/mcp` over stdio). This exposes the `leadbay_*` tools to the agent.
|
|
58
|
+
2. **Six auto-discovered Claude Code skills** under `skills/` — `leadbay_daily_check_in`, `leadbay_research_a_domain`, `leadbay_import_file`, `leadbay_log_outreach`, `leadbay_qualify_top_n`, `leadbay_refine_audience`. These auto-trigger on natural-language matches against each skill's description ("get me leadbay leads today", "research acme.com", etc.) and dispatch to the MCP tools above by name. Each `SKILL.md` is generated by `@leadbay/promptforge` from the same `.md.tmpl` source as the MCP prompt, so the two surfaces never drift.
|
|
59
|
+
|
|
60
|
+
You can verify the skills installed by running `/skill list` after install. To uninstall everything, `/plugin uninstall leadbay@leadbay-leadclaw` removes the MCP server registration **and** the skills together.
|
|
61
|
+
|
|
55
62
|
### Claude Desktop 2026 (DXT)
|
|
56
63
|
|
|
57
64
|
Claude Desktop 2026 ships the DXT (Desktop Extension) system — the legacy `claude_desktop_config.json` is UI-prefs-only there and gets overwritten by the app. If you're on 2026, **install the `.dxt` bundle** from [Releases](https://github.com/leadbay/leadclaw/releases/latest) (drag-drop into Settings → Extensions). `leadbay-mcp install` detects this and skips the legacy write automatically.
|
|
@@ -254,6 +261,8 @@ Then `prompts/get` materialises the chosen workflow as a structured `messages` a
|
|
|
254
261
|
"params": { "name": "research-a-domain", "arguments": { "domain": "acme.com" } } }
|
|
255
262
|
```
|
|
256
263
|
|
|
264
|
+
**Clients without a `prompts/list` UI** (Cowork is the prototypical case): the catalog still reaches the agent. The server's `initialize` response includes a `serverInfo.instructions` string that names every prompt, its trigger phrasing, and its required arguments — so the agent can match the user's natural-language ask and invoke `prompts/get` directly without the user clicking through a slash menu. No client action is needed beyond the standard MCP `initialize` handshake.
|
|
265
|
+
|
|
257
266
|
### `resources/list` and `resources/read` — URI-addressable read-only data
|
|
258
267
|
|
|
259
268
|
Three URI schemes are advertised: `lead://{uuid}/profile`, `lens://{id}/definition`, `org://taste-profile`. Capable clients cache them across turns.
|
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";
|
|
@@ -35,21 +35,54 @@ import {
|
|
|
35
35
|
var leadbay_daily_check_in = `
|
|
36
36
|
Run the Leadbay daily check-in for me. Treat this prompt the same way for any equivalent ask: "get me leadbay leads", "best leads to prospect today", "what should I work on", "show me my batch".
|
|
37
37
|
|
|
38
|
+
# Resilience rules for Leadbay long-running tools
|
|
39
|
+
|
|
40
|
+
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.**
|
|
41
|
+
|
|
42
|
+
## Rule 1 \u2014 Pin the lens
|
|
43
|
+
|
|
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
|
+
|
|
46
|
+
## Rule 2 \u2014 Prefer async for bulk operations
|
|
47
|
+
|
|
48
|
+
\`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.
|
|
49
|
+
|
|
50
|
+
## Rule 3 \u2014 Serialize \`leadbay_research_lead\` fan-out
|
|
51
|
+
|
|
52
|
+
\`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.
|
|
53
|
+
|
|
54
|
+
## Rule 4 \u2014 Retry, don't replan
|
|
55
|
+
|
|
56
|
+
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:
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
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.
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# PHASE 0 \u2014 RESUME CHECK
|
|
66
|
+
|
|
67
|
+
If you're resuming an interrupted session (you see a previous Phase already completed in your task list, or the user says "continue" / "continue from where you left off"), do NOT restart from Phase 1. Re-read the active \`lensId\` and your last completed phase from prior context, then resume from the next phase. If you genuinely have no state, restart from Phase 1.
|
|
68
|
+
|
|
38
69
|
# PHASE 1 \u2014 STATE
|
|
39
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.
|
|
40
71
|
|
|
41
72
|
# PHASE 2 \u2014 FRESH BATCH
|
|
42
|
-
Call \`leadbay_pull_leads\` to get today's fresh batch.
|
|
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.)
|
|
43
74
|
|
|
44
75
|
# PHASE 3 \u2014 TRIAGE (top 10, motivational framing)
|
|
45
76
|
|
|
46
77
|
Pick the top **10** leads \u2014 prefer leads with a fresh \`ai_agent_lead_score\` (those have been newly AI-qualified); fall back to \`score\` only when \`ai_agent_lead_score\` is absent. For each, write ONE motivational sentence \u2014 framed as *why prospecting this lead today might be a good idea right now* (almost a coach's nudge, not a flat description). Lean on \`qualification_summary\` for the substance, but reframe \u2014 don't paste it verbatim.
|
|
47
78
|
|
|
48
|
-
If the batch returns fewer than 10 qualified leads, top it up
|
|
79
|
+
If the batch returns fewer than 10 qualified leads, top it up: call \`leadbay_bulk_qualify_leads\` with \`lensId:<captured>\`, \`count:<1.5x deficit, capped at 25>\`, and **\`wait_for_completion:false\`**. Capture \`qualify_id\` from the response and poll \`leadbay_qualify_status\` every ~10s until \`status:'done'\`. Then re-pull with the same \`lensId\` to pick up the newly qualified leads. **Never re-pull without \`lensId\` \u2014 you will lose your batch to a lens shift.** (The \`leadbay_qualify_top_n\` slash-prompt wraps this same tool with a friendlier surface for users; agents should call the underlying tool directly here.)
|
|
49
80
|
|
|
50
81
|
# PHASE 4 \u2014 DEEP DIVE (every promising lead)
|
|
51
82
|
|
|
52
|
-
Call \`leadbay_research_lead\` on **every** lead from your top 10 that the user might realistically prospect today (filter out clearly weak fits if any). Don't pick just one.
|
|
83
|
+
Call \`leadbay_research_lead\` on **every** lead from your top 10 that the user might realistically prospect today (filter out clearly weak fits if any). Don't pick just one. **Call it sequentially** \u2014 one at a time, or batches of at most 3 in parallel. Do not fire 10 in parallel \u2014 it triggers transport backpressure that surfaces as \`"Tool permission stream closed"\` errors (see Rule 3 above). If a call fails, retry that single lead once; if the retry also fails, note the lead id and continue. Report Phase 4 results even if 1\u20132 leads were unresearchable.
|
|
84
|
+
|
|
85
|
+
For each researched lead surface:
|
|
53
86
|
- what makes it promising (1\u20132 sentences citing signals from the research)
|
|
54
87
|
- the **recommended contacts** the research returns \u2014 name, title, why they're the right starting point
|
|
55
88
|
|
|
@@ -243,6 +276,128 @@ After I answer, call \`leadbay_report_outreach({lead_id: '{{arg:lead_id}}', note
|
|
|
243
276
|
# PHASE 3 \u2014 CONFIRM
|
|
244
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.
|
|
245
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
|
+
`;
|
|
246
401
|
var leadbay_qualify_top_n = `
|
|
247
402
|
Qualify the top {{arg:count_or_default}} un-qualified leads in the active Leadbay lens.
|
|
248
403
|
|
|
@@ -302,13 +457,30 @@ triage the top 10, deep-dive on every promising one, and offer contact
|
|
|
302
457
|
enrichment. The user's typical morning workflow. Trigger when the user
|
|
303
458
|
asks for "leadbay leads", "best leads to prospect today", "what should
|
|
304
459
|
I work on", or anything resembling "show me the day's batch".
|
|
305
|
-
`, "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"] },
|
|
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'] },
|
|
306
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"] },
|
|
307
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"] },
|
|
308
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"] },
|
|
309
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"] },
|
|
310
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"] }
|
|
311
473
|
};
|
|
474
|
+
var PROMPT_CATALOG_HEADER = `This server exposes the following workflow prompts via \`prompts/list\` and \`prompts/get\`. Some MCP clients render them as slash commands; if your client does not, you (the agent) should invoke them directly via \`prompts/get\` when the user's request matches one of the triggers described below.`;
|
|
475
|
+
var PROMPT_CATALOG_BULLETS = {
|
|
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".`,
|
|
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.`,
|
|
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".`,
|
|
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.`,
|
|
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.`,
|
|
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.`
|
|
483
|
+
};
|
|
312
484
|
|
|
313
485
|
// src/prompts.ts
|
|
314
486
|
function userMessage(text) {
|
|
@@ -328,6 +500,12 @@ var CATALOG = [
|
|
|
328
500
|
arguments: [],
|
|
329
501
|
render: () => [userMessage(leadbay_daily_check_in)]
|
|
330
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
|
+
},
|
|
331
509
|
{
|
|
332
510
|
name: "leadbay_research_a_domain",
|
|
333
511
|
description: PROMPT_META.leadbay_research_a_domain.short_description,
|
|
@@ -555,23 +733,21 @@ function buildRhythmParagraph(has) {
|
|
|
555
733
|
}
|
|
556
734
|
return "Suggested rhythm: a healthy agent pattern is a daily check-in \u2014 pull fresh leads, skim the auto-qualified top, deepen 1-3 promising ones, propose outreach to the user. If your host supports scheduling, offer to set up a daily run.";
|
|
557
735
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
if (has("leadbay_bulk_qualify_leads")) {
|
|
572
|
-
commands.push("`/leadbay qualify-top-n {count}` (bulk_qualify_leads with progress streaming)");
|
|
736
|
+
var TOOL_REFERENCE_PATTERN = /\bleadbay_[a-z][a-z0-9_]*\b/g;
|
|
737
|
+
function buildPromptsCatalogParagraph(has) {
|
|
738
|
+
const safeBullets = [];
|
|
739
|
+
for (const [promptName, bullet] of Object.entries(PROMPT_CATALOG_BULLETS)) {
|
|
740
|
+
const referencedTools = /* @__PURE__ */ new Set();
|
|
741
|
+
for (const match of bullet.matchAll(TOOL_REFERENCE_PATTERN)) {
|
|
742
|
+
const name = match[0];
|
|
743
|
+
if (name === promptName) continue;
|
|
744
|
+
referencedTools.add(name);
|
|
745
|
+
}
|
|
746
|
+
const allExposed = [...referencedTools].every((n) => has(n));
|
|
747
|
+
if (allExposed) safeBullets.push(bullet);
|
|
573
748
|
}
|
|
574
|
-
|
|
749
|
+
if (safeBullets.length === 0) return "";
|
|
750
|
+
return [PROMPT_CATALOG_HEADER, "", ...safeBullets].join("\n");
|
|
575
751
|
}
|
|
576
752
|
var RESOURCES_PARAGRAPH = "Read-only resources (`resources/*`): three URI schemes are available \u2014 `lead://{uuid}/profile` (lead profile by id), `lens://{id}/definition` (filter + scoring config), `org://taste-profile` (qualification questions + intent tags). Capable clients cache these across turns \u2014 cheaper than re-running pull_leads / research_lead when the agent already has the id. Capable clients can also call `resources/subscribe` (the server stores the subscription; Leadbay's backend doesn't push deltas yet so notifications are not currently emitted) and `completion/complete` for URI auto-complete on the templates.";
|
|
577
753
|
function buildProtocolPrimitivesParagraph(has) {
|
|
@@ -628,7 +804,8 @@ function buildServerInstructions(exposed) {
|
|
|
628
804
|
parts.push(buildScoringParagraph(has));
|
|
629
805
|
parts.push(buildStartHereParagraph(has));
|
|
630
806
|
parts.push(buildRhythmParagraph(has));
|
|
631
|
-
|
|
807
|
+
const promptsCatalog = buildPromptsCatalogParagraph(has);
|
|
808
|
+
if (promptsCatalog) parts.push(promptsCatalog);
|
|
632
809
|
parts.push(RESOURCES_PARAGRAPH);
|
|
633
810
|
parts.push(buildProtocolPrimitivesParagraph(has));
|
|
634
811
|
return parts.join("\n\n");
|
|
@@ -684,7 +861,7 @@ function buildServer(client, opts = {}) {
|
|
|
684
861
|
}
|
|
685
862
|
const exposedNames = new Set(toolByName.keys());
|
|
686
863
|
const server = new Server(
|
|
687
|
-
{ name: "leadbay", version: "0.
|
|
864
|
+
{ name: "leadbay", version: opts.version ?? "0.0.0-dev" },
|
|
688
865
|
{
|
|
689
866
|
capabilities: {
|
|
690
867
|
tools: {},
|
|
@@ -884,7 +1061,7 @@ function buildServer(client, opts = {}) {
|
|
|
884
1061
|
|
|
885
1062
|
// src/bin.ts
|
|
886
1063
|
import { createRequire } from "module";
|
|
887
|
-
var VERSION = "0.
|
|
1064
|
+
var VERSION = "0.9.0";
|
|
888
1065
|
var HELP = `
|
|
889
1066
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
890
1067
|
|
|
@@ -1173,7 +1350,7 @@ async function runLogin(args) {
|
|
|
1173
1350
|
let result;
|
|
1174
1351
|
try {
|
|
1175
1352
|
if (pinnedRegion && !allowFallback) {
|
|
1176
|
-
const { REGIONS } = await import("./dist-
|
|
1353
|
+
const { REGIONS } = await import("./dist-IIPWWDS5.js");
|
|
1177
1354
|
const baseUrl = REGIONS[pinnedRegion];
|
|
1178
1355
|
const c = createClient({ region: pinnedRegion });
|
|
1179
1356
|
const token = await loginAt(baseUrl, email, password);
|
|
@@ -1665,7 +1842,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
1665
1842
|
let region;
|
|
1666
1843
|
try {
|
|
1667
1844
|
if (pinnedRegion && !allowFallback) {
|
|
1668
|
-
const { REGIONS } = await import("./dist-
|
|
1845
|
+
const { REGIONS } = await import("./dist-IIPWWDS5.js");
|
|
1669
1846
|
const baseUrl = REGIONS[pinnedRegion];
|
|
1670
1847
|
token = await loginAt(baseUrl, email, password);
|
|
1671
1848
|
region = pinnedRegion;
|
|
@@ -1809,7 +1986,8 @@ async function main() {
|
|
|
1809
1986
|
includeAdvanced,
|
|
1810
1987
|
includeWrite,
|
|
1811
1988
|
logger,
|
|
1812
|
-
bulkTracker
|
|
1989
|
+
bulkTracker,
|
|
1990
|
+
version: VERSION
|
|
1813
1991
|
});
|
|
1814
1992
|
const transport = new StdioServerTransport();
|
|
1815
1993
|
logger.info?.(
|