@leadbay/mcp 0.34.1 → 0.35.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,142 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.35.1 — 2026-09-08
4
+
5
+ Release-pipeline only. No source change, no behaviour change: `packages/core`
6
+ and `packages/mcp` are byte-identical to 0.35.0. This version exists because
7
+ the MCP Registry can only be reached by a tag push, and the two `release.yml`
8
+ bugs fixed in #208 had to ship before a tag could carry it.
9
+
10
+ **What was broken.** 0.35.0 published to npm and then the workflow died
11
+ before creating the GitHub Release: the release-notes pipeline ended in
12
+ `head -60` under `set -euo pipefail`, the 0.35.0 changelog section is 115
13
+ lines, so `head` closed the pipe, GNU sed failed its final flush and the step
14
+ exited 4. No Release meant no `.dxt`/`.mcpb`, and `Publish to MCP Registry`
15
+ was skipped for `needs:`-ing the job that failed. Separately, on 0.34.1 the
16
+ registry job did run and the registry refused it — it re-checks npm itself and
17
+ got a 404 for a version published seconds earlier. Between the two, the
18
+ directory listing sat at 0.34.0 and the desktop bundle at 0.34.x while npm and
19
+ the hosted server were on 0.35.0.
20
+
21
+ 0.35.0's Release and its four assets were created by hand once #208 landed;
22
+ this release is what carries the fix through the pipeline itself.
23
+
24
+ ## 0.35.0 — 2026-09-02
25
+
26
+ The MCP no longer keeps any record of the jobs it starts (product#4005,
27
+ product#4039). Every launch already receives a durable id from the Leadbay
28
+ backend, and the status tools now take that id.
29
+
30
+ **Why.** Three tools — `leadbay_bulk_qualify_leads`, `leadbay_import_leads`,
31
+ `leadbay_import_and_qualify` — failed on the hosted server with
32
+ `BULK_TRACKER_UNAVAILABLE` (9 customers, 53 times; `import_and_qualify` never
33
+ once worked there). The file they depended on (`~/.leadbay/bulks.json`) was only
34
+ ever created by the stdio entrypoint. Its own header said it existed *"while the
35
+ Leadbay backend doesn't yet issue a real bulk_id"*; the backend now does.
36
+
37
+ **Breaking.** `bulk_id`, `handle_id` and `qualify_id` are gone as inputs, with
38
+ no transition period — they were valid for minutes on a local install and never
39
+ worked on the server.
40
+
41
+ | Tool | Now returns / takes |
42
+ |---|---|
43
+ | `leadbay_enrich_titles`, `leadbay_bulk_qualify_leads` | return the backend's `notification_id` + `lead_ids` (+ `lens_id`) |
44
+ | `leadbay_import_leads`, `leadbay_import_and_qualify` | return the backend's `importIds` / `import_ids` |
45
+ | `leadbay_bulk_enrich_status`, `leadbay_qualify_status` | take `notification_id` and/or `lead_ids` (+ `lens_id`) |
46
+ | `leadbay_import_status` | takes `importIds`, which it already accepted |
47
+
48
+ `packages/core/src/jobs/bulk-store.ts` and its tests are deleted, along with
49
+ `LEADBAY_BULK_STORE_PATH`, `LEADBAY_BULK_STORE_ALLOW_MEMORY` and the `BULK_*`
50
+ error codes. What survives is a five-minute in-memory guard so an assistant
51
+ retry does not charge twice (`jobs/launch-guard.ts`); it claims the launch
52
+ BEFORE firing it, so a concurrent identical call is told `launch_in_flight`
53
+ rather than handed an empty ticket (product#4039).
54
+
55
+ Verified live on staging through fresh server processes: a job started in one
56
+ process resolves by its backend id from another, including after the
57
+ notification was archived with `leadbay_acknowledge_notification`. The
58
+ notification is listable within a second of the launch ack.
59
+
60
+ Found in that run and fixed in the same release:
61
+
62
+ - **A notification of the wrong kind was answered as if it were the right
63
+ one.** A qualification's notification fed to `leadbay_bulk_enrich_status`
64
+ read as a finished enrichment; an enrichment's fed to `leadbay_qualify_status`
65
+ read as a running qualification. Both tools now check the notification's kind
66
+ (`ENRICH_JOB_WRONG_KIND` / `QUALIFY_JOB_WRONG_KIND`) and name the right tool.
67
+ - **Enrichment notifications do not always carry counters.** On staging the
68
+ finished row had `in_progress` and a title but no `bulk_progress`, so the
69
+ `notification_id`-only call said "not a bulk job". It now says
70
+ `ENRICH_JOB_NO_COUNTERS` with the backend's running/finished flag and asks for
71
+ the `lead_ids` the launch returned, which always answer.
72
+ - `leadbay_qualify_status` no longer throws not-found when the caller also
73
+ passed `lead_ids` + `lens_id`; it answers per lead, as its own hint says to.
74
+ - `leadbay_enrich_titles` declared `notification_id` / `lead_ids` / `reused`
75
+ as inputs instead of outputs; `leadbay_bulk_enrich_status` required
76
+ `notification_id` in its output when the `lead_ids`-only path omits it.
77
+ - The artifact SDK's `lb.enrichment()` (`@leadbay/components`) still read
78
+ `bulk_id`, so every widget enrichment reported done instantly with no job
79
+ polled. It now carries `notification_id` + `lead_ids`.
80
+ - The live eval harness and two live smoke suites still imported the deleted
81
+ store; tool copy in eight places still told the assistant to poll with a
82
+ `bulk_id` / `qualify_id`.
83
+
84
+ **The assistant is now told, in the tool descriptions, that a launched job
85
+ cannot be stopped.** Leadbay has no cancel: enrichment, qualification and
86
+ imports have a launch route and a read route and nothing else
87
+ (`LeadsRoutes.kt`, `ImportsRoutes.kt`, `/1.6` specs). The retired store made
88
+ this reachable in the wrong direction — a host cancellation flipped a local
89
+ record to `cancelled`, and the status tools answered *"no further work is in
90
+ flight … relaunch"*, which spends the quota a second time on rows Leadbay is
91
+ still processing. That text is gone with the store; the true rule now ships on
92
+ the surface the assistant actually reads. Three variants ship, because handing a tool
93
+ the wrong one is its own bug.
94
+
95
+ | Tools | Snippet | Why |
96
+ |---|---|---|
97
+ | `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 |
98
+ | `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 |
99
+ | `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 |
100
+
101
+ Those same three tools shipped `idempotentHint: true`, which this repo defines as
102
+ *"calling the same tool twice with the same arguments is safe and produces the
103
+ same observable outcome (no double-write side-effect)"*. That is the
104
+ machine-readable half of the claim the prose now contradicts, and a host may act
105
+ on it automatically. They are `idempotentHint: false` now. This is the one
106
+ behaviour change in an otherwise text-only release;
107
+ `packages/mcp/test/annotations.test.ts` carried the old value in its expectation
108
+ table and is corrected with the code.
109
+
110
+ The six prompts that already carry the long-running-tool rules carry the guarded
111
+ variant too. `leadbay_import_leads` sits 157 chars under the
112
+ 17,000 cap and states the cancel case in one sentence instead; its own SLOW
113
+ BACKEND paragraph already carries the rest.
114
+
115
+ The rule applies to a launched or running result only, never to a discovery,
116
+ preview or `dry_run` result, and it branches because recovery does. With a
117
+ handle: poll it, and do not launch the work that handle covers again. With a
118
+ handle and a subset the result says never started — `failed[]` entries with
119
+ `error:"not_queued"`, or a `rows_pending_upload` count — re-run that subset
120
+ only. With no result at all: read `leadbay_account_status` first, because the
121
+ double-launch guard is in-memory, five minutes and per process, and
122
+ `bulk-qualify-leads.ts` drops its claim when the launch POST throws even if the
123
+ backend accepted it. The rule says so rather than promising a recovery the guard
124
+ cannot deliver.
125
+
126
+ `cancelled: true` on the two import results is described the same way instead of
127
+ as *"ctx.signal aborted mid-flight"*, each names the flag a timeout uses instead,
128
+ and the import one says the counts can stop moving on a chunk cancelled before
129
+ its mappings were committed (product#4064).
130
+ `packages/mcp/test/audit/launched-work-not-cancellable.test.ts` holds both halves
131
+ — every branch is present on the guarded tools, the no-guard warning on the
132
+ unguarded ones with the guarded advice asserted absent, matched on collapsed
133
+ whitespace so a rewrap cannot silently pass it, and no generated description or
134
+ prompt can tell the assistant to relaunch after a cancel again.
135
+ `packages/mcp/test/launched-work-rule-over-the-wire-e2e.test.ts` proves the same
136
+ thing where it counts, driving the real Hono app on a real socket through
137
+ `StreamableHTTPServerTransport` and reading `tools/list` — the catalogue objects
138
+ the audit reads are not what a chat host receives.
139
+
3
140
  ## 0.34.1 — 2026-09-07
4
141
 
5
142
  `leadbay_set_lead_status` threw `TypeError: (params.lead_ids ?? []).filter is
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, the bulk-store entry is marked `cancelled`, and the next `bulk_enrich_status` returns `BULK_CANCELLED` so the agent stops polling.
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",