From 8dbb2286dc9e5d7ec3a81498565cba7e5e65c7d7 Mon Sep 17 00:00:00 2001 From: jango-blockchained Date: Mon, 10 Feb 2025 03:36:42 +0100 Subject: [PATCH] feat: Enhance MCP tool execution and device listing with advanced filtering - Refactor MCP execution endpoint to improve error handling and result reporting - Update health check endpoint with MCP version and supported tools - Extend list_devices tool with optional domain, area, and floor filtering - Improve device listing response with more detailed device metadata - Standardize tool import and initialization in main index file --- src/index.ts | 115 ++++++++++++++------------------- src/tools/list-devices.tool.ts | 43 ++++++++++-- 2 files changed, 85 insertions(+), 73 deletions(-) diff --git a/src/index.ts b/src/index.ts index c223778..65a0e3c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,18 @@ import { speechService } from "./speech/index.js"; import { APP_CONFIG } from "./config/app.config.js"; import { loadEnvironmentVariables } from "./config/loadEnv.js"; import { MCP_SCHEMA } from "./mcp/schema.js"; +import { + listDevicesTool, + controlTool, + subscribeEventsTool, + getSSEStatsTool, + automationConfigTool, + addonTool, + packageTool, + sceneTool, + notifyTool, + historyTool, +} from "./tools/index.js"; // Load environment variables based on NODE_ENV await loadEnvironmentVariables(); @@ -46,67 +58,18 @@ export interface Tool { } // Array to store tools -const tools: Tool[] = []; - -// Define the list devices tool -const listDevicesTool: Tool = { - name: "list_devices", - description: "List all available Home Assistant devices", - parameters: z.object({}), - execute: async () => { - try { - const devices = await list_devices(); - return { - success: true, - devices, - }; - } catch (error) { - return { - success: false, - message: - error instanceof Error ? error.message : "Unknown error occurred", - }; - } - }, -}; - -// Add tools to the array -tools.push(listDevicesTool); - -// Add the Home Assistant control tool -const controlTool: Tool = { - name: "control", - description: "Control Home Assistant devices and services", - parameters: z.object({ - command: z.enum([ - ...commonCommands, - ...coverCommands, - ...climateCommands, - ] as [string, ...string[]]), - entity_id: z.string().describe("The ID of the entity to control"), - }), - execute: async (params: { command: Command; entity_id: string }) => { - try { - const [domain] = params.entity_id.split("."); - await call_service(domain, params.command, { - entity_id: params.entity_id, - }); - return { - success: true, - message: `Command ${params.command} executed successfully on ${params.entity_id}`, - }; - } catch (error) { - return { - success: false, - message: - error instanceof Error ? error.message : "Unknown error occurred", - }; - } - }, -}; - -// Add the control tool to the array -tools.push(controlTool); +const tools: Tool[] = [ + listDevicesTool, + controlTool, + subscribeEventsTool, + getSSEStatsTool, + automationConfigTool, + addonTool, + packageTool, + sceneTool, + notifyTool, + historyTool, +]; // Initialize Elysia app with middleware const app = new Elysia() @@ -119,24 +82,40 @@ const app = new Elysia() .use(errorHandler); // Mount API routes -app.get("/api/mcp", () => MCP_SCHEMA); -app.post("/api/mcp/execute", async ({ body }: { body: { tool: string; parameters: Record } }) => { - const { tool: toolName, parameters } = body; +app.get("/api/mcp/schema", () => MCP_SCHEMA); + +app.post("/api/mcp/execute", async ({ body }: { body: { name: string; parameters: Record } }) => { + const { name: toolName, parameters } = body; const tool = tools.find((t) => t.name === toolName); + if (!tool) { return { success: false, message: `Tool '${toolName}' not found`, }; } - return await tool.execute(parameters); + + try { + const result = await tool.execute(parameters); + return { + success: true, + result, + }; + } catch (error) { + return { + success: false, + message: error instanceof Error ? error.message : "Unknown error occurred", + }; + } }); -// Health check endpoint -app.get("/health", () => ({ +// Health check endpoint with MCP info +app.get("/api/mcp/health", () => ({ status: "ok", timestamp: new Date().toISOString(), - version: "0.1.0", + version: "1.0.0", + mcp_version: "1.0", + supported_tools: tools.map(t => t.name), speech_enabled: APP_CONFIG.SPEECH.ENABLED, wake_word_enabled: APP_CONFIG.SPEECH.WAKE_WORD_ENABLED, speech_to_text_enabled: APP_CONFIG.SPEECH.SPEECH_TO_TEXT_ENABLED, diff --git a/src/tools/list-devices.tool.ts b/src/tools/list-devices.tool.ts index fac1427..618431b 100644 --- a/src/tools/list-devices.tool.ts +++ b/src/tools/list-devices.tool.ts @@ -6,8 +6,26 @@ import { HassState } from "../types/index.js"; export const listDevicesTool: Tool = { name: "list_devices", description: "List all available Home Assistant devices", - parameters: z.object({}).describe("No parameters required"), - execute: async () => { + parameters: z.object({ + domain: z.enum([ + "light", + "climate", + "alarm_control_panel", + "cover", + "switch", + "contact", + "media_player", + "fan", + "lock", + "vacuum", + "scene", + "script", + "camera", + ]).optional(), + area: z.string().optional(), + floor: z.string().optional(), + }).describe("Filter devices by domain, area, or floor"), + execute: async (params: { domain?: string; area?: string; floor?: string }) => { try { const response = await fetch(`${APP_CONFIG.HASS_HOST}/api/states`, { headers: { @@ -21,10 +39,23 @@ export const listDevicesTool: Tool = { } const states = (await response.json()) as HassState[]; + let filteredStates = states; + + // Apply filters + if (params.domain) { + filteredStates = filteredStates.filter(state => state.entity_id.startsWith(`${params.domain}.`)); + } + if (params.area) { + filteredStates = filteredStates.filter(state => state.attributes?.area_id === params.area); + } + if (params.floor) { + filteredStates = filteredStates.filter(state => state.attributes?.floor === params.floor); + } + const devices: Record = {}; // Group devices by domain - states.forEach(state => { + filteredStates.forEach(state => { const [domain] = state.entity_id.split('.'); if (!devices[domain]) { devices[domain] = []; @@ -47,12 +78,14 @@ export const listDevicesTool: Tool = { sample: entities.slice(0, 2).map(e => ({ id: e.entity_id, state: e.state, - name: e.attributes?.friendly_name || e.entity_id + name: e.attributes?.friendly_name || e.entity_id, + area: e.attributes?.area_id, + floor: e.attributes?.floor, })) }; }); - const totalDevices = states.length; + const totalDevices = filteredStates.length; const deviceTypes = Object.keys(devices); const deviceSummary = {