hermes/backend/mcp/dev.ts
Nico ccee249618 v0.6.42: Hermes chat UI — Vue3/TS/Vite, audio STT/TTS, sidebar rail, MCP event loop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:35:10 +02:00

142 lines
5.7 KiB
TypeScript

/**
* 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<any> {
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}`);
}
}