Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239#!/usr/bin/env node// fleet-mcp.mjs — a single source of truth for "what machines do I have access// to, their capabilities, designations, and live status." Any agent (Claude// Code, or a hermes-agent instance) can discover the fleet by name over MCP.//// It merges STATIC registry data (from the private vault) with LIVE liveness// (from `tailscale status --json`, matched by magic-DNS short name). The code is// public-safe (slab/toolchain style); the machine DATA — IPs, ssh keys, roles —// stays in the vault. Point $FLEET_MACHINES at the file; nothing sensitive is// baked into this source.//// Tools:// fleet_list — every machine: name, designation, online?, one-line caps// fleet_machine(name) — full detail for one machine + live status// fleet_find(capability) — which machines advertise a capability// fleet_designations — the controlled vocabularies (designations + capabilities)//// Hand-rolled JSON-RPC over stdio (newline-delimited), matching the house style// of slab/bin/frame-mcp.mjs + puppet-mcp.mjs — no SDK, node builtins only.import { execFile } from "node:child_process";import { existsSync, readFileSync } from "node:fs";import { homedir } from "node:os";import { join } from "node:path";import { httpPort, serveHttp, serveStdio } from "../mcp/http-front.mjs";
const HOME = homedir();// Prefer the normalized/proposal file, fall back to canonical. Override with env.const CANDIDATES = [ process.env.FLEET_MACHINES, join(HOME, "aesthetic-computer-vault", "machines.normalized.json"), join(HOME, "aesthetic-computer-vault", "machines.json"),].filter(Boolean);
const TAILSCALE_BINS = [ process.env.TAILSCALE_BIN, "/Applications/Tailscale.app/Contents/MacOS/Tailscale", "tailscale",].filter(Boolean);
// ── static registry ─────────────────────────────────────────────────────────function registryPath() { return CANDIDATES.find((p) => existsSync(p)) || null;}function loadRegistry() { const path = registryPath(); if (!path) throw new Error(`no machine registry found (looked in: ${CANDIDATES.join(", ")})`); const data = JSON.parse(readFileSync(path, "utf8")); return { path, machines: data.machines || {}, schema: data._schema || null };}
// A machine's tailnet short name: explicit tailscale.name, else status.key, else// the machine key itself (many keys already match the tailnet name).function tailKey(name, m) { return m.tailscale?.name || m.status?.key || name;}
// ── live status via tailscale ───────────────────────────────────────────────function tailscaleStatus() { return new Promise((resolve) => { const tryBin = (i) => { if (i >= TAILSCALE_BINS.length) return resolve(null); // no tailscale — degrade gracefully execFile(TAILSCALE_BINS[i], ["status", "--json"], { timeout: 8000, maxBuffer: 8 * 1024 * 1024 }, (err, stdout) => { if (err || !stdout) return tryBin(i + 1); try { const j = JSON.parse(stdout); const nodes = {}; const short = (p) => (p.DNSName || p.HostName || "").split(".")[0]; const add = (p, isSelf) => { if (!p) return; const k = short(p); if (!k) return; nodes[k] = { online: isSelf ? true : !!p.Online, self: !!isSelf, os: p.OS || null, ip: (p.TailscaleIPs || [])[0] || null, lastSeen: p.LastSeen && !p.LastSeen.startsWith("0001") ? p.LastSeen : null, }; }; add(j.Self, true); for (const p of Object.values(j.Peer || {})) add(p, false); resolve(nodes); } catch { tryBin(i + 1); } }); }; tryBin(0); });}
function liveFor(name, m, nodes) { const src = m.status?.source; if (src !== "tailscale" || !nodes) { return { source: src || "none", online: null, note: src && src !== "tailscale" ? `liveness via ${src} (not probed here)` : "no tailnet liveness source" }; } const node = nodes[tailKey(name, m)]; if (!node) return { source: "tailscale", online: null, note: `tailnet node '${tailKey(name, m)}' not found in status` }; return { source: "tailscale", online: node.online, self: node.self, ip: node.ip, lastSeen: node.lastSeen };}
function statusGlyph(live) { if (live.online === true) return live.self ? "🟢(self)" : "🟢"; if (live.online === false) return "⚪"; return "❔";}
// ── tool implementations ────────────────────────────────────────────────────async function toolList() { const { machines, path } = loadRegistry(); const nodes = await tailscaleStatus(); const lines = [`fleet (${Object.keys(machines).length} machines) — source: ${path}`, ""]; // group by designation for legibility const groups = {}; for (const [name, m] of Object.entries(machines)) (groups[m.designation || "unclassified"] ||= []).push([name, m]); for (const [designation, entries] of Object.entries(groups)) { lines.push(`── ${designation} ──`); for (const [name, m] of entries) { const live = liveFor(name, m, nodes); const caps = (m.capabilities || []).join(",") || "—"; const flag = m._review ? " ⚠review" : ""; lines.push(` ${statusGlyph(live)} ${(m.emoji || "").padEnd(2)} ${name} — [${caps}]${flag}`); } lines.push(""); } return [{ type: "text", text: lines.join("\n").trimEnd() }];}
async function toolMachine({ name }) { if (!name) throw new Error("`name` is required (see fleet_list)"); const { machines } = loadRegistry(); const m = machines[name] || Object.entries(machines).find(([k, v]) => v.tailscale?.name === name || k.toLowerCase() === name.toLowerCase())?.[1]; if (!m) throw new Error(`unknown machine: ${name} — see fleet_list`); const nodes = await tailscaleStatus(); const live = liveFor(name, m, nodes); return [{ type: "text", text: JSON.stringify({ ...m, _live: live }, null, 2) }];}
async function toolFind({ capability }) { if (!capability) throw new Error("`capability` is required (see fleet_designations for the vocabulary)"); const cap = capability.toLowerCase(); const { machines } = loadRegistry(); const nodes = await tailscaleStatus(); const hits = []; for (const [name, m] of Object.entries(machines)) { const caps = (m.capabilities || []).map((c) => c.toLowerCase()); if (!caps.includes(cap)) continue; const live = liveFor(name, m, nodes); hits.push(` ${statusGlyph(live)} ${name} (${m.designation || "?"}) — ${m.fleetRole || m.role || ""}`.trimEnd()); } const head = hits.length ? `machines with capability '${capability}':` : `no machines advertise capability '${capability}' — see fleet_designations for valid tags.`; return [{ type: "text", text: [head, ...hits].join("\n") }];}
async function toolDesignations() { const { schema } = loadRegistry(); if (!schema) return [{ type: "text", text: "registry has no _schema block (canonical machines.json?) — run toolchain/fleet/normalize-machines.mjs to generate the normalized file with vocabularies." }]; const L = ["DESIGNATIONS (primary fleet role, one per machine):"]; for (const [k, v] of Object.entries(schema.designations || {})) L.push(` ${k} — ${v}`); L.push("", "CAPABILITIES (composable tags):"); for (const [k, v] of Object.entries(schema.capabilities || {})) L.push(` ${k} — ${v}`); return [{ type: "text", text: L.join("\n") }];}
const TOOLS = [ { name: "fleet_list", description: "List every machine @jeffrey has access to, grouped by fleet designation, with a live online/offline glyph (🟢 online · ⚪ offline · ❔ unknown) and a one-line capability summary. The single source of truth for 'what machines do I have?'. Merges the private vault registry with live `tailscale status`.", inputSchema: { type: "object", properties: {} }, }, { name: "fleet_machine", description: "Full detail for ONE machine (hardware, os, ssh, repoPath, designation, capabilities, notes) plus its live tailnet status. Accepts the registry key or the tailnet short name. Names come from fleet_list.", inputSchema: { type: "object", properties: { name: { type: "string", description: "Machine name, e.g. poorslice, jastow, neo." } }, required: ["name"] }, }, { name: "fleet_find", description: "Find which machines can do X — returns every machine advertising a given capability tag (e.g. gpu, mlx, chromium-pool, build-macos, always-on), with live status. Use fleet_designations for the capability vocabulary.", inputSchema: { type: "object", properties: { capability: { type: "string", description: "A capability tag, e.g. gpu, mlx, macos-automation, chromium-pool, ffmpeg-render, always-on, git-remote." } }, required: ["capability"] }, }, { name: "fleet_designations", description: "Explain the controlled vocabularies: the fleet designations (agent-endpoint, compute-node, control, build, service, display, legacy) and the capability tags. Read this to know what fleet_find accepts.", inputSchema: { type: "object", properties: {} }, },];
const HANDLERS = { fleet_list: toolList, fleet_machine: toolMachine, fleet_find: toolFind, fleet_designations: toolDesignations,};
async function handleMessage(message) { const { id, method, params } = message; try { switch (method) { case "initialize": return { jsonrpc: "2.0", id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "fleet-mcp", version: "1.0.0" } } }; case "initialized": case "notifications/initialized": return null; case "ping": return { jsonrpc: "2.0", id, result: {} }; case "tools/list": return { jsonrpc: "2.0", id, result: { tools: TOOLS } }; case "tools/call": { const fn = HANDLERS[params?.name]; if (!fn) throw new Error(`Unknown tool: ${params?.name}`); const content = await fn(params.arguments || {}); return { jsonrpc: "2.0", id, result: { content } }; } default: return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } }; } } catch (error) { if (method === "tools/call") return { jsonrpc: "2.0", id, result: { isError: true, content: [{ type: "text", text: String(error.message || error) }] } }; return { jsonrpc: "2.0", id, error: { code: -32000, message: String(error.message || error) } }; }}
// Allow a quick CLI smoke test: `node fleet-mcp.mjs list|find <cap>|machine <name>`.if (process.argv[2] && process.argv[2] !== "--http") { const [cmd, arg] = process.argv.slice(2); const map = { list: () => toolList(), find: () => toolFind({ capability: arg }), machine: () => toolMachine({ name: arg }), designations: () => toolDesignations() }; const fn = map[cmd]; if (!fn) { console.error("usage: fleet-mcp.mjs [list | find <capability> | machine <name> | designations]"); process.exit(1); } fn().then((c) => console.log(c.map((x) => x.text).join("\n"))).catch((e) => { console.error(String(e.message || e)); process.exit(1); });} else { const port = httpPort(process.argv, 7776); if (port) serveHttp({ handleMessage, port, banner: "🛰 fleet-mcp shared daemon" }); else serveStdio({ handleMessage, banner: "🛰 fleet-mcp server started (fleet_list, fleet_machine, fleet_find, fleet_designations)" });}