@leadbay/mcp 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +105 -0
- package/MIGRATION.md +140 -0
- package/README.md +9 -1
- package/dist/bin.js +4 -4
- package/dist/{chunk-NED7ATJI.js → chunk-MCTWP5F2.js} +3344 -1906
- package/dist/{dist-YMZYFHZK.js → dist-GUZQWQOO.js} +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-05-04
|
|
4
|
+
|
|
5
|
+
### `leadbay_import_and_qualify` — new composite
|
|
6
|
+
|
|
7
|
+
End-to-end import + AI qualification in one call. Wraps `leadbay_import_leads` (chunking, mapping preflight, custom-field validation), then fans out `web_fetch` on every imported leadId, polls until each lead's qualification answers populate, and returns the results. When the wall-clock budget overflows, returns a `qualify_id` UUID handle for resumable retrieval via the new `leadbay_qualify_status` tool.
|
|
8
|
+
|
|
9
|
+
**Inputs** (mostly mirrors `leadbay_import_leads`):
|
|
10
|
+
|
|
11
|
+
- `domains` OR `records` — same shape as import_leads.
|
|
12
|
+
- `mappings.fields` and `mappings.custom_fields` — same as 0.3.0 import_leads. Custom fields surface as first-class via `leadbay_list_mappable_fields` (also new).
|
|
13
|
+
- `dry_run: "preview"` — special mode: uploads the CSV in dry-run and returns the wizard's per-column AI mapping hints + sample rows + custom-field candidates from the org catalog matched against unmapped column names by exact / case-insensitive / fuzzy-substring. NO ai_rescore quota consumed.
|
|
14
|
+
- `total_budget_ms` (default 900_000 = 15 min), `per_lead_budget_ms` (default 90_000), `per_phase_budget_ms` (default 300_000).
|
|
15
|
+
- `skip_already_qualified` (default `true`) — skips `web_fetch` launch on leads with a non-null `ai_agent_lead_score`. Saves quota.
|
|
16
|
+
|
|
17
|
+
**Outputs** (full mode):
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{
|
|
21
|
+
kind: "result",
|
|
22
|
+
qualify_id: "<UUIDv4>" | null,
|
|
23
|
+
import_ids: [...],
|
|
24
|
+
imported: [{ leadId, domain?, name, rowId? }],
|
|
25
|
+
not_imported: [...],
|
|
26
|
+
qualified: [{ lead_id, qualifications: [{ question, score, response, computed_at }], qualification_summary, signals_count, ... }],
|
|
27
|
+
still_running: [{ lead_id }],
|
|
28
|
+
failed: [...],
|
|
29
|
+
quota_exceeded: bool,
|
|
30
|
+
skipped_already_qualified: [...],
|
|
31
|
+
reused?: bool, seconds_since_original?: number,
|
|
32
|
+
cancelled?: bool, budget_exhausted?: bool,
|
|
33
|
+
region, _meta
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`qualify_id` is persisted to `~/.leadbay/bulks.json` (30-day TTL, 5-min idempotency window) — same store as `leadbay_enrich_titles` but with a `kind: "qualify"` discriminator. Re-calling with the same records+mapping within 5 min returns the same handle (`reused: true`).
|
|
38
|
+
|
|
39
|
+
### `leadbay_qualify_status` — new resumable retrieval
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
leadbay_qualify_status({ qualify_id })
|
|
43
|
+
→ { qualify_id, status, lead_ids, qualified, still_running, ... }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Refreshes the per-lead state (`/web_fetch` + `/ai_agent_responses`) at call time. No backend mutation. Survives MCP restart.
|
|
47
|
+
|
|
48
|
+
### `leadbay_list_mappable_fields` — new discovery
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
leadbay_list_mappable_fields()
|
|
52
|
+
→ { standard_fields: [{name, description, mapping_value}], custom_fields: [{id, name, type, description, mapping_value}], region, _meta }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Lists every CRM field the agent can target in `mappings.fields`. Standard fields come from a static catalog with human descriptions; custom fields come from `GET /crm/custom_fields`. The `mapping_value` field is what the agent passes verbatim (e.g., `"CUSTOM.8"`).
|
|
56
|
+
|
|
57
|
+
### `leadbay_import_leads` 0.3.0 — `mappings.custom_fields` shorthand
|
|
58
|
+
|
|
59
|
+
In 0.2.0 the only way to map a custom field was `mappings.fields[col] = "CUSTOM.<id>"` (raw wire format). 0.3.0 adds `mappings.custom_fields[col] = <id>` (numeric) or `<name>` (string), resolved against `/crm/custom_fields` before the wizard sees it. New error codes: `IMPORT_CUSTOM_FIELD_UNKNOWN`, `IMPORT_INVALID_CUSTOM_MAPPING`, `IMPORT_CUSTOM_FIELD_NAME_AMBIGUOUS`, `IMPORT_CUSTOM_FIELD_CATALOG_REQUIRED`, `IMPORT_MAPPING_DUPLICATE_CUSTOM`. Catalog GET is suppressed when the mapping references no custom fields (saves a round trip).
|
|
60
|
+
|
|
61
|
+
### Bulk store schema widening
|
|
62
|
+
|
|
63
|
+
`BulkRecord` now has a `kind: "enrich" | "qualify"` discriminator. Old enrich rows (no `kind` field) default to `"enrich"` on read. Existing `leadbay_bulk_enrich_status` callers see no change. Cross-kind id queries are surfaced with the new `BULK_WRONG_KIND` error code that points the caller at the right tool.
|
|
64
|
+
|
|
65
|
+
### `not_in_lens` partition — terminate the silent infinite-poll
|
|
66
|
+
|
|
67
|
+
Both `leadbay_import_and_qualify` and `leadbay_qualify_status` now surface a `not_in_lens: string[]` array — lead ids that exist in the org (the wizard imported them) but are NOT admitted to the active lens. The backend's `queueAiRescoreForLead` is a no-op for these leads — they will never appear in `qualified[]`. Surfacing them in a distinct partition means the agent's poll loop terminates.
|
|
68
|
+
|
|
69
|
+
Discovered by iter-17 live e2e: imported 4 leads (Apple, Stripe, Datadog, GitHub) into a lens whose scoring rules only admitted Datadog. Datadog got `ai_agent_lead_score: -13` + 3/3 qualifications cleanly; Apple and Stripe sat in `still_running[]` indefinitely with no signal to stop polling. Now they land in `not_in_lens` with an actionable agent prompt: "either change the active lens or accept the lead won't be qualified."
|
|
70
|
+
|
|
71
|
+
`qualify_status` re-checks lens membership at each call — a lead added to the lens after the original `import_and_qualify` automatically migrates from `not_in_lens` back into the regular qualify pipeline on the next status call.
|
|
72
|
+
|
|
73
|
+
### Aligned with backend `/imports/{id}/leads` (PR #1801)
|
|
74
|
+
|
|
75
|
+
`leadbay_import_and_qualify` now sources the qualify-phase lead set from `GET /1.5/imports/{importId}/leads` (added backend-side 2026-05-06). This is the spec-prescribed source of truth — distinct lead ids the import touched (matched-existing AND newly-created). Replaces the per-record reconciliation pagination for the qualify input. Falls back gracefully to the per-record set when the endpoint is unavailable (older backend / 400 in_progress race).
|
|
76
|
+
|
|
77
|
+
Verified live: import 970bd47a-… (apple.com matched, salesforce.com uncrawled) → /leads returned `{lead_ids: [0a788a89-...]}` cleanly; qualify_id 08ad4555-… retrieved end-to-end.
|
|
78
|
+
|
|
79
|
+
### Monitor-membership disclosure
|
|
80
|
+
|
|
81
|
+
Both `leadbay_import_leads` and `leadbay_import_and_qualify` now flag in their tool descriptions that imported leads are NOT auto-promoted to the user's Monitor tab. Lens-scoring rules decide — only above-threshold leads get `in_monitor: true`. This was a real surprise discovered in production (journal entry `leadbay-monitor-lens-filter`, captured 2026-05-05). Surfacing it in the description prevents the agent from telling users "I imported your leads, check Monitor" — answer is the CRM-imports list, not Monitor.
|
|
82
|
+
|
|
83
|
+
### Stable qualification ordering + human_summary
|
|
84
|
+
|
|
85
|
+
Both `leadbay_import_and_qualify` and `leadbay_qualify_status` now sort `qualifications[]` by the org's `ai_agent_questions` catalog order — the same question appears at the same index across calls so LLM agents can position-index reliably. Catalog comes from `client.resolveTasteProfile()` (cached 10min). Falls back to alphabetical when the catalog is empty.
|
|
86
|
+
|
|
87
|
+
Each `qualified[]` entry also carries an optional `human_summary` string of the form `answered X/Y — <signal> on '<question>'[, <signal> on '<question>']` where `<signal>` is `strong positive` (score=20), `positive` (10), `neutral` (0), or `negative` (-10). Top-2 by absolute score. Saves the agent from reading every per-question response when it just needs the gist.
|
|
88
|
+
|
|
89
|
+
## 0.4.0 — 2026-05-04
|
|
90
|
+
|
|
91
|
+
### `leadbay_import_leads` 0.2.0 — custom field mapping
|
|
92
|
+
|
|
93
|
+
The MCP tool now drives the same CRM-import wizard the web UI exposes — pass arbitrary CSV-shaped records and tell Leadbay which column maps to which `StandardCrmFieldType`.
|
|
94
|
+
|
|
95
|
+
**Two modes** (pass exactly one of `domains` / `records`):
|
|
96
|
+
|
|
97
|
+
- **Mode A (existing, unchanged):** `domains: [{domain, name?}]` — synthesizes a 2-column CSV (LEAD_NAME, LEAD_WEBSITE) and uses the default mapping. Output shape is identical to 0.1.x: `{ leads: [{domain, leadId, name}], not_imported: [{domain, reason}], ... }`.
|
|
98
|
+
- **Mode B (new):** `records: [{Col1, Col2, ...}]` plus `mappings: { fields: { Col1: "LEAD_NAME", Col2: "LEAD_WEBSITE", Col3: "LEAD_SECTOR", ... } }`. The tool synthesizes a CSV from the union of record keys (sorted, deterministic) and POSTs the caller-supplied mapping to `/imports/{id}/update_mappings`. Output shape: `{ leads: [{rowId, domain?, leadId, name}], not_imported: [{rowId, domain?, reason}], ... }`. `rowId` round-trips your input row order; `domain` populated only when `LEAD_WEBSITE` was mapped and the value parsed.
|
|
99
|
+
|
|
100
|
+
Mappings.fields must include `LEAD_NAME` or `LEAD_WEBSITE` — the wizard's resolver needs at least one of those to find a lead. Other CRM fields (`LEAD_SECTOR`, `LEAD_LOCATION`, `LEAD_SIZE`, `EMAIL`, `CRM_ID`, `LEADBAY_ID`, `DEAL_CRM_ID`, `CONTACT_TITLE`, `LEAD_STATUS`, `LEAD_STATUS_DATE`) are passed through verbatim.
|
|
101
|
+
|
|
102
|
+
**Validation (records mode):** new typed error codes — `IMPORT_INPUT_CONFLICT` (both modes supplied), `IMPORT_MAPPING_REQUIRED` (no mappings.fields), `IMPORT_MAPPING_NO_RESOLVER` (no LEAD_NAME or LEAD_WEBSITE in mapping), `IMPORT_MAPPING_KEY_UNKNOWN` (mapping key absent from records), `IMPORT_RESERVED_COLUMN` (record or mapping key matches `MCP_ROW_ID` case-insensitively), `IMPORT_INVALID_COLUMN_NAME` (column name >128 chars or contains control chars), `IMPORT_INVALID_CELL_TYPE` (cell value is array/object — coerce to string before passing). null/undefined cells coerce to "", numbers/booleans coerce via `String(v)`.
|
|
103
|
+
|
|
104
|
+
**Security:** user-supplied column names now flow through the same `escapeCsvCell` (RFC 4180 quoting + formula-injection prefix) that data values use. Header injection vectors (`=`, `+`, `-`, `@`, `,`, `"`, newline) are neutered.
|
|
105
|
+
|
|
106
|
+
**Backward compat:** Mode A output shape unchanged — the new `rowId` field is records-mode only. Existing `domains: [...]` callers see no diff.
|
|
107
|
+
|
|
3
108
|
## 0.3.0 — 2026-04-29
|
|
4
109
|
|
|
5
110
|
Behavior-changing release: closes [product#3504](https://github.com/leadbay/product/issues/3504) end-to-end. Default-installed MCP server now matches its own system prompt out of the box, the `login` command never lands a bearer token in scrollback by default, and `claude mcp add` registers Leadbay at user scope so it's visible from any project.
|
package/MIGRATION.md
CHANGED
|
@@ -210,3 +210,143 @@ Default is read-only (exposeWrite=false, exposeGranular=false).
|
|
|
210
210
|
to detect when `enrichment.done` flips.
|
|
211
211
|
- A `DELETE /lenses/{draftId}` endpoint — not testable in our tenant; treated
|
|
212
212
|
as best-effort with `orphan_draft_id` surfaced on cleanup failure.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
# Migration: leadbay-mcp 0.4.x → 0.5.0
|
|
217
|
+
|
|
218
|
+
The 0.5.0 release adds three new tools and extends `leadbay_import_leads` to support custom fields. This is purely additive — existing 0.4.x callers see no behavior change.
|
|
219
|
+
|
|
220
|
+
## What's new
|
|
221
|
+
|
|
222
|
+
| Tool | Purpose |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `leadbay_list_mappable_fields` | Discovery: lists every CRM field (standard + this org's custom fields) the agent can target in `mappings.fields`. |
|
|
225
|
+
| `leadbay_import_and_qualify` | The mission verb: imports leads + triggers AI qualification + returns per-question answers in one call. Resumable via `qualify_id`. |
|
|
226
|
+
| `leadbay_qualify_status` | Retrieves a previously-launched qualification by `qualify_id` (handle persists 30d). |
|
|
227
|
+
| `leadbay_import_leads` 0.3.0 | Adds `mappings.custom_fields` ergonomic shorthand alongside the raw `mappings.fields[col] = "CUSTOM.<id>"` wire format. |
|
|
228
|
+
|
|
229
|
+
## Worked example: discover → import → qualify
|
|
230
|
+
|
|
231
|
+
```jsonc
|
|
232
|
+
// 1. Discover what fields are mappable on this org.
|
|
233
|
+
leadbay_list_mappable_fields()
|
|
234
|
+
// → {
|
|
235
|
+
// "standard_fields": [{name:"LEAD_NAME", description:"...", mapping_value:"LEAD_NAME"}, ...],
|
|
236
|
+
// "custom_fields": [{id:"8", name:"priority_test", type:"TEXT", mapping_value:"CUSTOM.8"}, ...],
|
|
237
|
+
// "_meta": {region: "us", endpoint: "GET /crm/custom_fields", latency_ms: 78}
|
|
238
|
+
// }
|
|
239
|
+
|
|
240
|
+
// 2. (Optional) preview the wizard's mapping suggestions for a sample.
|
|
241
|
+
leadbay_import_and_qualify({
|
|
242
|
+
records: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
243
|
+
dry_run: "preview"
|
|
244
|
+
})
|
|
245
|
+
// → {
|
|
246
|
+
// kind: "preview",
|
|
247
|
+
// mapping_hints: [{column: "Site", suggested_field: "LEAD_WEBSITE", ai_confidence: 95}],
|
|
248
|
+
// custom_field_candidates: [{column: "Priority", candidates: [{id:"8", mapping_value:"CUSTOM.8", reason:"fuzzy_substring_match"}]}],
|
|
249
|
+
// sample_rows: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
250
|
+
// import_id: "<uuid>"
|
|
251
|
+
// }
|
|
252
|
+
|
|
253
|
+
// 3. Run the full flow with the chosen mapping.
|
|
254
|
+
leadbay_import_and_qualify({
|
|
255
|
+
records: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
256
|
+
mappings: {
|
|
257
|
+
fields: {Brand: "LEAD_NAME", Site: "LEAD_WEBSITE"},
|
|
258
|
+
custom_fields: {Priority: "priority_test"} // resolved against /crm/custom_fields
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
// → {
|
|
262
|
+
// kind: "result",
|
|
263
|
+
// qualify_id: "<uuidv4>", // resumable handle
|
|
264
|
+
// import_ids: ["<uuid>"],
|
|
265
|
+
// imported: [{leadId: "...", domain: "apple.com", name: "APPLE", rowId: "..."}],
|
|
266
|
+
// not_imported: [],
|
|
267
|
+
// qualified: [{
|
|
268
|
+
// lead_id: "...",
|
|
269
|
+
// qualifications: [{question: "Is the company...", score: 20, response: "yes...", computed_at: "..."}, ...],
|
|
270
|
+
// qualification_summary: {answered: 3, total: 3, avg_qualification_boost: 13.3},
|
|
271
|
+
// human_summary: "answered 3/3 — strong positive on 'Is the company...', positive on '...'",
|
|
272
|
+
// signals_count: 12
|
|
273
|
+
// }],
|
|
274
|
+
// still_running: [],
|
|
275
|
+
// chosen_budgets: {strategy: "small", total_budget_ms: 180000, wall_clock_estimate_ms: 60000, ...}
|
|
276
|
+
// }
|
|
277
|
+
|
|
278
|
+
// 4. If still_running was non-empty, retrieve later via qualify_id.
|
|
279
|
+
leadbay_qualify_status({qualify_id: "<uuid>"})
|
|
280
|
+
// → same shape, refreshed against backend at call time. failed[] populated
|
|
281
|
+
// for any leads that 404'd between launch and status-check.
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Custom fields wire format
|
|
285
|
+
|
|
286
|
+
The backend serializer accepts `"CUSTOM.<id>"` as a value in the `mappings.fields` map. 0.3.0 of `leadbay_import_leads` accepts both:
|
|
287
|
+
|
|
288
|
+
```jsonc
|
|
289
|
+
// raw wire form (passes through unchanged):
|
|
290
|
+
mappings.fields = {col: "CUSTOM.8"}
|
|
291
|
+
|
|
292
|
+
// ergonomic shorthand (resolved against /crm/custom_fields):
|
|
293
|
+
mappings.custom_fields = {col: 8} // numeric id
|
|
294
|
+
mappings.custom_fields = {col: "8"} // string-shaped id
|
|
295
|
+
mappings.custom_fields = {col: "priority_test"} // exact name (case-insensitive fallback)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
New error codes (all surfaced with hint pointing at next action):
|
|
299
|
+
`IMPORT_CUSTOM_FIELD_UNKNOWN`, `IMPORT_INVALID_CUSTOM_MAPPING`, `IMPORT_CUSTOM_FIELD_NAME_AMBIGUOUS`, `IMPORT_CUSTOM_FIELD_CATALOG_REQUIRED`, `IMPORT_MAPPING_DUPLICATE_CUSTOM`, `IMPORT_MAPPING_CONFLICT_TARGET`.
|
|
300
|
+
|
|
301
|
+
## Bulk store schema widening
|
|
302
|
+
|
|
303
|
+
`~/.leadbay/bulks.json` now has a `kind: "enrich" | "qualify"` discriminator. Old enrich rows (no `kind` field) are read with `kind: "enrich"` defaulted in. Existing `leadbay_bulk_enrich_status` callers see no change. Querying an enrich `bulk_id` via `leadbay_qualify_status` (or vice versa) returns the new `BULK_WRONG_KIND` error pointing at the right tool.
|
|
304
|
+
|
|
305
|
+
## Adaptive budgets
|
|
306
|
+
|
|
307
|
+
When `leadbay_import_and_qualify` is called with no `total_budget_ms` or `per_lead_budget_ms`, the composite picks a strategy from input size:
|
|
308
|
+
|
|
309
|
+
| Input size | Strategy | total_budget_ms | per_lead_budget_ms |
|
|
310
|
+
|---|---|---|---|
|
|
311
|
+
| ≤ 5 leads | small | 3 min | 60s |
|
|
312
|
+
| 6 – 20 leads | default | 10 min | 90s |
|
|
313
|
+
| > 20 leads | large | 25 min | 120s |
|
|
314
|
+
|
|
315
|
+
The chosen values appear on the response as `chosen_budgets: {strategy, total_budget_ms, per_lead_budget_ms, wall_clock_estimate_ms}` so the agent can communicate the wall-clock to the human user.
|
|
316
|
+
|
|
317
|
+
## See also
|
|
318
|
+
|
|
319
|
+
- [CHANGELOG.md 0.5.0 entry](./CHANGELOG.md) — full release notes.
|
|
320
|
+
|
|
321
|
+
## Error-code reference (0.5.0)
|
|
322
|
+
|
|
323
|
+
Quick-reference for new error codes shipped in 0.5.0:
|
|
324
|
+
|
|
325
|
+
| Code | When | Next action |
|
|
326
|
+
|---|---|---|
|
|
327
|
+
| `IMPORT_CUSTOM_FIELD_UNKNOWN` | Caller passed `CUSTOM.<id>` or `mappings.custom_fields[col]` that doesn't exist on this org. | Call `leadbay_list_mappable_fields()`; pick a real id/name from `custom_fields[]`. |
|
|
328
|
+
| `IMPORT_INVALID_CUSTOM_MAPPING` | Caller passed a mapping value that's neither a StandardCrmFieldType nor a well-formed `CUSTOM.<digits>`. | Use a value from `leadbay_list_mappable_fields()` `mapping_value` field. |
|
|
329
|
+
| `IMPORT_CUSTOM_FIELD_NAME_AMBIGUOUS` | Two custom fields share the name (case-insensitive) the caller used. | Pass the numeric id instead. |
|
|
330
|
+
| `IMPORT_MAPPING_DUPLICATE_CUSTOM` | Same column appears in both `mappings.fields` and `mappings.custom_fields`. | Pick one of the two maps; remove the duplicate. |
|
|
331
|
+
| `IMPORT_MAPPING_CONFLICT_TARGET` | Two columns map to the same StandardCrmFieldType (e.g. both → `LEAD_NAME`). | Pick the column that contains the real value; drop the others from the mapping. |
|
|
332
|
+
| `IMPORT_CUSTOM_FIELD_CATALOG_REQUIRED` | Internal: catalog couldn't be fetched. | Retry; or use raw `CUSTOM.<id>` in `mappings.fields` instead of shorthand. |
|
|
333
|
+
| `BULK_TRACKER_UNAVAILABLE` | MCP server has no BulkTracker (qualify_id persistence). | Restart with `LEADBAY_BULK_STORE_ALLOW_MEMORY=1` or upgrade. |
|
|
334
|
+
| `BULK_INVALID_ID` | `qualify_id` is not a valid UUIDv4. | Pass the id returned by the prior `leadbay_import_and_qualify` call verbatim. |
|
|
335
|
+
| `BULK_NOT_FOUND` | Handle expired (>30d TTL) or never existed. | Re-launch via `leadbay_import_and_qualify`. |
|
|
336
|
+
| `BULK_PENDING` | Launch in flight or crashed mid-launch. | Retry in a few seconds; if persists >60s, relaunch. |
|
|
337
|
+
| `BULK_LAUNCH_FAILED` | The original launch failed permanently. | Re-launch. |
|
|
338
|
+
| `BULK_WRONG_KIND` | Caller passed an enrich `bulk_id` to `leadbay_qualify_status` (or vice versa). | Switch tools — enrich → `leadbay_bulk_enrich_status`, qualify → `leadbay_qualify_status`. |
|
|
339
|
+
| `IMPORT_PREVIEW_NO_UPLOAD` | Preview mode hit all-malformed input; nothing was uploaded. | Check that at least one record/domain is well-formed. |
|
|
340
|
+
|
|
341
|
+
## Per-tool prereqs (0.5.0)
|
|
342
|
+
|
|
343
|
+
Quick scan of what each new tool requires before invocation:
|
|
344
|
+
|
|
345
|
+
| Tool | Admin role | Active billing | LEADBAY_MCP_WRITE |
|
|
346
|
+
|---|---|---|---|
|
|
347
|
+
| `leadbay_list_mappable_fields` | no | no | no (read-only) |
|
|
348
|
+
| `leadbay_qualify_status` | no | no | no (read-only) |
|
|
349
|
+
| `leadbay_import_and_qualify` | yes | yes | yes (`=1`, default ON in 0.3.0+) |
|
|
350
|
+
| `leadbay_import_leads` (0.3.0) | yes | yes | yes |
|
|
351
|
+
|
|
352
|
+
Pre-flight: call `leadbay_account_status` to read `user.admin` and `organization.plan`. Both writes will return typed errors (`IMPORT_ADMIN_REQUIRED` / `IMPORT_BILLING_REQUIRED` / `FORBIDDEN`) at the call site too — pre-flighting just gives the agent a chance to ask the user politely instead of attempting a write that 403s.
|
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ Leadbay connection OK.
|
|
|
161
161
|
|
|
162
162
|
### Exposing the granular tools and disabling write tools
|
|
163
163
|
|
|
164
|
-
By default the server exposes the **composite workflow tools** — both reads (`leadbay_pull_leads`, `leadbay_research_lead`, `leadbay_account_status`, `leadbay_recall_ordered_titles`, `leadbay_research_company`, `leadbay_prepare_outreach`) and writes (`leadbay_bulk_qualify_leads`, `leadbay_enrich_titles`, `leadbay_refine_prompt`, `leadbay_report_outreach`, `leadbay_adjust_audience`, `leadbay_answer_clarification`, `leadbay_import_leads`). These work well with most prompts.
|
|
164
|
+
By default the server exposes the **composite workflow tools** — both reads (`leadbay_pull_leads`, `leadbay_research_lead`, `leadbay_account_status`, `leadbay_recall_ordered_titles`, `leadbay_research_company`, `leadbay_prepare_outreach`, `leadbay_qualify_status`, `leadbay_list_mappable_fields`) and writes (`leadbay_bulk_qualify_leads`, `leadbay_enrich_titles`, `leadbay_refine_prompt`, `leadbay_report_outreach`, `leadbay_adjust_audience`, `leadbay_answer_clarification`, `leadbay_import_leads`, `leadbay_import_and_qualify`). These work well with most prompts.
|
|
165
165
|
|
|
166
166
|
To **disable the write tools** (run a strictly read-only agent), set `LEADBAY_MCP_WRITE=0`. The server's system prompt will adapt to omit references to those tools.
|
|
167
167
|
|
|
@@ -207,6 +207,14 @@ Suitable for **occasional automation**. **Not** suitable for high-cadence (>5 ca
|
|
|
207
207
|
|
|
208
208
|
**Limitation:** the wedge maps domains to leads the crawler already knows. Uncrawled domains land in `not_imported` with `reason: "uncrawled"` — the tool does **not** create new leads for unknown websites; the caller decides what to do (skip, queue for the backend follow-up, etc.).
|
|
209
209
|
|
|
210
|
+
### Importing + qualifying in one verb (0.5.0)
|
|
211
|
+
|
|
212
|
+
`leadbay_import_and_qualify` collapses import → AI qualification into a single composite call. Returns per-lead qualification answers + ai_agent_lead_score inline when budget allows; otherwise returns a `qualify_id` UUID handle the agent can pass to `leadbay_qualify_status` later (handle persists 30 days, survives MCP restart). See [MIGRATION.md](./MIGRATION.md#migration-leadbay-mcp-04x--050) for the full worked example (discover → preview → import → qualify_status), JSON shapes, error-code reference, per-tool prereqs, and `not_in_lens` partition semantics.
|
|
213
|
+
|
|
214
|
+
Companion tool: `leadbay_list_mappable_fields` returns the union of standard CRM fields and this org's custom fields (with `mapping_value` ready for `mappings.fields` paste-in). Optional `for_records` param runs the wizard's preprocess on a sample to attach mapping hints + custom-field name-match candidates in a single call.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
210
218
|
**Requires:** `LEADBAY_MCP_WRITE` not set to `0` (it's ON by default since 0.3.0; or `exposeWrite: true` in OpenClaw); admin role on your Leadbay account; active billing.
|
|
211
219
|
|
|
212
220
|
Use `dry_run: true` to validate domain formatting and wizard reachability without committing the lead-CRM linking. (The CRM-imports row still appears — only a backend change can remove that.)
|
package/dist/bin.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
granularReadTools,
|
|
9
9
|
granularWriteTools,
|
|
10
10
|
resolveRegion
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MCTWP5F2.js";
|
|
12
12
|
|
|
13
13
|
// src/bin.ts
|
|
14
14
|
import { realpathSync } from "fs";
|
|
@@ -158,7 +158,7 @@ function buildServer(client, opts = {}) {
|
|
|
158
158
|
|
|
159
159
|
// src/bin.ts
|
|
160
160
|
import { createRequire } from "module";
|
|
161
|
-
var VERSION = "0.
|
|
161
|
+
var VERSION = "0.5.0";
|
|
162
162
|
var HELP = `
|
|
163
163
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
164
164
|
|
|
@@ -447,7 +447,7 @@ async function runLogin(args) {
|
|
|
447
447
|
let result;
|
|
448
448
|
try {
|
|
449
449
|
if (pinnedRegion && !allowFallback) {
|
|
450
|
-
const { REGIONS } = await import("./dist-
|
|
450
|
+
const { REGIONS } = await import("./dist-GUZQWQOO.js");
|
|
451
451
|
const baseUrl = REGIONS[pinnedRegion];
|
|
452
452
|
const c = createClient({ region: pinnedRegion });
|
|
453
453
|
const token = await loginAt(baseUrl, email, password);
|
|
@@ -939,7 +939,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
939
939
|
let region;
|
|
940
940
|
try {
|
|
941
941
|
if (pinnedRegion && !allowFallback) {
|
|
942
|
-
const { REGIONS } = await import("./dist-
|
|
942
|
+
const { REGIONS } = await import("./dist-GUZQWQOO.js");
|
|
943
943
|
const baseUrl = REGIONS[pinnedRegion];
|
|
944
944
|
token = await loginAt(baseUrl, email, password);
|
|
945
945
|
region = pinnedRegion;
|