Skip to main content
Glama

HomeAssistant MCP

climate.tool.ts10.2 kB
/** * Climate Control Tool for Home Assistant (fastmcp format) * * This tool allows controlling climate devices (thermostats, AC units, etc.) * in Home Assistant through the MCP. It supports modes, temperature settings, * and fan modes. */ import { z } from "zod"; import { logger } from "../../utils/logger.js"; import { BaseTool } from "../base-tool.js"; import { MCPContext } from "../../mcp/types.js"; import { get_hass } from "../../hass/index.js"; import { Tool } from "../../types/index.js"; // Real Home Assistant API service class HomeAssistantClimateService { async getClimateDevices(): Promise<Record<string, unknown>[]> { try { const hass = await get_hass(); const states = await hass.getStates(); return states .filter(state => state.entity_id.startsWith('climate.')) .map(state => ({ entity_id: state.entity_id, state: state.state, attributes: state.attributes })); } catch (error) { logger.error('Failed to get climate devices from HA:', error); return []; } } async getClimateDevice(entity_id: string): Promise<Record<string, unknown> | null> { try { const hass = await get_hass(); const state = await hass.getState(entity_id); return { entity_id: state.entity_id, state: state.state, attributes: state.attributes }; } catch (error) { logger.error(`Failed to get climate device ${entity_id} from HA:`, error); return null; } } async setHvacMode(entity_id: string, hvac_mode: string): Promise<boolean> { try { const hass = await get_hass(); await hass.callService('climate', 'set_hvac_mode', { entity_id, hvac_mode }); return true; } catch (error) { logger.error(`Failed to set HVAC mode for ${entity_id}:`, error); return false; } } async setTemperature(entity_id: string, temperature: number, target_temp_high?: number, target_temp_low?: number): Promise<boolean> { try { const hass = await get_hass(); const serviceData: Record<string, unknown> = { entity_id }; if (target_temp_high !== undefined && target_temp_low !== undefined) { serviceData.target_temp_high = target_temp_high; serviceData.target_temp_low = target_temp_low; } else if (temperature !== undefined) { serviceData.temperature = temperature; } await hass.callService('climate', 'set_temperature', serviceData); return true; } catch (error) { logger.error(`Failed to set temperature for ${entity_id}:`, error); return false; } } async setFanMode(entity_id: string, fan_mode: string): Promise<boolean> { try { const hass = await get_hass(); await hass.callService('climate', 'set_fan_mode', { entity_id, fan_mode }); return true; } catch (error) { logger.error(`Failed to set fan mode for ${entity_id}:`, error); return false; } } } // Singleton instance const haClimateService = new HomeAssistantClimateService(); // Define the schema for our tool parameters using Zod const climateControlSchema = z.object({ action: z.enum(["list", "get", "set_hvac_mode", "set_temperature", "set_fan_mode"]) .describe("The action to perform on the climate device"), entity_id: z.string() .optional() .describe("The entity ID of the climate device (required for get and set actions)"), hvac_mode: z.enum(["off", "heat", "cool", "auto", "dry", "fan_only"]) .optional() .describe("The HVAC mode to set (required for set_hvac_mode)"), temperature: z.number() .optional() .describe("The target temperature to set (use for single setpoint devices)"), target_temp_high: z.number() .optional() .describe("The maximum target temperature for range devices (use with target_temp_low)"), target_temp_low: z.number() .optional() .describe("The minimum target temperature for range devices (use with target_temp_high)"), fan_mode: z.enum(["auto", "low", "medium", "high"]) .optional() .describe("The fan mode to set (required for set_fan_mode)"), }); // Infer the type from the schema type ClimateControlParams = z.infer<typeof climateControlSchema>; // --- Shared Execution Logic --- async function executeClimateControlLogic(params: ClimateControlParams): Promise<Record<string, unknown>> { let success: boolean; let deviceDetails: Record<string, unknown> | null; switch (params.action) { case "list": { const devices = await haClimateService.getClimateDevices(); return { devices }; } case "get": { if (!params.entity_id) { throw new Error("entity_id is required for 'get' action"); } deviceDetails = await haClimateService.getClimateDevice(params.entity_id); if (!deviceDetails) { throw new Error(`Climate entity_id '${params.entity_id}' not found.`); } return deviceDetails; } case "set_hvac_mode": { if (!params.entity_id) { throw new Error("entity_id is required for 'set_hvac_mode' action"); } if (!params.hvac_mode) { throw new Error("hvac_mode is required for 'set_hvac_mode' action"); } success = await haClimateService.setHvacMode(params.entity_id, params.hvac_mode); if (!success) { throw new Error(`Failed to set HVAC mode for '${params.entity_id}'. Entity not found or mode not supported?`); } deviceDetails = await haClimateService.getClimateDevice(params.entity_id); return { status: "success", state: deviceDetails }; } case "set_temperature": { if (!params.entity_id) { throw new Error("entity_id is required for 'set_temperature' action"); } if (params.temperature === undefined && params.target_temp_high === undefined && params.target_temp_low === undefined) { throw new Error("At least one temperature parameter (temperature, target_temp_high, target_temp_low) is required for 'set_temperature' action"); } if ((params.target_temp_high !== undefined && params.target_temp_low === undefined) || (params.target_temp_high === undefined && params.target_temp_low !== undefined)){ throw new Error("Both target_temp_high and target_temp_low must be provided together for temperature range setting"); } success = await haClimateService.setTemperature( params.entity_id, params.temperature, params.target_temp_high, params.target_temp_low ); if (!success) { throw new Error(`Failed to set temperature for '${params.entity_id}'. Entity not found or temperature setting not supported?`); } deviceDetails = await haClimateService.getClimateDevice(params.entity_id); return { status: "success", state: deviceDetails }; } case "set_fan_mode": { if (!params.entity_id) { throw new Error("entity_id is required for 'set_fan_mode' action"); } if (!params.fan_mode) { throw new Error("fan_mode is required for 'set_fan_mode' action"); } success = await haClimateService.setFanMode(params.entity_id, params.fan_mode); if (!success) { throw new Error(`Failed to set fan mode for '${params.entity_id}'. Entity not found or mode not supported?`); } deviceDetails = await haClimateService.getClimateDevice(params.entity_id); return { status: "success", state: deviceDetails }; } default: throw new Error(`Unknown action: ${String(params.action)}`); } } // --- Tool Definition --- export const climateControlTool: Tool = { name: "climate_control", description: "Control climate devices (thermostats, AC) in Home Assistant", parameters: climateControlSchema, execute: executeClimateControlLogic, }; // --- Original BaseTool Class Definition (for compatibility with src/index.ts) --- export class ClimateControlTool extends BaseTool { constructor() { super({ name: climateControlTool.name, // Reuse name from fastmcp definition description: climateControlTool.description, // Reuse description parameters: climateControlSchema, // Reuse schema metadata: { category: "home_assistant", // Keep original metadata if needed version: "1.0.0", tags: ["climate", "thermostat", "hvac", "home_assistant"], // Add examples if BaseTool/MCPServer uses them } }); } /** * Execute method for the BaseTool class */ public async execute(params: ClimateControlParams, context: MCPContext): Promise<Record<string, unknown>> { logger.debug(`Executing ClimateControlTool (BaseTool) with params: ${JSON.stringify(params)}`); try { const validatedParams = this.validateParams(params); const result = await executeClimateControlLogic(validatedParams); // return this.validateResult(result); // Optional result validation return result; } catch (error) { logger.error(`Error in ClimateControlTool (BaseTool): ${String(error)}`); throw error; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/advanced-homeassistant-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server