feat(mcp): install/login subcommands + npm publish prep + LLM-hallucination-proof docs#6
Merged
Merged
Conversation
Adds the agent-facing composite-tool surface that lets Claude (or any MCP
client) drive Leadbay end-to-end without the agent needing to know about
lens permissions, region routing, polling, or selection state. Designed
through a full /autoplan dual-voice review (CEO + Eng + DX) followed by
live API exploration; all 29 approved revisions are in this PR.
New composite tools (agent's default surface):
- pull_leads (replaces find_prospects; adds qualification_summary per lead)
- research_lead (qualification → signals → firmographics → contacts → engagement)
- bulk_qualify_leads (paginates past already-qualified, fan-out + poll, 429 mid-fanout)
- enrich_titles (selection-lifecycle managed, dry_run, 429 handling)
- adjust_audience (admin/non-admin auto-routing, sector free-text resolution, draft fallback)
- refine_prompt + answer_clarification (admin-gated, stale-clarification guard)
- recall_ordered_titles (preview-field path + live-aggregate fallback)
- account_status (quota + admin + intelligence state)
- report_outreach with mandatory verification (gmail_message_id | calendar_event_id | user_confirmed) — prevents pipeline poisoning
New granular tools (28): lens filter/scoring/draft/promote/create/update/active,
selection select/deselect/clear/ids, sectors taxonomy, user_prompt CRUD,
clarifications get/pick/dismiss, epilogue set/remove + responses, prospecting actions,
notes read, web_fetch read, bulk-enrichment preview/launch.
Client refactor: HTTP header capture, _meta envelope (region + endpoint +
latency_ms + retry_after), 429→QUOTA_EXCEEDED mapping (was RATE_LIMITED),
60s /me cache with invalidateMe() called by every write tool that mutates
cached fields, selection Mutex (for concurrent enrich_titles), region
auto-detect on login (us → fr fallback).
Gating model:
- LEADBAY_MCP_WRITE=1 — exposes composite + granular write tools (off by default)
- LEADBAY_MCP_ADVANCED=1 — exposes granular API tools (off by default)
- OpenClaw plugin: exposeWrite + exposeGranular config flags (both off by default)
- leadbay_login still hidden from MCP (UC-3, prompt-injection vector)
Mock mode (LEADBAY_MOCK=1) reads fixtures from .context/leadbay-live-shapes/
for agent-author dry-running. dry_run param on every state-changing composite.
Tests: 89 unit (54→58 core, 11→12 leadclaw, 19 mcp), 10 live read-only smoke,
plus end-to-end MCP and OpenClaw plugin live smokes against the real backend.
Per-tool description style enforced ("When to use" + "When NOT to use" sections).
Live-probe drift documented in .context/leadbay-live-shapes/SHAPE-DRIFT.md
(gitignored). Migration notes in packages/mcp/MIGRATION.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Until app.leadbay.ai ships the API-tokens UI, MCP install instructions
that say "get a token from Settings → API Tokens" are a dead end. New
subcommand exchanges email + password for a bearer token via the existing
/auth/login endpoint and prints a ready-to-paste MCP client config (plus
the Claude Code one-liner).
npx -y @leadbay/mcp@0.2 login --email you@example.com
Password: (hidden)
→ JSON config to stdout (pipe to jq / pbcopy);
explanatory text + Claude Code one-liner to stderr.
Password sources (in order): --email arg + interactive prompt (raw mode,
no echo, Ctrl+C/D handled), or piped stdin (echo pwd | leadbay-mcp login
--email …), or $LEADBAY_PASSWORD env var. Token is treated as a credential
in the printed message.
README §1 leads with this flow now ("works today, no UI needed") and keeps
the API-tokens-page path as the future option once it ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two real issues from the autoplan Eng review applied to the login flow shipped in 8de49aa and were not addressed: 1) Region cross-leak. resolveRegion() tries us → fr; if the user's account is FR, their password hits api-us.leadbay.app even though that backend has no account for them. Fix: --region us|fr (or $LEADBAY_REGION) is REQUIRED by default. To opt in to fallback, pass --allow-region-fallback explicitly. Refuse the operation otherwise rather than silently cross-leak. 2) Token in stdout. Even with the new login subcommand the token landed in terminal scrollback (and could end up in CI logs or screen-shares). Fix: --write-config <path> writes a 0600 JSON file with the token instead of printing it. Default still prints to stdout but with a loud warning urging --write-config; --quiet pairs with --write-config to suppress the token-bearing Claude-Code one-liner from stderr too. UC-3 (prompt-injection) does NOT apply — that was about exposing leadbay_login as a tool the LLM could call. A CLI subcommand the user runs at their own terminal has a different threat model. The MCP server still does not expose login as a tool. README updated to lead with the recommended invocation: npx -y @leadbay/mcp@0.2 login \ --email you@example.com \ --region us \ --write-config ~/.leadbay-mcp.json Tests: 89 unit pass; manually verified all four code paths (refusal without consent, invalid --region, --write-config 0600 + token absent from stdout, --quiet suppresses one-liner). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the UX question: why should a user run `login` and then copy
config manually? They shouldn't.
`install` in a single call:
1. Mints a token from email + password (inherits login's region-pin
rules: --region us|fr required by default; --allow-region-fallback
opts in to cross-region attempt).
2. Auto-detects installed MCP clients: Claude Code (`which claude`),
Claude Desktop (config file at OS-specific path), Cursor
(~/.cursor/mcp.json).
3. Prompts per-target (or --yes for non-interactive) and writes the
token directly into each client's config — Claude Code via
`claude mcp add`, Desktop/Cursor via in-place JSON merge.
4. Token NEVER lands in terminal scrollback. Only ends up in the
client config files the user explicitly agreed to write.
Also: --include-write opts in to composite write tools
(LEADBAY_MCP_WRITE=1), off by default. --target claude-code,cursor
scopes to specific clients. Atomic JSON write (.tmp + rename) to avoid
clobbering an existing config on error.
README §1 now leads with:
npx -y @leadbay/mcp@0.2 install --email you@example.com --region us
`login` stays as the lower-level escape hatch for users who want to
paste the token into a non-detected client.
Manually tested against Claude Code, Claude Desktop, Cursor on this
machine — all three write the right env block and leadbay never
leaked to stdout. 89 unit tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of "Claude tells me to get a token from Settings → API Tokens":
- @leadbay/mcp was never published to npm (404 on registry).
- LLMs reading "the package" fall back to the GitHub README on `main`,
which still has the old install instructions.
- Until v0.2.0 ships to npm, the new `install` / `login` subcommands
don't exist for end users.
This commit makes the package publish-ready:
- Bump @leadbay/mcp 0.1.0 → 0.2.0 (matches the new bin surface)
- Bump @leadbay/core 0.1.0 → 0.2.0 (private; bundled into mcp via tsup)
- Bump @leadbay/leadclaw 0.1.0 → 0.2.0 (separate distribution channel)
- Add publishConfig.access=public so the scoped package publishes correctly
- Add prepublishOnly=tsup so the published tarball always matches src/
- Include MIGRATION.md in the published tarball
- Add a "For maintainers — publishing" section to the README
Verified with `npm pack --dry-run`:
44.4 kB tarball, 7 files. Self-contained (tsup noExternal: ["@leadbay/core"]
bundles the workspace dep).
To actually fix the user-visible issue:
cd packages/mcp && pnpm build && npm publish --access public
Updated README §6 (Advanced) to reflect the v0.2 tool surface
(pull_leads / research_lead / report_outreach with verification, etc.)
and added LEADBAY_MCP_WRITE / LEADBAY_MOCK to the env-vars table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two GitHub Actions workflows:
.github/workflows/release.yml
Triggered by `git tag vX.Y.Z && git push --tags`. Builds, tests,
verifies the tag matches packages/mcp/package.json version, then
`npm publish --access public --provenance` from packages/mcp/.
Manual workflow_dispatch with dry_run input also supported.
Requires NPM_TOKEN secret (one-time setup, documented in the file).
.github/workflows/ci.yml
Runs on every PR + push to main. Install, build, test, npm pack
--dry-run (catches "publishable tarball is broken" before tagging).
Smoke tests are NOT in CI (need a live tenant token); run those
manually or wire a separate nightly workflow with the secret bound.
README:
- Banner at top, marked with HTML comments so the maintainer can
excise it after the first publish lands.
- §7 (For maintainers — publishing) reorganized: tag-based CI flow
is the recommended path; manual `npm publish` stays as fallback.
Note on what this commit does NOT do:
- It does not actually publish the package (I don't have npm auth).
The maintainer pushes the v0.2.0 tag once they're happy with the
PR contents; CI takes it from there.
- It does not auto-bump versions. Bump in a regular PR, merge, tag.
- It does not add jobs for @leadbay/leadclaw or @leadbay/core
(leadclaw distributes through ClawHub; core is bundled into mcp
via tsup `noExternal`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Concrete report from a Claude Code session reading the README:
- Said "No more LEADBAY_REGION — region is now auto-detected" (false:
auto-probe sends the bearer token to BOTH backends; users SHOULD pin)
- Asked the user for "lb_..." — i.e. an API token from a Settings page
that doesn't exist
- Offered to "install from GitHub directly" without a working command
Three changes targeting that failure mode:
1) Top-of-README LLM HINT block (HTML comment so it doesn't render in
most viewers but DOES appear in raw text the LLM reads). Lists the
three specific hallucinations to avoid + names the right commands.
2) §1.1 "Install from source (works today)". Concrete recipe for the
pre-publish window: clone, build, run `node packages/mcp/dist/bin.js
install --email … --region us` (same install subcommand, just from
the local dist). Also documents the `node /abs/path/dist/bin.js`
shape for Claude Desktop / Cursor configs.
3) Tightened the env-vars table — LEADBAY_REGION is now "strongly
recommended" with default "(auto-probe)" + a stderr warning panel
below explaining the cross-leak.
Code change in bin.ts: when the runtime auto-probe is used (no
LEADBAY_REGION + no LEADBAY_BASE_URL), print a stderr warning that
ignores LEADBAY_LOG_LEVEL so an operator who relies on defaults still
sees the recommendation to pin the region.
Threat model note: the runtime token cross-leak is lower-stakes than
the password cross-leak addressed in 7d0ed00 (the wrong-region backend
just 401s — the bearer token isn't usable across tenants), but it's
still an info leak worth surfacing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-ups to PR #5 (now merged on
main). Six commits making the v0.2.0 install actually work for end users — both as a maintainer (publishing to npm) and as an end user (install command, source-install fallback, LLM-readable docs that don't get re-hallucinated).New CLI subcommands:
leadbay-mcp install --email … --region us— one-shot mint a token AND auto-detect + register the MCP server with installed MCP clients (Claude Code viaclaude mcp add, Claude Desktop via JSON merge, Cursor via~/.cursor/mcp.json). Token never lands in terminal scrollback.--include-writeopts in to write tools,--targetscopes to specific clients,--yesskips per-target confirmations.leadbay-mcp login --email … --region us [--write-config PATH]— lower-level: just mint a token.--regionrequired by default (or--allow-region-fallback) to avoid sending the password to a backend that doesn't own the account.--write-configwrites a0600JSON file instead of stdout.Publish prep:
@leadbay/mcp0.1.0 → 0.2.0 (matches new bin surface),@leadbay/coreand@leadbay/leadclawto 0.2.0 for monorepo consistency.publishConfig.access=publicandprepublishOnly=tsupsonpm publishalways ships a fresh, public-scoped tarball.npm pack --dry-runproduces 44 KB tarball, 7 files, self-contained (tsupnoExternal: ["@leadbay/core"]bundles the workspace dep).CI:
.github/workflows/release.yml—git tag v0.2.0 && git push --tagsbuilds, tests, verifies tag matchespackage.jsonversion, publishes to npm with--access public --provenance. Requires one-timeNPM_TOKENrepo secret (instructions inline)..github/workflows/ci.yml— every PR + push to main: install, build, test,npm pack --dry-run.Docs hardened against LLM hallucination (after a real Claude Code session was caught suggesting the wrong install):
<!-- LLM HINT -->block (HTML comment so it doesn't render but is in the raw text every LLM reads). Names the exact three hallucinations to avoid: droppingLEADBAY_REGION, sending users to a non-existent "Settings → API Tokens" page, made-up GitHub install URLs.git clone+pnpm build+node packages/mcp/dist/bin.js install …).LEADBAY_REGIONis now "strongly recommended" with the auto-probe cross-leak warning explained.Runtime warning in
bin.ts: when the server starts withoutLEADBAY_REGIONand falls into the auto-probe path, prints a stderr warning (ignoringLEADBAY_LOG_LEVEL) about the bearer-token cross-leak, recommending the user pin the region.Test plan
pnpm test— all 89 unit tests still passinstall --target cursorwrites~/.cursor/mcp.jsonwith the right env block, token NOT printed to stdoutinstall --target claude-code --include-writecallsclaude mcp addsuccessfullylogin --write-configwrites a0600-mode fileloginrefuses without--region(cross-leak prevention)npm pack --dry-runsucceeds; tarball is 44 KB and includes README, MIGRATION.md, LICENSE, dist/After this merges
Maintainer one-time setup for the npm publish path:
NPM_TOKENGitHub Actions secret: Settings → Secrets and variables → Actions → New repository secretgit tag v0.2.0 && git push --tags—release-mcpworkflow handles the restpackages/mcp/README.md🤖 Generated with Claude Code