@leadbay/mcp 0.2.2 → 0.3.0
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 +72 -0
- package/MIGRATION.md +72 -0
- package/README.md +74 -23
- package/dist/bin.js +358 -75
- package/dist/{chunk-FJBO2MY2.js → chunk-NED7ATJI.js} +608 -12
- package/dist/{dist-FENQ2I7R.js → dist-YMZYFHZK.js} +5 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
compositeWriteTools,
|
|
5
5
|
createClient,
|
|
6
6
|
createDefaultBulkStore,
|
|
7
|
+
formatLoginError,
|
|
7
8
|
granularReadTools,
|
|
8
9
|
granularWriteTools,
|
|
9
10
|
resolveRegion
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-NED7ATJI.js";
|
|
11
12
|
|
|
12
13
|
// src/bin.ts
|
|
13
14
|
import { realpathSync } from "fs";
|
|
@@ -20,7 +21,44 @@ import {
|
|
|
20
21
|
CallToolRequestSchema,
|
|
21
22
|
ListToolsRequestSchema
|
|
22
23
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
-
var
|
|
24
|
+
var VERIFICATION_MANDATE = "After every email, call, message, or meeting with a lead's contact, you MUST call leadbay_report_outreach with verification={source, ref} (gmail_message_id from the Gmail send, calendar_event_id from a booking, or user_confirmed='<the user's literal confirmation>'). Skipping or fabricating verification poisons the human team's pipeline.";
|
|
25
|
+
var MENTAL_MODEL_PARAGRAPH = "How Leadbay works (mental model): Leadbay is a sales inbox, not a queryable database. Each day the user logs back in, a fresh batch of leads is delivered. Batch size is paced by how many leads the user has actually acted on recently \u2014 some workflows produce a big stream of smaller prospects, others a narrow stream of bigger ones. Pulling more won't produce more; the user acting on leads (outreach, skips, saves) does.";
|
|
26
|
+
function buildScoringParagraph(has) {
|
|
27
|
+
const base = "Two scoring layers: every lead has a basic `score` (firmographic \u2014 already decent, usually correlates with AI). Roughly the top 10 of each batch are also AI-qualified (targeted web research + qualification questions \u2192 `ai_agent_lead_score`, surfaced as `qualification_summary` on leadbay_pull_leads). Leads past the top ~10 are not worse \u2014 the system is saving resources.";
|
|
28
|
+
const deepenTools = [];
|
|
29
|
+
if (has("leadbay_bulk_qualify_leads")) deepenTools.push("leadbay_bulk_qualify_leads for deeper qualification");
|
|
30
|
+
if (has("leadbay_enrich_titles")) deepenTools.push("leadbay_enrich_titles for contacts");
|
|
31
|
+
if (deepenTools.length > 0) {
|
|
32
|
+
return base + ` Call ${deepenTools.join(" or ")} on any lead that looks worth it.`;
|
|
33
|
+
}
|
|
34
|
+
return base;
|
|
35
|
+
}
|
|
36
|
+
function buildStartHereParagraph(has) {
|
|
37
|
+
const base = "Start with leadbay_account_status to see the user's state, then leadbay_pull_leads to surface fresh leads. Use leadbay_research_lead to dig into one lead deeply (qualification answers, signals, contacts).";
|
|
38
|
+
const compositeNames = ["bulk_qualify_leads", "adjust_audience", "refine_prompt", "enrich_titles"].filter((n) => has(`leadbay_${n}`));
|
|
39
|
+
if (compositeNames.length > 0) {
|
|
40
|
+
return base + ` When the user wants more leads, narrower audience, refined criteria, or contact enrichment, use the matching composite tool (${compositeNames.join(" / ")}) \u2014 they hide lens permissions, region routing, polling, and selection state from you.`;
|
|
41
|
+
}
|
|
42
|
+
return base + " When the user asks for refinement, contact enrichment, audience changes, or outreach reporting, tell them: those actions require write tools, currently disabled. Re-enable by removing `LEADBAY_MCP_WRITE=0` from your MCP client config and restarting the client. Also: do not promise to log outreach \u2014 the report_outreach tool is not available in this configuration.";
|
|
43
|
+
}
|
|
44
|
+
function buildRhythmParagraph(has) {
|
|
45
|
+
if (has("leadbay_report_outreach")) {
|
|
46
|
+
return "Suggested rhythm: a healthy agent pattern is a daily check-in \u2014 pull fresh leads, skim the auto-qualified top, deepen 1-3 promising ones, propose outreach to the user, then leadbay_report_outreach on what actually got sent. If your host supports scheduling, offer to set up a daily run.";
|
|
47
|
+
}
|
|
48
|
+
return "Suggested rhythm: a healthy agent pattern is a daily check-in \u2014 pull fresh leads, skim the auto-qualified top, deepen 1-3 promising ones, propose outreach to the user. If your host supports scheduling, offer to set up a daily run.";
|
|
49
|
+
}
|
|
50
|
+
function buildServerInstructions(exposed) {
|
|
51
|
+
const has = (name) => exposed.has(name);
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (has("leadbay_report_outreach")) {
|
|
54
|
+
parts.push(VERIFICATION_MANDATE);
|
|
55
|
+
}
|
|
56
|
+
parts.push(MENTAL_MODEL_PARAGRAPH);
|
|
57
|
+
parts.push(buildScoringParagraph(has));
|
|
58
|
+
parts.push(buildStartHereParagraph(has));
|
|
59
|
+
parts.push(buildRhythmParagraph(has));
|
|
60
|
+
return parts.join("\n\n");
|
|
61
|
+
}
|
|
24
62
|
function formatErrorForLLM(err) {
|
|
25
63
|
if (err && typeof err === "object" && err.error === true) {
|
|
26
64
|
const parts = [`${err.message}.`, err.hint];
|
|
@@ -45,13 +83,6 @@ function toolsListPayload(tools) {
|
|
|
45
83
|
}));
|
|
46
84
|
}
|
|
47
85
|
function buildServer(client, opts = {}) {
|
|
48
|
-
const server = new Server(
|
|
49
|
-
{ name: "leadbay", version: "0.2.0" },
|
|
50
|
-
{
|
|
51
|
-
capabilities: { tools: {} },
|
|
52
|
-
instructions: SERVER_INSTRUCTIONS
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
86
|
const exposedTools = [];
|
|
56
87
|
exposedTools.push(...compositeReadTools);
|
|
57
88
|
if (opts.includeWrite) {
|
|
@@ -69,6 +100,14 @@ function buildServer(client, opts = {}) {
|
|
|
69
100
|
toolByName.set(t.name, t);
|
|
70
101
|
}
|
|
71
102
|
}
|
|
103
|
+
const exposedNames = new Set(toolByName.keys());
|
|
104
|
+
const server = new Server(
|
|
105
|
+
{ name: "leadbay", version: "0.3.0" },
|
|
106
|
+
{
|
|
107
|
+
capabilities: { tools: {} },
|
|
108
|
+
instructions: buildServerInstructions(exposedNames)
|
|
109
|
+
}
|
|
110
|
+
);
|
|
72
111
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
73
112
|
tools: toolsListPayload([...toolByName.values()])
|
|
74
113
|
}));
|
|
@@ -119,7 +158,7 @@ function buildServer(client, opts = {}) {
|
|
|
119
158
|
|
|
120
159
|
// src/bin.ts
|
|
121
160
|
import { createRequire } from "module";
|
|
122
|
-
var VERSION = "0.
|
|
161
|
+
var VERSION = "0.3.0";
|
|
123
162
|
var HELP = `
|
|
124
163
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
125
164
|
|
|
@@ -143,10 +182,12 @@ ENV VARS
|
|
|
143
182
|
LEADBAY_BASE_URL (optional) Override API base URL (for staging/dev).
|
|
144
183
|
LEADBAY_MCP_ADVANCED (optional) Set to "1" to expose granular API tools alongside
|
|
145
184
|
the composite workflow tools. Most users don't need this.
|
|
146
|
-
LEADBAY_MCP_WRITE (optional)
|
|
147
|
-
report_outreach, adjust_audience,
|
|
148
|
-
|
|
149
|
-
|
|
185
|
+
LEADBAY_MCP_WRITE (optional) Default "1" (ON) since 0.3.0: exposes write composites
|
|
186
|
+
(refine_prompt, report_outreach, adjust_audience, bulk_qualify_leads,
|
|
187
|
+
enrich_titles, answer_clarification, import_leads). Set to "0" /
|
|
188
|
+
"false" / "no" / "off" for read-only mode. Note: in 0.2.x, only
|
|
189
|
+
"1" turned writes ON; "true" / "yes" / "on" were treated as OFF.
|
|
190
|
+
The 0.3.0 parser accepts all those values as truthy. See MIGRATION.md.
|
|
150
191
|
LEADBAY_MOCK (optional) Set to "1" to serve all responses from on-disk fixtures
|
|
151
192
|
(no network, no real auth). Useful for agent-author dry-running.
|
|
152
193
|
GETs are matched against fixture JSON files; POSTs/DELETEs are
|
|
@@ -159,7 +200,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
|
|
|
159
200
|
"mcpServers": {
|
|
160
201
|
"leadbay": {
|
|
161
202
|
"command": "npx",
|
|
162
|
-
"args": ["-y", "@leadbay/mcp@0.
|
|
203
|
+
"args": ["-y", "@leadbay/mcp@0.3"],
|
|
163
204
|
"env": {
|
|
164
205
|
"LEADBAY_TOKEN": "lb_...",
|
|
165
206
|
"LEADBAY_REGION": "us"
|
|
@@ -193,6 +234,18 @@ function parseLogLevel(raw) {
|
|
|
193
234
|
if (raw === "debug" || raw === "info") return raw;
|
|
194
235
|
return "error";
|
|
195
236
|
}
|
|
237
|
+
function parseWriteEnv() {
|
|
238
|
+
const raw = process.env.LEADBAY_MCP_WRITE;
|
|
239
|
+
if (raw === void 0 || raw === "") return true;
|
|
240
|
+
const v = raw.trim().toLowerCase();
|
|
241
|
+
if (v === "0" || v === "false" || v === "no" || v === "off") return false;
|
|
242
|
+
if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
|
|
243
|
+
process.stderr.write(
|
|
244
|
+
`[leadbay-mcp warn] LEADBAY_MCP_WRITE='${raw}' not recognized; defaulting to ON. Use 1/0.
|
|
245
|
+
`
|
|
246
|
+
);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
196
249
|
function exitWithTokenError() {
|
|
197
250
|
process.stderr.write(
|
|
198
251
|
"leadbay-mcp: LEADBAY_TOKEN environment variable is required.\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\nRun `leadbay-mcp --help` for the full config template.\n"
|
|
@@ -299,11 +352,73 @@ function parseFlag(args, name) {
|
|
|
299
352
|
function hasFlag(args, name) {
|
|
300
353
|
return args.some((a) => a === `--${name}`);
|
|
301
354
|
}
|
|
355
|
+
function resolveDefaultCredentialsPath() {
|
|
356
|
+
const fs = require_("node:fs");
|
|
357
|
+
const path = require_("node:path");
|
|
358
|
+
const legacyPath = path.join(require_("node:os").homedir(), ".leadbay-mcp.json");
|
|
359
|
+
if (fs.existsSync(legacyPath)) {
|
|
360
|
+
return { path: legacyPath, legacy: true };
|
|
361
|
+
}
|
|
362
|
+
return { path: computeFreshDefaultPath(), legacy: false };
|
|
363
|
+
}
|
|
364
|
+
function checkLoginCollision(existingConfig, email, region) {
|
|
365
|
+
if (!existingConfig || typeof existingConfig !== "object") {
|
|
366
|
+
return "existing file is not valid JSON";
|
|
367
|
+
}
|
|
368
|
+
const cfg = existingConfig;
|
|
369
|
+
const existingEmail = typeof cfg.email === "string" && cfg.email.length > 0 ? cfg.email : void 0;
|
|
370
|
+
const existingRegion = typeof cfg.mcpServers?.leadbay?.env?.LEADBAY_REGION === "string" ? cfg.mcpServers.leadbay.env.LEADBAY_REGION : void 0;
|
|
371
|
+
if (existingEmail !== void 0 && existingEmail !== email) {
|
|
372
|
+
return `existing email=${existingEmail} (this login is email=${email})`;
|
|
373
|
+
}
|
|
374
|
+
if (existingRegion !== void 0 && existingRegion !== region) {
|
|
375
|
+
return `existing region=${existingRegion} (this login is region=${region})`;
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
function computeFreshDefaultPath() {
|
|
380
|
+
const os = require_("node:os");
|
|
381
|
+
const path = require_("node:path");
|
|
382
|
+
const home = os.homedir();
|
|
383
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
384
|
+
if (xdg && xdg.length > 0) {
|
|
385
|
+
return path.join(xdg, "leadbay", "credentials.json");
|
|
386
|
+
}
|
|
387
|
+
if (process.platform === "darwin") {
|
|
388
|
+
return path.join(home, "Library", "Application Support", "leadbay", "credentials.json");
|
|
389
|
+
}
|
|
390
|
+
if (process.platform === "win32") {
|
|
391
|
+
const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
392
|
+
return path.join(appdata, "leadbay", "credentials.json");
|
|
393
|
+
}
|
|
394
|
+
return path.join(home, ".config", "leadbay", "credentials.json");
|
|
395
|
+
}
|
|
302
396
|
async function runLogin(args) {
|
|
303
397
|
const email = parseFlag(args, "email");
|
|
398
|
+
const defaultPathPreview = (() => {
|
|
399
|
+
try {
|
|
400
|
+
return resolveDefaultCredentialsPath().path;
|
|
401
|
+
} catch {
|
|
402
|
+
return "<HOME>/.config/leadbay/credentials.json";
|
|
403
|
+
}
|
|
404
|
+
})();
|
|
304
405
|
if (!email) {
|
|
305
406
|
process.stderr.write(
|
|
306
|
-
|
|
407
|
+
`Usage: leadbay-mcp login --email you@example.com [--region us|fr] [--allow-region-fallback]
|
|
408
|
+
[--write-config PATH] [--unsafe-print-token] [--force] [--quiet]
|
|
409
|
+
Then enter your password (hidden), or pipe it via stdin / set $LEADBAY_PASSWORD.
|
|
410
|
+
--region Pin the backend (us|fr); avoids sending your password to a backend you don't use.
|
|
411
|
+
Defaults to $LEADBAY_REGION if set; otherwise asks you to pass --allow-region-fallback.
|
|
412
|
+
--allow-region-fallback Try us, then fr (or fr, then us). Your password hits BOTH backends if the
|
|
413
|
+
first 401s. Only do this if you're OK with that.
|
|
414
|
+
Default behavior (0.3.0+): writes the MCP-client JSON to the platform-correct credentials path:
|
|
415
|
+
${defaultPathPreview} (mode 0600).
|
|
416
|
+
--write-config PATH Override the default path with PATH (mode 0600).
|
|
417
|
+
--unsafe-print-token Print the token to stdout (legacy 0.2.x behavior). Use only for CI flows that
|
|
418
|
+
scrape stdout. The token will end up in scrollback / logs.
|
|
419
|
+
--force Overwrite the credentials file even if it already contains a different token/region.
|
|
420
|
+
--quiet With --write-config / default file-write, suppress the printed Claude-Code one-liner.
|
|
421
|
+
`
|
|
307
422
|
);
|
|
308
423
|
return 2;
|
|
309
424
|
}
|
|
@@ -332,7 +447,7 @@ async function runLogin(args) {
|
|
|
332
447
|
let result;
|
|
333
448
|
try {
|
|
334
449
|
if (pinnedRegion && !allowFallback) {
|
|
335
|
-
const { REGIONS } = await import("./dist-
|
|
450
|
+
const { REGIONS } = await import("./dist-YMZYFHZK.js");
|
|
336
451
|
const baseUrl = REGIONS[pinnedRegion];
|
|
337
452
|
const c = createClient({ region: pinnedRegion });
|
|
338
453
|
const token = await loginAt(baseUrl, email, password);
|
|
@@ -347,10 +462,11 @@ async function runLogin(args) {
|
|
|
347
462
|
return 1;
|
|
348
463
|
}
|
|
349
464
|
const config = {
|
|
465
|
+
email,
|
|
350
466
|
mcpServers: {
|
|
351
467
|
leadbay: {
|
|
352
468
|
command: "npx",
|
|
353
|
-
args: ["-y", "@leadbay/mcp@0.
|
|
469
|
+
args: ["-y", "@leadbay/mcp@0.3"],
|
|
354
470
|
env: {
|
|
355
471
|
LEADBAY_TOKEN: result.token,
|
|
356
472
|
LEADBAY_REGION: result.region
|
|
@@ -360,61 +476,150 @@ async function runLogin(args) {
|
|
|
360
476
|
};
|
|
361
477
|
const writeConfigPath = parseFlag(args, "write-config");
|
|
362
478
|
const quiet = hasFlag(args, "quiet");
|
|
479
|
+
const force = hasFlag(args, "force");
|
|
480
|
+
const unsafePrint = hasFlag(args, "unsafe-print-token");
|
|
481
|
+
const printTokenLegacy = hasFlag(args, "print-token");
|
|
482
|
+
if (printTokenLegacy && !unsafePrint) {
|
|
483
|
+
process.stderr.write(
|
|
484
|
+
"[leadbay-mcp warn] --print-token is deprecated since 0.3.0; renaming to --unsafe-print-token. The flag still works for one release.\n"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const printToStdout = unsafePrint || printTokenLegacy;
|
|
488
|
+
if (printToStdout) {
|
|
489
|
+
process.stderr.write(
|
|
490
|
+
`
|
|
491
|
+
Logged in to ${result.region.toUpperCase()} backend (${result.verified ? "verified" : "UNVERIFIED \u2014 check your email"}).
|
|
492
|
+
|
|
493
|
+
\u26A0\uFE0F About to print your bearer token to STDOUT.
|
|
494
|
+
Treat it like a password. Do NOT paste this into chat, screen-share, or commit it.
|
|
495
|
+
For safer handling, re-run without --unsafe-print-token (default writes a 0600 file).
|
|
496
|
+
|
|
497
|
+
Add this to your MCP client config:
|
|
498
|
+
|
|
499
|
+
`
|
|
500
|
+
);
|
|
501
|
+
process.stdout.write(JSON.stringify(config, null, 2) + "\n");
|
|
502
|
+
process.stderr.write(
|
|
503
|
+
`
|
|
504
|
+
Or for Claude Code (token included \u2014 same warning applies):
|
|
505
|
+
|
|
506
|
+
claude mcp add leadbay --scope user \\
|
|
507
|
+
--env LEADBAY_TOKEN=${result.token} \\
|
|
508
|
+
--env LEADBAY_REGION=${result.region} \\
|
|
509
|
+
-- npx -y @leadbay/mcp@0.3
|
|
510
|
+
|
|
511
|
+
Restart your MCP client to pick up the new server.
|
|
512
|
+
`
|
|
513
|
+
);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
let targetPath;
|
|
517
|
+
let usingLegacyPath = false;
|
|
363
518
|
if (writeConfigPath) {
|
|
364
|
-
|
|
365
|
-
|
|
519
|
+
targetPath = writeConfigPath;
|
|
520
|
+
} else {
|
|
521
|
+
const resolved = resolveDefaultCredentialsPath();
|
|
522
|
+
targetPath = resolved.path;
|
|
523
|
+
usingLegacyPath = resolved.legacy;
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
527
|
+
if (existsSync(targetPath) && !force) {
|
|
528
|
+
let existing;
|
|
529
|
+
try {
|
|
530
|
+
existing = JSON.parse(readFileSync(targetPath, "utf8"));
|
|
531
|
+
} catch {
|
|
532
|
+
process.stderr.write(
|
|
533
|
+
`leadbay-mcp login: ${targetPath} exists but is not valid JSON. Pass --force to overwrite.
|
|
534
|
+
`
|
|
535
|
+
);
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
const collision = checkLoginCollision(existing, email, result.region);
|
|
539
|
+
if (collision) {
|
|
540
|
+
process.stderr.write(
|
|
541
|
+
`leadbay-mcp login: refusing to overwrite ${targetPath} \u2014 ${collision}.
|
|
542
|
+
Pass --force to overwrite, or --write-config /some/other/path.json to keep both.
|
|
543
|
+
`
|
|
544
|
+
);
|
|
545
|
+
return 1;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (err) {
|
|
549
|
+
process.stderr.write(`leadbay-mcp login: ${err?.message ?? String(err)}
|
|
550
|
+
`);
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
let actualMode;
|
|
554
|
+
try {
|
|
555
|
+
const { writeFileSync, chmodSync, mkdirSync, renameSync, statSync, unlinkSync } = await import("fs");
|
|
556
|
+
const { dirname } = await import("path");
|
|
557
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
558
|
+
const tmp = targetPath + ".tmp." + process.pid;
|
|
559
|
+
writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", {
|
|
366
560
|
encoding: "utf8",
|
|
367
561
|
mode: 384
|
|
368
562
|
});
|
|
369
563
|
try {
|
|
370
|
-
chmodSync(
|
|
564
|
+
chmodSync(tmp, 384);
|
|
371
565
|
} catch {
|
|
372
566
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
567
|
+
renameSync(tmp, targetPath);
|
|
568
|
+
try {
|
|
569
|
+
actualMode = statSync(targetPath).mode & 511;
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
unlinkSync(tmp);
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
} catch (err) {
|
|
577
|
+
const code = err?.code;
|
|
578
|
+
if (code === "EACCES" || code === "EROFS" || code === "ENOENT") {
|
|
380
579
|
process.stderr.write(
|
|
381
|
-
`
|
|
382
|
-
|
|
383
|
-
|
|
580
|
+
`leadbay-mcp login: cannot write ${targetPath} (${code}).
|
|
581
|
+
Use --write-config /tmp/leadbay-mcp.json (or another writable path),
|
|
582
|
+
or --unsafe-print-token (last resort \u2014 token in stdout).
|
|
384
583
|
`
|
|
385
584
|
);
|
|
585
|
+
return 1;
|
|
386
586
|
}
|
|
387
|
-
process.stderr.write(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
Delete the config file once your MCP client has it loaded, or keep it 0600.
|
|
391
|
-
`
|
|
392
|
-
);
|
|
393
|
-
return 0;
|
|
587
|
+
process.stderr.write(`leadbay-mcp login: ${err?.message ?? String(err)}
|
|
588
|
+
`);
|
|
589
|
+
return 1;
|
|
394
590
|
}
|
|
591
|
+
const modeNote = actualMode === 384 ? "(mode 0600)" : actualMode !== void 0 ? `(mode 0${actualMode.toString(8)} \u2014 chmod 0600 failed; treat the file as sensitive)` : "(mode unknown)";
|
|
395
592
|
process.stderr.write(
|
|
396
593
|
`
|
|
397
594
|
Logged in to ${result.region.toUpperCase()} backend (${result.verified ? "verified" : "UNVERIFIED \u2014 check your email"}).
|
|
398
|
-
|
|
399
|
-
\u26A0\uFE0F About to print your bearer token to STDOUT.
|
|
400
|
-
Treat it like a password. Do NOT paste this into chat, screen-share, or commit it.
|
|
401
|
-
For safer handling, re-run with --write-config /path/to/config.json (writes 0600).
|
|
402
|
-
|
|
403
|
-
Add this to your MCP client config:
|
|
404
|
-
|
|
595
|
+
Wrote MCP config to ${targetPath} ${modeNote}. Token NOT printed to terminal.
|
|
405
596
|
`
|
|
406
597
|
);
|
|
407
|
-
|
|
598
|
+
if (usingLegacyPath) {
|
|
599
|
+
const newPath = computeFreshDefaultPath();
|
|
600
|
+
process.stderr.write(
|
|
601
|
+
`
|
|
602
|
+
[leadbay-mcp note] Used the legacy 0.2.x path ${targetPath}. The 0.3.0 default is ${newPath}.
|
|
603
|
+
Move the file there at your convenience (no code change required \u2014 both paths are read).
|
|
604
|
+
`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (!quiet) {
|
|
608
|
+
const quotedPath = `'${targetPath.replace(/'/g, `'\\''`)}'`;
|
|
609
|
+
process.stderr.write(
|
|
610
|
+
`
|
|
611
|
+
For Claude Code, run:
|
|
612
|
+
claude mcp add leadbay --scope user \\
|
|
613
|
+
--env LEADBAY_TOKEN=$(jq -r .mcpServers.leadbay.env.LEADBAY_TOKEN ${quotedPath}) \\
|
|
614
|
+
--env LEADBAY_REGION=${result.region} \\
|
|
615
|
+
-- npx -y @leadbay/mcp@0.3
|
|
616
|
+
`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
408
619
|
process.stderr.write(
|
|
409
620
|
`
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
claude mcp add leadbay \\
|
|
413
|
-
--env LEADBAY_TOKEN=${result.token} \\
|
|
414
|
-
--env LEADBAY_REGION=${result.region} \\
|
|
415
|
-
-- npx -y @leadbay/mcp@0.2
|
|
416
|
-
|
|
417
|
-
Restart your MCP client to pick up the new server.
|
|
621
|
+
TREAT THE TOKEN AS A SECRET. It grants full access to your Leadbay account.
|
|
622
|
+
Delete the config file once your MCP client has it loaded, or keep it 0600.
|
|
418
623
|
`
|
|
419
624
|
);
|
|
420
625
|
return 0;
|
|
@@ -450,9 +655,7 @@ async function loginAt(baseUrl, email, password) {
|
|
|
450
655
|
}
|
|
451
656
|
}
|
|
452
657
|
reject(
|
|
453
|
-
new Error(
|
|
454
|
-
`login failed (${res.statusCode}) at ${baseUrl}: ${raw.slice(0, 200)}`
|
|
455
|
-
)
|
|
658
|
+
new Error(formatLoginError(res.statusCode ?? 0, raw, baseUrl))
|
|
456
659
|
);
|
|
457
660
|
});
|
|
458
661
|
}
|
|
@@ -462,6 +665,31 @@ async function loginAt(baseUrl, email, password) {
|
|
|
462
665
|
r.end();
|
|
463
666
|
});
|
|
464
667
|
}
|
|
668
|
+
function detectClaudeDesktopMode(claudeSupportDir) {
|
|
669
|
+
const { existsSync, readFileSync } = require_("node:fs");
|
|
670
|
+
const { join } = require_("node:path");
|
|
671
|
+
const markers = [];
|
|
672
|
+
const legacy = existsSync(join(claudeSupportDir, "claude_desktop_config.json"));
|
|
673
|
+
if (existsSync(join(claudeSupportDir, "Claude Extensions"))) {
|
|
674
|
+
markers.push("Claude Extensions/");
|
|
675
|
+
}
|
|
676
|
+
if (existsSync(join(claudeSupportDir, "extensions-installations.json"))) {
|
|
677
|
+
markers.push("extensions-installations.json");
|
|
678
|
+
}
|
|
679
|
+
const cfgPath = join(claudeSupportDir, "config.json");
|
|
680
|
+
if (existsSync(cfgPath)) {
|
|
681
|
+
try {
|
|
682
|
+
const raw = readFileSync(cfgPath, "utf8");
|
|
683
|
+
const parsed = JSON.parse(raw);
|
|
684
|
+
if (parsed && typeof parsed === "object") {
|
|
685
|
+
const hasDxtKey = Object.keys(parsed).some((k) => k.startsWith("dxt:"));
|
|
686
|
+
if (hasDxtKey) markers.push("config.json (dxt:* keys)");
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { legacy, dxt: markers.length > 0, markers };
|
|
692
|
+
}
|
|
465
693
|
async function detectClients() {
|
|
466
694
|
const out = [];
|
|
467
695
|
const { existsSync } = await import("fs");
|
|
@@ -482,9 +710,16 @@ async function detectClients() {
|
|
|
482
710
|
out.push({ id: "claude-code", label: "Claude Code", detail: `${claudeBin} mcp add ...` });
|
|
483
711
|
}
|
|
484
712
|
const home = os.homedir();
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
713
|
+
const claudeSupportDir = process.platform === "win32" ? `${process.env.APPDATA ?? `${home}\\AppData\\Roaming`}\\Claude` : process.platform === "darwin" ? `${home}/Library/Application Support/Claude` : `${home}/.config/Claude`;
|
|
714
|
+
const cdPath = process.platform === "win32" ? `${claudeSupportDir}\\claude_desktop_config.json` : `${claudeSupportDir}/claude_desktop_config.json`;
|
|
715
|
+
const mode = detectClaudeDesktopMode(claudeSupportDir);
|
|
716
|
+
if (mode.legacy || mode.dxt) {
|
|
717
|
+
out.push({
|
|
718
|
+
id: "claude-desktop",
|
|
719
|
+
label: "Claude Desktop",
|
|
720
|
+
detail: cdPath,
|
|
721
|
+
mode
|
|
722
|
+
});
|
|
488
723
|
}
|
|
489
724
|
const cursorPath = process.platform === "win32" ? `${home}\\.cursor\\mcp.json` : `${home}/.cursor/mcp.json`;
|
|
490
725
|
if (existsSync(cursorPath)) {
|
|
@@ -539,19 +774,25 @@ async function readChoice(prompt, def = true) {
|
|
|
539
774
|
process.stdin.on("data", onData);
|
|
540
775
|
});
|
|
541
776
|
}
|
|
542
|
-
|
|
543
|
-
const cp = await import("child_process");
|
|
777
|
+
function buildClaudeCodeAddArgs(token, region, includeWrite) {
|
|
544
778
|
const args = [
|
|
545
779
|
"mcp",
|
|
546
780
|
"add",
|
|
547
781
|
"leadbay",
|
|
782
|
+
"--scope",
|
|
783
|
+
"user",
|
|
548
784
|
"--env",
|
|
549
785
|
`LEADBAY_TOKEN=${token}`,
|
|
550
786
|
"--env",
|
|
551
787
|
`LEADBAY_REGION=${region}`
|
|
552
788
|
];
|
|
553
|
-
if (includeWrite) args.push("--env", `LEADBAY_MCP_WRITE=
|
|
554
|
-
args.push("--", "npx", "-y", "@leadbay/mcp@0.
|
|
789
|
+
if (!includeWrite) args.push("--env", `LEADBAY_MCP_WRITE=0`);
|
|
790
|
+
args.push("--", "npx", "-y", "@leadbay/mcp@0.3");
|
|
791
|
+
return args;
|
|
792
|
+
}
|
|
793
|
+
async function installInClaudeCode(token, region, includeWrite) {
|
|
794
|
+
const cp = await import("child_process");
|
|
795
|
+
const args = buildClaudeCodeAddArgs(token, region, includeWrite);
|
|
555
796
|
return await new Promise((resolve) => {
|
|
556
797
|
const child = cp.spawn("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
557
798
|
let stderr = "";
|
|
@@ -591,10 +832,10 @@ async function installInJsonConfig(configPath, token, region, includeWrite) {
|
|
|
591
832
|
LEADBAY_TOKEN: token,
|
|
592
833
|
LEADBAY_REGION: region
|
|
593
834
|
};
|
|
594
|
-
if (includeWrite) env.LEADBAY_MCP_WRITE = "
|
|
835
|
+
if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
|
|
595
836
|
parsed.mcpServers.leadbay = {
|
|
596
837
|
command: "npx",
|
|
597
|
-
args: ["-y", "@leadbay/mcp@0.
|
|
838
|
+
args: ["-y", "@leadbay/mcp@0.3"],
|
|
598
839
|
env
|
|
599
840
|
};
|
|
600
841
|
const tmp = configPath + ".tmp";
|
|
@@ -617,10 +858,15 @@ async function runInstall(args) {
|
|
|
617
858
|
const email = parseFlag(args, "email");
|
|
618
859
|
if (!email) {
|
|
619
860
|
process.stderr.write(
|
|
620
|
-
"Usage: leadbay-mcp install --email you@example.com [--region us|fr]\n [--allow-region-fallback] [--
|
|
861
|
+
"Usage: leadbay-mcp install --email you@example.com [--region us|fr]\n [--allow-region-fallback] [--no-write]\n [--target claude-code,claude-desktop,cursor]\n [--yes] [--force-legacy]\n Mints a token AND registers the MCP server with your installed clients (at user scope).\n --target Comma-separated subset; default = all detected.\n --no-write Disable composite write tools (refine_prompt, report_outreach,\n adjust_audience, etc.). They are ON by default since 0.3.0;\n pass --no-write for read-only agents.\n --include-write (deprecated since 0.3.0; now a no-op \u2014 writes are on by default).\n --yes Don't ask before installing into each detected client.\n --force-legacy Write to claude_desktop_config.json even when Claude Desktop 2026\n DXT is detected. Not recommended \u2014 the app overwrites that file.\n Use the .dxt bundle instead: https://github.com/leadbay/leadclaw/releases\n"
|
|
621
862
|
);
|
|
622
863
|
return 2;
|
|
623
864
|
}
|
|
865
|
+
if (hasFlag(args, "include-write")) {
|
|
866
|
+
process.stderr.write(
|
|
867
|
+
"[leadbay-mcp warn] --include-write is the default since 0.3.0; the flag is now a no-op.\n Composite write tools (refine_prompt, report_outreach, adjust_audience, etc.) are ON by default.\n Pass --no-write to install in read-only mode.\n\n"
|
|
868
|
+
);
|
|
869
|
+
}
|
|
624
870
|
const regionArg = parseFlag(args, "region");
|
|
625
871
|
const regionEnv = process.env.LEADBAY_REGION;
|
|
626
872
|
const allowFallback = hasFlag(args, "allow-region-fallback");
|
|
@@ -663,9 +909,27 @@ async function runInstall(args) {
|
|
|
663
909
|
leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
664
910
|
`
|
|
665
911
|
);
|
|
666
|
-
for (const c of chosen)
|
|
912
|
+
for (const c of chosen) {
|
|
913
|
+
const dxtSuffix = c.mode?.dxt ? " [DXT \u2014 legacy write will be skipped]" : "";
|
|
914
|
+
process.stderr.write(` \u2022 ${c.label.padEnd(16)} ${c.detail}${dxtSuffix}
|
|
667
915
|
`);
|
|
916
|
+
}
|
|
668
917
|
process.stderr.write("\n");
|
|
918
|
+
const forceLegacy = hasFlag(args, "force-legacy");
|
|
919
|
+
const hasDxtClient = chosen.some((c) => c.id === "claude-desktop" && c.mode?.dxt);
|
|
920
|
+
if (hasDxtClient && !forceLegacy) {
|
|
921
|
+
const dxtClient = chosen.find((c) => c.id === "claude-desktop" && c.mode?.dxt);
|
|
922
|
+
process.stderr.write(
|
|
923
|
+
`\u26A0\uFE0F Claude Desktop 2026 DXT detected (markers: ${dxtClient.mode.markers.join(", ")}).
|
|
924
|
+
The legacy claude_desktop_config.json is UI-prefs-only in this version \u2014
|
|
925
|
+
Claude Desktop will overwrite any \`mcpServers\` block written there.
|
|
926
|
+
Install the Leadbay .dxt instead (drag-drop into Settings \u2192 Extensions):
|
|
927
|
+
https://github.com/leadbay/leadclaw/releases/latest
|
|
928
|
+
Override with --force-legacy to write the legacy file anyway (not recommended).
|
|
929
|
+
|
|
930
|
+
`
|
|
931
|
+
);
|
|
932
|
+
}
|
|
669
933
|
const password = await readPassword();
|
|
670
934
|
if (!password) {
|
|
671
935
|
process.stderr.write("leadbay-mcp install: empty password\n");
|
|
@@ -675,7 +939,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
675
939
|
let region;
|
|
676
940
|
try {
|
|
677
941
|
if (pinnedRegion && !allowFallback) {
|
|
678
|
-
const { REGIONS } = await import("./dist-
|
|
942
|
+
const { REGIONS } = await import("./dist-YMZYFHZK.js");
|
|
679
943
|
const baseUrl = REGIONS[pinnedRegion];
|
|
680
944
|
token = await loginAt(baseUrl, email, password);
|
|
681
945
|
region = pinnedRegion;
|
|
@@ -692,15 +956,28 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
692
956
|
process.stderr.write(`Logged in to ${region.toUpperCase()} backend.
|
|
693
957
|
|
|
694
958
|
`);
|
|
695
|
-
const includeWrite = hasFlag(args, "
|
|
696
|
-
if (
|
|
959
|
+
const includeWrite = !hasFlag(args, "no-write");
|
|
960
|
+
if (includeWrite) {
|
|
961
|
+
process.stderr.write(
|
|
962
|
+
"Composite write tools ENABLED (bulk_qualify_leads, enrich_titles, refine_prompt,\n report_outreach, adjust_audience, answer_clarification, import_leads).\n To disable: set LEADBAY_MCP_WRITE=0 in the env block, or re-run install with --no-write.\n\n"
|
|
963
|
+
);
|
|
964
|
+
} else {
|
|
697
965
|
process.stderr.write(
|
|
698
|
-
"
|
|
966
|
+
"Composite write tools DISABLED (read-only agent). Re-run without --no-write to enable.\n\n"
|
|
699
967
|
);
|
|
700
968
|
}
|
|
701
969
|
const skipPrompts = hasFlag(args, "yes");
|
|
702
970
|
const results = [];
|
|
703
971
|
for (const c of chosen) {
|
|
972
|
+
if (c.id === "claude-desktop" && c.mode?.dxt && !forceLegacy) {
|
|
973
|
+
results.push({
|
|
974
|
+
id: c.id,
|
|
975
|
+
label: c.label,
|
|
976
|
+
ok: false,
|
|
977
|
+
message: "skipped (DXT detected \u2014 install the .dxt bundle instead)"
|
|
978
|
+
});
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
704
981
|
const ok = skipPrompts || await readChoice(`Install into ${c.label} (${c.detail})?`, true);
|
|
705
982
|
if (!ok) {
|
|
706
983
|
results.push({ id: c.id, label: c.label, ok: false, message: "skipped by user" });
|
|
@@ -725,7 +1002,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
725
1002
|
process.stderr.write(
|
|
726
1003
|
`
|
|
727
1004
|
The token was written into client config files but never printed to your terminal.
|
|
728
|
-
Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.
|
|
1005
|
+
Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.3 doctor
|
|
729
1006
|
Restart your MCP client(s) to pick up the new server.
|
|
730
1007
|
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).
|
|
731
1008
|
`
|
|
@@ -800,7 +1077,7 @@ async function main() {
|
|
|
800
1077
|
const logger = makeStderrLogger(parseLogLevel(process.env.LEADBAY_LOG_LEVEL));
|
|
801
1078
|
const client = await resolveClientFromEnv(logger);
|
|
802
1079
|
const includeAdvanced = process.env.LEADBAY_MCP_ADVANCED === "1";
|
|
803
|
-
const includeWrite =
|
|
1080
|
+
const includeWrite = parseWriteEnv();
|
|
804
1081
|
const bulkTracker = await createDefaultBulkStore({ logger });
|
|
805
1082
|
const server = buildServer(client, {
|
|
806
1083
|
includeAdvanced,
|
|
@@ -832,5 +1109,11 @@ if (isEntrypoint) {
|
|
|
832
1109
|
});
|
|
833
1110
|
}
|
|
834
1111
|
export {
|
|
835
|
-
|
|
1112
|
+
buildClaudeCodeAddArgs,
|
|
1113
|
+
checkLoginCollision,
|
|
1114
|
+
computeFreshDefaultPath,
|
|
1115
|
+
detectClaudeDesktopMode,
|
|
1116
|
+
parseWriteEnv,
|
|
1117
|
+
resolveClientFromEnv,
|
|
1118
|
+
resolveDefaultCredentialsPath
|
|
836
1119
|
};
|