@leadbay/mcp 0.4.0 → 0.6.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 +98 -0
- package/MIGRATION.md +236 -0
- package/README.md +217 -11
- package/dist/bin.js +444 -15
- package/dist/{chunk-O2UOXRZO.js → chunk-NLG7GUZ3.js} +5393 -2221
- package/dist/{dist-RONMQBYU.js → dist-JUTSXWBL.js} +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.6.0 — UNRELEASED
|
|
4
|
+
|
|
5
|
+
Massive spec-coverage upgrade. Each tool declares **annotations** (`readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint`); 17 composites + 12 highest-leverage granulars declare typed `outputSchema` + emit `structuredContent`. New surfaces: `prompts/*` (5 canned slash-commands), `resources/*` (`lead://`, `lens://`, `org://taste-profile`), `notifications/progress` (per-lead streaming on every long-running composite), `notifications/cancelled` → `ToolContext.signal` (bulk-store entries marked `cancelled` so subsequent status polls return `BULK_CANCELLED`), `elicitation/create` (refine_prompt clarification + report_outreach user_confirmed anti-poisoning).
|
|
6
|
+
|
|
7
|
+
**Hardening**: every `inputSchema` declares `additionalProperties: false`. Runtime conformance test pins `structuredContent` against `outputSchema` for every declarer (drift-catcher). Static-scan audit of every error-hint string asserts each names a recovery action. `report_outreach.verification` rejects extra keys at runtime (closes the SDK's nested-additionalProperties limitation). `score_0_to_10` deprecated alias of `boost_score` removed in 0.7.0.
|
|
8
|
+
|
|
9
|
+
**Token economy**: pagination payloads carry `has_more` + `next_page`. `research_lead` truncates large payloads with a `truncation_hint`. Per-tool opt-in `response_format: "json" | "markdown"` lets chat-rendering agents pick the cheaper render (research_lead first; pattern reusable). `LEADBAY_DEBUG=1` enables a per-tools/call observability line on stderr.
|
|
10
|
+
|
|
11
|
+
**DXT → MCPB**: bundle now publishes both `leadbay-X.Y.Z.dxt` (legacy) and `leadbay-X.Y.Z.mcpb` (new Claude Desktop format) for one cycle. Manifest `dxt_version` field stays for backwards-compat with current installers; field rename will follow Anthropic's spec when finalised. The two filenames have identical content; downstream installers can match either glob.
|
|
12
|
+
|
|
13
|
+
**Versioning + docs**: 0.4.0 / 0.6.0 bumps; README §3a "Spec primitives in action" adds wire-level JSON-RPC transcripts for every primitive; MIGRATION.md 0.5 → 0.6 walkthrough.
|
|
14
|
+
|
|
15
|
+
## 0.5.0 — 2026-05-04
|
|
16
|
+
|
|
17
|
+
### `leadbay_import_and_qualify` — new composite
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
**Inputs** (mostly mirrors `leadbay_import_leads`):
|
|
22
|
+
|
|
23
|
+
- `domains` OR `records` — same shape as import_leads.
|
|
24
|
+
- `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).
|
|
25
|
+
- `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.
|
|
26
|
+
- `total_budget_ms` (default 900_000 = 15 min), `per_lead_budget_ms` (default 90_000), `per_phase_budget_ms` (default 300_000).
|
|
27
|
+
- `skip_already_qualified` (default `true`) — skips `web_fetch` launch on leads with a non-null `ai_agent_lead_score`. Saves quota.
|
|
28
|
+
|
|
29
|
+
**Outputs** (full mode):
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
{
|
|
33
|
+
kind: "result",
|
|
34
|
+
qualify_id: "<UUIDv4>" | null,
|
|
35
|
+
import_ids: [...],
|
|
36
|
+
imported: [{ leadId, domain?, name, rowId? }],
|
|
37
|
+
not_imported: [...],
|
|
38
|
+
qualified: [{ lead_id, qualifications: [{ question, score, response, computed_at }], qualification_summary, signals_count, ... }],
|
|
39
|
+
still_running: [{ lead_id }],
|
|
40
|
+
failed: [...],
|
|
41
|
+
quota_exceeded: bool,
|
|
42
|
+
skipped_already_qualified: [...],
|
|
43
|
+
reused?: bool, seconds_since_original?: number,
|
|
44
|
+
cancelled?: bool, budget_exhausted?: bool,
|
|
45
|
+
region, _meta
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`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`).
|
|
50
|
+
|
|
51
|
+
### `leadbay_qualify_status` — new resumable retrieval
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
leadbay_qualify_status({ qualify_id })
|
|
55
|
+
→ { qualify_id, status, lead_ids, qualified, still_running, ... }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Refreshes the per-lead state (`/web_fetch` + `/ai_agent_responses`) at call time. No backend mutation. Survives MCP restart.
|
|
59
|
+
|
|
60
|
+
### `leadbay_list_mappable_fields` — new discovery
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
leadbay_list_mappable_fields()
|
|
64
|
+
→ { standard_fields: [{name, description, mapping_value}], custom_fields: [{id, name, type, description, mapping_value}], region, _meta }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
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"`).
|
|
68
|
+
|
|
69
|
+
### `leadbay_import_leads` 0.3.0 — `mappings.custom_fields` shorthand
|
|
70
|
+
|
|
71
|
+
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).
|
|
72
|
+
|
|
73
|
+
### Bulk store schema widening
|
|
74
|
+
|
|
75
|
+
`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.
|
|
76
|
+
|
|
77
|
+
### `not_in_lens` partition — terminate the silent infinite-poll
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
81
|
+
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."
|
|
82
|
+
|
|
83
|
+
`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.
|
|
84
|
+
|
|
85
|
+
### Aligned with backend `/imports/{id}/leads` (PR #1801)
|
|
86
|
+
|
|
87
|
+
`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).
|
|
88
|
+
|
|
89
|
+
Verified live: import 970bd47a-… (apple.com matched, salesforce.com uncrawled) → /leads returned `{lead_ids: [0a788a89-...]}` cleanly; qualify_id 08ad4555-… retrieved end-to-end.
|
|
90
|
+
|
|
91
|
+
### Monitor-membership disclosure
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
### Stable qualification ordering + human_summary
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
3
101
|
## 0.4.0 — 2026-05-04
|
|
4
102
|
|
|
5
103
|
### `leadbay_import_leads` 0.2.0 — custom field mapping
|
package/MIGRATION.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
+
# Migration: leadbay-mcp 0.5.x → 0.6.0 (UNRELEASED)
|
|
2
|
+
|
|
3
|
+
The "MCP best-practice" upgrade. Five behaviour additions and ONE
|
|
4
|
+
softer-than-it-looks behavior callout. All changes are MCP-spec
|
|
5
|
+
aligned (2025-11-25).
|
|
6
|
+
|
|
7
|
+
## 1. Tool annotations on every tool
|
|
8
|
+
|
|
9
|
+
Every tool now declares `annotations: { readOnlyHint, destructiveHint,
|
|
10
|
+
idempotentHint, openWorldHint, title }` in the `tools/list` payload.
|
|
11
|
+
Capable clients (Claude Desktop, Cursor) use these hints to decide
|
|
12
|
+
whether to auto-approve a call or prompt for confirmation. No agent-
|
|
13
|
+
side change required.
|
|
14
|
+
|
|
15
|
+
## 2. `additionalProperties: false` on every tool's inputSchema
|
|
16
|
+
|
|
17
|
+
**Behavior callout**: any client that was passing extra unrecognized
|
|
18
|
+
fields will now get a schema rejection. This was a deliberate
|
|
19
|
+
hardening — extra fields were never documented, and the rejection
|
|
20
|
+
closes a prompt-injection surface (the eval doc explicitly endorsed
|
|
21
|
+
this). To migrate: drop the unknown field. The error message names
|
|
22
|
+
the rejected field.
|
|
23
|
+
|
|
24
|
+
If you discover an unknown field in your call shape, double-check the
|
|
25
|
+
relevant tool's documented `inputSchema`; if you believe a field
|
|
26
|
+
should exist, file an issue.
|
|
27
|
+
|
|
28
|
+
## 3. `outputSchema` + `structuredContent` on top-5 composites
|
|
29
|
+
|
|
30
|
+
`leadbay_pull_leads`, `leadbay_research_lead`, `leadbay_account_status`,
|
|
31
|
+
`leadbay_bulk_qualify_leads`, `leadbay_report_outreach` now declare
|
|
32
|
+
`outputSchema` AND emit `structuredContent` on success alongside the
|
|
33
|
+
existing text content. Clients that read structuredContent get the
|
|
34
|
+
typed payload without re-parsing through the LLM. Backwards-compat:
|
|
35
|
+
text content is unchanged.
|
|
36
|
+
|
|
37
|
+
## 4. `boost_score` on `research_lead.qualification[]`
|
|
38
|
+
|
|
39
|
+
The qualification entry was previously labelled `score_0_to_10`. The
|
|
40
|
+
actual scale is the discrete `-10|0|10|20` boost (per
|
|
41
|
+
`AiAgentResponse.score`), NOT a 0-10 average. 0.6.0 introduces:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"question": "...",
|
|
46
|
+
"boost_score": 10,
|
|
47
|
+
"score_scale": "-10|0|10|20",
|
|
48
|
+
"score_0_to_10": 10, // DEPRECATED — same value as boost_score
|
|
49
|
+
"response": "...",
|
|
50
|
+
"computed_at": "..."
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`score_0_to_10` is kept as a deprecated alias for one minor version
|
|
55
|
+
and removed in 0.7.0. Switch to `boost_score` now.
|
|
56
|
+
|
|
57
|
+
## 5. New spec primitives
|
|
58
|
+
|
|
59
|
+
The server now advertises and serves:
|
|
60
|
+
|
|
61
|
+
- **`prompts/*`** — 5 canned slash-commands (`leadbay_daily_check_in`,
|
|
62
|
+
`leadbay_research_a_domain`, `leadbay_refine_audience`,
|
|
63
|
+
`leadbay_log_outreach`, `leadbay_qualify_top_n`). Use these from
|
|
64
|
+
the client UI; agents that read prompts compose workflows
|
|
65
|
+
automatically.
|
|
66
|
+
- **`resources/*`** — three URI schemes addressable by client:
|
|
67
|
+
`lead://{uuid}/profile`, `lens://{id}/definition`,
|
|
68
|
+
`org://taste-profile`. Cache-friendly for capable clients.
|
|
69
|
+
- **`notifications/progress`** — long-running composites (today:
|
|
70
|
+
`leadbay_bulk_qualify_leads`) stream per-lead progress updates
|
|
71
|
+
when the client opts in via `_meta.progressToken`.
|
|
72
|
+
- **`notifications/cancelled`** → `ToolContext.signal` — clients
|
|
73
|
+
that send cancellation now actually stop in-flight work in
|
|
74
|
+
composites that observe `ctx.signal` (`leadbay_import_and_qualify`,
|
|
75
|
+
`leadbay_import_leads`, etc.).
|
|
76
|
+
|
|
77
|
+
Clients without these capabilities negotiate away — backwards-compat
|
|
78
|
+
is preserved.
|
|
79
|
+
|
|
80
|
+
## 6. Pagination metadata: `has_more` + `next_page`
|
|
81
|
+
|
|
82
|
+
`leadbay_pull_leads` and `leadbay_discover_leads` payloads now
|
|
83
|
+
include `has_more: boolean` and `next_page: number | null` next to
|
|
84
|
+
the existing `pagination` object. Spec-aligned; agents no longer
|
|
85
|
+
need to compute `page < pages - 1`.
|
|
86
|
+
|
|
87
|
+
## 7. Truncation steering on `research_lead`
|
|
88
|
+
|
|
89
|
+
When the response would exceed ~25k characters, the composite now
|
|
90
|
+
emits `truncated: true` plus `truncation_hint: "Pass concise:true to
|
|
91
|
+
filter to hot signals only."`. Load-bearing fields (qualification,
|
|
92
|
+
firmographics, contacts) are never trimmed; only signals.entries get
|
|
93
|
+
trimmed first.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
1
97
|
# Migration: leadbay-mcp 0.2.x → 0.3.0
|
|
2
98
|
|
|
3
99
|
This release fixes [product#3504](https://github.com/leadbay/product/issues/3504): the default-installed MCP server's system prompt told the agent to call tools that the server didn't actually expose. Three behavior changes you need to know about.
|
|
@@ -210,3 +306,143 @@ Default is read-only (exposeWrite=false, exposeGranular=false).
|
|
|
210
306
|
to detect when `enrichment.done` flips.
|
|
211
307
|
- A `DELETE /lenses/{draftId}` endpoint — not testable in our tenant; treated
|
|
212
308
|
as best-effort with `orphan_draft_id` surfaced on cleanup failure.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
# Migration: leadbay-mcp 0.4.x → 0.5.0
|
|
313
|
+
|
|
314
|
+
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.
|
|
315
|
+
|
|
316
|
+
## What's new
|
|
317
|
+
|
|
318
|
+
| Tool | Purpose |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `leadbay_list_mappable_fields` | Discovery: lists every CRM field (standard + this org's custom fields) the agent can target in `mappings.fields`. |
|
|
321
|
+
| `leadbay_import_and_qualify` | The mission verb: imports leads + triggers AI qualification + returns per-question answers in one call. Resumable via `qualify_id`. |
|
|
322
|
+
| `leadbay_qualify_status` | Retrieves a previously-launched qualification by `qualify_id` (handle persists 30d). |
|
|
323
|
+
| `leadbay_import_leads` 0.3.0 | Adds `mappings.custom_fields` ergonomic shorthand alongside the raw `mappings.fields[col] = "CUSTOM.<id>"` wire format. |
|
|
324
|
+
|
|
325
|
+
## Worked example: discover → import → qualify
|
|
326
|
+
|
|
327
|
+
```jsonc
|
|
328
|
+
// 1. Discover what fields are mappable on this org.
|
|
329
|
+
leadbay_list_mappable_fields()
|
|
330
|
+
// → {
|
|
331
|
+
// "standard_fields": [{name:"LEAD_NAME", description:"...", mapping_value:"LEAD_NAME"}, ...],
|
|
332
|
+
// "custom_fields": [{id:"8", name:"priority_test", type:"TEXT", mapping_value:"CUSTOM.8"}, ...],
|
|
333
|
+
// "_meta": {region: "us", endpoint: "GET /crm/custom_fields", latency_ms: 78}
|
|
334
|
+
// }
|
|
335
|
+
|
|
336
|
+
// 2. (Optional) preview the wizard's mapping suggestions for a sample.
|
|
337
|
+
leadbay_import_and_qualify({
|
|
338
|
+
records: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
339
|
+
dry_run: "preview"
|
|
340
|
+
})
|
|
341
|
+
// → {
|
|
342
|
+
// kind: "preview",
|
|
343
|
+
// mapping_hints: [{column: "Site", suggested_field: "LEAD_WEBSITE", ai_confidence: 95}],
|
|
344
|
+
// custom_field_candidates: [{column: "Priority", candidates: [{id:"8", mapping_value:"CUSTOM.8", reason:"fuzzy_substring_match"}]}],
|
|
345
|
+
// sample_rows: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
346
|
+
// import_id: "<uuid>"
|
|
347
|
+
// }
|
|
348
|
+
|
|
349
|
+
// 3. Run the full flow with the chosen mapping.
|
|
350
|
+
leadbay_import_and_qualify({
|
|
351
|
+
records: [{Brand: "Apple", Site: "apple.com", Priority: "high"}],
|
|
352
|
+
mappings: {
|
|
353
|
+
fields: {Brand: "LEAD_NAME", Site: "LEAD_WEBSITE"},
|
|
354
|
+
custom_fields: {Priority: "priority_test"} // resolved against /crm/custom_fields
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
// → {
|
|
358
|
+
// kind: "result",
|
|
359
|
+
// qualify_id: "<uuidv4>", // resumable handle
|
|
360
|
+
// import_ids: ["<uuid>"],
|
|
361
|
+
// imported: [{leadId: "...", domain: "apple.com", name: "APPLE", rowId: "..."}],
|
|
362
|
+
// not_imported: [],
|
|
363
|
+
// qualified: [{
|
|
364
|
+
// lead_id: "...",
|
|
365
|
+
// qualifications: [{question: "Is the company...", score: 20, response: "yes...", computed_at: "..."}, ...],
|
|
366
|
+
// qualification_summary: {answered: 3, total: 3, avg_qualification_boost: 13.3},
|
|
367
|
+
// human_summary: "answered 3/3 — strong positive on 'Is the company...', positive on '...'",
|
|
368
|
+
// signals_count: 12
|
|
369
|
+
// }],
|
|
370
|
+
// still_running: [],
|
|
371
|
+
// chosen_budgets: {strategy: "small", total_budget_ms: 180000, wall_clock_estimate_ms: 60000, ...}
|
|
372
|
+
// }
|
|
373
|
+
|
|
374
|
+
// 4. If still_running was non-empty, retrieve later via qualify_id.
|
|
375
|
+
leadbay_qualify_status({qualify_id: "<uuid>"})
|
|
376
|
+
// → same shape, refreshed against backend at call time. failed[] populated
|
|
377
|
+
// for any leads that 404'd between launch and status-check.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Custom fields wire format
|
|
381
|
+
|
|
382
|
+
The backend serializer accepts `"CUSTOM.<id>"` as a value in the `mappings.fields` map. 0.3.0 of `leadbay_import_leads` accepts both:
|
|
383
|
+
|
|
384
|
+
```jsonc
|
|
385
|
+
// raw wire form (passes through unchanged):
|
|
386
|
+
mappings.fields = {col: "CUSTOM.8"}
|
|
387
|
+
|
|
388
|
+
// ergonomic shorthand (resolved against /crm/custom_fields):
|
|
389
|
+
mappings.custom_fields = {col: 8} // numeric id
|
|
390
|
+
mappings.custom_fields = {col: "8"} // string-shaped id
|
|
391
|
+
mappings.custom_fields = {col: "priority_test"} // exact name (case-insensitive fallback)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
New error codes (all surfaced with hint pointing at next action):
|
|
395
|
+
`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`.
|
|
396
|
+
|
|
397
|
+
## Bulk store schema widening
|
|
398
|
+
|
|
399
|
+
`~/.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.
|
|
400
|
+
|
|
401
|
+
## Adaptive budgets
|
|
402
|
+
|
|
403
|
+
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:
|
|
404
|
+
|
|
405
|
+
| Input size | Strategy | total_budget_ms | per_lead_budget_ms |
|
|
406
|
+
|---|---|---|---|
|
|
407
|
+
| ≤ 5 leads | small | 3 min | 60s |
|
|
408
|
+
| 6 – 20 leads | default | 10 min | 90s |
|
|
409
|
+
| > 20 leads | large | 25 min | 120s |
|
|
410
|
+
|
|
411
|
+
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.
|
|
412
|
+
|
|
413
|
+
## See also
|
|
414
|
+
|
|
415
|
+
- [CHANGELOG.md 0.5.0 entry](./CHANGELOG.md) — full release notes.
|
|
416
|
+
|
|
417
|
+
## Error-code reference (0.5.0)
|
|
418
|
+
|
|
419
|
+
Quick-reference for new error codes shipped in 0.5.0:
|
|
420
|
+
|
|
421
|
+
| Code | When | Next action |
|
|
422
|
+
|---|---|---|
|
|
423
|
+
| `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[]`. |
|
|
424
|
+
| `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. |
|
|
425
|
+
| `IMPORT_CUSTOM_FIELD_NAME_AMBIGUOUS` | Two custom fields share the name (case-insensitive) the caller used. | Pass the numeric id instead. |
|
|
426
|
+
| `IMPORT_MAPPING_DUPLICATE_CUSTOM` | Same column appears in both `mappings.fields` and `mappings.custom_fields`. | Pick one of the two maps; remove the duplicate. |
|
|
427
|
+
| `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. |
|
|
428
|
+
| `IMPORT_CUSTOM_FIELD_CATALOG_REQUIRED` | Internal: catalog couldn't be fetched. | Retry; or use raw `CUSTOM.<id>` in `mappings.fields` instead of shorthand. |
|
|
429
|
+
| `BULK_TRACKER_UNAVAILABLE` | MCP server has no BulkTracker (qualify_id persistence). | Restart with `LEADBAY_BULK_STORE_ALLOW_MEMORY=1` or upgrade. |
|
|
430
|
+
| `BULK_INVALID_ID` | `qualify_id` is not a valid UUIDv4. | Pass the id returned by the prior `leadbay_import_and_qualify` call verbatim. |
|
|
431
|
+
| `BULK_NOT_FOUND` | Handle expired (>30d TTL) or never existed. | Re-launch via `leadbay_import_and_qualify`. |
|
|
432
|
+
| `BULK_PENDING` | Launch in flight or crashed mid-launch. | Retry in a few seconds; if persists >60s, relaunch. |
|
|
433
|
+
| `BULK_LAUNCH_FAILED` | The original launch failed permanently. | Re-launch. |
|
|
434
|
+
| `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`. |
|
|
435
|
+
| `IMPORT_PREVIEW_NO_UPLOAD` | Preview mode hit all-malformed input; nothing was uploaded. | Check that at least one record/domain is well-formed. |
|
|
436
|
+
|
|
437
|
+
## Per-tool prereqs (0.5.0)
|
|
438
|
+
|
|
439
|
+
Quick scan of what each new tool requires before invocation:
|
|
440
|
+
|
|
441
|
+
| Tool | Admin role | Active billing | LEADBAY_MCP_WRITE |
|
|
442
|
+
|---|---|---|---|
|
|
443
|
+
| `leadbay_list_mappable_fields` | no | no | no (read-only) |
|
|
444
|
+
| `leadbay_qualify_status` | no | no | no (read-only) |
|
|
445
|
+
| `leadbay_import_and_qualify` | yes | yes | yes (`=1`, default ON in 0.3.0+) |
|
|
446
|
+
| `leadbay_import_leads` (0.3.0) | yes | yes | yes |
|
|
447
|
+
|
|
448
|
+
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.
|