142 lines
5.7 KiB
TypeScript
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}`);
|
|
}
|
|
}
|