@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.
@@ -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.1";
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
- emit("done", "Install stopped.");
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
- emit("done", "Install stopped.");
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
- emit("done", "Install stopped.");
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
- emit(okCount > 0 ? "success" : "error", selectedHasOnlyManualSetup ? "Manual ChatGPT setup instructions ready." : `${okCount}/${selected.length} agent(s) installed, updated, or prepared.`);
1094
- emit("done", selectedHasOnlyManualSetup ? "Follow the manual setup instructions shown above." : "Restart your MCP client(s) to pick up the new server.");
1095
- res.end();
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
- emit("done", "Uninstall stopped.");
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
- emit("done", "Uninstall stopped.");
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
- emit("done", "Restart your MCP client(s) to complete the removal.");
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 command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
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
- if (req.method === "GET" && req.url === "/") {
1327
- const raw = pageHtml();
1328
- res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1329
- res.end(raw);
1330
- return;
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
- return await new Promise((resolve, reject) => {
1358
- server.once("error", reject);
1359
- server.listen(options.port ?? PORT, "127.0.0.1", async () => {
1360
- server.off("error", reject);
1361
- const address = server.address();
1362
- const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
1363
- expectedHost = `127.0.0.1:${port}`;
1364
- const url = `http://127.0.0.1:${port}/`;
1365
- process.stderr.write(`Leadbay MCP installer GUI: ${url}
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
- async function startUninstallerGui(options = {}) {
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 = pageUninstallHtml();
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 (req.method === "GET" && req.url === "/api/status") {
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 await new Promise((resolve, reject) => {
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 uninstaller GUI: ${url}
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((closeResolve, closeReject) => server.close((err) => err ? closeReject(err) : closeResolve())) });
1395
+ resolve({ url, done, close: () => new Promise((res, rej) => server.close((e) => e ? rej(e) : res())) });
1414
1396
  });
1415
1397
  });
1416
1398
  }
1417
- async function main() {
1418
- const uninstall = process.argv.includes("--uninstall");
1419
- const handle = uninstall ? await startUninstallerGui({ openBrowser: !process.argv.includes("--no-open") }) : await startInstallerGui({ openBrowser: !process.argv.includes("--no-open") });
1420
- await new Promise((resolve) => {
1421
- process.once("SIGINT", () => resolve());
1422
- process.once("SIGTERM", () => resolve());
1423
- });
1424
- await handle.close().catch(() => void 0);
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
- var isEntrypoint = (() => {
1427
- try {
1428
- const entry = process.argv[1];
1429
- if (!entry) return false;
1430
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
1431
- } catch {
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.1",
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 && electron --no-sandbox installer/electron-main.cjs",
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",