claude-bridge-cli 2.0.24 → 2.0.26

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.
Files changed (2) hide show
  1. package/lib/bridge.js +57 -0
  2. package/package.json +1 -1
package/lib/bridge.js CHANGED
@@ -64,6 +64,8 @@ function dataDir() {
64
64
  return d;
65
65
  }
66
66
 
67
+ let mcpHealth = { at: 0, data: {} }; // cached `claude mcp list` health
68
+
67
69
  // Where always-on personal skills live for THIS machine (a plugin dir, since
68
70
  // ~/.claude/skills is not discovered by the CLI). Shared by the turn builder
69
71
  // (--plugin-dir) and the /skills endpoint so they can never disagree.
@@ -1212,6 +1214,61 @@ function startBridge(config) {
1212
1214
  // machine is active. Without it the request 404s, the host list comes back
1213
1215
  // empty, and the menu silently shows ONLY the CLI built-ins — which reads
1214
1216
  // as "this machine has no skills". Mirrors the Python bridge's contract.
1217
+ // ── /mcp — configured MCP servers for THIS machine ───────────────────────
1218
+ // Config read is instant; ?health=1 shells out to `claude mcp list`, which
1219
+ // actually connects to each server (seconds), so it's cached — but it's the
1220
+ // only way to distinguish Connected from needs-authentication.
1221
+ if (url.pathname === "/mcp") {
1222
+ const servers = {};
1223
+ const bases = new Set([process.env.CLAUDE_CONFIG_DIR || homeDir(), homeDir()]);
1224
+ for (const base of bases) {
1225
+ let cfg; try { cfg = JSON.parse(fs.readFileSync(path.join(base, ".claude.json"), "utf8")); }
1226
+ catch { continue; }
1227
+ for (const [n, spec] of Object.entries(cfg.mcpServers || {}))
1228
+ if (!(n in servers)) servers[n] = spec || {};
1229
+ for (const proj of Object.values(cfg.projects || {}))
1230
+ for (const [n, spec] of Object.entries((proj && proj.mcpServers) || {}))
1231
+ if (!(n in servers)) servers[n] = spec || {};
1232
+ }
1233
+ const out = Object.keys(servers).sort().map((n) => ({
1234
+ name: n,
1235
+ transport: servers[n].type || (servers[n].url ? "http" : "stdio"),
1236
+ target: servers[n].url || servers[n].command || "",
1237
+ }));
1238
+ // claude.ai CONNECTORS (Gmail, Drive, Gamma…) come from the signed-in
1239
+ // ACCOUNT, not local config, so neither mcpServers nor `claude mcp list`
1240
+ // shows them — yet sessions really do get their tools. Without this a
1241
+ // machine whose only MCP access is connectors reports "no servers".
1242
+ try {
1243
+ const cfg = JSON.parse(fs.readFileSync(path.join(homeDir(), ".claude.json"), "utf8"));
1244
+ for (const n of cfg.claudeAiMcpEverConnected || []) {
1245
+ if (!out.some((s) => s.name === n)) {
1246
+ out.push({ name: n, transport: "claude.ai connector", target: "", connector: true });
1247
+ }
1248
+ }
1249
+ } catch {}
1250
+ out.sort((a, b) => a.name.localeCompare(b.name));
1251
+ const want = url.searchParams.get("health");
1252
+ if (out.length && want && want !== "0" && want !== "false") {
1253
+ const now = Date.now();
1254
+ if (now - mcpHealth.at > 120000) {
1255
+ const states = {};
1256
+ try {
1257
+ const p = execSync(`${JSON.stringify(config.claudeBin)} mcp list`,
1258
+ { encoding: "utf8", timeout: 60000, stdio: ["ignore", "pipe", "pipe"] });
1259
+ for (const line of String(p).split("\n")) {
1260
+ const m = /^\s*([A-Za-z0-9._-]+):\s*(.*?)\s*-\s*(.+?)\s*$/.exec(line);
1261
+ if (m) states[m[1]] = m[3].replace(/[✔⚠○⏸]/g, "").trim();
1262
+ }
1263
+ } catch {}
1264
+ mcpHealth = { at: now, data: states };
1265
+ }
1266
+ for (const s of out) s.state = mcpHealth.data[s.name];
1267
+ }
1268
+ send(200, { servers: out });
1269
+ return;
1270
+ }
1271
+
1215
1272
  if (url.pathname === "/skills" || url.pathname.startsWith("/skills/")) {
1216
1273
  const skillsRoot = path.join(globalSkillsDir(), "skills");
1217
1274
  const nameOf = decodeURIComponent(url.pathname.slice("/skills/".length) || "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-bridge-cli",
3
- "version": "2.0.24",
3
+ "version": "2.0.26",
4
4
  "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
5
  "main": "lib/bridge.js",
6
6
  "bin": {