@leadbay/mcp 0.6.3 → 0.7.1

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.
@@ -532,6 +532,444 @@ function parseRetryAfter(value) {
532
532
  return null;
533
533
  }
534
534
 
535
+ // ../core/dist/tool-descriptions.generated.js
536
+ var leadbay_account_status = `Show the user's account state \u2014 admin rights, language, last-active lens, current quota usage across daily/weekly/monthly windows for llm_completion / ai_rescore / web_fetch resources, and whether the org's intelligence is mid-regeneration. Quota windows also hint at the user's consumption pace: heavy recent activity (ai_rescore / web_fetch near their window limits) is a signal that Leadbay will deliver a larger fresh batch next time the user logs back in, since batch size is paced by real consumption.
537
+
538
+ WHEN TO USE: at the start of a session to know what the agent can/can't do, or after a 429 to explain to the user which resource window was exhausted and when it resets.
539
+
540
+ WHEN NOT TO USE: as a pre-flight gate before bulk ops \u2014 operations themselves return 429; this tool is for context, not gating.
541
+ `;
542
+ var leadbay_add_note = `Add a note to a lead. Notes are visible to the whole organization in Leadbay.
543
+
544
+ WHEN TO USE: low-level \u2014 for free-form notes not tied to outreach actions, including meaningful per-lead notes/context preserved from an imported file after the import returns lead IDs.
545
+
546
+ WHEN NOT TO USE: to log an outreach action \u2014 use leadbay_report_outreach, which requires verification (gmail/calendar/user_confirmed) to prevent hallucinated outreach poisoning the SDR pipeline.
547
+
548
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
549
+ `;
550
+ var leadbay_adjust_audience = `Restrict (or expand) the lens audience by sector / size. Free-text sectors are auto-resolved against the sector taxonomy; ambiguous matches are surfaced to the agent rather than guessed silently. Permission routing is hidden: the default lens auto-clones to a new user lens; an org-level lens defaults to a per-user draft (admins can override with \`save_for_org:true\`). Filter MERGES with existing criteria (unrelated criteria are not dropped).
551
+
552
+ WHEN TO USE: when the user wants to see different kinds of leads (sector / size / etc.).
553
+
554
+ WHEN NOT TO USE: to refine BEYOND firmographics \u2014 that's leadbay_refine_prompt.
555
+
556
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
557
+ `;
558
+ var leadbay_answer_clarification = `Answer the pending clarification question Leadbay raised after a refine_prompt. The answer is stored as the new \`user_prompt\` and triggers regeneration. Pass \`option_id\` (preferred \u2014 pick from the offered options) or \`text_answer\` (free-text). Admin-only.
559
+
560
+ WHEN TO USE: after leadbay_refine_prompt returns \`status='clarification_pending'\`.
561
+
562
+ WHEN NOT TO USE: to set a brand-new prompt \u2014 use leadbay_refine_prompt.
563
+
564
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
565
+ `;
566
+ var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Returns the \`bulk_id\`, progress per lead (done/total enrichable contacts), and overall progress. When \`include_contacts=true\` (opt-in), includes each contact's email/phone/job_title/enrichment.done.
567
+
568
+ WHEN TO USE: poll this after leadbay_enrich_titles returns a \`bulk_id\`. Default \`include_contacts=false\` for cheap status polls; set \`include_contacts=true\` once \`all_done\` flips for the final read.
569
+
570
+ WHEN NOT TO USE: as a substitute for leadbay_research_lead \u2014 that already includes enriched contacts for a single lead.
571
+ `;
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
+
574
+ 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
+
576
+ 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\`.
577
+
578
+ WHEN NOT TO USE: to qualify a single specific lead \u2014 that's leadbay_qualify_lead (granular, advanced).
579
+
580
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
581
+ `;
582
+ var leadbay_clear_selection = `Clear the user's transient selection.
583
+
584
+ WHEN TO USE: cleanup after manual selection work, or recovery from a stuck composite.
585
+
586
+ WHEN NOT TO USE: in normal flow \u2014 composites clear in their own finally blocks.
587
+
588
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
589
+ `;
590
+ var leadbay_clear_user_prompt = `Remove the org's intelligence-refinement prompt (revert to AI-only generation). Admin-only. Triggers full intelligence regeneration.
591
+
592
+ WHEN TO USE: when a refinement turned out to be the wrong direction.
593
+
594
+ WHEN NOT TO USE: to replace with a different prompt \u2014 just call leadbay_refine_prompt; that overwrites.
595
+
596
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
597
+ `;
598
+ var leadbay_create_custom_field = `Create an org-level CRM custom field for imports, then use the returned \`mapping_value\` in leadbay_import_leads / leadbay_import_and_qualify mappings. Use when the user's file contains valuable columns that do not fit Leadbay's standard fields, such as source-system deep links, source record IDs, campaign provenance, or user-requested enrichment attributes.
599
+
600
+ For HubSpot record links, prefer \`type:'EXTERNAL_ID'\` with \`config.url_template\` and import only the stable HubSpot id as the CSV value. Example: create field name 'HubSpot Contact', type 'EXTERNAL_ID', config \`{url_template:'https://app.hubspot.com/contacts/<portal-id>/record/0-1/{value}'}\`; then map \`hubspot_id\` to the returned \`mapping_value\`. If only a full URL column exists and the id cannot be safely extracted, use a TEXT field instead.
601
+
602
+ WHEN TO USE: after leadbay_list_mappable_fields shows no suitable existing custom field and preserving the column matters to the user's goal.
603
+
604
+ WHEN NOT TO USE: for standard company/contact data that maps to LEAD_WEBSITE, LEAD_NAME, CONTACT_EMAIL, etc.; do not create custom fields for noisy scraper notes unless the user explicitly asks to preserve them.
605
+ `;
606
+ var leadbay_create_lens = `Create a new user-level lens by cloning an existing lens's filter/scoring as the starting point.
607
+
608
+ WHEN TO USE: when adjust_audience determined the current lens cannot be edited (e.g. it's the org default).
609
+
610
+ WHEN NOT TO USE: to update an existing lens \u2014 use leadbay_update_lens or leadbay_update_lens_filter.
611
+
612
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
613
+ `;
614
+ var leadbay_create_lens_draft = `Create (or fetch existing) draft of an org-level lens. Idempotent \u2014 same user calling twice returns the same draft. The returned lens has \`draft_of\` set to the original lens id.
615
+
616
+ WHEN TO USE: when a non-admin needs to modify an org-level lens \u2014 make a draft, edit the draft.
617
+
618
+ WHEN NOT TO USE: from agent flow \u2014 leadbay_adjust_audience handles the draft-routing transparently.
619
+
620
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
621
+ `;
622
+ var leadbay_deselect_leads = `Remove leads from the user's transient selection.
623
+
624
+ WHEN TO USE: when narrowing a previously-built selection without clearing it entirely.
625
+
626
+ WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles handles selection lifecycle.
627
+
628
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
629
+ `;
630
+ var leadbay_discover_leads = `Get AI-recommended leads from Leadbay. Returns paginated lead summaries with scores, AI summaries, tags, and recommended contacts. Auto-resolves to the active lens if \`lensId\` is omitted; \`count\` is capped at 50 per page.
631
+
632
+ WHEN TO USE: low-level \u2014 when you need raw paginated wishlist access without the qualification_summary attached by leadbay_pull_leads.
633
+
634
+ WHEN NOT TO USE: as the agent's default lead-discovery entry point \u2014 use leadbay_pull_leads, which adds a one-line qualification summary per lead.
635
+ `;
636
+ var leadbay_dismiss_clarification = `Dismiss the pending clarification without answering. Leadbay proceeds with its best guess. Admin-only.
637
+
638
+ WHEN TO USE: when the user explicitly doesn't want to answer the disambiguation.
639
+
640
+ WHEN NOT TO USE: as a default \u2014 answering with even a free-text reason gives Leadbay better signal.
641
+
642
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
643
+ `;
644
+ var leadbay_enrich_contacts = `Order email and/or phone enrichment for a specific contact. Performs an advisory credit check, then tries the paid-contact path and falls back to the org-contact path on NOT_FOUND. Consumes enrichment credits.
645
+
646
+ WHEN TO USE: when you have a specific \`contact_id\` (from leadbay_get_contacts) and want to enrich just that one.
647
+
648
+ WHEN NOT TO USE: for bulk enrichment by job title across many leads \u2014 use leadbay_enrich_titles, which handles the selection lifecycle and returns a clean preview/launch flow.
649
+
650
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
651
+ `;
652
+ var leadbay_enrich_titles = `Order contact enrichments by job title across many leads. Contacts are NOT returned by default with a lead (Leadbay keeps enrichment out-of-band to control cost); the agent requests them on demand via this tool when it's ready to actually reach out. Two modes: (A) NO \`titles\` param \u2014 returns the available titles + Leadbay's \`title_suggestions\` + \`auto_included_titles\` + a count of enrichable contacts, so the agent can ask the user which titles to enrich. (B) \`titles\` given \u2014 calls preview, then launches if there's anything enrichable. On 429 returns \`{status:'quota_exceeded'}\` cleanly. Selection lifecycle is wrapped in a try/finally so the user's selection is left clean even on error.
653
+
654
+ WHEN TO USE: as the agent's go-to enrichment entry point, immediately before proposing outreach.
655
+
656
+ WHEN NOT TO USE: to enrich a single contact \u2014 that's leadbay_enrich_contacts (granular). Speculatively, before the user has committed to outreaching \u2014 enrichment spends credits.
657
+
658
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
659
+ `;
660
+ var leadbay_get_clarification = `Check whether Leadbay has a pending clarification question \u2014 a question raised when refining the intelligence prompt produced contradictory or ambiguous criteria. Returns \`{pending: false, clarification: null}\` when nothing is pending (the backend returns 204).
661
+
662
+ WHEN TO USE: after leadbay_refine_prompt, to see if Leadbay needs the user to disambiguate.
663
+
664
+ WHEN NOT TO USE: to answer the question \u2014 use leadbay_answer_clarification.
665
+ `;
666
+ var leadbay_get_contacts = `Get contacts for a lead, including enriched email and phone data. Returns both organization contacts and enrichable contacts with IDs, tagged with \`source:'org'|'paid'\`.
667
+
668
+ WHEN TO USE: to check enrichment status (\`contact.enrichment.done\`) on individual leads after a bulk enrichment was launched, or to find the \`contact_id\` needed by leadbay_enrich_contacts.
669
+
670
+ WHEN NOT TO USE: as a substitute for leadbay_research_lead, which already includes enriched contacts in its return.
671
+ `;
672
+ var leadbay_get_enrichment_job_titles = `List the actual job titles present across the leads currently in the user's selection \u2014 the candidate set the user can ask to enrich.
673
+
674
+ WHEN TO USE: after leadbay_select_leads, to know which titles are even available before launching a bulk enrichment.
675
+
676
+ WHEN NOT TO USE: standalone \u2014 the selection must already be populated, otherwise the result is an empty array. leadbay_enrich_titles wraps this whole flow when you don't need to inspect the title list manually.
677
+ `;
678
+ var leadbay_get_epilogue_responses = `Read the lead's epilogue history \u2014 what status (still chasing, meeting booked, etc.) was set when, and by whom. Paginated (\`count\` 1-200 default 20, \`page\` 0-indexed).
679
+
680
+ WHEN TO USE: to see the lead's outreach progression before deciding the next step.
681
+
682
+ WHEN NOT TO USE: when the lead summary's \`epilogue_actions_count\` is 0.
683
+ `;
684
+ var leadbay_get_lead_activities = `Get prospecting activity history for a lead (emails sent, calls made, status changes, notes). Each entry is \`{type, date}\`; older activities are trimmed by \`count\` (max 100, default 50).
685
+
686
+ WHEN TO USE: to avoid redundant outreach and understand where this lead is in the sales process.
687
+
688
+ WHEN NOT TO USE: when leadbay_research_lead has already been called \u2014 it includes recent prospecting actions in its engagement block.
689
+ `;
690
+ var leadbay_get_lead_notes = `Read existing notes on a lead \u2014 context the human team or prior agent runs have already captured.
691
+
692
+ WHEN TO USE: before adding a note via leadbay_report_outreach, to avoid duplicating or overwriting context the SDR already wrote.
693
+
694
+ WHEN NOT TO USE: when the lead summary's \`notes_count\` is 0 \u2014 there's nothing to fetch.
695
+ `;
696
+ var leadbay_get_lead_profile = `Get a full lead profile including company details, AI qualification scores, web insights, and contacts. Also marks the lead as SEEN+CLICKED in the user's lens so it ages out of the "new" Discover view (fire-and-forget; profile fetch never blocks on it).
697
+
698
+ WHEN TO USE: low-level \u2014 for fine-grained access to the raw shape of the lead profile.
699
+
700
+ WHEN NOT TO USE: as the agent's default lead-detail tool \u2014 use leadbay_research_lead, which structures the data top-down (qualification first, then signals, then firmographics, then contacts, then engagement) and reshapes web_fetch.content into a stable array form.
701
+ `;
702
+ var leadbay_get_lens_filter = `Read the firmographic filter (sectors, sizes, locations) currently applied to a lens.
703
+
704
+ WHEN TO USE: before adjusting an audience \u2014 see what's already restricted so changes are diffs, not full replacements.
705
+
706
+ WHEN NOT TO USE: to actually apply changes \u2014 use the leadbay_adjust_audience composite, which handles permissions transparently.
707
+ `;
708
+ var leadbay_get_lens_scoring = `Read the AI-scoring criteria configured on a lens (what makes a lead score 100 vs 30).
709
+
710
+ WHEN TO USE: when explaining why a lead got the score it did.
711
+
712
+ WHEN NOT TO USE: to mutate scoring \u2014 that's an admin/setup operation, not part of the agent loop.
713
+ `;
714
+ var leadbay_get_prospecting_actions = `Read the CRM-style activity log for a lead (calls, emails, meetings \u2014 actions performed by humans or prior agent runs). Paginated (\`count\` 1-200 default 20, \`page\` 0-indexed).
715
+
716
+ WHEN TO USE: before contacting the lead, to avoid duplicating outreach the team already did.
717
+
718
+ WHEN NOT TO USE: when the lead summary's \`prospecting_actions_count\` is 0.
719
+ `;
720
+ var leadbay_get_quota = `Read remaining quota / spend across daily, weekly, and monthly windows for the org's resources (\`llm_completion\`, \`ai_rescore\`, \`web_fetch\`). Each entry shows \`current_units\` vs \`max_units\` and \`resets_at\`.
721
+
722
+ WHEN TO USE: after a 429 error, to explain to the user which window was hit and when it resets.
723
+
724
+ WHEN NOT TO USE: as a pre-flight gate before bulk operations \u2014 operations themselves return 429 with hints; this tool is for diagnostics, not gating.
725
+ `;
726
+ var leadbay_get_selection_ids = `List the lead ids currently in the user's selection (the transient set that bulk operations like enrichment act on).
727
+
728
+ WHEN TO USE: to verify the selection state before/after bulk ops if a composite call has misbehaved.
729
+
730
+ WHEN NOT TO USE: in the normal flow \u2014 leadbay_enrich_titles manages selection lifecycle automatically (select \u2192 action \u2192 clear).
731
+ `;
732
+ var leadbay_get_taste_profile = `Get the user's Ideal Buyer Profile, purchase-intent tags, and qualification questions. The result is cached on the client. Returns an operator \`hint\` when no profile is configured yet.
733
+
734
+ WHEN TO USE: at the very start of a session to understand what kind of leads the user is looking for.
735
+
736
+ WHEN NOT TO USE: per-lead \u2014 leadbay_research_lead already includes the per-lead qualification answers (which are scored against these org-level questions).
737
+ `;
738
+ var leadbay_get_user_prompt = `Read the org's intelligence-refinement prompt (free-text instruction that steers lead recommendations beyond firmographics). Returns \`{prompt: null, set: false}\` when none is configured (the backend returns 204 in that case).
739
+
740
+ WHEN TO USE: to know what's currently steering the agent's recommendations before suggesting a refine.
741
+
742
+ WHEN NOT TO USE: to set/change the prompt \u2014 use leadbay_refine_prompt.
743
+ `;
744
+ var leadbay_get_web_fetch = `Read the AI-generated web-research summary for a lead \u2014 company profile, business signals, prospecting clues, each with sources and "hot" flags marking high-signal recent items. The content is dictioned by emoji-prefixed section labels in the raw API.
745
+
746
+ WHEN TO USE: when the agent already qualified this lead and wants the underlying research to reason from.
747
+
748
+ WHEN NOT TO USE: as the first read on a lead \u2014 the leadbay_research_lead composite bundles this with qualification answers and reshapes the dict into a stable array form.
749
+ `;
750
+ var leadbay_import_and_qualify = `Import + qualify leads in one call. Pass either \`domains: [{domain, name?}]\` (Mode A) OR \`records[]\` with \`mappings\` (Mode B). At least one mapped field must be LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Discover the org's mappable surface via \`leadbay_list_mappable_fields\`. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan \u2192 resolve \u2192 preserve \u2192 commit phases.
751
+
752
+ WHEN TO USE: agent has a list of companies (domains, or CSV-shaped rows from the user's CRM) and wants the full AI qualification \u2014 qualification answers, web-research signals \u2014 without orchestrating import + bulk_qualify_leads + lead_profile chains by hand.
753
+
754
+ WHEN NOT TO USE: discovery (use leadbay_pull_leads); single-lead deep dive (use leadbay_research_lead); high-cadence or untrusted automation \u2014 this mutates user state and consumes ai_rescore + web_fetch quota.
755
+
756
+ Budgets: \`total_budget_ms\` caps wall-clock; \`per_lead_budget_ms\` caps each lead's poll. For short transport timeouts, pass \`wait_for_completion:false\` and poll \`leadbay_import_status\`. Outputs \`qualified[]\`, \`still_running[]\`, \`not_imported[]\`, \`qualify_id\` (resumable handle). Idempotent within a 5-min window. \`dry_run:'preview'\` returns mapping hints + custom-field candidates without importing.
757
+
758
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
759
+
760
+
761
+ Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw); admin role; active billing. Imported leads are NOT auto-promoted to the Monitor view; lens-scoring threshold decides.
762
+ `;
763
+ var leadbay_import_leads = `Import leads into Leadbay's CRM via the file-import wizard. Returns stable Leadbay leadIds for downstream chaining into leadbay_bulk_qualify_leads / leadbay_research_lead. For MCP clients with short transport timeouts, pass \`wait_for_completion:false\` to return quickly with \`{status:'running', handle_id}\`; poll leadbay_import_status with that handle. For end-to-end import+qualify in one call, prefer leadbay_import_and_qualify. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan \u2192 resolve \u2192 preserve \u2192 commit phases.
764
+
765
+ TWO MODES: (A) Domain-list shortcut \u2014 pass \`domains: [{domain, name?}]\`. The tool builds a 2-column CSV (LEAD_NAME, LEAD_WEBSITE) and imports with the default mapping. (B) Custom records + mapping \u2014 pass \`records: [{Col1, Col2, ...}]\` plus \`mappings.fields: {Col1: 'LEAD_NAME', ...}\`. \`mappings.fields\` must include LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE (resolver needs at least one identity key). Pass exactly one of \`domains\` / \`records\`. Reserved column \`MCP_ROW_ID\` cannot appear in records/mappings \u2014 the tool injects it for stable reconciliation.
766
+
767
+ MUTATES USER STATE: each call creates a row in the user's CRM-imports list (visible in the web UI) and touches onboarding state. Suitable for occasional automation, NOT for high-cadence (>5 calls/day). Imported leads are NOT auto-promoted to the user's Monitor view; lens-scoring threshold decides. For messy files call leadbay_resolve_import_rows first, then pass \`records_for_import\`/\`mappings_for_import\` here. Agents should inspect every column, build a preservation plan, and pass an explicit final mapping. For each meaningful column decide standard field, CONTACT_* field, Leadbay note, custom field, derived helper, or skip with a reason. For contact-only exports, derive a company-domain column from CONTACT_EMAIL only when it's a real business domain. Multiple rows can share the same LEADBAY_ID and import as separate contacts on that lead. Custom fields use \`CUSTOM.<id>\` in \`mappings.fields\` or the \`mappings.custom_fields\` shorthand. For source-system deep links create a custom field via leadbay_create_custom_field first (prefer EXTERNAL_ID + url_template). Preserve meaningful per-lead notes by calling leadbay_add_note after import returns lead IDs.
768
+
769
+ WHEN TO USE: you have a list of company domains from another system (CRM, analytics, email correspondents) and need stable Leadbay leadIds; or CRM-shaped rows with custom columns and want to drive the wizard with explicit field mappings.
770
+
771
+ WHEN NOT TO USE: for prospect discovery (use leadbay_pull_leads); for one specific company's profile (use leadbay_research_company); when you can't tolerate the side effects above; when you also want qualification in the same call (use leadbay_import_and_qualify).
772
+
773
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
774
+
775
+
776
+ Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw); admin role on the Leadbay account; active billing.
777
+ `;
778
+ var leadbay_import_status = `Retrieve the current state of an async lead import. Pass \`handle_id\` returned by \`leadbay_import_leads({wait_for_completion:false})\`, or pass legacy \`importIds[]\` to inspect backend wizard rows. This status call performs a single refresh pass and never polls in a loop.
779
+
780
+ WHEN TO USE: after leadbay_import_leads or leadbay_import_and_qualify returns \`{status:'running', handle_id}\` for the import phase, call this tool later to retrieve progress or the final import result without re-running the import.
781
+
782
+ WHEN NOT TO USE: for qualification handles returned as \`qualify_id\` \u2014 use leadbay_qualify_status for those; or when you still want the legacy blocking behavior from leadbay_import_leads with \`wait_for_completion=true\`.
783
+ `;
784
+ var leadbay_launch_bulk_enrichment = `Launch a bulk-enrichment job against the current selection. The backend requires \`email=true\` OR \`phone=true\` (both can be true). Returns 204 with no body \u2014 there is no bulk_id and no per-job status endpoint. Track results by polling individual leads via leadbay_get_contacts after ~60s; \`contact.enrichment.done\` flips to true. \`dry_run:true\` returns the call shape without contacting the backend.
785
+
786
+ WHEN TO USE: low-level.
787
+
788
+ WHEN NOT TO USE: from agent flow \u2014 leadbay_enrich_titles handles selection lifecycle, preview, launch, and cleanup.
789
+
790
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
791
+ `;
792
+ var leadbay_list_lenses = `List all available Leadbay lenses (saved lead-search configurations). Each lens defines a different target market or buyer segment. The lens with \`is_last_active=true\` is used by default for lead discovery.
793
+
794
+ WHEN TO USE: when the user wants to switch lens or asks "what lenses do I have".
795
+
796
+ WHEN NOT TO USE: in normal flow \u2014 composites auto-resolve the active lens via \`/me.last_requested_lens\`.
797
+ `;
798
+ var leadbay_list_mappable_fields = `List every CRM field the agent can target when calling leadbay_import_leads or leadbay_import_and_qualify. Returns two arrays: \`standard_fields\` (Leadbay's built-in StandardCrmFieldType enum \u2014 LEAD_NAME, LEAD_WEBSITE, LEAD_STATUS, contact + location + sector fields) and \`custom_fields\` (this org's user-defined fields \u2014 id, name, type, and the literal \`mapping_value\` you pass in \`mappings.fields\`). For custom fields, \`mapping_value\` is the wire-format string \`CUSTOM.<id>\` \u2014 pass it verbatim.
799
+
800
+ For contact exports, map person data to CONTACT_* fields and still provide parent-company identity via LEADBAY_ID/LEAD_WEBSITE/LEAD_NAME/CRM_ID/SIREN. When contact emails contain business domains, agents may derive a clean company-domain column for LEAD_WEBSITE only when the domain agrees with the row's company/deal/brand context, while preserving the original email as CONTACT_EMAIL. For import files, audit every meaningful source column. If no standard/contact field fits, preserve the data by creating or reusing a custom field unless the column is blank, duplicate plumbing, raw unparsed noise after useful extraction, or harmful to data quality. For HubSpot or other source-system deep links, create or reuse an EXTERNAL_ID/TEXT custom field with leadbay_create_custom_field, then map the source id/link to the returned \`mapping_value\`. Backend mapping_hints are advisory only; for contact files, do not accept hints such as first_name -> LEAD_NAME when the column is clearly a person field.
801
+
802
+ Optional \`for_records\` param: pass a sample of CSV-shaped rows and the tool also runs the wizard's preprocess on them, attaching \`mapping_hints\` (per-column AI-confidence suggestions) and \`custom_field_candidates\` (custom fields that match unmapped columns by exact / case-insensitive / fuzzy name). Saves a separate preview round-trip.
803
+
804
+ WHEN TO USE: before authoring an import mapping, especially when the CSV has columns that aren't obvious matches for standard fields.
805
+
806
+ WHEN NOT TO USE: when you already know the mapping \u2014 this call is cheap (~50ms without for_records, ~5\u201310s with) but unnecessary if the agent has already cached the catalog within the same conversation.
807
+ `;
808
+ var leadbay_list_sectors = `List the sector taxonomy (id + display name in the requested language). Default: \`lang\` follows the caller's language; \`includeInvisible=false\` returns ~1,091 visible sectors.
809
+
810
+ WHEN TO USE: to resolve a free-text sector name (e.g. "Healthcare") into the sector ids that leadbay_adjust_audience needs.
811
+
812
+ WHEN NOT TO USE: when you already have sector ids \u2014 pass them directly.
813
+ `;
814
+ var leadbay_login = `Log in to Leadbay with email and password. Auto-detects region (us|fr) \u2014 the user does not need to know which backend their account lives on. On success, sets the client's bearer token and switches the active region if needed.
815
+
816
+ WHEN TO USE: at the start of a session if no token is preconfigured (cfg.token / LEADBAY_TOKEN).
817
+
818
+ WHEN NOT TO USE: if a token is already preconfigured \u2014 you'll just overwrite it. The user needs a Leadbay account first; they can register at https://wow.leadbay.ai/?register=true.
819
+
820
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
821
+ `;
822
+ var leadbay_pick_clarification = `Answer the pending clarification question \u2014 either by picking one of the offered options (\`option_id\`) or by typing a free-text answer. The answer is stored as the new user_prompt and triggers regeneration. Admin-only.
823
+
824
+ WHEN TO USE: low-level.
825
+
826
+ WHEN NOT TO USE: from agent flow \u2014 use leadbay_answer_clarification.
827
+
828
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
829
+ `;
830
+ var leadbay_prepare_outreach = `Prepare an outreach package for a single lead: recommended contact + enriched contact details + AI summary. Optionally trigger contact enrichment in-flight (\`enrich:true\`); enrichment is async, so poll leadbay_get_contacts after ~60s if you need the result inline.
831
+
832
+ WHEN TO USE: when the agent is about to draft outreach for ONE specific lead and needs the contact's email/phone.
833
+
834
+ WHEN NOT TO USE: across many leads \u2014 use leadbay_enrich_titles for bulk; for general lead detail use leadbay_research_lead (richer signals); to actually log the outreach action use leadbay_report_outreach (requires verification).
835
+ `;
836
+ var leadbay_preview_bulk_enrichment = `Preview a bulk-enrichment cost given a set of job titles applied to the current selection. Returns \`{selected_leads, enriched_contacts, enrichable_contacts, title_suggestions, auto_included_titles, previously_enriched_titles}\`. \`previously_enriched_titles\` is a newer field (in prod soon) \u2014 when present, the agent can recommend repeating those titles for new leads.
837
+
838
+ WHEN TO USE: between selecting leads and launching, to know what the enrichment will cost.
839
+
840
+ WHEN NOT TO USE: from agent flow \u2014 leadbay_enrich_titles wraps preview + launch with the right safety checks.
841
+ `;
842
+ var leadbay_promote_lens = `Promote a user-level lens (or draft) to org-level so all teammates see it. Admin-only.
843
+
844
+ WHEN TO USE: rare \u2014 when an admin user has built a lens (or refined a draft) and wants to share it org-wide.
845
+
846
+ WHEN NOT TO USE: as a non-admin (will fail with 403); for personal lens changes (those stay user-scoped).
847
+
848
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
849
+ `;
850
+ var leadbay_pull_leads = `Pull up new leads from the user's last-active lens \u2014 the canonical "show me today's prospects" tool. Leadbay works like an inbox: each time the user logs back in, a fresh batch is delivered, paced by how many leads they've actually acted on recently. Pulling more won't produce more; user outreach/skips/saves does. Each returned lead carries a one-line \`qualification_summary\` built from leadbay_ai_agent_responses, plus the rich tags / scores / recommended_contact_title / engagement counters / in-flight flags from the lead summary.
851
+
852
+ Roughly the top 10 of the batch come pre-qualified (populated qualification_summary + ai_agent_lead_score); leads below the top ~10 carry only the basic firmographic \`score\` \u2014 not worse, just resource-saved by the system. Call leadbay_bulk_qualify_leads to deepen any of them on demand.
853
+
854
+ 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
+
856
+ 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).
857
+ `;
858
+ 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
+
860
+ WHEN TO USE: low-level \u2014 when you need to kick qualification on exactly one lead without composite orchestration.
861
+
862
+ WHEN NOT TO USE: as the agent's bulk-qualify path \u2014 use leadbay_bulk_qualify_leads, which paginates past already-qualified leads, fans out, polls, and bails out cleanly on 429.
863
+
864
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
865
+ `;
866
+ var leadbay_qualify_status = `Retrieve the current state of an import_and_qualify (or bulk_qualify_leads) launch by \`qualify_id\`. Returns the same \`qualified[]\` / \`still_running[]\` shape as the original composite, refreshed against the backend at call time. The handle is persisted to \`~/.leadbay/bulks.json\` with a 30-day TTL and survives MCP restart.
867
+
868
+ WHEN TO USE: after leadbay_import_and_qualify or leadbay_bulk_qualify_leads returned a \`qualify_id\` with non-empty \`still_running[]\`, call this tool a few minutes later (or hours) to retrieve the now-completed qualifications without re-running the import or re-spending qualify quota.
869
+
870
+ WHEN NOT TO USE: as a substitute for leadbay_research_lead \u2014 that's a deeper per-lead profile and includes contacts. This tool is purely the qualification answers + signals_count.
871
+ `;
872
+ var leadbay_recall_ordered_titles = `Show job titles the org has previously enriched, so the agent can repeat the same titles for new leads (or skip already-saturated ones). Two implementation paths: (1) PREFERRED \u2014 a selection-scoped preview call that reads \`previously_enriched_titles\` from the backend (newer prod field). (2) FALLBACK \u2014 live aggregation across each lead's enriched contacts. The composite picks transparently.
873
+
874
+ WHEN TO USE: before leadbay_enrich_titles, to plan which titles to order.
875
+
876
+ WHEN NOT TO USE: when you already know the exact titles you want to enrich.
877
+ `;
878
+ var leadbay_refine_prompt = `Refine the kind of leads Leadbay surfaces, beyond firmographics. Free-text instruction (e.g. "focus on hospitals running their own IT"). Sets the org's \`user_prompt\`; if the new prompt produces ambiguous criteria, Leadbay raises a clarification question, which this composite polls for and surfaces. Admin-only on the backend (will return 403 for non-admins).
879
+
880
+ WHEN TO USE: when audience filters (leadbay_adjust_audience) aren't enough.
881
+
882
+ WHEN NOT TO USE: to answer a pending clarification \u2014 that's leadbay_answer_clarification.
883
+
884
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
885
+ `;
886
+ var leadbay_remove_epilogue = `Bulk-clear the epilogue status from a set of leads.
887
+
888
+ WHEN TO USE: when an outreach action was logged in error and needs to be undone.
889
+
890
+ WHEN NOT TO USE: to change status \u2014 call leadbay_set_epilogue_status with the new status (it overwrites).
891
+
892
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
893
+ `;
894
+ var leadbay_report_outreach = `Log an outreach action (email, call, message, meeting) on a lead so the human team using Leadbay sees the progress in their UI. Writes a NOTE on the lead and (optionally) sets an EPILOGUE status (still chasing, meeting booked, etc.). Bulk variant: pass \`lead_ids=[uuid,...]\` instead of \`lead_id\` (epilogue is bulk-native; notes fan out per-lead).
895
+
896
+ VERIFICATION REQUIRED: every call must include \`verification={source: 'gmail_message_id'|'calendar_event_id'|'user_confirmed', ref: '<id-or-confirmation>'}\` to prevent hallucinated outreach poisoning the pipeline. The verification is appended to the note body. Skipping or fabricating verification poisons the human team's pipeline.
897
+
898
+ WHEN TO USE: AFTER actually emailing/calling/meeting/messaging a contact, OR after a substantive decision the user wants logged (skip, save, hand off).
899
+
900
+ WHEN NOT TO USE: BEFORE doing the outreach (use \`dry_run:true\` to validate args first); without verification (call will be rejected); from a flow where the user did not consent to having actions logged automatically.
901
+
902
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
903
+ `;
904
+ var leadbay_research_company = `Deep-dive research on a specific company by NAME (fuzzy match against the active lens's wishlist). Pass \`companyName\` (matches the top-scoring lead with that name) or \`leadId\` (takes precedence when both supplied).
905
+
906
+ WHEN TO USE: when the user references a company by name and you don't yet have its \`lead_id\`.
907
+
908
+ WHEN NOT TO USE: when you already have the lead_id \u2014 use leadbay_research_lead directly (it bundles richer signals + better top-down ordering for the agent).
909
+ `;
910
+ var leadbay_research_lead = `Tell me everything decision-relevant about a single lead. Bundles the lens-scoped lead profile, the AI qualification answers (the agent's knowledge-base food), the structured web-research signals (with hot flags + sources), the enriched contacts, and the recent notes/epilogue/prospecting activity in one call. Order is deliberate: qualification first, then signals, then firmographics, then contacts, then engagement.
911
+
912
+ Scoring has two layers: the basic \`score\` (firmographic, always present, already decent) and the AI qualification layer (\`ai_agent_lead_score\` + per-question answers + web_fetch signals). The AI layer is pre-populated for roughly the top 10 of each daily batch, and on-demand (via leadbay_bulk_qualify_leads) for anything below that. Combine both layers when judging a lead.
913
+
914
+ WHEN TO USE: when picking up a single lead from leadbay_pull_leads to decide whether to act on it.
915
+
916
+ 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.)
917
+ `;
918
+ 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
+
920
+ WHEN TO USE: before importing user-supplied files when domains, names, CRM IDs, registry numbers, or Leadbay IDs may be inconsistently formatted; when the agent needs to pre-resolve messy rows, inspect ambiguous candidates, or prepare LEADBAY_ID values for the import composites. For contact-only files, first derive company website/domain from business contact emails where possible, while ignoring consumer mailbox domains. Deterministic matches get a LEADBAY_ID column inserted so the standard import commits immediately. Ambiguous rows are deliberately left without LEADBAY_ID; inspect candidates and choose one only when the evidence is good. Rows with websites but no match can still be imported; Leadbay may crawl and match them later, and leadbay_import_status can surface late matches.
921
+
922
+ WHEN NOT TO USE: for prospect discovery from scratch (use leadbay_pull_leads); for one known company profile (use leadbay_research_company / leadbay_research_lead); or when the file already has clean, final LEADBAY_ID/CRM_ID/SIREN mappings and no row-level identity disambiguation is needed.
923
+ `;
924
+ var leadbay_select_leads = `Add leads to the user's transient selection (used by selection-scoped bulk operations). Accepts 1-1000 \`leadIds\` per call.
925
+
926
+ WHEN TO USE: low-level. The user's selection is a per-token global state \u2014 be careful when invoking directly.
927
+
928
+ WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192 action \u2192 clear in one call with proper Mutex protection. Calling this directly without acquiring the selection lock can clobber concurrent composite calls.
929
+
930
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
931
+ `;
932
+ var leadbay_set_active_lens = `Mark a lens as last-used. Subsequent \`/me\` reads return it as \`last_requested_lens\`, so all composite tools default to it.
933
+
934
+ WHEN TO USE: after the user explicitly switched contexts (e.g. created a new lens via leadbay_create_lens).
935
+
936
+ WHEN NOT TO USE: in normal flow \u2014 leadbay_pull_leads and leadbay_adjust_audience auto-set the right lens.
937
+
938
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
939
+ `;
940
+ var leadbay_set_epilogue_status = `Bulk-set the outreach progress (epilogue) status across a set of leads. Status values: \`STILL_CHASING\`, \`COULD_NOT_REACH_STILL_TRYING\`, \`INTEREST_VALIDATED_OR_MEETING_PLANED\` ("meeting booked"), \`NOT_INTERESTED_LOST\` (short labels accepted; mapped to the \`EPILOGUE_*\` enum). Up to 1000 leads per call.
941
+
942
+ WHEN TO USE: low-level.
943
+
944
+ WHEN NOT TO USE: from agent flow \u2014 leadbay_report_outreach pairs this with a note + verification, which is what humans actually need to see in Leadbay.
945
+
946
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
947
+ `;
948
+ var leadbay_set_user_prompt = `Set the org's intelligence-refinement prompt \u2014 free-text instruction that steers Leadbay's lead recommendations beyond firmographics. Admin-only. Setting this clears any pending clarification and triggers a full intelligence regeneration (web search + high-reasoning). \`dry_run:true\` returns the call shape without contacting the backend.
949
+
950
+ WHEN TO USE: low-level.
951
+
952
+ WHEN NOT TO USE: from agent flow \u2014 use leadbay_refine_prompt, which polls for follow-up clarifications.
953
+
954
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
955
+ `;
956
+ var leadbay_update_lens = `Update lens metadata (name, description, mode flags). Does NOT change the audience filter \u2014 use leadbay_update_lens_filter for that.
957
+
958
+ WHEN TO USE: rename a lens or toggle \`multi_product_mode\` / \`use_hq_only\`.
959
+
960
+ WHEN NOT TO USE: to change which leads the lens shows \u2014 that's a filter operation.
961
+
962
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
963
+ `;
964
+ var leadbay_update_lens_filter = `Replace the audience filter (sectors, sizes, locations) on a lens. Body is the full \`Filter\` object \u2014 this is a REPLACE, not a merge. Returns 400 \`default_lens\` if applied to the org default lens (clone it first). \`dry_run:true\` returns the call shape without contacting the backend.
965
+
966
+ WHEN TO USE: low-level mutation when you've already prepared the merged filter.
967
+
968
+ WHEN NOT TO USE: from agent flow \u2014 use leadbay_adjust_audience, which handles draft-vs-direct routing, permission fallback, and the merge logic so unrelated criteria aren't dropped.
969
+
970
+ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
971
+ `;
972
+
535
973
  // ../core/dist/tools/login.js
536
974
  var login = {
537
975
  name: "leadbay_login",
@@ -542,7 +980,7 @@ var login = {
542
980
  idempotentHint: false,
543
981
  openWorldHint: true
544
982
  },
545
- description: "Log in to Leadbay with email and password. Auto-detects region (us|fr) \u2014 the user does not need to know which backend their account lives on. When to use: at the start of a session if no token is preconfigured (cfg.token / LEADBAY_TOKEN). When NOT to use: if a token is already preconfigured (you'll just overwrite it). The user needs a Leadbay account \u2014 they can register at https://wow.leadbay.ai/?register=true",
983
+ description: leadbay_login,
546
984
  inputSchema: {
547
985
  type: "object",
548
986
  properties: {
@@ -593,7 +1031,7 @@ var listLenses = {
593
1031
  idempotentHint: true,
594
1032
  openWorldHint: true
595
1033
  },
596
- description: "List all available Leadbay lenses (saved lead search configurations). Each lens defines a different target market or buyer segment. The lens with is_last_active=true is used by default for lead discovery. When to use: when the user wants to switch lens or asks 'what lenses do I have'. When NOT to use: in normal flow \u2014 composites auto-resolve the active lens via /me.last_requested_lens.",
1034
+ description: leadbay_list_lenses,
597
1035
  inputSchema: {
598
1036
  type: "object",
599
1037
  properties: {},
@@ -641,7 +1079,7 @@ var discoverLeads = {
641
1079
  idempotentHint: true,
642
1080
  openWorldHint: true
643
1081
  },
644
- description: "Get AI-recommended leads from Leadbay. Returns paginated lead summaries with scores, AI summaries, tags, and recommended contacts. When to use: low-level when you need raw paginated wishlist access without the qualification_summary attached by leadbay_pull_leads. When NOT to use: as the agent's default lead-discovery entry point \u2014 use leadbay_pull_leads, which adds a one-line qualification summary per lead.",
1082
+ description: leadbay_discover_leads,
645
1083
  inputSchema: {
646
1084
  type: "object",
647
1085
  properties: {
@@ -705,7 +1143,7 @@ var getLeadProfile = {
705
1143
  idempotentHint: true,
706
1144
  openWorldHint: true
707
1145
  },
708
- description: "Get a full lead profile including company details, AI qualification scores, web insights, and contacts. When to use: low-level \u2014 for fine-grained access to the raw shape of the lead profile. When NOT to use: as the agent's default lead-detail tool \u2014 use leadbay_research_lead, which structures the data top-down (qualification first, then signals, then firmographics, then contacts, then engagement) and reshapes web_fetch.content into a stable array form.",
1146
+ description: leadbay_get_lead_profile,
709
1147
  inputSchema: {
710
1148
  type: "object",
711
1149
  properties: {
@@ -837,7 +1275,7 @@ var getContacts = {
837
1275
  idempotentHint: true,
838
1276
  openWorldHint: true
839
1277
  },
840
- description: "Get contacts for a lead, including enriched email and phone data. Returns both organization contacts and enrichable contacts with IDs. When to use: to check enrichment status (contact.enrichment.done) on individual leads after a bulk enrichment was launched, or to find the contact_id needed by leadbay_enrich_contacts. When NOT to use: as a substitute for leadbay_research_lead, which already includes enriched contacts in its return.",
1278
+ description: leadbay_get_contacts,
841
1279
  inputSchema: {
842
1280
  type: "object",
843
1281
  properties: {
@@ -897,7 +1335,7 @@ var getQuota = {
897
1335
  idempotentHint: true,
898
1336
  openWorldHint: true
899
1337
  },
900
- description: "Read remaining quota / spend across daily, weekly, monthly windows for the org's resources (llm_completion, ai_rescore, web_fetch). Each entry shows current_units vs max_units and resets_at. When to use: after a 429 error, to explain to the user which window was hit and when it resets. When NOT to use: as a pre-flight gate before bulk operations \u2014 operations themselves return 429 with hints; this tool is for diagnostics, not gating.",
1338
+ description: leadbay_get_quota,
901
1339
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
902
1340
  outputSchema: {
903
1341
  type: "object",
@@ -926,7 +1364,7 @@ var getTasteProfile = {
926
1364
  idempotentHint: true,
927
1365
  openWorldHint: true
928
1366
  },
929
- description: "Get the user's Ideal Buyer Profile, purchase intent tags, and qualification questions. When to use: at the very start of a session to understand what kind of leads the user is looking for. Data is cached. When NOT to use: per-lead \u2014 leadbay_research_lead already includes the per-lead qualification answers (which are scored against these org-level questions).",
1367
+ description: leadbay_get_taste_profile,
930
1368
  inputSchema: {
931
1369
  type: "object",
932
1370
  properties: {},
@@ -990,7 +1428,7 @@ var qualifyLead = {
990
1428
  idempotentHint: true,
991
1429
  openWorldHint: true
992
1430
  },
993
- description: "Trigger AI qualification for a single lead (web fetch + AI rescore). The operation is asynchronous \u2014 results take ~60s. When to use: low-level. When NOT to use: as the agent's bulk-qualify path \u2014 use leadbay_bulk_qualify_leads, which paginates past already-qualified leads, fan-outs, polls, and bails out cleanly on 429.",
1431
+ description: leadbay_qualify_lead,
994
1432
  optional: true,
995
1433
  inputSchema: {
996
1434
  type: "object",
@@ -1027,7 +1465,7 @@ var enrichContacts = {
1027
1465
  idempotentHint: true,
1028
1466
  openWorldHint: true
1029
1467
  },
1030
- description: "Order email and/or phone enrichment for a specific contact. When to use: when you have a specific contact_id (from leadbay_get_contacts) and want to enrich just that one. When NOT to use: for bulk enrichment by job title across many leads \u2014 use leadbay_enrich_titles, which handles the selection lifecycle and returns a clean preview/launch flow.",
1468
+ description: leadbay_enrich_contacts,
1031
1469
  optional: true,
1032
1470
  inputSchema: {
1033
1471
  type: "object",
@@ -1101,7 +1539,7 @@ var addNote = {
1101
1539
  idempotentHint: false,
1102
1540
  openWorldHint: true
1103
1541
  },
1104
- description: "Add a note to a lead. Notes are visible to the whole organization in Leadbay. When to use: low-level \u2014 for free-form notes not tied to outreach actions. When NOT to use: to log an outreach action \u2014 use leadbay_report_outreach, which requires verification (gmail/calendar/user_confirmed) to prevent hallucinated outreach poisoning the SDR pipeline.",
1542
+ description: leadbay_add_note,
1105
1543
  optional: true,
1106
1544
  inputSchema: {
1107
1545
  type: "object",
@@ -1151,7 +1589,7 @@ var getLeadActivities = {
1151
1589
  idempotentHint: true,
1152
1590
  openWorldHint: true
1153
1591
  },
1154
- description: "Get prospecting activity history for a lead (emails sent, calls made, status changes, notes). When to use: to avoid redundant outreach and understand where this lead is in the sales process. When NOT to use: when leadbay_research_lead has already been called \u2014 it includes recent prospecting actions in its engagement block.",
1592
+ description: leadbay_get_lead_activities,
1155
1593
  inputSchema: {
1156
1594
  type: "object",
1157
1595
  properties: {
@@ -1211,7 +1649,7 @@ var getLensFilter = {
1211
1649
  idempotentHint: true,
1212
1650
  openWorldHint: true
1213
1651
  },
1214
- description: "Read the firmographic filter (sectors, sizes, locations) currently applied to a lens. When to use: before adjusting an audience \u2014 see what's already restricted so changes are diffs, not full replacements. When NOT to use: to actually apply changes \u2014 use the leadbay_adjust_audience composite, which handles permissions transparently.",
1652
+ description: leadbay_get_lens_filter,
1215
1653
  inputSchema: {
1216
1654
  type: "object",
1217
1655
  properties: {
@@ -1235,7 +1673,7 @@ var getLensScoring = {
1235
1673
  idempotentHint: true,
1236
1674
  openWorldHint: true
1237
1675
  },
1238
- description: "Read the AI-scoring criteria configured on a lens (what makes a lead score 100 vs 30). When to use: when explaining why a lead got the score it did. When NOT to use: to mutate scoring \u2014 that's an admin/setup operation, not part of the agent loop.",
1676
+ description: leadbay_get_lens_scoring,
1239
1677
  inputSchema: {
1240
1678
  type: "object",
1241
1679
  properties: { lensId: { type: "number", description: "Lens id (required)" } },
@@ -1257,7 +1695,7 @@ var listSectors = {
1257
1695
  idempotentHint: true,
1258
1696
  openWorldHint: true
1259
1697
  },
1260
- description: "List the sector taxonomy (id + display name in the requested language). When to use: to resolve a free-text sector name (e.g. 'Healthcare') into the sector ids that leadbay_adjust_audience needs. Default: lang follows the caller's language; includeInvisible=false returns ~1,091 visible sectors. When NOT to use: when you already have sector ids \u2014 pass them directly.",
1698
+ description: leadbay_list_sectors,
1261
1699
  inputSchema: {
1262
1700
  type: "object",
1263
1701
  properties: {
@@ -1295,7 +1733,7 @@ var getUserPrompt = {
1295
1733
  idempotentHint: true,
1296
1734
  openWorldHint: true
1297
1735
  },
1298
- description: "Read the org's intelligence-refinement prompt (free-text instruction that steers lead recommendations beyond firmographics). Returns null if none is set (the backend returns 204 in that case). When to use: to know what's currently steering the agent's recommendations before suggesting a refine. When NOT to use: to set/change the prompt \u2014 use leadbay_refine_prompt.",
1736
+ description: leadbay_get_user_prompt,
1299
1737
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1300
1738
  outputSchema: {
1301
1739
  type: "object",
@@ -1335,7 +1773,7 @@ var getClarification = {
1335
1773
  idempotentHint: true,
1336
1774
  openWorldHint: true
1337
1775
  },
1338
- description: "Check whether Leadbay has a pending clarification question \u2014 a question raised when refining the intelligence prompt produced contradictory or ambiguous criteria. Returns null when nothing is pending (the backend returns 204). When to use: after leadbay_refine_prompt, to see if Leadbay needs the user to disambiguate. When NOT to use: to answer the question \u2014 use leadbay_answer_clarification.",
1776
+ description: leadbay_get_clarification,
1339
1777
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1340
1778
  outputSchema: {
1341
1779
  type: "object",
@@ -1377,7 +1815,7 @@ var getLeadNotes = {
1377
1815
  idempotentHint: true,
1378
1816
  openWorldHint: true
1379
1817
  },
1380
- description: "Read existing notes on a lead \u2014 context the human team or prior agent runs have already captured. When to use: before adding a note via leadbay_report_outreach, to avoid duplicating or overwriting context the SDR already wrote. When NOT to use: when the lead summary's notes_count is 0 \u2014 there's nothing to fetch.",
1818
+ description: leadbay_get_lead_notes,
1381
1819
  inputSchema: {
1382
1820
  type: "object",
1383
1821
  properties: { leadId: { type: "string", description: "Lead UUID (required)" } },
@@ -1399,7 +1837,7 @@ var getEpilogueResponses = {
1399
1837
  idempotentHint: true,
1400
1838
  openWorldHint: true
1401
1839
  },
1402
- description: "Read the lead's epilogue history \u2014 what status (still chasing, meeting booked, etc.) was set when, and by whom. When to use: to see the lead's outreach progression before deciding the next step. When NOT to use: when the lead summary's epilogue_actions_count is 0.",
1840
+ description: leadbay_get_epilogue_responses,
1403
1841
  inputSchema: {
1404
1842
  type: "object",
1405
1843
  properties: {
@@ -1427,7 +1865,7 @@ var getProspectingActions = {
1427
1865
  idempotentHint: true,
1428
1866
  openWorldHint: true
1429
1867
  },
1430
- description: "Read the CRM-style activity log for a lead (calls, emails, meetings \u2014 actions performed by humans or prior agent runs). When to use: before contacting the lead, to avoid duplicating outreach the team already did. When NOT to use: when the lead summary's prospecting_actions_count is 0.",
1868
+ description: leadbay_get_prospecting_actions,
1431
1869
  inputSchema: {
1432
1870
  type: "object",
1433
1871
  properties: {
@@ -1455,7 +1893,7 @@ var getWebFetch = {
1455
1893
  idempotentHint: true,
1456
1894
  openWorldHint: true
1457
1895
  },
1458
- description: "Read the AI-generated web-research summary for a lead \u2014 company profile, business signals, prospecting clues, each with sources and 'hot' flags marking high-signal recent items. The content is dictioned by emoji-prefixed section labels in the raw API. When to use: when the agent already qualified this lead and wants the underlying research to reason from. When NOT to use: as the first read on a lead \u2014 the leadbay_research_lead composite bundles this with qualification answers and reshapes the dict into a stable array form.",
1896
+ description: leadbay_get_web_fetch,
1459
1897
  inputSchema: {
1460
1898
  type: "object",
1461
1899
  properties: { leadId: { type: "string", description: "Lead UUID (required)" } },
@@ -1498,7 +1936,7 @@ var getSelectionIds = {
1498
1936
  idempotentHint: true,
1499
1937
  openWorldHint: true
1500
1938
  },
1501
- description: "List the lead ids currently in the user's selection (the transient set that bulk operations like enrichment act on). When to use: to verify the selection state before/after bulk ops if a composite call has misbehaved. When NOT to use: in the normal flow \u2014 leadbay_enrich_titles manages selection lifecycle automatically (select \u2192 action \u2192 clear).",
1939
+ description: leadbay_get_selection_ids,
1502
1940
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1503
1941
  execute: async (client) => {
1504
1942
  return await client.request("GET", "/leads/selection/ids");
@@ -1515,7 +1953,7 @@ var getEnrichmentJobTitles = {
1515
1953
  idempotentHint: true,
1516
1954
  openWorldHint: true
1517
1955
  },
1518
- description: "List the actual job titles present across the leads currently in the user's selection \u2014 the candidate set the user can ask to enrich. When to use: after leadbay_select_leads, to know which titles are even available before launching a bulk enrichment. When NOT to use: standalone \u2014 the selection must already be populated, otherwise the result is an empty array. leadbay_enrich_titles wraps this whole flow when you don't need to inspect the title list manually.",
1956
+ description: leadbay_get_enrichment_job_titles,
1519
1957
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1520
1958
  execute: async (client) => {
1521
1959
  return await client.request("GET", "/leads/selection/enrichment/job_titles");
@@ -1883,6 +2321,13 @@ var STABILIZATION_POLLS = 2;
1883
2321
  var MAX_COLUMN_NAME_LEN = 128;
1884
2322
  var RESERVED_COLUMN_RE = /^mcp_row_id$/i;
1885
2323
  var CUSTOM_FIELD_RE = /^CUSTOM\.(\d+)$/;
2324
+ var IMPORT_RESOLVER_FIELDS = /* @__PURE__ */ new Set([
2325
+ "LEADBAY_ID",
2326
+ "CRM_ID",
2327
+ "LEAD_NAME",
2328
+ "LEAD_WEBSITE",
2329
+ "SIREN"
2330
+ ]);
1886
2331
  function isCustomFieldMappingValue(v) {
1887
2332
  return CUSTOM_FIELD_RE.test(v);
1888
2333
  }
@@ -2125,8 +2570,8 @@ function prepareRecordsMode(client, records, mappings, customFieldCatalog) {
2125
2570
  throw client.makeError("IMPORT_MAPPING_REQUIRED", "mappings.fields must contain at least one column \u2192 CRM field entry", "Map at least one CSV column to LEAD_NAME or LEAD_WEBSITE.", "POST /imports");
2126
2571
  }
2127
2572
  const targets = new Set(fieldEntries.map(([, v]) => v));
2128
- if (!targets.has("LEAD_NAME") && !targets.has("LEAD_WEBSITE")) {
2129
- throw client.makeError("IMPORT_MAPPING_NO_RESOLVER", "mappings.fields must include LEAD_NAME or LEAD_WEBSITE", "The wizard needs at least one of those fields to match a lead. Map a CSV column to one of them.", "POST /imports");
2573
+ if (![...targets].some((t) => IMPORT_RESOLVER_FIELDS.has(t))) {
2574
+ throw client.makeError("IMPORT_MAPPING_NO_RESOLVER", "mappings.fields must include LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE", "The wizard needs at least one identity field to match a lead. Use leadbay_resolve_import_rows to prepare LEADBAY_ID values when the input file is messy.", "POST /imports");
2130
2575
  }
2131
2576
  const targetCounts = /* @__PURE__ */ new Map();
2132
2577
  for (const [col, target] of fieldEntries) {
@@ -2538,7 +2983,7 @@ var importLeads = {
2538
2983
  idempotentHint: true,
2539
2984
  openWorldHint: true
2540
2985
  },
2541
- description: "Import leads into Leadbay's CRM via the file-import wizard. Returns stable Leadbay leadIds for downstream chaining into leadbay_bulk_qualify_leads / leadbay_research_lead. For MCP clients with short transport timeouts, pass `wait_for_completion:false` to return quickly with `{status:'running', handle_id}`; poll `leadbay_import_status` with that handle for progress and the final `{leads, not_imported, importIds}` result.\n\nTWO MODES:\n A) Domain-list shortcut \u2014 pass `domains: [{domain, name?}]`. The tool builds a 2-column CSV (LEAD_NAME, LEAD_WEBSITE) and imports with the default mapping. Output: { leads: [{domain, leadId, name}], not_imported: [{domain, reason}], importIds, _meta }.\n B) Custom records + mapping \u2014 pass `records: [{Col1, Col2, ...}]` plus `mappings.fields: {Col1: 'LEAD_NAME', Col2: 'LEAD_WEBSITE', ...}`. The tool synthesizes a CSV from the union of record keys (deterministic order) and POSTs the caller-supplied mapping to the wizard. mappings.fields must include LEAD_NAME or LEAD_WEBSITE (the resolver needs at least one). Output: { leads: [{rowId, domain?, leadId, name}], not_imported: [{rowId, domain?, reason}], importIds, _meta }. `rowId` round-trips your input order.\n\nPass exactly one of `domains` / `records`. Reserved column MCP_ROW_ID (any case) cannot appear in records or mappings \u2014 the tool injects it for stable reconciliation.\n\n\u26A0\uFE0F MUTATES USER STATE. Each call:\n - creates a row in the user's CRM-imports list (visible in the web UI)\n - touches onboarding state (startFileless, onboarding step \u2192 PROCESSING)\nSuitable for occasional automation. NOT suitable for high-cadence (>5 calls/day) \u2014 wait for the backend programmatic endpoint (issue: leadbay/backend prolonged-import-with-crawl).\n\n\u2139\uFE0F Monitor-tab membership: imported leads are NOT auto-promoted to the user's Monitor view. Lens-scoring decides \u2014 only above-threshold leads get `in_monitor: true` server-side.\n\nWhen to use: you have a list of company domains from another system (CRM, analytics, email correspondents) and need stable Leadbay leadIds; or you have CRM-shaped rows with custom columns (sector, location, status, etc.) and want to drive the wizard with explicit field mappings.\nWhen NOT to use: for prospect discovery (use leadbay_pull_leads); for one specific company's profile (use leadbay_research_company); when you can't tolerate the side effects above.\n\nCustom fields: pass org-defined custom field mappings as 'CUSTOM.<id>' (raw wire format) in `mappings.fields`, OR use the ergonomic `mappings.custom_fields` shorthand: `{ColName: 8}` (numeric id) or `{ColName: 'priority_test'}` (field name). Discover available custom fields via leadbay_list_mappable_fields.\n\nRequires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw); admin role on the Leadbay account; active billing.",
2986
+ description: leadbay_import_leads,
2542
2987
  write: true,
2543
2988
  version: "0.3.0",
2544
2989
  inputSchema: {
@@ -2576,7 +3021,7 @@ var importLeads = {
2576
3021
  properties: {
2577
3022
  fields: {
2578
3023
  type: "object",
2579
- description: "Object whose keys are CSV column names (matching keys in `records`) and whose values are either Leadbay's StandardCrmFieldType (LEAD_NAME, LEAD_WEBSITE, LEAD_STATUS, LEAD_LOCATION, LEAD_LOCATION_*, LEAD_SECTOR, LEAD_SIZE, CRM_ID, LEADBAY_ID, EMAIL, DEAL_CRM_ID, CONTACT_FIRST_NAME, CONTACT_LAST_NAME, CONTACT_EMAIL, CONTACT_PHONE_NUMBER, CONTACT_TITLE, CONTACT_LINKEDIN, LEAD_STATUS_DATE, OWNER, SCORE, SIREN) or the wire-format string 'CUSTOM.<id>' for org-defined custom fields. At least one entry must target LEAD_NAME or LEAD_WEBSITE \u2014 the wizard needs that to find leads. Use leadbay_list_mappable_fields to discover the org's custom fields."
3024
+ description: "Object whose keys are CSV column names (matching keys in `records`) and whose values are either Leadbay's StandardCrmFieldType (LEAD_NAME, LEAD_WEBSITE, LEAD_STATUS, LEAD_LOCATION, LEAD_LOCATION_*, LEAD_SECTOR, LEAD_SIZE, CRM_ID, LEADBAY_ID, EMAIL, DEAL_CRM_ID, CONTACT_FIRST_NAME, CONTACT_LAST_NAME, CONTACT_EMAIL, CONTACT_PHONE_NUMBER, CONTACT_TITLE, CONTACT_LINKEDIN, LEAD_STATUS_DATE, OWNER, SCORE, SIREN) or the wire-format string 'CUSTOM.<id>' for org-defined custom fields. At least one entry must target LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE \u2014 the wizard needs an identity field to find leads. Use leadbay_resolve_import_rows to prepare LEADBAY_ID values, and leadbay_list_mappable_fields to discover the org's custom fields. Contact rows should include both parent lead identity fields and CONTACT_* fields; repeated LEADBAY_ID/company values create multiple contacts on the same lead. Preserve HubSpot/source links by mapping them to a CUSTOM.<id> field returned by leadbay_create_custom_field when no suitable custom field already exists."
2580
3025
  },
2581
3026
  custom_fields: {
2582
3027
  type: "object",
@@ -2851,7 +3296,7 @@ async function runImportInBackground(client, prep, uploadedChunks, opts, ctx, ha
2851
3296
  var STANDARD_FIELDS = [
2852
3297
  { name: "LEAD_NAME", description: "Company name. Required for fuzzy match." },
2853
3298
  { name: "LEAD_WEBSITE", description: "Company domain (preferred matcher; protocol/path auto-stripped)." },
2854
- { name: "EMAIL", description: "Email \u2014 domain part used as a website-fallback matcher." },
3299
+ { name: "EMAIL", description: "Lead/company email \u2014 domain part may be used as a website-fallback matcher. For a person's email, use CONTACT_EMAIL and optionally derive a separate business-domain column for LEAD_WEBSITE." },
2855
3300
  { name: "CRM_ID", description: "Your CRM's stable lead identifier (round-trips back as crm_id on the lead)." },
2856
3301
  { name: "LEADBAY_ID", description: "Leadbay UUID, if you already have one (matches by id, no fuzzy needed)." },
2857
3302
  { name: "DEAL_CRM_ID", description: "Your CRM's deal id (one deal per row; combined with LEAD_STATUS forms a sales record)." },
@@ -2869,7 +3314,7 @@ var STANDARD_FIELDS = [
2869
3314
  { name: "SIREN", description: "French SIREN registry number (9 digits) \u2014 auto-matches against the FR registry." },
2870
3315
  { name: "CONTACT_FIRST_NAME", description: "Contact first name (creates an org contact)." },
2871
3316
  { name: "CONTACT_LAST_NAME", description: "Contact last name." },
2872
- { name: "CONTACT_EMAIL", description: "Contact email." },
3317
+ { name: "CONTACT_EMAIL", description: "Contact email. Does not replace the parent company's LEAD_WEBSITE; derive a company domain from this only when it is a business domain, not a personal mailbox provider." },
2873
3318
  { name: "CONTACT_PHONE_NUMBER", description: "Contact phone (free-form)." },
2874
3319
  { name: "CONTACT_TITLE", description: "Contact job title." },
2875
3320
  { name: "CONTACT_LINKEDIN", description: "Contact LinkedIn URL." }
@@ -2887,7 +3332,7 @@ function describeCustomField(f) {
2887
3332
  case "DATETIME":
2888
3333
  return `Custom DATETIME field${f.config?.format ? ` (format: ${f.config.format})` : " (ISO datetime)"}.`;
2889
3334
  case "EXTERNAL_ID":
2890
- return `Custom EXTERNAL_ID field \u2014 opaque id${f.config?.urlTemplate ? ` (deep-link template configured)` : ""}.`;
3335
+ return `Custom EXTERNAL_ID field \u2014 opaque id${f.config?.url_template || f.config?.urlTemplate ? ` (deep-link template configured)` : ""}.`;
2891
3336
  default:
2892
3337
  return `Custom field of unrecognized type "${f.type}" \u2014 pass values as strings.`;
2893
3338
  }
@@ -2903,7 +3348,7 @@ var listMappableFields = {
2903
3348
  idempotentHint: true,
2904
3349
  openWorldHint: true
2905
3350
  },
2906
- description: "List every CRM field the agent can target when calling leadbay_import_leads or leadbay_import_and_qualify. Returns two arrays: `standard_fields` (Leadbay's built-in StandardCrmFieldType enum \u2014 LEAD_NAME, LEAD_WEBSITE, LEAD_STATUS, contact + location + sector fields) and `custom_fields` (this org's user-defined fields \u2014 id, name, type, and the literal `mapping_value` you pass in `mappings.fields`). For custom fields, `mapping_value` is the wire-format string `CUSTOM.<id>` \u2014 pass it verbatim.\n\nOptional `for_records` param: pass a sample of CSV-shaped rows and the tool also runs the wizard's preprocess on them, attaching `mapping_hints` (per-column AI-confidence suggestions) and `custom_field_candidates` (custom fields that match unmapped columns by exact / case-insensitive / fuzzy name) to the response. Saves a separate preview round-trip when the agent already has data in hand.\n\nWhen to use: before authoring an import mapping, especially when the CSV has columns that aren't obvious matches for standard fields. When NOT to use: when you already know the mapping \u2014 this call is cheap (~50ms with no for_records, ~5\u201310s with) but unnecessary if the agent has already cached the catalog within the same conversation.",
3351
+ description: leadbay_list_mappable_fields,
2907
3352
  inputSchema: {
2908
3353
  type: "object",
2909
3354
  properties: {
@@ -2981,7 +3426,10 @@ var listMappableFields = {
2981
3426
  }
2982
3427
  };
2983
3428
  if (Array.isArray(params.for_records) && params.for_records.length > 0) {
2984
- const notes = [];
3429
+ const notes = [
3430
+ "mapping_hints are backend suggestions, not final truth. Inspect values semantically before importing; person columns like first_name/last_name should map to CONTACT_* fields, not LEAD_NAME.",
3431
+ "If mapping_hints disagree with the user's file semantics, ignore the hint. Use leadbay_resolve_import_rows with explicit identity_mappings for identity matching, then author final mappings yourself."
3432
+ ];
2985
3433
  try {
2986
3434
  const sample = params.for_records.slice(0, PREVIEW_SAMPLE_CAP);
2987
3435
  const headerSet = /* @__PURE__ */ new Set();
@@ -3065,7 +3513,7 @@ var selectLeads = {
3065
3513
  idempotentHint: true,
3066
3514
  openWorldHint: true
3067
3515
  },
3068
- description: "Add leads to the user's transient selection (used by selection-scoped bulk operations). When to use: low-level. The user's selection is a per-token global state \u2014 be careful when invoking directly. When NOT to use: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192 action \u2192 clear in one call with proper Mutex protection. Calling this directly without acquiring the selection lock can clobber concurrent composite calls.",
3516
+ description: leadbay_select_leads,
3069
3517
  optional: true,
3070
3518
  write: true,
3071
3519
  inputSchema: {
@@ -3109,7 +3557,7 @@ var deselectLeads = {
3109
3557
  idempotentHint: true,
3110
3558
  openWorldHint: true
3111
3559
  },
3112
- description: "Remove leads from the user's transient selection. When to use: when narrowing a previously-built selection without clearing it entirely. When NOT to use: in normal flow \u2014 leadbay_enrich_titles handles selection lifecycle.",
3560
+ description: leadbay_deselect_leads,
3113
3561
  optional: true,
3114
3562
  write: true,
3115
3563
  inputSchema: {
@@ -3142,7 +3590,7 @@ var clearSelection = {
3142
3590
  idempotentHint: true,
3143
3591
  openWorldHint: true
3144
3592
  },
3145
- description: "Clear the user's transient selection. When to use: cleanup after manual selection work, or recovery from a stuck composite. When NOT to use: in normal flow \u2014 composites clear in their own finally blocks.",
3593
+ description: leadbay_clear_selection,
3146
3594
  optional: true,
3147
3595
  write: true,
3148
3596
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
@@ -3162,7 +3610,7 @@ var setActiveLens = {
3162
3610
  idempotentHint: true,
3163
3611
  openWorldHint: true
3164
3612
  },
3165
- description: "Mark a lens as last-used. Subsequent /me reads return it as last_requested_lens, so all composite tools default to it. When to use: after the user explicitly switched contexts (e.g. created a new lens via leadbay_create_lens). When NOT to use: in normal flow \u2014 leadbay_pull_leads and leadbay_adjust_audience auto-set the right lens.",
3613
+ description: leadbay_set_active_lens,
3166
3614
  optional: true,
3167
3615
  write: true,
3168
3616
  inputSchema: {
@@ -3189,7 +3637,7 @@ var createLens = {
3189
3637
  idempotentHint: false,
3190
3638
  openWorldHint: true
3191
3639
  },
3192
- description: "Create a new user-level lens by cloning an existing lens's filter/scoring as the starting point. When to use: when adjust_audience determined the current lens cannot be edited (e.g. it's the org default). When NOT to use: to update an existing lens \u2014 use leadbay_update_lens or leadbay_update_lens_filter.",
3640
+ description: leadbay_create_lens,
3193
3641
  optional: true,
3194
3642
  write: true,
3195
3643
  inputSchema: {
@@ -3236,7 +3684,7 @@ var updateLens = {
3236
3684
  idempotentHint: true,
3237
3685
  openWorldHint: true
3238
3686
  },
3239
- description: "Update lens metadata (name, description, mode flags). Does NOT change the audience filter \u2014 use leadbay_update_lens_filter for that. When to use: rename a lens or toggle multi_product_mode/use_hq_only. When NOT to use: to change which leads the lens shows \u2014 that's a filter operation.",
3687
+ description: leadbay_update_lens,
3240
3688
  optional: true,
3241
3689
  write: true,
3242
3690
  inputSchema: {
@@ -3269,7 +3717,7 @@ var updateLensFilter = {
3269
3717
  idempotentHint: true,
3270
3718
  openWorldHint: true
3271
3719
  },
3272
- description: "Replace the audience filter (sectors, sizes, locations) on a lens. Body is the full Filter object \u2014 this is a REPLACE, not a merge. Returns 400 'default_lens' if applied to the org default lens (clone it first). When to use: low-level mutation when you've already prepared the merged filter. When NOT to use: from agent flow \u2014 use leadbay_adjust_audience, which handles draft-vs-direct routing, permission fallback, and the merge logic so unrelated criteria aren't dropped.",
3720
+ description: leadbay_update_lens_filter,
3273
3721
  optional: true,
3274
3722
  write: true,
3275
3723
  inputSchema: {
@@ -3315,7 +3763,7 @@ var createLensDraft = {
3315
3763
  idempotentHint: false,
3316
3764
  openWorldHint: true
3317
3765
  },
3318
- description: "Create (or fetch existing) draft of an org-level lens. Idempotent \u2014 same user calling twice returns the same draft. The returned lens has draft_of set to the original lens id. When to use: when a non-admin needs to modify an org-level lens \u2014 make a draft, edit the draft. When NOT to use: from agent flow \u2014 leadbay_adjust_audience handles the draft-routing transparently.",
3766
+ description: leadbay_create_lens_draft,
3319
3767
  optional: true,
3320
3768
  write: true,
3321
3769
  inputSchema: {
@@ -3339,7 +3787,7 @@ var promoteLens = {
3339
3787
  idempotentHint: false,
3340
3788
  openWorldHint: true
3341
3789
  },
3342
- description: "Promote a user-level lens (or draft) to org-level so all teammates see it. Admin-only. When to use: rare \u2014 when an admin user has built a lens (or refined a draft) and wants to share it org-wide. When NOT to use: as a non-admin (will fail with 403); for personal lens changes (those stay user-scoped).",
3790
+ description: leadbay_promote_lens,
3343
3791
  optional: true,
3344
3792
  write: true,
3345
3793
  inputSchema: {
@@ -3365,7 +3813,7 @@ var setUserPrompt = {
3365
3813
  idempotentHint: true,
3366
3814
  openWorldHint: true
3367
3815
  },
3368
- description: "Set the org's intelligence-refinement prompt \u2014 free-text instruction that steers Leadbay's lead recommendations beyond firmographics. Admin-only. Setting this clears any pending clarification and triggers a full intelligence regeneration (web search + high-reasoning). When to use: low-level. When NOT to use: from agent flow \u2014 use leadbay_refine_prompt, which polls for follow-up clarifications.",
3816
+ description: leadbay_set_user_prompt,
3369
3817
  optional: true,
3370
3818
  write: true,
3371
3819
  inputSchema: {
@@ -3410,7 +3858,7 @@ var clearUserPrompt = {
3410
3858
  idempotentHint: true,
3411
3859
  openWorldHint: true
3412
3860
  },
3413
- description: "Remove the org's intelligence-refinement prompt (revert to AI-only generation). Admin-only. Triggers full intelligence regeneration. When to use: when a refinement turned out to be the wrong direction. When NOT to use: to replace with a different prompt \u2014 just call leadbay_refine_prompt; that overwrites.",
3861
+ description: leadbay_clear_user_prompt,
3414
3862
  optional: true,
3415
3863
  write: true,
3416
3864
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
@@ -3432,7 +3880,7 @@ var pickClarification = {
3432
3880
  idempotentHint: false,
3433
3881
  openWorldHint: true
3434
3882
  },
3435
- description: "Answer the pending clarification question \u2014 either by picking one of the offered options (option_id) or by typing a free-text answer. The answer is stored as the new user_prompt and triggers regeneration. Admin-only. When to use: low-level. When NOT to use: from agent flow \u2014 use leadbay_answer_clarification.",
3883
+ description: leadbay_pick_clarification,
3436
3884
  optional: true,
3437
3885
  write: true,
3438
3886
  inputSchema: {
@@ -3479,7 +3927,7 @@ var dismissClarification = {
3479
3927
  idempotentHint: false,
3480
3928
  openWorldHint: true
3481
3929
  },
3482
- description: "Dismiss the pending clarification without answering. Leadbay proceeds with its best guess. Admin-only. When to use: when the user explicitly doesn't want to answer the disambiguation. When NOT to use: as a default \u2014 answering with even a free-text reason gives Leadbay better signal.",
3930
+ description: leadbay_dismiss_clarification,
3483
3931
  optional: true,
3484
3932
  write: true,
3485
3933
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
@@ -3512,7 +3960,7 @@ var setEpilogueStatus = {
3512
3960
  idempotentHint: true,
3513
3961
  openWorldHint: true
3514
3962
  },
3515
- description: "Bulk-set the outreach progress (epilogue) status across a set of leads. Status values: STILL_CHASING, COULD_NOT_REACH_STILL_TRYING, INTEREST_VALIDATED_OR_MEETING_PLANED ('meeting booked'), NOT_INTERESTED_LOST (short labels accepted; mapped to the EPILOGUE_* enum). Up to 1000 leads per call. When to use: low-level. When NOT to use: from agent flow \u2014 leadbay_report_outreach pairs this with a note + verification, which is what humans actually need to see in Leadbay.",
3963
+ description: leadbay_set_epilogue_status,
3516
3964
  optional: true,
3517
3965
  write: true,
3518
3966
  inputSchema: {
@@ -3559,7 +4007,7 @@ var removeEpilogue = {
3559
4007
  idempotentHint: true,
3560
4008
  openWorldHint: true
3561
4009
  },
3562
- description: "Bulk-clear the epilogue status from a set of leads. When to use: when an outreach action was logged in error and needs to be undone. When NOT to use: to change status \u2014 call leadbay_set_epilogue_status with the new status (it overwrites).",
4010
+ description: leadbay_remove_epilogue,
3563
4011
  optional: true,
3564
4012
  write: true,
3565
4013
  inputSchema: {
@@ -3592,7 +4040,7 @@ var previewBulkEnrichment = {
3592
4040
  idempotentHint: true,
3593
4041
  openWorldHint: true
3594
4042
  },
3595
- description: "Preview a bulk-enrichment cost given a set of job titles applied to the current selection. Returns {selected_leads, enriched_contacts, enrichable_contacts, title_suggestions, auto_included_titles, previously_enriched_titles}. previously_enriched_titles is a newer field (in prod soon) \u2014 when present, the agent can recommend repeating those titles for new leads. When to use: between selecting leads and launching, to know what the enrichment will cost. When NOT to use: from agent flow \u2014 leadbay_enrich_titles wraps preview + launch with the right safety checks.",
4043
+ description: leadbay_preview_bulk_enrichment,
3596
4044
  optional: true,
3597
4045
  write: true,
3598
4046
  inputSchema: {
@@ -3622,7 +4070,7 @@ var launchBulkEnrichment = {
3622
4070
  idempotentHint: true,
3623
4071
  openWorldHint: true
3624
4072
  },
3625
- description: "Launch a bulk-enrichment job against the current selection. The backend requires email=true OR phone=true (both can be true). Returns 204 with no body \u2014 there is no bulk_id and no per-job status endpoint. Track results by polling individual leads via leadbay_get_contacts after ~60s; contact.enrichment.done flips to true. When to use: low-level. When NOT to use: from agent flow \u2014 leadbay_enrich_titles handles selection lifecycle, preview, launch, and cleanup.",
4073
+ description: leadbay_launch_bulk_enrichment,
3626
4074
  optional: true,
3627
4075
  write: true,
3628
4076
  inputSchema: {
@@ -3675,6 +4123,98 @@ var launchBulkEnrichment = {
3675
4123
  }
3676
4124
  };
3677
4125
 
4126
+ // ../core/dist/tools/create-custom-field.js
4127
+ var createCustomField = {
4128
+ name: "leadbay_create_custom_field",
4129
+ annotations: {
4130
+ title: "Create CRM custom field",
4131
+ readOnlyHint: false,
4132
+ destructiveHint: false,
4133
+ idempotentHint: true,
4134
+ openWorldHint: true
4135
+ },
4136
+ description: leadbay_create_custom_field,
4137
+ write: true,
4138
+ version: "0.6.4",
4139
+ inputSchema: {
4140
+ type: "object",
4141
+ properties: {
4142
+ name: {
4143
+ type: "string",
4144
+ description: "User-visible custom field name, e.g. 'HubSpot Contact'."
4145
+ },
4146
+ type: {
4147
+ type: "string",
4148
+ description: "Custom field type: TEXT, NUMBER, PRICE, DATE, DATETIME, or EXTERNAL_ID. Defaults to TEXT."
4149
+ },
4150
+ config: {
4151
+ type: ["object", "null"],
4152
+ description: "Type-specific config. EXTERNAL_ID requires {url_template:'https://.../{value}'}; PRICE requires {currency:'USD'}; DATE/DATETIME may set {format}."
4153
+ },
4154
+ if_not_exists: {
4155
+ type: "boolean",
4156
+ description: "Default true. If a custom field with the same name already exists, return it instead of creating a duplicate."
4157
+ }
4158
+ },
4159
+ required: ["name"],
4160
+ additionalProperties: false
4161
+ },
4162
+ outputSchema: {
4163
+ type: "object",
4164
+ properties: {
4165
+ id: { type: "string" },
4166
+ name: { type: "string" },
4167
+ type: { type: "string" },
4168
+ config: { type: ["object", "null"] },
4169
+ mapping_value: {
4170
+ type: "string",
4171
+ description: "Wire mapping value to use in import mappings, e.g. CUSTOM.123."
4172
+ },
4173
+ existed: {
4174
+ type: "boolean",
4175
+ description: "True when if_not_exists reused an existing custom field."
4176
+ }
4177
+ },
4178
+ required: ["id", "name", "type", "mapping_value", "existed"]
4179
+ },
4180
+ execute: async (client, params) => {
4181
+ const name = params.name?.trim();
4182
+ if (!name) {
4183
+ throw client.makeError("CUSTOM_FIELD_NAME_REQUIRED", "name must be a non-empty string", "Pass a user-visible custom field name, e.g. 'HubSpot Contact'.", "POST /crm/custom_fields");
4184
+ }
4185
+ const type = params.type ?? "TEXT";
4186
+ const config = params.config ?? null;
4187
+ if (type === "EXTERNAL_ID") {
4188
+ const urlTemplate = config?.url_template ?? config?.urlTemplate;
4189
+ if (!urlTemplate || !urlTemplate.includes("{value}")) {
4190
+ throw client.makeError("CUSTOM_FIELD_EXTERNAL_ID_TEMPLATE_REQUIRED", "EXTERNAL_ID custom fields require config.url_template containing {value}", "Use a URL template like https://app.hubspot.com/contacts/<portal-id>/record/0-1/{value}.", "POST /crm/custom_fields");
4191
+ }
4192
+ }
4193
+ if (params.if_not_exists ?? true) {
4194
+ const existing = await client.request("GET", "/crm/custom_fields");
4195
+ const found = (existing ?? []).find((f) => f.name.toLowerCase() === name.toLowerCase());
4196
+ if (found) {
4197
+ return {
4198
+ ...found,
4199
+ mapping_value: `CUSTOM.${found.id}`,
4200
+ existed: true
4201
+ };
4202
+ }
4203
+ }
4204
+ const body = {
4205
+ name,
4206
+ type,
4207
+ ...config ? { config } : {}
4208
+ };
4209
+ const created = await client.request("POST", "/crm/custom_fields", body);
4210
+ return {
4211
+ ...created,
4212
+ mapping_value: `CUSTOM.${created.id}`,
4213
+ existed: false
4214
+ };
4215
+ }
4216
+ };
4217
+
3678
4218
  // ../core/dist/composite/research-company.js
3679
4219
  var researchCompany = {
3680
4220
  name: "leadbay_research_company",
@@ -3685,7 +4225,7 @@ var researchCompany = {
3685
4225
  idempotentHint: true,
3686
4226
  openWorldHint: true
3687
4227
  },
3688
- description: "Deep-dive research on a specific company by NAME (fuzzy match against the active lens's wishlist). When to use: when the user references a company by name and you don't yet have its lead_id. When NOT to use: when you already have the lead_id \u2014 use leadbay_research_lead directly (it bundles richer signals + better top-down ordering for the agent).",
4228
+ description: leadbay_research_company,
3689
4229
  inputSchema: {
3690
4230
  type: "object",
3691
4231
  properties: {
@@ -3769,7 +4309,7 @@ var prepareOutreach = {
3769
4309
  idempotentHint: true,
3770
4310
  openWorldHint: true
3771
4311
  },
3772
- description: "Prepare an outreach package for a single lead: recommended contact + enriched contact details + AI summary. When to use: when the agent is about to draft outreach for ONE specific lead and needs the contact's email/phone. When NOT to use: across many leads \u2014 use leadbay_enrich_titles for bulk; for general lead detail use leadbay_research_lead (richer signals); to actually log the outreach action use leadbay_report_outreach (requires verification).",
4312
+ description: leadbay_prepare_outreach,
3773
4313
  optional: true,
3774
4314
  inputSchema: {
3775
4315
  type: "object",
@@ -3884,7 +4424,7 @@ var pullLeads = {
3884
4424
  idempotentHint: true,
3885
4425
  openWorldHint: true
3886
4426
  },
3887
- description: "Pull up new leads from the user's last-active lens \u2014 the canonical 'show me today's prospects' tool. Leadbay works like an inbox: each time the user logs back in, a fresh batch is delivered, paced by how many leads they've actually acted on recently. Pulling more won't produce more; user outreach/skips/saves does. Each returned lead carries a one-line qualification_summary built from leadbay_ai_agent_responses, plus the rich tags / scores / recommended_contact_title / engagement counters / in-flight flags from the lead summary. Roughly the top 10 of the batch come pre-qualified (populated qualification_summary + ai_agent_lead_score); leads below the top ~10 carry only the basic firmographic `score` \u2014 not worse, just resource-saved by the system. Call leadbay_bulk_qualify_leads to deepen any of them on demand. 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. 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 (which is removed in v0.2.0).",
4427
+ description: leadbay_pull_leads,
3888
4428
  inputSchema: {
3889
4429
  type: "object",
3890
4430
  properties: {
@@ -4142,7 +4682,7 @@ var researchLead = {
4142
4682
  idempotentHint: true,
4143
4683
  openWorldHint: true
4144
4684
  },
4145
- description: "Tell me everything decision-relevant about a single lead. Bundles the lens-scoped lead profile, the AI qualification answers (the agent's knowledge-base food), the structured web-research signals (with hot flags + sources), the enriched contacts, and the recent notes/epilogue/prospecting activity in one call. Order is deliberate: qualification first, then signals, then firmographics, then contacts, then engagement. Scoring has two layers: the basic `score` (firmographic, always present, already decent) and the AI qualification layer (`ai_agent_lead_score` + per-question answers + web_fetch signals). The AI layer is pre-populated for roughly the top 10 of each daily batch, and on-demand (via leadbay_bulk_qualify_leads) for anything below that. Combine both layers when judging a lead. When to use: when picking up a single lead from leadbay_pull_leads to decide whether to act on it. 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.)",
4685
+ description: leadbay_research_lead,
4146
4686
  inputSchema: {
4147
4687
  type: "object",
4148
4688
  properties: {
@@ -4427,7 +4967,7 @@ var recallOrderedTitles = {
4427
4967
  idempotentHint: true,
4428
4968
  openWorldHint: true
4429
4969
  },
4430
- description: "Show job titles the org has previously enriched, so the agent can repeat the same titles for new leads (or skip already-saturated ones). Two implementation paths: (1) PREFERRED: a selection-scoped preview call that reads previously_enriched_titles from the backend (newer prod field). (2) FALLBACK: live aggregation across each lead's enriched contacts. The composite picks transparently. When to use: before leadbay_enrich_titles, to plan which titles to order. When NOT to use: when you already know the exact titles you want to enrich.",
4970
+ description: leadbay_recall_ordered_titles,
4431
4971
  inputSchema: {
4432
4972
  type: "object",
4433
4973
  properties: {
@@ -4548,7 +5088,7 @@ var accountStatus = {
4548
5088
  idempotentHint: true,
4549
5089
  openWorldHint: true
4550
5090
  },
4551
- description: "Show the user's account state \u2014 admin rights, language, last-active lens, current quota usage across daily/weekly/monthly windows for llm_completion / ai_rescore / web_fetch resources, and whether the org's intelligence is mid-regeneration. Quota windows also hint at the user's consumption pace: heavy recent activity (ai_rescore / web_fetch near their window limits) is a signal that Leadbay will deliver a larger fresh batch next time the user logs back in, since batch size is paced by real consumption. When to use: at the start of a session to know what the agent can/can't do, or after a 429 to explain to the user which resource window was exhausted and when it resets. When NOT to use: as a pre-flight gate before bulk ops \u2014 operations themselves return 429; this tool is for context, not gating.",
5091
+ description: leadbay_account_status,
4552
5092
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
4553
5093
  outputSchema: {
4554
5094
  type: "object",
@@ -4645,7 +5185,7 @@ var bulkQualifyLeads = {
4645
5185
  idempotentHint: true,
4646
5186
  openWorldHint: true
4647
5187
  },
4648
- description: "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. 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. 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. When NOT to use: to qualify a single specific lead \u2014 that's leadbay_qualify_lead (granular, advanced).",
5188
+ description: leadbay_bulk_qualify_leads,
4649
5189
  inputSchema: {
4650
5190
  type: "object",
4651
5191
  properties: {
@@ -4946,6 +5486,324 @@ var bulkQualifyLeads = {
4946
5486
  }
4947
5487
  };
4948
5488
 
5489
+ // ../core/dist/composite/resolve-import-rows.js
5490
+ var SOCIAL_FIELDS = /* @__PURE__ */ new Set(["linkedin", "facebook", "instagram", "twitter", "tiktok"]);
5491
+ var RESOLVER_TARGETS = /* @__PURE__ */ new Set(["LEADBAY_ID", "CRM_ID", "LEAD_NAME", "LEAD_WEBSITE", "SIREN"]);
5492
+ var DEFAULT_CANDIDATE_PROFILE_LIMIT = 5;
5493
+ function coerceCell2(v) {
5494
+ if (v == null)
5495
+ return "";
5496
+ if (typeof v === "string")
5497
+ return v;
5498
+ if (typeof v === "number" || typeof v === "boolean")
5499
+ return String(v);
5500
+ return JSON.stringify(v) ?? "";
5501
+ }
5502
+ function compactMappings(mappings) {
5503
+ const out = {};
5504
+ for (const [k, v] of Object.entries(mappings)) {
5505
+ if (v)
5506
+ out[k] = v;
5507
+ }
5508
+ return out;
5509
+ }
5510
+ function payloadForRecord(row, mappings) {
5511
+ const payload = {};
5512
+ const socials = {};
5513
+ for (const [field, column] of Object.entries(mappings)) {
5514
+ const value = (row[column] ?? "").trim();
5515
+ if (!value)
5516
+ continue;
5517
+ if (SOCIAL_FIELDS.has(field)) {
5518
+ socials[field] = value;
5519
+ } else {
5520
+ payload[field] = value;
5521
+ }
5522
+ }
5523
+ if (Object.keys(socials).length > 0)
5524
+ payload.socials = socials;
5525
+ return payload;
5526
+ }
5527
+ function identityMappingsForImport(records, identityMappings) {
5528
+ const fields = {};
5529
+ if (records.some((r) => (r.LEADBAY_ID ?? "").trim() !== "")) {
5530
+ fields.LEADBAY_ID = "LEADBAY_ID";
5531
+ }
5532
+ const add = (field, target) => {
5533
+ const column = identityMappings[field];
5534
+ if (!column || fields[column])
5535
+ return;
5536
+ if (!records.some((r) => (r[column] ?? "").trim() !== ""))
5537
+ return;
5538
+ fields[column] = target;
5539
+ };
5540
+ add("leadbay_id", "LEADBAY_ID");
5541
+ add("crm_id", "CRM_ID");
5542
+ add("registry_number", "SIREN");
5543
+ add("name", "LEAD_NAME");
5544
+ add("website", "LEAD_WEBSITE");
5545
+ return { fields, statuses: {}, default_status: null };
5546
+ }
5547
+ function disambiguationPolicy() {
5548
+ return [
5549
+ "Use `matched` lead_id values directly; the tool already writes those into LEADBAY_ID.",
5550
+ "For `ambiguous` rows, do not choose a candidate from score alone. Score is a tied evidence-band, not a confidence percentage.",
5551
+ "For every ambiguous row you resolve, keep a short decision note: selected candidate id, evidence used, conflicting evidence checked, or why LEADBAY_ID stayed blank. Report counts and examples to the user.",
5552
+ "Try to disambiguate relentlessly before giving up: rerun the row with include_candidate_profiles=true and a larger candidate_profile_limit if candidate facts are truncated, and include every trustworthy source signal available (website, full address, postcode, city, phone, registry/CRM id, source URL path, neighborhood/location words).",
5553
+ "Compare addresses intelligently as a human would. Recognize ordinary formatting, abbreviation, spelling, punctuation, casing, accent, direction, ordinal, and suite/unit differences without treating address comparison as a rigid rule checklist. A clear same-place street address match is strong evidence.",
5554
+ "Auto-select an ambiguous candidate when hydrated candidate facts uniquely agree with the source row on strong evidence: exact registry number, exact CRM ID, exact canonical website/domain with only one candidate, exact phone, or name plus clear same-place address match with postcode/city and no conflicting evidence.",
5555
+ "If several candidates share the same website/domain, do not fail fast. Treat it as a chain/multi-location problem: use source street address, postcode, city/neighborhood, phone, source URL path/location slug, and location words in the source name to pick the specific location when exactly one candidate matches.",
5556
+ "Postcode/city alone is not enough, and brand/root-domain alone is not enough for multi-location sources. If several candidates remain plausible after checking location/phone/path evidence, leave LEADBAY_ID blank.",
5557
+ "A domain derived from a contact email is useful only when it is a business domain (not gmail/hotmail/outlook/yahoo/icloud/proton/aol/etc.) and the company/contact context agrees with the candidate. If the domain looks like a POS/vendor/agency/group domain or conflicts with row notes, do not use it for LEADBAY_ID selection.",
5558
+ "If evidence is name-only, fuzzy-name-only, generic directory website, or multiple candidates remain plausible after exhausting location/phone/path evidence, leave LEADBAY_ID blank and import with website/name so Leadbay can crawl or late-match later.",
5559
+ "When the user asked for qualification after import, qualify only the lead IDs that the import returns. Late website matches may appear later via leadbay_import_status."
5560
+ ];
5561
+ }
5562
+ function mappingGuidance() {
5563
+ return [
5564
+ "Treat mappings_for_import as a safe identity starting point, not a complete CRM mapping.",
5565
+ "Before importing, inspect every user column and sample values, then make a preservation plan: standard field, CONTACT_* field, Leadbay note, custom field, derived helper, or skip with a reason. The model, not this helper, should decide the complete mapping.",
5566
+ "Default to preserving client-provided business data. For meaningful columns with no standard Leadbay field, call leadbay_list_mappable_fields and create/reuse custom fields instead of silently dropping them. Skip only blank placeholders, duplicate plumbing, raw unparsed blobs after useful values are extracted, or values that would actively harm data quality.",
5567
+ "Always include LEADBAY_ID when records_for_import contains it; it makes deterministic matches import immediately.",
5568
+ "Also map the best available source identity columns: website/domain/url -> LEAD_WEBSITE, company/account/restaurant name -> LEAD_NAME, CRM/system id -> CRM_ID, registry/SIREN/SIRET/company number -> SIREN.",
5569
+ "For contact-only or HubSpot contact exports, derive a separate company_domain/company_website column from CONTACT_EMAIL only when the email domain is a real business domain and agrees with the company/deal/brand context. Do not use POS/vendor/group domains that conflict with the row, and do not derive company identity from private mailbox domains such as gmail.com, hotmail.com, outlook.com, yahoo.com, icloud.com, proton.me/protonmail.com, aol.com, or similar consumer email providers.",
5570
+ "Map contact-person columns when the file contains people: first_name -> CONTACT_FIRST_NAME, last_name -> CONTACT_LAST_NAME, job_title/title -> CONTACT_TITLE, contact email -> CONTACT_EMAIL, contact phone -> CONTACT_PHONE_NUMBER, contact LinkedIn -> CONTACT_LINKEDIN. If a company/restaurant row contains structured owners, decision makers, or contact lists, expand those people into additional import rows that repeat the parent lead identity and contain one CONTACT_* person per row. Multiple rows may point to the same LEADBAY_ID/company; import them as separate contacts on that lead.",
5571
+ "Preserve valuable source-system links. For HubSpot URLs, prefer extracting the stable object id into a clean column and mapping it to an existing or newly created EXTERNAL_ID custom field. Reuse an existing HubSpot linked-id field when present. Preserve raw source identifiers such as hubspot_id and associated_deal in custom fields when they are not already represented by a better standard/custom field. Use TEXT only when no stable id/template can be recovered.",
5572
+ "Clean source-system deal names before using them as LEAD_NAME: strip import campaign suffixes such as BYOC, BYOC only, DD, Uber, trailing separators, and duplicate pipeline labels, while preserving the original associated deal/source value in a custom field when it is meaningful to the user's workflow.",
5573
+ "Drop blank-header columns and placeholder values like `couldn't find`, `yes`, empty arrays, and raw JSON blobs unless you first extract meaningful scalar fields.",
5574
+ "Leadbay has CONTACT_PHONE_NUMBER but no standard LEAD_PHONE field in this surface. Preserve establishment/company phone only via an intentional custom field, not by pretending it is a contact phone.",
5575
+ "Preserve meaningful client notes, data-quality warnings that affect outreach, source record links, and owner/evidence URLs when they help the user's workflow. Do not map noisy scraper plumbing, duplicate blank columns, placeholder values, or long reasoning text.",
5576
+ "If the file contains meaningful per-lead notes/context, keep that text aside during import and add it to the imported/resolved leads with leadbay_add_note after import when that tool is available. For dry runs, report which notes would be written. If notes cannot be written and the user asked to preserve the text, create/reuse an import-notes custom field.",
5577
+ "For scraped owner/email JSON columns, extract the best scalar values into new clean columns before import; do not pass raw JSON blobs as core CRM fields.",
5578
+ "If no confident standard/custom mapping exists for a meaningful user column, create or reuse a custom field unless the column is blank/noisy/duplicate and record why it was skipped."
5579
+ ];
5580
+ }
5581
+ async function hydrateCandidateProfiles(client, candidates, lensId, limit) {
5582
+ const selected = candidates.slice(0, Math.max(0, limit));
5583
+ const settled = await Promise.allSettled(selected.map((c) => client.request("GET", `/lenses/${lensId}/leads/${c.lead_id}`)));
5584
+ const out = [];
5585
+ settled.forEach((r, i) => {
5586
+ if (r.status !== "fulfilled")
5587
+ return;
5588
+ const lead = r.value;
5589
+ out.push({
5590
+ lead_id: selected[i].lead_id,
5591
+ name: lead.name ?? null,
5592
+ website: lead.website ?? null,
5593
+ location: lead.location ? {
5594
+ full: lead.location.full ?? null,
5595
+ city: lead.location.city ?? null,
5596
+ state: lead.location.state ?? null,
5597
+ country: lead.location.country ?? null
5598
+ } : null,
5599
+ phone_numbers: Array.isArray(lead.phone_numbers) ? lead.phone_numbers : [],
5600
+ description: typeof lead.description === "string" ? lead.description.slice(0, 400) : null
5601
+ });
5602
+ });
5603
+ return out;
5604
+ }
5605
+ function hasResolverTarget(mappings) {
5606
+ return Object.values(mappings.fields).some((v) => RESOLVER_TARGETS.has(v));
5607
+ }
5608
+ var resolveImportRows = {
5609
+ name: "leadbay_resolve_import_rows",
5610
+ annotations: {
5611
+ title: "Resolve import row identities",
5612
+ readOnlyHint: true,
5613
+ destructiveHint: false,
5614
+ idempotentHint: true,
5615
+ openWorldHint: true
5616
+ },
5617
+ description: leadbay_resolve_import_rows,
5618
+ write: false,
5619
+ version: "0.6.4",
5620
+ inputSchema: {
5621
+ type: "object",
5622
+ properties: {
5623
+ records: {
5624
+ type: "array",
5625
+ description: "CSV-shaped rows from the user file. Values may be strings, numbers, booleans, null, arrays, or objects; non-scalars are JSON-stringified for resolver/import preparation.",
5626
+ items: { type: "object" }
5627
+ },
5628
+ identity_mappings: {
5629
+ type: "object",
5630
+ description: "Resolver field -> source column map chosen by the agent after inspecting the file, e.g. {website:'company_domain', name:'Company', crm_id:'Salesforce ID', registry_number:'SIREN'}. May point to clean columns the agent derived before calling this tool. This tool does not infer mappings from header names.",
5631
+ properties: {
5632
+ leadbay_id: { type: "string" },
5633
+ crm_id: { type: "string" },
5634
+ name: { type: "string" },
5635
+ website: { type: "string" },
5636
+ phone: { type: "string" },
5637
+ email: { type: "string" },
5638
+ registry_number: { type: "string" },
5639
+ registry_type: { type: "string" },
5640
+ address: { type: "string" },
5641
+ city: { type: "string" },
5642
+ postcode: { type: "string" },
5643
+ country: { type: "string" },
5644
+ linkedin: { type: "string" },
5645
+ facebook: { type: "string" },
5646
+ instagram: { type: "string" },
5647
+ twitter: { type: "string" },
5648
+ tiktok: { type: "string" }
5649
+ },
5650
+ additionalProperties: false
5651
+ },
5652
+ include_candidate_profiles: {
5653
+ type: "boolean",
5654
+ description: "When true, hydrate ambiguous candidate IDs with lightweight lead facts from the active lens. Use on small batches or rerun on only ambiguous rows; large ambiguous files can return many candidates."
5655
+ },
5656
+ candidate_profile_limit: {
5657
+ type: "number",
5658
+ description: `Maximum candidates to hydrate per ambiguous row when include_candidate_profiles=true (default ${DEFAULT_CANDIDATE_PROFILE_LIMIT}).`
5659
+ },
5660
+ lensId: {
5661
+ type: "number",
5662
+ description: "Lens ID used for candidate profile hydration. Defaults to the user's active lens."
5663
+ }
5664
+ },
5665
+ required: ["records"],
5666
+ additionalProperties: false
5667
+ },
5668
+ outputSchema: {
5669
+ type: "object",
5670
+ properties: {
5671
+ rows: {
5672
+ type: "array",
5673
+ description: "Per-input resolution result. Matched rows include lead_id; ambiguous rows include candidates; none/unidentifiable rows explain what extra signal would help.",
5674
+ items: { type: "object" }
5675
+ },
5676
+ records_for_import: {
5677
+ type: "array",
5678
+ description: "Import-ready records. Matched rows include LEADBAY_ID. Ambiguous/unresolved rows preserve user data and rely on website/name/CRM fields for normal or late matching.",
5679
+ items: { type: "object" }
5680
+ },
5681
+ mappings_for_import: {
5682
+ type: "object",
5683
+ description: "Safe identity-only mapping starter. The agent should review/extend this using mapping_guidance and leadbay_list_mappable_fields before importing."
5684
+ },
5685
+ identity_mappings_used: { type: "object" },
5686
+ mapping_guidance: {
5687
+ type: "array",
5688
+ description: "Instructions for building the final import mappings from the source columns.",
5689
+ items: { type: "string" }
5690
+ },
5691
+ disambiguation_policy: {
5692
+ type: "array",
5693
+ description: "Rules the agent should follow before writing LEADBAY_ID onto ambiguous rows.",
5694
+ items: { type: "string" }
5695
+ },
5696
+ summary: { type: "object" },
5697
+ next_action: { type: "string" },
5698
+ region: { type: "string" },
5699
+ _meta: { type: "object" }
5700
+ },
5701
+ required: [
5702
+ "rows",
5703
+ "records_for_import",
5704
+ "mappings_for_import",
5705
+ "identity_mappings_used",
5706
+ "mapping_guidance",
5707
+ "disambiguation_policy",
5708
+ "summary",
5709
+ "next_action",
5710
+ "region",
5711
+ "_meta"
5712
+ ]
5713
+ },
5714
+ execute: async (client, params) => {
5715
+ if (!Array.isArray(params.records) || params.records.length === 0) {
5716
+ throw client.makeError("RESOLVE_IMPORT_EMPTY_INPUT", "records[] must contain at least one row", "Pass the rows from the user file, then import records_for_import.", "POST /leads/resolve");
5717
+ }
5718
+ const rows = params.records.map((rec, i) => {
5719
+ if (rec == null || typeof rec !== "object" || Array.isArray(rec)) {
5720
+ throw client.makeError("RESOLVE_IMPORT_INVALID_ROW", `records[${i}] must be a plain object`, "Pass each input row as { ColumnName: value, ... }.", "POST /leads/resolve");
5721
+ }
5722
+ const out = {};
5723
+ for (const [k, v] of Object.entries(rec))
5724
+ out[k] = coerceCell2(v);
5725
+ return out;
5726
+ });
5727
+ const identityMappings = compactMappings(params.identity_mappings ?? {});
5728
+ const allColumns = new Set(rows.flatMap((r) => Object.keys(r)));
5729
+ for (const [field, column] of Object.entries(identityMappings)) {
5730
+ if (!allColumns.has(column)) {
5731
+ throw client.makeError("RESOLVE_IMPORT_MAPPING_KEY_UNKNOWN", `identity_mappings.${field} points to missing column ${JSON.stringify(column)}`, "Use a source column that exists in at least one input record.", "POST /leads/resolve");
5732
+ }
5733
+ }
5734
+ const outputs = [];
5735
+ const recordsForImport = [];
5736
+ const results = await Promise.all(rows.map(async (row) => {
5737
+ const payload = payloadForRecord(row, identityMappings);
5738
+ const result = await client.request("POST", "/leads/resolve", payload);
5739
+ return { payload, result };
5740
+ }));
5741
+ let matched = 0;
5742
+ let ambiguous = 0;
5743
+ let none = 0;
5744
+ let unidentifiable = 0;
5745
+ const hydrateProfiles = params.include_candidate_profiles === true;
5746
+ const candidateProfileLimit = params.candidate_profile_limit ?? DEFAULT_CANDIDATE_PROFILE_LIMIT;
5747
+ const hydrationLensId = hydrateProfiles ? params.lensId ?? await client.resolveDefaultLens() : null;
5748
+ for (let index = 0; index < results.length; index++) {
5749
+ const { payload, result } = results[index];
5750
+ const importRecord = { ...rows[index] };
5751
+ const rowOut = {
5752
+ index,
5753
+ type: result.type,
5754
+ resolver_payload: payload,
5755
+ import_record: importRecord
5756
+ };
5757
+ if (result.type === "matched") {
5758
+ matched++;
5759
+ importRecord.LEADBAY_ID = result.lead_id;
5760
+ rowOut.lead_id = result.lead_id;
5761
+ rowOut.matched_on = result.matched_on;
5762
+ } else if (result.type === "ambiguous") {
5763
+ ambiguous++;
5764
+ rowOut.candidates = result.candidates;
5765
+ if (hydrateProfiles && hydrationLensId !== null) {
5766
+ rowOut.candidate_profiles = await hydrateCandidateProfiles(client, result.candidates, hydrationLensId, candidateProfileLimit);
5767
+ }
5768
+ } else if (result.type === "none") {
5769
+ none++;
5770
+ rowOut.would_help = result.would_help;
5771
+ } else {
5772
+ unidentifiable++;
5773
+ rowOut.reason = result.reason;
5774
+ }
5775
+ recordsForImport.push(importRecord);
5776
+ outputs.push(rowOut);
5777
+ }
5778
+ const mappingsForImport = identityMappingsForImport(recordsForImport, identityMappings);
5779
+ const readyForImport = hasResolverTarget(mappingsForImport);
5780
+ return {
5781
+ rows: outputs,
5782
+ records_for_import: recordsForImport,
5783
+ mappings_for_import: mappingsForImport,
5784
+ identity_mappings_used: identityMappings,
5785
+ mapping_guidance: mappingGuidance(),
5786
+ disambiguation_policy: disambiguationPolicy(),
5787
+ summary: {
5788
+ total: rows.length,
5789
+ matched,
5790
+ ambiguous,
5791
+ none,
5792
+ unidentifiable,
5793
+ ready_for_import: readyForImport
5794
+ },
5795
+ next_action: readyForImport ? "Review ambiguous candidates using disambiguation_policy. Build the final mapping from mappings_for_import plus mapping_guidance, then call leadbay_import_leads or leadbay_import_and_qualify with records_for_import and the reviewed mapping." : "Add or map at least one import resolver column (LEADBAY_ID, CRM_ID, LEAD_NAME, LEAD_WEBSITE, or SIREN), then call leadbay_import_leads.",
5796
+ region: client.region,
5797
+ _meta: client.lastMeta ?? {
5798
+ region: client.region,
5799
+ endpoint: "POST /leads/resolve",
5800
+ latency_ms: null,
5801
+ retry_after: null
5802
+ }
5803
+ };
5804
+ }
5805
+ };
5806
+
4949
5807
  // ../core/dist/composite/import-and-qualify.js
4950
5808
  function escapeCsv(v) {
4951
5809
  return escapeCsvCell(v);
@@ -5035,28 +5893,7 @@ var importAndQualify = {
5035
5893
  idempotentHint: true,
5036
5894
  openWorldHint: true
5037
5895
  },
5038
- description: `Composite: import a list of leads (CSV-shaped records OR a list of domains), then trigger Leadbay's AI qualification (web research + per-question scoring) on every imported leadId, and return both the import outcome and the per-lead qualification answers \u2014 in one call. For MCP clients with short transport timeouts, pass \`wait_for_completion:false\`; the tool returns quickly with an import \`handle_id\` that can be polled with leadbay_import_status before continuing qualification. Honours a total wall-clock budget; when the budget is exhausted before all leads finish qualifying, returns a \`qualify_id\` UUID handle that survives MCP restart and can be passed to leadbay_qualify_status to retrieve the rest of the answers later.
5039
-
5040
- Inputs:
5041
- - \`domains\`: list of \`{domain, name?}\` (Mode A) \u2014 mutually exclusive with \`records\`.
5042
- - \`records\`: list of CSV-shaped objects (Mode B), accompanied by \`mappings\`. Use \`mappings.fields\` with StandardCrmFieldType names or 'CUSTOM.<id>' wire values; or \`mappings.custom_fields\` with field id or name shorthand. Discover the org's mappable surface via leadbay_list_mappable_fields.
5043
- - Budgets: \`total_budget_ms\` (default ${DEFAULT_TOTAL_BUDGET_MS3 / 6e4} min) caps the entire wall-clock; \`per_lead_budget_ms\` (default ${DEFAULT_PER_LEAD_BUDGET_MS2 / 1e3}s) caps each lead's individual qualification poll.
5044
-
5045
- Outputs include \`qualified[]\` (per-lead question answers), \`still_running[]\` (lead ids whose qualification exceeded the budget), \`not_imported[]\` (rows the wizard couldn't match), and \`qualify_id\` (the resumable handle when at least one lead is still running). Idempotent within a 5-min window: re-calling with the same records+mapping returns the same qualify_id (\`reused: true\`). The result has a \`kind\` discriminator (\`'result' | 'preview'\`); preview-mode (\`dry_run: 'preview'\`) returns mapping hints + custom-field candidates instead of importing. Pass \`dry_run: true\` for input-validation only (top-level \`dry_run: true\` appears in the result so the agent can distinguish from all-malformed input).
5046
-
5047
- When to use: the agent has a list of companies (domains, or CSV-shaped rows from the user's CRM) and wants Leadbay's full AI qualification \u2014 qualification answers, web-research signals \u2014 without orchestrating import + bulk_qualify_leads + lead_profile chains by hand.
5048
- When NOT to use: discovery (use leadbay_pull_leads); single-lead deep dive (use leadbay_research_lead); high-cadence or untrusted automation \u2014 this mutates user state by creating CRM-import rows and consumes ai_rescore + web_fetch quota.
5049
-
5050
- \u26A0\uFE0F MUTATES USER STATE. Each call:
5051
- - creates a CRM-imports row (visible in the web UI)
5052
- - touches onboarding state
5053
- - launches up to N\xD7ai_rescore + N\xD7web_fetch quota where N = imported lead count (unless \`skip_already_qualified: true\` (default) excludes already-scored leads)
5054
-
5055
- \u2139\uFE0F Monitor-tab membership: imported leads are NOT auto-promoted to the user's Monitor view. The lens-scoring rule decides \u2014 only leads that score above the lens threshold get \`in_monitor: true\` server-side. Lower-scoring imports stay invisible to the Monitor tab. Tell the user this if they ask 'where did my import go?' \u2014 answer is the CRM-imports list, not Monitor.
5056
-
5057
- \u2139\uFE0F In-lens-but-unscored leads: a lead may be admitted to the active lens (lens GET returns 200) but the lens-scoring job may not materialize for it (qualification never starts). The composite today surfaces hard-rejected leads (404 from lens GET) in \`not_in_lens[]\`, but in-lens-unscored leads currently sit in \`still_running[]\` \u2014 there's no per-lead 'scoring queued vs won't_compute' signal from the backend yet (tracked: leadbay/product#3571). If still_running stays non-empty for a lead more than 5 minutes after a successful import, suggest the user verify the lens covers that lead's profile (e.g., sector match) \u2014 qualify_status's poll won't ever produce answers.
5058
-
5059
- Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw); admin role; active billing.`,
5896
+ description: leadbay_import_and_qualify,
5060
5897
  write: true,
5061
5898
  version: "0.2.0",
5062
5899
  inputSchema: {
@@ -5097,7 +5934,7 @@ Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw); admin role;
5097
5934
  properties: {
5098
5935
  fields: {
5099
5936
  type: "object",
5100
- description: "Object whose keys are CSV column names and whose values are either StandardCrmFieldType (LEAD_NAME, LEAD_WEBSITE, ..., CONTACT_TITLE) or 'CUSTOM.<id>'. Discover via leadbay_list_mappable_fields. At least one entry must target LEAD_NAME or LEAD_WEBSITE."
5937
+ description: "Object whose keys are CSV column names and whose values are either StandardCrmFieldType (LEAD_NAME, LEAD_WEBSITE, ..., CONTACT_TITLE) or 'CUSTOM.<id>'. Discover via leadbay_list_mappable_fields. At least one entry must target LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Use leadbay_resolve_import_rows to prepare LEADBAY_ID values from messy user files. Contact exports and embedded owner/contact lists should map CONTACT_EMAIL/PHONE/TITLE/name fields while preserving parent lead identity; expand structured people into repeated parent rows. HubSpot/source links should map to CUSTOM.<id> fields created or discovered before import."
5101
5938
  },
5102
5939
  custom_fields: {
5103
5940
  type: "object",
@@ -6285,7 +7122,7 @@ var importStatus = {
6285
7122
  idempotentHint: true,
6286
7123
  openWorldHint: true
6287
7124
  },
6288
- description: "Retrieve the current state of an async lead import. Pass `handle_id` returned by leadbay_import_leads({wait_for_completion:false}), or pass legacy `importIds[]` to inspect backend wizard rows. This status call performs a single refresh pass and never polls in a loop.\nWhen to use: after leadbay_import_leads or leadbay_import_and_qualify returns `{status:'running', handle_id}` for the import phase, call this tool later to retrieve progress or the final import result without re-running the import.\nWhen NOT to use: for qualification handles returned as `qualify_id` \u2014 use leadbay_qualify_status for those; or when you still want the legacy blocking behavior from leadbay_import_leads with wait_for_completion=true.",
7125
+ description: leadbay_import_status,
6289
7126
  inputSchema: {
6290
7127
  type: "object",
6291
7128
  properties: {
@@ -6443,7 +7280,7 @@ var qualifyStatus = {
6443
7280
  idempotentHint: true,
6444
7281
  openWorldHint: true
6445
7282
  },
6446
- description: "Retrieve the current state of an import_and_qualify launch by `qualify_id`. Returns the same `qualified[]` / `still_running[]` shape as the original composite, refreshed against the backend at call time. The handle is persisted to ~/.leadbay/bulks.json with a 30-day TTL and survives MCP restart.\n\nWhen to use: after leadbay_import_and_qualify returned a qualify_id with non-empty `still_running[]`, call this tool a few minutes later (or hours) to retrieve the now-completed qualifications without re-running the import or re-spending qualify quota.\nWhen NOT to use: as a substitute for leadbay_research_lead \u2014 that's a deeper per-lead profile and includes contacts. This tool is purely the qualification answers + signals_count.",
7283
+ description: leadbay_qualify_status,
6447
7284
  inputSchema: {
6448
7285
  type: "object",
6449
7286
  properties: {
@@ -6631,7 +7468,7 @@ var enrichTitles = {
6631
7468
  idempotentHint: true,
6632
7469
  openWorldHint: true
6633
7470
  },
6634
- description: "Order contact enrichments by job title across many leads. Contacts are NOT returned by default with a lead (Leadbay keeps enrichment out-of-band to control cost); the agent requests them on demand via this tool when it's ready to actually reach out. Two modes: (A) NO titles param \u2014 returns the available titles + Leadbay's title_suggestions + auto_included_titles + a count of enrichable contacts, so the agent can ask the user which titles to enrich. (B) titles given \u2014 calls preview, then launches if there's anything enrichable. On 429 returns {status:'quota_exceeded'} cleanly. Selection lifecycle is wrapped in a try/finally so the user's selection is left clean even on error. When to use: as the agent's go-to enrichment entry point, immediately before proposing outreach. When NOT to use: to enrich a single contact \u2014 that's leadbay_enrich_contacts (granular). Speculatively, before the user has committed to outreaching \u2014 enrichment spends credits.",
7471
+ description: leadbay_enrich_titles,
6635
7472
  inputSchema: {
6636
7473
  type: "object",
6637
7474
  properties: {
@@ -6992,7 +7829,7 @@ var bulkEnrichStatus = {
6992
7829
  idempotentHint: true,
6993
7830
  openWorldHint: true
6994
7831
  },
6995
- description: "Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Returns the bulk_id, progress per lead (done/total enrichable contacts), and overall progress. When include_contacts=true (opt-in), includes each contact's email/phone/job_title/enrichment.done. When to use: poll this after leadbay_enrich_titles returns a bulk_id. Default include_contacts=false for cheap status polls; set include_contacts=true once all_done flips for the final read. When NOT to use: as a substitute for leadbay_research_lead \u2014 that already includes enriched contacts for a single lead.",
7832
+ description: leadbay_bulk_enrich_status,
6996
7833
  inputSchema: {
6997
7834
  type: "object",
6998
7835
  properties: {
@@ -7312,7 +8149,7 @@ var adjustAudience = {
7312
8149
  idempotentHint: true,
7313
8150
  openWorldHint: true
7314
8151
  },
7315
- description: "Restrict (or expand) the lens audience by sector / size. Free-text sectors are auto-resolved against the sector taxonomy; ambiguous matches are surfaced to the agent rather than guessed silently. Permission routing is hidden: the default lens auto-clones to a new user lens; an org-level lens defaults to a per-user draft (admins can override with save_for_org:true). Filter MERGES with existing criteria (unrelated criteria are not dropped). When to use: when the user wants to see different kinds of leads (sector / size / etc.). When NOT to use: to refine BEYOND firmographics \u2014 that's leadbay_refine_prompt.",
8152
+ description: leadbay_adjust_audience,
7316
8153
  inputSchema: {
7317
8154
  type: "object",
7318
8155
  properties: {
@@ -7497,7 +8334,7 @@ var refinePrompt = {
7497
8334
  idempotentHint: false,
7498
8335
  openWorldHint: true
7499
8336
  },
7500
- description: "Refine the kind of leads Leadbay surfaces, beyond firmographics. Free-text instruction (e.g. 'focus on hospitals running their own IT'). Sets the org's user_prompt; if the new prompt produces ambiguous criteria, Leadbay raises a clarification question, which this composite polls for and surfaces. Admin-only on the backend (will return 403 for non-admins). When to use: when audience filters (leadbay_adjust_audience) aren't enough. When NOT to use: to answer a pending clarification \u2014 that's leadbay_answer_clarification.",
8337
+ description: leadbay_refine_prompt,
7501
8338
  inputSchema: {
7502
8339
  type: "object",
7503
8340
  properties: {
@@ -7685,7 +8522,7 @@ var answerClarification = {
7685
8522
  idempotentHint: false,
7686
8523
  openWorldHint: true
7687
8524
  },
7688
- description: "Answer the pending clarification question Leadbay raised after a refine_prompt. The answer is stored as the new user_prompt and triggers regeneration. Pass option_id (preferred \u2014 pick from the offered options) or text_answer (free-text). Admin-only. When to use: after leadbay_refine_prompt returns status='clarification_pending'. When NOT to use: to set a brand-new prompt \u2014 use leadbay_refine_prompt.",
8525
+ description: leadbay_answer_clarification,
7689
8526
  inputSchema: {
7690
8527
  type: "object",
7691
8528
  properties: {
@@ -7776,7 +8613,7 @@ var reportOutreach = {
7776
8613
  idempotentHint: false,
7777
8614
  openWorldHint: true
7778
8615
  },
7779
- description: "Log an outreach action (email, call, message, meeting) on a lead so the human team using Leadbay sees the progress in their UI. Writes a NOTE on the lead and (optionally) sets an EPILOGUE status (still chasing, meeting booked, etc.). VERIFICATION REQUIRED: every call must include verification={source: 'gmail_message_id'|'calendar_event_id'|'user_confirmed', ref: '<id-or-confirmation>'} to prevent hallucinated outreach poisoning the pipeline. The verification is appended to the note body. Bulk variant: pass lead_ids=[uuid,...] instead of lead_id (epilogue is bulk-native; notes fan out per-lead). When to use: AFTER actually emailing/calling/meeting/messaging a contact, OR after a substantive decision the user wants logged (skip, save, hand off). When NOT to use: BEFORE doing the outreach (use dry_run:true to validate args first); without verification (call will be rejected); from a flow where the user did not consent to having actions logged automatically.",
8616
+ description: leadbay_report_outreach,
7780
8617
  optional: true,
7781
8618
  write: true,
7782
8619
  inputSchema: {
@@ -8085,7 +8922,8 @@ var granularWriteTools = [
8085
8922
  setEpilogueStatus,
8086
8923
  removeEpilogue,
8087
8924
  previewBulkEnrichment,
8088
- launchBulkEnrichment
8925
+ launchBulkEnrichment,
8926
+ createCustomField
8089
8927
  ];
8090
8928
  var granularTools = [
8091
8929
  login,
@@ -8103,6 +8941,7 @@ var compositeReadTools = [
8103
8941
  bulkEnrichStatus,
8104
8942
  qualifyStatus,
8105
8943
  importStatus,
8944
+ resolveImportRows,
8106
8945
  // listMappableFields is granular-shaped but the import composites depend on
8107
8946
  // it for discoverability; expose it always-on so agents can find custom fields
8108
8947
  // without needing LEADBAY_MCP_ADVANCED=1.
@@ -8119,7 +8958,13 @@ var compositeWriteTools = [
8119
8958
  answerClarification,
8120
8959
  reportOutreach,
8121
8960
  importLeads,
8122
- importAndQualify
8961
+ importAndQualify,
8962
+ // createCustomField is granular-shaped but file-import prompts depend on it
8963
+ // to preserve source-system links without requiring advanced-tool exposure.
8964
+ createCustomField,
8965
+ // addNote is granular-shaped but file-import prompts depend on it to preserve
8966
+ // meaningful source-file notes after imports return lead ids.
8967
+ addNote
8123
8968
  ];
8124
8969
  var compositeTools = [
8125
8970
  ...compositeReadTools,
@@ -8176,6 +9021,7 @@ export {
8176
9021
  removeEpilogue,
8177
9022
  previewBulkEnrichment,
8178
9023
  launchBulkEnrichment,
9024
+ createCustomField,
8179
9025
  researchCompany,
8180
9026
  prepareOutreach,
8181
9027
  pullLeads,
@@ -8183,6 +9029,7 @@ export {
8183
9029
  recallOrderedTitles,
8184
9030
  accountStatus,
8185
9031
  bulkQualifyLeads,
9032
+ resolveImportRows,
8186
9033
  importAndQualify,
8187
9034
  isValidBulkId,
8188
9035
  LocalBulkStore,