Tool Explorer
Browse all 84 MCP tools across 27 categories.
Add-ons
Description
Get Home Assistant add-ons - list installed, available, or get details for one. This tool retrieves add-on information based on the parameters: - slug provided: Returns detailed info for a single add-on (ingress, ports, options, state) - source='installed' (default): Lists currently installed add-ons - source='available': Lists add-ons available in the add-on store **Note:** This tool only works with Home Assistant OS or Supervised installations. **SINGLE ADD-ON (slug provided):** Returns comprehensive details including ingress entry, ports, options, state, and (when the add-on exposes one) a top-level ``log_level`` reflecting the current Supervisor option — useful for confirming ha_manage_addon log_level changes. Useful for discovering what APIs an add-on exposes before calling ha_manage_addon. **INSTALLED ADD-ONS (source='installed'):** Returns add-ons with version, state (started/stopped), and update availability. - include_stats: Optionally include CPU/memory usage statistics **AVAILABLE ADD-ONS (source='available'):** Returns add-ons from official and custom repositories that can be installed. - repository: Filter by repository slug (e.g., 'core', 'community') - query: Search by name or description (case-insensitive) **Example Usage:** - List installed add-ons: ha_get_addon() - Get Node-RED details: ha_get_addon(slug="<prefix>_nodered") - List with resource usage: ha_get_addon(include_stats=True) - List available add-ons: ha_get_addon(source="available") - Search for MQTT: ha_get_addon(source="available", query="mqtt")
Parameters
source - (Annotated[Literal['installed', 'available'] | None, Field(description="Add-on source: 'installed' (default) for currently installed add-ons, 'available' for add-ons in the store that can be installed.", default=None)])
= null slug - (Annotated[str | None, Field(description="Add-on slug for detailed info (e.g., '<prefix>_nodered'). Slug prefixes vary by add-on repository — omit to list all add-ons and discover the actual installed slug.", default=None)])
= null include_stats - (Annotated[bool, Field(description="Include CPU/memory usage statistics (only for source='installed')", default=False)])
= false repository - (Annotated[str | None, Field(description="Filter by repository slug, e.g., 'core', 'community' (only for source='available')", default=None)])
= null query - (Annotated[str | None, Field(description="Search filter for add-on names/descriptions (only for source='available')", default=None)])
= null Description
Manage a Home Assistant add-on — update its configuration or call its internal API. Five mutually exclusive operating modes: **Lifecycle mode** (when ``action`` is one of install/uninstall/start/ stop/restart/rebuild/update): Runs a Supervisor add-on action on ``slug``. ``install`` / ``update`` go through the store (the add-on's repository must be registered — it shows up in ``ha_get_addon(source="available")``); the rest act on an installed add-on. This is how an assistant brings an add-on online for the user (e.g. installing + starting the dashboard screenshot engine). **Store-repository mode** (when ``action`` is ``add_repository`` or ``remove_repository``): Registers or unregisters a custom add-on store repository. These actions operate on the store rather than an installed add-on, so they take the ``repository`` param and no ``slug``: ``add_repository`` POSTs the repository URL to ``/store/repositories``; ``remove_repository`` DELETEs ``/store/repositories/{slug}`` by the repository's slug. Adding a repository (e.g. balloob's add-ons) is the missing step that lets an assistant then install an add-on from it via ``action="install"``. **Config mode** (when any of options/network/boot/auto_update/watchdog is provided): Updates the add-on's Supervisor configuration via POST /addons/{slug}/options. All config parameters are optional; only provided fields are updated — current values are fetched and merged automatically (including one level of nested dicts). **Proxy mode** (when path is provided without array_patch): Routes HTTP or WebSocket requests through Home Assistant's Ingress proxy by default (works on HAOS, Supervised, and off-host PyPI/uvx installs). Pass `port=...` to bypass Ingress and connect directly to an add-on's container port — that mode requires the MCP host to share Home Assistant's container network (i.e. only the HAOS addon). Use ha_get_addon(slug="...") to discover available ports and endpoints. **ESPHome Device Builder dashboard (current rewrite):** config and log access is a WebSocket JSON-command API, NOT REST. The legacy endpoints are gone — `GET /edit?configuration=` now returns the dashboard SPA, and the old `/compile` `/validate` `/logs` WebSocket paths (which took `{"type": "spawn", ...}` bodies) reject the upgrade (HTTP 200). Use instead: - HTTP `GET /devices` → JSON list of configured devices; each entry's `configuration` field is the YAML filename to pass below. - WebSocket `path="/ws"` with body `{"command": "<cmd>", "message_id": "1", "args": {...}}`. The server sends a `server_info` message first, then one reply per `message_id`. Wire-confirmed commands: `devices/get_config` `{configuration}` → raw YAML (in the reply's `result`); `devices/logs` (stream) `{configuration, port: "OTA"}` → live device logs. Also exposed by the dashboard frontend (command/arg names not wire-tested here): `devices/update_config` `{configuration, content}` → save, `devices/validate`, `firmware/compile`. - The `/ws` channel stays open, so for a one-shot read or a bounded log capture pass `wait_for_close=False` with `message_limit` (and `message_offset` to skip the server_info / config-banner preamble). Reach the dashboard through Ingress — omit `port`; direct `port=` does not route to it. **Array-patch mode** (when path AND array_patch are provided): Atomic "GET array, mutate, POST array" workflow for addon APIs whose write contract is "send the whole resource collection back". Operations are applied in order to a working copy; if any op fails validation (unknown id, collision, malformed shape) nothing is posted. Returns a compact summary instead of the full array. Designed for Node-RED /flows and similar endpoints. **Response shaping (proxy mode):** - WebSocket streams can be noisy (e.g. the ESPHome dashboard's devices/logs dumps the device's full config banner on connect). By default, `summarize=True` collapses long runs of non-signal messages into short elision markers; INFO/WARNING/ERROR/exit lines always pass through. Pagination via `message_offset` / `message_limit` works on the raw collected list before summarize runs. - `python_transform` applies a sandboxed Python expression as a final post-processing step in both HTTP and WebSocket modes. The variable `response` is bound to: * WebSocket: `list[dict | str]` — parsed JSON messages are dicts, undecodable frames stay as ANSI-stripped strings. Elision markers appear as `{"elided": N, "note": "..."}` dicts when summarize ran. * HTTP: `dict | list | str` — whichever the content-type produced. Transforms may mutate in place (response.append(...), del response[k]) or reassign (response = [...]). This is post-processing only — it does NOT provide optimistic-locking or write-back semantics. **WARNING:** Setting boot="auto"/"manual" will fail for add-ons whose Supervisor metadata locks the boot mode. The Supervisor returns an error in this case. **NOTE:** This tool only works with Home Assistant OS or Supervised installations. **Examples:** - Install an add-on: ha_manage_addon(slug="...", action="install") - Start an add-on: ha_manage_addon(slug="...", action="start") - Add a store repository: ha_manage_addon(action="add_repository", repository="https://github.com/balloob/home-assistant-addons") - Remove a store repository: ha_manage_addon(action="remove_repository", repository="0f1cc410") - Set add-on option: ha_manage_addon(slug="...", options={"log_level": "debug"}) Note: only the fields you provide are updated — current values are fetched first and merged automatically. Fields not in the add-on's schema are ignored with a warning. - Disable auto-update: ha_manage_addon(slug="...", auto_update=False) - Change host port: ha_manage_addon(slug="...", network={"5800/tcp": 8082}) - Set boot mode: ha_manage_addon(slug="...", boot="manual") - Call HTTP API: ha_manage_addon(slug="...", path="/api/events") - Direct port: ha_manage_addon(slug="...", path="/flows", port=1880) - ESPHome list devices (HTTP): ha_manage_addon(slug="<prefix>_esphome", path="/devices") - ESPHome read a device's YAML (WS one-shot): ha_manage_addon(slug="<prefix>_esphome", path="/ws", websocket=True, wait_for_close=False, message_limit=2, body={"command": "devices/get_config", "message_id": "1", "args": {"configuration": "device.yaml"}}) - ESPHome live logs (WS, bounded): ha_manage_addon(slug="<prefix>_esphome", path="/ws", websocket=True, wait_for_close=False, message_limit=60, body={"command": "devices/logs", "message_id": "1", "args": {"configuration": "device.yaml", "port": "OTA"}}) - Filter WS errors only: ha_manage_addon(slug="...", path="/ws", websocket=True, python_transform="response = [m for m in response if 'ERROR' in str(m) or 'WARN' in str(m)]") - HTTP subset: ha_manage_addon(slug="...", path="/flows", python_transform="response = [f['id'] for f in response]") - Array-patch (Node-RED, rename a node): ha_manage_addon( slug="a0d7b954_nodered", path="/flows", array_patch={"operations": [ {"op": "patch", "id": "abc123", "patches": {"name": "New Name"}}, ]}, ) - Array-patch (Node-RED, replace one tab's nodes atomically): ha_manage_addon( slug="a0d7b954_nodered", path="/flows", array_patch={"operations": [ {"op": "delete_where", "field": "z", "value": "tab-id"}, {"op": "add", "item": {"id": "n1", "type": "inject", "z": "tab-id", ...}}, {"op": "add", "item": {"id": "n2", "type": "function", "z": "tab-id", ...}}, ]}, request_headers={"Node-RED-Deployment-Type": "full"}, ) - Custom request headers (proxy mode): ha_manage_addon(slug="...", path="/api/state", request_headers={"Accept": "text/plain"})
Parameters
slug - (Annotated[str, Field(description="Add-on slug (e.g., '<prefix>_nodered', '<prefix>_frigate'). Slug prefixes vary by add-on repository — call ha_get_addon() to discover the actual installed slug. Required for every mode except the store-repository actions (action='add_repository'/'remove_repository'), which use 'repository' instead and take no slug.", default='')])
= "" path - (Annotated[str | None, Field(description="Proxy mode: API path relative to the add-on root (e.g., '/flows', '/api/events', '/api/stats'). Required for proxy mode; mutually exclusive with config parameters.", default=None)])
= null method - (Annotated[str, Field(description='Proxy mode only. HTTP method: GET, POST, PUT, DELETE, PATCH. Defaults to GET.', default='GET')])
= "GET" body - (Annotated[dict[str, Any] | str | None, Field(description='Proxy mode only. Request body for POST/PUT/PATCH — or, with websocket=True, the initial WebSocket message. Pass a JSON object or JSON string.', default=None)])
= null debug - (Annotated[bool, Field(description='Proxy mode only. Include diagnostic info (request URL, headers sent, response headers). Default: false.', default=False)])
= false port - (Annotated[int | None, Field(description="Proxy mode only. Connect to this port instead of the Ingress port. Use ha_get_addon(slug='...') to find available ports.", default=None)])
= null offset - (Annotated[int, Field(description='Proxy mode only. HTTP: skip this many items in a JSON array response. Default: 0.', default=0)])
= 0 limit - (Annotated[int | None, Field(description='Proxy mode only. HTTP: return at most this many items from a JSON array response.', default=None)])
= null websocket - (Annotated[bool, Field(description="Proxy mode only. Use WebSocket instead of HTTP — for an add-on's WebSocket API (e.g. the ESPHome dashboard's '/ws' command channel; see the docstring's ESPHome section). Sends 'body' as the initial message, collects responses. Default: false.", default=False)])
= false wait_for_close - (Annotated[bool, Field(description="Proxy mode only. WebSocket: True: wait for the server to close the stream (run-to-completion ops like an ESPHome compile/validate). False: return after the first response batch — use for a one-shot command/response or a bounded log capture on a channel that stays open (e.g. ESPHome '/ws'). Default: true.", default=True)])
= true message_limit - (Annotated[int | None, Field(description='Proxy mode only. WebSocket: cap on messages collected from the wire, bounded by an internal safety ceiling. None = collect up to the ceiling. Lower to save tokens on noisy streams (e.g., message_limit=50 for a quick health check).', default=None)])
= null message_offset - (Annotated[int, Field(description='Proxy mode only. WebSocket: drop this many messages from the start of the collected list before returning. Useful for paginating past known-noisy headers. Default: 0.', default=0)])
= 0 summarize - (Annotated[bool, Field(description='Proxy mode only. WebSocket: when True (default), collapse runs of non-signal messages (typically YAML config dumps) into short elision markers. Set to False to return the raw stream.', default=True)])
= true python_transform - (Annotated[str | None, Field(description="Proxy mode only. Sandboxed Python expression that post-processes the response. Variable `response` is exposed — a list[dict | str] for WebSocket (parsed JSON or raw text), or dict/list/str for HTTP (parsed body). Supports in-place mutation (response.append(...)) or reassignment (response = [...]). Example: response = [m for m in response if 'ERROR' in str(m)]. Post-processing only — does not provide optimistic-locking write semantics.", default=None)])
= null options - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Config mode: Add-on configuration values (the 'Configuration' tab in the UI).", default=None)])
= null network - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Config mode: Host port mappings (e.g., {'5800/tcp': 8081}).", default=None)])
= null boot - (Annotated[str | None, Field(description="Config mode: Boot strategy — 'auto' (start with HA) or 'manual'.", default=None)])
= null auto_update - (Annotated[bool | None, Field(description='Config mode: Enable or disable automatic updates for this add-on.', default=None)])
= null watchdog - (Annotated[bool | None, Field(description='Config mode: Enable or disable Supervisor watchdog (auto-restart on crash).', default=None)])
= null array_patch - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Array-patch mode: atomically GET a JSON array endpoint, apply ordered ops, then POST the mutated array back. Requires 'path'; mutually exclusive with body / websocket / offset / limit and config params. See the docstring Examples and ha_get_skill_guide for op shapes.", default=None)])
= null request_headers - (Annotated[dict[str, str] | None, JSON_STRING_COERCION, Field(description="Proxy/array-patch mode: extra HTTP headers to send to the addon API. Useful for addon-specific requirements such as Node-RED's `Node-RED-Deployment-Type: full`. The proxy's internal framing (`X-Ingress-Path`, `X-Hass-Source`, `Cookie`, `Content-Type`) is layered on top, so caller-supplied values for those keys are overridden. Not valid in config or websocket mode.", default=None)])
= null action - (Annotated[str | None, Field(description="Lifecycle mode: run a Supervisor add-on action. One of 'install', 'uninstall', 'start', 'stop', 'restart', 'rebuild', 'update'. 'install'/'update' require the add-on's repository to be registered (it appears in ha_get_addon(source='available')). Store-repository mode: 'add_repository' / 'remove_repository' register or unregister a custom add-on store repository — these use the 'repository' param instead of 'slug'. Mutually exclusive with path / config parameters / array_patch. HA OS / Supervised only.", default=None)])
= null repository - (Annotated[str | None, Field(description="Store-repository mode only (action='add_repository' or 'remove_repository'). For add_repository: the repository URL (e.g., 'https://github.com/balloob/home-assistant-addons'). For remove_repository: the repository slug (e.g., '0f1cc410', as shown in ha_get_addon(source='available')). Required for those actions; ignored otherwise.", default=None)])
= null Areas & Floors
Description
List floors sorted by level ascending, each with their assigned areas nested, plus areas without a floor. Use for location-based reasoning where floor-to-area relationships matter, such as "which rooms are on the ground floor" or operations scoped to a level. Optionally project the response with fields= (top-level keys) or area_fields= (per-area-record keys, applied uniformly across nested, unassigned, and orphaned buckets). Floors with level=None sort alongside level 0 (ground floor). Areas without a floor assignment appear in unassigned_areas; areas whose floor_id points to a non-existent floor appear in orphaned_areas — a topology snapshot may diverge from individual list calls if the registries change between reads.
Parameters
fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified top-level response keys to reduce response size (e.g. ["floors"]). None = full response (default). Available keys: success, floor_count, area_count, unassigned_count, orphaned_count, floors, unassigned_areas, orphaned_areas, message.')])
= null area_fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Project each area record (in floors[].areas, unassigned_areas, and orphaned_areas) to only the specified keys. E.g. ["area_id", "name"] returns slim area records. None = full records (default). Unknown keys yield empty records. Available keys: area_id, name, icon, floor_id, aliases, picture, labels.')])
= null Description
Remove a Home Assistant area or floor. Removing an area unassigns its entities and devices (the entities and devices themselves are not removed). Removing a floor unassigns its areas. May break automations referencing the removed area/floor.
Parameters
kind required - (Annotated[Literal['area', 'floor'], Field(description="Which registry to delete from: 'area' or 'floor'")]) id required - (Annotated[str, Field(description='Area ID or floor ID to delete (use ha_list_floors_areas to find IDs)')]) Description
Create or update a Home Assistant area or floor. Pass kind='area' (with optional floor_id, picture) or kind='floor' (with optional level). Provide name only to create a new entry; provide id to update an existing one. Cross-kind parameters (e.g., picture under kind='floor') are rejected with VALIDATION_INVALID_PARAMETER. EXAMPLES: ha_set_area_or_floor(kind="area", name="Kitchen") ha_set_area_or_floor(kind="area", id="kitchen", floor_id="ground_floor") ha_set_area_or_floor(kind="floor", name="Basement", level=-1) ha_set_area_or_floor(kind="floor", id="ground_floor", level=0)
Parameters
kind required - (Annotated[Literal['area', 'floor'], Field(description="Which registry to operate on: 'area' for rooms, 'floor' for building levels")]) name - (Annotated[str | None, Field(description="Name (required when creating; optional when updating, e.g., 'Living Room', 'Ground Floor')", default=None)])
= null id - (Annotated[str | None, Field(description='Existing area_id or floor_id to update (omit to create a new entry; use ha_list_floors_areas to find IDs)', default=None)])
= null floor_id - (Annotated[str | None, Field(description="Floor assignment when kind='area' (use empty string to clear). Only valid when kind='area'.", default=None)])
= null level - (Annotated[int | None, Field(description="Numeric level when kind='floor' (0=ground, 1=first, -1=basement). Only valid when kind='floor'.", default=None)])
= null icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:sofa', 'mdi:home-floor-1', empty string to remove)", default=None)])
= null aliases - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description="Alternative names for voice assistant recognition (e.g., ['lounge'], empty list to clear)", default=None)])
= null picture - (Annotated[str | None, Field(description="Picture URL when kind='area' (empty string to remove). Only valid when kind='area'.", default=None)])
= null Assist
Description
Manage Home Assistant Assist pipelines. Use action='list' to discover pipeline IDs, action='get' to inspect one pipeline, action='create' or action='update' to write pipeline settings, and action='set_preferred' to choose the preferred pipeline. EXAMPLES: - List pipelines: ha_manage_pipeline(action="list") - Get one pipeline: ha_manage_pipeline(action="get", pipeline_id="preferred") - Create by cloning preferred: ha_manage_pipeline( action="create", name="Local Assist", conversation_engine="conversation.local_llm", ) - Create by cloning a specific pipeline: ha_manage_pipeline( action="create", base_pipeline_id="preferred", name="Local Assist", conversation_engine="conversation.local_llm", ) - Update conversation agent and clear TTS voice: ha_manage_pipeline( action="update", pipeline_id="preferred", conversation_engine="conversation.local_llm", tts_voice="", ) - Set preferred: ha_manage_pipeline( action="set_preferred", pipeline_id="preferred", ) Empty string clears nullable STT/TTS/wake-word fields. Non-nullable fields such as name, language, conversation_language, and conversation_engine must be omitted or non-empty.
Parameters
action required - (Annotated[PipelineAction, Field(description='Pipeline operation: list, get, create, update, or set_preferred.')]) pipeline_id - (Annotated[str | None, Field(description='Assist pipeline ID. Required for get, update, and set_preferred.', default=None)])
= null name - (Annotated[str | None, Field(description="Pipeline display name. Required when action='create'.", default=None)])
= null conversation_engine - (Annotated[str | None, Field(description="Conversation agent entity ID or engine ID. Required when action='create'.", default=None)])
= null base_pipeline_id - (Annotated[str | None, Field(description='Pipeline ID to clone when creating. Omit to clone the preferred pipeline. Ignored for non-create actions.', default=None)])
= null conversation_language - (Annotated[str | None, Field(description="Conversation language, usually '*'.", default=None)])
= null language - (Annotated[str | None, Field(description="Pipeline language, e.g. 'en'.", default=None)])
= null stt_engine - (Annotated[str | None, Field(description='Speech-to-text engine. Pass empty string to clear.', default=None)])
= null stt_language - (Annotated[str | None, Field(description='Speech-to-text language. Pass empty string to clear.', default=None)])
= null tts_engine - (Annotated[str | None, Field(description='Text-to-speech engine. Pass empty string to clear.', default=None)])
= null tts_language - (Annotated[str | None, Field(description='Text-to-speech language. Pass empty string to clear.', default=None)])
= null tts_voice - (Annotated[str | None, Field(description='Text-to-speech voice. Pass empty string to clear.', default=None)])
= null wake_word_entity - (Annotated[str | None, Field(description='Wake-word entity ID. Pass empty string to clear.', default=None)])
= null wake_word_id - (Annotated[str | None, Field(description='Wake-word ID. Pass empty string to clear.', default=None)])
= null prefer_local_intents - (Annotated[bool | None, Field(description='Whether Home Assistant local intents should be preferred before the conversation engine.', default=None)])
= null make_preferred - (Annotated[bool, Field(description='For create/update only, also set the resulting pipeline as preferred with an extra websocket call. Ignored for other actions.', default=False)])
= false Automations
Description
Retrieve Home Assistant automation configuration. Returns the complete configuration including triggers, conditions, actions, and mode settings. The returned `config_hash` is stable across consecutive reads of an unchanged config — `compute_config_hash` documents the underlying contract. The returned `automation_id` is the resolved entity_id (canonical form, e.g. `automation.morning_routine`) when the registry lookup succeeds, falling back to the input `identifier` otherwise. EXAMPLES: - Get automation: ha_config_get_automation("automation.morning_routine") - Get by unique_id: ha_config_get_automation("my_unique_automation_id") For comprehensive automation documentation, use ha_get_skill_guide.
Parameters
identifier required - (Annotated[str, Field(description="Automation entity_id (e.g., 'automation.morning_routine') or unique_id")]) Description
Delete a Home Assistant automation. The returned `automation_id` is the resolved entity_id (canonical form, e.g. `automation.morning_routine`) when the registry lookup succeeded before the delete, falling back to the input `identifier` otherwise. EXAMPLES: - Delete automation: ha_config_remove_automation("automation.old_automation") - Delete by unique_id: ha_config_remove_automation("my_unique_id") **WARNING:** Deleting an automation removes it permanently from your Home Assistant configuration.
Parameters
identifier required - (Annotated[str, Field(description="Automation entity_id (e.g., 'automation.old_automation') or unique_id to delete")]) wait - (Annotated[bool, Field(description='Wait for automation to be fully removed before returning. Default: True.', default=True)])
= true Description
Create or update a Home Assistant automation. MUST call ha_get_skill_guide first. PREFER NATIVE SOLUTIONS OVER TEMPLATES (read this before writing any `{{ ... }}`): Native triggers/conditions/actions are validated at config load, fail loudly, and do not bypass HA's schema. Templates fail silently at runtime and obscure intent. - `condition: numeric_state` instead of `{{ states('x') | float > N }}` - `condition: state` (with `state:` list) instead of `{{ is_state(...) }}` / `{{ states(x) in [...] }}` - `condition: time` instead of `{{ now().hour ... }}` or `{{ now().weekday() ... }}` - `condition: sun` instead of `{{ is_state('sun.sun', ...) }}` - Native `for:` field on `state`/`numeric_state` triggers and `state` conditions over `{{ now() - X.last_changed > timedelta(...) }}` duration math. - `wait_for_trigger` instead of `wait_template` - `choose` action instead of template-based service names - For one-shot date firing, use a `time` trigger plus `automation.turn_off` on a hardcoded entity_id — not `{{ now().date() ... }}`. - Hardcode `target.entity_id` literals — never `{{ this.entity_id }}`. Templates are appropriate ONLY in `data.*` fields, notification message/title, `event_data`, and `variables`. The reactive best-practice checker on this tool will surface anything in a logic position that should be native; consult the `best_practice_warnings` field on the response and fix before re-submitting. The relevant skill section is auto-embedded under `skill_content` on warnings, and the full `automation-patterns.md` + `template-guidelines.md` references ship under `skill_content` proactively by default. For comprehensive guidance beyond that, call `ha_get_skill_guide`. The returned `automation_id` is the resolved entity_id (canonical form, e.g. `automation.morning_routine`) when entity registration succeeds, falling back to the input `identifier` (update path) or the generated `unique_id` from the upsert response (fresh create when no identifier was passed). Before reaching for ``ha_config_set_automation``, consider whether a dedicated tool fits the use case better: - State snapshot of one or more entities (capture-then-replay, no trigger needed) -> ha_config_set_scene - State-derived value that recomputes when its inputs change (template sensor / binary sensor / number / select) -> ha_config_set_helper(helper_type='template') - Stateful counter / timer / schedule / boolean / etc. -> ha_config_set_helper(helper_type='counter' | 'timer' | ...) Supports two modes: full config replacement OR Python transformation. WHEN TO USE WHICH MODE: - python_transform: RECOMMENDED for edits to existing automations. Surgical updates. - config: Use for creating new automations or full restructures. IMPORTANT: python_transform requires 'identifier' and 'config_hash' from ha_config_get_automation(). PYTHON TRANSFORM EXAMPLES (operate on the fetched config, which uses HA's canonical plural root keys 'triggers'/'actions'/'conditions'): - Update action: python_transform="config['actions'][0]['data']['brightness'] = 255" - Add trigger: python_transform="config['triggers'].append({'trigger': 'state', 'entity_id': 'binary_sensor.motion', 'to': 'on'})" - Remove last action: python_transform="config['actions'].pop()" Creates a new automation (if identifier omitted) or updates existing automation with provided configuration. AUTOMATION TYPES: 1. Regular Automations - Define triggers and actions directly 2. Blueprint Automations - Use pre-built templates with customizable inputs REQUIRED FIELDS (Regular Automations): - alias: Human-readable automation name - triggers: List of triggers (time, state, event, etc.) - actions: List of actions to execute REQUIRED FIELDS (Blueprint Automations): - alias: Human-readable automation name - use_blueprint: Blueprint configuration - path: Blueprint file path (e.g., "motion_light.yaml") - input: Dictionary of input values for the blueprint OPTIONAL CONFIG FIELDS (Regular Automations): - description: Detailed description of the user's intent (RECOMMENDED: helps safely modify implementation later) - category: Category ID for organization (use ha_config_get_category to list, ha_config_set_category to create) - conditions: Additional conditions that must be met - mode: 'single' (default), 'restart', 'queued', 'parallel' - max: Maximum concurrent executions (for queued/parallel modes) - initial_state: Whether automation starts enabled (true/false) - variables: Variables for use in automation BASIC EXAMPLES: Simple time-based automation: ha_config_set_automation(config={ "alias": "Morning Lights", "description": "Turn on bedroom lights at 7 AM to help wake up", "triggers": [{"trigger": "time", "at": "07:00:00"}], "actions": [{"action": "light.turn_on", "target": {"area_id": "bedroom"}}] }) Motion-activated lighting — `for:` on the off-transition replaces action-delay: ha_config_set_automation(config={ "alias": "Motion Light", "triggers": [ {"trigger": "state", "entity_id": "binary_sensor.motion", "to": "on", "id": "motion_on"}, {"trigger": "state", "entity_id": "binary_sensor.motion", "to": "off", "for": {"minutes": 5}, "id": "motion_off"} ], "actions": [ {"choose": [ {"conditions": [ {"condition": "trigger", "id": "motion_on"}, {"condition": "sun", "after": "sunset"} ], "sequence": [{"action": "light.turn_on", "target": {"entity_id": "light.hallway"}}]}, {"conditions": [{"condition": "trigger", "id": "motion_off"}], "sequence": [{"action": "light.turn_off", "target": {"entity_id": "light.hallway"}}]} ]} ] }) Update existing automation: ha_config_set_automation( identifier="automation.morning_routine", config={ "alias": "Updated Morning Routine", "triggers": [{"trigger": "time", "at": "06:30:00"}], "actions": [ {"action": "light.turn_on", "target": {"area_id": "bedroom"}}, {"action": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} ] } ) BLUEPRINT AUTOMATION EXAMPLES: Create automation from blueprint: ha_config_set_automation(config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 120 } } }) Update blueprint automation inputs: ha_config_set_automation( identifier="automation.motion_light_kitchen", config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 300 } } } ) TRIGGER TYPES: time, time_pattern, sun, state, numeric_state, event, device, zone, template, and more CONDITION TYPES: state, numeric_state, time, sun, template, device, zone, and more ACTION TYPES: action calls, delays, wait_for_trigger, wait_template, if/then/else, choose, repeat, parallel For comprehensive automation documentation with all trigger/condition/action types and advanced examples: - Use: ha_get_skill_guide - Or visit: https://www.home-assistant.io/docs/automation/ TROUBLESHOOTING: - Use ha_get_state() to verify entity_ids exist - Use ha_search() to find correct entity_ids - IF you must use Jinja2 and have no native alternative, test it first with ha_eval_template() before embedding it in the automation config — catches syntax errors and unresolved entity_ids before they fail silently at runtime - Use ha_search(domain_filter='automation') to find existing automations
Parameters
config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Complete automation configuration with required fields: 'alias', 'triggers', 'actions'. Optional: 'description', 'conditions', 'mode', 'max', 'initial_state', 'variables'. Mutually exclusive with python_transform.", default=None)])
= null identifier - (Annotated[str | None, Field(description='Automation entity_id or unique_id for updates. Required for python_transform. Omit to create new automation with generated unique_id.', default=None)])
= null python_transform - (Annotated[str | None, Field(description='Python expression to transform existing automation config. Mutually exclusive with config. Requires identifier and config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Simple: python_transform="config[\'actions\'][0][\'data\'][\'brightness\'] = 255" Pattern: python_transform="for a in config[\'actions\']: if a.get(\'alias\') == \'My Step\': a[\'data\'][\'value\'] = 100" \n\n' + get_security_documentation())])
= null config_hash - (Annotated[str | None, Field(description='Config hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Optional for config updates (validates before full replacement if provided).')])
= null category - (Annotated[str | None, Field(description="Category ID to assign to this automation. Use ha_config_get_category(scope='automation') to list available categories, or ha_config_set_category() to create one.", default=None)])
= null wait - (Annotated[bool, Field(description='Wait for automation to be queryable before returning. Default: True. Set to False for bulk operations.', default=True)])
= true MandatoryBPS - (Annotated[bool, Field(default=True)])
= true Blueprints
Description
Get blueprint information - list all blueprints or get details for a specific one. Without a path: Lists all installed blueprints for the specified domain. With a path: Retrieves full blueprint configuration including inputs, triggers, conditions, and actions. EXAMPLES: - List all automation blueprints: ha_get_blueprint(domain="automation") - List script blueprints: ha_get_blueprint(domain="script") - Get specific blueprint: ha_get_blueprint(path="homeassistant/motion_light.yaml", domain="automation") RETURNS (when listing): - List of blueprints with path, name, and domain information - Count of blueprints found RETURNS (when getting specific blueprint): - Blueprint metadata (name, description, author, source_url) - Input definitions with selectors and defaults - Blueprint configuration (triggers, conditions, actions for automations; sequence for scripts)
Parameters
path - (Annotated[str | None, Field(description="Blueprint path to get details for (e.g., 'homeassistant/motion_light.yaml'). If omitted, lists all blueprints in the domain.", default=None)])
= null domain - (Annotated[str, Field(description="Blueprint domain: 'automation' or 'script'", default='automation')])
= "automation" Description
Import a blueprint from a URL. Imports a blueprint from GitHub, Home Assistant Community forums, or any direct URL to a blueprint YAML file. EXAMPLES: - Import from GitHub: ha_import_blueprint("https://github.com/user/repo/blob/main/blueprint.yaml") - Import from HA Community: ha_import_blueprint("https://community.home-assistant.io/t/motion-light/123456") - Import direct YAML: ha_import_blueprint("https://example.com/my-blueprint.yaml") SUPPORTED SOURCES: - GitHub repository URLs (will be converted to raw URLs) - Home Assistant Community forum posts with blueprint code - Direct URLs to YAML blueprint files RETURNS: - Import result with the blueprint path where it was saved - Blueprint metadata (name, domain, description) - Error details if import fails
Parameters
url required - (Annotated[str, Field(description='URL to import blueprint from (GitHub, Home Assistant Community, or direct YAML URL)')]) Calendar
Description
Retrieve calendar events from a calendar entity. Retrieves calendar events within a specified time range. **Parameters:** - entity_id: Calendar entity ID (e.g., 'calendar.family') - start: Start datetime in ISO format (default: now) - end: End datetime in ISO format (default: 7 days from start) - max_results: Maximum number of events to return (default: 20) **Example Usage:** ```python # Get events for the next week events = ha_config_get_calendar_events("calendar.family") # Get events for a specific date range events = ha_config_get_calendar_events( "calendar.work", start="2024-01-01T00:00:00", end="2024-01-31T23:59:59" ) ``` **Note:** To find calendar entities, use ha_search(query='calendar', domain_filter='calendar') **Returns:** - List of calendar events with summary, start, end, description, location
Parameters
entity_id required - (Annotated[str, Field(description="Calendar entity ID (e.g., 'calendar.family')")]) start - (Annotated[str | None, Field(description='Start datetime in ISO format (default: now)', default=None)])
= null end - (Annotated[str | None, Field(description='End datetime in ISO format (default: 7 days from start)', default=None)])
= null max_results - (Annotated[int, Field(description='Maximum number of events to return', default=20)])
= 20 Description
Delete an event from a calendar. Deletes a calendar event via the WebSocket ``calendar/event/delete`` command. HA's calendar component only registers ``create_event`` and ``get_events`` as REST services — delete and update live on the WebSocket API only. **Parameters:** - entity_id: Calendar entity ID (e.g., 'calendar.family') - uid: Unique identifier of the event to delete - recurrence_id: Optional recurrence ID for recurring events - recurrence_range: Optional recurrence range ('THIS_AND_FUTURE' to delete this and future occurrences) **Example Usage:** ```python # Delete a single event result = ha_config_remove_calendar_event( "calendar.family", uid="event-12345" ) # Delete a recurring event instance and future occurrences result = ha_config_remove_calendar_event( "calendar.work", uid="recurring-event-67890", recurrence_id="20240115T100000", recurrence_range="THIS_AND_FUTURE" ) ``` **Note:** To get the event UID, first use ha_config_get_calendar_events() to list events. The UID is returned in each event's data. **Returns:** - Success status and deletion confirmation
Parameters
entity_id required - (Annotated[str, Field(description="Calendar entity ID (e.g., 'calendar.family')")]) uid required - (Annotated[str, Field(description='Unique identifier of the event to delete')]) recurrence_id - (Annotated[str | None, Field(description='Optional recurrence ID for recurring events', default=None)])
= null recurrence_range - (Annotated[str | None, Field(description="Optional recurrence range ('THIS_AND_FUTURE' to delete this and future occurrences)", default=None)])
= null Description
Create a new event in a calendar. Creates a one-off event via the calendar.create_event service, or a recurring series via the WebSocket ``calendar/event/create`` command when ``rrule`` is provided (the REST service schema does not accept recurrence rules). **Parameters:** - entity_id: Calendar entity ID (e.g., 'calendar.family') - summary: Event title/summary - start: Event start datetime in ISO format - end: Event end datetime in ISO format - description: Optional event description - location: Optional event location - rrule: Optional RFC 5545 recurrence rule (creates a recurring series) **Example Usage:** ```python # Create a simple event result = ha_config_set_calendar_event( "calendar.family", summary="Doctor appointment", start="2024-01-15T14:00:00", end="2024-01-15T15:00:00" ) # Create a recurring event (every Monday, 10 occurrences) result = ha_config_set_calendar_event( "calendar.work", summary="Team meeting", start="2024-01-15T10:00:00", end="2024-01-15T11:00:00", rrule="FREQ=WEEKLY;BYDAY=MO;COUNT=10" ) ``` **Note:** Not every calendar integration supports event creation; recurring events additionally require the integration to support recurrence (the built-in Local Calendar does). **Returns:** - Success status and event details
Parameters
entity_id required - (Annotated[str, Field(description="Calendar entity ID (e.g., 'calendar.family')")]) summary required - (Annotated[str, Field(description='Event title/summary')]) start required - (Annotated[str, Field(description='Event start datetime in ISO format')]) end required - (Annotated[str, Field(description='Event end datetime in ISO format')]) description - (Annotated[str | None, Field(description='Optional event description', default=None)])
= null location - (Annotated[str | None, Field(description='Optional event location', default=None)])
= null rrule - (Annotated[str | None, Field(description="Optional RFC 5545 recurrence rule, without 'RRULE:' prefix (e.g., 'FREQ=WEEKLY;BYDAY=MO' or 'FREQ=MONTHLY;BYDAY=3SA'). Creates a recurring event series.", default=None)])
= null Camera
Description
Retrieve a snapshot image from a Home Assistant camera entity. This tool fetches the current camera image and returns it directly for visual analysis. Use this when you need to see what a camera is currently viewing. **Parameters:** - entity_id: Camera entity ID (e.g., 'camera.front_door', 'camera.living_room') - width: Optional width to resize the image (reduces token usage for large images) - height: Optional height to resize the image **Use Cases:** - Security checks: "Is someone at the front door?" - Pet monitoring: "Is my dog still on the couch?" - Delivery verification: "Did my package get delivered?" - Visual confirmation: "Did the garage door actually close?" - Incident investigation: "What triggered the motion sensor?" **Example Usage:** ```python # Get current snapshot from front door camera ha_get_camera_image(entity_id="camera.front_door") # Get resized image to reduce token usage ha_get_camera_image(entity_id="camera.backyard", width=640, height=480) ``` **Notes:** - Only cameras exposed to Home Assistant are accessible - The existing HA authentication/authorization applies - Images are returned in their native format (JPEG, PNG, or GIF) - Use width/height parameters for large high-resolution cameras to reduce token usage when full resolution is not needed **Related Services:** - camera.snapshot: Save snapshot to file on HA server - camera.turn_on/turn_off: Control camera power - camera.enable_motion_detection: Enable motion detection
Parameters
entity_id required - (str) width - (int | None)
= null height - (int | None)
= null Dashboard
Description
Get a rendered PNG image of a Home Assistant Lovelace dashboard view. Use it to visually verify a dashboard you just created or edited (pair with ha_config_set_dashboard, or use its return_screenshot param for a one-call create-and-see). Charts render best-effort — raise wait_ms if a chart card is blank. Set full_page=True to capture content below the fold.
Parameters
dashboard_path required - (Annotated[str, Field(description="Lovelace frontend path to render, e.g. 'lovelace/0' (default dashboard, first view), 'lovelace-home/kitchen', or 'my-dashboard'. Leading slash optional.")]) width - (Annotated[int, Field(description='Viewport width in px.', ge=64, le=4096)]) height - (Annotated[int, Field(description='Viewport height in px. Ignored when full_page=True (the engine renders a tall page instead).', ge=64, le=4096)]) zoom - (Annotated[float, Field(description='Page zoom factor (1.0 = 100%).', ge=0.1, le=5.0)])
= 1 wait_ms - (Annotated[int, Field(description='Extra render-settle time (ms) after the dashboard reports loaded. Raise it if a chart card (ApexCharts, mini-graph, history-graph) comes back blank.', ge=0, le=30000)]) full_page - (Annotated[bool, Field(description=f"{FULL_PAGE_PARAM_DESC[:1].upper()}{FULL_PAGE_PARAM_DESC[1:]}. Overrides 'height' with a tall render; raise 'wait_ms' for long dashboards so lazy cards finish painting.")])
= false Dashboards
Description
Delete a storage-mode dashboard completely. WARNING: This permanently deletes the dashboard and all its configuration. Cannot be undone. Does not work on YAML-mode dashboards. Accepts either the URL path or the internal dashboard ID. HA internal IDs may differ from url_path (e.g. hyphens → underscores); the tool resolves either form to the actual registry ID before deletion. EXAMPLES: - Delete dashboard: ha_config_delete_dashboard("mobile-dashboard") Note: The default dashboard cannot be deleted via this method.
Parameters
url_path required - (Annotated[str, Field(description="Dashboard URL path or internal ID to delete (e.g., 'my-dashboard' or 'my_dashboard'). Both forms are accepted.")]) Description
Delete a dashboard resource. Removes a resource from Home Assistant. The resource will no longer be loaded on dashboards. WARNING: Deleting a resource used by custom cards in your dashboards will cause those cards to fail to load. EXAMPLES: ha_config_delete_dashboard_resource(resource_id="abc123") Note: Use ha_config_list_dashboard_resources() to find resource IDs before deleting. Ensure no dashboards depend on the resource.
Parameters
resource_id required - (Annotated[str, Field(description='Resource ID to delete. Get from ha_config_list_dashboard_resources()')]) Description
Get dashboard info - list all dashboards, get config, or search for cards. MODE 1 — List: list_only=True Lists all storage-mode dashboards with metadata (url_path, title, icon). MODE 2 — Search: any of entity_id / card_type / heading provided Finds cards, badges, and header cards matching the criteria, including cards nested inside stacks, grids, conditional cards, button-card custom_fields, and state-switch states. Each match carries a python_path and a jq_path that locate the card for nested as well as top-level cards. The python_path is a Python subscript chain to be appended after `config` — e.g. python_transform=f'config{m["python_path"]}["icon"] = "mdi:x"' (it is NOT valid on its own without the `config` prefix). jq_path is the same location in jq dot-notation. Multiple criteria are AND-ed. Always fetches fresh config (force=True). Search covers cards/card/custom_fields/states containers up to a depth bound; if the dashboard carries a non-traversed child-bearing shape (e.g. picture-elements `elements`), the result carries a `warnings` entry naming where, so its hidden content is not mistaken for absent. Strategy dashboards are not searchable (no explicit cards). MODE 3 — Get: Active when list_only=False and no search parameters are provided. Returns the full Lovelace dashboard config, defaulting to the main dashboard if url_path is omitted. Return a stable `config_hash` (Get and Search modes only; not present in list_only mode) across consecutive reads of an unchanged config — `compute_config_hash` documents the underlying contract. EXAMPLES: - List all dashboards: ha_config_get_dashboard(list_only=True) - Get default dashboard: ha_config_get_dashboard(url_path="default") - Get custom dashboard: ha_config_get_dashboard(url_path="lovelace-mobile") - Force reload: ha_config_get_dashboard(url_path="lovelace-home", force_reload=True) - Find cards by entity: ha_config_get_dashboard(url_path="my-dash", entity_id="light.living_room") - Find by wildcard: ha_config_get_dashboard(url_path="my-dash", entity_id="sensor.temperature_*") - Find by type: ha_config_get_dashboard(url_path="my-dash", card_type="tile") - Find heading: ha_config_get_dashboard(url_path="my-dash", heading="Climate", card_type="heading") SEARCH WORKFLOW EXAMPLE: 1. find = ha_config_get_dashboard(url_path="my-dash", entity_id="light.bedroom") 2. ha_config_set_dashboard( url_path="my-dash", config_hash=find["config_hash"], python_transform=f'config{find["matches"][0]["python_path"]}["icon"] = "mdi:lamp"' ) Note: YAML-mode dashboards (defined in configuration.yaml) are not included in list.
Parameters
url_path - (Annotated[str | None, Field(description="Dashboard URL path (e.g., 'lovelace-home'). Use 'default' for default dashboard. If omitted with list_only=True, lists all dashboards.")])
= null list_only - (Annotated[bool, Field(description='If True, list all dashboards instead of getting config. When True, url_path is ignored.')])
= false force_reload - (Annotated[bool, Field(description='Force reload from storage (bypass cache). Not applicable in search mode (search always uses force=True for fresh results).')])
= false entity_id - (Annotated[str | None, Field(description="Find cards by entity ID. Supports wildcards, e.g. 'sensor.temperature_*'. Matches cards with this entity in 'entity' or 'entities' field, view-level badges, and header cards. When provided, activates search mode (returns matches, not full config).")])
= null card_type - (Annotated[str | None, Field(description="Find cards by type, e.g. 'tile', 'button', 'heading'. When provided, activates search mode.")])
= null heading - (Annotated[str | None, Field(description='Find cards by heading/title text (case-insensitive partial match). When provided, activates search mode.')])
= null include_config - (Annotated[bool, Field(description="In search mode: include each matched card's own configuration object in results (increases output size). Note that a matched container card's config contains its descendants, which are themselves separate matches with their own config, so deeply-nested stacks multiply the payload — keep the default (False) unless you need the bodies. Does not affect whether the full dashboard config is returned — search mode always returns matches only, not the full dashboard. Ignored outside search mode.")])
= false include_screenshot - (Annotated[bool, Field(description="Get mode only: also return a rendered PNG of the dashboard for visual verification. Requires the 'dashboard screenshot' beta feature + engine add-on/sidecar. If the feature is disabled the config is returned with a warning; if the engine is configured but the render fails, the call errors (the screenshot is the requested payload). Ignored in list/search mode.")])
= false full_page - (Annotated[bool, Field(description=f'With include_screenshot: {FULL_PAGE_PARAM_DESC}.')])
= false Description
List all Lovelace dashboard resources (custom cards, themes, CSS/JS). Returns all registered resources. For inline resources (created with ha_config_set_dashboard_resource(content=...)), shows a preview of the content instead of the full encoded URL to save tokens. Args: include_content: If True, includes full decoded content for inline resources in "_content" field. Default False (150-char preview only). Resource types: - module: ES6 JavaScript modules (modern custom cards) - js: Legacy JavaScript files - css: CSS stylesheets Each resource has a unique ID for update/delete operations. EXAMPLES: - List all resources: ha_config_list_dashboard_resources() - List with full content: ha_config_list_dashboard_resources(include_content=True) Note: Home Assistant 2026.6+ exposes resource management in the UI by default, and API access works regardless of UI availability.
Parameters
include_content - (Annotated[bool, Field(description='Include full decoded content for inline resources. Default False to save tokens (shows 150-char preview instead).')])
= false Description
Create or update a Home Assistant dashboard. MUST call ha_get_skill_guide first. Creates a new dashboard or updates an existing one with the provided configuration. Supports two modes: full config replacement OR Python transformation. Use 'default' or 'lovelace' to target the built-in default dashboard. New dashboards require a hyphenated url_path (e.g., 'my-dashboard'). WHEN TO USE WHICH MODE: - python_transform: RECOMMENDED for edits. Surgical/pattern-based updates, works on all platforms. - config: New dashboards only, or full restructure. Replaces everything. IMPORTANT: After delete/add operations, indices shift! Subsequent python_transform calls must use fresh config_hash from ha_config_get_dashboard() to get updated structure. Chain multiple ops in ONE expression when possible. TIP: Use ha_config_get_dashboard(entity_id=...) to get the path for any card. PYTHON TRANSFORM EXAMPLES (RECOMMENDED): - Update card icon: 'config["views"][0]["cards"][0]["icon"] = "mdi:thermometer"' - Add card: 'config["views"][0]["cards"].append({"type": "button", "entity": "light.bedroom"})' - Delete card: 'del config["views"][0]["cards"][2]' - Pattern-based update: 'for card in config["views"][0]["cards"]: if "light" in card.get("entity", ""): card["icon"] = "mdi:lightbulb"' - Multi-operation: 'config["views"][0]["cards"][0]["icon"] = "mdi:a"; config["views"][0]["cards"][1]["icon"] = "mdi:b"' MODERN DASHBOARD BEST PRACTICES: - Use "sections" view type (default) with grid-based layouts - Use "tile" cards as primary card type (replaces legacy entity/light/climate cards) - Use "grid" cards for multi-column layouts within sections - Create multiple views with navigation paths (avoid single-view endless scrolling) - Use "area" cards with navigation for hierarchical organization DISCOVERING ENTITY IDs FOR DASHBOARDS: Do NOT guess entity IDs - use these tools to find exact entity IDs: 1. ha_get_overview(include_entity_id=True) - Get all entities organized by domain/area 2. ha_search(query, domain_filter, area_filter, search_types) - Find entities and config-body references in one call If unsure about entity IDs, ALWAYS use one of these tools first. DASHBOARD DOCUMENTATION: - dashboard-guide.md and dashboard-cards.md ship in this response under ``skill_content`` by default — layout patterns, card-type taxonomy, and worked examples. - ha_get_skill_guide — deeper card-type and configuration guidance. EXAMPLES: Create empty dashboard: ha_config_set_dashboard( url_path="mobile-dashboard", title="Mobile View", icon="mdi:cellphone" ) Create dashboard with modern sections view: ha_config_set_dashboard( url_path="home-dashboard", title="Home Overview", config={ "views": [{ "title": "Home", "type": "sections", "sections": [{ "title": "Climate", "cards": [{ "type": "tile", "entity": "climate.living_room", "features": [{"type": "target-temperature"}] }] }] }] } ) Create strategy-based dashboard (auto-generated): ha_config_set_dashboard( url_path="my-home", title="My Home", config={ "strategy": { "type": "home", "favorite_entities": ["light.bedroom"] } } ) Note: Strategy dashboards cannot be converted to custom dashboards via this tool. Use the "Take Control" feature in the Home Assistant interface to convert them. Update existing dashboard config: ha_config_set_dashboard( url_path="existing-dashboard", config={ "views": [{ "title": "Updated View", "type": "sections", "sections": [{ "cards": [{"type": "markdown", "content": "Updated!"}] }] }] } ) Note: When updating an existing dashboard, title/icon/require_admin/show_in_sidebar are also updated if explicitly provided alongside (or instead of) a config change. STORAGE-MODE vs YAML-MODE DASHBOARDS: This tool only manages storage-mode dashboards (created via UI/API and stored in Home Assistant's storage backend). It does NOT touch YAML-defined dashboards. Two distinct YAML cases exist and this tool covers neither: - "YAML-mode" dashboards: written in their own .yaml file referenced from configuration.yaml under ``lovelace: dashboards:``. The dashboard itself lives in a separate YAML file but its registration is in configuration.yaml. - Dashboards inlined directly in ``configuration.yaml`` under the ``lovelace:`` key (legacy single-dashboard mode). For either YAML case, edit the dashboard's .yaml file directly. ``ha_config_set_yaml`` can update the ``lovelace:`` registration entry in configuration.yaml but does NOT touch the dashboard body in the referenced .yaml file.
Parameters
url_path required - (Annotated[str, Field(description="Dashboard URL path (e.g., 'my-dashboard'). Use 'default' or 'lovelace' for the default dashboard. New dashboards must use a hyphenated path.")]) config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description='Dashboard configuration with views and cards. Omit or set to None to create dashboard without initial config. Mutually exclusive with python_transform.')])
= null python_transform - (Annotated[str | None, Field(description='Python expression to transform existing dashboard config. Mutually exclusive with config. Requires config_hash for validation. See PYTHON TRANSFORM SECURITY below for allowed operations. Examples: Simple: python_transform="config[\'views\'][0][\'cards\'][0][\'icon\'] = \'mdi:lamp\'" Pattern: python_transform="for card in config[\'views\'][0][\'cards\']: if \'light\' in card.get(\'entity\', \'\'): card[\'icon\'] = \'mdi:lightbulb\'" Multi-op: python_transform="config[\'views\'][0][\'cards\'][0][\'icon\'] = \'mdi:lamp\'; del config[\'views\'][0][\'cards\'][2]" \n\n' + get_security_documentation())])
= null config_hash - (Annotated[str | None, Field(description='Config hash from ha_config_get_dashboard for optimistic locking. REQUIRED for python_transform (validates dashboard unchanged). Optional for config (validates before full replacement if provided).')])
= null title - (Annotated[str | None, Field(description='Dashboard display name shown in sidebar')])
= null icon - (Annotated[str | None, Field(description="MDI icon name (e.g., 'mdi:home', 'mdi:cellphone'). Defaults to 'mdi:view-dashboard'")])
= null require_admin - (Annotated[bool | None, Field(description='Restrict dashboard to admin users only. For existing dashboards, only updated when explicitly provided.')])
= null show_in_sidebar - (Annotated[bool | None, Field(description='Show dashboard in sidebar navigation. For existing dashboards, only updated when explicitly provided.')])
= null MandatoryBPS - (Annotated[bool, Field(default=True)])
= true return_screenshot - (Annotated[bool, Field(description="After writing, also return a rendered PNG of the dashboard so you can see what it looks like in a single call (the dashboard creation/iteration loop). Requires the 'dashboard screenshot' beta feature + engine add-on/sidecar; if unavailable, the write result is returned with a warning.")])
= false full_page - (Annotated[bool, Field(description=f'With return_screenshot: {FULL_PAGE_PARAM_DESC}.')])
= false Description
Create or update a dashboard resource (inline code or external URL). Provide exactly one of: - content: Inline JavaScript or CSS code (embedded in URL, no file storage needed) - url: External resource URL (/local/, /hacsfiles/, or https://...) INLINE MODE (content=): - Custom card code written inline - CSS styling for dashboards - Small utility modules (<24KB) - URLs are deterministic (same content = same URL) - Supports 'module' and 'css' types only (not 'js') URL MODE (url=): - Files in /config/www/ directory (/local/...) - HACS-installed cards (/hacsfiles/...) - External CDN resources (https://...) - Supports all types: 'module', 'js', 'css' RESOURCE TYPES: - module: ES6 JavaScript modules (recommended for custom cards) - js: Legacy JavaScript files (older custom cards, url mode only) - css: CSS stylesheets (themes, global styles) EXAMPLES: Inline custom card: ha_config_set_dashboard_resource( content=""" class MyCard extends HTMLElement { setConfig(config) { this.config = config; } set hass(hass) { this.innerHTML = `<ha-card>Hello ${hass.states[this.config.entity]?.state}</ha-card>`; } } customElements.define('my-card', MyCard); """, resource_type="module" ) Add custom card from www/ directory: ha_config_set_dashboard_resource( url="/local/my-custom-card.js", resource_type="module" ) Add HACS card (after installing via ha_manage_hacs(action='download')): ha_config_set_dashboard_resource( url="/hacsfiles/lovelace-mushroom/mushroom.js", resource_type="module" ) Update existing resource: ha_config_set_dashboard_resource( url="/local/my-card-v2.js", resource_type="module", resource_id="abc123" ) Note: After adding a resource, clear browser cache or hard refresh (Ctrl+Shift+R) to load changes.
Parameters
content - (Annotated[str | None, Field(description="JavaScript or CSS code to host inline (max ~24KB). The code is embedded in the URL via Cloudflare Worker - no file storage needed. Mutually exclusive with url. Supports 'module' and 'css' types only.")])
= null url - (Annotated[str | None, Field(description='URL of the resource. Can be: /local/file.js (www/ directory), /hacsfiles/component/file.js (HACS), https://cdn.example.com/card.js (external). Mutually exclusive with content.')])
= null resource_type - (Annotated[Literal['module', 'js', 'css'], Field(description="Resource type: 'module' for ES6 modules (modern cards, default), 'js' for legacy JavaScript (url mode only), 'css' for stylesheets")])
= "module" resource_id - (Annotated[str | None, Field(description='Resource ID to update. If omitted, creates a new resource. Get IDs from ha_config_list_dashboard_resources()')])
= null Device Registry
Description
Get device information with pagination, including Zigbee (ZHA/Z2M) and Z-Wave JS devices. Without device_id/entity_id: Lists devices with optional filters and pagination. With device_id or entity_id: Returns full detail for that specific device. **List devices (paginated):** - First page: ha_get_device() - Next page: ha_get_device(offset=50) - By area: ha_get_device(area_id="living_room") - By integration: ha_get_device(integration="zigbee2mqtt") - Full details in list: ha_get_device(detail_level="full", limit=10) **Single device lookup (always full detail):** - By device_id: ha_get_device(device_id="abc123") - By entity_id: ha_get_device(entity_id="light.living_room") **Zigbee:** integration="zha" or "zigbee2mqtt". Returns ieee_address, radio metrics. **Z-Wave:** integration="zwave_js". Returns node_id, node_status.
Parameters
device_id - (Annotated[str | None, Field(description='Device ID to retrieve details for. If omitted, lists devices.', default=None)])
= null entity_id - (Annotated[str | None, Field(description="Entity ID to find the associated device for (e.g., 'light.living_room')", default=None)])
= null integration - (Annotated[str | None, Field(description="Filter devices by integration: 'zha', 'zigbee2mqtt', 'zwave_js', 'mqtt', 'hue', etc.", default=None)])
= null area_id - (Annotated[str | None, Field(description="Filter devices by area ID (e.g., 'living_room')", default=None)])
= null manufacturer - (Annotated[str | None, Field(description="Filter devices by manufacturer name (e.g., 'Philips')", default=None)])
= null limit - (Annotated[int, Field(default=50, ge=1, le=200, description='Max devices to return per page in list mode (default: 50)')])
= 50 offset - (Annotated[int, Field(default=0, ge=0, description='Number of devices to skip for pagination (default: 0)')])
= 0 detail_level - (Annotated[Literal['summary', 'full'], Field(default='summary', description="'summary': basic device info and protocol identifiers (default for list mode). 'full': include entities and all integration details. Single device lookups always return full detail.")])
= "summary" Description
Remove an orphaned device from the Home Assistant device registry. WARNING: This removes the device entry from the registry. - Use only for orphaned devices that are no longer connected - Active devices will typically be re-added by their integration - Associated entities may also be removed This uses the config entry removal which is the safe way to remove devices. If the device has multiple config entries, they must all be removed. EXAMPLES: - Remove orphaned device: ha_remove_device("abc123def456") NOTE: For most use cases, consider disabling the device instead: ha_set_device(device_id="abc123", disabled_by="user")
Parameters
device_id required - (Annotated[str, Field(description='Device ID to remove from the registry')]) Description
Update device properties such as name, area, disabled state, or labels. IMPORTANT: Renaming a device does NOT rename its entities! Device and entity names are independent. To rename entities, use ha_set_entity(new_entity_id=...). Common workflow for full rename: 1. ha_set_device(device_id="abc", name="Living Room Sensor") # Rename device 2. ha_set_entity("sensor.old", new_entity_id="sensor.living_room") # Rename entities separately PARAMETERS: - name: Sets the user-defined display name (name_by_user) - area_id: Assigns device to an area/room. Use '' to remove from area. - disabled_by: Set to 'user' to disable, or empty to enable - labels: List of labels (replaces existing labels) EXAMPLES: - Rename device: ha_set_device("abc123", name="Living Room Hub") - Move to area: ha_set_device("abc123", area_id="living_room") - Disable device: ha_set_device("abc123", disabled_by="user") - Enable device: ha_set_device("abc123", disabled_by="") - Add labels: ha_set_device("abc123", labels=["important", "sensor"])
Parameters
device_id required - (Annotated[str, Field(description='Device ID to update')]) name - (Annotated[str | None, Field(description='New display name for the device (sets name_by_user)', default=None)])
= null area_id - (Annotated[str | None, Field(description="Area/room ID to assign the device to. Use empty string '' to unassign.", default=None)])
= null disabled_by - (Annotated[str | None, Field(description="Set to 'user' to disable, or None/empty string to enable", default=None)])
= null labels - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='Labels to assign to the device (replaces existing labels)', default=None)])
= null Energy
Description
Manage the Home Assistant Energy Dashboard preferences. The Energy Dashboard configuration (grid/solar/battery/gas/water energy sources, individual device consumption sensors for electricity and water, cost tariffs) is stored in ``.storage/energy`` and not otherwise reachable via REST, services, or helper flows — this tool is the only way for agents to inspect or modify it. WHEN TO USE: - mode='get' / 'set': inspect or replace the full Energy Dashboard config. Use 'set' for bulk edits or anything touching multiple top-level keys at once. - mode='add_device' / 'remove_device': add or remove a single device-consumption entry. The tool performs a fresh read-modify-write internally; the caller does NOT manage config_hash. Use ``water=True`` to target the water meter list instead of electricity. - mode='add_source': append a single entry to ``energy_sources`` (grid, solar, battery, gas, or water). Same atomic read-modify-write semantics. WHEN NOT TO USE: - To create the underlying statistics themselves — they must already exist as HA entities before being referenced here; create them via the relevant integration's config flow first. CAVEATS: - ``energy/save_prefs`` has per-key FULL-REPLACE semantics. Passing ``{"device_consumption": [<one entry>]}`` deletes every other device the user had configured — silently, with no error. mode='set' requires a fresh ``config_hash`` for optimistic locking; convenience modes hide this entirely. - ``config_hash`` accepts both a single ``str`` (full-blob lock) and a ``dict[_PrefsKey, str]`` keyed by top-level keys (per-key lock, taken from the ``config_hash_per_key`` field of the mode='get' response). The per-key form lets an agent submit only the top- level key it wants to change — set-equality between ``config`` keys and dict keys is enforced, and any key outside the canonical set (typo, etc.) on either side is rejected with ``VALIDATION_FAILED`` rather than silently dropped (so an empty submission cannot succeed as a no-op). A per-key submission still fully replaces that key's value as the save endpoint requires. Mismatch on any locked key returns ``RESOURCE_LOCKED`` with the offending keys in the response's top-level ``mismatched_keys`` (``create_error_response`` flattens the ``context`` dict onto the response root). - ``dry_run=True`` skips the hash check entirely for both forms; the per-key form is therefore silently accepted on dry runs even if its keys would mismatch the current state. - A local shape check runs before every write; malformed payloads are rejected with a ``shape_errors`` list. - After a successful write, the tool calls ``energy/validate`` and returns any residual issues as ``post_save_validation_errors`` in the response. These reflect semantic problems (missing stats, unit mismatches) that shape checks can't catch; the save persists regardless — correct the config and write again if needed. - The underlying save endpoint is admin-only. Non-admin tokens will receive an authorization error from Home Assistant. - Convenience modes are NOT idempotent: 'add_device' on an existing ``stat_consumption`` returns RESOURCE_ALREADY_EXISTS; 'remove_device' on a missing entry returns RESOURCE_NOT_FOUND. 'add_source' rejects duplicates by ``(type, stat_energy_from)`` for solar/battery/gas/water (RESOURCE_ALREADY_EXISTS); grid entries are appended without a duplicate check (multiple grid variants are legitimate, and grid has no single canonical uniqueness key) — the caller is responsible for de-duplicating grid sources. - Convenience modes do NOT bypass the local shape check on dry_run: ``dry_run=True`` still raises ``RESOURCE_ALREADY_EXISTS`` (duplicate add_device / add_source), ``RESOURCE_NOT_FOUND`` (missing remove_device), or ``VALIDATION_FAILED`` (post-mutator shape error) when the proposed mutation is not applicable. The mutator and shape check both run before the dry-run short-circuit.
Parameters
mode required - (Annotated[Literal['get', 'set', 'add_device', 'remove_device', 'add_source'], Field(description="Operation mode. Primitives: 'get' reads the current prefs; 'set' writes a full prefs payload (per-top-level-key full-replace). Convenience modes: 'add_device' / 'remove_device' / 'add_source' perform a single read-modify-write atomically — no config_hash from the caller, the tool fetches it fresh internally.")]) config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Full prefs payload for mode='set'. Must contain the top-level keys you intend to replace: 'energy_sources', 'device_consumption', 'device_consumption_water'. Any top-level key present in this payload REPLACES the existing list entirely; any omitted key is preserved. Call with mode='get' first, mutate the returned config, then pass the whole object back. Ignored by convenience modes.", default=None)])
= null config_hash - (Annotated[str | dict[_PrefsKey, str] | None, Field(description="Hash from a previous mode='get' call. REQUIRED for mode='set' unless dry_run=True. Two forms: str (full-blob lock) or dict (per-key lock, taken from the config_hash_per_key field of mode='get'). Pass the dict form as a native object, NOT a JSON-encoded string — a stringified dict is treated as a full-blob token and will report RESOURCE_LOCKED; clients that can only send strings should use the str full-blob form. See the tool docstring for fail-closed semantics. Ignored by convenience modes.", default=None)])
= null dry_run - (Annotated[bool, Field(description="If True, no write is performed. For mode='set': runs a local shape check on the proposed config AND calls the server's energy/validate against the CURRENT persisted state (Home Assistant's validate endpoint cannot validate an unsubmitted payload). For convenience modes: simulates the mutation against a fresh read and reports what would change without writing — but still raises RESOURCE_ALREADY_EXISTS (duplicate add_device, or duplicate add_source for solar/battery/gas/water), RESOURCE_NOT_FOUND (missing remove_device), or VALIDATION_FAILED (post-mutator shape error) when the proposed mutation is not applicable. Default False.", default=False)])
= false stat_consumption - (Annotated[str | None, Field(description="Statistic entity_id for mode='add_device' / 'remove_device' (e.g. 'sensor.fridge_energy'). Required for those modes; ignored otherwise.", default=None)])
= null name - (Annotated[str | None, Field(description="Optional display name for mode='add_device'. Only used when adding a new device entry; ignored otherwise.", default=None)])
= null included_in_stat - (Annotated[str | None, Field(description="Optional 'parent' statistic for mode='add_device'. Set this to a statistic that already INCLUDES this device's consumption (e.g., a whole-home or circuit-level meter that this device feeds into). The Energy Dashboard will subtract this device's reading from the parent so the parent's contribution is not double-counted. Ignored otherwise.", default=None)])
= null water - (Annotated[bool, Field(description="If True, mode='add_device' / 'remove_device' targets 'device_consumption_water' instead of 'device_consumption'. Default False.", default=False)])
= false source - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Single energy_sources entry for mode='add_source'. Must contain 'type' (one of grid|solar|battery|gas|water) and the type-specific required fields (e.g. solar/battery/gas/water require 'stat_energy_from'). Every source type also accepts an optional 'name' (display label in the energy graphs); battery additionally accepts 'stat_soc' (state-of-charge statistic). Note: HA Core's voluptuous schema for grid sources requires the full field set (cost_adjustment_day, stat_energy_to, stat_cost, entity_energy_price, number_energy_price, entity_energy_price_export, number_energy_price_export, stat_compensation) — the local shape check is narrower, so a minimal {'type': 'grid'} passes locally but surfaces in post_save_validation_errors after writing. Pass the unused fields as None to satisfy the server. Required for mode='add_source'; ignored otherwise.", default=None)])
= null Entity Registry
Description
Get entity registry information for one or more entities. Returns detailed entity registry metadata including area assignment, custom name/icon, enabled/hidden state, aliases, labels, and more. RELATED TOOLS: - ha_set_entity(): Modify entity properties (area, name, icon, enabled, hidden, aliases) - ha_get_state(): Get current state/attributes (on/off, temperature, etc.) - ha_search(): Find entities by name, domain, or area EXAMPLES: - Single entity: ha_get_entity("sensor.temperature") - Multiple entities: ha_get_entity(["light.living_room", "switch.porch"]) RESPONSE FIELDS: - entity_id: Full entity identifier - name: Custom display name (null if using original_name) - original_name: Default name from integration - icon: Custom icon (null if using default) - area_id: Assigned area/room ID (null if unassigned) - disabled_by: Why disabled (null=enabled, "user"/"integration"/etc) - hidden_by: Why hidden (null=visible, "user"/"integration"/etc) - enabled: Boolean shorthand (True if disabled_by is null) - hidden: Boolean shorthand (True if hidden_by is not null) - aliases: Voice assistant aliases - labels: Assigned label IDs - categories: Category assignments (dict mapping scope to category_id) - device_class: User "Show As" override (null = use original_device_class) - original_device_class: Default device class from the integration - options: Per-domain registry options (e.g. sensor display_precision). Voice-assistant exposure is also stored here but should be set/cleared via the ha_set_entity(expose_to=...) parameter, not the options dict. - platform: Integration platform (e.g., "hue", "zwave_js") - device_id: Associated device ID (null if standalone) - config_entry_id: Parent config entry's ID (null for YAML-only entities). When non-null — e.g. for UI-created template/group/ utility_meter/derivative/... helpers — pass it to ``ha_get_integration(entry_id=..., include_options=True)`` to read the helper's current config (template body, group members, etc.) without scanning a domain list. - unique_id: Integration's unique identifier
Parameters
entity_id required - (Annotated[str | list[str], JSON_STRING_COERCION, Field(description="Entity ID or list of entity IDs to retrieve (e.g., 'sensor.temperature' or ['light.living_room', 'switch.porch'])")]) Description
Get entity exposure settings - list all or get settings for a specific entity. Without an entity_id: Lists all entities and their exposure status to voice assistants (Alexa, Google Assistant, Assist). With an entity_id: Returns which voice assistants the specific entity is exposed to. EXAMPLES: - List all exposures: ha_get_entity_exposure() - Filter by assistant: ha_get_entity_exposure(assistant="cloud.alexa") - Get specific entity: ha_get_entity_exposure(entity_id="light.living_room") RETURNS (when listing): - exposed_entities: Dict mapping entity_ids to their exposure status - summary: Count of entities exposed to each assistant RETURNS (when getting specific entity): - exposed_to: Dict of assistant -> True/False for each assistant - is_exposed_anywhere: True if exposed to at least one assistant
Parameters
entity_id - (Annotated[str | None, Field(description='Entity ID to check exposure settings for. If omitted, lists all entities with exposure settings.', default=None)])
= null assistant - (Annotated[str | None, Field(description="Filter by assistant: 'conversation', 'cloud.alexa', or 'cloud.google_assistant'. If not specified, returns all.", default=None)])
= null Description
Remove an entity from the Home Assistant entity registry. Permanently removes the entity registration from Home Assistant. The entity will no longer appear in the UI or be available to automations. WARNING: This permanently removes the entity registration. - Use only for orphaned or stale entity entries - If the underlying device or integration is still active, the entity may be re-added automatically on the next HA restart or reload - This action cannot be undone without restoring from backup EXAMPLES: - Remove orphaned sensor: ha_remove_entity("sensor.old_temperature") - Remove stale helper entry: ha_remove_entity("input_boolean.deleted_helper") NOTE: For most use cases, consider disabling instead: ha_set_entity(entity_id="sensor.old", enabled=False) RELATED TOOLS: - ha_search: Find entities to verify the entity_id before removing - ha_get_entity: Check entity details before removal
Parameters
entity_id required - (Annotated[str, Field(description="Entity ID to remove from the entity registry (e.g., 'sensor.old_temperature'). This permanently removes the entity registration.")]) Description
Update entity properties in the entity registry. Allows modifying entity metadata such as area assignment, display name, icon, "Show As" device class override, per-domain registry options, enabled/disabled state, visibility, aliases, labels, voice assistant exposure, and entity_id rename in a single call. BULK OPERATIONS: When entity_id is a list, only labels, expose_to, and categories parameters are supported. Other parameters (area_id, name, icon, device_class, options, enabled, hidden, aliases, new_entity_id, new_device_name) require single entity. LABEL OPERATIONS: - label_operation="set" (default): Replace all labels with the provided list. Use [] to clear. - label_operation="add": Add labels to existing ones without removing any. - label_operation="remove": Remove specified labels from the entity. SHOW AS / DEVICE CLASS: device_class overrides the entity's display device class — equivalent to the HA UI's "Show As" dropdown. Use empty string '' to clear. Applies instantly, no reload needed. REGISTRY OPTIONS: options carries per-domain registry options (sensor display_precision, weather forecast_type, etc). Pass {domain: {key: value}}; multi-domain dicts are sent as separate registry updates because HA's WS schema requires options_domain + options to be paired one domain at a time. ENTITY ID RENAME: Use new_entity_id to change an entity's ID (e.g., sensor.old -> sensor.new). Domain must match. Voice exposure settings are preserved automatically. WARNING: Renaming an entity_id does NOT update references in automations, scripts, templates, or dashboards. All consumers of the old entity_id must be updated manually — HA does not propagate the rename automatically. Rename limitations: - Entity history is preserved (HA 2022.4+) - Entities without unique IDs cannot be renamed - Entities disabled by their integration cannot be renamed DEVICE RENAME: Use new_device_name to rename the associated device. Can be combined with new_entity_id to rename both in one call. The device is looked up automatically. Use ha_search() or ha_get_device() to find entity IDs. Use ha_config_get_label() to find available label IDs. EXAMPLES: Single entity: - Assign to area: ha_set_entity("sensor.temp", area_id="living_room") - Rename display name: ha_set_entity("sensor.temp", name="Living Room Temperature") - Set Show As: ha_set_entity("binary_sensor.zone_10", device_class="window") - Clear Show As: ha_set_entity("binary_sensor.zone_10", device_class="") - Set sensor precision: ha_set_entity("sensor.power", options={"sensor": {"display_precision": 2}}) - Rename entity_id: ha_set_entity("light.old_name", new_entity_id="light.new_name") - Rename entity and device: ha_set_entity("light.old", new_entity_id="light.new", new_device_name="New Lamp") - Rename entity_id with friendly name: ha_set_entity("sensor.old", new_entity_id="sensor.new", name="New Name") - Set labels: ha_set_entity("light.lamp", labels=["outdoor", "smart"]) - Add labels: ha_set_entity("light.lamp", labels=["new_label"], label_operation="add") - Remove labels: ha_set_entity("light.lamp", labels=["old_label"], label_operation="remove") - Clear labels: ha_set_entity("light.lamp", labels=[]) - Expose to Alexa: ha_set_entity("light.lamp", expose_to={"cloud.alexa": True}) Bulk operations: - Set labels on multiple: ha_set_entity(["light.a", "light.b"], labels=["outdoor"]) - Add labels to multiple: ha_set_entity(["light.a", "light.b"], labels=["new"], label_operation="add") - Expose multiple to Alexa: ha_set_entity(["light.a", "light.b"], expose_to={"cloud.alexa": True}) ENABLED/DISABLED WARNING: Setting enabled=False performs a **registry-level disable** — the entity is completely removed from the Home Assistant state machine and hidden from the UI. It will NOT appear in state queries, dashboards, or automations until re-enabled AND the integration is reloaded. This is NOT the same as "turning off" an entity. For automations and scripts, enabled=False is blocked. Use these instead: - ha_call_service("automation", "turn_off", entity_id="automation.xxx") - ha_call_service("script", "turn_off", entity_id="script.xxx")
Parameters
entity_id required - (Annotated[str | list[str], JSON_STRING_COERCION, Field(description='Entity ID or list of entity IDs to update. Bulk operations (list) only support labels, expose_to, and categories parameters.')]) area_id - (Annotated[str | None, Field(description="Area/room ID to assign the entity to. Use empty string '' to unassign from current area. Single entity only.", default=None)])
= null name - (Annotated[str | None, Field(description="Display name for the entity. Use empty string '' to remove custom name and revert to default. Single entity only.", default=None)])
= null icon - (Annotated[str | None, Field(description="Icon for the entity (e.g., 'mdi:thermometer'). Use empty string '' to remove custom icon. Single entity only.", default=None)])
= null device_class - (Annotated[str | None, Field(description="Override the entity's display device class — what the HA UI's 'Show As' dropdown writes. Use empty string '' to clear the override and fall back to the integration default. None (the default) means 'no change' — pass an explicit '' to clear. Single entity only. Examples: 'window', 'door', 'motion' for binary_sensor; 'temperature', 'humidity' for sensor.", default=None)])
= null options - (Annotated[dict[str, dict[str, Any]] | None, JSON_STRING_COERCION, Field(description='Per-domain entity registry options (e.g. sensor \'display_precision\', weather \'forecast_type\'). Pass a dict mapping domain to a sub-dict, e.g. {"sensor": {"display_precision": 2}}. Multiple domains are sent as separate registry updates. For \'Show As\' use the dedicated `device_class` parameter — that is what the HA UI Show As dropdown writes. Voice-assistant exposure is stored under `options.<assistant>.should_expose` but must be managed via the dedicated `expose_to` parameter, not this options dict. Single entity only.', default=None)])
= null enabled - (Annotated[bool | None, Field(description='True to enable the entity, False to disable it. Single entity only. WARNING: Setting enabled=False is a registry-level disable — it completely removes the entity from the state machine and hides it from the UI. A reload or restart is required to restore it after re-enabling. NOT allowed for automation or script entities — use automation.turn_off / script.turn_off via ha_call_service() instead.', default=None)])
= null hidden - (Annotated[bool | None, Field(description='True to hide the entity from UI, False to show it. Single entity only.', default=None)])
= null aliases - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='List of voice assistant aliases for the entity (replaces existing aliases). Single entity only.', default=None)])
= null categories - (Annotated[dict[str, str | None] | None, JSON_STRING_COERCION, Field(description='Category assignment as a dict mapping scope to category_id. Example: {"automation": "category_id_here"}. Use null value to clear: {"automation": null}. Single entity only.', default=None)])
= null labels - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='List of label IDs for the entity. Behavior depends on label_operation parameter. Supports bulk operations.', default=None)])
= null label_operation - (Annotated[Literal['set', 'add', 'remove'], Field(description="How to apply labels: 'set' replaces all labels, 'add' adds to existing, 'remove' removes specified labels.", default='set')])
= "set" expose_to - (Annotated[dict[str, bool] | None, JSON_STRING_COERCION, Field(description='Control voice assistant exposure. Pass a dict mapping assistant IDs to booleans. Valid assistants: \'conversation\' (Assist), \'cloud.alexa\', \'cloud.google_assistant\'. Example: {"conversation": true, "cloud.alexa": false}. Supports bulk operations.', default=None)])
= null new_entity_id - (Annotated[str | None, Field(description="New entity ID to rename to (e.g., 'light.new_name'). Domain must match the original. Single entity only.", default=None)])
= null new_device_name - (Annotated[str | None, Field(description='New display name for the associated device. If provided, both entity and device are updated in one operation. Single entity only.', default=None)])
= null Files
Description
Delete a file from allowed directories in the Home Assistant config. Permanently removes a file from the allowed directories. This action cannot be undone. **Allowed Delete Directories:** - `www/` - Web assets - `themes/` - Theme files - `custom_templates/` - Template files - `dashboards/` - YAML-mode dashboard files - Plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`, `/backup`) configured in the ha-mcp settings UI (pass the absolute path for volumes) **Security:** - Only the directories above allow deletions - Configuration files cannot be deleted - Path traversal (../) is blocked - Requires confirm=True to prevent accidents **Returns:** - success: Whether the operation succeeded - path: The file path that was deleted - message: Confirmation message **Example:** ```python # Delete an old CSS file result = ha_delete_file( path="www/deprecated-style.css", confirm=True ) ```
Parameters
path required - (Annotated[str, Field(description="File path. Must be in a writable built-in dir (www/, themes/, custom_templates/, dashboards/), a configured custom directory, or a configured HAOS sibling volume (/share, /media, /ssl, /backup — pass the absolute path). Example: 'www/old-file.css'")]) confirm - (Annotated[bool, Field(default=False, description='Must be True to confirm deletion. This is a safety measure to prevent accidental deletions.')])
= false Description
List files in a directory within the Home Assistant config directory. Lists files in allowed directories (www/, themes/, custom_templates/, dashboards/) with optional glob pattern filtering. Returns file names, sizes, and modification times. **Allowed Directories:** - `www/` - Web assets (CSS, JS, images for dashboards) - `themes/` - Theme files - `custom_templates/` - Jinja2 template files - `dashboards/` - YAML-mode dashboard files - Plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`, `/backup`) configured in the ha-mcp settings UI (pass the absolute path for volumes) **Security:** Only directories in the allowed list can be accessed. Path traversal attempts (../) are blocked. **Returns:** - success: Whether the operation succeeded - path: The directory path that was listed - files: List of file info objects with name, size, is_dir, modified - count: Number of files found **Example:** ```python # List all CSS files in www/ result = ha_list_files(path="www/", pattern="*.css") ```
Parameters
path required - (Annotated[str, Field(description="Directory path. Relative to the config dir for the built-in allowlist (www/, themes/, custom_templates/, dashboards/). Custom directories and HAOS sibling volumes (/share, /media, /ssl, /backup) configured in the ha-mcp settings UI are also allowed (pass the absolute path). Example: 'www/' or '/share/llm'")]) pattern - (Annotated[str | None, Field(default=None, description="Optional glob pattern to filter files. Example: '*.css', '*.yaml', '*.js'")])
= null Description
Read a file from the Home Assistant config directory. Reads files from allowed paths within the config directory. Some files have special handling: - `secrets.yaml`: Values are masked for security - `home-assistant.log`: Limited to tail (last N lines) by default **Allowed Read Paths:** - `configuration.yaml`, `automations.yaml`, `scripts.yaml`, `scenes.yaml` - `secrets.yaml` (values masked) - `packages/*.yaml` - `home-assistant.log` (tail only) - `www/**`, `themes/**`, `custom_templates/**`, `dashboards/**` - `custom_components/**/*.py` (read-only) - Plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`, `/backup`) configured in the ha-mcp settings UI (pass the absolute path for volumes) **Security:** - Path traversal (../) is blocked - Only allowed paths can be read - Sensitive data in secrets.yaml is masked **Returns:** - success: Whether the operation succeeded - content: The file content (may be truncated for logs) - size: File size in bytes - modified: Last modification timestamp - path: The file path that was read **Example:** ```python # Read configuration result = ha_read_file(path="configuration.yaml") # Read last 100 lines of log result = ha_read_file(path="home-assistant.log", tail_lines=100) ```
Parameters
path required - (Annotated[str, Field(description="File path. Relative to the config dir for the built-in allowlist; absolute for a configured HAOS sibling volume (/share, /media, /ssl, /backup). Examples: 'configuration.yaml', 'www/custom.css', '/share/llm/notes.md'")]) tail_lines - (Annotated[int | None, Field(default=None, ge=1, le=10000, description='For log files, return only the last N lines. Recommended for home-assistant.log to avoid large responses. Default: None (return full file, or last 1000 lines for logs)')])
= null Description
Write a file to allowed directories in the Home Assistant config. Creates or updates files in restricted directories only. This is useful for: - Creating custom CSS/JS for dashboards - Creating Jinja2 templates **Allowed Write Directories:** - `www/` - Web assets for dashboards - `themes/` - Theme YAML files - `custom_templates/` - Jinja2 template files - `dashboards/` - YAML-mode dashboard files - Plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`, `/backup`) configured in the ha-mcp settings UI (pass the absolute path for volumes) **Security:** - Only the directories above allow writes - Configuration files (configuration.yaml, etc.) cannot be written - Path traversal (../) is blocked Text content only. Overwriting a file that currently holds binary content still succeeds, but its prior bytes cannot be captured by auto-backup (only modifications/deletions of text files are snapshotted); the skip is logged, the write is not blocked. **Returns:** - success: Whether the operation succeeded - path: The file path that was written - size: Size of the written file in bytes - created: Whether this was a new file (vs overwrite) **Example:** ```python # Create a custom CSS file result = ha_write_file( path="www/custom-dashboard.css", content=".card { background: #333; }", overwrite=True ) # Create a custom Jinja template file result = ha_write_file( path="custom_templates/formatters.jinja", content="{% macro shout(text) %}{{ text | upper }}{% endmacro %}", overwrite=False ) ```
Parameters
path required - (Annotated[str, Field(description="File path. Must be in a writable built-in dir (www/, themes/, custom_templates/, dashboards/), a configured custom directory, or a configured HAOS sibling volume (/share, /media, /ssl, /backup — pass the absolute path). Example: 'www/custom.css', '/share/llm/out.txt'")]) content required - (Annotated[str, Field(description='The content to write to the file.')]) overwrite - (Annotated[bool, Field(default=False, description='Whether to overwrite if file exists. Default is False to prevent accidental overwrites.')])
= false create_dirs - (Annotated[bool, Field(default=True, description="Whether to create parent directories if they don't exist. Default is True.")])
= true Groups
Description
List all Home Assistant entity groups with their member entities. Returns all groups created via group.set service or YAML configuration, including: - Entity ID (group.xxx) - Friendly name - State (on/off based on member states) - Member entities - Icon (if set) - All mode (if all entities must be on) EXAMPLES: - List all groups: ha_config_list_groups() **NOTE:** This returns old-style groups (created via group.set or YAML). Platform-specific groups (light groups, cover groups) are separate entities.
Description
Remove a service-based Home Assistant entity group via the group.remove service. **When NOT to use:** for groups created through `ha_config_set_helper(helper_type="group", ...)`, use `ha_remove_helpers_integrations`. Those config-entry-backed groups are not reachable via the group.remove service. **When to use:** removing groups created with `ha_config_set_group` or defined in YAML via `group:` configuration. Config-entry-backed deletion tools cannot find these. EXAMPLES: - Remove group: ha_config_remove_group("living_room_lights") Use ha_config_list_groups() to find existing groups. **WARNING:** - Removing a group used in automations may cause those automations to fail. - Groups defined in YAML can be removed at runtime but will reappear after restart. - This only removes old-style groups, not platform-specific groups.
Parameters
object_id required - (Annotated[str, Field(description="Group identifier without 'group.' prefix (e.g., 'living_room_lights')")]) wait - (Annotated[bool, Field(description='Wait for group to be fully removed before returning. Default: True.', default=True)])
= true Description
Create or update a service-based Home Assistant entity group via the group.set service. **When NOT to use:** for typical "combine these entities into one controllable group" requests, prefer `ha_config_set_helper(helper_type="group", ...)`. Config-entry-backed groups are registered in the entity registry, so `ha_set_entity` can assign them to areas and they are deletable via `ha_remove_helpers_integrations`. **When to use:** compatibility with existing groups already configured via group.set or YAML, or the rare case where entity-registry membership is explicitly unwanted. Groups created here are only removable via `ha_config_remove_group` — `ha_remove_helpers_integrations` will not find them. **For NEW groups:** Provide object_id and entities (required). **For EXISTING groups:** Provide object_id and any fields to update. EXAMPLES: - Create group: ha_config_set_group("bedroom_lights", entities=["light.lamp", "light.ceiling"]) - Create with name: ha_config_set_group("sensors", entities=["sensor.temp"], name="All Sensors") - Update name: ha_config_set_group("lights", name="Living Room Lights") - Add entities: ha_config_set_group("lights", add_entities=["light.extra"]) - Remove entities: ha_config_set_group("lights", remove_entities=["light.old"]) - Replace all entities: ha_config_set_group("lights", entities=["light.new1", "light.new2"]) **NOTE:** entities, add_entities, and remove_entities are mutually exclusive.
Parameters
object_id required - (Annotated[str, Field(description="Group identifier without 'group.' prefix (e.g., 'living_room_lights')")]) entities - (Annotated[list[str] | None, JSON_STRING_COERCION, Field(description='List of entity IDs for the group. Required when creating new group. When updating, replaces all entities (mutually exclusive with add_entities/remove_entities).', default=None)])
= null name - (Annotated[str | None, Field(description='Friendly display name for the group', default=None)])
= null icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:lightbulb-group')", default=None)])
= null all_on - (Annotated[bool | None, Field(description='If True, all entities must be on for group to be on (default: False)', default=None)])
= null add_entities - (Annotated[list[str] | None, JSON_STRING_COERCION, Field(description='Add these entities to an existing group (mutually exclusive with entities)', default=None)])
= null remove_entities - (Annotated[list[str] | None, JSON_STRING_COERCION, Field(description='Remove these entities from an existing group (mutually exclusive with entities)', default=None)])
= null wait - (Annotated[bool, Field(description='Wait for group to be queryable before returning. Default: True. Set to False for bulk operations.', default=True)])
= true HACS
Description
Get HACS (Home Assistant Community Store) data — search the store or fetch repository details. Use ``action="search"`` to search/browse/list store repositories, or ``action="info"`` for one repository's full details (README, versions, GitHub stats). This tool is read-only; to install or add repositories use ``ha_manage_hacs``, and for non-HACS entities/config use the domain-specific tools. **DASHBOARD TIP:** ``action="search", installed_only=True, category="lovelace"`` discovers installed custom cards to wire into ``ha_config_set_dashboard()``. **Examples:** - Search the store: ha_get_hacs_info(action="search", query="mushroom", category="lovelace") - List installed: ha_get_hacs_info(action="search", installed_only=True) - Repository details: ha_get_hacs_info(action="info", repository_id="441028036") **Caveats:** ``info`` fetches full repository detail from GitHub, so it can hit GitHub rate limits / needs HACS's configured GitHub token; ``search`` reads HACS's locally cached repository index. ``repository_id`` accepts a numeric HACS ID or an ``owner/repo`` path.
Parameters
action required - (Annotated[Literal['search', 'info'], Field(description="'search' the store, or 'info' for one repository")]) query - (Annotated[str, Field(description="Search keyword (action='search')")])
= "" category - (Annotated[Literal['integration', 'lovelace', 'theme', 'appdaemon', 'python_script'] | None, Field(description="Filter by category (action='search')")])
= null installed_only - (Annotated[bool, Field(description="Only return installed repositories (action='search', default: False)")])
= false max_results - (Annotated[int, Field(ge=1, le=100, description="Maximum number of results (action='search', default: 10, max: 100)")])
= 10 offset - (Annotated[int, Field(ge=0, description="Results to skip for pagination (action='search', default: 0)")])
= 0 repository_id - (Annotated[str | None, Field(description="Numeric HACS ID or 'owner/repo' path (action='info')")])
= null Description
Manage HACS (Home Assistant Community Store) — install/update or add custom repositories. Use ``action="download"`` to install or update a repository, or ``action="add_repository"`` to register a custom GitHub repository with HACS. This tool performs writes; to search the store or read repository details use ``ha_get_hacs_info``. **Examples:** - Install latest: ha_manage_hacs(action="download", repository_id="441028036") - Install a version: ha_manage_hacs(action="download", repository_id="piitaya/lovelace-mushroom", version="v4.0.0") - Add a custom repo: ha_manage_hacs(action="add_repository", repository="owner/repo", category="lovelace") **Caveats:** Installing an integration usually needs a Home Assistant restart to activate; new Lovelace cards need a browser cache clear. ``repository_id`` accepts a numeric HACS ID or an ``owner/repo`` path; ``add_repository`` requires ``owner/repo`` format plus a matching ``category``.
Parameters
action required - (Annotated[Literal['download', 'add_repository'], Field(description="'download' to install/update, or 'add_repository'")]) repository_id - (Annotated[str | None, Field(description="Numeric HACS ID or 'owner/repo' path (action='download')")])
= null version - (Annotated[str | None, Field(description="Specific version to install (action='download')")])
= null repository - (Annotated[str | None, Field(description="GitHub repo 'owner/repo' to add (action='add_repository')")])
= null category - (Annotated[Literal['integration', 'lovelace', 'theme', 'appdaemon', 'python_script'] | None, Field(description="Repository category (action='add_repository')")])
= null Helper Entities
Description
List all Home Assistant helpers of a specific type with their configurations. Returns complete configuration for all helpers of the specified type including: - ID, name, icon - Type-specific settings (min/max for input_number, options for input_select, etc.) - Area and label assignments SUPPORTED HELPER TYPES: - input_button: Virtual buttons for triggering automations - input_boolean: Toggle switches/checkboxes - input_select: Dropdown selection lists - input_number: Numeric sliders/input boxes - input_text: Text input fields - input_datetime: Date/time pickers - counter: Counters with increment/decrement/reset - timer: Countdown timers with start/pause/cancel - schedule: Weekly schedules with time ranges (on/off per day) - zone: Geographical zones for presence detection - person: Person entities linked to device trackers - tag: NFC/QR tags for automation triggers EXAMPLES: - List all number helpers: ha_config_list_helpers("input_number") - List all counters: ha_config_list_helpers("counter") - List all zones: ha_config_list_helpers("zone") - List all persons: ha_config_list_helpers("person") - List all tags: ha_config_list_helpers("tag") **NOTE:** This only returns storage-based helpers (created via UI/API), not YAML-defined helpers. Flow-based types (template / group / utility_meter / derivative / etc.) cannot be listed via this tool — use ``ha_search`` for those. For detailed helper documentation, use ha_get_skill_guide.
Parameters
helper_type required - (Annotated[Literal['input_button', 'input_boolean', 'input_select', 'input_number', 'input_text', 'input_datetime', 'counter', 'timer', 'schedule', 'zone', 'person', 'tag'], Field(description='Type of helper entity to list')]) Description
Create or update Home Assistant helper entities and config subentries (28 types, unified interface). MUST call ha_get_skill_guide first. SIMPLE/FLOW helper create requires `name`; SIMPLE/FLOW helper update requires `helper_id`. Config subentry create requires `entry_id` and `subentry_type`; config subentry update also requires `subentry_id`. SIMPLE types (structured params, WebSocket API): input_boolean, input_button, input_select, input_number, input_text, input_datetime, counter, timer, schedule, zone, person, tag. FLOW types (pass `config` dict, Config Entry Flow API): template, group, utility_meter, derivative, min_max, threshold, integration, statistics, trend, random, filter, tod, generic_thermostat, switch_as_x, generic_hygrostat. Note: `tod` is the purpose-built "is-current-time-in-range" indicator (supports cross-midnight ranges, unlike `schedule`). CONFIG_SUBENTRY type (Config Subentry Flow API): config_subentry. Pass `entry_id`, `subentry_type`, and `config`. Pass `subentry_id` to reconfigure an existing subentry; omit it to create a new subentry. For flow-type updates, pass the existing entry_id as `helper_id`. Options flows reject the `name` key on update — to rename a flow helper, delete and recreate. Behavior notes: - UPDATE preserves type-specific fields not re-passed (rename never wipes initial/icon/etc. for any simple helper). - Pass `action="create"` or `action="update"` to disambiguate intent. For SIMPLE/FLOW helpers, omitted action falls back to the implicit `helper_id`-presence discriminator. For config subentries, omitted action falls back to the `subentry_id`-presence discriminator. - For flow-based helpers, config keys not declared by any step's data_schema are silently ignored by HA; submit once and the validation error returns the `data_schema` for that helper so subsequent calls use the correct field names. - Validation errors raised by this tool carry the helper's `data_schema` in the response context (and `menu_options` for menu-rooted helpers like `template`/`group` when no sub-type is chosen yet) so a follow-up call can self-correct without a separate schema-discovery round-trip. EXAMPLES (menu-based types + tod, where first-call payload is non-obvious): - template sensor: ha_config_set_helper(helper_type="template", name="Room Temp", config={"next_step_id": "sensor", "state": "{{ states('sensor.x')|float }}", "unit_of_measurement": "°C"}) - group (light): ha_config_set_helper(helper_type="group", name="Kitchen Lights", config={"group_type": "light", "entities": ["light.a", "light.b"]}) - tod (time-of-day indicator, cross-midnight OK): ha_config_set_helper(helper_type="tod", name="Quiet Hours", config={"after_time": "22:00:00", "before_time": "07:00:00"}) - config subentry (create under an existing integration): ha_config_set_helper(helper_type="config_subentry", entry_id="01HXYZ...", subentry_type="conversation", config={"name": "Local agent", "model": "gemma3:27b"}) ``helper-selection.md`` ships in this response under ``skill_content`` by default — decision matrix for picking the right helper type plus worked examples and per-type field tables. For deeper helper-design guidance beyond what ships here, call ha_get_skill_guide.
Parameters
helper_type required - (Annotated[Literal['counter', 'config_subentry', 'derivative', 'filter', 'generic_hygrostat', 'generic_thermostat', 'group', 'input_boolean', 'input_button', 'input_datetime', 'input_number', 'input_select', 'input_text', 'integration', 'min_max', 'person', 'random', 'schedule', 'statistics', 'switch_as_x', 'tag', 'template', 'threshold', 'timer', 'tod', 'trend', 'utility_meter', 'zone'], Field(description='Type of helper entity to create or update')]) name - (Annotated[str | None, Field(description="Display name for simple/flow helper creation. Required when creating a helper without helper_id. Optional on helper update. Ignored for helper_type='config_subentry', which uses entry_id/subentry_type/subentry_id instead. For flow-based helper updates (template, group, utility_meter, ...), this is typically ignored because options flows don't expose renaming. Rename a flow helper by deleting and recreating instead.", default=None)])
= null helper_id - (Annotated[str | None, Field(description="REQUIRED when updating an existing helper. Bare ID ('my_button') or full entity ID ('input_button.my_button'). Omit to create a new helper.", default=None)])
= null entry_id - (Annotated[str | None, Field(description="Parent config entry ID when helper_type='config_subentry'. Use ha_get_integration() to find entry IDs.", default=None)])
= null subentry_type - (Annotated[str | None, Field(description="Integration-defined subentry type when helper_type='config_subentry'.", default=None)])
= null subentry_id - (Annotated[str | None, Field(description="Existing config subentry ID to reconfigure when helper_type='config_subentry'. Omit to create.", default=None)])
= null show_advanced_options - (Annotated[bool, Field(description="When helper_type='config_subentry', ask older Home Assistant versions to expose advanced flow options. No-op on HA 2026.6+; pending removal before HA 2027.6.", default=False)])
= false icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:bell', 'mdi:toggle-switch')", default=None)])
= null area_id - (Annotated[str | None, Field(description='Area/room ID to assign the helper to', default=None)])
= null labels - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='Labels to categorize the helper', default=None)])
= null min_value - (Annotated[float | None, Field(description="Minimum value (input_number/counter) or minimum length (input_text). Also accepts shorthand 'min'.", default=None, validation_alias=AliasChoices('min_value', 'min'))])
= null max_value - (Annotated[float | None, Field(description="Maximum value (input_number/counter) or maximum length (input_text). Also accepts shorthand 'max'.", default=None, validation_alias=AliasChoices('max_value', 'max'))])
= null step - (Annotated[float | None, Field(description='Step/increment value for input_number or counter', default=None)])
= null unit_of_measurement - (Annotated[str | None, Field(description="Unit of measurement for input_number (e.g., '°C', '%', 'W'). Also accepts shorthand 'unit'.", default=None, validation_alias=AliasChoices('unit_of_measurement', 'unit'))])
= null options - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='List of options for input_select (required for input_select)', default=None)])
= null initial - (Annotated[str | int | None, Field(description='Initial value for the helper (input_select, input_text, input_boolean, input_datetime, counter)', default=None)])
= null mode - (Annotated[str | None, Field(description="Display mode: 'box'/'slider' for input_number, 'text'/'password' for input_text", default=None)])
= null has_date - (Annotated[bool | None, Field(description='Include date component for input_datetime', default=None)])
= null has_time - (Annotated[bool | None, Field(description='Include time component for input_datetime', default=None)])
= null restore - (Annotated[bool | None, Field(description='Restore state after restart (counter, timer). Defaults to True for counter, False for timer', default=None)])
= null duration - (Annotated[str | None, Field(description="Default duration for timer in format 'HH:MM:SS' or seconds (e.g., '0:05:00' for 5 minutes)", default=None)])
= null monday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Monday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes (e.g. {'from': '07:00', 'to': '22:00', 'data': {'mode': 'comfort'}})", default=None)])
= null tuesday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Tuesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null wednesday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Wednesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null thursday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Thursday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null friday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Friday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null saturday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Saturday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null sunday - (Annotated[list[dict[str, Any]] | None, JSON_STRING_COERCION, Field(description="Schedule time ranges for Sunday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.", default=None)])
= null latitude - (Annotated[float | None, Field(description='Latitude for zone (required for zone)', default=None)])
= null longitude - (Annotated[float | None, Field(description='Longitude for zone (required for zone)', default=None)])
= null radius - (Annotated[float | None, Field(description='Radius in meters for zone (default: 100)', default=None)])
= null passive - (Annotated[bool | None, Field(description="Passive zone (won't trigger state changes for person entities)", default=None)])
= null user_id - (Annotated[str | None, Field(description='User ID to link to person entity', default=None)])
= null device_trackers - (Annotated[list[str] | None, JSON_STRING_COERCION, Field(description='List of device_tracker entity IDs for person', default=None)])
= null picture - (Annotated[str | None, Field(description='Picture URL for person entity', default=None)])
= null tag_id - (Annotated[str | None, Field(description="Tag ID for tag. On create, omit to auto-generate a unique uuid4 hex (HA's tag/create requires this field; the tool fills it in for you). On update, the tag's existing tag_id is required (passed via helper_id).", default=None)])
= null description - (Annotated[str | None, Field(description='Description for tag', default=None)])
= null category - (Annotated[str | None, Field(description="Category ID to assign to this helper. Use ha_config_get_category(scope='helpers') to list available categories, or ha_config_set_category() to create one.", default=None)])
= null config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Config dict for flow-based helper types and helper_type='config_subentry' (template, group, utility_meter, derivative, min_max, threshold, integration, statistics, trend, random, filter, tod, generic_thermostat, switch_as_x, generic_hygrostat). Ignored for simple helper types. Field set is delivered as data_schema on the first validation error.", default=None)])
= null wait - (Annotated[bool, Field(description='Wait for helper entity to be queryable before returning. Default: True. Set to False for bulk operations.', default=True)])
= true action - (Annotated[Literal['create', 'update'] | None, Field(description="Explicit intent: 'create' a new helper or 'update' an existing one. When omitted, falls back to the implicit discriminator: presence of helper_id => update, absence => create. Pass 'create' or 'update' to disambiguate (e.g. so a typo in helper_id surfaces as a clear 'helper not found' error instead of being mistaken for a create call).", default=None)])
= null MandatoryBPS - (Annotated[bool, Field(default=True)])
= true Description
Remove a Home Assistant helper or integration config entry. Unifies three backend removal mechanisms — simple-helper websocket delete, config-entry delete, and config-subentry delete — behind one entry point with four routing paths driven by helper_type. WHEN NOT TO USE: - Removing only an entity (without deleting its underlying helper or config entry) — use `ha_remove_entity` instead. - YAML-configured helpers — they have no storage backend. Edit the YAML file and reload the relevant integration. SUPPORTED HELPER TYPES: - SIMPLE (12, websocket-delete): input_button, input_boolean, input_select, input_number, input_text, input_datetime, counter, timer, schedule, zone, person, tag. - FLOW (15, config-entry-delete via entity lookup): template, group, utility_meter, derivative, min_max, threshold, integration, statistics, trend, random, filter, tod, generic_thermostat, switch_as_x, generic_hygrostat. ROUTING: - SIMPLE helper_type + bare helper_id or entity_id → websocket delete. - FLOW helper_type + entity_id → resolve entity_id to config_entry_id via entity_registry, then delete the config entry. All sub-entities (e.g. utility_meter tariffs) are removed together. - helper_type=None + entry_id → direct config entry delete (any integration). - helper_type="config_subentry" + parent entry_id + subentry_id → delete one config subentry. MISSING-TARGET CONTRACT: A target that is *confirmed absent* raises a structured error rather than returning silent success, so a typo'd or stale identifier surfaces immediately at the caller layer (the ``success`` boolean is what agent wrappers branch on). The error code per-path follows the target shape: - SIMPLE (bare helper_id or entity_id): state-machine empty AND entity registry empty → raises ``ENTITY_NOT_FOUND``. - FLOW (entity_id): not in entity registry → raises ``ENTITY_NOT_FOUND``. YAML-configured helpers (no config entry backing) raise ``RESOURCE_NOT_FOUND``. A bare helper_id (no ``.``) on a FLOW target raises ``ENTITY_NOT_FOUND`` — FLOW resolution needs a full entity_id. TOCTOU 404 on the resolved entry_id raises ``RESOURCE_NOT_FOUND``. - Direct config entry (helper_type=None): backend returns HTTP 404 → raises ``RESOURCE_NOT_FOUND``. - Config subentry: backend returns a "not_found" error → raises ``RESOURCE_NOT_FOUND``. Idempotency at the contract level still holds (call N times = same response). Transient connectivity failures (WebSocket disconnected, network timeouts) raise their own codes (``WEBSOCKET_DISCONNECTED``, ``CONNECTION_FAILED``) so retry logic can branch separately. EXAMPLES: - Remove SIMPLE button: ha_remove_helpers_integrations( target="my_button", helper_type="input_button", confirm=True ) - Remove FLOW utility_meter (any sub-entity works): ha_remove_helpers_integrations( target="sensor.energy_peak", helper_type="utility_meter", confirm=True, ) - Remove any integration by entry_id: ha_remove_helpers_integrations( target="01HXYZ...", confirm=True ) - Remove a config subentry: ha_remove_helpers_integrations( target="01HXYZ...", helper_type="config_subentry", subentry_id="subentry-123", confirm=True ) **WARNING:** Removing a helper or integration that is referenced by automations, scripts, or other integrations may cause those to fail. Use ha_search() / ha_get_integration() to verify before removal. Cannot be undone.
Parameters
target required - (Annotated[str, Field(description="What to remove. One of: (a) bare helper_id for SIMPLE helpers (requires helper_type), e.g. 'my_button'; (b) full entity_id (requires helper_type), e.g. 'input_button.my_button' or 'sensor.my_meter'; (c) config entry_id for any integration (helper_type=None), e.g. value from ha_get_integration(); (d) parent config entry_id for config_subentry (requires helper_type='config_subentry' and subentry_id).")]) helper_type - (Annotated[HelperTypeLiteral | None, Field(description="Helper type. Required when target is a helper_id (bare) or entity_id. Set to None when target is a config entry_id to remove any integration. Use 'config_subentry' to remove a config subentry under target.", default=None)])
= null subentry_id - (Annotated[str | None, Field(description="Config subentry ID to remove when helper_type='config_subentry'.", default=None)])
= null confirm - (Annotated[bool, Field(description='Must be True to confirm removal.', default=False)])
= false wait - (Annotated[bool, Field(description="Wait for entity removal. Default: True. Ignored when helper_type=None or helper_type='config_subentry' (no entity poll, require_restart returned).", default=True)])
= true History & Statistics
Description
Retrieve execution traces for automations and scripts to debug issues. Traces show what happened during automation/script runs: - What triggered the automation - Which conditions passed or failed - What actions were executed - Any errors that occurred - Variable values during execution USAGE MODES: 1. List recent traces (omit run_id): ha_get_automation_traces("automation.motion_light") Returns a summary of recent execution runs with timestamps, triggers, and status. Use `offset` to page deeper when `has_more` is true, or `order="oldest"` to start from the earliest stored trace instead of the most recent. 2. Get detailed trace (provide run_id): ha_get_automation_traces("automation.motion_light", run_id="1705312800.123456") Returns full execution details including trigger info, condition results, action trace with timing, and context variables. 3. Get detailed trace with logbook (provide run_id and detailed=True): ha_get_automation_traces("automation.motion_light", run_id="1705312800.123456", detailed=True) Returns the formatted trace plus logbook entries and context metadata. Useful when the standard trace summary doesn't reveal enough for debugging. Note: script-style action paths (sequence/, numeric) are always matched regardless of this flag. 4. Get full variables without deduplication (provide run_id and deduplicate=False): ha_get_automation_traces("automation.motion_light", run_id="1705312800.123456", deduplicate=False) Returns the formatted trace with full variables at every action step. DEBUGGING EXAMPLES: Automation not triggering: - Check if traces exist (automation may not be triggered) - Look at trigger info to see what event was received Automation runs but conditions fail: - Get detailed trace to see condition_results - Each condition shows whether it passed (true) or failed (false) Unexpected behavior in actions: - Get detailed trace to see action_trace - Shows each action step with result and any errors - For 'choose' actions, shows which branch was taken Template debugging: - Detailed trace shows evaluated template values in context - Trigger variables available under trigger_variables NOTES: - Traces are stored for a limited time by Home Assistant - Works for both automations and scripts (use full entity_id) - The 'state' field shows: 'stopped' (completed), 'running', or error state
Parameters
automation_id required - (Annotated[str, Field(description="Automation or script entity_id (e.g., 'automation.motion_light' or 'script.morning_routine')")]) run_id - (Annotated[str | None, Field(description='Specific trace run_id to retrieve detailed trace. Omit to list recent traces.', default=None)])
= null limit - (Annotated[int, Field(description='Maximum number of traces to return when listing (default: 10, max: 50).', default=10, ge=1, le=50)])
= 10 deduplicate - (Annotated[bool, Field(description='Deduplicate variables across action steps (default: True). Set to False to include full variables at every step.', default=True)])
= true detailed - (Annotated[bool, Field(description='Include extra diagnostic data: logbook entries and context metadata (default: False). Use when standard trace lacks detail for debugging.', default=False)])
= false sections - (Annotated[str | None, Field(description="Comma-separated list of trace sections to return. Valid values: trigger, conditions, actions, config, error, logbook, context. Omit to return all sections. Example: 'actions' or 'trigger,conditions'.", default=None)])
= null offset - (Annotated[int, Field(description='Number of traces to skip from the start of the requested order. Use with `limit` to page through stored traces when `total_available > limit`.', default=0, ge=0)])
= 0 order - (Annotated[Literal['newest', 'oldest'], Field(description="Order traces are returned in. 'newest' (default) returns most-recent first; 'oldest' returns chronological-first.", default='newest')])
= "newest" Description
Retrieve historical data from Home Assistant's recorder. **Sources:** - "history" (default): Raw state changes, ~10 day retention, full resolution - "statistics": Pre-aggregated data, permanent retention, requires state_class **Shared params:** entity_ids, start_time, end_time, limit, offset **History params:** minimal_response, significant_changes_only **Statistics params:** period, statistic_types **Default time range:** 24h for history, 30 days for statistics **Use ha_get_history (default) when:** - Troubleshooting why a value changed ("Why was my bedroom cold last night?") - Checking event sequences ("Did my garage door open while I was away?") - Analyzing recent patterns ("What time does motion usually trigger?") **Use ha_get_history(source="statistics") when:** - Tracking long-term trends beyond 10 days ("Energy use this month vs last month?") - Computing period averages ("Average living room temperature over 6 months?") - Entities must have state_class (measurement, total, total_increasing) **WARNING:** limit and offset apply per entity (not globally across all entities). All data is fetched from HA before slicing; limit/offset are client-side. With multiple entity_ids, offset must be 0 — use a single entity_id for offset > 0. Use has_more and next_offset from the response to paginate. **Example -- history (default):** ```python ha_get_history(entity_ids="sensor.bedroom_temperature", start_time="24h") ha_get_history(entity_ids=["sensor.temperature", "sensor.humidity"], start_time="7d", limit=500) # Default order="desc" returns newest states first. # To paginate oldest-first, use order="asc": ha_get_history(entity_ids="sensor.temperature", start_time="7d", limit=100, offset=100, order="asc") ``` **Example -- statistics:** ```python ha_get_history(source="statistics", entity_ids="sensor.total_energy_kwh", start_time="30d", period="day") ha_get_history(source="statistics", entity_ids="sensor.living_room_temperature", start_time="6m", period="month", statistic_types=["mean", "min", "max"]) ha_get_history(source="statistics", entity_ids="sensor.energy_kwh", start_time="30d", period="5minute", limit=100, offset=200) ```
Parameters
entity_ids required - (Annotated[str | list[str], JSON_STRING_COERCION, Field(description='Entity ID(s) to query. Can be a single ID, comma-separated string, or JSON array.')]) source - (Annotated[Literal['history', 'statistics'], Field(description='Data source: "history" (default) for raw state changes (~10 day retention), or "statistics" for pre-aggregated long-term data (permanent, requires state_class).', default='history')])
= "history" start_time - (Annotated[str | None, Field(description="Start time: ISO datetime or relative (e.g., '24h', '7d', '30d'). Default: 24h ago for history, 30d ago for statistics", default=None)])
= null end_time - (Annotated[str | None, Field(description='End time: ISO datetime. Default: now', default=None)])
= null minimal_response - (Annotated[bool, Field(description='Return only states/timestamps without attributes. Default: true. Ignored when source="statistics"', default=True)])
= true significant_changes_only - (Annotated[bool, Field(description='Filter to significant state changes only. Default: true. Ignored when source="statistics"', default=True)])
= true limit - (Annotated[int | None, Field(description='Max entries per entity. Default: 100, Max: 1000. For source="history": state changes. For source="statistics": aggregated rows. With multiple entity_ids, offset must be 0 and total rows returned can reach limit × len(entity_ids).', default=None, ge=1, le=1000)])
= null offset - (Annotated[int | None, Field(description='Number of entries to skip per entity for pagination. Default: 0. Offset > 0 requires a single entity_id. Use with limit and has_more/next_offset in the response.', default=None, ge=0)])
= null period - (Annotated[str, Field(description='Aggregation period: "5minute", "hour", "day", "week", "month", "year". Default: "day". Ignored when source="history"', default='day')])
= "day" statistic_types - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(description='Statistics types: "mean", "min", "max", "sum", "state", "change". Default: all. Ignored when source="history"', default=None)])
= null order - (Annotated[Literal['asc', 'desc'], Field(default='desc', description='Sort order for history entries. "desc" (default): newest first. "asc": oldest first (chronological, as returned by HA API). Ignored when source="statistics".')])
= "desc" fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified top-level response keys to reduce response size. None = full response (default). History keys: success, source, entities, period, query_params. Statistics keys: success, source, entities, period_type, time_range, statistic_types, query_params, warnings.')])
= null Description
Get Home Assistant logs from various sources. **Sources:** - "logbook" (default): Entity state change history with pagination - "system": Structured system log entries (errors, warnings) via system_log/list - "error_log": Raw home-assistant.log text - "supervisor": Add-on container logs (requires slug = add-on slug) - "system_service": HA-Supervisor-managed system service logs (requires slug ∈ {supervisor, host, core, dns, audio, cli, multicast, observer}) - "logger": Effective log level per integration via logger/log_info (confirms logger.set_level changes took effect) **Shared params:** limit, search (keyword filter on entries/lines; matches integration domain for source='logger') **Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service); ignored for source='logger'. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window. **Logbook params:** hours_back, entity_id, end_time, offset, compact (default True — strips attribute dicts to save context) **System/error_log params:** level (ERROR, WARNING, INFO, DEBUG) **Supervisor params:** slug = add-on slug, e.g. "core_mosquitto" (use ha_get_addon() to list installed slugs) **System-service params:** slug = service name. The slug "supervisor" here means the Supervisor service's own logs, NOT an add-on with that name — the source param disambiguates.
Parameters
source - (Literal['logbook', 'system', 'error_log', 'supervisor', 'system_service', 'logger'])
= "logbook" limit - (int | None)
= null search - (str | None)
= null order - (Annotated[Literal['newest', 'oldest'], Field(description="Sort order for time-ordered sources (logbook, system, error_log, supervisor, system_service): 'newest' (default) returns most-recent first; 'oldest' returns chronological-first. Ignored for source='logger'.")])
= "newest" hours_back - (Annotated[int, Field(ge=1)])
= 1 entity_id - (str | None)
= null end_time - (str | None)
= null offset - (Annotated[int, Field(ge=0)])
= 0 compact - (bool)
= true level - (str | None)
= null slug - (str | None)
= null Integrations
Description
Get integration (config entry) information with pagination. Without an entry_id: Lists all configured integrations with optional filters. With an entry_id: Returns detailed information including full options/configuration. EXAMPLES: - List all integrations: ha_get_integration() - Paginate: ha_get_integration(offset=50) - Search: ha_get_integration(query="zigbee") - Get specific entry: ha_get_integration(entry_id="abc123") - Get entry with editable fields: ha_get_integration(entry_id="abc123", include_schema=True) - Get entry with diagnostics dump: ha_get_integration(entry_id="abc123", include_diagnostics=True) - Get device-scoped diagnostics: ha_get_integration(entry_id="abc123", include_diagnostics=True, device_id="dev123") - Get the parsed KNX ETS project (group-address table): ha_get_integration(entry_id="<knx entry>", include_knx_project=True) - Walk a sub-tree: ha_get_integration(entry_id="abc123", include_diagnostics=True, diagnostics_data_path="<dotted-path>") - Paginate a large list: ha_get_integration(entry_id="abc123", include_diagnostics=True, diagnostics_data_path="<list-valued path>", diagnostics_data_limit=10, diagnostics_data_offset=20) - List config subentries: ha_get_integration(entry_id="abc123", include_subentries=True) - Inspect subentry create schema: ha_get_integration(entry_id="abc123", include_subentry_schema=True, subentry_type="conversation") - Inspect subentry reconfigure schema: ha_get_integration(entry_id="abc123", include_subentry_schema=True, subentry_type="conversation", subentry_id="sub123") - List template entries: ha_get_integration(domain="template") STATES: 'loaded', 'setup_error', 'setup_retry', 'not_loaded', 'failed_unload', 'migration_error'. Each entry carries: - ``log_level``: the canonical Python logger level name (``DEBUG``/``INFO``/``WARNING``/``ERROR``/``CRITICAL``) when the integration has a ``logger.set_level`` override, or ``"DEFAULT"`` (uppercase sentinel) when no override is set. - ``log_level_raw``: the original numeric level (e.g. ``10`` for DEBUG) when HA returned an int, ``None`` otherwise (no override set, or HA provided a level name as a string). This is distinct from the add-on side, where ``ha_get_addon`` returns Supervisor's lowercase ``"default"`` literal — do not cross-compare.
Parameters
entry_id - (Annotated[str | None, Field(description='Config entry ID to get details for. If omitted, lists all integrations.', default=None)])
= null query - (Annotated[str | None, Field(description='When listing, search by domain or title. Uses exact substring matching by default; set exact_match=False for fuzzy.', default=None)])
= null domain - (Annotated[str | None, Field(description="Filter by integration domain (e.g. 'template', 'group'). When set, includes the full options/configuration for each entry.", default=None)])
= null include_options - (Annotated[bool, Field(description='Include the options object for each entry. Automatically enabled when domain filter is set. For UI-created flow-based helpers (template, group, utility_meter, derivative, ...), the current config — template body, group members, source entity, etc. — is surfaced here by probing the options flow. Prefer this over include_schema when you only need to read the current values; use include_schema when you also need the field types or selector metadata.', default=False)])
= false include_schema - (Annotated[bool, Field(description='When entry_id is set, also return the options flow schema (available fields and their types). Use before ha_config_set_helper to understand what can be updated. Only applies when supports_options=true.', default=False)])
= false include_subentries - (Annotated[bool, Field(description='When entry_id is set, include config subentries for the integration entry. Useful for integrations that expose conversation agents, devices, or other extension points as subentries.', default=False)])
= false include_subentry_schema - (Annotated[bool, Field(description='When entry_id is set, return introspection-only config subentry schema information; no subentry is created. Pair with subentry_type, and optionally subentry_id for reconfigure schema.', default=False)])
= false subentry_type - (Annotated[str | None, Field(description='Integration-defined subentry type used with include_subentry_schema=True.', default=None)])
= null subentry_id - (Annotated[str | None, Field(description='Existing subentry ID used with include_subentry_schema=True to inspect a reconfigure flow.', default=None)])
= null show_advanced_options - (Annotated[bool, Field(description='When include_subentry_schema=True, ask older Home Assistant versions to expose advanced flow options. No-op on HA 2026.6+; pending removal before HA 2027.6.', default=False)])
= false exact_match - (Annotated[bool, Field(description='Use exact substring matching for query filter (default: True). Set to False for fuzzy matching when the query may contain typos.', default=True)])
= true limit - (Annotated[int, Field(default=50, ge=1, le=200, description='Max entries to return per page in list mode (default: 50)')])
= 50 offset - (Annotated[int, Field(default=0, ge=0, description='Number of entries to skip for pagination (default: 0)')])
= 0 include_diagnostics - (Annotated[bool, Field(description="When entry_id is set, also fetch the integration's diagnostics dump — integration-defined JSON (commonly includes redacted config, device list, state snapshots; exact top-level keys vary by integration). The canonical artifact users grab via Settings → Devices & Services → [integration] → ⋯ → Download diagnostics. Use when triaging integration bugs or filing ha_report_issue for a specific integration. Payloads can be large (Hue ~290 KB, ZHA/MQTT/ESPHome several MB) — pair with diagnostics_fields or diagnostics_truncate_at_bytes to fit the LLM context budget.", default=False)])
= false include_knx_project - (Annotated[bool, Field(description='When entry_id is a KNX config entry, also return the parsed ETS project: the full group-address table (address, name, DPT, description) under knx_project.group_addresses, plus the group-range hierarchy and project metadata. This is the parsed-project GA table that is NOT in the diagnostics dump; per-entity GA assignments are already covered by include_diagnostics (config_store / configuration_yaml). Ignored (with a warning) when the entry is not a KNX integration. The KNX integration exposes a single project, so the result is the same regardless of which KNX entry_id is used.', default=False)])
= false device_id - (Annotated[str | None, Field(description='Optional. When set with include_diagnostics=True, returns the device-scoped diagnostics dump for that specific device under the integration (rather than the full integration dump). Some integrations only expose config-entry-level dumps; others expose both.', default=None)])
= null diagnostics_fields - (Annotated[list[str] | str | None, JSON_STRING_COERCION, Field(description="Optional list of top-level keys to keep from the diagnostics data payload (e.g. ['home_assistant', 'issues']). Trims the payload before it hits the LLM context budget. Accepts a JSON list or comma-separated string. Only applies when include_diagnostics=True and the data payload is a dict. Unknown keys are silently dropped and surfaced via the omitted_fields sub-key.", default=None)])
= null diagnostics_truncate_at_bytes - (Annotated[int | None, Field(description='Optional byte cap on the serialized diagnostics payload (after diagnostics_fields and diagnostics_data_path have been applied). On hit, drops data and emits truncated=true, bytes_total, byte_cap, plus available_fields (when the capped value is a dict). Recommended starting point: 20000 bytes. Only applies when include_diagnostics=True.', default=None, ge=1)])
= null diagnostics_data_path - (Annotated[str | None, Field(description="Optional dotted path into the diagnostics data sub-tree (e.g. '<list-valued path>' for per-device records, 'home_assistant.version' for HA core version; the exact key path varies by integration version). Walks into the post-fields payload. Resolution failures replace data with null and surface data_path_error. Use this when the interesting payload lives several levels deep — top-level diagnostics_fields can't address sub-trees on integrations where the bulk lives under one key (ZHA, MQTT, ESPHome). Only applies when include_diagnostics=True.", default=None)])
= null diagnostics_data_offset - (Annotated[int | None, Field(description='Pagination start index (default 0) for list-valued diagnostics_data_path results. Ignored when diagnostics_data_path is unset, diagnostics_data_limit is unset, or the resolved value is not a list. Only applies when include_diagnostics=True.', default=0, ge=0)])
= 0 diagnostics_data_limit - (Annotated[int | None, Field(description='Pagination window size for list-valued diagnostics_data_path results. When set with a list-resolved path, swaps data for a pagination envelope {path, items, offset, limit, total, has_more}. Default None returns the full resolved value. Workflow: probe with a list-valued diagnostics_data_path and diagnostics_data_limit=10 to walk a large list one page at a time (the exact path varies by integration version). Only applies when include_diagnostics=True.', default=None, ge=1)])
= null Description
Get Home Assistant system health, including Zigbee (ZHA), Z-Wave JS, and per-integration diagnostics dumps. Returns health check results from integrations, system resources, and connectivity. Available information varies by installation type and loaded integrations. The result also carries an ``ha_mcp_update`` object — ``{current, latest, update_available}`` — reporting whether a newer ha-mcp release is available (from PyPI for pip/Docker, or the Supervisor add-on store for the add-on), so you can proactively tell the user to upgrade. Present on every install type including the HA add-on (so a user who missed the Supervisor's update prompt still hears about it); omitted only for the ``unknown`` version and when ``HA_MCP_DISABLE_UPDATE_CHECK`` is set. **Parameters:** - include: Optional comma-separated list of additional data to include. - "repairs": Repair items from Settings > System > Repairs (active only by default; pass `include_dismissed_repairs=True` for all) - "zha_network": ZHA Zigbee devices with radio signal summary (name, LQI, RSSI) - "zha_network_full": ZHA Zigbee devices with all device details (can be large on 100+ device networks; prefer "zha_network" for summary) - "zwave_network": Z-Wave JS network status and node summary (status, security, routing) - "themes": Installed theme names and defaults (sorted list of theme names, count, default_theme, default_dark_theme) - "diagnostics": Per-integration diagnostics dump — integration-defined JSON (commonly includes redacted config, device list, state snapshots; exact top-level keys vary by integration). REQUIRES ``config_entry_id``. The canonical artifact users grab via Settings → Devices & Services → [integration] → ⋯ → Download diagnostics. Use this when triaging integration bugs or filing ``ha_report_issue`` for a specific integration. Payloads can be large (Hue ~290 KB, ZHA/MQTT/ESPHome several MB) — pair with ``diagnostics_fields`` or ``diagnostics_truncate_at_bytes`` to fit the LLM context budget. - "config_check": Validate HA configuration via POST /config/core/check_config (the pre-restart safety check; ha_restart runs it automatically). Returns {result: valid|invalid, is_valid, errors}; read-only/idempotent, takes no args. - "dead_entities": Surface orphaned/stale entity-registry entries by diffing the registry against the state machine and the live config-entries set. Returns confidence-tiered buckets — ``config_entry_orphans`` (owning integration instance gone; definitively dead) and ``stale_restored`` (HA restored the entity from the registry on startup but the loaded integration no longer provides it). Each item carries entity_id + platform so a client can propose cleanup with ha_remove_entity. Deliberately excludes ``unknown``-state entities and merely-offline devices to keep false positives low. Read-only; takes no args. - Example: include="repairs,zha_network,zwave_network,config_check" - Example: include="diagnostics", config_entry_id="abc123..." - include_dismissed_repairs: Include user-dismissed/ignored repairs (default: False). Only meaningful when "repairs" is in `include`. - config_entry_id: Required when ``include`` contains ``diagnostics``. The config entry ID of the integration (find via ``ha_get_integration``). - device_id: Optional. When set with ``include=diagnostics``, returns the device-scoped diagnostics dump for that specific device under the integration (rather than the full integration dump). Some integrations only expose config-entry-level dumps; others expose both. - diagnostics_fields: Optional list of top-level keys to keep from the diagnostics ``data`` payload (e.g. ``["home_assistant", "issues"]``). Accepts a JSON list or comma-separated string. Only applies with ``include=diagnostics``. - diagnostics_truncate_at_bytes: Optional byte cap on the serialized diagnostics payload (post-projection / post-data_path). On hit, drops ``data`` and emits ``truncated=true``, ``bytes_total``, ``byte_cap``, plus ``available_fields`` (when the capped value is a dict). Only applies when ``include`` contains ``diagnostics``. Recommended starting point: 20000 bytes. - diagnostics_data_path: Optional dotted path into the diagnostics ``data`` sub-tree (e.g. ``"data.devices"`` for ZHA per-device records). Walks into the post-fields payload. Resolution failures replace ``data`` with ``null`` and surface ``data_path_error``. Only applies when ``include`` contains ``diagnostics``. - diagnostics_data_offset / diagnostics_data_limit: Pagination on list-valued ``diagnostics_data_path`` results. When ``data_limit`` is set and the resolved path is a list, ``data`` becomes ``{"path", "items", "offset", "limit", "total", "has_more"}``. Only applies when ``include`` contains ``diagnostics``. Example workflow (walk a list-valued sub-tree one page at a time; the exact ``data_path`` varies by integration version): ``ha_get_system_health(include="diagnostics", config_entry_id="abc", diagnostics_data_path="<list-valued path>", diagnostics_data_limit=10)`` → inspect the page envelope's ``total`` / ``has_more`` → repeat with ``diagnostics_data_offset=10`` for the next slice.
Parameters
include - (str | None)
= null include_dismissed_repairs - (bool | None)
= false config_entry_id - (str | None)
= null device_id - (str | None)
= null diagnostics_fields - (Annotated[list[str] | str | None, JSON_STRING_COERCION])
= null diagnostics_truncate_at_bytes - (Annotated[int, Field(ge=1)] | None)
= null diagnostics_data_path - (str | None)
= null diagnostics_data_offset - (Annotated[int, Field(ge=0)] | None)
= 0 diagnostics_data_limit - (Annotated[int, Field(ge=1)] | None)
= null Description
Enable/disable integration (config entry). Use ha_get_integration() to find entry IDs.
Parameters
entry_id required - (Annotated[str, Field(description='Config entry ID')]) enabled required - (Annotated[bool, Field(description='True to enable, False to disable')]) Labels & Categories
Description
Get category info - list all categories for a scope or get a specific one by ID. Without a category_id: Lists all Home Assistant categories for the given scope. With a category_id: Returns configuration for that specific category. Categories are domain-scoped organizational groups for automations, scripts, scenes, and helpers. CATEGORY PROPERTIES: - ID (category_id), Name - Icon (optional) EXAMPLES: - List automation categories: ha_config_get_category("automation") - List script categories: ha_config_get_category("script") - List helper categories: ha_config_get_category("helpers") - Get specific category: ha_config_get_category("automation", category_id="my_category_id") Use ha_config_set_category() to create or update categories. Use ha_set_entity(categories={"automation": "category_id"}) to assign categories to entities.
Parameters
scope required - (Annotated[str, Field(description="Domain scope for categories (e.g., 'automation', 'script', 'scene', 'helpers').")]) category_id - (Annotated[str | None, Field(description='ID of the category to retrieve. If omitted, lists all categories for the scope.', default=None)])
= null Description
Get label info - list all labels or get a specific one by ID. Without a label_id: Lists all Home Assistant labels with their configurations. With a label_id: Returns configuration for that specific label. LABEL PROPERTIES: - ID (label_id), Name - Color (optional), Icon (optional), Description (optional) EXAMPLES: - List all labels: ha_config_get_label() - Get specific label: ha_config_get_label("my_label_id") Use ha_config_set_label() to create or update labels. Use ha_set_entity(labels=["label1", "label2"]) to assign labels to entities.
Parameters
label_id - (Annotated[str | None, Field(description='ID of the label to retrieve. If omitted, lists all labels.', default=None)])
= null Description
Delete a Home Assistant category. Removes the category from the category registry for the given scope (e.g., 'automation', 'script', 'scene', 'helpers'). This will also remove the category assignment from all entities in that scope. EXAMPLES: - Delete category: ha_config_remove_category("automation", "my_category_id") Use ha_config_get_category() to find category IDs. **WARNING:** Deleting a category will remove it from all assigned entities. This action cannot be undone.
Parameters
scope required - (Annotated[str, Field(description="Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').")]) category_id required - (Annotated[str, Field(description='ID of the category to delete')]) Description
Delete a Home Assistant label. Removes the label from the label registry. This will also remove the label from all entities, devices, and areas that have it assigned. EXAMPLES: - Delete label: ha_config_remove_label("my_label_id") Use ha_config_get_label() to find label IDs. **WARNING:** Deleting a label will remove it from all assigned entities. This action cannot be undone.
Parameters
label_id required - (Annotated[str, Field(description='ID of the label to delete')]) Description
Create or update a Home Assistant category. Creates a new category if category_id is not provided, or updates an existing category if category_id is provided. Categories are domain-scoped organizational groups for automations, scripts, scenes, and helpers. Unlike labels (which are cross-domain), categories are specific to a single domain scope. EXAMPLES: - Create automation category: ha_config_set_category("Lighting", scope="automation") - Create with icon: ha_config_set_category("Security", scope="automation", icon="mdi:shield") - Update category: ha_config_set_category("Updated Name", scope="automation", category_id="my_category_id") After creating a category, use ha_set_entity(categories={"automation": "category_id"}) to assign it.
Parameters
name required - (Annotated[str, Field(description='Display name for the category')]) scope required - (Annotated[str, Field(description="Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').")]) category_id - (Annotated[str | None, Field(description='Category ID for updates. If not provided, creates a new category.', default=None)])
= null icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:tag', 'mdi:label')", default=None)])
= null Description
Create or update a Home Assistant label. Creates a new label if label_id is not provided, or updates an existing label if label_id is provided. Labels are a flexible tagging system that can be applied to entities, devices, and areas for organization and automation purposes. EXAMPLES: - Create simple label: ha_config_set_label("Critical") - Create colored label: ha_config_set_label("Outdoor", color="green") - Create label with icon: ha_config_set_label("Battery Powered", icon="mdi:battery") - Create full label: ha_config_set_label("Security", color="red", icon="mdi:shield", description="Security-related devices") - Update label: ha_config_set_label("Updated Name", label_id="my_label_id", color="blue") After creating a label, use ha_set_entity(labels=["label_id"]) to assign it to entities.
Parameters
name required - (Annotated[str, Field(description='Display name for the label')]) label_id - (Annotated[str | None, Field(description='Label ID for updates. If not provided, creates a new label.', default=None)])
= null color - (Annotated[str | None, Field(description="Color for the label (e.g., 'red', 'blue', 'green', or hex like '#FF5733')", default=None)])
= null icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:tag', 'mdi:label')", default=None)])
= null description - (Annotated[str | None, Field(description="Description of the label's purpose", default=None)])
= null Scenes
Description
Retrieve Home Assistant scene configuration. Returns the complete configuration for a scene, including the ``entities`` dict and other settings (``name``, ``icon``, ``id``). EXAMPLES: - Get scene: ha_config_get_scene("movie_night") - Get scene: ha_config_get_scene("bedroom_dim") RELATED TOOLS: - ha_config_set_scene — pass the returned ``config_hash`` for ``python_transform`` updates. For detailed scene configuration help, use ha_get_skill_guide.
Parameters
scene_id required - (Annotated[str, Field(description="Scene identifier (e.g., 'movie_night')")]) Description
Delete a Home Assistant scene. EXAMPLES: - Delete scene: ha_config_remove_scene("old_scene") - Delete scene: ha_config_remove_scene("temporary_scene") **IMPORTANT LIMITATION:** This tool can only delete scenes created via the Home Assistant UI. Scenes defined in YAML configuration files (scenes.yaml or configuration.yaml) cannot be deleted through the API and will return a 405 Method Not Allowed error. To remove YAML-defined scenes, you must edit the configuration file directly. **WARNING:** Deleting a scene that is referenced by automations or scripts (via ``scene.turn_on``) may cause those to fail.
Parameters
scene_id required - (Annotated[str, Field(description="Scene identifier to delete (e.g., 'old_scene')")]) wait - (Annotated[bool, Field(description='Wait for scene to be fully removed before returning. Default: True.', default=True)])
= true Description
Create or update a Home Assistant scene. MUST call ha_get_skill_guide first. Supports two modes: full config replacement (``config``) or Python transformation of an existing scene (``python_transform``). See the field descriptions for ``python_transform`` examples and the ``config`` shape contract. WHEN TO USE: - ``python_transform``: surgical edits to an existing scene (add/remove/update a single entity entry). Requires ``config_hash`` from ha_config_get_scene() for optimistic locking. - ``config``: creating a new scene, or wholesale replacement. WHEN NOT TO USE: - To activate a scene at runtime, use ha_call_service(domain="scene", service="turn_on", target=...) — this tool only manages scene *configuration*, not the runtime turn-on/off side. - To list or look up existing scenes, use ha_search(domain_filter="scene"). SCENE SHAPE: ``entities`` is a dict keyed by entity_id (e.g., ``{'light.kitchen': {'state': 'on', 'brightness': 200}}``), NOT a list. Automations use a list of actions; scenes capture a snapshot of states as a dict. EXAMPLE: ha_config_set_scene(scene_id="movie_night", config={ "name": "Movie Night", "entities": { "light.living_room": {"state": "on", "brightness": 50}, }, "icon": "mdi:movie", }) The top-level ``SKILL.md`` for home-assistant-best-practices ships in this response under ``skill_content`` by default — generic best-practice index covering entity-naming and safe-refactoring patterns that intersect with scene authoring. For detailed scene configuration help beyond that, use ha_get_skill_guide.
Parameters
scene_id required - (Annotated[str, Field(description="Scene identifier (e.g., 'movie_night')")]) config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Scene configuration dictionary. Must include 'entities' (a dict keyed by entity_id, NOT a list). Optional fields: 'name' (defaults to scene_id), 'icon', 'id'. Mutually exclusive with python_transform.", default=None)])
= null python_transform - (Annotated[str | None, Field(description='Python expression to transform existing scene config. Mutually exclusive with config. Requires config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Add entity: python_transform="config[\'entities\'][\'light.bed\'] = {\'state\': \'on\'}" Update brightness: python_transform="config[\'entities\'][\'light.kitchen\'][\'brightness\'] = 50" Remove entity: python_transform="del config[\'entities\'][\'light.kitchen\']" \n\n' + get_security_documentation())])
= null config_hash - (Annotated[str | None, Field(description='Config hash from ha_config_get_scene for optimistic locking. REQUIRED for python_transform (validates scene unchanged). Optional for config updates (validates before full replacement if provided).')])
= null category - (Annotated[str | None, Field(description="Category ID to assign to this scene. Use ha_config_get_category(scope='scene') to list available categories, or ha_config_set_category() to create one.", default=None)])
= null wait - (Annotated[bool, Field(description='Wait for scene to be queryable before returning. Default: True. Set to False for bulk operations.', default=True)])
= true MandatoryBPS - (Annotated[bool, Field(default=True)])
= true Scripts
Description
Retrieve Home Assistant script configuration. Returns the complete configuration for a script, including sequence, mode, fields, and other settings. The returned `config_hash` is stable across consecutive reads of an unchanged config — `compute_config_hash` documents the underlying contract. The returned `script_id` is the canonical bare storage key resolved by the REST client (matching what `ha_config_set_script` / `ha_config_remove_script` expect), falling back to the input identifier on the rare path where the REST envelope omits it. A leading `script.` prefix on the input is stripped before lookup — behavioral parity with `ha_config_get_automation` (mechanism differs: automations resolve via state lookup; scripts strip the prefix). EXAMPLES: - Get script (bare form): ha_config_get_script("morning_routine") - Get script (entity_id form): ha_config_get_script("script.morning_routine") For detailed script configuration help, use ha_get_skill_guide.
Parameters
script_id required - (Annotated[str, Field(description="Script identifier — bare storage key ('morning_routine') or entity_id form ('script.morning_routine'); a leading 'script.' prefix is stripped before lookup.")]) Description
Delete a Home Assistant script. EXAMPLES: - Delete script: ha_config_remove_script("old_script") - Delete script: ha_config_remove_script("temporary_script") **IMPORTANT LIMITATION:** This tool can only delete scripts created via the Home Assistant UI. Scripts defined in YAML configuration files (scripts.yaml or configuration.yaml) cannot be deleted through the API and will return a 405 Method Not Allowed error. To remove YAML-defined scripts, you must edit the configuration file directly. **WARNING:** Deleting a script that is used by automations may cause those automations to fail.
Parameters
script_id required - (Annotated[str, Field(description="Script identifier to delete — bare storage key ('old_script') or entity_id form ('script.old_script'); a leading 'script.' prefix is stripped before lookup.")]) wait - (Annotated[bool, Field(description='Wait for script to be fully removed before returning. Default: True.', default=True)])
= true Description
Create or update a Home Assistant script. MUST call ha_get_skill_guide first. PREFER NATIVE ACTIONS OVER TEMPLATES (read this before writing any `{{ ... }}`): Native actions are validated at config load, fail loudly, and do not bypass HA's schema. Templates in logic positions fail silently and obscure intent. - `choose` / `if/then/else` instead of template-based service names - `wait_for_trigger` instead of `wait_template` - Native `for:` field on `state` conditions inside `choose`/`if`, and on `state`/`numeric_state` triggers in `wait_for_trigger`, instead of `{{ now() - X.last_changed > timedelta(...) }}` duration math. - `repeat` with `for_each` instead of template loops - Hardcode `target.entity_id` literals — never `{{ this.entity_id }}`. Templates are appropriate ONLY in `data.*` fields, notification message/title, `event_data`, and `variables`. The reactive best-practice checker on this tool will surface anything in a logic position that should be native; consult the `best_practice_warnings` field on the response and fix before re-submitting. The relevant skill section is auto-embedded under `skill_content` on warnings, and the full `automation-patterns.md` + `template-guidelines.md` references ship under `skill_content` proactively by default. For comprehensive guidance beyond that, call `ha_get_skill_guide`. Supports two modes: full config replacement OR Python transformation. WHEN TO USE WHICH MODE: - python_transform: RECOMMENDED for edits to existing scripts. Surgical updates. - config: Use for creating new scripts or full restructures. IMPORTANT: python_transform requires 'config_hash' from ha_config_get_script(). PYTHON TRANSFORM EXAMPLES: - Update step: python_transform="config['sequence'][0]['data']['message'] = 'Hello'" - Add step: python_transform="config['sequence'].append({'delay': {'seconds': 5}})" - Remove last step: python_transform="config['sequence'].pop()" Creates a new script or updates an existing one with the provided configuration. Supports both regular scripts (with sequence) and blueprint-based scripts. Required config fields (choose one): - sequence: List of actions to execute (for regular scripts) - use_blueprint: Blueprint configuration (for blueprint-based scripts) Optional config fields: - alias: Display name (defaults to script_id) - description: Script description - icon: Icon to display - mode: Execution mode ('single', 'restart', 'queued', 'parallel') - max: Maximum concurrent executions (for queued/parallel modes) - fields: Input parameters for the script SCRIPTS vs AUTOMATIONS: Scripts use 'sequence', NOT 'trigger' or 'action'. If you need trigger-based execution, use ha_config_set_automation instead. EXAMPLES: Create basic delay script: ha_config_set_script(script_id="wait_script", config={ "sequence": [{"delay": {"seconds": 5}}], "alias": "Wait 5 Seconds", "description": "Simple delay script" }) Create service call script: ha_config_set_script(script_id="blink_light", config={ "sequence": [ {"action": "light.turn_on", "target": {"entity_id": "light.living_room"}}, {"delay": {"seconds": 2}}, {"action": "light.turn_off", "target": {"entity_id": "light.living_room"}} ], "alias": "Light Blink", "mode": "single" }) Create script with parameters: ha_config_set_script(script_id="backup_script", config={ "alias": "Backup with Reference", "description": "Create backup with optional reference parameter", "fields": { "reference": { "name": "Reference", "description": "Optional reference for backup identification", "selector": {"text": None} } }, "sequence": [ { "action": "hassio.backup_partial", "data": { "compressed": False, "homeassistant": True, "homeassistant_exclude_database": True, "name": "Backup_{{ reference | default('auto') }}_{{ now().strftime('%Y%m%d_%H%M%S') }}" } } ] }) Update script: ha_config_set_script(script_id="morning_routine", config={ "sequence": [ {"action": "light.turn_on", "target": {"area_id": "bedroom"}}, {"action": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} ], "alias": "Updated Morning Routine" }) Create blueprint-based script: ha_config_set_script(script_id="notification_script", config={ "alias": "My Notification Script", "use_blueprint": { "path": "notification_script.yaml", "input": { "message": "Hello World", "title": "Test Notification" } } }) Update blueprint script inputs: ha_config_set_script(script_id="notification_script", config={ "alias": "My Notification Script", "use_blueprint": { "path": "notification_script.yaml", "input": { "message": "Updated message", "title": "Updated Title" } } }) Note: Scripts use Home Assistant's action syntax. Check the documentation for advanced features like conditions, variables, parallel execution, and service call options.
Parameters
script_id required - (Annotated[str, Field(description="Script identifier — bare storage key ('morning_routine') or entity_id form ('script.morning_routine'); a leading 'script.' prefix is stripped before lookup.")]) config - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION, Field(description="Script configuration dictionary. Must include EITHER 'sequence' (for regular scripts) OR 'use_blueprint' (for blueprint-based scripts). Optional fields: 'alias', 'description', 'icon', 'mode', 'max', 'fields'. Mutually exclusive with python_transform.", default=None)])
= null python_transform - (Annotated[str | None, Field(description='Python expression to transform existing script config. Mutually exclusive with config. Requires config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Simple: python_transform="config[\'sequence\'][0][\'data\'][\'message\'] = \'Hello\'" Pattern: python_transform="for step in config[\'sequence\']: if step.get(\'alias\') == \'My Step\': step[\'data\'][\'value\'] = 100" \n\n' + get_security_documentation())])
= null config_hash - (Annotated[str | None, Field(description='Config hash from ha_config_get_script for optimistic locking. REQUIRED for python_transform (validates script unchanged). Optional for config updates (validates before full replacement if provided).')])
= null category - (Annotated[str | None, Field(description="Category ID to assign to this script. Use ha_config_get_category(scope='script') to list available categories, or ha_config_set_category() to create one.", default=None)])
= null wait - (Annotated[bool, Field(description='Wait for script to be queryable before returning. Default: True. Set to False for bulk operations.', default=True)])
= true MandatoryBPS - (Annotated[bool, Field(default=True)])
= true Search & Discovery
Description
Get AI-friendly system overview with intelligent categorization. Returns comprehensive system information at the requested detail level, including Home Assistant base_url, version, location, timezone, entity overview, and active persistent notifications (if any). Use 'minimal' (default) for most queries. Domain counts and states_summary are always complete regardless of entity pagination. Standard/full modes paginate entities (default 200 per page) — use offset to fetch more. Use 'domains' filter to narrow scope. Use fields= to project the response to only the keys you need — a significantly smaller payload when fetching a single sub-section (e.g. fields=["system_info"] returns just that section instead of the full overview). When (and only when) the ha-mcp settings-UI sidecar is running (stdio mode, e.g. Claude Desktop / Claude Code), the response includes a ``settings_url`` field — the local URL to the tool-configuration page. Hand this URL to the user when they ask how to enable or disable tools or change server settings. ``settings_url`` is emitted regardless of ``fields=`` projection (so it stays discoverable even when callers minimize the response) but only when the sidecar URL file actually exists. In HTTP / Docker / OAuth modes there is no sidecar URL file and the server can't know its externally reachable host, so the response instead carries a ``settings_url_hint`` string telling the user where the page is mounted and to read the full URL from the startup logs. Hand whichever of the two fields is present to the user. The response also carries an ``ha_mcp_update`` object ``{current, latest, update_available}`` reporting whether a newer ha-mcp release is available (PyPI for pip/Docker, the Supervisor add-on store for the add-on) — proactively tell the user when ``update_available`` is true. Emitted regardless of ``fields=``; omitted only for the ``unknown`` version and when ``HA_MCP_DISABLE_UPDATE_CHECK`` is set.
Parameters
detail_level - (Annotated[Literal['minimal', 'standard', 'full'], Field(default='minimal', description="'minimal': 10 entities/domain, top-5 states (default); 'standard': 200 entities/page, top-10 states (use offset for more); 'full': 200 entities/page + entity_id + state + full states. Use 'domains', 'limit', or max_entities_per_domain to control size")])
= "minimal" domains - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description="Filter to specific domains (e.g. 'light,sensor' or ['light','sensor']). None = all domains. Useful to avoid context window overload.")])
= null limit - (Annotated[int | None, Field(default=None, ge=1, description='Max total entities across all domains (default: unlimited for minimal, 200 for standard/full). Counts and states always complete. Use with offset for pagination.')])
= null offset - (Annotated[int, Field(default=0, ge=0, description='Number of entities to skip for pagination (default: 0)')])
= 0 max_entities_per_domain - (Annotated[int | None, Field(default=None, description='Override default entity cap per domain (minimal=10, standard/full=unlimited). 0 = no limit on entities or states.')])
= null include_state - (Annotated[bool | None, Field(default=None, description='Include state field for entities (None = auto based on level). Full defaults to True.')])
= null include_entity_id - (Annotated[bool | None, Field(default=None, description='Include entity_id field for entities (None = auto based on level). Full defaults to True.')])
= null include_notifications - (Annotated[bool | None, Field(default=True, description='Include active persistent notifications (default: True). Set False to skip.')])
= true include_dismissed_repairs - (Annotated[bool | None, Field(default=False, description='Include user-dismissed/ignored repairs (default: False). Matches the HA Repairs UI which hides dismissed items by default.')])
= false fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified top-level response keys to reduce response size (e.g. ["system_info", "domains"]). None = full response (default). Available keys: success, system_summary, domain_stats, area_analysis, ai_insights, pagination, partial, warnings, device_types, service_availability, system_info, notification_count, notifications, repair_count, dismissed_repair_count, repairs, repairs_error, tool_discovery, settings_url, settings_url_hint, read_only_mode, read_only_mode_hint, ha_mcp_update. Note: ``settings_url`` (stdio mode), ``settings_url_hint`` (HTTP/Docker/OAuth mode), the ``read_only_mode`` / ``read_only_mode_hint`` pair (only while Read Only Mode is on), and ``ha_mcp_update`` (when an update check applies) are emitted regardless of ``fields=`` projection so the settings page, the active mode, and a newer ha-mcp release stay discoverable; see the tool description.')])
= null Description
Get current status, state, and attributes of one or more entities (lights, switches, sensors, climate, covers, locks, fans, etc.). SINGLE ENTITY: Pass a string entity_id. Returns the entity's full state and attributes. MULTIPLE ENTITIES: Pass a list of entity IDs (max 100). Efficiently retrieves states using parallel requests. Duplicates are automatically deduplicated. Returns success=True if at least one entity state was retrieved. Check 'error_count' for any failed lookups in partial-success scenarios. FIELDS PROJECTION: `fields=` projects the per-entity record keys (see the fields= parameter description for the full key list), NOT the outer bulk response wrapper. In single-entity mode it filters keys of the returned record directly. In bulk mode it filters keys of each record inside `states[entity_id]`; outer keys (`success`, `count`, `states`, `errors`, ...) are always preserved. `attribute_keys=` further narrows the `attributes` sub-dict and is only applied when `"attributes"` is in `fields=` (or `fields=None`); otherwise it is a no-op. When `attribute_keys=` is set but has no effect (because `attributes` was excluded by `fields=`), a `warnings` list is emitted outside the projected entity record(s): in bulk mode at the response wrapper level (sibling of `success`/`count`/`states`); in single-entity mode at the top-level result (sibling of `data`/`metadata`, since the projected record IS `data`). The warnings list is never a record key, so `fields=["state"]` returns a record with only `state` regardless of whether the no-effect warning fires. EXAMPLES: - Single: ha_get_state("light.kitchen") - Multiple: ha_get_state(["light.kitchen", "light.living_room", "sensor.temperature"]) - State only: ha_get_state("light.kitchen", fields=["state"]) - Slim bulk: ha_get_state(["light.kitchen", "sensor.temperature"], fields=["state", "attributes"], attribute_keys=["brightness"])
Parameters
entity_id required - (Annotated[str | list[str], JSON_STRING_COERCION, Field(description="Entity ID or list of entity IDs to retrieve state for (e.g., 'light.kitchen' or ['light.kitchen', 'sensor.temperature'])")]) fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified top-level entity record keys to reduce response size (e.g. ["state", "attributes"]). None = full entity record (default). Available keys: entity_id, state, attributes, last_changed, last_reported, last_updated, context.')])
= null attribute_keys - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified keys from each entity\'s attributes dict (e.g. ["brightness", "color_temp_kelvin"] for lights). None = full attributes (default). Unknown keys are silently dropped. Requires "attributes" to be present in fields= (or fields=None).')])
= null Description
Search for entities (lights, sensors, switches, climate, etc.) by name, domain, or area — AND inside automation/script/scene/helper/dashboard configurations — in one call. Searches two surfaces in parallel and returns tagged results: - **entities**: matches from the entity registry (entity_id, friendly name, area). Use `domain_filter` and/or `area_filter` to list/narrow. Omit `query` to enumerate entities by domain/area. - **automations / scripts / scenes / helpers / dashboards**: matches *inside* configuration definitions — triggers, actions, sequences, scene entity-sets, helper bodies, dashboard cards. Use `query` with config-body terms; filter with `search_types`. Eligibility: - Registry (entity) search runs whenever `query`, `domain_filter`, or `area_filter` is set, except when `search_types` is explicitly set (which pins to config-only). - Config-body search runs only when `query` is non-empty AND the caller's inputs do not signal entity-only intent — i.e. when none of `domain_filter`/`area_filter`/`state_filter` is set, OR when `search_types` is explicitly set as an override. The "filter set ⇒ skip body" rule keeps name-based single-entity lookups (e.g. `ha_search("bedroom motion", domain_filter= "binary_sensor")`) off the expensive config-body backend; pass `search_types=[...]` to opt back in (a warning surfaces in the response when the gate fires so the skip is visible). Use this whenever you need to find something in HA — without needing to decide between entity-name search vs config-body search up front. When NOT to use: - To fetch the state of a known entity_id: use `ha_get_state` (cheaper, no search overhead). - To inspect a specific automation/script/scene config by id: use the matching `ha_config_get_*` tool. - To list installed add-ons: use `ha_get_addon`. Caveats: - Both surfaces fan out in parallel; response carries `partial: True` plus an `errors[]` array tagged by surface ("entities" / "configs") when one branch raises. Empty `entities`/`automations`/... combined with `partial: True` means "search failed", not "no results". - `partial: True` is ALSO set (with `partial_reason`) when the config-body branch loses data on the per-type fetch paths — either the per-id wall-clock budget exhausts and skips unfetched configs, OR individual fetches raise exceptions (caught at debug-level so they would otherwise be silent), OR an `input_*` helper-type list fetch fails. Helpers run on every default call, so silent per-type-list failures would otherwise leave callers unable to tell a real zero-match from a partial backend outage. - When `partial: True` is set, the `partial_reason` text is also mirrored into `warnings[]` with an `"incomplete results: "` prefix. Agents that read `warnings` consistently (the entity-intent skip warning lands fine) but ignore `partial` still see the truncation diagnostic this way. - When the body branch is skipped by the entity-intent gate above, the response carries a `warnings[]` entry naming the skip reason; pass `search_types=[...]` to override. - The `fields=` parameter projects the response to only the named top-level keys; diagnostic / pagination keys (`success`, `warnings`, `errors`, `partial`, `partial_reason`, `*_total_matches`, `has_more`, `next_offset`, and per-surface counterparts) are always retained so projection cannot hide incomplete-results state. Distinct from `result_fields=` which projects each entity record's fields. - `count` is items in this response (post-pagination), not total matches across the corpus. Use `entity_total_matches` + `config_total_matches` for the totals. - `limit`/`offset` are applied per-surface independently. The flat `has_more` / `next_offset` keys describe the next caller-page (same offset/limit semantics as a single-surface tool — iterate with `offset = next_offset`). Per-surface `entity_has_more`/`entity_next_offset` and `config_has_more`/`config_next_offset` let callers see which surface still has results when only one of two does. Examples: - List sensors in an area: ha_search(domain_filter="sensor", area_filter="Living Room") - List all calendars: ha_search(domain_filter="calendar") - Find a light by name: ha_search("kitchen", domain_filter="light") - Which automations use an entity (no filter, body included): ha_search("light.bed_light") - Scenes touching a light: ha_search("light.kitchen", search_types=["scene"]) - Narrow the response to only the entity bucket: ha_search("kitchen", fields=["entities"])
Parameters
query - (Annotated[str | None, Field(default=None, description='What to search for (entity name fragment, free-text config term, entity_id). Searches BOTH the entity registry (entity_ids, friendly names, areas) AND configuration bodies (automation triggers/actions, script sequences, scene contents, helper bodies, dashboard cards) in one call. Use this for any find-something-in-HA question — entity OR config. Omit `query` to enumerate by `domain_filter` and/or `area_filter` alone (registry-listing mode); configuration-body search is skipped in that mode because there is no term to match against.')])
= null domain_filter - (Annotated[str | None, Field(default=None, description="Narrow entity-registry results to a single domain (e.g. 'light', 'sensor'). Does not affect configuration search.")])
= null area_filter - (Annotated[str | None, Field(default=None, description='Narrow entity-registry results to an area (id or name). Does not affect configuration search.')])
= null search_types - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description="Configuration types to include in body search: 'automation', 'script', 'scene', 'helper', 'dashboard'. Default = automation+script+scene+helper. Pass as list or JSON-array string.")])
= null limit - (Annotated[int, Field(default=10, ge=1, description='Maximum results per surface (entities, configs). Default: 10.')])
= 10 offset - (Annotated[int, Field(default=0, ge=0, description='Number of results to skip for pagination.')])
= 0 exact_match - (Annotated[bool, Field(default=True, description='Exact substring matching (default). Set False for fuzzy matching when the query may have typos.')])
= true include_hidden - (Annotated[bool, Field(default=True, description='Include hidden entities in registry results (with a score penalty so they sort below visible matches). Set False to exclude entirely.')])
= true include_config - (Annotated[bool, Field(default=False, description='Include full configuration bodies in body-search results. Default: False (summary only).')])
= false group_by_domain - (Annotated[bool, Field(default=False, description='Group entity-registry results by domain (entity-side only). Adds a `by_domain` map to the response.')])
= false per_domain_limit - (Annotated[int | None, Field(default=None, description='When `group_by_domain=True`, cap entity-registry results per domain to this number. Ignored otherwise.')])
= null state_filter - (Annotated[str | None, Field(default=None, description='Filter entity-registry results to a specific state (e.g. "on", "off", "unavailable"). Case-insensitive.')])
= null result_fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Project each entity-registry record to only the specified keys (e.g. ["entity_id", "state"]). None = full records.')])
= null fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Project the response to only the specified top-level keys (e.g. ["entities", "automations"]). Diagnostic / pagination keys are always retained regardless of projection, so narrowing the response shape cannot hide partial / error state. None = full response. Distinct from `result_fields` (which projects each entity record\'s fields). Available keys: success, query, entities, automations, scripts, scenes, helpers, dashboards, search_types, search_type, entity_total_matches, config_total_matches, count, offset, limit, has_more, next_offset, entity_has_more, entity_next_offset, config_has_more, config_next_offset, by_domain, state_filter_note, area_names, domain_filter, area_filter, message, warnings, errors, partial, partial_reason.')])
= null config_time_budget - (Annotated[float | None, Field(default=None, gt=0, le=300, description="Per-call override for the per-id config-fetch wall-clock budget (seconds). Replaces the per-type HAMCP_*_CONFIG_TIME_BUDGET defaults for the automation, script, AND scene branches when their bulk-fetch falls through to per-id Attempt-C. Use when a `partial: True` response names time-budget skipping. Stateless per-call: one caller raising the budget doesn't affect others. None = use the per-type env defaults.")])
= null Service & Device Control
Description
Control multiple devices with bulk operation support and WebSocket tracking.
Parameters
operations required - (Annotated[list[dict[str, Any]], JSON_STRING_COERCION]) parallel - (bool)
= true Description
Execute a custom event on the Home Assistant event bus. When NOT to use: for controlling entities (lights, switches, climate) — use ha_call_service instead. For triggering automations by name, use ha_call_service("automation", "trigger"). Use this to publish custom event types consumed by event-triggered automations, Node-RED flows, or custom integrations that subscribe to specific event types. Caveats: Events are fire-and-forget; this tool confirms the event was accepted by the bus but does not verify whether any automation or subscriber acted on it.
Parameters
event_type required - (str) data - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION])
= null Description
Execute Home Assistant services to control entities and trigger automations. This is the universal tool for controlling all Home Assistant entities. Services follow the pattern domain.service (e.g., light.turn_on, climate.set_temperature). **Basic Usage:** ```python # Turn on a light ha_call_service("light", "turn_on", entity_id="light.living_room") # Set temperature with parameters ha_call_service("climate", "set_temperature", entity_id="climate.thermostat", data={"temperature": 22}) # Trigger automation ha_call_service("automation", "trigger", entity_id="automation.morning_routine") # Universal controls work with any entity ha_call_service("homeassistant", "toggle", entity_id="switch.porch_light") ``` **Key behavior:** - **wait** (default True): wait for the entity state to change before returning. Only applies to state-changing services on a single entity. - **Result compaction (default ON)**: ``result`` is trimmed to the targeted entity's record (drops parent-group propagation) and stripped of ``context`` / ``last_*`` metadata and heavy attribute lists (``effect_list``, ``hue_scenes``). Escape hatches: ``verbose=True`` for the raw HA response, or ``result_fields`` / ``result_attribute_keys`` for explicit per-record projection (mirrors ``ha_get_state``). **For detailed service documentation, use ha_get_skill_guide.** Common patterns: Use ha_get_state() to check current values before making changes. Use ha_search() to find correct entity IDs.
Parameters
domain required - (str) service required - (str) entity_id - (str | None)
= null data - (Annotated[dict[str, Any] | None, JSON_STRING_COERCION])
= null return_response - (bool)
= false wait - (bool)
= true verbose - (Annotated[bool, Field(description="Return HA's raw service response unchanged (default: False). Use as an escape hatch when you need the full propagation chain or raw attribute payload (debug / inspection). WARNING: brings back token-bloat for nested-group targets — prefer result_fields / result_attribute_keys for targeted control.")])
= false result_fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description="Project each record in 'result' to only these top-level keys (e.g. ['entity_id', 'state']). Mirrors ha_get_state's fields=. Setting this DISABLES default compaction — no entity-id filter, no metadata strip — and applies the explicit projection instead.")])
= null result_attribute_keys - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description="Project each record's 'attributes' dict to only these keys (e.g. ['brightness', 'rgb_color']). Mirrors ha_get_state's attribute_keys=. Setting this DISABLES default compaction. Requires 'attributes' to be present in result_fields (or result_fields=None).")])
= null Description
Check status of one or more device operations with real-time WebSocket verification. Pass a single operation_id string to check one operation, or a list of IDs to check multiple operations at once (bulk status). The timeout_seconds parameter applies to single-operation checks only. Bulk checks poll each operation individually with a short internal timeout. Use this to track operations initiated by ha_bulk_control or ha_call_service. For current entity states, use ha_get_state instead.
Parameters
operation_id required - (Annotated[str | list[str], JSON_STRING_COERCION, Field(description='Single operation ID or list of operation IDs to check. Use a single string for one operation, or a list for bulk status checks.')]) timeout_seconds - (int)
= 10 Description
List available Home Assistant services with optional pagination and detail control. Discovers services/actions that can be called via ha_call_service. Use domain or query filters to narrow results. Defaults to summary mode (name + description only) to keep responses compact. Args: domain: Filter by domain (e.g., 'light', 'switch', 'climate'). query: Search in service names and descriptions. limit: Max services per page (default: 50). offset: Pagination offset (default: 0). detail_level: 'summary' (default) returns name/description only; 'full' includes parameter field schemas. Examples: # Browse first page of all services (compact) ha_list_services() # List all light services with full parameter details ha_list_services(domain="light", detail_level="full") # Search for temperature-related services ha_list_services(query="temperature") # Paginate through all services ha_list_services(offset=50)
Parameters
domain - (str | None)
= null query - (str | None)
= null limit - (Annotated[int, Field(default=50, ge=1, le=200, description='Max services to return per page (default: 50)')])
= 50 offset - (Annotated[int, Field(default=0, ge=0, description='Number of services to skip for pagination (default: 0)')])
= 0 detail_level - (Annotated[Literal['summary', 'full'], Field(default='summary', description="'summary': service name + description only (default). 'full': include parameter field schemas.")])
= "summary" service_fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Project each service record to only the specified keys. E.g. ["name", "description"] returns slim service records. None = full records (default). Unknown keys yield empty records. Available keys: name, description, domain, service, fields (full mode only), target (full mode only).')])
= null fields - (Annotated[str | list[str] | None, JSON_STRING_COERCION, Field(default=None, description='Return only the specified top-level response keys to reduce response size (e.g. ["services"]). None = full response (default). Available keys: success, domains, services, total_count, count, offset, limit, has_more, next_offset, detail_level, filters_applied.')])
= null System
Description
Update raw YAML configuration in configuration.yaml, packages/*.yaml, or themes/*.yaml (LAST RESORT). MUST call ha_get_skill_guide first. **WARNING:** Destructive, disabled by default. Dedicated tools exist for almost every use case and should be preferred: - Template sensors (state-based or trigger-based) -> ha_config_set_helper(helper_type='template') - Automations (storage-mode) -> ha_config_set_automation - Scripts (storage-mode) -> ha_config_set_script - Scenes (storage-mode) -> ha_config_set_scene - All 28 helper types (input_*, counter, timer, schedule, zone, person, tag, group, min_max, threshold, derivative, statistics, utility_meter, trend, filter, switch_as_x, etc.) -> ha_config_set_helper Intended for YAML-only integrations with no config-flow or API equivalent (command_line, rest, shell_command, notify platforms), for integrations with significant YAML-only configuration (knx entities in package files), for registering YAML-mode dashboards via ``lovelace.dashboards.<url_path>`` (no other ``lovelace.*`` keys), and for editing theme files in ``themes/*.yaml`` (keyed by theme name; ``frontend.reload_themes`` is triggered automatically so no restart is needed). Themes only load when configuration.yaml carries the ``frontend: themes:`` include (e.g. ``!include_dir_merge_named themes``); this tool cannot add that include (``frontend`` is not an allowed key). Also accepts ``automation``, ``script``, and ``scene`` keys when ``file`` is a ``packages/*.yaml`` — for git-managed YAML configs that track these alongside templates and other YAML items. Writes to ``configuration.yaml`` for those three keys remain rejected so storage-mode and YAML-mode collections don't collide; use the dedicated storage-mode tools instead. Check ``post_action`` in the response: most keys need a full HA restart. For ``themes/*.yaml`` this tool *performs* the reload itself (``frontend.reload_themes``), so ``post_action`` is ``reload_performed`` (or ``reload_available`` plus ``reload_error`` if that reload failed). For template, mqtt, group, automation, script, and scene it only *advertises* the reload service (``post_action: reload_available`` with a ``reload_service`` to call yourself); the edit is on disk but not yet live. Preserves YAML comments and HA tags (``!include``, ``!secret``) on round-trip; ``replace`` swaps the subtree as-is. ``template-guidelines.md`` ships in this response under ``skill_content`` by default — YAML packages frequently include template sensors / command_line entities / mqtt templates, exactly where template misuse causes the subtlest bugs. For deeper routing guidance beyond what ships here, use ha_get_skill_guide.
Parameters
yaml_path required - (Annotated[str, Field(description="Top-level YAML key to modify. Only a narrow allowlist of YAML-only or YAML-heavy integration keys is accepted (e.g., 'command_line', 'rest', 'shell_command', 'notify', 'knx'). For YAML-mode dashboards, use the dotted form 'lovelace.dashboards.<url_path>' where <url_path> is lowercase, hyphenated, and not a reserved HA route. For themes in themes/*.yaml, use the theme name (simple name without dots; content is the mapping of theme variables only, without the theme name). 'automation', 'script', and 'scene' are accepted only when file is under packages/*.yaml; in configuration.yaml use the dedicated storage-mode tools (ha_config_set_automation, ha_config_set_script, ha_config_set_scene). Not for template sensors or input_* helpers — those have dedicated tools.")]) action required - (Annotated[str, Field(description="Action to perform: 'add' (insert/merge content under key), 'replace' (overwrite key with new content), or 'remove' (delete the key entirely).")]) content - (Annotated[str | None, Field(default=None, description="YAML content for the value under yaml_path. Required for 'add' and 'replace' actions. Must be valid YAML.")])
= null file - (Annotated[str, Field(default='configuration.yaml', description="Relative path to the YAML config file. Defaults to 'configuration.yaml'. Also supports 'packages/*.yaml' and 'themes/*.yaml' (yaml_path is the theme name; frontend.reload_themes is triggered automatically).")])
= "configuration.yaml" MandatoryBPS - (Annotated[bool, Field(default=True)])
= true Description
Get update information -- list all updates or get details for a specific one. Without an entity_id: Lists all available updates across the system including Home Assistant Core, add-ons, device firmware, HACS, and OS updates. With an entity_id: Returns detailed information about a specific update including version info, category, and release notes (if available). With include_release_notes=True (Core updates only): Also fetches HA release blog posts for every monthly version between installed and latest. Returns structured breaking changes and installed integration domains for cross-referencing. EXAMPLES: - List all updates: ha_get_updates() - List including skipped: ha_get_updates(include_skipped=True) - Get specific update: ha_get_updates(entity_id="update.home_assistant_core_update") - Pre-update analysis: ha_get_updates(entity_id="update.home_assistant_core_update", include_release_notes=True) RETURNS (when listing): - updates_available: Count of available updates - updates: List of update entities with version info - categories: Updates grouped by category (core, addons, devices, hacs, os) - ha_mcp_update: This MCP server's own update status {current, latest, update_available} — so you can flag a newer ha-mcp release (from PyPI for pip/Docker, the Supervisor add-on store for the add-on). Present on all install types; omitted only for the unknown version and when HA_MCP_DISABLE_UPDATE_CHECK is set. RETURNS (when getting specific update): - Update details including installed/latest versions - Release notes (fetched from WebSocket API or GitHub) - Category and installation status RETURNS (with include_release_notes=True, Core only): - breaking_changes.entries[]: Each has integration, description, version - multi_version_release_notes[]: Full text per version {version, content, source_url} - installed_integrations: Your integration domains for cross-referencing
Parameters
entity_id - (Annotated[str | None, Field(description="Update entity ID to get details for (e.g., 'update.home_assistant_core_update'). If omitted, lists all available updates.", default=None)])
= null include_skipped - (Annotated[bool, Field(description='When listing all updates, include updates that have been skipped (default: False)', default=False)])
= false include_release_notes - (Annotated[bool, Field(description='When getting a Core update entity, fetch multi-version release notes and breaking changes for all versions between installed and latest (default: False). Adds breaking_changes, multi_version_release_notes, and installed_integrations to the response.', default=False)])
= false Description
Polymorphic backup tool. See the tool description for the routing matrix.
Parameters
scope required - (Annotated[Literal['snapshot', 'edits'], Field(description="'snapshot' for full HA tarballs; 'edits' for per-entity auto-backups.")]) action required - (Annotated[Literal['create', 'restore', 'list', 'view', 'diff', 'delete'], Field(description='Operation to perform. Valid (scope, action) combinations are listed in the tool description.')]) name - (Annotated[str | None, Field(default=None, description='(snapshot.create) Tarball name. Auto-generated if not provided.')])
= null backup_id - (Annotated[str | None, Field(default=None, description="(snapshot.restore) Tarball ID to restore (e.g. 'dd7550ed').")])
= null restore_database - (Annotated[bool, Field(default=False, description='(snapshot.restore) Include database in the restore. Default false (config-only).')])
= false domain - (Annotated[str | None, Field(default=None, description="(edits.list / edits.delete) Filter auto-backups by domain (e.g. 'automation', 'helper_timer').")])
= null entity_id - (Annotated[str | None, Field(default=None, description='(edits.list / edits.delete) Filter auto-backups by entity ID.')])
= null backup_name - (Annotated[str | None, Field(default=None, description="(edits.view / edits.restore / edits.delete) Auto-backup filename (format '<domain>.<entity_id>.<timestamp>.yaml'). Not a tarball ID.")])
= null older_than_days - (Annotated[int | None, Field(default=None, ge=0, description='(edits.delete) Bulk-delete auto-backups older than this many days.')])
= null limit - (Annotated[int, Field(default=200, ge=1, le=10000, description='(edits.list / snapshot.list) Maximum number of entries to return.')])
= 200 Description
Create and run a custom tool in a sandbox, or manage saved custom tools. ⚠️ **LAST RESORT** — search for existing tools first. **Modes** (mutually exclusive): - Provide ``code`` + ``justification`` to execute custom code - Set ``run_saved`` to re-run a previously saved tool by name - Set ``list_saved=True`` to list all saved tools **Available functions in sandbox:** - ``api_get(endpoint)`` — GET request to HA REST API - ``api_post(endpoint, data)`` — POST request to HA REST API - ``ws_send(message)`` — send a HA WebSocket command (e.g. registry lookups, ``render_template``, dashboard ops). ``message`` must include a ``"type"`` field; the MCP server adds ``id`` and handles auth. - ``call_tool(name, args)`` — call a registered MCP tool - ``delete_saved_tool(name)`` — remove a previously saved custom tool by name. Returns ``{"deleted": True, "name": name}`` or ``{"error": ...}``. Use ``api_get``/``api_post`` for REST operations not covered by existing tools. Use ``ws_send`` when the operation is only available over the Home Assistant WebSocket API (most registry CRUD, template rendering, and Lovelace operations). Use ``call_tool`` when an existing tool already does what you need. Use ``delete_saved_tool`` to clean up saved tools you no longer need. Saved tools persist across server restarts when ``CODE_MODE_SAVED_TOOLS_PATH`` is set (the addon sets this by default to ``/data/saved_tools.json``). Example — check repairs (no built-in tool for this): ```python repairs = await api_get("/repairs/issues") repairs ``` Example — list areas via WebSocket: ```python result = await ws_send({"type": "config/area_registry/list"}) result.get("result", []) ``` Example — chain existing tools: ```python result = await call_tool("ha_search", {"query": "light", "limit": 5}) data = result.get("data", result) lights = data.get("results", []) for e in lights: await call_tool("ha_call_service", { "domain": "light", "service": "turn_off", "entity_id": e["entity_id"]}) {"turned_off": len(lights)} ``` Example — delete an obsolete saved tool: ```python delete_saved_tool("old_movie_mode") ``` Args: code: Python code to execute. Last expression is the return value. justification: Why no existing tool works (required with code). save_as: Save the tool under this name for reuse (alphanumeric/underscores, max 64 chars). run_saved: Name of a previously saved tool to re-run. list_saved: Set True to list all saved tools.
Parameters
code - (str | None)
= null justification - (str | None)
= null save_as - (str | None)
= null run_saved - (str | None)
= null list_saved - (bool)
= false Description
Manage Home Assistant frontend themes. When NOT to use: themes are YAML files - Home Assistant has no API to create or edit them. Installing community themes goes through HACS (ha_manage_hacs); editing custom theme files goes through ha_config_set_yaml (beta, edits themes/<name>.yaml keyed by theme name and attempts an automatic theme reload). When to use: action='list' discovers installed theme names and the current defaults; action='set' selects the backend default theme (optionally per light/dark mode). Caveats: action='set' changes the backend-selected default only - users who explicitly picked a theme in their profile keep their choice. Theme names are validated by Home Assistant at call time. EXAMPLES: - List themes: ha_manage_theme(action="list") - Set default theme: ha_manage_theme(action="set", theme_name="nord") - Set dark-mode theme: ha_manage_theme( action="set", theme_name="nord", mode="dark") - Restore built-in default: ha_manage_theme( action="set", theme_name="default")
Parameters
action required - (Annotated[ThemeAction, Field(description='Theme operation: list installed themes or set the default theme.')]) theme_name - (Annotated[str | None, Field(description="Theme name when action='set'. Must be an installed theme; 'default' restores the built-in theme, 'none' resets the chosen mode to the built-in default.", default=None)])
= null mode - (Annotated[Literal['light', 'dark'] | None, Field(description="Which mode the theme applies to when action='set'. Defaults to light.", default=None)])
= null Description
Reload Home Assistant configuration without full restart. This tool reloads specific configuration components, allowing changes to take effect without restarting the entire Home Assistant instance. This is much faster than a full restart. **Parameters:** - target: What to reload. Options: - "all": Reload all reloadable components - "automations": Reload automation configurations - "scripts": Reload script configurations - "scenes": Reload scene configurations - "groups": Reload group configurations - "input_booleans": Reload input_boolean helpers - "input_numbers": Reload input_number helpers - "input_texts": Reload input_text helpers - "input_selects": Reload input_select helpers - "input_datetimes": Reload input_datetime helpers - "input_buttons": Reload input_button helpers - "timers": Reload timer helpers - "templates": Reload template sensors/entities - "persons": Reload person configurations - "zones": Reload zone configurations - "core": Reload core configuration (customize, packages) - "themes": Reload frontend themes **Example Usage:** ```python # Reload just automations after editing ha_reload_core(target="automations") # Reload all configurations ha_reload_core(target="all") # Reload input helpers after adding new ones ha_reload_core(target="input_booleans") ``` **When to Use:** - After editing automation/script YAML files - After adding new input helpers via YAML - After modifying customize.yaml - After theme changes
Parameters
target - (str)
= "all" Description
Restart Home Assistant. **WARNING: This will restart the entire Home Assistant instance!** All automations will be temporarily unavailable during restart. The restart typically takes 1-5 minutes depending on your setup. **Parameters:** - confirm: Must be set to True to confirm the restart. This is a safety measure to prevent accidental restarts. **Best Practices:** 1. Config is validated automatically before the restart proceeds; to pre-check, call ha_get_system_health(include="config_check") 2. Notify users before restarting (if applicable) 3. Schedule restarts during low-activity periods **Example Usage:** ```python # Optional pre-check (ha_restart also validates config automatically) health = ha_get_system_health(include="config_check") if health["config_check"]["is_valid"]: # Restart with confirmation result = ha_restart(confirm=True) ``` **Alternative:** For configuration changes, consider using ha_reload_core() instead, which reloads specific components without a full restart.
Parameters
confirm - (bool)
= false Todo Lists
Description
Get todo lists or items - list all todo lists or get items from a specific list. Without an entity_id: Lists all todo list entities in Home Assistant. With an entity_id: Gets items from that specific todo list, optionally filtered by status. **LISTING TODO LISTS (entity_id omitted):** Returns all entities in the 'todo' domain, including shopping lists and any other todo-type integrations. Each todo list includes: - entity_id: The unique identifier (e.g., 'todo.shopping_list') - friendly_name: Human-readable name - state: Number of incomplete items or current status **GETTING TODO ITEMS (entity_id provided):** Retrieves items from the specified todo list. Status filter values: - needs_action: Items that still need to be done - completed: Items that have been marked as done - None (default): Returns all items regardless of status Item properties: - uid: Unique identifier for the item - summary: The item text/description - status: Current status (needs_action or completed) - description: Optional detailed description - due: Optional due date (if supported) EXAMPLES: - List all todo lists: ha_get_todo() - Get all items: ha_get_todo("todo.shopping_list") - Get incomplete items: ha_get_todo("todo.shopping_list", status="needs_action") - Get completed items: ha_get_todo("todo.shopping_list", status="completed") USE CASES: - "What todo lists do I have?" - "Show me my shopping list" - "What's on my todo list?" - "Show completed items"
Parameters
entity_id - (Annotated[str | None, Field(description="Todo list entity ID (e.g., 'todo.shopping_list'). If omitted, lists all todo list entities.", default=None)])
= null status - (Annotated[Literal['needs_action', 'completed'] | None, Field(description="Filter items by status: 'needs_action' for incomplete, 'completed' for done. Only applies when entity_id is provided.", default=None)])
= null Description
Remove an item from a Home Assistant todo list. Permanently deletes an item from the specified todo list. IDENTIFYING ITEMS: - Use the item's UID (from ha_get_todo) - Or use the exact item summary/name text EXAMPLES: - Remove by name: ha_remove_todo_item("todo.shopping_list", "Buy milk") - Remove by UID: ha_remove_todo_item("todo.shopping_list", "abc123-uid") USE CASES: - "Remove milk from my shopping list" - "Delete the eggs item" - "Clear 'call mom' from my todo" WARNING: This permanently removes the item. To mark as completed instead, use ha_set_todo_item() with status="completed".
Parameters
entity_id required - (Annotated[str, Field(description="Todo list entity ID (e.g., 'todo.shopping_list')")]) item required - (Annotated[str, Field(description='Item to remove - can be the item UID or the exact item summary/name')]) Description
Create or update a todo item in Home Assistant. WITHOUT item parameter (create mode): Creates a new item. summary is required. WITH item parameter (update mode): Updates an existing item identified by UID or exact name. At least one update field (rename, status, description, due_date, due_datetime) is required. EXAMPLES: - Add item: ha_set_todo_item("todo.shopping_list", summary="Buy milk") - Add with description: ha_set_todo_item("todo.shopping_list", summary="Buy milk", description="2% organic") - Add with due date: ha_set_todo_item("todo.tasks", summary="Pay bills", due_date="2024-12-31") - Complete item: ha_set_todo_item("todo.shopping_list", item="Buy milk", status="completed") - Rename item: ha_set_todo_item("todo.tasks", item="Old task", rename="New task name") - Update due date: ha_set_todo_item("todo.tasks", item="Pay bills", due_date="2024-12-31") - Reopen item: ha_set_todo_item("todo.tasks", item="Task to redo", status="needs_action") NOTE: Not all todo integrations support all features (description, due dates). The Shopping List integration only supports summary.
Parameters
entity_id required - (Annotated[str, Field(description="Todo list entity ID (e.g., 'todo.shopping_list')")]) summary - (Annotated[str | None, Field(description="Item text/name. Required when creating a new item. Ignored in update mode — use 'rename' to change the item name.", default=None)])
= null item - (Annotated[str | None, Field(description='Existing item to update - can be the item UID or the exact item summary/name. When provided, operates in update mode. When omitted, creates a new item.', default=None)])
= null status - (Annotated[Literal['needs_action', 'completed'] | None, Field(description="Item status: 'completed' to mark done, 'needs_action' to mark incomplete. Only used in update mode.", default=None)])
= null description - (Annotated[str | None, Field(description='Detailed description for the item', default=None)])
= null due_date - (Annotated[str | None, Field(description="Due date in YYYY-MM-DD format (e.g., '2024-12-25')", default=None)])
= null due_datetime - (Annotated[str | None, Field(description="Due datetime in ISO format (e.g., '2024-12-25T14:00:00'). Overrides due_date if both provided.", default=None)])
= null rename - (Annotated[str | None, Field(description='New name/summary for an existing item. Only used in update mode.', default=None)])
= null Utilities
Description
Evaluate Jinja2 templates using Home Assistant's template engine. This tool allows testing and debugging of Jinja2 template expressions that are commonly used in Home Assistant automations, scripts, and configurations. It provides real-time evaluation with access to all Home Assistant states, functions, and template variables. **When NOT to use this for automation/script logic:** Templates have legitimate uses (notification bodies, dynamic `data.*` values, debugging existing templates), but `condition:` / `trigger:` positions and action service names are better expressed as native HA constructs: native constructs are schema-validated at config load and surface structural errors loudly, whereas equivalent template logic only errors at runtime — and a template that renders a non-truthy value is silently treated as false. Prefer: - `condition: numeric_state` over `{{ states('x') | float > N }}` - `condition: state` over `{{ is_state(...) }}` - `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)` - Native `for:` field on state/numeric_state triggers and state conditions over `{{ now() - X.last_changed > timedelta(...) }}` duration math - `choose` action over templated `service:` / `action:` strings See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list. **When to use (reach for this tool, don't compute it yourself):** Any one-shot question whose answer is DERIVED from current HA state — an average/sum/min/max across sensors, a count of entities matching a condition, a boolean comparison, or a rendered message with live values. One render call beats fetching N states and doing the math yourself, and it is the canonical way to *test* a template before embedding it. This is for one-shot answers and template testing only — NOT for putting templates into automation logic; for `condition:` / `trigger:` positions native constructs win. - "average temperature across the bedroom sensors" -> `{{ ([states('sensor.a'), states('sensor.b')] | map('float', 0) | sum) / 2 }}` - "how many lights are on" -> `{{ states.light | selectattr('state', 'eq', 'on') | list | count }}` NOT for a plain single-entity value ("what's the state of X") — that is `ha_get_state` / `ha_search`; rendering `{{ states('X') }}` there is over-use. **Parameters:** - template: The Jinja2 template string to evaluate - timeout: Maximum evaluation time in seconds (default: 3) - report_errors: Whether to return detailed error information (default: True) **Common Template Functions:** **State Access:** ```jinja2 {{ states('sensor.temperature') }} # Get entity state value {{ states.sensor.temperature.state }} # Alternative syntax {{ state_attr('light.bedroom', 'brightness') }} # Get entity attribute {{ is_state('light.living_room', 'on') }} # Check if entity has specific state ``` **Numeric Operations:** ```jinja2 {{ states('sensor.temperature') | float(0) }} # Convert to float with default {{ states('sensor.humidity') | int(0) }} # Convert to integer with default {{ (states('sensor.temp') | float(0) + 5) | round(1) }} # Math operations ``` **Time and Date:** ```jinja2 {{ now() }} # Current datetime {{ now().strftime('%H:%M:%S') }} # Format current time {{ as_timestamp(now()) }} # Convert to Unix timestamp {{ now().hour }} # Current hour (0-23) {{ now().weekday() }} # Day of week (0=Monday) ``` **Conditional Logic (for display strings — not for `condition:` positions):** ```jinja2 {{ 'Day' if now().hour < 18 else 'Night' }} # Ternary operator {% if is_state('alarm_control_panel.home', 'armed_away') %} Alarm is armed {% else %} Alarm is disarmed {% endif %} ``` **Lists and Loops:** ```jinja2 {% for entity in states.light %} {{ entity.entity_id }}: {{ entity.state }} {% endfor %} {{ states.light | selectattr('state', 'eq', 'on') | list | count }} # Count on lights ``` **String Operations:** ```jinja2 {{ states('sensor.weather') | title }} # Title case {{ 'Hello ' + states('input_text.name') }} # String concatenation {{ states('sensor.data') | regex_replace('pattern', 'replacement') }} ``` **Device and Area Functions:** ```jinja2 {{ device_entities('device_id_here') }} # Get entities for device {{ area_entities('living_room') }} # Get entities in area {{ device_id('light.bedroom') }} # Get device ID for entity ``` **Common Use Cases (legitimate template positions):** **Dynamic Service Data:** ```jinja2 # Dynamic brightness based on time {{ 255 if now().hour < 22 else 50 }} # Message with current values "Temperature is {{ states('sensor.temp') }}°C, humidity {{ states('sensor.humidity') }}%" ``` **Examples:** **Test basic state access:** ```python ha_eval_template("{{ states('light.living_room') }}") ``` **Test a string expression (e.g. for a notification body):** ```python ha_eval_template("{{ 'Day' if now().hour < 18 else 'Night' }}") ``` **Test mathematical operations:** ```python ha_eval_template("{{ (states('sensor.temperature') | float(0) + 5) | round(1) }}") ``` **Test entity counting:** ```python ha_eval_template("{{ states.light | selectattr('state', 'eq', 'on') | list | count }}") ``` **IMPORTANT NOTES:** - Templates have access to all current Home Assistant states and attributes - Use this tool to test templates before using them in automations or scripts - Template evaluation respects Home Assistant's security model and timeouts - Complex templates may affect Home Assistant performance - keep them efficient - Use default values (e.g., `| float(0)`) to handle missing or invalid states **For template documentation:** https://www.home-assistant.io/docs/configuration/templating/
Parameters
template required - (str) timeout - (int)
= 3 report_errors - (bool)
= true Description
Install the ha_mcp_tools custom component via HACS. This tool installs the ha_mcp_tools custom component which provides advanced services not available through standard Home Assistant APIs: **Available Services (after installation):** - `ha_mcp_tools.list_files`: List files in allowed directories (www/, themes/) - More services coming soon: file write, backup cleanup, event buffer, etc. **Installation Process:** 1. Checks if HACS is available 2. Checks if ha_mcp_tools is already installed 3. Adds the repository to HACS if not present 4. Downloads and installs the component 5. Optionally restarts Home Assistant **Note:** A restart is required for the integration to load and become available. Set `restart=True` to automatically restart, or manually restart later. Args: restart: Whether to restart Home Assistant after installation (default: False) Returns: Installation status and next steps.
Parameters
restart - (Annotated[bool, Field(default=False, description='Whether to restart Home Assistant after installation (required for integration to load)')])
= false Description
Collect diagnostic information for filing issue reports or feedback. This tool generates templates for TWO types of reports: 1. **Runtime Bug Report** - For ha-mcp errors, failures, unexpected behavior 2. **Agent Behavior Feedback** - For AI agent inefficiency, wrong tool usage **IMPORTANT FOR AI AGENTS:** You MUST analyze the conversation context to determine which template to present: 🐛 **Present RUNTIME BUG template if:** - User reports an error, failure, or unexpected behavior - A tool returned an error or incorrect result - Something is broken or not working in ha-mcp 🤖 **Present AGENT BEHAVIOR template if:** - User mentions YOU (the agent) used the wrong tool - User suggests a more efficient workflow - User reports YOUR inefficiency or mistakes - User says you should have done something differently **If unclear which type, ASK the user:** "Are you reporting a bug in ha-mcp, or providing feedback on how I used the tools?" **WHEN TO USE THIS TOOL:** - "I want to file a bug/issue/report" - "This isn't working" - "You should have used [other tool]" - "That was inefficient" **OUTPUT:** Returns both templates plus diagnostic data. Key fields: - `runtime_bug_template`, `agent_behavior_template` — pick based on context - `recent_logs`, `startup_logs` — captured ha-mcp tool/server log entries - `addon_logs` — addon container stdout/stderr (HA add-on installs only; empty string otherwise) - `suggested_title`, `duplicate_check_urls`, `anonymization_guide`
Parameters
tool_call_count - (Annotated[int, Field(default=10, ge=1, le=16, description='Number of tool calls made since the issue started. This determines how many log entries to include. Count how many ha_* tools were called from when the issue began. Default: 10. Max: 16 (limited by 200-entry log buffer: 16*4*3=192)')])
= 10 Zones
Description
Get zone information - list all zones or get details for a specific one. Without a zone_id: Lists all Home Assistant zones with their coordinates and radius. With a zone_id: Returns detailed configuration for a specific zone. ZONE PROPERTIES: - ID, name, icon - Latitude, longitude, radius - Passive mode setting EXAMPLES: - List all zones: ha_get_zone() - Get specific zone: ha_get_zone(zone_id="abc123") **NOTE:** This returns storage-based zones (created via UI/API), not YAML-defined zones. The 'home' zone is typically defined in YAML and may not appear in this list.
Parameters
zone_id - (Annotated[str | None, Field(description='Zone ID to get details for (from ha_get_zone() list). If omitted, lists all zones.', default=None)])
= null Description
Remove a Home Assistant zone. EXAMPLES: - Remove zone: ha_remove_zone("abc123") **WARNING:** Removing a zone used in automations may cause those automations to fail. Use ha_get_zone() to find the zone_id for the zone you want to remove. **NOTE:** The 'home' zone cannot be removed as it is typically defined in configuration.yaml.
Parameters
zone_id required - (Annotated[str, Field(description='Zone ID to remove (use ha_get_zone to find IDs)')]) Description
Create or update a Home Assistant zone. Omit zone_id to create a new zone (name, latitude, longitude required). Provide zone_id to update an existing zone (only specified fields change). EXAMPLES: - Create: ha_set_zone(name="Office", latitude=40.7128, longitude=-74.0060, radius=150, icon="mdi:briefcase") - Update name: ha_set_zone(zone_id="abc123", name="New Office") - Update radius: ha_set_zone(zone_id="abc123", radius=200) - Update location: ha_set_zone(zone_id="abc123", latitude=40.7128, longitude=-74.0060) Note: The 'home' zone is typically defined in YAML and cannot be modified via this API.
Parameters
name - (Annotated[str | None, Field(description='Display name for the zone (required for create)', default=None)])
= null latitude - (Annotated[float | None, Field(description='Latitude coordinate of the zone center (required for create)', default=None)])
= null longitude - (Annotated[float | None, Field(description='Longitude coordinate of the zone center (required for create)', default=None)])
= null zone_id - (Annotated[str | None, Field(description='Zone ID to update (omit to create new zone, use ha_get_zone to find IDs)', default=None)])
= null radius - (Annotated[float | None, Field(description='Radius of the zone in meters (must be > 0, defaults to 100 on create)', default=None)])
= null icon - (Annotated[str | None, Field(description="Material Design Icon (e.g., 'mdi:briefcase', 'mdi:school')", default=None)])
= null passive - (Annotated[bool | None, Field(description='Passive mode - if True, zone will not trigger enter/exit automations (defaults to False on create)', default=None)])
= null