@leadbay/mcp 0.2.1 → 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 +84 -0
- package/MIGRATION.md +72 -0
- package/README.md +78 -27
- package/dist/bin.js +378 -83
- package/dist/{chunk-BGJ6JWIO.js → chunk-NED7ATJI.js} +1216 -28
- package/dist/{dist-PIXZN6N4.js → dist-YMZYFHZK.js} +15 -1
- package/package.json +2 -1
package/dist/bin.js
CHANGED
|
@@ -3,12 +3,16 @@ import {
|
|
|
3
3
|
compositeReadTools,
|
|
4
4
|
compositeWriteTools,
|
|
5
5
|
createClient,
|
|
6
|
+
createDefaultBulkStore,
|
|
7
|
+
formatLoginError,
|
|
6
8
|
granularReadTools,
|
|
7
9
|
granularWriteTools,
|
|
8
10
|
resolveRegion
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-NED7ATJI.js";
|
|
10
12
|
|
|
11
13
|
// src/bin.ts
|
|
14
|
+
import { realpathSync } from "fs";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
12
16
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
17
|
|
|
14
18
|
// src/server.ts
|
|
@@ -17,7 +21,44 @@ import {
|
|
|
17
21
|
CallToolRequestSchema,
|
|
18
22
|
ListToolsRequestSchema
|
|
19
23
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
-
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
|
+
}
|
|
21
62
|
function formatErrorForLLM(err) {
|
|
22
63
|
if (err && typeof err === "object" && err.error === true) {
|
|
23
64
|
const parts = [`${err.message}.`, err.hint];
|
|
@@ -42,13 +83,6 @@ function toolsListPayload(tools) {
|
|
|
42
83
|
}));
|
|
43
84
|
}
|
|
44
85
|
function buildServer(client, opts = {}) {
|
|
45
|
-
const server = new Server(
|
|
46
|
-
{ name: "leadbay", version: "0.2.0" },
|
|
47
|
-
{
|
|
48
|
-
capabilities: { tools: {} },
|
|
49
|
-
instructions: SERVER_INSTRUCTIONS
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
86
|
const exposedTools = [];
|
|
53
87
|
exposedTools.push(...compositeReadTools);
|
|
54
88
|
if (opts.includeWrite) {
|
|
@@ -66,6 +100,14 @@ function buildServer(client, opts = {}) {
|
|
|
66
100
|
toolByName.set(t.name, t);
|
|
67
101
|
}
|
|
68
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
|
+
);
|
|
69
111
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
70
112
|
tools: toolsListPayload([...toolByName.values()])
|
|
71
113
|
}));
|
|
@@ -85,7 +127,10 @@ function buildServer(client, opts = {}) {
|
|
|
85
127
|
}
|
|
86
128
|
const args = req.params.arguments ?? {};
|
|
87
129
|
try {
|
|
88
|
-
const result = await tool.execute(client, args, {
|
|
130
|
+
const result = await tool.execute(client, args, {
|
|
131
|
+
logger: opts.logger,
|
|
132
|
+
bulkTracker: opts.bulkTracker
|
|
133
|
+
});
|
|
89
134
|
if (result && typeof result === "object" && result.error === true) {
|
|
90
135
|
return {
|
|
91
136
|
content: [
|
|
@@ -113,7 +158,7 @@ function buildServer(client, opts = {}) {
|
|
|
113
158
|
|
|
114
159
|
// src/bin.ts
|
|
115
160
|
import { createRequire } from "module";
|
|
116
|
-
var VERSION = "0.
|
|
161
|
+
var VERSION = "0.3.0";
|
|
117
162
|
var HELP = `
|
|
118
163
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
119
164
|
|
|
@@ -132,15 +177,17 @@ USAGE
|
|
|
132
177
|
leadbay-mcp --help Print this help
|
|
133
178
|
|
|
134
179
|
ENV VARS
|
|
135
|
-
LEADBAY_TOKEN (required) Bearer token
|
|
180
|
+
LEADBAY_TOKEN (required) Bearer token (run \`leadbay-mcp install\` to mint one).
|
|
136
181
|
LEADBAY_REGION (optional) "us" or "fr". Auto-detected from /users/me if unset.
|
|
137
182
|
LEADBAY_BASE_URL (optional) Override API base URL (for staging/dev).
|
|
138
183
|
LEADBAY_MCP_ADVANCED (optional) Set to "1" to expose granular API tools alongside
|
|
139
184
|
the composite workflow tools. Most users don't need this.
|
|
140
|
-
LEADBAY_MCP_WRITE (optional)
|
|
141
|
-
report_outreach, adjust_audience,
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
144
191
|
LEADBAY_MOCK (optional) Set to "1" to serve all responses from on-disk fixtures
|
|
145
192
|
(no network, no real auth). Useful for agent-author dry-running.
|
|
146
193
|
GETs are matched against fixture JSON files; POSTs/DELETEs are
|
|
@@ -153,7 +200,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
|
|
|
153
200
|
"mcpServers": {
|
|
154
201
|
"leadbay": {
|
|
155
202
|
"command": "npx",
|
|
156
|
-
"args": ["-y", "@leadbay/mcp@0.
|
|
203
|
+
"args": ["-y", "@leadbay/mcp@0.3"],
|
|
157
204
|
"env": {
|
|
158
205
|
"LEADBAY_TOKEN": "lb_...",
|
|
159
206
|
"LEADBAY_REGION": "us"
|
|
@@ -187,9 +234,21 @@ function parseLogLevel(raw) {
|
|
|
187
234
|
if (raw === "debug" || raw === "info") return raw;
|
|
188
235
|
return "error";
|
|
189
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
|
+
}
|
|
190
249
|
function exitWithTokenError() {
|
|
191
250
|
process.stderr.write(
|
|
192
|
-
"leadbay-mcp: LEADBAY_TOKEN environment variable is required.\n 1.
|
|
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"
|
|
193
252
|
);
|
|
194
253
|
process.exit(1);
|
|
195
254
|
}
|
|
@@ -293,11 +352,73 @@ function parseFlag(args, name) {
|
|
|
293
352
|
function hasFlag(args, name) {
|
|
294
353
|
return args.some((a) => a === `--${name}`);
|
|
295
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
|
+
}
|
|
296
396
|
async function runLogin(args) {
|
|
297
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
|
+
})();
|
|
298
405
|
if (!email) {
|
|
299
406
|
process.stderr.write(
|
|
300
|
-
|
|
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
|
+
`
|
|
301
422
|
);
|
|
302
423
|
return 2;
|
|
303
424
|
}
|
|
@@ -326,7 +447,7 @@ async function runLogin(args) {
|
|
|
326
447
|
let result;
|
|
327
448
|
try {
|
|
328
449
|
if (pinnedRegion && !allowFallback) {
|
|
329
|
-
const { REGIONS } = await import("./dist-
|
|
450
|
+
const { REGIONS } = await import("./dist-YMZYFHZK.js");
|
|
330
451
|
const baseUrl = REGIONS[pinnedRegion];
|
|
331
452
|
const c = createClient({ region: pinnedRegion });
|
|
332
453
|
const token = await loginAt(baseUrl, email, password);
|
|
@@ -341,10 +462,11 @@ async function runLogin(args) {
|
|
|
341
462
|
return 1;
|
|
342
463
|
}
|
|
343
464
|
const config = {
|
|
465
|
+
email,
|
|
344
466
|
mcpServers: {
|
|
345
467
|
leadbay: {
|
|
346
468
|
command: "npx",
|
|
347
|
-
args: ["-y", "@leadbay/mcp@0.
|
|
469
|
+
args: ["-y", "@leadbay/mcp@0.3"],
|
|
348
470
|
env: {
|
|
349
471
|
LEADBAY_TOKEN: result.token,
|
|
350
472
|
LEADBAY_REGION: result.region
|
|
@@ -354,61 +476,150 @@ async function runLogin(args) {
|
|
|
354
476
|
};
|
|
355
477
|
const writeConfigPath = parseFlag(args, "write-config");
|
|
356
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;
|
|
357
518
|
if (writeConfigPath) {
|
|
358
|
-
|
|
359
|
-
|
|
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", {
|
|
360
560
|
encoding: "utf8",
|
|
361
561
|
mode: 384
|
|
362
562
|
});
|
|
363
563
|
try {
|
|
364
|
-
chmodSync(
|
|
564
|
+
chmodSync(tmp, 384);
|
|
365
565
|
} catch {
|
|
366
566
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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") {
|
|
374
579
|
process.stderr.write(
|
|
375
|
-
`
|
|
376
|
-
|
|
377
|
-
|
|
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).
|
|
378
583
|
`
|
|
379
584
|
);
|
|
585
|
+
return 1;
|
|
380
586
|
}
|
|
381
|
-
process.stderr.write(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
Delete the config file once your MCP client has it loaded, or keep it 0600.
|
|
385
|
-
`
|
|
386
|
-
);
|
|
387
|
-
return 0;
|
|
587
|
+
process.stderr.write(`leadbay-mcp login: ${err?.message ?? String(err)}
|
|
588
|
+
`);
|
|
589
|
+
return 1;
|
|
388
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)";
|
|
389
592
|
process.stderr.write(
|
|
390
593
|
`
|
|
391
594
|
Logged in to ${result.region.toUpperCase()} backend (${result.verified ? "verified" : "UNVERIFIED \u2014 check your email"}).
|
|
392
|
-
|
|
393
|
-
\u26A0\uFE0F About to print your bearer token to STDOUT.
|
|
394
|
-
Treat it like a password. Do NOT paste this into chat, screen-share, or commit it.
|
|
395
|
-
For safer handling, re-run with --write-config /path/to/config.json (writes 0600).
|
|
396
|
-
|
|
397
|
-
Add this to your MCP client config:
|
|
398
|
-
|
|
595
|
+
Wrote MCP config to ${targetPath} ${modeNote}. Token NOT printed to terminal.
|
|
399
596
|
`
|
|
400
597
|
);
|
|
401
|
-
|
|
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
|
+
}
|
|
402
619
|
process.stderr.write(
|
|
403
620
|
`
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
claude mcp add leadbay \\
|
|
407
|
-
--env LEADBAY_TOKEN=${result.token} \\
|
|
408
|
-
--env LEADBAY_REGION=${result.region} \\
|
|
409
|
-
-- npx -y @leadbay/mcp@0.2
|
|
410
|
-
|
|
411
|
-
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.
|
|
412
623
|
`
|
|
413
624
|
);
|
|
414
625
|
return 0;
|
|
@@ -444,9 +655,7 @@ async function loginAt(baseUrl, email, password) {
|
|
|
444
655
|
}
|
|
445
656
|
}
|
|
446
657
|
reject(
|
|
447
|
-
new Error(
|
|
448
|
-
`login failed (${res.statusCode}) at ${baseUrl}: ${raw.slice(0, 200)}`
|
|
449
|
-
)
|
|
658
|
+
new Error(formatLoginError(res.statusCode ?? 0, raw, baseUrl))
|
|
450
659
|
);
|
|
451
660
|
});
|
|
452
661
|
}
|
|
@@ -456,6 +665,31 @@ async function loginAt(baseUrl, email, password) {
|
|
|
456
665
|
r.end();
|
|
457
666
|
});
|
|
458
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
|
+
}
|
|
459
693
|
async function detectClients() {
|
|
460
694
|
const out = [];
|
|
461
695
|
const { existsSync } = await import("fs");
|
|
@@ -476,9 +710,16 @@ async function detectClients() {
|
|
|
476
710
|
out.push({ id: "claude-code", label: "Claude Code", detail: `${claudeBin} mcp add ...` });
|
|
477
711
|
}
|
|
478
712
|
const home = os.homedir();
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
});
|
|
482
723
|
}
|
|
483
724
|
const cursorPath = process.platform === "win32" ? `${home}\\.cursor\\mcp.json` : `${home}/.cursor/mcp.json`;
|
|
484
725
|
if (existsSync(cursorPath)) {
|
|
@@ -533,19 +774,25 @@ async function readChoice(prompt, def = true) {
|
|
|
533
774
|
process.stdin.on("data", onData);
|
|
534
775
|
});
|
|
535
776
|
}
|
|
536
|
-
|
|
537
|
-
const cp = await import("child_process");
|
|
777
|
+
function buildClaudeCodeAddArgs(token, region, includeWrite) {
|
|
538
778
|
const args = [
|
|
539
779
|
"mcp",
|
|
540
780
|
"add",
|
|
541
781
|
"leadbay",
|
|
782
|
+
"--scope",
|
|
783
|
+
"user",
|
|
542
784
|
"--env",
|
|
543
785
|
`LEADBAY_TOKEN=${token}`,
|
|
544
786
|
"--env",
|
|
545
787
|
`LEADBAY_REGION=${region}`
|
|
546
788
|
];
|
|
547
|
-
if (includeWrite) args.push("--env", `LEADBAY_MCP_WRITE=
|
|
548
|
-
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);
|
|
549
796
|
return await new Promise((resolve) => {
|
|
550
797
|
const child = cp.spawn("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
551
798
|
let stderr = "";
|
|
@@ -585,10 +832,10 @@ async function installInJsonConfig(configPath, token, region, includeWrite) {
|
|
|
585
832
|
LEADBAY_TOKEN: token,
|
|
586
833
|
LEADBAY_REGION: region
|
|
587
834
|
};
|
|
588
|
-
if (includeWrite) env.LEADBAY_MCP_WRITE = "
|
|
835
|
+
if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
|
|
589
836
|
parsed.mcpServers.leadbay = {
|
|
590
837
|
command: "npx",
|
|
591
|
-
args: ["-y", "@leadbay/mcp@0.
|
|
838
|
+
args: ["-y", "@leadbay/mcp@0.3"],
|
|
592
839
|
env
|
|
593
840
|
};
|
|
594
841
|
const tmp = configPath + ".tmp";
|
|
@@ -611,10 +858,15 @@ async function runInstall(args) {
|
|
|
611
858
|
const email = parseFlag(args, "email");
|
|
612
859
|
if (!email) {
|
|
613
860
|
process.stderr.write(
|
|
614
|
-
"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"
|
|
615
862
|
);
|
|
616
863
|
return 2;
|
|
617
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
|
+
}
|
|
618
870
|
const regionArg = parseFlag(args, "region");
|
|
619
871
|
const regionEnv = process.env.LEADBAY_REGION;
|
|
620
872
|
const allowFallback = hasFlag(args, "allow-region-fallback");
|
|
@@ -657,9 +909,27 @@ async function runInstall(args) {
|
|
|
657
909
|
leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
658
910
|
`
|
|
659
911
|
);
|
|
660
|
-
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}
|
|
661
915
|
`);
|
|
916
|
+
}
|
|
662
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
|
+
}
|
|
663
933
|
const password = await readPassword();
|
|
664
934
|
if (!password) {
|
|
665
935
|
process.stderr.write("leadbay-mcp install: empty password\n");
|
|
@@ -669,7 +939,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
669
939
|
let region;
|
|
670
940
|
try {
|
|
671
941
|
if (pinnedRegion && !allowFallback) {
|
|
672
|
-
const { REGIONS } = await import("./dist-
|
|
942
|
+
const { REGIONS } = await import("./dist-YMZYFHZK.js");
|
|
673
943
|
const baseUrl = REGIONS[pinnedRegion];
|
|
674
944
|
token = await loginAt(baseUrl, email, password);
|
|
675
945
|
region = pinnedRegion;
|
|
@@ -686,15 +956,28 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
686
956
|
process.stderr.write(`Logged in to ${region.toUpperCase()} backend.
|
|
687
957
|
|
|
688
958
|
`);
|
|
689
|
-
const includeWrite = hasFlag(args, "
|
|
690
|
-
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 {
|
|
691
965
|
process.stderr.write(
|
|
692
|
-
"
|
|
966
|
+
"Composite write tools DISABLED (read-only agent). Re-run without --no-write to enable.\n\n"
|
|
693
967
|
);
|
|
694
968
|
}
|
|
695
969
|
const skipPrompts = hasFlag(args, "yes");
|
|
696
970
|
const results = [];
|
|
697
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
|
+
}
|
|
698
981
|
const ok = skipPrompts || await readChoice(`Install into ${c.label} (${c.detail})?`, true);
|
|
699
982
|
if (!ok) {
|
|
700
983
|
results.push({ id: c.id, label: c.label, ok: false, message: "skipped by user" });
|
|
@@ -719,9 +1002,9 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
|
|
|
719
1002
|
process.stderr.write(
|
|
720
1003
|
`
|
|
721
1004
|
The token was written into client config files but never printed to your terminal.
|
|
722
|
-
Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.
|
|
1005
|
+
Verify with: LEADBAY_TOKEN=$(...) npx -y @leadbay/mcp@0.3 doctor
|
|
723
1006
|
Restart your MCP client(s) to pick up the new server.
|
|
724
|
-
If you ever leak the token,
|
|
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).
|
|
725
1008
|
`
|
|
726
1009
|
);
|
|
727
1010
|
return anyOk ? 0 : 1;
|
|
@@ -794,11 +1077,17 @@ async function main() {
|
|
|
794
1077
|
const logger = makeStderrLogger(parseLogLevel(process.env.LEADBAY_LOG_LEVEL));
|
|
795
1078
|
const client = await resolveClientFromEnv(logger);
|
|
796
1079
|
const includeAdvanced = process.env.LEADBAY_MCP_ADVANCED === "1";
|
|
797
|
-
const includeWrite =
|
|
798
|
-
const
|
|
1080
|
+
const includeWrite = parseWriteEnv();
|
|
1081
|
+
const bulkTracker = await createDefaultBulkStore({ logger });
|
|
1082
|
+
const server = buildServer(client, {
|
|
1083
|
+
includeAdvanced,
|
|
1084
|
+
includeWrite,
|
|
1085
|
+
logger,
|
|
1086
|
+
bulkTracker
|
|
1087
|
+
});
|
|
799
1088
|
const transport = new StdioServerTransport();
|
|
800
1089
|
logger.info?.(
|
|
801
|
-
`Starting MCP server v${VERSION} (advanced=${includeAdvanced}, write=${includeWrite}, baseUrl=${client.baseUrl})`
|
|
1090
|
+
`Starting MCP server v${VERSION} (advanced=${includeAdvanced}, write=${includeWrite}, baseUrl=${client.baseUrl}, bulk_store=${bulkTracker.durability})`
|
|
802
1091
|
);
|
|
803
1092
|
await server.connect(transport);
|
|
804
1093
|
}
|
|
@@ -806,8 +1095,8 @@ var isEntrypoint = (() => {
|
|
|
806
1095
|
try {
|
|
807
1096
|
const entry = process.argv[1];
|
|
808
1097
|
if (!entry) return false;
|
|
809
|
-
const
|
|
810
|
-
return
|
|
1098
|
+
const self = fileURLToPath(import.meta.url);
|
|
1099
|
+
return realpathSync(self) === realpathSync(entry);
|
|
811
1100
|
} catch {
|
|
812
1101
|
return false;
|
|
813
1102
|
}
|
|
@@ -820,5 +1109,11 @@ if (isEntrypoint) {
|
|
|
820
1109
|
});
|
|
821
1110
|
}
|
|
822
1111
|
export {
|
|
823
|
-
|
|
1112
|
+
buildClaudeCodeAddArgs,
|
|
1113
|
+
checkLoginCollision,
|
|
1114
|
+
computeFreshDefaultPath,
|
|
1115
|
+
detectClaudeDesktopMode,
|
|
1116
|
+
parseWriteEnv,
|
|
1117
|
+
resolveClientFromEnv,
|
|
1118
|
+
resolveDefaultCredentialsPath
|
|
824
1119
|
};
|