@leadbay/mcp 0.17.1 → 0.17.3
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 +16 -0
- package/README.md +38 -1
- package/dist/bin.js +844 -35
- package/dist/http-server.js +822 -30
- package/dist/installer-electron.js +168 -196
- package/dist/installer-gui.js +104 -113
- package/package.json +3 -4
package/dist/installer-gui.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// installer/installer-gui.ts
|
|
4
4
|
import { createServer as createServer2 } from "http";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
|
-
import { realpathSync } from "fs";
|
|
7
6
|
import { resolve as resolvePath } from "path";
|
|
8
7
|
import { fileURLToPath } from "url";
|
|
9
8
|
|
|
@@ -27,7 +26,7 @@ function buildClaudeCodeAddArgs(token, region, includeWrite, telemetryEnabled, l
|
|
|
27
26
|
if (localBinPath) {
|
|
28
27
|
args.push("--", "node", localBinPath);
|
|
29
28
|
} else {
|
|
30
|
-
args.push("--", "npx", "-y", "@leadbay/mcp@latest");
|
|
29
|
+
args.push("--", "npx", "-y", "-p", "@leadbay/mcp@latest", "leadbay-mcp");
|
|
31
30
|
}
|
|
32
31
|
return args;
|
|
33
32
|
}
|
|
@@ -107,7 +106,7 @@ async function installInJsonConfig(configPath, token, region, includeWrite, tele
|
|
|
107
106
|
LEADBAY_TELEMETRY_ENABLED: telemetryEnabled ? "true" : "false"
|
|
108
107
|
};
|
|
109
108
|
if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
|
|
110
|
-
parsed.mcpServers.leadbay = localBinPath ? { command: "node", args: [localBinPath], env } : { command: "npx", args: ["-y", "@leadbay/mcp@latest"], env };
|
|
109
|
+
parsed.mcpServers.leadbay = localBinPath ? { command: "node", args: [localBinPath], env } : { command: "npx", args: ["-y", "-p", "@leadbay/mcp@latest", "leadbay-mcp"], env };
|
|
111
110
|
const tmp = configPath + ".tmp";
|
|
112
111
|
writeFileSync(tmp, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
113
112
|
const { renameSync, chmodSync } = await import("fs");
|
|
@@ -874,7 +873,7 @@ async function oauthLogin(opts) {
|
|
|
874
873
|
}
|
|
875
874
|
|
|
876
875
|
// installer/installer-gui.ts
|
|
877
|
-
var VERSION = "0.17.
|
|
876
|
+
var VERSION = "0.17.3";
|
|
878
877
|
var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
879
878
|
var sessions = /* @__PURE__ */ new Map();
|
|
880
879
|
var OAUTH_BASE_URLS = {
|
|
@@ -1043,7 +1042,7 @@ async function install(body) {
|
|
|
1043
1042
|
].join("\n");
|
|
1044
1043
|
return { ok: results.some((result) => result.ok), output: sanitizeOutput(output), results };
|
|
1045
1044
|
}
|
|
1046
|
-
async function streamInstall(url, res) {
|
|
1045
|
+
async function streamInstall(url, res, onDone) {
|
|
1047
1046
|
cleanupSessions();
|
|
1048
1047
|
res.writeHead(200, {
|
|
1049
1048
|
"content-type": "text/event-stream; charset=utf-8",
|
|
@@ -1055,16 +1054,23 @@ async function streamInstall(url, res) {
|
|
|
1055
1054
|
const includeWrite = url.searchParams.get("write") !== "0";
|
|
1056
1055
|
const telemetryEnabled = url.searchParams.get("telemetry") !== "0";
|
|
1057
1056
|
const emit = (level, message) => sendSse(res, { level, message: sanitizeOutput(message) });
|
|
1057
|
+
const abort = (msg) => {
|
|
1058
|
+
emit("done", msg);
|
|
1059
|
+
res.end();
|
|
1060
|
+
};
|
|
1061
|
+
const finish = (msg) => {
|
|
1062
|
+
emit("done", msg);
|
|
1063
|
+
res.end();
|
|
1064
|
+
onDone?.();
|
|
1065
|
+
};
|
|
1058
1066
|
if (!session) {
|
|
1059
1067
|
emit("error", "Login expired. Go back and sign in again.");
|
|
1060
|
-
|
|
1061
|
-
res.end();
|
|
1068
|
+
abort("Install stopped.");
|
|
1062
1069
|
return;
|
|
1063
1070
|
}
|
|
1064
1071
|
if (!clientIds.length) {
|
|
1065
1072
|
emit("error", "Select at least one agent.");
|
|
1066
|
-
|
|
1067
|
-
res.end();
|
|
1073
|
+
abort("Install stopped.");
|
|
1068
1074
|
return;
|
|
1069
1075
|
}
|
|
1070
1076
|
emit("info", `Connected to ${session.accountLabel}.`);
|
|
@@ -1075,8 +1081,7 @@ async function streamInstall(url, res) {
|
|
|
1075
1081
|
const selectedHasOnlyManualSetup = selected.length > 0 && selected.every(isManualSetupClient);
|
|
1076
1082
|
if (!selected.length) {
|
|
1077
1083
|
emit("error", "No selected agents were detected on this machine.");
|
|
1078
|
-
|
|
1079
|
-
res.end();
|
|
1084
|
+
abort("Install stopped.");
|
|
1080
1085
|
return;
|
|
1081
1086
|
}
|
|
1082
1087
|
let okCount = 0;
|
|
@@ -1090,11 +1095,16 @@ async function streamInstall(url, res) {
|
|
|
1090
1095
|
emit("error", `${result.label}: ${result.message}`);
|
|
1091
1096
|
}
|
|
1092
1097
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1098
|
+
const summary = selectedHasOnlyManualSetup ? "Manual ChatGPT setup instructions ready." : `${okCount}/${selected.length} agent(s) installed, updated, or prepared.`;
|
|
1099
|
+
const closing = selectedHasOnlyManualSetup ? "Follow the manual setup instructions shown above." : "Restart your MCP client(s) to pick up the new server.";
|
|
1100
|
+
emit(okCount > 0 ? "success" : "error", summary);
|
|
1101
|
+
if (okCount > 0) {
|
|
1102
|
+
finish(closing);
|
|
1103
|
+
} else {
|
|
1104
|
+
abort(closing);
|
|
1105
|
+
}
|
|
1096
1106
|
}
|
|
1097
|
-
async function streamUninstall(url, res) {
|
|
1107
|
+
async function streamUninstall(url, res, onDone) {
|
|
1098
1108
|
res.writeHead(200, {
|
|
1099
1109
|
"content-type": "text/event-stream; charset=utf-8",
|
|
1100
1110
|
"cache-control": "no-cache, no-transform",
|
|
@@ -1102,18 +1112,25 @@ async function streamUninstall(url, res) {
|
|
|
1102
1112
|
});
|
|
1103
1113
|
const clientIds = (url.searchParams.get("clients") ?? "").split(",").filter(Boolean);
|
|
1104
1114
|
const emit = (level, message) => sendSse(res, { level, message });
|
|
1115
|
+
const abort = (msg) => {
|
|
1116
|
+
emit("done", msg);
|
|
1117
|
+
res.end();
|
|
1118
|
+
};
|
|
1119
|
+
const finish = (msg) => {
|
|
1120
|
+
emit("done", msg);
|
|
1121
|
+
res.end();
|
|
1122
|
+
onDone?.();
|
|
1123
|
+
};
|
|
1105
1124
|
if (!clientIds.length) {
|
|
1106
1125
|
emit("error", "Select at least one agent.");
|
|
1107
|
-
|
|
1108
|
-
res.end();
|
|
1126
|
+
abort("Uninstall stopped.");
|
|
1109
1127
|
return;
|
|
1110
1128
|
}
|
|
1111
1129
|
const detected = await detectClients();
|
|
1112
1130
|
const selected = detected.filter((c) => clientIds.includes(c.id));
|
|
1113
1131
|
if (!selected.length) {
|
|
1114
1132
|
emit("error", "No selected agents were detected on this machine.");
|
|
1115
|
-
|
|
1116
|
-
res.end();
|
|
1133
|
+
abort("Uninstall stopped.");
|
|
1117
1134
|
return;
|
|
1118
1135
|
}
|
|
1119
1136
|
let okCount = 0;
|
|
@@ -1137,8 +1154,7 @@ async function streamUninstall(url, res) {
|
|
|
1137
1154
|
}
|
|
1138
1155
|
}
|
|
1139
1156
|
emit(okCount > 0 ? "success" : "error", `${okCount}/${selected.length} agent(s) removed.`);
|
|
1140
|
-
|
|
1141
|
-
res.end();
|
|
1157
|
+
finish("Restart your MCP client(s) to complete the removal.");
|
|
1142
1158
|
}
|
|
1143
1159
|
function pageUninstallHtml() {
|
|
1144
1160
|
return `<!doctype html>
|
|
@@ -1310,67 +1326,43 @@ function pageHtml() {
|
|
|
1310
1326
|
}
|
|
1311
1327
|
async function openBrowser(url) {
|
|
1312
1328
|
const { spawn: spawn4 } = await import("child_process");
|
|
1313
|
-
const
|
|
1314
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
1315
|
-
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
1316
|
-
child.unref();
|
|
1317
|
-
}
|
|
1318
|
-
async function startInstallerGui(options = {}) {
|
|
1319
|
-
let expectedHost = `127.0.0.1:${(options.port ?? PORT) || 0}`;
|
|
1320
|
-
const server = createServer2(async (req, res) => {
|
|
1321
|
-
if (!isAllowedOrigin(req, expectedHost)) {
|
|
1322
|
-
sendJson(res, 403, { ok: false, error: "forbidden" });
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1329
|
+
const trySpawn = (command, args) => new Promise((resolve) => {
|
|
1325
1330
|
try {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
if (req.method === "GET" && req.url === "/api/status") {
|
|
1333
|
-
sendJson(res, 200, {
|
|
1334
|
-
os: formatInstallOsLabel(),
|
|
1335
|
-
hostedMcpUrl: HOSTED_MCP_URL,
|
|
1336
|
-
clients: await clientsWithConfiguredStatus()
|
|
1337
|
-
});
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
if (req.method === "POST" && req.url === "/api/oauth-login") {
|
|
1341
|
-
sendJson(res, 200, await loginWithOAuth());
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
if (req.method === "POST" && req.url === "/api/install") {
|
|
1345
|
-
sendJson(res, 200, await install(await readJson(req)));
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
if (req.method === "GET" && req.url?.startsWith("/api/install-stream")) {
|
|
1349
|
-
await streamInstall(new URL(req.url, "http://127.0.0.1"), res);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
sendJson(res, 404, { ok: false, error: "not found" });
|
|
1353
|
-
} catch (err) {
|
|
1354
|
-
sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
|
|
1331
|
+
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
1332
|
+
child.unref();
|
|
1333
|
+
child.on("error", () => resolve(false));
|
|
1334
|
+
child.on("close", (code) => resolve(code === 0));
|
|
1335
|
+
} catch {
|
|
1336
|
+
resolve(false);
|
|
1355
1337
|
}
|
|
1356
1338
|
});
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1339
|
+
if (process.platform === "darwin") {
|
|
1340
|
+
await trySpawn("open", [url]);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (process.platform === "win32") {
|
|
1344
|
+
await trySpawn("cmd", ["/c", "start", "", url]);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const candidates = ["xdg-open", "sensible-browser", "google-chrome", "chromium-browser", "firefox"];
|
|
1348
|
+
for (const cmd of candidates) {
|
|
1349
|
+
if (await trySpawn(cmd, [url])) return;
|
|
1350
|
+
}
|
|
1351
|
+
process.stderr.write(`
|
|
1352
|
+
Open this URL in your browser to continue:
|
|
1353
|
+
${url}
|
|
1354
|
+
|
|
1366
1355
|
`);
|
|
1367
|
-
if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
|
|
1368
|
-
resolve({ url, close: () => new Promise((closeResolve, closeReject) => server.close((err) => err ? closeReject(err) : closeResolve())) });
|
|
1369
|
-
});
|
|
1370
|
-
});
|
|
1371
1356
|
}
|
|
1372
|
-
|
|
1357
|
+
function makeGuiServer(options, pageContent, extraRoutes, logLabel) {
|
|
1373
1358
|
let expectedHost = `127.0.0.1:${(options.port ?? PORT) || 0}`;
|
|
1359
|
+
let resolveDone;
|
|
1360
|
+
const done = new Promise((r) => {
|
|
1361
|
+
resolveDone = r;
|
|
1362
|
+
});
|
|
1363
|
+
const onDone = () => setTimeout(() => {
|
|
1364
|
+
resolveDone();
|
|
1365
|
+
}, 1500);
|
|
1374
1366
|
const server = createServer2(async (req, res) => {
|
|
1375
1367
|
if (!isAllowedOrigin(req, expectedHost)) {
|
|
1376
1368
|
sendJson(res, 403, { ok: false, error: "forbidden" });
|
|
@@ -1378,28 +1370,18 @@ async function startUninstallerGui(options = {}) {
|
|
|
1378
1370
|
}
|
|
1379
1371
|
try {
|
|
1380
1372
|
if (req.method === "GET" && req.url === "/") {
|
|
1381
|
-
const raw =
|
|
1373
|
+
const raw = pageContent();
|
|
1382
1374
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
|
|
1383
1375
|
res.end(raw);
|
|
1384
1376
|
return;
|
|
1385
1377
|
}
|
|
1386
|
-
if (
|
|
1387
|
-
sendJson(res, 200, {
|
|
1388
|
-
os: formatInstallOsLabel(),
|
|
1389
|
-
clients: await clientsWithConfiguredStatus()
|
|
1390
|
-
});
|
|
1391
|
-
return;
|
|
1392
|
-
}
|
|
1393
|
-
if (req.method === "GET" && req.url?.startsWith("/api/uninstall-stream")) {
|
|
1394
|
-
await streamUninstall(new URL(req.url, "http://127.0.0.1"), res);
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1378
|
+
if (await extraRoutes(req, res, onDone)) return;
|
|
1397
1379
|
sendJson(res, 404, { ok: false, error: "not found" });
|
|
1398
1380
|
} catch (err) {
|
|
1399
1381
|
sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
|
|
1400
1382
|
}
|
|
1401
1383
|
});
|
|
1402
|
-
return
|
|
1384
|
+
return new Promise((resolve, reject) => {
|
|
1403
1385
|
server.once("error", reject);
|
|
1404
1386
|
server.listen(options.port ?? PORT, "127.0.0.1", async () => {
|
|
1405
1387
|
server.off("error", reject);
|
|
@@ -1407,37 +1389,46 @@ async function startUninstallerGui(options = {}) {
|
|
|
1407
1389
|
const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
|
|
1408
1390
|
expectedHost = `127.0.0.1:${port}`;
|
|
1409
1391
|
const url = `http://127.0.0.1:${port}/`;
|
|
1410
|
-
process.stderr.write(`Leadbay MCP
|
|
1392
|
+
process.stderr.write(`Leadbay MCP ${logLabel} GUI: ${url}
|
|
1411
1393
|
`);
|
|
1412
1394
|
if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
|
|
1413
|
-
resolve({ url, close: () => new Promise((
|
|
1395
|
+
resolve({ url, done, close: () => new Promise((res, rej) => server.close((e) => e ? rej(e) : res())) });
|
|
1414
1396
|
});
|
|
1415
1397
|
});
|
|
1416
1398
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1399
|
+
function startInstallerGui(options = {}) {
|
|
1400
|
+
return makeGuiServer(options, pageHtml, async (req, res, onDone) => {
|
|
1401
|
+
if (req.method === "GET" && req.url === "/api/status") {
|
|
1402
|
+
sendJson(res, 200, { os: formatInstallOsLabel(), hostedMcpUrl: HOSTED_MCP_URL, clients: await clientsWithConfiguredStatus() });
|
|
1403
|
+
return true;
|
|
1404
|
+
}
|
|
1405
|
+
if (req.method === "POST" && req.url === "/api/oauth-login") {
|
|
1406
|
+
sendJson(res, 200, await loginWithOAuth());
|
|
1407
|
+
return true;
|
|
1408
|
+
}
|
|
1409
|
+
if (req.method === "POST" && req.url === "/api/install") {
|
|
1410
|
+
sendJson(res, 200, await install(await readJson(req)));
|
|
1411
|
+
return true;
|
|
1412
|
+
}
|
|
1413
|
+
if (req.method === "GET" && req.url?.startsWith("/api/install-stream")) {
|
|
1414
|
+
await streamInstall(new URL(req.url, "http://127.0.0.1"), res, onDone);
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
return false;
|
|
1418
|
+
}, "installer");
|
|
1425
1419
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1420
|
+
function startUninstallerGui(options = {}) {
|
|
1421
|
+
return makeGuiServer(options, pageUninstallHtml, async (req, res, onDone) => {
|
|
1422
|
+
if (req.method === "GET" && req.url === "/api/status") {
|
|
1423
|
+
sendJson(res, 200, { os: formatInstallOsLabel(), clients: await clientsWithConfiguredStatus() });
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
if (req.method === "GET" && req.url?.startsWith("/api/uninstall-stream")) {
|
|
1427
|
+
await streamUninstall(new URL(req.url, "http://127.0.0.1"), res, onDone);
|
|
1428
|
+
return true;
|
|
1429
|
+
}
|
|
1432
1430
|
return false;
|
|
1433
|
-
}
|
|
1434
|
-
})();
|
|
1435
|
-
if (isEntrypoint) {
|
|
1436
|
-
main().catch((err) => {
|
|
1437
|
-
process.stderr.write(`leadbay-mcp-installer: ${err?.message ?? err}
|
|
1438
|
-
`);
|
|
1439
|
-
process.exit(1);
|
|
1440
|
-
});
|
|
1431
|
+
}, "uninstaller");
|
|
1441
1432
|
}
|
|
1442
1433
|
export {
|
|
1443
1434
|
install,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|
|
@@ -28,9 +28,8 @@
|
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:smoke": "vitest run --config vitest.smoke.config.ts",
|
|
30
30
|
"prepublishOnly": "pnpm run build && pnpm run typecheck && pnpm run test",
|
|
31
|
-
"installer": "pnpm run build &&
|
|
32
|
-
"installer:gui": "pnpm run installer"
|
|
33
|
-
"installer:gui:web": "pnpm run build && node dist/installer-gui.js"
|
|
31
|
+
"installer": "pnpm run build && node dist/installer-electron.js",
|
|
32
|
+
"installer:gui": "pnpm run installer"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
35
|
"@hono/node-server": "^1.13.7",
|