Skip to content

feat(mcp): install/login subcommands + npm publish prep + LLM-hallucination-proof docs#6

Merged
milstan merged 7 commits into
mainfrom
milstan/leadbay-agent-skills
Apr 21, 2026
Merged

feat(mcp): install/login subcommands + npm publish prep + LLM-hallucination-proof docs#6
milstan merged 7 commits into
mainfrom
milstan/leadbay-agent-skills

Conversation

@milstan

@milstan milstan commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

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 via claude mcp add, Claude Desktop via JSON merge, Cursor via ~/.cursor/mcp.json). Token never lands in terminal scrollback. --include-write opts in to write tools, --target scopes to specific clients, --yes skips per-target confirmations.
  • leadbay-mcp login --email … --region us [--write-config PATH] — lower-level: just mint a token. --region required by default (or --allow-region-fallback) to avoid sending the password to a backend that doesn't own the account. --write-config writes a 0600 JSON file instead of stdout.

Publish prep:

  • Bump @leadbay/mcp 0.1.0 → 0.2.0 (matches new bin surface), @leadbay/core and @leadbay/leadclaw to 0.2.0 for monorepo consistency.
  • Add publishConfig.access=public and prepublishOnly=tsup so npm publish always ships a fresh, public-scoped tarball.
  • npm pack --dry-run produces 44 KB tarball, 7 files, self-contained (tsup noExternal: ["@leadbay/core"] bundles the workspace dep).

CI:

  • .github/workflows/release.ymlgit tag v0.2.0 && git push --tags builds, tests, verifies tag matches package.json version, publishes to npm with --access public --provenance. Requires one-time NPM_TOKEN repo 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):

  • Top-of-README <!-- 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: dropping LEADBAY_REGION, sending users to a non-existent "Settings → API Tokens" page, made-up GitHub install URLs.
  • New §1.1 "Install from source (works today)" — concrete recipe for the pre-publish window (git clone + pnpm build + node packages/mcp/dist/bin.js install …).
  • Pre-release banner at top of README (HTML-comment-marked so it's easy to remove after first publish).
  • Env-vars table corrected: LEADBAY_REGION is now "strongly recommended" with the auto-probe cross-leak warning explained.

Runtime warning in bin.ts: when the server starts without LEADBAY_REGION and falls into the auto-probe path, prints a stderr warning (ignoring LEADBAY_LOG_LEVEL) about the bearer-token cross-leak, recommending the user pin the region.

Test plan

  • pnpm test — all 89 unit tests still pass
  • Manually verified install --target cursor writes ~/.cursor/mcp.json with the right env block, token NOT printed to stdout
  • Manually verified install --target claude-code --include-write calls claude mcp add successfully
  • Manually verified login --write-config writes a 0600-mode file
  • Manually verified login refuses without --region (cross-leak prevention)
  • npm pack --dry-run succeeds; tarball is 44 KB and includes README, MIGRATION.md, LICENSE, dist/

After this merges

Maintainer one-time setup for the npm publish path:

  1. Generate npm Automation token: https://www.npmjs.com/settings//tokens → New Token → Automation
  2. Add as NPM_TOKEN GitHub Actions secret: Settings → Secrets and variables → Actions → New repository secret
  3. git tag v0.2.0 && git push --tagsrelease-mcp workflow handles the rest
  4. Once first publish succeeds, remove the pre-release banner at the top of packages/mcp/README.md

🤖 Generated with Claude Code

milstan and others added 7 commits April 20, 2026 21:08
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>
@milstan milstan merged commit 9f235db into main Apr 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant