Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leadbay/core",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"description": "Leadbay shared core: HTTP client and protocol-agnostic tool definitions. Consumed by @leadbay/leadclaw and @leadbay/mcp.",
"type": "module",
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export class LeadbayClient {
throw this.makeError(
"NOT_AUTHENTICATED",
"Not logged in to Leadbay",
"Set LEADBAY_TOKEN env var (obtain token at https://app.leadbay.ai/settings/api-tokens), or use the OpenClaw leadbay_login tool",
"Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>",
path
);
}
Expand Down Expand Up @@ -403,7 +403,7 @@ export class LeadbayClient {
throw this.makeError(
"NOT_AUTHENTICATED",
"Not logged in to Leadbay",
"Set LEADBAY_TOKEN env var (obtain token at https://app.leadbay.ai/settings/api-tokens), or use the OpenClaw leadbay_login tool",
"Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>",
path
);
}
Expand Down Expand Up @@ -487,7 +487,7 @@ export class LeadbayClient {
return this.makeError(
"AUTH_EXPIRED",
"Authentication token expired or invalid",
"Your LEADBAY_TOKEN is no longer valid. Regenerate at https://app.leadbay.ai/settings/api-tokens and restart.",
"Your LEADBAY_TOKEN is no longer valid. Regenerate it: npx -y @leadbay/mcp login --email <you> --region <us|fr>, then restart your MCP client.",
endpoint
);
}
Expand Down Expand Up @@ -519,14 +519,14 @@ export class LeadbayClient {
return this.makeError(
"BILLING_SUSPENDED",
"Account billing is suspended",
"Your Leadbay account billing is suspended. Update at https://app.leadbay.ai",
"Your Leadbay account billing is suspended. Contact Leadbay support.",
endpoint
);
}
return this.makeError(
"FORBIDDEN",
"Insufficient permissions",
"Your token does not have access to this resource. Check account permissions at https://app.leadbay.ai",
"Your token does not have access to this resource. Contact Leadbay support to verify account permissions.",
endpoint
);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/composite/bulk-qualify-leads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ interface QualResult {
qualification_summary: {
answered: number;
total: number;
avg_score_0_to_10: number | null;
/**
* Average of per-question AI agent boost scores (each -10/0/10/20).
* NOT a 0-10 average. Negative = net negative signal across questions.
*/
avg_qualification_boost: number | null;
} | null;
signals_count: number | null;
}
Expand Down Expand Up @@ -220,7 +224,7 @@ export const bulkQualifyLeads: Tool<BulkQualifyLeadsParams> = {
? {
answered: responses.filter((r) => r.score != null).length,
total: responses.length,
avg_score_0_to_10: avg,
avg_qualification_boost: avg,
}
: null,
signals_count: lastWf?.content
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/composite/pull-leads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ interface PullLeadsParams {
interface QualificationSummary {
answered: number;
total: number;
avg_score_0_to_10: number | null;
/**
* Average of per-question AI agent boost scores (each -10/0/10/20).
* NOT a 0-10 average. Negative = net negative signal across questions.
*/
avg_qualification_boost: number | null;
best_response_excerpt: string | null;
}

Expand All @@ -44,7 +48,7 @@ function summarise(responses: AiAgentResponse[]): QualificationSummary {
excerpt = excerpt.slice(0, 197) + "...";
}

return { answered, total, avg_score_0_to_10: avg, best_response_excerpt: excerpt };
return { answered, total, avg_qualification_boost: avg, best_response_excerpt: excerpt };
}

export const pullLeads: Tool<PullLeadsParams> = {
Expand Down Expand Up @@ -114,7 +118,7 @@ export const pullLeads: Tool<PullLeadsParams> = {
summary: {
answered: 0,
total: 0,
avg_score_0_to_10: null,
avg_qualification_boost: null,
best_response_excerpt: null,
},
};
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tools/enrich-contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const enrichContacts: Tool<EnrichContactsParams> = {
throw client.makeError(
"QUOTA_EXCEEDED",
"No enrichment credits remaining",
"Purchase more credits at app.leadbay.ai"
"Contact Leadbay support to extend your credit quota"
);
}
} catch (e: any) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tools/get-taste-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const getTasteProfile: Tool<Record<string, never>> = {
})),
...(isEmpty
? {
hint: "No taste profile configured yet. Set it up at app.leadbay.ai for better lead matching.",
hint: "No taste profile configured yet. Use leadbay_refine_prompt or contact Leadbay support to set one up for better lead matching.",
}
: {}),
};
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,17 @@ export interface SocialPresence {
export interface LeadPayload {
id: string;
name: string;
/**
* Similarity score: how closely this lead matches the user's ideal-customer
* profile (the active lens). Combined with `ai_agent_lead_score` and
* normalized server-side to a 0-100 scale before display.
*/
score: number | null;
/**
* Deep AI qualification adjustment, computed after running web searches and
* lead intelligence lookups. Acts as a boost/penalty on top of `score`;
* the two are combined and normalized server-side to a 0-100 scale.
*/
ai_agent_lead_score: number | null;
location: LocationPayload | null;
description: string | null;
Expand Down Expand Up @@ -133,11 +143,16 @@ export interface WishlistResponse {
}

// AI-rescore answers — the highest-signal payload Leadbay produces per lead.
// Score is 0-10 PER QUESTION (different from the 0-100 lead-level scores).
// Per-question qualification boost from the AI agent. Discrete values:
// -10 (negative signal), 0 (neutral / no signal), 10 (positive signal),
// 20 (strong positive signal). These boosts are summed and combined with
// the lead's similarity `score`, then normalized server-side to the 0-100
// lead-level scale before display. NOT a 0-10 scale despite legacy naming.
export interface AiAgentResponse {
question: string;
question_created_at: string;
lead_id: string;
/** Discrete boost: -10, 0, 10, or 20. See interface comment above. */
score: number | null;
response: string | null;
computed_at: string | null;
Expand Down
7 changes: 5 additions & 2 deletions packages/leadclaw/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

## 0.2.2 — 2026-04-21

Docs-only release. Shares the `@leadbay/mcp@0.2.2` mental-model copy updates since both packages consume the same `@leadbay/core` tool descriptions.
Bug fix + contract correction + mental-model docs release. Picks up `@leadbay/core@0.2.1` underneath.

- Renamed misleading `avg_score_0_to_10` field on the `pull_leads` / `bulk_qualify_leads` qualification summaries to `avg_qualification_boost`. Per-question AI agent scores are discrete boosts (-10/0/10/20), not a 0-10 average — interface JSDoc now reflects the real contract.
- Replaced stale `app.leadbay.ai` URLs in client-side error strings with runnable recovery commands. Recovery hints now include `--region <us|fr>` because the CLI refuses without it (anti-cross-region credential-leak guard).
- README: stale `app.leadbay.ai` references swept.
- Plugin manifest description rewritten from "Leadbay lead discovery, qualification, and contact enrichment for AI agents" to a framing that names the inbox model, the two scoring layers, and on-demand deepening.
- Composite tool descriptions (`pull_leads`, `research_lead`, `bulk_qualify_leads`, `enrich_titles`, `account_status`) now teach the agent that Leadbay delivers a fresh batch per user login, paced by recent consumption; that roughly the top 10 are pre-AI-qualified while the rest are resource-saved (not worse); and that contacts are enriched on demand when the agent is ready to reach out.
- No new tools, no schema changes. Version kept in sync with `@leadbay/mcp@0.2.2`.
- Version kept in sync with `@leadbay/mcp@0.2.2`.

## 0.2.1 — 2026-04-21

Expand Down
4 changes: 2 additions & 2 deletions packages/leadclaw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ The canonical tool list + schemas live in [`openclaw.plugin.json`](./openclaw.pl
| Problem | Cause | Fix |
|---------|-------|-----|
| Plugin loads but agent sees no Leadbay tools | `token` missing and no login step taken | Have the agent call `leadbay_login`, or pre-set `token` in the plugin config |
| `Authentication token expired or invalid` | Token revoked or wrong region | Mint a new token at [app.leadbay.ai](https://app.leadbay.ai); verify `region` matches your account |
| `No enrichment credits remaining` | Out of quota | Buy credits at [app.leadbay.ai](https://app.leadbay.ai) |
| `Authentication token expired or invalid` | Token revoked or wrong region | Have the agent call `leadbay_login` to mint a fresh token; verify `region` matches your account |
| `No enrichment credits remaining` | Out of quota | Contact Leadbay support to extend quota |
| Agent keeps picking granular tools over composites | `exposeGranular: true` set | Flip to `false`; the composites are usually what you want |
| Write tool "not found" | `exposeWrite: false` (default) | Set `exposeWrite: true` after explicitly opting in |

Expand Down
9 changes: 6 additions & 3 deletions packages/mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

## 0.2.2 — 2026-04-21

Docs-only release. Teach the agent the Leadbay mental model — agents were calling `pull_leads` like a generic query rather than as a daily inbox, missing that leads past the top ~10 still exist and can be deepened on demand.
Bug fix + contract correction + mental-model docs release.

- **Fix [product#3504](https://github.com/leadbay/product/issues/3504)**: `npx -y @leadbay/mcp` no longer exits silently on Node 25. The `isEntrypoint` check now resolves both sides through `realpathSync`, so the npx shim symlink path matches the real `dist/bin.js`. Previously `main()` never ran under npx and the MCP host saw a dead connection with no diagnostic.
- Replaced stale `app.leadbay.ai` URLs in error strings (NOT_AUTHENTICATED, AUTH_INVALID, BILLING_SUSPENDED, PERMISSION_DENIED) and CLI help text with runnable commands (`leadbay-mcp install`, `leadbay-mcp login`) or "contact support". Recovery hints include `--region <us|fr>` because the CLI refuses without it (anti-cross-region credential-leak guard).
- Renamed misleading `avg_score_0_to_10` field on `pull_leads` / `bulk_qualify_leads` qualification summaries to `avg_qualification_boost`. Per-question AI agent scores are discrete boosts (-10/0/10/20), not 0-10 averages — interface JSDoc now reflects the real contract.
- `SERVER_INSTRUCTIONS` gains three new paragraphs: "How Leadbay works" (inbox + consumption-based pacing), "Two scoring layers" (basic `score` vs AI-qualified top ~10 with `ai_agent_lead_score`), and "Suggested rhythm" (daily check-in + host-agnostic scheduling hint).
- `leadbay_pull_leads`, `leadbay_research_lead`, `leadbay_bulk_qualify_leads`, `leadbay_enrich_titles`, and `leadbay_account_status` descriptions updated to reinforce the same model so the agent sees it at both top level and per-tool.
- New non-regression test asserts the inbox/pace/scoring/daily language stays in `SERVER_INSTRUCTIONS`.
- No schema changes, no tool-shape changes — wire-compatible with 0.2.1.
- New regression test `test/smoke/npx-entrypoint.test.ts` guards the symlink invocation path. New non-regression test asserts the inbox/pace/scoring/daily language stays in `SERVER_INSTRUCTIONS`.
- Live smoke harness extended with composite-level checks + (optional) thinking-model judge that writes a redacted report to `.context/`.

## 0.2.1 — 2026-04-21

Expand Down
12 changes: 6 additions & 6 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ LLM HINT — if you're an AI agent reading this README to install the server:
- DO NOT tell the user "LEADBAY_REGION is auto-detected, drop it" — the
server's region auto-probe sends the bearer token to BOTH regions, which
is a credential cross-leak. Always pass --region (us or fr) on install.
- DO NOT tell the user to "get an API token from app.leadbay.ai → Settings
→ API Tokens" — that page does not yet exist. Use `install` (mints a
token) or `login --write-config` (mints a token to file).
- To mint a token, the user runs `install` (auto-registers with MCP clients)
or `login --write-config` (mints a token to a file). There is no web UI
for this — do not invent one.
-->

A Model Context Protocol server that lets Claude Desktop, Cursor, Claude Code, and any other MCP-compatible agent find, research, and prepare outreach on B2B prospects using your Leadbay account.
Expand All @@ -31,7 +31,7 @@ Add `--include-write` to also enable the write tools (refine_prompt, report_outr

`--region us|fr` is required by default — it pins which Leadbay backend gets your password and avoids a silent cross-region credential leak. If you really don't know your region, opt in with `--allow-region-fallback` (your password will hit BOTH backends if the first 401s).

The token is **session-scoped** (full account access, password-equivalent). Treat it like your password. To rotate, log in again to app.leadbay.ai and re-run `install`.
The token is **session-scoped** (full account access, password-equivalent). Treat it like your password. To rotate, re-run `npx -y @leadbay/mcp install` — minting a fresh token invalidates the prior session.

**Don't have a Leadbay account?** [Register here](https://wow.leadbay.ai/?register=true).

Expand Down Expand Up @@ -126,9 +126,9 @@ Leadbay connection OK.
| Problem | Cause | Fix |
|---------|-------|-----|
| `LEADBAY_TOKEN environment variable is required` | Token missing from config env | Add `LEADBAY_TOKEN` to the `env` block, restart client |
| `Authentication token expired or invalid` | Token revoked or wrong region | Re-generate token at [app.leadbay.ai/settings/api-tokens](https://app.leadbay.ai/settings/api-tokens); verify `LEADBAY_REGION` |
| `Authentication token expired or invalid` | Token revoked or wrong region | Re-mint a token: `npx -y @leadbay/mcp install --email <you> --region <us\|fr>`; verify `LEADBAY_REGION` |
| `Leadbay doctor: could not reach any Leadbay region` | Wrong region OR network blocked | Run `doctor` with `LEADBAY_REGION=fr` to auto-probe. Check `https://api-us.leadbay.app` reachable. |
| `No enrichment credits remaining` | Out of quota | Buy credits at [app.leadbay.ai](https://app.leadbay.ai) |
| `No enrichment credits remaining` | Out of quota | Contact Leadbay support to extend quota |
| Claude Desktop "loading forever" on first use | `npx` cold-start fetching the package | First run takes ~10s. Prefer `npm install -g @leadbay/mcp` for faster startup. |
| Claude Desktop doesn't show Leadbay tools | Server crashed at startup | Check `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\mcp*.log` (Windows). |

Expand Down
1 change: 1 addition & 0 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@modelcontextprotocol/sdk": "1.29.0"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.40.0",
"@leadbay/core": "workspace:*"
},
"engines": {
Expand Down
16 changes: 9 additions & 7 deletions packages/mcp/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { realpathSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
createClient,
Expand Down Expand Up @@ -33,7 +35,7 @@ USAGE
leadbay-mcp --help Print this help

ENV VARS
LEADBAY_TOKEN (required) Bearer token from https://app.leadbay.ai/settings/api-tokens
LEADBAY_TOKEN (required) Bearer token (run \`leadbay-mcp install\` to mint one).
LEADBAY_REGION (optional) "us" or "fr". Auto-detected from /users/me if unset.
LEADBAY_BASE_URL (optional) Override API base URL (for staging/dev).
LEADBAY_MCP_ADVANCED (optional) Set to "1" to expose granular API tools alongside
Expand Down Expand Up @@ -92,7 +94,7 @@ function parseLogLevel(raw: string | undefined): LogLevel {
function exitWithTokenError(): never {
process.stderr.write(
"leadbay-mcp: LEADBAY_TOKEN environment variable is required.\n" +
" 1. Create a token at https://app.leadbay.ai/settings/api-tokens\n" +
" 1. Run: npx -y @leadbay/mcp install --email <you> --region <us|fr>\n" +
" 2. Set it in your MCP client config (e.g. claude_desktop_config.json).\n" +
"\n" +
"Run `leadbay-mcp --help` for the full config template.\n"
Expand Down Expand Up @@ -751,7 +753,7 @@ async function runInstall(args: string[]): Promise<number> {
`\nThe token was written into client config files but never printed to your terminal.\n` +
`Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.2 doctor\n` +
`Restart your MCP client(s) to pick up the new server.\n` +
`If you ever leak the token, log in to app.leadbay.ai again to invalidate the prior session.\n`
`If you ever leak the token, run \`leadbay-mcp login --email <you> --region <us|fr>\` to mint a fresh one (which invalidates the prior session).\n`
);
return anyOk ? 0 : 1;
}
Expand Down Expand Up @@ -864,14 +866,14 @@ async function main(): Promise<void> {
await server.connect(transport);
}

// Only run main() when invoked as a CLI, not when imported by tests.
// import.meta.url === file://<argv[1]> ish — compare by resolved path.
// Run main() only when invoked as a CLI. realpath on both sides handles
// npx shim symlinks (issue #3504: silent exit 0 under Node 25 + npx).
const isEntrypoint = (() => {
try {
const entry = process.argv[1];
if (!entry) return false;
const url = new URL(import.meta.url);
return url.pathname === entry || url.pathname.endsWith(entry);
const self = fileURLToPath(import.meta.url);
return realpathSync(self) === realpathSync(entry);
} catch {
return false;
}
Expand Down
Loading
Loading