@leadbay/mcp 0.7.0 → 0.8.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,6 +1,18 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
- ## 0.7.0 — UNRELEASED
3
+ ## 0.8.0 — 2026-05-15
4
+
5
+ **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`.
6
+
7
+ **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.
8
+
9
+ **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`).
10
+
11
+ ## 0.7.1 — 2026-05-14
12
+
13
+ **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.
14
+
15
+ ## 0.7.0 — 2026-05-15
4
16
 
5
17
  **Compile pipeline for prompts and tool descriptions**: every MCP prompt body and every Tool description is now authored as a `.md.tmpl` source file in the new `@leadbay/promptforge` workspace package and compiled into `prompts.generated.ts` / `tool-descriptions.generated.ts` at build time. Authors edit prose in one place; the generated TS modules are the bundle's source of inlined strings. No wire-format change: every consumer (Claude Desktop, Cursor, Claude Code, OpenClaw) sees the same MCP protocol shape — `tools/list`, `prompts/list`, `prompts/get` are unchanged. Backwards-compat: every tool name, every `inputSchema`, every annotation set, every `outputSchema` is preserved byte-for-byte. The descriptions themselves are rewritten (see below) — that is the visible change.
6
18
 
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-3WNCQ7MP.js";
11
+ } from "./chunk-NVGZ432E.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.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.
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 \`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.)
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 by calling \`leadbay_bulk_qualify_leads\` with \`count\` set a bit above what you still need (some leads may get disqualified during qualification \u2014 request 1.5x the deficit, capped at 25). Then re-pull the batch and continue. (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.)
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. For each researched lead surface:
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
 
@@ -302,13 +335,22 @@ triage the top 10, deep-dive on every promising one, and offer contact
302
335
  enrichment. The user's typical morning workflow. Trigger when the user
303
336
  asks for "leadbay leads", "best leads to prospect today", "what should
304
337
  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"] },
338
+ `, "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
339
  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
340
  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"] },
308
341
  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
342
  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
343
  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
344
  };
345
+ 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.`;
346
+ var PROMPT_CATALOG_BULLETS = {
347
+ 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
+ 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
+ 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.`,
350
+ 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
+ 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
+ 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.`
353
+ };
312
354
 
313
355
  // src/prompts.ts
314
356
  function userMessage(text) {
@@ -555,23 +597,21 @@ function buildRhythmParagraph(has) {
555
597
  }
556
598
  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
599
  }
558
- function buildSlashCommandsParagraph(has) {
559
- const commands = [];
560
- commands.push("`/leadbay daily-check-in` (chains account_status \u2192 pull_leads \u2192 research_lead on the top hit)");
561
- if (has("leadbay_import_and_qualify")) {
562
- commands.push("`/leadbay research-a-domain {domain}` (import_and_qualify \u2192 research_lead)");
563
- commands.push("`/leadbay import-file {file}` (resolve_import_rows \u2192 import_leads/import_and_qualify \u2192 status)");
564
- }
565
- if (has("leadbay_refine_prompt")) {
566
- commands.push("`/leadbay refine-audience {instruction}` (refine_prompt with clarification handling)");
567
- }
568
- if (has("leadbay_report_outreach")) {
569
- commands.push("`/leadbay log-outreach {lead_id, summary}` (gathers verification then report_outreach)");
570
- }
571
- if (has("leadbay_bulk_qualify_leads")) {
572
- commands.push("`/leadbay qualify-top-n {count}` (bulk_qualify_leads with progress streaming)");
600
+ var TOOL_REFERENCE_PATTERN = /\bleadbay_[a-z][a-z0-9_]*\b/g;
601
+ function buildPromptsCatalogParagraph(has) {
602
+ const safeBullets = [];
603
+ for (const [promptName, bullet] of Object.entries(PROMPT_CATALOG_BULLETS)) {
604
+ const referencedTools = /* @__PURE__ */ new Set();
605
+ for (const match of bullet.matchAll(TOOL_REFERENCE_PATTERN)) {
606
+ const name = match[0];
607
+ if (name === promptName) continue;
608
+ referencedTools.add(name);
609
+ }
610
+ const allExposed = [...referencedTools].every((n) => has(n));
611
+ if (allExposed) safeBullets.push(bullet);
573
612
  }
574
- return "Slash commands (`prompts/*`): the user's MCP client may surface registered prompts as slash commands. Available: " + commands.join(", ") + ". When the user invokes one, the rendered messages tell you which tools to call and in what order \u2014 follow them.";
613
+ if (safeBullets.length === 0) return "";
614
+ return [PROMPT_CATALOG_HEADER, "", ...safeBullets].join("\n");
575
615
  }
576
616
  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
617
  function buildProtocolPrimitivesParagraph(has) {
@@ -628,7 +668,8 @@ function buildServerInstructions(exposed) {
628
668
  parts.push(buildScoringParagraph(has));
629
669
  parts.push(buildStartHereParagraph(has));
630
670
  parts.push(buildRhythmParagraph(has));
631
- parts.push(buildSlashCommandsParagraph(has));
671
+ const promptsCatalog = buildPromptsCatalogParagraph(has);
672
+ if (promptsCatalog) parts.push(promptsCatalog);
632
673
  parts.push(RESOURCES_PARAGRAPH);
633
674
  parts.push(buildProtocolPrimitivesParagraph(has));
634
675
  return parts.join("\n\n");
@@ -884,7 +925,7 @@ function buildServer(client, opts = {}) {
884
925
 
885
926
  // src/bin.ts
886
927
  import { createRequire } from "module";
887
- var VERSION = "0.7.0";
928
+ var VERSION = "0.8.0";
888
929
  var HELP = `
889
930
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
890
931
 
@@ -1173,7 +1214,7 @@ async function runLogin(args) {
1173
1214
  let result;
1174
1215
  try {
1175
1216
  if (pinnedRegion && !allowFallback) {
1176
- const { REGIONS } = await import("./dist-2RTYPHB3.js");
1217
+ const { REGIONS } = await import("./dist-HS5N4SIS.js");
1177
1218
  const baseUrl = REGIONS[pinnedRegion];
1178
1219
  const c = createClient({ region: pinnedRegion });
1179
1220
  const token = await loginAt(baseUrl, email, password);
@@ -1665,7 +1706,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
1665
1706
  let region;
1666
1707
  try {
1667
1708
  if (pinnedRegion && !allowFallback) {
1668
- const { REGIONS } = await import("./dist-2RTYPHB3.js");
1709
+ const { REGIONS } = await import("./dist-HS5N4SIS.js");
1669
1710
  const baseUrl = REGIONS[pinnedRegion];
1670
1711
  token = await loginAt(baseUrl, email, password);
1671
1712
  region = pinnedRegion;
@@ -571,6 +571,8 @@ WHEN NOT TO USE: as a substitute for leadbay_research_lead \u2014 that already i
571
571
  `;
572
572
  var leadbay_bulk_qualify_leads = `Pick the next N unqualified leads in the active lens and qualify them (run AI rescore + web fetch). Pass \`wait_for_completion:false\` to return quickly with \`{status:'running', qualify_id}\`; poll leadbay_qualify_status with that id. With \`wait_for_completion\` omitted/true, the legacy behavior polls until the answers are populated or a budget is exhausted. Already-qualified leads (those with a non-null \`ai_agent_lead_score\`) are silently no-ops on the backend, so this composite paginates past them to find fresh candidates. On 429 mid-fanout, stops launching but keeps polling already-launched leads.
573
573
 
574
+ **Default to \`wait_for_completion:false\`** for any \`count > 5\` or when chained inside a multi-phase workflow \u2014 the blocking default can hit the MCP per-call timeout and surface as \`"Request timed out"\` even when the server is still working fine. The async pattern (capture \`qualify_id\`, poll \`leadbay_qualify_status\` every ~10s) is timeout-proof. Reserve the blocking form for tiny single-digit counts in interactive use.
575
+
574
576
  Context: Leadbay auto-qualifies roughly the top 10 of each daily batch. Leads below the top ~10 are NOT worse \u2014 the system is saving resources. This tool is how the agent spends more resources to go deeper on promising-looking leads the user hasn't had time to surface yet.
575
577
 
576
578
  WHEN TO USE: when the user wants more qualified leads than what's currently shown, or when a lead looks promising in leadbay_pull_leads but has an empty \`qualification_summary\`.
@@ -854,6 +856,8 @@ Roughly the top 10 of the batch come pre-qualified (populated qualification_summ
854
856
  WHEN TO USE: as the agent's default opening move when the user wants to see leads, or as a daily check-in for what's new today.
855
857
 
856
858
  WHEN NOT TO USE: when the user has named a specific lens \u2014 pass \`lensId\` to override the auto-resolution. Replaces the older leadbay_find_prospects (removed in v0.2.0).
859
+
860
+ The active lens can change between calls (5-min cache + backend \`last_requested_lens\`). If a multi-step workflow depends on staying on one lens, **capture \`lensId\` from the first response and pass it explicitly to every subsequent Leadbay call** \u2014 including re-pulls, bulk qualifies, and research. Re-pulling without \`lensId\` after a long-running tool may silently switch to a different lens and discard prior work.
857
861
  `;
858
862
  var leadbay_qualify_lead = `Trigger AI qualification for a single lead (web fetch + AI rescore). The operation is asynchronous \u2014 results take ~60s. \`forceFetch:true\` re-runs even if recent data exists.
859
863
 
@@ -914,6 +918,8 @@ Scoring has two layers: the basic \`score\` (firmographic, always present, alrea
914
918
  WHEN TO USE: when picking up a single lead from leadbay_pull_leads to decide whether to act on it.
915
919
 
916
920
  WHEN NOT TO USE: across many leads at once \u2014 that's leadbay_pull_leads' job. (This composite supersedes the lower-level leadbay_get_lead_profile in agent flow; the granular tool stays available for fine-grained access.)
921
+
922
+ **Concurrency note**: this is a composite that reads many sub-resources per call. Call it **sequentially** or in small batches (\u22643 parallel) when researching multiple leads. Firing 10+ in parallel can saturate the transport and produce misleading \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. On a transient stream/timeout failure, retry the same lead once before moving on.
917
923
  `;
918
924
  var leadbay_resolve_import_rows = `Resolve messy CSV-shaped lead rows against Leadbay before file import. The tool sends each row's available identity signals to \`POST /leads/resolve\`, returns matched lead IDs or ambiguous candidate IDs, and produces \`records_for_import\` plus a SAFE identity-only \`mappings_for_import\` starting point for leadbay_import_leads / leadbay_import_and_qualify. This tool deliberately does not try to understand every CSV dialect; the agent should inspect the file, derive clean helper columns when useful, pass explicit \`identity_mappings\`, and build the final CRM mapping from \`mapping_guidance\`.
919
925
 
@@ -77,7 +77,7 @@ import {
77
77
  tools,
78
78
  updateLens,
79
79
  updateLensFilter
80
- } from "./chunk-3WNCQ7MP.js";
80
+ } from "./chunk-NVGZ432E.js";
81
81
  export {
82
82
  InMemoryBulkStore,
83
83
  LeadbayClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "mcpName": "io.github.leadbay/leadbay-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
6
6
  "type": "module",