import { get_hass } from './hass/index.js'; import { LiteMCP } from 'litemcp'; import { z } from 'zod'; import { DomainSchema } from './schemas.js'; // Configuration const HASS_HOST = process.env.HASS_HOST || 'http://192.168.178.63:8123'; const HASS_TOKEN = process.env.HASS_TOKEN; interface CommandParams { command: string; entity_id: string; // Common parameters state?: string; // Light parameters brightness?: number; color_temp?: number; rgb_color?: [number, number, number]; // Cover parameters position?: number; tilt_position?: number; // Climate parameters temperature?: number; target_temp_high?: number; target_temp_low?: number; hvac_mode?: string; fan_mode?: string; humidity?: number; } const commonCommands = ['turn_on', 'turn_off', 'toggle'] as const; const coverCommands = [...commonCommands, 'open', 'close', 'stop', 'set_position', 'set_tilt_position'] as const; const climateCommands = [...commonCommands, 'set_temperature', 'set_hvac_mode', 'set_fan_mode', 'set_humidity'] as const; async function main() { const hass = await get_hass(); // Create MCP server const server = new LiteMCP('home-assistant', '0.1.0'); // Add the list devices tool server.addTool({ name: 'list_devices', description: 'List all available Home Assistant devices', parameters: z.object({}), execute: async () => { try { const response = await fetch(`${HASS_HOST}/api/states`, { headers: { Authorization: `Bearer ${HASS_TOKEN}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to fetch devices: ${response.statusText}`); } const states = await response.json(); const devices = states.reduce((acc: any, state: any) => { const domain = state.entity_id.split('.')[0]; if (!acc[domain]) { acc[domain] = []; } acc[domain].push({ entity_id: state.entity_id, state: state.state, attributes: state.attributes, }); return acc; }, {}); return { success: true, devices, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : 'Unknown error occurred', }; } }, }); // Add the Home Assistant control tool server.addTool({ name: 'control', description: 'Control Home Assistant devices and services', parameters: z.object({ command: z.enum([...commonCommands, ...coverCommands, ...climateCommands]) .describe('The command to execute'), entity_id: z.string().describe('The entity ID to control'), // Common parameters state: z.string().optional().describe('The desired state for the entity'), // Light parameters brightness: z.number().min(0).max(255).optional() .describe('Brightness level for lights (0-255)'), color_temp: z.number().optional() .describe('Color temperature for lights'), rgb_color: z.tuple([z.number(), z.number(), z.number()]).optional() .describe('RGB color values'), // Cover parameters position: z.number().min(0).max(100).optional() .describe('Position for covers (0-100)'), tilt_position: z.number().min(0).max(100).optional() .describe('Tilt position for covers (0-100)'), // Climate parameters temperature: z.number().optional() .describe('Target temperature for climate devices'), target_temp_high: z.number().optional() .describe('Target high temperature for climate devices'), target_temp_low: z.number().optional() .describe('Target low temperature for climate devices'), hvac_mode: z.enum(['off', 'heat', 'cool', 'heat_cool', 'auto', 'dry', 'fan_only']).optional() .describe('HVAC mode for climate devices'), fan_mode: z.enum(['auto', 'low', 'medium', 'high']).optional() .describe('Fan mode for climate devices'), humidity: z.number().min(0).max(100).optional() .describe('Target humidity for climate devices') }), execute: async (params: CommandParams) => { try { const domain = params.entity_id.split('.')[0] as keyof typeof DomainSchema.Values; if (!Object.values(DomainSchema.Values).includes(domain)) { throw new Error(`Unsupported domain: ${domain}`); } const service = params.command; const serviceData: Record = { entity_id: params.entity_id }; // Handle domain-specific parameters switch (domain) { case 'light': if (params.brightness !== undefined) { serviceData.brightness = params.brightness; } if (params.color_temp !== undefined) { serviceData.color_temp = params.color_temp; } if (params.rgb_color !== undefined) { serviceData.rgb_color = params.rgb_color; } break; case 'cover': if (service === 'set_position' && params.position !== undefined) { serviceData.position = params.position; } if (service === 'set_tilt_position' && params.tilt_position !== undefined) { serviceData.tilt_position = params.tilt_position; } break; case 'climate': if (service === 'set_temperature') { if (params.temperature !== undefined) { serviceData.temperature = params.temperature; } if (params.target_temp_high !== undefined) { serviceData.target_temp_high = params.target_temp_high; } if (params.target_temp_low !== undefined) { serviceData.target_temp_low = params.target_temp_low; } } if (service === 'set_hvac_mode' && params.hvac_mode !== undefined) { serviceData.hvac_mode = params.hvac_mode; } if (service === 'set_fan_mode' && params.fan_mode !== undefined) { serviceData.fan_mode = params.fan_mode; } if (service === 'set_humidity' && params.humidity !== undefined) { serviceData.humidity = params.humidity; } break; case 'switch': case 'contact': // These domains only support basic operations (turn_on, turn_off, toggle) break; default: throw new Error(`Unsupported operation for domain: ${domain}`); } // Call Home Assistant service try { const response = await fetch(`${HASS_HOST}/api/services/${domain}/${service}`, { method: 'POST', headers: { Authorization: `Bearer ${HASS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify(serviceData), }); if (!response.ok) { throw new Error(`Failed to execute ${service} for ${params.entity_id}: ${response.statusText}`); } return { success: true, message: `Successfully executed ${service} for ${params.entity_id}` }; } catch (error) { throw new Error(`Failed to execute ${service} for ${params.entity_id}: ${error instanceof Error ? error.message : 'Unknown error occurred'}`); } } catch (error) { return { success: false, message: error instanceof Error ? error.message : 'Unknown error occurred' }; } } }); // Start the server await server.start(); console.log('MCP Server started'); } main().catch(console.error);