Skip to content

Commit 6a54ff7

Browse files
milstanclaude
andauthored
feat(mcp): #3504 — 0.3.0 coverage + login + scope (#20)
* feat(mcp,dxt,leadclaw): #3504 0.3.0 — coverage + login + scope (writes-on default, file-write login, --scope user) Closes leadbay/product#3504 end-to-end. The 0.2.x default install shipped a SERVER_INSTRUCTIONS that referenced tools the server didn't expose, the login command leaked tokens to stdout, and `claude mcp add` registered project-local so a fresh conversation couldn't see Leadbay. All three are fixed in 0.3.0. Reviewed via /autoplan with dual voices (CEO + Eng + DX, Codex + Claude subagent each phase) — 38 findings, 11 mechanical refinements applied, 2 user challenges surfaced at the gate (sub-tier split for risky writes deferred to 0.4.0 once audit-log + undo land; --scope user docs nudge bundled into this release). Coverage — composite write tools default ON * `LEADBAY_MCP_WRITE` defaults to "1" (ON). Composite write tools (bulk_qualify_leads, enrich_titles, refine_prompt, report_outreach, adjust_audience, answer_clarification, import_leads) are exposed by default. Set LEADBAY_MCP_WRITE=0 (or `--no-write` on install) to disable. * SERVER_INSTRUCTIONS is now built dynamically from the actual exposed tool set (buildServerInstructions(exposedNames)). Read-only-mode agents get a shorter prompt that drops the verification mandate and tells the agent to ask the user to enable writes if needed. Tests parameterize the matrix (default | read-only | advanced+writes) instead of asserting one static const. * `parseWriteEnv()` accepts 1/true/yes/on as ON and 0/false/no/off as OFF; unrecognized values default to ON with a one-shot stderr warning. Note: in 0.2.x only `=== "1"` was ON, so `=true|yes|on` are flipped to ON in 0.3.0. Documented in MIGRATION.md. * `--include-write` is now a no-op deprecation; warning prints BEFORE the password prompt so it's visible. Login — never leaks token * `leadbay-mcp login` defaults to writing a 0600 credentials file at the platform-correct path: - $XDG_CONFIG_HOME/leadbay/credentials.json (if set) - ~/Library/Application Support/leadbay/credentials.json (macOS) - %APPDATA%\leadbay\credentials.json (Windows) - ~/.config/leadbay/credentials.json (else) Backward-compat: if 0.2.x's ~/.leadbay-mcp.json already exists, it's used with a deprecation note pointing at the new path (no auto-migration). * `--unsafe-print-token` restores stdout printing for legacy CI flows; `--print-token` still works for one release with a deprecation warning. * `--force` overrides the default refusal to overwrite a credentials file that holds a different account's token/region. * EACCES/EROFS/ENOENT print actionable remediation pointing at --write-config /tmp/... or --unsafe-print-token. Scope — visible from any project * `leadbay-mcp install` now passes `--scope user` to `claude mcp add`. The buildClaudeCodeAddArgs helper is exported and tested. README §1, §2 (Claude Code), §4 (troubleshooting), and §5 (upgrade) all reference the new scope. Tests * Drop SERVER_INSTRUCTIONS const re-export — tests now exercise buildServerInstructions(set) directly. * New unit suites (43 new tests): - parse-write-env.test.ts (17): tri-state parser - login-default.test.ts (8): platform path resolution - install-flags.test.ts (7): argv contract * Existing server.test.ts updated for default-mode flips (20 tests). * All 63 MCP + 145 core + 12 leadclaw tests pass; typecheck + build green. Files * packages/mcp/{src/bin.ts,src/server.ts,README.md,CHANGELOG.md,MIGRATION.md,package.json} * packages/mcp/test/server.test.ts + 3 new unit test files * packages/dxt/manifest.template.json (leadbay_mcp_write defaults true) * packages/leadclaw/src/index.ts (stale comment update) * CHANGELOG.md (root) Backend follow-up filed as leadbay/product#3540 — processing-worker queue hung today (US region); imports + qualify stuck at progress 0.0. Tools correctly time out and return structured errors with importId for resume. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): pre-landing review fixes — Server version 0.3.0, dead code, vacuous test, partial-exposure tests - server.ts:170 — Server({ version }) bumped 0.2.0 → 0.3.0 to match package.json (was misreporting in agent diagnostics). - bin.ts: dead `void fresh;` lines removed; legacy-path branch now calls a new `computeFreshDefaultPath()` helper instead of duplicating resolveDefaultCredentialsPath's platform routing inline. - server.test.ts:345 — replaced the vacuous "buildServer wires dynamic instructions" test (every assertion was wrapped in `if (typeof instructions === 'string')` which silently passed when getInstructions was undefined). Now asserts against `Server._instructions` (the SDK 1.29.0 internal accessor) for both read-only AND default-writes-on modes — fails loudly if the wiring breaks. - New test/unit/build-instructions-partial.test.ts (5 tests) — covers buildServerInstructions partial-exposure branches (only bulk_qualify_leads exposed, only enrich_titles exposed, neither, partial composite list, report_outreach absent). Surfaced by the testing specialist; fills a real gap between FULL_EXPOSURE and READ_ONLY matrix coverage. 69/69 tests pass, typecheck + build green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): adversarial review fixes — re-login UX, atomic write, shell quoting, identity-based collision Adversarial dual voices (Claude subagent + Codex) converged on a real critical bug and three correctness issues. All fixed. - **Critical: re-login on same account always failed.** loginAt() mints a fresh token on every call; the old collision check compared tokens by equality and always tripped, forcing users to pass --force on every re-login. Identity rule rewritten: collision = different email OR different region. The new config envelope stamps the email at the root so future re-logins can detect the mismatch. 0.2.x configs (no email field) silently upgrade. New helper `checkLoginCollision(existing, email, region)` is pure and unit-tested. - **Atomic write + TOCTOU fix.** runLogin's credentials write was a direct writeFileSync, so SIGINT mid-write left a half-written file that the next login refused to touch. Replaced with tmp + chmod + rename — the same pattern installInJsonConfig has used since 0.2.x. Also eliminates the writeFileSync→ chmod TOCTOU window where the token could briefly sit at the umask default. - **Honest mode reporting.** statSync the final file and surface the actual mode in the success message instead of unconditionally claiming "(mode 0600)". On filesystems that don't honor POSIX modes (FAT32/exFAT/some NFS), the user now sees the truth and can act on it. - **Shell quoting in printed snippet.** Default macOS path is `~/Library/ Application Support/leadbay/credentials.json` (has a space). The printed `claude mcp add … jq -r … ${targetPath}` was unquoted and broke on copy-paste. Path is now single-quoted with proper escaping for embedded apostrophes. Tests: new packages/mcp/test/unit/login-collision.test.ts (10 tests) covers same-account re-login, different-email refusal, different-region refusal, 0.2.x compat, and degraded inputs (null/non-object/empty/non-string email). Live verified on the test account: same-account re-login succeeds without --force (was failing in the demo earlier today); different-email collision refuses with a clear message; 0.2.x file upgrades silently. 79/79 tests pass, typecheck + build green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(mcp): pin npx examples to 0.3 + clarify login default in LLM hint The EACCES troubleshooting section claimed all examples use @leadbay/mcp@0.2, but the §1 / §1.5 examples were already pinned to @0.3 — fix the description. The LLM HINT in the header was also stale: it told agents that `login --write-config` mints a token to a file, but in 0.3.0 file-write is the default; --write-config now overrides the path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3e347a9 commit 6a54ff7

15 files changed

Lines changed: 1100 additions & 165 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.3.0 — 2026-04-29
4+
5+
- **`@leadbay/mcp` 0.3.0**: closes [product#3504](https://github.com/leadbay/product/issues/3504) end-to-end. Composite write tools (`refine_prompt`, `report_outreach`, `adjust_audience`, `bulk_qualify_leads`, `enrich_titles`, `answer_clarification`, `import_leads`) are now ON by default — `LEADBAY_MCP_WRITE` defaults to `"1"`. The `SERVER_INSTRUCTIONS` is now built dynamically from the actual exposed tool set, so the system prompt no longer references tools the server doesn't register. `leadbay-mcp login` defaults to writing a 0600-mode credentials file at the platform-correct path (`$XDG_CONFIG_HOME/leadbay/credentials.json`, `~/Library/Application Support/leadbay/credentials.json`, or `%APPDATA%\leadbay\credentials.json`); pass `--unsafe-print-token` for legacy CI flows. `leadbay-mcp install` now registers Claude Code at `--scope user` so the MCP server is visible from any project. **Behavior callout**: in 0.2.x the parser only recognized `LEADBAY_MCP_WRITE === "1"` as ON; 0.3.0 also accepts `true|yes|on` as ON. See `packages/mcp/MIGRATION.md`.
6+
37
## 0.2.5 — 2026-04-28
48

59
- **`@leadbay/mcp` 0.2.5** + **`@leadbay/leadclaw` 0.2.5**: new `leadbay_import_leads` composite write tool ([product#3537](https://github.com/leadbay/product/issues/3537)). Imports a list of company domains and returns Leadbay leadIds for the ones the crawler already knows, chainable into `leadbay_bulk_qualify_leads` and `leadbay_research_lead`. Writes user state (creates a CRM-imports row visible in the web UI). Gated behind `LEADBAY_MCP_WRITE=1` (MCP) and `exposeWrite: true` (OpenClaw). See package CHANGELOGs for full surface, error codes, and limitations.

packages/dxt/manifest.template.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"leadbay_token": {
4444
"type": "string",
4545
"title": "Leadbay bearer token",
46-
"description": "Mint one with: npx -y @leadbay/mcp login --email <you> --region <us|fr> --write-config ~/leadbay.json (then copy the token from that file)",
46+
"description": "Mint one with: npx -y @leadbay/mcp@0.3 login --email <you> --region <us|fr> (then copy LEADBAY_TOKEN from the credentials file the command writes — path is shown on stderr)",
4747
"sensitive": true,
4848
"required": true
4949
},
@@ -58,8 +58,8 @@
5858
"leadbay_mcp_write": {
5959
"type": "boolean",
6060
"title": "Enable write tools",
61-
"description": "Let Claude mutate your Leadbay account (refine_prompt, adjust_audience, report_outreach). Off by default — enable only when you want the agent to modify state.",
62-
"default": false,
61+
"description": "Allow Claude to call composite write tools (refine_prompt, adjust_audience, report_outreach, bulk_qualify_leads, enrich_titles, answer_clarification, import_leads). ON by default since 0.3.0 — turn this off only if you want a strictly read-only agent.",
62+
"default": true,
6363
"required": false
6464
}
6565
},

packages/leadclaw/src/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import type { Tool } from "@leadbay/core";
1313
//
1414
// Tool exposure is gated by plugin config:
1515
// - composite read tools: ALWAYS exposed (the agent's default surface)
16-
// - composite write tools: ALWAYS exposed (OpenClaw runs in user context;
17-
// the agent has explicit consent. Write composites enforce their own
18-
// verification — e.g. report_outreach requires verification={source, ref}.)
16+
// - composite write tools: exposed only when exposeWrite=true
17+
// (independent of the @leadbay/mcp default — OpenClaw plugins go through
18+
// a separate consent surface. The MCP server flipped this to default-on
19+
// in 0.3.0 so its own SERVER_INSTRUCTIONS would stop referencing
20+
// unregistered tools; OpenClaw doesn't ship those instructions, so the
21+
// gate stays explicit here.)
1922
// - granular read tools: exposed only when exposeGranular=true
2023
// - granular write tools: exposed only when both exposeGranular=true
2124
// AND exposeWrite=true

packages/mcp/CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
# Changelog — @leadbay/mcp
22

3+
## 0.3.0 — 2026-04-29
4+
5+
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.
6+
7+
### Coverage — composite write tools default ON
8+
9+
- **`LEADBAY_MCP_WRITE` default is now `"1"` (ON).** The composite write tools (`leadbay_bulk_qualify_leads`, `leadbay_enrich_titles`, `leadbay_refine_prompt`, `leadbay_report_outreach`, `leadbay_adjust_audience`, `leadbay_answer_clarification`, `leadbay_import_leads`) are exposed by default. Set `LEADBAY_MCP_WRITE=0` (or `--no-write` on `install`) to disable them.
10+
- **`SERVER_INSTRUCTIONS` is now dynamic.** The system prompt sent to MCP clients references only the tools actually registered on this instance. Read-only-mode agents receive a different prompt that omits the verification mandate and tells the agent to ask the user to enable writes if they request a config-mutating action.
11+
- **`leadbay-mcp install --include-write` is a no-op (deprecated).** Writes are on by default. Pass `--no-write` for the inverse. The deprecation warning prints **before** the password prompt so it's not buried.
12+
- **`LEADBAY_MCP_WRITE` value-vocabulary expanded.** In 0.2.x only `"1"` was ON; `"true"` / `"yes"` / `"on"` were treated as OFF. In 0.3.0 the parser accepts all of those as ON, and `"0"` / `"false"` / `"no"` / `"off"` as OFF. Unrecognized values default to ON with a one-shot stderr warning. **Existing operators using `=true` / `=yes` / `=on` will see writes flip ON.** See [MIGRATION.md](./MIGRATION.md).
13+
14+
### Login — no token in stdout
15+
16+
- **`leadbay-mcp login` default writes a 0600-mode credentials file.** The path resolves to `$XDG_CONFIG_HOME/leadbay/credentials.json` if set, else `~/Library/Application Support/leadbay/credentials.json` on macOS, `%APPDATA%\leadbay\credentials.json` on Windows, else `~/.config/leadbay/credentials.json`. Existing `~/.leadbay-mcp.json` files (0.2.x) are still honored on this run with a deprecation note pointing at the new path (no automatic file move).
17+
- **`--unsafe-print-token`** restores the previous "print JSON config to stdout" behavior. The deprecated `--print-token` alias still works for one release with a deprecation warning.
18+
- **Collision detection** — if the target credentials file already exists with a different `LEADBAY_TOKEN` or `LEADBAY_REGION`, `login` refuses without `--force` and tells the operator how to keep both files.
19+
- **EACCES / EROFS / ENOENT** errors on the file write print actionable remediation pointing at `--write-config /tmp/...` or `--unsafe-print-token`.
20+
21+
### Scope — visible from any project
22+
23+
- **`leadbay-mcp install` now passes `--scope user` to `claude mcp add`.** This was Ludo's third complaint in #3504: the default `claude mcp add` is project-local, so a freshly-opened conversation from a different directory can't see the server. README §2 (Claude Code), §1 (install), and §4 (troubleshooting table) all reflect the user-scope recommendation.
24+
25+
### Migration
26+
27+
- Read [MIGRATION.md](./MIGRATION.md) for the value-vocabulary flip, the login default, and the install scope change.
28+
- The legacy DXT manifest key (`leadbay_mcp_write`) is unchanged in shape — it now defaults `true` (ON) and feeds the same `LEADBAY_MCP_WRITE` env var. Users who explicitly set the toggle to `false` keep their read-only behavior; users who never touched it (the bug case Ludo hit) now get the new default ON.
29+
- Tests: dropped the `SERVER_INSTRUCTIONS` const re-export. Tests now exercise `buildServerInstructions(exposedSet)` directly across the default/read-only/advanced matrices. New unit suites: `parse-write-env.test.ts`, `login-default.test.ts`, `install-flags.test.ts`.
30+
31+
### Known follow-up (0.4.0)
32+
33+
- `compositeWriteTools` will split into safer-tier (credit-spending: bulk_qualify_leads, enrich_titles, report_outreach, answer_clarification) and config-mutating-tier (refine_prompt, adjust_audience, import_leads), with audit-log + one-click undo on the config-mutating side. Tracked separately.
34+
335
## 0.2.5 — 2026-04-28
436

537
New `leadbay_import_leads` composite write tool.

packages/mcp/MIGRATION.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,75 @@
1+
# Migration: leadbay-mcp 0.2.x → 0.3.0
2+
3+
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.
4+
5+
## 1. `LEADBAY_MCP_WRITE` defaults to ON
6+
7+
In 0.2.x the composite write tools (`leadbay_bulk_qualify_leads`, `leadbay_enrich_titles`, `leadbay_refine_prompt`, `leadbay_report_outreach`, `leadbay_adjust_audience`, `leadbay_answer_clarification`, `leadbay_import_leads`) were gated behind `LEADBAY_MCP_WRITE=1`. The `SERVER_INSTRUCTIONS` referenced them anyway → users got an agent system prompt that lied about what was available.
8+
9+
**0.3.0**: `LEADBAY_MCP_WRITE` defaults to `"1"` (ON). The system prompt is built from the actual exposed tool set, so it stops lying. To restore the previous read-only behavior, set `LEADBAY_MCP_WRITE=0` (or `--no-write` on `leadbay-mcp install`).
10+
11+
### Value-vocabulary flip
12+
13+
In 0.2.x the parser was strict: only `LEADBAY_MCP_WRITE === "1"` turned writes on. So `=true`, `=yes`, `=on` were treated as OFF (probably accidentally — the user clearly meant "on"). The 0.3.0 parser accepts all of these as ON:
14+
15+
| Value | 0.2.x meaning | 0.3.0 meaning |
16+
|---|---|---|
17+
| unset | OFF | **ON** |
18+
| `""` | OFF | **ON** |
19+
| `"1"` / `"true"` / `"yes"` / `"on"` | OFF (only `"1"`) / OFF (the rest) | **ON** |
20+
| `"0"` / `"false"` / `"no"` / `"off"` | OFF | OFF |
21+
| anything else | OFF | ON + stderr warning |
22+
23+
If you were relying on `LEADBAY_MCP_WRITE=true` to mean OFF (unlikely but possible), switch to `LEADBAY_MCP_WRITE=0`.
24+
25+
## 2. `leadbay-mcp login` no longer prints the token to stdout
26+
27+
In 0.2.x `login` printed the bearer token (inside an MCP-config JSON blob) to stdout by default, with a stderr warning. Real users (Ludo's incident) had tokens leak into terminal scrollback / agent chat / CI logs.
28+
29+
**0.3.0**: `login` writes a `0600`-mode credentials file by default. The path resolves per-platform:
30+
31+
| Platform | Default path |
32+
|---|---|
33+
| Linux (or anywhere `XDG_CONFIG_HOME` is set) | `$XDG_CONFIG_HOME/leadbay/credentials.json` (or `~/.config/leadbay/credentials.json`) |
34+
| macOS | `~/Library/Application Support/leadbay/credentials.json` |
35+
| Windows | `%APPDATA%\leadbay\credentials.json` |
36+
37+
If `~/.leadbay-mcp.json` (the 0.2.x default) already exists, `login` writes to that path with a one-shot deprecation note pointing at the new location.
38+
39+
### `--unsafe-print-token` (legacy CI use)
40+
41+
Pass `--unsafe-print-token` to restore the old "print to stdout" behavior. The deprecated `--print-token` alias still works for one release with a warning. Use only if you have to — the token will end up in scrollback / logs.
42+
43+
### Collision detection
44+
45+
If the target file already exists with a different `LEADBAY_TOKEN` or `LEADBAY_REGION`, `login` refuses without `--force` and tells you how to keep both files. Toggling between accounts no longer silently overwrites the prior token.
46+
47+
### File-write errors
48+
49+
`EACCES` / `EROFS` / `ENOENT` print actionable remediation pointing at `--write-config /tmp/...` or `--unsafe-print-token`.
50+
51+
## 3. `leadbay-mcp install` registers Claude Code at `--scope user`
52+
53+
Previously `claude mcp add leadbay …` defaulted to project-local scope, so opening Claude Code from a different directory made Leadbay invisible. Ludo's #3504 third complaint.
54+
55+
**0.3.0**: `install` injects `--scope user` into the `claude mcp add` argv. New installs are visible from any project.
56+
57+
If you have a 0.2.x project-scope install and want to upgrade to user scope, run:
58+
```bash
59+
claude mcp remove leadbay
60+
npx -y @leadbay/mcp@0.3 install --email you@yourcompany.com --region us
61+
```
62+
Or do it manually:
63+
```bash
64+
claude mcp add leadbay --scope user --env LEADBAY_TOKEN=<token> --env LEADBAY_REGION=us -- npx -y @leadbay/mcp@0.3
65+
```
66+
67+
## 4. `--include-write` is a no-op
68+
69+
The legacy `leadbay-mcp install --include-write` flag is accepted but a no-op — writes are on by default in 0.3.0. The deprecation warning prints **before** the password prompt so users see it.
70+
71+
---
72+
173
# Migration: leadclaw / leadbay-mcp 0.1.x → 0.2.0
274

375
This release is the autoplan-reviewed agent-experience overhaul. The OpenClaw

0 commit comments

Comments
 (0)