/** * Process-wide registry of BrokerClient connections, keyed by meshId. * * The MCP server lazily starts a client per joined mesh on startup, * keeps them alive for the life of the process, and uses them to * service MCP tool calls. */ import { BrokerClient } from "./client"; import type { Config, JoinedMesh } from "../state/config"; import { env } from "../env"; const clients = new Map(); let configDisplayName: string | undefined; let configGroups: Config["groups"] = []; /** Ensure a BrokerClient exists + is connecting/open for this mesh. */ export async function ensureClient(mesh: JoinedMesh): Promise { const existing = clients.get(mesh.meshId); if (existing) return existing; const client = new BrokerClient(mesh, { debug: env.CLAUDEMESH_DEBUG, displayName: configDisplayName }); clients.set(mesh.meshId, client); try { await client.connect(); // Auto-join groups declared at launch time (--groups flag or config). for (const g of configGroups ?? []) { try { await client.joinGroup(g.name, g.role); } catch { /* best effort */ } } } catch { // Connect failed → client is in "reconnecting" state, leave it // wired so tool calls can surface the status. } return client; } /** Start clients for every joined mesh. Called once on MCP server start. */ export async function startClients(config: Config): Promise { configDisplayName = config.displayName; configGroups = config.groups ?? []; await Promise.allSettled(config.meshes.map(ensureClient)); } /** Look up a client by mesh slug (human-friendly) or meshId. */ export function findClient(needle: string): BrokerClient | null { // Try meshId first, then slug. const byId = clients.get(needle); if (byId) return byId; for (const c of clients.values()) { if (c.meshSlug === needle) return c; } return null; } /** All clients across all meshes. */ export function allClients(): BrokerClient[] { return [...clients.values()]; } /** Close every client (shutdown hook). */ export function stopAll(): void { for (const c of clients.values()) c.close(); clients.clear(); }