@leadbay/mcp 0.34.0 → 0.35.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 +154 -0
- package/MIGRATION.md +51 -0
- package/README.md +1 -1
- package/dist/bin.js +1236 -1559
- package/dist/http-server.js +1102 -817
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,159 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.35.0 — 2026-09-02
|
|
4
|
+
|
|
5
|
+
The MCP no longer keeps any record of the jobs it starts (product#4005,
|
|
6
|
+
product#4039). Every launch already receives a durable id from the Leadbay
|
|
7
|
+
backend, and the status tools now take that id.
|
|
8
|
+
|
|
9
|
+
**Why.** Three tools — `leadbay_bulk_qualify_leads`, `leadbay_import_leads`,
|
|
10
|
+
`leadbay_import_and_qualify` — failed on the hosted server with
|
|
11
|
+
`BULK_TRACKER_UNAVAILABLE` (9 customers, 53 times; `import_and_qualify` never
|
|
12
|
+
once worked there). The file they depended on (`~/.leadbay/bulks.json`) was only
|
|
13
|
+
ever created by the stdio entrypoint. Its own header said it existed *"while the
|
|
14
|
+
Leadbay backend doesn't yet issue a real bulk_id"*; the backend now does.
|
|
15
|
+
|
|
16
|
+
**Breaking.** `bulk_id`, `handle_id` and `qualify_id` are gone as inputs, with
|
|
17
|
+
no transition period — they were valid for minutes on a local install and never
|
|
18
|
+
worked on the server.
|
|
19
|
+
|
|
20
|
+
| Tool | Now returns / takes |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `leadbay_enrich_titles`, `leadbay_bulk_qualify_leads` | return the backend's `notification_id` + `lead_ids` (+ `lens_id`) |
|
|
23
|
+
| `leadbay_import_leads`, `leadbay_import_and_qualify` | return the backend's `importIds` / `import_ids` |
|
|
24
|
+
| `leadbay_bulk_enrich_status`, `leadbay_qualify_status` | take `notification_id` and/or `lead_ids` (+ `lens_id`) |
|
|
25
|
+
| `leadbay_import_status` | takes `importIds`, which it already accepted |
|
|
26
|
+
|
|
27
|
+
`packages/core/src/jobs/bulk-store.ts` and its tests are deleted, along with
|
|
28
|
+
`LEADBAY_BULK_STORE_PATH`, `LEADBAY_BULK_STORE_ALLOW_MEMORY` and the `BULK_*`
|
|
29
|
+
error codes. What survives is a five-minute in-memory guard so an assistant
|
|
30
|
+
retry does not charge twice (`jobs/launch-guard.ts`); it claims the launch
|
|
31
|
+
BEFORE firing it, so a concurrent identical call is told `launch_in_flight`
|
|
32
|
+
rather than handed an empty ticket (product#4039).
|
|
33
|
+
|
|
34
|
+
Verified live on staging through fresh server processes: a job started in one
|
|
35
|
+
process resolves by its backend id from another, including after the
|
|
36
|
+
notification was archived with `leadbay_acknowledge_notification`. The
|
|
37
|
+
notification is listable within a second of the launch ack.
|
|
38
|
+
|
|
39
|
+
Found in that run and fixed in the same release:
|
|
40
|
+
|
|
41
|
+
- **A notification of the wrong kind was answered as if it were the right
|
|
42
|
+
one.** A qualification's notification fed to `leadbay_bulk_enrich_status`
|
|
43
|
+
read as a finished enrichment; an enrichment's fed to `leadbay_qualify_status`
|
|
44
|
+
read as a running qualification. Both tools now check the notification's kind
|
|
45
|
+
(`ENRICH_JOB_WRONG_KIND` / `QUALIFY_JOB_WRONG_KIND`) and name the right tool.
|
|
46
|
+
- **Enrichment notifications do not always carry counters.** On staging the
|
|
47
|
+
finished row had `in_progress` and a title but no `bulk_progress`, so the
|
|
48
|
+
`notification_id`-only call said "not a bulk job". It now says
|
|
49
|
+
`ENRICH_JOB_NO_COUNTERS` with the backend's running/finished flag and asks for
|
|
50
|
+
the `lead_ids` the launch returned, which always answer.
|
|
51
|
+
- `leadbay_qualify_status` no longer throws not-found when the caller also
|
|
52
|
+
passed `lead_ids` + `lens_id`; it answers per lead, as its own hint says to.
|
|
53
|
+
- `leadbay_enrich_titles` declared `notification_id` / `lead_ids` / `reused`
|
|
54
|
+
as inputs instead of outputs; `leadbay_bulk_enrich_status` required
|
|
55
|
+
`notification_id` in its output when the `lead_ids`-only path omits it.
|
|
56
|
+
- The artifact SDK's `lb.enrichment()` (`@leadbay/components`) still read
|
|
57
|
+
`bulk_id`, so every widget enrichment reported done instantly with no job
|
|
58
|
+
polled. It now carries `notification_id` + `lead_ids`.
|
|
59
|
+
- The live eval harness and two live smoke suites still imported the deleted
|
|
60
|
+
store; tool copy in eight places still told the assistant to poll with a
|
|
61
|
+
`bulk_id` / `qualify_id`.
|
|
62
|
+
|
|
63
|
+
**The assistant is now told, in the tool descriptions, that a launched job
|
|
64
|
+
cannot be stopped.** Leadbay has no cancel: enrichment, qualification and
|
|
65
|
+
imports have a launch route and a read route and nothing else
|
|
66
|
+
(`LeadsRoutes.kt`, `ImportsRoutes.kt`, `/1.6` specs). The retired store made
|
|
67
|
+
this reachable in the wrong direction — a host cancellation flipped a local
|
|
68
|
+
record to `cancelled`, and the status tools answered *"no further work is in
|
|
69
|
+
flight … relaunch"*, which spends the quota a second time on rows Leadbay is
|
|
70
|
+
still processing. That text is gone with the store; the true rule now ships on
|
|
71
|
+
the surface the assistant actually reads. Three variants ship, because handing a tool
|
|
72
|
+
the wrong one is its own bug.
|
|
73
|
+
|
|
74
|
+
| Tools | Snippet | Why |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `enrich_titles`, `bulk_qualify_leads`, `import_and_qualify` | `heuristics/launched-work-cannot-be-stopped.md` | they call `beginLaunch`, so a re-call inside the window returns the ids the first call produced — best-effort, never a guarantee |
|
|
77
|
+
| `bulk_enrich_status`, `qualify_status`, `import_status` | `heuristics/launched-work-poll-only.md` | read-only. Re-calling launches nothing, and the launcher's "check your quota before retrying" text would stall the poll loop these tools exist for |
|
|
78
|
+
| `qualify_lead`, `enrich_contacts`, `launch_bulk_enrichment` | `heuristics/unguarded-launch.md` | they POST directly with no guard, so the guarded re-call advice would buy a second paid launch. `enrich_contacts` is on the hosted route, so this is not only an advanced-mode concern |
|
|
79
|
+
|
|
80
|
+
Those same three tools shipped `idempotentHint: true`, which this repo defines as
|
|
81
|
+
*"calling the same tool twice with the same arguments is safe and produces the
|
|
82
|
+
same observable outcome (no double-write side-effect)"*. That is the
|
|
83
|
+
machine-readable half of the claim the prose now contradicts, and a host may act
|
|
84
|
+
on it automatically. They are `idempotentHint: false` now. This is the one
|
|
85
|
+
behaviour change in an otherwise text-only release;
|
|
86
|
+
`packages/mcp/test/annotations.test.ts` carried the old value in its expectation
|
|
87
|
+
table and is corrected with the code.
|
|
88
|
+
|
|
89
|
+
The six prompts that already carry the long-running-tool rules carry the guarded
|
|
90
|
+
variant too. `leadbay_import_leads` sits 157 chars under the
|
|
91
|
+
17,000 cap and states the cancel case in one sentence instead; its own SLOW
|
|
92
|
+
BACKEND paragraph already carries the rest.
|
|
93
|
+
|
|
94
|
+
The rule applies to a launched or running result only, never to a discovery,
|
|
95
|
+
preview or `dry_run` result, and it branches because recovery does. With a
|
|
96
|
+
handle: poll it, and do not launch the work that handle covers again. With a
|
|
97
|
+
handle and a subset the result says never started — `failed[]` entries with
|
|
98
|
+
`error:"not_queued"`, or a `rows_pending_upload` count — re-run that subset
|
|
99
|
+
only. With no result at all: read `leadbay_account_status` first, because the
|
|
100
|
+
double-launch guard is in-memory, five minutes and per process, and
|
|
101
|
+
`bulk-qualify-leads.ts` drops its claim when the launch POST throws even if the
|
|
102
|
+
backend accepted it. The rule says so rather than promising a recovery the guard
|
|
103
|
+
cannot deliver.
|
|
104
|
+
|
|
105
|
+
`cancelled: true` on the two import results is described the same way instead of
|
|
106
|
+
as *"ctx.signal aborted mid-flight"*, each names the flag a timeout uses instead,
|
|
107
|
+
and the import one says the counts can stop moving on a chunk cancelled before
|
|
108
|
+
its mappings were committed (product#4064).
|
|
109
|
+
`packages/mcp/test/audit/launched-work-not-cancellable.test.ts` holds both halves
|
|
110
|
+
— every branch is present on the guarded tools, the no-guard warning on the
|
|
111
|
+
unguarded ones with the guarded advice asserted absent, matched on collapsed
|
|
112
|
+
whitespace so a rewrap cannot silently pass it, and no generated description or
|
|
113
|
+
prompt can tell the assistant to relaunch after a cancel again.
|
|
114
|
+
`packages/mcp/test/launched-work-rule-over-the-wire-e2e.test.ts` proves the same
|
|
115
|
+
thing where it counts, driving the real Hono app on a real socket through
|
|
116
|
+
`StreamableHTTPServerTransport` and reading `tools/list` — the catalogue objects
|
|
117
|
+
the audit reads are not what a chat host receives.
|
|
118
|
+
|
|
119
|
+
## 0.34.1 — 2026-09-07
|
|
120
|
+
|
|
121
|
+
`leadbay_set_lead_status` threw `TypeError: (params.lead_ids ?? []).filter is
|
|
122
|
+
not a function` five times in 29 s on the hosted route (product#4079, Sentry
|
|
123
|
+
MCP-3T): a scheduled agent passed `lead_ids` as something other than an array.
|
|
124
|
+
The same class hit `leadbay_new_lens` on 2026-08-04 (`texts.filter is not a
|
|
125
|
+
function`, `locations` as a string, MCP-3B). Both times the agent retried the
|
|
126
|
+
identical call four or five times, because a raw TypeError names no argument.
|
|
127
|
+
|
|
128
|
+
The CallTool handler in `server.ts` never checked `inputSchema` before
|
|
129
|
+
`tool.execute`; its own comment said enforcement was ours, and it only enforced
|
|
130
|
+
`_triggered_by`. 32 tools declare a top-level `array` or `object` parameter and
|
|
131
|
+
none of them defended against a string: four threw, two silently corrupted
|
|
132
|
+
(`adjust_audience` spread `"tech"` into `["t","e","c","h"]`), the rest forwarded
|
|
133
|
+
the string to the backend.
|
|
134
|
+
|
|
135
|
+
One guard now runs before `execute`, after the `LAST_PROMPT_REQUIRED` check
|
|
136
|
+
(`findShapeMismatch`): for each top-level schema property whose `type` is
|
|
137
|
+
`array` or `object`, a present non-null argument of the wrong JSON shape returns
|
|
138
|
+
`BAD_INPUT` naming the field, the expected shape and the received type
|
|
139
|
+
(`lead_ids must be a JSON array (got string)`), through the same envelope branch
|
|
140
|
+
a tool's own `BAD_INPUT` takes. So PostHog sees `ok:false error_code:BAD_INPUT`
|
|
141
|
+
and Sentry groups the event with the tool's existing `BAD_INPUT` issue
|
|
142
|
+
(`source:business`) instead of a `source:unexpected` stack fingerprint.
|
|
143
|
+
|
|
144
|
+
Deliberately not done: no coercion of a bare string into a one-element array (a
|
|
145
|
+
JSON-stringified array would become one bogus id and fail per lead inside
|
|
146
|
+
`failed[]`); no scalar checks (hosts send `"20"` for a number and tools coerce);
|
|
147
|
+
no `required`, `items`, nested or `additionalProperties` validation; no runtime
|
|
148
|
+
dependency. `null` counts as absent. One visible change on an existing tool:
|
|
149
|
+
`leadbay_report_outreach` with `verification` given as a string answered
|
|
150
|
+
`VERIFICATION_REQUIRED`; it now answers `BAD_INPUT … must be a JSON object (got
|
|
151
|
+
string)`. Tools executed directly outside the MCP server (OpenClaw consumes
|
|
152
|
+
`@leadbay/core` without `buildServer`) keep the raw throw.
|
|
153
|
+
|
|
154
|
+
Test: `packages/mcp/test/unit/input-shape-guard.test.ts` drives `tools/call`
|
|
155
|
+
through `buildServer` with the incident shapes.
|
|
156
|
+
|
|
3
157
|
## 0.34.0 — 2026-09-02
|
|
4
158
|
|
|
5
159
|
The OpenAI app directory rejects an app that sells digital goods — "plugins may
|
package/MIGRATION.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
# Migration: leadbay-mcp 0.34.x → 0.35.0
|
|
2
|
+
|
|
3
|
+
The MCP no longer keeps a local job file. Every long-running launch hands back
|
|
4
|
+
the **backend's own id**, so a job started in one process resolves in any other
|
|
5
|
+
— including on the hosted server, which never had the local file.
|
|
6
|
+
|
|
7
|
+
## The three handles are gone
|
|
8
|
+
|
|
9
|
+
| Was | Pass instead | On |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `bulk_id` | `notification_id` (job counters) and/or `lead_ids` (per-lead progress) | `leadbay_bulk_enrich_status` |
|
|
12
|
+
| `qualify_id` | `notification_id` and/or `lead_ids` + `lens_id` | `leadbay_qualify_status` |
|
|
13
|
+
| `handle_id` | `importIds[]` | `leadbay_import_status` |
|
|
14
|
+
|
|
15
|
+
All three are returned by the launch call. Nothing is stored MCP-side, so they
|
|
16
|
+
resolve from a later message, a later conversation, or the next day. A call that
|
|
17
|
+
still passes an old name is answered with an error naming the id to pass, not a
|
|
18
|
+
crash.
|
|
19
|
+
|
|
20
|
+
`leadbay_enrich_titles` renamed its `re_used` output field to `reused`, matching
|
|
21
|
+
`leadbay_bulk_qualify_leads` and `leadbay_import_and_qualify`.
|
|
22
|
+
|
|
23
|
+
`leadbay_import_and_qualify` returns NO qualification `notification_id` — its
|
|
24
|
+
qualify phase runs per-lead. Poll it with the `lead_ids` + `lens_id` it returned;
|
|
25
|
+
its `notification_ids[]` are the FILE-IMPORT ones, polled via
|
|
26
|
+
`leadbay_import_status({importIds})`.
|
|
27
|
+
|
|
28
|
+
## Knowing a job is finished
|
|
29
|
+
|
|
30
|
+
`leadbay_qualify_status.status` is always `"launched"` — it is not a progress
|
|
31
|
+
field. On the `notification_id` path the job is done when `in_progress` is false;
|
|
32
|
+
`still_running[]` is empty on that path from the first poll and must not be read
|
|
33
|
+
as "done". On the `lead_ids` path, done means `still_running[]` is empty.
|
|
34
|
+
|
|
35
|
+
`leadbay_bulk_enrich_status` answers `ENRICH_JOB_NO_COUNTERS` when the enrichment
|
|
36
|
+
notification carries no per-contact counters (the common case) — re-call with the
|
|
37
|
+
`lead_ids` the launch returned.
|
|
38
|
+
|
|
39
|
+
## Errors removed
|
|
40
|
+
|
|
41
|
+
`BULK_TRACKER_UNAVAILABLE`, `BULK_INVALID_ID`, `BULK_NOT_FOUND`, `BULK_PENDING`,
|
|
42
|
+
`BULK_LAUNCH_FAILED`, `BULK_WRONG_KIND`, `BULK_CANCELLED`, `BULK_STORE_UNAVAILABLE`
|
|
43
|
+
no longer exist, and neither does `~/.leadbay/bulks.json` or the
|
|
44
|
+
`LEADBAY_BULK_STORE_ALLOW_MEMORY` escape hatch. The replacements are
|
|
45
|
+
`ENRICH_STATUS_INPUT_REQUIRED`, `ENRICH_JOB_NOT_FOUND`, `ENRICH_JOB_WRONG_KIND`,
|
|
46
|
+
`ENRICH_JOB_NO_COUNTERS`, `QUALIFY_STATUS_INPUT_REQUIRED`, `QUALIFY_JOB_NOT_FOUND`
|
|
47
|
+
and `QUALIFY_JOB_WRONG_KIND`. The 0.5.0 sections below describe the old store and
|
|
48
|
+
are kept as history.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
1
52
|
# Migration: leadbay-mcp 0.5.x → 0.6.0 (UNRELEASED)
|
|
2
53
|
|
|
3
54
|
The "MCP best-practice" upgrade. Five behaviour additions and ONE
|
package/README.md
CHANGED
|
@@ -460,7 +460,7 @@ Then the final `tools/call` response.
|
|
|
460
460
|
|
|
461
461
|
### `notifications/cancelled` — actually cancelling
|
|
462
462
|
|
|
463
|
-
Send the cancellation by id; the server's `ToolContext.signal` aborts the polling loop within ≤2 seconds
|
|
463
|
+
Send the cancellation by id; the server's `ToolContext.signal` aborts the polling loop within ≤2 seconds. The job itself keeps running on the backend — poll `leadbay_bulk_enrich_status` with the `notification_id` later to pick it up.
|
|
464
464
|
|
|
465
465
|
```json
|
|
466
466
|
{ "jsonrpc": "2.0", "method": "notifications/cancelled",
|