perf(cli): instant MCP startup — WS connects in background
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Move startClients() to run after server.connect(), not before.
MCP server is available to Claude Code in <0.5s instead of ~30s.
Tool handlers gracefully return errors until WS is ready.
Push event wiring happens in background callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-09 01:11:50 +01:00
parent c080bc517f
commit 4cb5a97512
2 changed files with 38 additions and 29 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "0.9.0", "version": "0.9.1",
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.", "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
"keywords": [ "keywords": [
"claude-code", "claude-code",

View File

@@ -1733,16 +1733,25 @@ Your message mode is "${messageMode}".
} }
}); });
// Start broker clients for every joined mesh BEFORE MCP connects. // Start MCP transport IMMEDIATELY so Claude Code discovers tools/prompts/resources
await startClients(config); // without waiting for WS connections. Tool handlers gracefully return errors when
// not connected. WS connects in background; push wiring happens once ready.
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);
// Wire WSS pushes → MCP channel notifications. Each inbound push on // Connect to broker WS in background — don't block MCP startup.
// any mesh's broker connection becomes a <channel source="claudemesh"> startClients(config).then(() => {
// system reminder injected into Claude Code's context. wirePushHandlers();
for (const client of allClients()) { }).catch(() => {
// Connect failed — clients are in reconnecting state, push wiring still needed
wirePushHandlers();
});
async function wirePushHandlers() {
// Wire WSS pushes → MCP channel notifications. Each inbound push on
// any mesh's broker connection becomes a <channel source="claudemesh">
// system reminder injected into Claude Code's context.
for (const client of allClients()) {
// Event-driven push: WS onPush fires immediately when a message arrives. // Event-driven push: WS onPush fires immediately when a message arrives.
// Claude Code's setNotificationHandler → enqueue → React useEffect pipeline // Claude Code's setNotificationHandler → enqueue → React useEffect pipeline
// processes notifications instantly (no polling needed on Claude's side). // processes notifications instantly (no polling needed on Claude's side).
@@ -1887,28 +1896,28 @@ Your message mode is "${messageMode}".
}); });
} catch { /* best effort */ } } catch { /* best effort */ }
}); });
} }
// Welcome notification: give Claude immediate context on connect. // Welcome notification: give Claude immediate context on connect.
// Triggers Claude to call mesh_info/list_peers without user input. // No delay needed — WS is already connected at this point.
setTimeout(async () => { const welcomeClient = allClients()[0];
const client = allClients()[0]; if (welcomeClient && welcomeClient.status === "open") {
if (!client || client.status !== "open") return; try {
try { const peers = await welcomeClient.listPeers();
const peers = await client.listPeers(); const peerNames = peers
const peerNames = peers .filter(p => p.displayName !== myName)
.filter(p => p.displayName !== myName) .map(p => p.displayName)
.map(p => p.displayName) .join(", ") || "none";
.join(", ") || "none"; await server.notification({
await server.notification({ method: "notifications/claude/channel",
method: "notifications/claude/channel", params: {
params: { content: `[system] Connected as ${myName} to mesh ${welcomeClient.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
content: `[system] Connected as ${myName} to mesh ${client.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`, meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug },
meta: { kind: "welcome", mesh_slug: client.meshSlug }, },
}, });
}); } catch { /* best effort */ }
} catch { /* best effort */ } }
}, 3_000); // 3s delay: let WS connect + hello_ack complete first } // end wirePushHandlers
// Event loop keepalive: Node.js stdout to a pipe is buffered. Without // Event loop keepalive: Node.js stdout to a pipe is buffered. Without
// periodic event loop activity, stdout.write() from WS callbacks may not // periodic event loop activity, stdout.write() from WS callbacks may not