/** * mcp/dev.ts — Dev server health and status tools */ import { gatewayRequest } from '../gateway.ts'; import { waitForEvent, getEvents, getActiveMcpKey, broadcastToBrowsers } from './events.ts'; export const tools = [ { name: "dev_health", description: "Check Hermes backend health: gateway, sessions, agents.", inputSchema: { type: "object" as const, properties: { env: { type: "string", enum: ["dev", "prod"], description: "Environment (default: dev)" }, }, }, }, { name: "dev_vite_status", description: "Check if Vite dev server is running.", inputSchema: { type: "object" as const, properties: {}, }, }, { name: "dev_chat_send", description: "Send a chat message to an agent session via the gateway. Use for internal messaging/testing.", inputSchema: { type: "object" as const, properties: { sessionKey: { type: "string", description: "Session key (e.g. agent:titan:web:nico)" }, message: { type: "string", description: "Message text to send" }, }, required: ["sessionKey", "message"], }, }, { name: "dev_subscribe", description: "Long-poll for events from Hermes (system access approvals, deploy status, etc.). Blocks until an event arrives or timeout. Use in background tasks for push notifications.", inputSchema: { type: "object" as const, properties: { timeout: { type: "number", description: "Max wait time in ms (default 30000, max 55000)" }, }, }, }, { name: "dev_events", description: "Get recent queued events without blocking. Useful for catching up after a gap.", inputSchema: { type: "object" as const, properties: { count: { type: "number", description: "Max events to return (default 10)" }, }, }, }, { name: "dev_push_state", description: "Push state to all connected browser sessions via WebSocket. Used for real-time UI updates (counter, notifications, etc.).", inputSchema: { type: "object" as const, properties: { type: { type: "string", description: "Message type (e.g. 'counter_update')" }, data: { type: "object", description: "Payload data to send" }, }, required: ["type", "data"], }, }, ]; export async function handle(name: string, args: any): Promise { switch (name) { case "dev_health": { const port = args.env === "prod" ? 3001 : 3003; try { const res = await fetch(`http://localhost:${port}/health`); const data = await res.json(); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (e: any) { return { content: [{ type: "text", text: `Backend unreachable on port ${port}: ${e.message}` }] }; } } case "dev_vite_status": { try { const proc = Bun.spawn(["pgrep", "-f", "vite"], { stdout: "pipe", stderr: "pipe" }); const stdout = await new Response(proc.stdout).text(); const pids = stdout.trim().split("\n").filter(Boolean); return { content: [{ type: "text", text: pids.length > 0 ? JSON.stringify({ running: true, pids, count: pids.length }, null, 2) : "Vite is NOT running", }], }; } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }] }; } } case "dev_chat_send": { const { sessionKey, message } = args; if (!sessionKey || !message) return { content: [{ type: "text", text: "sessionKey and message are required" }] }; try { const idempotencyKey = crypto.randomUUID(); const res = await gatewayRequest("chat.send", { sessionKey, message, idempotencyKey }); return { content: [{ type: "text", text: JSON.stringify({ ok: true, sessionKey, idempotencyKey, response: res }, null, 2) }] }; } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }] }; } } case "dev_subscribe": { const mcpKey = getActiveMcpKey(); if (!mcpKey) return { content: [{ type: "text", text: JSON.stringify({ type: "error", message: "No MCP key context" }) }] }; const timeout = Math.min(args.timeout ?? 30000, 55000); const event = await waitForEvent(mcpKey, timeout); if (!event) return { content: [{ type: "text", text: JSON.stringify({ type: "timeout", message: "No events within timeout" }) }] }; return { content: [{ type: "text", text: JSON.stringify(event, null, 2) }] }; } case "dev_push_state": { broadcastToBrowsers({ type: args.type, ...args.data }); return { content: [{ type: "text", text: JSON.stringify({ ok: true, type: args.type }) }] }; } case "dev_events": { const mcpKey = getActiveMcpKey(); if (!mcpKey) return { content: [{ type: "text", text: "[]" }] }; const events = getEvents(mcpKey, args.count ?? 10); return { content: [{ type: "text", text: JSON.stringify(events, null, 2) }] }; } default: throw new Error(`Unknown dev tool: ${name}`); } }