Skip to main content
Glama

HomeAssistant MCP

automation-config.tool.ts15.4 kB
import { z } from "zod"; import type { Tool, AutomationConfigParams, AutomationConfig, AutomationResponse, } from "../types/index.js"; import { APP_CONFIG } from "../config/app.config.ts"; const hassDictionarySchema = z .record(z.string(), z.unknown()) .describe("Arbitrary Home Assistant data payload"); const stringOrStringArraySchema = z .union([ z.string(), z.array(z.string()).min(1).describe("List of entity IDs"), ]) .describe("Entity ID or list of entity IDs"); const templatedScalarSchema = z .union([z.string(), z.number(), hassDictionarySchema]) .describe("Literal or templated value"); const automationTriggerSchema = z .object({ id: z .string() .optional() .describe("Optional trigger identifier"), platform: z .string() .min(1) .describe("Trigger platform (state, time, event, device, etc.)"), entity_id: stringOrStringArraySchema .optional() .describe("Entity IDs monitored by the trigger"), from: z .string() .optional() .describe("Starting state for state triggers"), to: z .string() .optional() .describe("Target state for state triggers"), attribute: z .string() .optional() .describe("Entity attribute used in state triggers"), for: templatedScalarSchema .optional() .describe("Duration the trigger condition must hold"), at: z .string() .optional() .describe("Time expression for time triggers"), event: hassDictionarySchema .optional() .describe("Event payload for event triggers"), device_id: z .string() .optional() .describe("Device identifier for device triggers"), domain: z .string() .optional() .describe("Device domain for device triggers"), type: z .string() .optional() .describe("Device trigger type"), zone: hassDictionarySchema .optional() .describe("Zone configuration for zone triggers"), minutes: z .number() .optional() .describe("Minute interval for time pattern triggers"), seconds: z .number() .optional() .describe("Second interval for time pattern triggers"), }) .passthrough() .describe("Home Assistant trigger definition"); const automationConditionSchema: z.ZodTypeAny = z.lazy(() => z .object({ alias: z .string() .optional() .describe("Optional condition alias"), condition: z .string() .min(1) .describe("Condition type (state, numeric_state, and/or, etc.)"), enabled: z .boolean() .optional() .describe("Whether the condition is enabled"), entity_id: stringOrStringArraySchema .optional() .describe("Entity IDs referenced by the condition"), state: z .string() .optional() .describe("Expected state for state conditions"), attribute: z .string() .optional() .describe("Entity attribute evaluated by the condition"), for: templatedScalarSchema .optional() .describe("Duration the condition must hold"), above: z .union([z.number(), z.string()]) .optional() .describe("Upper threshold for numeric conditions"), below: z .union([z.number(), z.string()]) .optional() .describe("Lower threshold for numeric conditions"), value_template: z .string() .optional() .describe("Template used by template conditions"), conditions: z .array(automationConditionSchema) .optional() .describe("Nested conditions"), sequence: z .array(automationActionSchema) .optional() .describe("Action sequence for choose/then blocks"), then: z .array(automationActionSchema) .optional() .describe("Actions executed when condition succeeds"), else: z .array(automationActionSchema) .optional() .describe("Actions executed when condition fails"), }) .passthrough() .describe("Home Assistant condition definition"), ); const automationTargetSchema = z .object({ entity_id: stringOrStringArraySchema .optional() .describe("Entity IDs to target"), device_id: z .string() .optional() .describe("Device identifier to target"), area_id: z .string() .optional() .describe("Area identifier to target"), }) .passthrough() .describe("Target selector"); const automationActionSchema: z.ZodTypeAny = z.lazy(() => z .object({ alias: z .string() .optional() .describe("Optional action alias"), service: z .string() .optional() .describe("Home Assistant service to call"), target: automationTargetSchema .optional() .describe("Entities, devices, or areas targeted by the action"), data: hassDictionarySchema .optional() .describe("Service data payload"), metadata: hassDictionarySchema .optional() .describe("Additional metadata for the action"), entity_id: stringOrStringArraySchema .optional() .describe("Entities affected by the action"), device_id: z .string() .optional() .describe("Device affected by the action"), domain: z .string() .optional() .describe("Domain associated with the action"), type: z .string() .optional() .describe("Action type for device actions"), choose: z .array( z .object({ alias: z .string() .optional() .describe("Optional branch alias"), conditions: z .array(automationConditionSchema) .optional() .describe("Conditions for this branch"), sequence: z .array(automationActionSchema) .min(1) .describe("Actions executed when branch conditions pass"), default: z .array(automationActionSchema) .optional() .describe("Fallback actions when branch conditions fail"), }) .passthrough() .describe("Conditional branch definition"), ) .optional() .describe("Conditional branches"), sequence: z .array(automationActionSchema) .optional() .describe("Nested sequence of actions"), if: z .array(automationConditionSchema) .optional() .describe("Inline conditions evaluated before actions"), then: z .array(automationActionSchema) .optional() .describe("Actions executed when inline conditions pass"), else: z .array(automationActionSchema) .optional() .describe("Actions executed when inline conditions fail"), repeat: z .object({ count: templatedScalarSchema .optional() .describe("Number of repetitions"), while: z .array(automationConditionSchema) .optional() .describe("Conditions checked before each iteration"), until: z .array(automationConditionSchema) .optional() .describe("Conditions that end the repetition"), sequence: z .array(automationActionSchema) .optional() .describe("Actions repeated on each iteration"), for_each: z .array(z.union([z.string(), z.number(), hassDictionarySchema])) .optional() .describe("Items to iterate over"), }) .passthrough() .optional() .describe("Repeat configuration"), delay: templatedScalarSchema .optional() .describe("Delay before continuing"), wait_template: z .string() .optional() .describe("Template evaluated until it returns true"), wait_for_trigger: z .array(automationTriggerSchema) .optional() .describe("Triggers to wait for before continuing"), parallel: z .array(automationActionSchema) .optional() .describe("Actions executed in parallel"), }) .passthrough() .describe("Home Assistant action step"), ); const _automationConfigSchema = z .object({ alias: z .string() .min(1) .describe("Friendly name for the automation"), description: z .string() .optional() .describe("Description of what the automation does"), mode: z .enum(["single", "parallel", "queued", "restart"]) .optional() .describe("How multiple triggerings are handled"), trigger: z .array(automationTriggerSchema) .min(1, "At least one trigger is required") .describe("List of triggers"), condition: z .array(automationConditionSchema) .optional() .describe("List of conditions"), action: z .array(automationActionSchema) .min(1, "At least one action is required") .describe("List of actions"), variables: hassDictionarySchema .optional() .describe("Optional variables available to the automation"), trace: hassDictionarySchema .optional() .describe("Optional trace configuration"), }) .passthrough() .describe("Automation configuration (required for create and update)"); export const automationConfigTool: Tool = { name: "automation_config", description: "Advanced automation configuration and management", // Simplified schema to avoid recursive $ref issues in VSCode validation parameters: z.object({ action: z .enum(["create", "update", "delete", "duplicate"]) .describe("Action to perform with automation config"), automation_id: z .string() .optional() .describe("Automation ID (required for update, delete, and duplicate)"), config: z .record(z.string(), z.unknown()) .optional() .describe("Automation configuration object (required for create and update)"), }), execute: async (params: AutomationConfigParams) => { try { switch (params.action) { case "create": { if (params.config == null) { throw new Error( "Configuration is required for creating automation", ); } const response = await fetch( `${APP_CONFIG.HASS_HOST}/api/config/automation/config`, { method: "POST", headers: { Authorization: `Bearer ${APP_CONFIG.HASS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(params.config), }, ); if (!response.ok) { throw new Error( `Failed to create automation: ${response.statusText}`, ); } const responseData = (await response.json()) as { automation_id: string; }; return { success: true, message: "Successfully created automation", automation_id: responseData.automation_id, }; } case "update": { if (params.automation_id == null || params.config == null) { throw new Error( "Automation ID and configuration are required for updating automation", ); } const response = await fetch( `${APP_CONFIG.HASS_HOST}/api/config/automation/config/${params.automation_id}`, { method: "PUT", headers: { Authorization: `Bearer ${APP_CONFIG.HASS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(params.config), }, ); if (!response.ok) { throw new Error( `Failed to update automation: ${response.statusText}`, ); } const responseData = (await response.json()) as { automation_id: string; }; return { success: true, automation_id: responseData.automation_id, message: "Automation updated successfully", }; } case "delete": { if (!params.automation_id) { throw new Error( "Automation ID is required for deleting automation", ); } const response = await fetch( `${APP_CONFIG.HASS_HOST}/api/config/automation/config/${params.automation_id}`, { method: "DELETE", headers: { Authorization: `Bearer ${APP_CONFIG.HASS_TOKEN}`, "Content-Type": "application/json", }, }, ); if (!response.ok) { throw new Error( `Failed to delete automation: ${response.statusText}`, ); } return { success: true, message: `Successfully deleted automation ${params.automation_id}`, }; } case "duplicate": { if (!params.automation_id) { throw new Error( "Automation ID is required for duplicating automation", ); } // First, get the existing automation config const getResponse = await fetch( `${APP_CONFIG.HASS_HOST}/api/config/automation/config/${params.automation_id}`, { headers: { Authorization: `Bearer ${APP_CONFIG.HASS_TOKEN}`, "Content-Type": "application/json", }, }, ); if (!getResponse.ok) { throw new Error( `Failed to get automation config: ${getResponse.statusText}`, ); } const config = (await getResponse.json()) as AutomationConfig; config.alias = `${config.alias} (Copy)`; // Create new automation with modified config const createResponse = await fetch( `${APP_CONFIG.HASS_HOST}/api/config/automation/config`, { method: "POST", headers: { Authorization: `Bearer ${APP_CONFIG.HASS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(config), }, ); if (!createResponse.ok) { throw new Error( `Failed to create duplicate automation: ${createResponse.statusText}`, ); } const newAutomation = (await createResponse.json()) as AutomationResponse; return { success: true, message: `Successfully duplicated automation ${params.automation_id}`, new_automation_id: newAutomation.automation_id, }; } } } catch (error) { return { success: false, message: error instanceof Error ? error.message : "Unknown error occurred", }; } }, };

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