Tool Explorer
Browse all 87 MCP tools across 29 categories.
Apps (add-ons)
Description
Get installed or available Home Assistant apps (add-ons), or details for one. Do not use this tool to change app state or configuration; use ``ha_manage_app``. Use ``slug`` for details, ``source="installed"`` for an inventory, or ``source="available"`` for store discovery. Requires Home Assistant OS or Supervised. ``include_stats`` applies only to installed-app listings.
Parameters
source-App (add-on) source: 'installed' (default) for currently installed apps, 'available' for apps in the store that can be installed. With source='available', 'version' is the version you would get by installing (Supervisor's version_latest) and 'version_installed' is the running one, null when the app is not installed — so compare the two, not 'version' alone, to tell whether an installed app is current.(Literal['installed', 'available'] | None)= nullslug-App (add-on) slug for detailed info (e.g., '<prefix>_nodered'). Slug prefixes vary by app repository — omit to list all apps and discover the actual installed slug.(str | None)= nullinclude_stats-Include CPU/memory usage statistics (only for source='installed')(bool)= falserepository-Filter by repository slug, e.g., 'core', 'community' (only for source='available')(str | None)= nullquery-App (add-on) name/description filter (only for source='available')(str | None)= nullDescription
Manage Home Assistant apps (add-ons) or proxy an app API. For app inventory, status, and Supervisor metadata, call ``ha_get_app`` first; use proxy mode here for documented app-specific read APIs. Do not infer private app API schemas; consult version-matched app docs, and use ``ha_get_skill_guide`` for complex Home Assistant workflows. Use exactly one mode: lifecycle/store action, configuration fields, ``path`` proxy, or ``path`` with ``array_patch``. Requires Home Assistant OS or Supervised. ``options`` merges top-level keys and one nested mapping level; supply complete values for deeper nested mappings because they are replaced. A non-empty ``network`` replaces the full port override map. Prefer Ingress: direct-port access requires a shared container network and may require weakening the target app authentication. If a Supervisor lifecycle, configuration, or repository write has an unknown outcome, verify durable state with ``ha_get_app`` before retrying. That cannot prove whether ``restart`` or ``rebuild`` ran; inspect Supervisor jobs and logs and do not automatically replay them. For a proxy or array-patch write, query the target app's own read API before retrying.
Parameters
slug-App (add-on) slug (e.g., '<prefix>_nodered', '<prefix>_frigate'). Slug prefixes vary by app repository — call ha_get_app() 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, and the store-wide action (action='check_updates'), which takes neither.(str)= ""path-Proxy mode: API path relative to the app (add-on) root (e.g., '/flows', '/api/events', '/api/stats'). Required for proxy mode; mutually exclusive with config parameters.(str | None)= nullmethod-Proxy mode only. HTTP method: GET, POST, PUT, DELETE, PATCH. Defaults to GET.(str)= "GET"body-Proxy mode only. Request body for POST/PUT/PATCH — or, with websocket=True, the initial WebSocket message. Pass a JSON object or JSON string.(dict[str, Any] | str | None)= nulldebug-Proxy mode only. Include diagnostic info (request URL, headers sent, response headers). Default: false.(bool)= falseport-Proxy mode only. Connect to this port instead of the Ingress port. Use ha_get_app(slug='...') to find available ports. Some apps, including Node-RED, reject direct access unless their leave_front_door_open option is enabled and the app is restarted; related errors include an actionable, security-qualified ha_manage_app options command.(int | None)= nulloffset-Proxy mode only. HTTP: skip this many items in a JSON array response. Default: 0.(int)= 0limit-Proxy mode only. HTTP: return at most this many items from a JSON array response.(int | None)= nullwebsocket-Proxy mode only. Use WebSocket instead of HTTP for an app (add-on) WebSocket API. Sends 'body' as the initial message and collects responses; command names and body schemas are app/version-specific. Default: false.(bool)= falsewait_for_close-Proxy mode only. WebSocket: True waits for the server to close a run-to-completion stream. False returns after the first response batch; use for one-shot command/response or bounded capture on a channel that stays open. Default: true.(bool)= truemessage_limit-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).(int | None)= nullmessage_offset-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.(int)= 0summarize-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.(bool)= truepython_transform-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.(str | None)= nulloptions-Config mode: App (add-on) configuration values (the 'Configuration' tab in the UI).(dict[str, Any] | None)= nullnetwork-Config mode: Complete desired host-port override map (e.g., {'5800/tcp': 8081}). A non-empty map replaces current overrides, so omitted entries are cleared. Omit 'network' to leave mappings unchanged. An empty map is ignored and does not by itself select config mode.(dict[str, Any] | None)= nullboot-Config mode: Boot strategy — 'auto' (start with HA) or 'manual'.(str | None)= nullauto_update-Config mode: Enable or disable automatic updates for this app (add-on).(bool | None)= nullwatchdog-Config mode: Enable or disable Supervisor watchdog (auto-restart on crash).(bool | None)= nullarray_patch-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. Use ha_get_skill_guide for operation shapes.(dict[str, Any] | None)= nullrequest_headers-Proxy/array-patch mode: extra HTTP headers for the app (add-on) API. Useful for app-specific requirements such as Node-RED's `Node-RED-Deployment-Type: full`. Ingress routing headers override caller values on Ingress routes; direct-port calls have no internal routing headers. `Content-Type` is derived from the body when supplied. Not valid in config or websocket mode.(dict[str, str] | None)= nullaction-Lifecycle mode: run a Supervisor app (add-on) action. One of 'install', 'uninstall', 'start', 'stop', 'restart', 'rebuild', 'update'. 'insta…
ll'/'update' require the app's repository to be registered (it appears in ha_get_app(source='available')). Store-repository mode: 'add_repository' / 'remove_repository' register or unregister a custom app store repository — these use the 'repository' param instead of 'slug'. Store-wide mode: 'check_updates' reloads the store so Supervisor re-scans its repositories, mirroring the Apps UI 'Check for updates' item — it takes neither 'slug' nor 'repository', refreshes available metadata only, and installs nothing. Use it to pick up an edited local app's config.yaml on demand instead of waiting for Supervisor's own reload (every 3h). Follow with action='update' to install a new version, or action='rebuild' for a local app whose source changed but whose version did not. Returns 'changed' and 'updates_available', each null (not empty) if the store could not be read to measure it — check 'warnings'. When ha-mcp runs as an app, it can update other apps but cannot update its own running slug; update ha-mcp from the Home Assistant Apps UI. Mutually exclusive with path / config parameters / array_patch. HA OS / Supervised only.nullrepository-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_app(source='available')). Required for those actions; rejected otherwise.(str | None)= nullAreas & 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. When the ha_mcp_tools component's registries capability is available, both registries come from a single in-process snapshot, so this classification is always consistent. Without it (legacy path), the two registries are fetched via independent WebSocket calls and a registry change between reads may transiently misclassify an area.
Parameters
fields-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.(str | list[str] | None)= nullarea_fields-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.(str | list[str] | None)= nullDescription
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
kindrequired-Which registry to delete from: 'area' or 'floor'(Literal['area', 'floor'])idrequired-Area ID or floor ID to delete (use ha_list_floors_areas to find IDs)(str)Description
Create or update a Home Assistant area or floor. Pass kind='area' (with optional floor_id, picture, labels) 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 or labels 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="area", id="kitchen", labels=["site_home"]) ha_set_area_or_floor(kind="floor", name="Basement", level=-1) ha_set_area_or_floor(kind="floor", id="ground_floor", level=0)
Parameters
kindrequired-Which registry to operate on: 'area' for rooms, 'floor' for building levels(Literal['area', 'floor'])name-Name (required when creating; optional when updating, e.g., 'Living Room', 'Ground Floor')(str | None)= nullid-Existing area_id or floor_id to update (omit to create a new entry; use ha_list_floors_areas to find IDs)(str | None)= nullfloor_id-Floor assignment when kind='area' (use empty string to clear). Only valid when kind='area'.(str | None)= nulllevel-Numeric level when kind='floor' (0=ground, 1=first, -1=basement). Only valid when kind='floor'.(int | None)= nullicon-Material Design Icon (e.g., 'mdi:sofa', 'mdi:home-floor-1', empty string to remove)(str | None)= nullaliases-Alternative names for voice assistant recognition (e.g., ['lounge'], empty list to clear)(str | list[str] | None)= nullpicture-Picture URL when kind='area' (empty string to remove). Only valid when kind='area'.(str | None)= nulllabels-Label IDs when kind='area' (replaces the area's label set; empty list to clear). Omit to leave labels unchanged. Only valid when kind='area' — floors have no labels.(str | list[str] | None)= nullAssist
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, action='set_preferred' to choose the preferred pipeline, and action='process' to run a sentence through Assist. action='process' sends the sentence straight to Assist's conversation agent, so a matched intent executes: it turns on the light rather than reporting that it would. Its result carries response_type ('action_done', 'query_answer' or 'error') and, on an error, error_code such as 'no_intent_match' — Assist declining a sentence is an answer, not a tool failure, so inspect those fields rather than expecting a raised error. Use ha_call_service to act on an entity directly; use this to test what Assist itself understands. When the built-in agent answers, a matching conversation trigger runs its automation: that agent checks its sentence triggers before it matches intents, so this is not limited to intents. pipeline_id borrows a pipeline's conversation agent and language, but the sentence still goes to the agent directly. So with an agent other than the built-in one, neither sentence triggers nor prefer_local_intents apply — a full pipeline run is what adds those for other agents. 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", ) - Run a sentence: ha_manage_pipeline( action="process", sentence="turn on the kitchen light", ) - Run it through one pipeline's agent: ha_manage_pipeline( action="process", sentence="turn on the kitchen light", pipeline_id="preferred", ) - Continue a conversation: ha_manage_pipeline( action="process", sentence="and the hallway?", conversation_id="<id from the previous response>", ) 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
actionrequired-Pipeline operation: list, get, create, update, set_preferred, or process.(PipelineAction)pipeline_id-Assist pipeline ID. Required for get, update, and set_preferred. Optional for process, where it selects the conversation agent and language that pipeline is configured with.(str | None)= nullsentence-Natural-language command to run through Assist. Required when action='process'. A matched intent executes, and with the built-in agent a sentence matching a conversation trigger runs that automation.(str | None)= nullconversation_id-For process only, the conversation to continue. Returned in the response so follow-up sentences keep their context.(str | None)= nullagent_id-For process only, the conversation agent entity ID to answer, e.g. 'conversation.home_assistant'. Overrides the agent taken from pipeline_id; omit both for the default agent.(str | None)= nullname-Pipeline display name. Required when action='create'.(str | None)= nullconversation_engine-Conversation agent entity ID or engine ID. Required when action='create'.(str | None)= nullbase_pipeline_id-Pipeline ID to clone when creating. Omit to clone the preferred pipeline. Ignored for non-create actions.(str | None)= nullconversation_language-Conversation language, usually '*'.(str | None)= nulllanguage-Pipeline language, e.g. 'en'. For process, the language to recognise the sentence in.(str | None)= nullstt_engine-Speech-to-text engine. Pass empty string to clear.(str | None)= nullstt_language-Speech-to-text language. Pass empty string to clear.(str | None)= nulltts_engine-Text-to-speech engine. Pass empty string to clear.(str | None)= nulltts_language-Text-to-speech language. Pass empty string to clear.(str | None)= nulltts_voice-Text-to-speech voice. Pass empty string to clear.(str | None)= nullwake_word_entity-Wake-word entity ID. Pass empty string to clear.(str | None)= nullwake_word_id-Wake-word ID. Pass empty string to clear.(str | None)= nullprefer_local_intents-Whether Home Assistant local intents should be preferred before the conversation engine.(bool | None)= nullmake_preferred-For create/update only, also set the resulting pipeline as preferred with an extra websocket call. Ignored for other actions.(bool)= falseAutomations
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
identifierrequired-Automation entity_id (e.g., 'automation.morning_routine') or unique_id(str)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
identifierrequired-Automation entity_id (e.g., 'automation.old_automation') or unique_id to delete(str)wait-Wait for automation to be fully removed before returning. Default: True.(bool)= trueDescription
Create or update a Home Assistant automation. MUST call ha_get_skill_guide OR refer to your locally installed skills 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 three modes: full config replacement, Python transformation, or take_control_of_blueprint (see below). WHEN TO USE WHICH MODE: - python_transform: RECOMMENDED for edits to existing automations. Surgical updates. - config: Use for creating new automations or full restructures. - take_control_of_blueprint: converts a blueprint-backed automation into a standalone one. Takes no config of its own. 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()" Omit identifier and config['id'] to create a new automation with a generated ID. A previously unused raw ID can also create an automation with that specific ID. Reusing an identifier targets the same automation, even if the alias changes. To intentionally rename or replace it, first read it with ha_config_get_automation and pass its config_hash. A changed alias without that hash is rejected before writing. 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: current = ha_config_get_automation(identifier="automation.morning_routine") ha_config_set_automation( identifier="automation.morning_routine", config_hash=current["config_hash"], 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 } } } ) TAKE CONTROL OF A BLUEPRINT AUTOMATION: take_control_of_blueprint=True converts a blueprint-backed automation into a standalone one — the UI's "Take control". The blueprint is rendered with the automation's CURRENT inputs and the result is saved over the same automation, which keeps its entity_id, alias and description but gains its own triggers/conditions/actions and loses 'use_blueprint'. ha_config_set_automation( identifier="automation.motion_light_kitchen", take_control_of_blueprint=True, ) This is one-way: the automation is no longer linked to the blueprint, so later blueprint edits stop reaching it. To change an input value, update 'use_blueprint.input' instead (see the example above) — that keeps the link. Taking control does NOT free the blueprint. Home Assistant goes on counting a converted automation as a user of it, so deleting that blueprint stays refused until the automation itself is removed (verified against Home Assistant 2026.9; an automation reload does not clear it either). The response names the blueprint in `took_control_of_blueprint`. To see what the rendering looks like WITHOUT writing anything, call ha_manage_blueprints(action="substitute", path=..., input=...), which returns the config and leaves the automation alone. ha_manage_blueprints also lists, imports, saves and deletes blueprints, and action="get" reports which automations use one. 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-Complete automation configuration with required fields: 'alias', 'triggers', 'actions'. Optional: 'description', 'conditions', 'mode', 'max', 'initial_state', 'variables'. Purpose-specific triggers/conditions (HA 2026.7+ default: 'trigger': '<domain>.<name>' with 'target'/'options') are valid config. Mutually exclusive with python_transform.(dict[str, Any] | None)= nullidentifier-Target automation entity_id or HA config 'id' (unique_id). Omit for creation with a generated ID. Values such as 'new' are literal IDs, not placeholders. Required for python_transform.(str | None)= nullpython_transform-Python expression to transform existing automation config. Mutually exclusive with config. Requires identifier and config_hash for validatio…
n. 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" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire listnullconfig_hash-Config hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Required when a config update changes an existing automation's alias. Otherwise optional for config updates (validates before full replacement if provided).(str | None)= nulltake_control_of_blueprint-Convert a blueprint-backed automation into an editable standalone one -- the UI's "Take control". Renders the blueprint with its current inp…
uts and saves the result over the same automation, which then has its own triggers/conditions/actions and no 'use_blueprint'. Requires identifier; mutually exclusive with config and python_transform. Irreversible: the link to the blueprint is gone afterwards, so edit inputs instead if you only want to change a value. Does NOT free the blueprint: Home Assistant keeps counting the converted automation as a user, so deleting that blueprint stays refused until the automation is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute").falsecategory-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.(str | None)= nullwait-Wait for automation to be queryable before returning. Default: True. Set to False for bulk operations.(bool)= trueMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullBlueprints
Description
Manage Home Assistant blueprints — list, read, import, save, delete, or render a standalone config. One interface for the whole blueprint lifecycle in the ``automation`` and ``script`` domains. DO NOT use this to create an automation or script FROM a blueprint — that is ``ha_config_set_automation`` / ``ha_config_set_script`` with a ``use_blueprint`` config. Use ``action="list"`` to discover installed blueprints, ``action="get"`` for one blueprint's metadata, inputs and YAML, ``action="import"`` to install one from a URL, ``action="save"`` to write YAML text to a blueprint path, ``action="delete"`` to remove an installed one, and ``action="substitute"`` to render a blueprint plus inputs into a standalone config (the UI's "Take control"). To duplicate a blueprint, ``get`` it and ``save`` its ``yaml`` under a new ``path``; to edit one in place, ``get`` it, change the text, and ``save`` it back to the same ``path`` with ``overwrite=True``. ``get`` also reports ``used_by``: the automations or scripts built on the blueprint, which is the UI's "Show automations using this blueprint". Check it before deleting — Home Assistant refuses to delete a blueprint anything still uses, and it goes on counting a consumer that has since taken control of its own config until that consumer is removed. CAVEATS: ``get`` returns the on-disk YAML only when something can read it — an in-process server, the ha_mcp_tools component, the File & YAML Tools entry, or the blueprint's ``source_url``; ``yaml_source`` names which one answered, and ``source_url`` text is a fresh download that can differ from the installed file. Core's blueprint API alone exposes metadata only, so a locally authored blueprint on a bare install has no readable text. ``save`` needs ``overwrite=True`` to replace an existing path and reloads every automation/script using it. ``delete`` requires ``confirm=True``, and Home Assistant refuses it while any automation or script still uses the blueprint — the error lists the consumers. Both writes are snapshotted first when a copy can be read, so ``ha_manage_backup(scope="edits")`` can restore the previous file. ``substitute`` only renders — it writes nothing, so pass the returned config to ``ha_config_set_automation`` / ``ha_config_set_script`` to persist it. To convert an automation or script that ALREADY exists, prefer ``ha_config_set_automation`` / ``ha_config_set_script`` with ``take_control_of_blueprint=True``: it renders with that item's own current inputs and saves the result over itself in one call, where ``substitute`` would need those inputs restated and the config written back by hand. Taking control does NOT free the blueprint — Home Assistant goes on counting a converted automation or script as a user of it, so ``delete`` stays refused until the consumers are removed. EXAMPLES: - List: ha_manage_blueprints(action="list", domain="automation") - Get one (with its consumers in ``used_by``): ha_manage_blueprints(action="get", path="homeassistant/motion_light.yaml") - Import: ha_manage_blueprints(action="import", url="https://example.com/bp.yaml") - Duplicate: ha_manage_blueprints(action="save", path="user/my_copy.yaml", yaml=<text from get>) - Edit in place: ha_manage_blueprints(action="save", path="user/motion.yaml", yaml=<edited text>, overwrite=True) - Delete: ha_manage_blueprints(action="delete", path="user/motion.yaml", confirm=True) - Detach: ha_manage_blueprints(action="substitute", path="user/motion.yaml", input={"motion_sensor": "binary_sensor.hall"}) - Convert an existing consumer to a standalone config: ha_config_set_automation(identifier="automation.hall", take_control_of_blueprint=True) RELATED TOOLS: ``ha_config_set_automation`` / ``ha_config_set_script`` to build on a blueprint or persist a substituted config, ``ha_config_remove_automation`` / ``ha_config_remove_script`` to clear consumers blocking a delete, ``ha_search`` to find them, and ``ha_manage_backup(scope="edits")`` to restore a deleted blueprint.
Parameters
actionrequired-'list' installed blueprints, 'get' one blueprint's metadata/inputs/YAML, 'import' one from a URL, 'save' YAML text to a blueprint path, 'delete' an installed one, or 'substitute' to render a standalone config(Literal['list', 'get', 'import', 'save', 'delete', 'substitute'])domain-Blueprint domain: 'automation' or 'script'. Ignored by action='import' — the blueprint file declares its own domain.(str)= "automation"path-Installed blueprint path, e.g. 'homeassistant/motion_light.yaml' (action='get' / 'save' / 'delete' / 'substitute'). 'save' appends '.yaml' when it is missing, as Home Assistant does.(str | None)= nullurl-URL to import from — GitHub, Home Assistant Community, or a direct YAML link (action='import')(str | None)= nullyaml-Blueprint YAML text to write (action='save')(str | None)= nullsource_url-Origin URL to stamp into the saved blueprint's metadata (action='save'); omit for a hand-authored blueprint(str | None)= nulloverwrite-Write over an already-installed blueprint (action='import' / 'save'). Home Assistant reloads every automation/script using it.(bool)= falseinput-Blueprint input values keyed by input name (action='substitute'); defaults to {}(dict[str, Any] | None)= nullconfirm-Required confirmation for action='delete'(bool)= falseCalendar
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_idrequired-Calendar entity ID (e.g., 'calendar.family')(str)start-Start datetime in ISO format (default: now)(str | None)= nullend-End datetime in ISO format (default: 7 days from start)(str | None)= nullmax_results-Maximum number of events to return(int)= 20Description
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 ('THISANDFUTURE' 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="THISANDFUTURE" ) ``` **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_idrequired-Calendar entity ID (e.g., 'calendar.family')(str)uidrequired-Unique identifier of the event to delete(str)recurrence_id-Optional recurrence ID for recurring events(str | None)= nullrecurrence_range-Optional recurrence range ('THISANDFUTURE' to delete this and future occurrences). Home Assistant compares this value verbatim, so no other spelling (including 'THIS_AND_FUTURE') selects the range.(Literal['THISANDFUTURE'] | None)= nullDescription
Create a new event in a calendar, or update an existing one. 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). Passing ``uid`` switches to update mode, which uses the WebSocket ``calendar/event/update`` command — HA registers no REST service for updating an event. **When NOT to use:** - To retrieve calendar events, use ``ha_config_get_calendar_events``. - To delete an event, use ``ha_config_remove_calendar_event``. - To find the ``uid`` of an event to update, use ``ha_config_get_calendar_events``; this tool does not search. **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" ) # Update an existing event (uid from ha_config_get_calendar_events). # The event is REPLACED, so re-supply every field you want to keep. result = ha_config_set_calendar_event( "calendar.family", summary="Doctor appointment (rescheduled)", start="2024-01-15T16:00:00", end="2024-01-15T17:00:00", location="Clinic", uid="event-12345" ) # Update one occurrence of a recurring series and all later ones result = ha_config_set_calendar_event( "calendar.work", summary="Team meeting (new time)", start="2024-02-05T11:00:00", end="2024-02-05T12:00:00", uid="recurring-event-67890", recurrence_id="20240205T100000", recurrence_range="THISANDFUTURE" ) # 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" ) # Create an all-day event (date-only, no time component). The end # date is EXCLUSIVE, so this spans 2026-07-04 through 2026-07-10. result = ha_config_set_calendar_event( "calendar.family", summary="Vacation", start="2026-07-04", end="2026-07-11" ) ``` **Note:** Passing date-only values (``YYYY-MM-DD``) for both ``start`` and ``end`` creates an all-day event; passing full ISO datetimes creates a timed event. The two forms cannot be mixed — a date-only ``start`` with a datetime ``end`` (or vice versa) is rejected. Because the all-day ``end`` date is exclusive, a single-day all-day event must set ``end`` to ``start + 1 day``. An update replaces the whole event rather than patching it, so ``summary``, ``start`` and ``end`` stay required in update mode, and a ``description`` or ``location`` that is not re-supplied is cleared. An ``rrule`` is the exception: Home Assistant accepts a new rule but has no way to express "no recurrence", so an existing rule survives an update that omits it. Delete the event and create it again to drop the recurrence. Not every calendar integration supports event creation; recurring events additionally require the integration to support recurrence (the built-in Local Calendar does). Update support is narrower still: Local Calendar implements it, while the core Google Calendar and CalDAV integrations do not. **Returns:** - Success status and event details
Parameters
entity_idrequired-Calendar entity ID (e.g., 'calendar.family')(str)summaryrequired-Event title/summary(str)startrequired-Event start date or datetime in ISO format(str)endrequired-Event end date or datetime in ISO format. For all-day events (date-only) the end date is exclusive; a single-day all-day event needs end = start + 1 day.(str)description-Optional event description(str | None)= nulllocation-Optional event location(str | None)= nullrrule-Optional RFC 5545 recurrence rule, without 'RRULE:' prefix (e.g., 'FREQ=WEEKLY;BYDAY=MO' or 'FREQ=MONTHLY;BYDAY=3SA'). Creates a recurring event series.(str | None)= nulluid-UID of an existing event to update. Omit to create a new event. Get UIDs from ha_config_get_calendar_events.(str | None)= nullrecurrence_id-Only meaningful with 'uid': identifies one occurrence of a recurring series to update.(str | None)= nullrecurrence_range-Only meaningful with 'uid': 'THISANDFUTURE' to update this and all following occurrences. Home Assistant compares this value verbatim, so no other spelling (including 'THIS_AND_FUTURE') selects the range.(Literal['THISANDFUTURE'] | None)= nullCamera
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_idrequired-(str)width-(int | None)= nullheight-(int | None)= nullDashboard
Description
Get rendered images of a Home Assistant Lovelace dashboard view. When not to use: while reading or writing dashboard configuration, use ha_config_get_dashboard(include_screenshot=True) or ha_config_set_dashboard(return_screenshot=True) for a single workflow. Use it for repeatable visual checks, including ordered mobile, tablet, and desktop captures. Puppet reports image bytes but does not confirm that the frontend accepted a requested theme or language; structured metadata therefore records the values sent to the engine. To change the Puppet engine app (add-on) itself (keep_browser_open, restart), use ha_manage_app.
Parameters
dashboard_path-Legacy Lovelace frontend path to render, e.g. 'lovelace/0' (default dashboard, first view), 'lovelace-home/kitchen', or 'my-dashboard'. Leading slash optional. Prefer url_path + view_path for a stable named view. Mutually exclusive with url_path.(str | None)= nullurl_path-Stable dashboard URL path, e.g. 'lovelace-home' or 'default'. Use with view_path instead of dashboard_path.(str | None)= nullview_path-Stable Lovelace views[].path value to render. Requires url_path.(str | None)= nullwidth-Viewport width in px.(int)ge: 64, le: 4096= 1280height-Viewport height in px (64-4096), or 'auto' for content height. full_page=True is a compatibility alias for 'auto'.(int | Literal['auto'])= 800viewport_presets-Render one or more named responsive viewports in this order: mobile (390x844), tablet (768x1024), desktop (1280x800). Overrides width/height.(list[ViewportPreset] | None)min_length: 1, max_length: 3= nullorientation-Optional responsive orientation. Swaps viewport dimensions when needed; this does not rotate final pixels.(Orientation | None)= nullzoom-Page zoom factor (1.0 = 100%).(float)ge: 0.1, le: 5= 1wait_ms-Extra render-settle time (ms) after the dashboard reports loaded. Raise it if a chart card (ApexCharts, mini-graph, history-graph) comes back blank.(int)ge: 0, le: 30000= 2500full_page-Capture the whole scrollable dashboard instead of just the viewport (use when content runs below the fold). Uses Puppet's native auto-height capture (currently capped at 4000 px); raise wait_ms for lazy cards.(bool)= falsetheme-Installed Home Assistant frontend theme name, applied to this render. The engine persists this on the engine account's profile; this tool reports the change in warnings but does not undo it (see ha_manage_theme action='set_engine_theme').(str | None)= nulldark_mode-Render the requested theme in dark mode, applied to this render. The engine persists this on the engine account's profile; this tool reports the change in warnings but does not undo it (see ha_manage_theme action='set_engine_theme').(bool)= falselanguage-Frontend language code, e.g. 'en' or 'de'.(str | None)= nullimage_format-Image format: png, jpeg, webp, or bmp.(ScreenshotFormat)= "png"render_timeout_seconds-HTTP render timeout in seconds.(float)ge: 1, le: 300= 60Dashboards
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_pathrequired-Dashboard URL path or internal ID to delete (e.g., 'my-dashboard' or 'my_dashboard'). Both forms are accepted.(str)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_idrequired-Resource ID to delete. Get from ha_config_list_dashboard_resources()(str)Description
Get dashboard info - list all dashboards, get config, or search for cards. MODE 1 — List: list_only=True Lists every dashboard's metadata (url_path, title, icon), storage and YAML alike (metadata only — bodies are never included here). 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, bypassing the cache. 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. Pass view_path=<views[].path> to return ONLY that view: the response then carries `view` + `view_index` instead of `config`, keeping the payload small on multi-view dashboards. `config_hash` still covers the FULL config, so a follow-up ha_config_set_dashboard(python_transform=...) addressing config['views'][view_index] validates unchanged. An unknown view_path errors and lists the available view paths. include_screenshot=True also returns rendered image(s) of the dashboard (beta feature); when you only need the render and not the config, use the dedicated ha_get_dashboard_screenshot tool instead. MODE 4 — Search all: mode="search" with query=<entity_id or text> Answers "which dashboards contain this entity/card" by walking every storage-mode dashboard's views/cards/sections for the query substring. Each match names the url_path, view, card_path, card_type, and the matched field/value. Takes precedence over the other modes (list_only / entity_id / card_type / heading are ignored when mode="search"). YAML-mode dashboards are never searched on either path — the component walk skips them in-process and the component-less legacy walk skips any row tagged mode="yaml" — because HA resolves `!secret` when loading a YAML Lovelace config, so searching one could surface resolved secrets. On installs without the ha_mcp_tools component, the default (unnamed) dashboard is also not searched — only dashboards with a url_path are. 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") - Get one view only: ha_config_get_dashboard(url_path="lovelace-mobile", view_path="office") - 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-Dashboard URL path (e.g., 'lovelace-home'). Use 'default' for default dashboard. If omitted with list_only=True, lists all dashboards.(str | None)= nulllist_only-If True, list all dashboards instead of getting config. When True, url_path is ignored.(bool)= falseforce_reload-Force reload from storage (bypass cache). Not applicable in search mode, which always reads fresh config.(bool)= falseentity_id-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).(str | None)= nullcard_type-Find cards by type, e.g. 'tile', 'button', 'heading'. When provided, activates search mode.(str | None)= nullheading-Find cards by heading/title text (case-insensitive partial match). When provided, activates search mode.(str | None)= nullinclude_config-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. Config bodies are surfaced only for dashboards provably in storage mode; for a YAML or unconfirmed dashboard the bodies are withheld (they may carry resolved !secret values) and the response says so, with match locations still reported. Ignored outside search mode.falseinclude_screenshot-Get mode only: also return rendered image(s) 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. When you already have the config and only need the render, use the dedicated ha_get_dashboard_screenshot tool (registered when the same beta feature is on) — it returns images without echoing the config.(bool)= falseview_path-Get mode: return ONLY the view whose Lovelace views[].path matches (response carries 'view' + 'view_index' instead of the full 'config') — use this to keep multi-view dashboards from blowing up the response when you only need one view. Does not require any beta feature. With include_screenshot, also selects the view to render. Ignored in list/search mode. Omit for the full config.(str | None)= nullmode-Set to 'search' for a CROSS-dashboard search: which dashboards contain a given entity_id or text (requires query). Leave unset for the default list/get/single-dashboard-search behavior selected by list_only / entity_id / card_type / heading.(Literal['search'] | None)= nullquery-With mode='search': the entity_id or substring to find across all storage-mode dashboards. Ignored otherwise.(str | None)= nullDescription
List Lovelace dashboard resources (custom cards, themes, CSS/JS). Returns one page of registered resources; `total_count` and `has_more` report the full set. 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. `inline_count` and `by_type` summarise every resource, not just this page. 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: - First page of resources: ha_config_list_dashboard_resources() - Next page: ha_config_list_dashboard_resources(offset=100) - 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-Include full decoded content for inline resources. Default False to save tokens (shows 150-char preview instead).(bool)= falselimit-Max resources to return per page (default: 100)(int)ge: 1, le: 500= 100offset-Number of resources to skip for pagination (default: 0)(int)ge: 0= 0Description
Create or update a Home Assistant dashboard. MUST call ha_get_skill_guide OR refer to your locally installed skills first. Creates a new dashboard or updates an existing one with the provided configuration. Supports full config replacement, Python transformation, or structured patch edits. 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: - patch: Edit known paths with literal values using add/remove/replace/test and config_hash. Example: patch=[{"op": "replace", "path": "/views/0/title", "value": "Home"}]. Append with /views/0/cards/-; escape ~ as ~0 and / as ~1 in path keys. move/copy are unsupported. See the full patch guide: https://github.com/homeassistant-ai/ha-mcp/blob/master/docs/dashboard-edits.md - python_transform: Use loops or pattern-based changes across cards and views. - 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. TIP: return_screenshot=True bundles rendered image(s) with the write result (beta feature); for visual re-checks after the write, use the dedicated ha_get_dashboard_screenshot tool instead of re-sending config. PYTHON TRANSFORM EXAMPLES: - 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: title/icon/require_admin/show_in_sidebar can be updated in metadata-only calls or alongside a full config replacement. For python_transform or patch, update metadata in a separate call; combining it with patch is rejected. 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_pathrequired-Dashboard URL path (e.g., 'my-dashboard'). Use 'default' or 'lovelace' for the default dashboard. New dashboards must use a hyphenated path.(str)config-Dashboard configuration with views and cards. Omit or set to None to create dashboard without initial config. Mutually exclusive with python_transform and patch.(dict[str, Any] | None)= nullpython_transform-Python expression to transform existing dashboard config. Mutually exclusive with config and patch. 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]" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire listnullconfig_hash-Config hash from ha_config_get_dashboard for optimistic locking. REQUIRED for python_transform and patch (validates dashboard unchanged). Optional for config (validates before full replacement if provided).(str | None)= nulltitle-Dashboard display name shown in sidebar(str | None)= nullicon-MDI icon name (e.g., 'mdi:home', 'mdi:cellphone'). Defaults to 'mdi:view-dashboard'(str | None)= nullrequire_admin-Restrict dashboard to admin users only. For existing dashboards, only updated when explicitly provided.(bool | None)= nullshow_in_sidebar-Show dashboard in sidebar navigation. For existing dashboards, only updated when explicitly provided.(bool | None)= nullMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullreturn_screenshot-After writing, also return rendered image(s) 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. For visual re-checks after the write (no config round-trip), use the dedicated ha_get_dashboard_screenshot tool instead.(bool)= falseview_path-With return_screenshot: stable Lovelace views[].path to render.(str | None)= nullpatch-Structured dashboard edits: up to 100 JSON Patch add, remove, replace or test operations using RFC 6901 paths. Use /- to append to an array; escape ~ as ~0 and / as ~1 in keys. Requires config_hash. Mutually exclusive with config and python_transform. Update title/icon/require_admin/show_in_sidebar in a separate call. Strings in value are preserved literally.(list[dict[str, Any]] | None)= nullDescription
Create or update a dashboard resource (inline code or external URL). Provide exactly one of: - content: Inline JavaScript or CSS code (embedded in the resource URL as a data: URI — no file storage or external hosting involved) - url: External resource URL (/local/, /hacsfiles/, or https://...) INLINE MODE (content=): - Custom card code written inline - CSS styling for dashboards - Self-contained files up to ~128KB - URLs are deterministic (same content = same URL) - Content must be self-contained: a data: URI has no base URL, so relative imports inside a module and relative url() references inside CSS cannot resolve (use fully-qualified URLs instead) - If Home Assistant is behind a reverse proxy that injects a Content-Security-Policy without 'data:' in script-src/style-src, the browser blocks these resources: this call still succeeds and the card simply never renders. Register the code as a file and use url='/local/...' on such a deployment. (HA itself ships no CSP.) - 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-JavaScript or CSS code to host inline (max ~128KB). The code is embedded directly in the resource URL as a data: URI - no file storage or external hosting involved. Mutually exclusive with url. Supports 'module' and 'css' types only.(str | None)= nullurl-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.(str | None)= nullresource_type-Resource type: 'module' for ES6 modules (modern cards, default), 'js' for legacy JavaScript (url mode only), 'css' for stylesheets(Literal['module', 'js', 'css'])= "module"resource_id-Resource ID to update. If omitted, creates a new resource. Get IDs from ha_config_list_dashboard_resources()(str | None)= nullDeveloper
Description
Manage the running ha-mcp server itself (developer mode). When NOT to use: to restart Home Assistant use ha_restart; to update Home Assistant Apps (add-ons) or HACS packages use ha_manage_app / ha_manage_hacs. When to use: development/testing workflows — inspecting how this server is deployed, switching the in-process (custom component) server to another release channel or an arbitrary pip spec such as a PR tarball, and restarting the server so config or code changes take effect. Caveats: update_source changes ONLY the ha_mcp_tools custom component's separate in-process server entry — it never updates the app (add-on), Docker, standalone, or PyPI server that may be serving this connection (update those via ha_manage_app / docker pull / pip). In embedded mode that entry IS this server, so the update self-interrupts; elsewhere this connection is untouched and keeps its current version. Success means the entry's options were applied — the component then reinstalls in the background, which can take minutes and can still fail (check HA logs). update_source requires the component's in-process server entry to exist. restart interrupts this MCP connection in embedded and app deployments (the reply arrives just before the server goes down) and supports those two deployments only (standalone processes must be restarted externally). list_pending/approve/deny are exempt from policy gating (gating queue management would deadlock approvals), so approve/deny instead require the separate 'dev_tools_security_policy_access' setting — off by default, because gated-call errors carry the approval token and an agent could otherwise self-approve its own gated calls. Dev mode is a trusted-operator feature; leave it off otherwise. EXAMPLES: ha_dev_manage_server("info") ha_dev_manage_server("update_source", channel="dev") ha_dev_manage_server("update_source", pip_spec="https://github.com/homeassistant-ai/ha-mcp/archive/refs/pull/1234/head.tar.gz") ha_dev_manage_server("update_source", pip_spec="clear", channel="stable") ha_dev_manage_server("restart") ha_dev_manage_server("list_pending") ha_dev_manage_server("approve", token="abc123")
Parameters
actionrequired-info: deployment/version report; update_source: point the ha_mcp_tools component's separate in-process server at a channel or pip spec and reinstall it (never changes the server serving this connection, unless embedded); restart: restart this server; list_pending: list tool calls blocked on a security-policy approval; approve / deny: decide one blocked call by token(Literal['info', 'update_source', 'restart', 'list_pending', 'approve', 'deny'])channel-Release channel for update_source: 'stable' or 'dev'(str | None)= nullpip_spec-Explicit pip requirement for update_source — a version pin (ha-mcp==7.9.0) or a GitHub tarball URL such as https://github.com/homeassistant-ai/ha-mcp/archive/refs/pull/<PR>/head.tar.gz. The bare name 'clear' (case-insensitive) is reserved: it clears the override and falls back to the release channel instead of being treated as a requirement (pin a specific version, e.g. 'clear==2.0.0', if you genuinely need that PyPI package). An empty string also clears but some MCP clients mangle it in transit — prefer 'clear'.(str | None)= nulltoken-Approval token (required for approve/deny)(str | None)= nullDescription
Manage ha-mcp server settings and the Tools/Policies/Backups surfaces (developer mode). Drives everything the web settings UI can change: the Server Settings matrix (list/set/reset), the Tools tab (enable/disable/pin, LLM-API exposure, and the per-tool security gate), the Tool Security Policies editor (get_policy/set_policy), and the auto-backup config (get_backup_config/set_backup_config). Use ha_dev_manage_server for the live approval queue and to restart. When NOT to use: for HA entity/automation configuration use the ha_config_* tools. Caveats: enable/disable/pin and most server settings take effect only after a restart (ha_dev_manage_server action="restart"); LLM-API exposure, security gates, and policy edits apply live. Env-pinned settings/tools are read-only until the env var is unset. These actions can flip security-sensitive state — treat with the same care as editing the web UI. The policy-override surfaces (set_policy, set_tool with gated=, and set/reset of enable_tool_security_policies) additionally require the 'dev_tools_security_policy_access' setting, which is off by default; reads are always available. That setting itself is never writable by these tools — change it in the web settings UI or via HAMCP_DEV_SECURITY_POLICY_ACCESS. EXAMPLES: ha_dev_manage_settings("list_tools") ha_dev_manage_settings("set_tool", tool="ha_write_file", state="disabled") ha_dev_manage_settings("set_tool", tool="ha_call_service", gated=True) ha_dev_manage_settings("get_policy") ha_dev_manage_settings("set_backup_config", backup={"enable_auto_backup": False})
Parameters
actionrequired-Server-settings matrix: list / set / reset. Tools tab: list_tools / set_tool (enable-disable-pin, LLM-API, security gate). Security policies: get_policy / set_policy. Auto-backup config: get_backup_config / set_backup_config.(Literal['list', 'set', 'reset', 'list_tools', 'set_tool', 'get_policy', 'set_policy', 'get_backup_config', 'set_backup_config'])setting-Setting name (required for set/reset)(str | None)= nullvalue-New value (required for set)(bool | int | float | str | None)= nulltool-Tool name (required for set_tool)(str | None)= nullstate-set_tool: enable, disable, or pin the tool(Literal['enabled', 'disabled', 'pinned'] | None)= nullllm_api-set_tool: expose the tool to HA conversation agents (effective only on the embedded custom-component server)(bool | None)= nullgated-set_tool: require user approval before every call to this tool (adds/removes an unconditional security-policy rule)(bool | None)= nullpolicy-set_policy: the full policy object {wait_seconds, approval_ttl_minutes, rules, version, schema_version}(dict[str, Any] | None)= nullexpected_version-set_policy: the version from your last get_policy, for optimistic-concurrency safety (else the policy's own version field is used)(int | None)= nullbackup-set_backup_config: {field: value} of auto-backup settings to change (see get_backup_config for field names)(dict[str, Any] | None)= nullDevice 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. **Matter:** integration="matter". Returns node_diagnostics (network type, reachability, IPs, fabrics). For management use ha_manage_radio.
Parameters
device_id-Device ID to retrieve details for. If omitted, lists devices.(str | None)= nullentity_id-Entity ID to find the associated device for (e.g., 'light.living_room')(str | None)= nullintegration-Filter devices by integration: 'zha', 'zigbee2mqtt', 'zwave_js', 'mqtt', 'hue', etc.(str | None)= nullarea_id-Filter devices by area ID (e.g., 'living_room')(str | None)= nullmanufacturer-Filter devices by manufacturer name (e.g., 'Philips')(str | None)= nulllimit-Max devices to return per page in list mode (default: 50)(int)ge: 1, le: 200= 50offset-Number of devices to skip for pagination (default: 0)(int)ge: 0= 0detail_level-'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.(Literal['summary', 'full'])= "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_idrequired-Device ID to remove from the registry(str)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_idrequired-Device ID to update(str)name-New display name for the device (sets name_by_user)(str | None)= nullarea_id-Area/room ID to assign the device to. Use empty string '' to unassign.(str | None)= nulldisabled_by-Set to 'user' to disable, or None/empty string to enable(str | None)= nulllabels-Labels to assign to the device (replaces existing labels)(str | list[str] | None)= nullEnergy
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
moderequired-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.(Literal['get', 'set', 'add_device', 'remove_device', 'add_source'])config-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.(dict[str, Any] | None)= nullconfig_hash-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.(str | dict[_PrefsKey, str] | None)= nulldry_run-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 again…
st 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.falsestat_consumption-Statistic entity_id for mode='add_device' / 'remove_device' (e.g. 'sensor.fridge_energy'). Required for those modes; ignored otherwise.(str | None)= nullname-Optional display name for mode='add_device'. Only used when adding a new device entry; ignored otherwise.(str | None)= nullincluded_in_stat-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.(str | None)= nullwater-If True, mode='add_device' / 'remove_device' targets 'device_consumption_water' instead of 'device_consumption'. Default False.(bool)= falsesource-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.nullEntity 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. RESOLVER MODE: Pass unique_id (instead of entity_id) to resolve a stable integration unique_id to its entity_id(s). Since the registry's unique key is (domain, platform, unique_id), the same unique_id can match multiple platforms — all matches are returned in entity_entries with a `matches` count. Narrow with domain/platform. Resolver reads as_partial_dict, so aliases and the device_class override come back as defaults ([]/null). 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 (a null entry = the entity's own name) - 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 Resolved-name enrichment (present only when the ha_mcp_tools component advertises it; otherwise these keys are absent): - area: Assigned area NAME (device-inherited when the entity has none; resolves area_id above) - floor: Floor NAME of the assigned area - label_names: Assigned label NAMES (resolves the label ids in labels) Resolved label names live under label_names HERE (this tool's base `labels` already carries the label ids); ha_search result_fields and ha_get_entity_exposure instead emit the resolved names under `labels`.
Parameters
entity_id-Entity ID or list of entity IDs to retrieve (e.g., 'sensor.temperature' or ['light.living_room', 'switch.porch']). Mutually exclusive with unique_id.(str | list[str] | None)= nullunique_id-Resolve a stable integration unique_id to its entity_id(s) (entity_id is mutable, unique_id is not). Mutually exclusive with entity_id. Optionally narrow with domain/platform.(str | None)= nulldomain-Resolver filter (unique_id mode only): restrict matches to this entity domain, e.g. 'sensor'.(str | None)= nullplatform-Resolver filter (unique_id mode only): restrict matches to this integration platform, e.g. 'hue'.(str | None)= nullDescription
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 When the ha_mcp_tools component advertises the exposure capability, each record is additively enriched with the entity's name/area so no second ha_search is needed to identify it: friendly_name, domain, area, floor, and labels (plus state for entities that have one) on a single-entity lookup, and a parallel entity_info map keyed by entity_id when listing. These fields are absent when the component is unavailable.
Parameters
entity_id-Entity ID to check exposure settings for. If omitted, lists all entities with exposure settings.(str | None)= nullassistant-Filter by assistant: 'conversation', 'cloud.alexa', or 'cloud.google_assistant'. If not specified, returns all.(str | None)= nullDescription
Remove one or more entities 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 BULK MODE: Pass a list of entity IDs to remove up to 100 at once — handy for clearing the restored=true orphans an integration leaves behind after its filters change. Removals run sequentially and return: {removed: [...], skipped: [...], errors: [{entity_id, code, message}]} where skipped = ids already absent (not-found is idempotent, not an error). Bulk mode is NOT auto-backed-up (the snapshot is single-entity); single-id removal still is. EXAMPLES: - Remove orphaned sensor: ha_remove_entity("sensor.old_temperature") - Remove stale helper entry: ha_remove_entity("input_boolean.deleted_helper") - Bulk cleanup: ha_remove_entity(["sensor.orphan_1", "sensor.orphan_2"]) 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_idrequired-Entity ID, or a list of entity IDs, to remove from the entity registry (e.g., 'sensor.old_temperature'). Permanently removes the registration(s).(str | list[str])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, use_entity_name_alias, 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_idrequired-Entity ID or list of entity IDs to update. Bulk operations (list) only support labels, expose_to, and categories parameters.(str | list[str])area_id-Area/room ID to assign the entity to. Use empty string '' to unassign from current area. Single entity only.(str | None)= nullname-Display name for the entity. Use empty string '' to remove custom name and revert to default. Single entity only.(str | None)= nullicon-Icon for the entity (e.g., 'mdi:thermometer'). Use empty string '' to remove custom icon. Single entity only.(str | None)= nulldevice_class-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.(str | None)= nulloptions-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.(dict[str, dict[str, Any]] | None)= nullenabled-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.(bool | None)= nullhidden-True to hide the entity from UI, False to show it. Single entity only.(bool | None)= nullaliases-List of voice assistant aliases for the entity (replaces existing aliases). A null entry is the entity's own name (HA's 'use entity name' switch); it is kept automatically unless your list already contains null. To turn that switch off or on, use use_entity_name_alias. Single entity only.(str | list[str | None] | None)= nulluse_entity_name_alias-HA's 'use entity name' voice-alias switch. True keeps the entity's own name answering in Assist, False turns it off so only the aliases match. Omit to leave it as is. Works with or without aliases. Single entity only.(bool | None)= nullcategories-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.(dict[str, str | None] | None)= nulllabels-List of label IDs for the entity. Behavior depends on label_operation parameter. Supports bulk operations.(str | list[str] | None)= nulllabel_operation-How to apply labels: 'set' replaces all labels, 'add' adds to existing, 'remove' removes specified labels.(Literal['set', 'add', 'remove'])= "set"expose_to-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.(dict[str, bool] | None)= nullnew_entity_id-New entity ID to rename to (e.g., 'light.new_name'). Domain must match the original. Single entity only.(str | None)= nullnew_device_name-New display name for the associated device. If provided, both entity and device are updated in one operation. Single entity only.(str | None)= nullFiles
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
pathrequired-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'(str)confirm-Must be True to confirm deletion. This is a safety measure to prevent accidental deletions.(bool)= falseDescription
List files in a directory within the Home Assistant config directory. Lists files in allowed directories (www/, themes/, custom_templates/, dashboards/, blueprints/) 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 - `blueprints/` - Automation/script blueprint sources (read-only) - Your configured `packages/` folder, when `homeassistant: packages:` is set (the folder name you bound, default `packages/`) - 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
pathrequired-Directory path. Relative to the config dir for the built-in allowlist (www/, themes/, custom_templates/, dashboards/, blueprints/). 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'(str)pattern-Optional glob pattern to filter files. Example: '*.css', '*.yaml', '*.js'(str | None)= nullDescription
Read a file from the Home Assistant config directory. General-purpose escape hatch — prefer a dedicated tool when one exists: ha_manage_blueprints(action="get") for a blueprint body, ha_config_get_yaml for a config key, ha_config_get_automation/script/scene for storage-mode items. Reach for ha_read_file only for raw on-disk text those tools don't expose. Reads files from allowed paths within the config directory. Some files have special handling: - `secrets.yaml`: Values are masked for security - `home-assistant.log` / `home-assistant.log.fault`: Limited to tail (last N lines) by default. Prefer ha_get_logs(source='error_log') and ha_get_logs(source='fault_log') over reading these directly. **Allowed Read Paths:** - `configuration.yaml`, `automations.yaml`, `scripts.yaml`, `scenes.yaml` - `secrets.yaml` (values masked) - `packages/*.yaml` - `home-assistant.log`, `home-assistant.log.fault` (tail only) - `www/**`, `themes/**`, `custom_templates/**`, `dashboards/**`, `blueprints/**` - `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 - subtree: Round-trip text of the `yaml_path` key, when that arg is set (null when the key is absent). Comments and HA tags (`!secret`, `!include`) survive as written — a `!secret` is never resolved. **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) # Read just the alert2 block out of a package file result = ha_read_file(path="packages/alert2.yaml", yaml_path="alert2") ```
Parameters
pathrequired-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'(str)tail_lines-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)(int | None)ge: 1, le: 10000= nullyaml_path-Dotted YAML key path (e.g. 'alert2', 'mqtt.sensor'). When set, the response also carries 'subtree': the round-trip text of just that key's value. To look a key up across packages/*.yaml, or to get it as structured data, use ha_config_get_yaml instead.(str | None)= nullDescription
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
pathrequired-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'(str)contentrequired-The content to write to the file.(str)overwrite-Whether to overwrite if file exists. Default is False to prevent accidental overwrites.(bool)= falsecreate_dirs-Whether to create parent directories if they don't exist. Default is True.(bool)= trueGroups
Description
List Home Assistant entity groups with their member entities. Returns one page of groups created via group.set service or YAML configuration; `total_count` and `has_more` report the full set. Each group includes: - 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: - First page of groups: ha_config_list_groups() - Next page: ha_config_list_groups(offset=100) **NOTE:** This returns old-style groups (created via group.set or YAML). Platform-specific groups (light groups, cover groups) are separate entities.
Parameters
limit-Max groups to return per page (default: 100)(int)ge: 1, le: 500= 100offset-Number of groups to skip for pagination (default: 0)(int)ge: 0= 0Description
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_idrequired-Group identifier without 'group.' prefix (e.g., 'living_room_lights')(str)wait-Wait for group to be fully removed before returning. Default: True.(bool)= trueDescription
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_idrequired-Group identifier without 'group.' prefix (e.g., 'living_room_lights')(str)entities-List of entity IDs for the group. Required when creating new group. When updating, replaces all entities (mutually exclusive with add_entities/remove_entities).(list[str] | None)= nullname-Friendly display name for the group(str | None)= nullicon-Material Design Icon (e.g., 'mdi:lightbulb-group')(str | None)= nullall_on-If True, all entities must be on for group to be on (default: False)(bool | None)= nulladd_entities-Add these entities to an existing group (mutually exclusive with entities)(list[str] | None)= nullremove_entities-Remove these entities from an existing group (mutually exclusive with entities)(list[str] | None)= nullwait-Wait for group to be queryable before returning. Default: True. Set to False for bulk operations.(bool)= trueHACS
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
actionrequired-'search' the store, or 'info' for one repository(Literal['search', 'info'])query-Search keyword (action='search')(str)= ""category-Filter by category (action='search')(Literal['integration', 'lovelace', 'theme', 'appdaemon', 'python_script'] | None)= nullinstalled_only-Only return installed repositories (action='search', default: False)(bool)= falsemax_results-Maximum number of results (action='search', default: 10, max: 100)(int)ge: 1, le: 100= 10offset-Results to skip for pagination (action='search', default: 0)(int)ge: 0= 0repository_id-Numeric HACS ID or 'owner/repo' path (action='info')(str | None)= nullDescription
Manage HACS (Home Assistant Community Store) — install/update, remove, add custom repositories, or refresh repository information. Use ``action="download"`` to install or update a repository, ``action="remove"`` to uninstall a downloaded 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``. Use ``action="update_information"`` to run the HACS UI's "Update information" action — a forced re-fetch of one repository's release data from GitHub, so a pending update becomes visible to HACS and its update entity immediately. **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") - Remove: ha_manage_hacs(action="remove", repository_id="owner/repo") - Add a custom repo: ha_manage_hacs(action="add_repository", repository="owner/repo", category="lovelace") - Refresh release data: ha_manage_hacs(action="update_information", repository_id="owner/repo") **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``. Removing an integration deletes its files but the loaded module persists until the next Home Assistant restart — delete its config entries first (``ha_remove_helpers_integrations``). HACS refreshes custom repositories on its own only about every 48 hours, so ``update_information`` is the way to surface a just-published release.
Parameters
actionrequired-'download' to install/update, 'add_repository' to register a custom repo, 'remove' to uninstall a downloaded repo, or 'update_information' to refresh a repository's release data from GitHub(Literal['download', 'add_repository', 'remove', 'update_information'])repository_id-Numeric HACS ID or 'owner/repo' path (action='download' / 'remove' / 'update_information')(str | None)= nullversion-Specific version to install (action='download')(str | None)= nullrepository-GitHub repo 'owner/repo' to add (action='add_repository')(str | None)= nullcategory-Repository category (action='add_repository')(Literal['integration', 'lovelace', 'theme', 'appdaemon', 'python_script'] | None)= nullHelper Entities
Description
List Home Assistant helpers of a specific type with their configurations. Returns one page of helpers; `total_count` and `has_more` report the full set. Each record carries the complete configuration for its helper, including: - id (immutable storage key), entity_id (current — address the helper by this, where available), name (current display name), original_name (creation-time name), icon - Type-specific settings (min/max for input_number, options for input_select, etc.) - Area and label assignments For a helper renamed in the UI, id/original_name keep the storage values while entity_id/name reflect the current entity registry (entity_id is the identifier ha_config_set_helper resolves against, so prefer it over id for a renamed helper). entity_id/original_name are present only for storage-collection helpers matched in the entity registry — types with no backing entity (e.g. tag), and every record when the registry read degrades, carry only id/name (a warning flags the degraded case). 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") - List every helper type at once: ha_config_list_helpers("all") - Next page: ha_config_list_helpers("input_boolean", offset=100) **NOTE:** Storage types list what HA's ``{type}/list`` command returns: the storage-backed helpers (created via UI/API), not the YAML-defined ones. ``person`` is the exception — HA lists its YAML-configured persons alongside the storage ones, so both appear here. Flow-based types (template / group / utility_meter / derivative / etc.) require the ha_mcp_tools custom component (>= 1.1.0) and are served only through it; storage types are listed on all installs. Requesting a flow type without the component returns a COMPONENT_NOT_INSTALLED error. Pass helper_type="all" to enumerate every helper type in a single call. Each record carries its own ``helper_type``. This mode is component-only (there is no single built-in command that lists all types): without the ha_mcp_tools component it returns a COMPONENT_NOT_INSTALLED error rather than a partial or empty list. For detailed helper documentation, use ha_get_skill_guide.
Parameters
helper_typerequired-Helper type to list. Storage types are listed on all installs; flow-based types require the ha_mcp_tools custom component. Pass 'all' to list every helper type in one call (also requires the ha_mcp_tools component).(Literal['input_button', 'input_boolean', 'input_select', 'input_number', 'input_text', 'input_datetime', 'counter', 'timer', 'schedule', 'zone', 'person', 'tag', 'all'] | SUPPORTED_HELPERS)limit-Max helpers to return per page (default: 100)(int)ge: 1, le: 500= 100offset-Number of helpers to skip for pagination (default: 0)(int)ge: 0= 0Description
Create or update Home Assistant helper entities and config subentries (30 types, unified interface). MUST call ha_get_skill_guide OR refer to your locally installed skills 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, history_stats, mold_indicator. Note: `tod` is the purpose-built "is-current-time-in-range" indicator (supports cross-midnight ranges, unlike `schedule`). Note: `otp` is a helper in the HA UI but is not offered here — its flow requires a live TOTP code. Create it with ha_set_integration(domain="otp"), as with any other helper-domain flow outside this list. 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). Flow-helper and config subentry updates behave the same way: a field omitted from `config` keeps its current value, and a field set to null is cleared where the schema allows that field to be empty. - 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. - Flows that present more than one menu (e.g. an MQTT device subentry reconfigure looping through its summary menu) take `next_step_id` as a LIST of successive selections, consumed one per menu encounter. 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_typerequired-Type of helper entity to create or update(Literal['counter', 'config_subentry', 'derivative', 'filter', 'generic_hygrostat', 'generic_thermostat', 'group', 'history_stats', 'input_boolean', 'input_button', 'input_datetime', 'input_number', 'input_select', 'input_text', 'integration', 'min_max', 'mold_indicator', 'person', 'random', 'schedule', 'statistics', 'switch_as_x', 'tag', 'template', 'threshold', 'timer', 'tod', 'trend', 'utility_meter', 'zone'])name-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.(str | None)= nullhelper_id-REQUIRED when updating an existing helper. Bare ID ('my_button') or full entity ID ('input_button.my_button'). Omit to create a new helper.(str | None)= nullentry_id-Parent config entry ID when helper_type='config_subentry'. Use ha_get_integration() to find entry IDs.(str | None)= nullsubentry_type-Integration-defined subentry type when helper_type='config_subentry'.(str | None)= nullsubentry_id-Existing config subentry ID to reconfigure when helper_type='config_subentry'. Omit to create.(str | None)= nullshow_advanced_options-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.(bool)= falseicon-Material Design Icon (e.g., 'mdi:bell', 'mdi:toggle-switch')(str | None)= nullarea_id-Area/room ID to assign the helper to(str | None)= nulllabels-Labels to categorize the helper(str | list[str] | None)= nullmin_value-Minimum value (input_number/counter) or minimum length (input_text). Also accepts shorthand 'min'.(float | None)= nullmax_value-Maximum value (input_number/counter) or maximum length (input_text). Also accepts shorthand 'max'.(float | None)= nullstep-Step/increment value for input_number or counter(float | None)= nullunit_of_measurement-Unit of measurement for input_number (e.g., '°C', '%', 'W'). Also accepts shorthand 'unit'.(str | None)= nulloptions-List of options for input_select (required for input_select)(str | list[str] | None)= nullinitial-Initial value for applicable helper types. For input_boolean, input_select, input_number, input_text, and input_datetime: setting `initial` — even to false/0 — disables last-state restore and forces that value on every HA restart; omit unless you want the helper to reset to that value on every restart instead of restoring its last state. For counter, `initial` is just the starting value — restore-on-restart is controlled separately by `restore` (default True).(str | int | None)= nullmode-Display mode: 'box'/'slider' for input_number, 'text'/'password' for input_text(str | None)= nullhas_date-Include date component for input_datetime(bool | None)= nullhas_time-Include time component for input_datetime(bool | None)= nullrestore-Restore state after restart (counter, timer). Defaults to True for counter, False for timer(bool | None)= nullduration-Default duration for timer in format 'HH:MM:SS' or seconds (e.g., '0:05:00' for 5 minutes)(str | None)= nullmonday-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'}})(list[dict[str, Any]] | None)= nulltuesday-Schedule time ranges for Tuesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nullwednesday-Schedule time ranges for Wednesday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nullthursday-Schedule time ranges for Thursday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nullfriday-Schedule time ranges for Friday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nullsaturday-Schedule time ranges for Saturday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nullsunday-Schedule time ranges for Sunday. List of {'from': 'HH:MM', 'to': 'HH:MM'} dicts. Optional 'data' dict for additional attributes.(list[dict[str, Any]] | None)= nulllatitude-Latitude for zone (required for zone)(float | None)= nulllongitude-Longitude for zone (required for zone)(float | None)= nullradius-Radius in meters for zone (default: 100)(float | None)= nullpassive-Passive zone (won't trigger state changes for person entities)(bool | None)= nulluser_id-User ID to link to person entity(str | None)= nulldevice_trackers-List of device_tracker entity IDs for person(list[str] | None)= nullpicture-Picture URL for person entity(str | None)= nulltag_id-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).(str | None)= nulldescription-Description for tag(str | None)= nullcategory-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.(str | None)= nullconfig-Config dict for flow-based helper types and helper_type='config_subentry' (template, group, utility_meter, derivative, min_max, threshold, i…
ntegration, statistics, trend, random, filter, tod, generic_thermostat, switch_as_x, generic_hygrostat, history_stats, mold_indicator). Ignored for simple helper types. On update it is a patch: a field you omit keeps its current value, and a field set to null is cleared where the schema allows that field to be empty. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave it out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Field set is delivered as data_schema on the first validation error.nullwait-Wait for helper entity to be queryable before returning. Default: True. Set to False for bulk operations.(bool)= trueaction-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).(Literal['create', 'update'] | None)= nullMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullDescription
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 (17, 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, history_stats, mold_indicator. 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. Recovery requires a usable backup and supported restore path.
Parameters
targetrequired-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).(str)helper_type-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.(HelperTypeLiteral | None)= nullsubentry_id-Config subentry ID to remove when helper_type='config_subentry'.(str | None)= nullconfirm-Must be True to confirm removal.(bool)= falsewait-Wait for entity removal. Default: True. Ignored when helper_type=None or helper_type='config_subentry' (no entity poll, require_restart returned).(bool)= trueHistory & 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_idrequired-Automation or script entity_id (e.g., 'automation.motion_light' or 'script.morning_routine')(str)run_id-Specific trace run_id to retrieve detailed trace. Omit to list recent traces.(str | None)= nulllimit-Maximum number of traces to return when listing (default: 10, max: 50).(int)ge: 1, le: 50= 10deduplicate-Deduplicate variables across action steps (default: True). Set to False to include full variables at every step.(bool)= truedetailed-Include extra diagnostic data: logbook entries and context metadata (default: False). Use when standard trace lacks detail for debugging.(bool)= falsesections-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'.(str | None)= nulloffset-Number of traces to skip from the start of the requested order. Use with `limit` to page through stored traces when `total_available > limit`.(int)ge: 0= 0order-Order traces are returned in. 'newest' (default) returns most-recent first; 'oldest' returns chronological-first.(Literal['newest', 'oldest'])= "newest"Description
Get 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. Administrators can optionally enable recorder workload guardrails in Advanced settings. When enabled, oversized entity/time workloads are rejected before the recorder query is issued; narrow the time range or entity list to stay within the budget. Calendar statistics may first read HA's configured timezone so the estimate follows local calendar boundaries. **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="3d", 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_idsrequired-Entity ID(s) to query. Can be a single ID, comma-separated string, or JSON array.(str | list[str])source-Data source: "history" (default) for raw state changes (~10 day retention), or "statistics" for pre-aggregated long-term data (permanent, requires state_class).(Literal['history', 'statistics'])= "history"start_time-Start time: ISO datetime or relative (e.g., '24h', '7d', '30d'). Default: 24h ago for history, 30d ago for statistics(str | None)= nullend_time-End time: ISO datetime. Default: now(str | None)= nullminimal_response-Return only states/timestamps without attributes. Default: true. Ignored when source="statistics"(bool)= truesignificant_changes_only-Filter to significant state changes only. Default: true. Ignored when source="statistics"(bool)= truelimit-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).(int | None)ge: 1, le: 1000= nulloffset-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.(int | None)ge: 0= nullperiod-Aggregation period: "5minute", "hour", "day", "week", "month", "year". Default: "day". Ignored when source="history"(str)= "day"statistic_types-Statistics types: "mean", "min", "max", "sum", "state", "change". Default: all. Ignored when source="history"(str | list[str] | None)= nullorder-Sort order for history entries. "desc" (default): newest first. "asc": oldest first (chronological, as returned by HA API). Ignored when source="statistics".(Literal['asc', 'desc'])= "desc"fields-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.(str | list[str] | None)= nullDescription
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 log text (home-assistant.log on container/pip installs; HA Core's journald stream on Supervisor-backed installs) - "supervisor": App (add-on) container logs (requires slug = app 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) - "fault_log": HA Core's faulthandler crash dump (home-assistant.log.fault). Written only when HA dies from a native fatal signal (segfault, abort, Python fatal error), which never reaches journald or error_log. Empty on a healthy install (crash_recorded=False). Whole crash blocks are ordered (newest first by default) with each block's lines kept in place so the traceback reads correctly; search keeps every block that mentions the term; offset/limit page through the assembled text. Reads through the "HA-MCP File & YAML Tools" entry (component >= 2.2.0). **Prefer source='system' for triage.** It returns HA's own deduplicated system_log entries with counts, first_occurred and full tracebacks; of those only the tracebacks are unrecoverable from the structured error_log summary — they are present in the raw text, so structured=False gets them back. Its counts also run since each error first occurred, while structured error_log counts only what is inside the fetched window (reported as window_start/window_end; every install now reads a capped window). Use error_log with structured=True for entries below system_log's WARNING+ ~50-entry cap, or for the per-component rollup. **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, fault_log); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window; fault_log orders whole crash blocks instead of lines. **Logbook params:** hours_back, entity_id, end_time, compact (default True — strips attribute dicts to save context) **Pagination (logbook + error_log + fault_log):** offset pages deeper; ignored for the other sources. fault_log always reads a fixed window from the end of the file, orders its crash blocks, and pages the assembled text from the start with has_more/next_offset. Logbook responses carry has_more plus a pagination_hint. On error_log, offset counts raw log lines back from the newest entry (journald entries on Supervisor-backed installs), both modes read a bounded window per call — so `level`/`search` filter and `limit` slice within that window only, and window_lines reports the size actually requested — and the response carries has_more with a next_offset to pass back while it stays true. **System/error_log params:** level (ERROR, WARNING, INFO, DEBUG, CRITICAL) **error_log params:** structured, top_n. In structured mode `search` matches the message and logger name only, whereas on the raw path it matches the whole line; `limit`/`order` do not apply, issues are ranked by count, then severity, then recency, and the summary covers a fixed deep window rather than the caller's limit. **Supervisor params:** slug = app slug, e.g. "core_mosquitto" (use ha_get_app() to list installed slugs) **System-service params:** slug = service name. The slug "supervisor" here means the Supervisor service's own logs, NOT an app with that name — the source param disambiguates.
Parameters
source-(Literal['logbook', 'system', 'error_log', 'supervisor', 'system_service', 'logger', 'fault_log'])= "logbook"limit-(int | None)= nullsearch-(str | None)= nullorder-Sort order for time-ordered sources (logbook, system, error_log, supervisor, system_service, fault_log): 'newest' (default) returns most-recent first; 'oldest' returns chronological-first. Ignored for source='logger', and for source='error_log' with structured=True (that summary is ranked by occurrence count, not by time).(Literal['newest', 'oldest'])= "newest"hours_back-(int)ge: 1= 1entity_id-(str | None)= nullend_time-(str | None)= nulloffset-Page deeper into source='logbook', 'error_log' and 'fault_log' (ignored for other sources). On error_log it counts raw log lines back from the newest entry; on fault_log it counts lines from the start of the assembled crash text. Pass the response's 'next_offset' to continue while 'has_more' is true.(int)ge: 0= 0compact-(bool)= truelevel-(str | None)= nullstructured-source='error_log' only. When True, return a deduplicated, component-grouped summary of the log (counted issues sorted by frequency) instead of raw text. Use this on busy instances where the raw log is large enough to exhaust context. Ignored for other sources.(bool)= falsetop_n-Max distinct issues to return when structured=True (default 20, capped at 500). Bounds the response regardless of log size.(int | None)ge: 1= nullslug-(str | None)= nullIntegrations
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'. OPTIONS: ``options`` reflect the entry's persisted values; a field that was never set may be absent (rather than shown at its schema default). Values that match a ``secrets.yaml`` entry are returned as ``"**redacted**"``. Use ``include_schema=True`` to see every editable field and its default/type. Nested option *sections* (e.g. a template helper's ``advanced_options``) are additively flattened one level — each section's leaf keys are copied to the top of ``options`` (mirroring the OptionsFlow-derived read) while the raw nested section is preserved for fidelity, and an existing top-level key is never overwritten. 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_app`` returns Supervisor's lowercase ``"default"`` literal — do not cross-compare.
Parameters
entry_id-Config entry ID to get details for. If omitted, lists all integrations.(str | None)= nullquery-When listing, search by domain or title. Uses exact substring matching by default; set exact_match=False for fuzzy.(str | None)= nulldomain-Filter by integration domain (e.g. 'template', 'group'). When set, includes the full options/configuration for each entry.(str | None)= nullinclude_options-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.(bool)= falseinclude_schema-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.(bool)= falseinclude_subentries-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.(bool)= falseinclude_subentry_schema-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.(bool)= falsesubentry_type-Integration-defined subentry type used with include_subentry_schema=True.(str | None)= nullsubentry_id-Existing subentry ID used with include_subentry_schema=True to inspect a reconfigure flow.(str | None)= nullshow_advanced_options-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.(bool)= falseexact_match-Use exact substring matching for query filter (default: True). Set to False for fuzzy matching when the query may contain typos.(bool)= truelimit-Max entries to return per page in list mode (default: 50)(int)ge: 1, le: 200= 50offset-Number of entries to skip for pagination (default: 0)(int)ge: 0= 0include_diagnostics-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.(bool)= falseinclude_knx_project-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.(bool)= falsedevice_id-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.(str | None)= nulldiagnostics_fields-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.(list[str] | str | None)= nulldiagnostics_truncate_at_bytes-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.(int | None)ge: 1= nulldiagnostics_data_path-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.(str | None)= nulldiagnostics_data_offset-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.(int | None)ge: 0= 0diagnostics_data_limit-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.(int | None)ge: 1= nullDescription
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). To dismiss/ignore a repair, call `ha_call_service(ws_command="repairs/ignore_issue", data={"domain": <domain>, "issue_id": <issue_id>, "ignore": true})`. - "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) - "thread_network": Thread/OpenThread Border Router (OTBR) summary — per border-router channel, extended_pan_id, and border_agent_id (integration-presence + radio-network view, not per-node Thread health) - "matter_network": Matter integration presence summary — config_entry_id, state, and title (per-node health is exposed separately via Matter node diagnostics, not here) - "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)= nullinclude_dismissed_repairs-(bool | None)= falseconfig_entry_id-(str | None)= nulldevice_id-(str | None)= nulldiagnostics_fields-(list[str] | str | None)= nulldiagnostics_truncate_at_bytes-(int | None)ge: 1= nulldiagnostics_data_path-(str | None)= nulldiagnostics_data_offset-(int | None)ge: 0= 0diagnostics_data_limit-(int | None)ge: 1= nullDescription
Manage an integration (config entry): enable/disable, add, update options, or reconfigure. Modes (pick one): - Enable/disable: entry_id + enabled. - Add integration: domain (+ config) — drives the domain's config flow, including menus and multi-step forms. - Update options: entry_id + config — drives the entry's options flow (what the "Configure" button does in the HA UI). Like that dialog it is a patch: omitted fields keep their current values, and a field set to null is cleared where the integration's schema allows that field to be empty. - Reconfigure: entry_id + reconfigure=True + config — drives the existing entry's official reconfigure flow (host, port, credentials). Call it without confirm_token for a read-only preflight; repeat with the token it returns to apply. WHEN NOT TO USE: - Helpers (template, group, utility_meter, ...): use ha_config_set_helper. The exception is `otp`, which is a helper in the HA UI but is created HERE via domain="otp" — its flow needs a live TOTP code, so ha_config_set_helper deliberately omits it. - Config subentries: use ha_config_set_helper(helper_type='config_subentry'). - Removing an entry: use ha_remove_helpers_integrations. Use ha_get_integration() to find entry IDs, and ha_get_integration(entry_id=..., include_schema=True) to inspect the options fields before an update. Its supports_reconfigure field tells you whether an entry qualifies for reconfigure=True; only integrations implementing async_step_reconfigure do. Caveats: adding an integration runs its config flow exactly as the HA UI would (may pair devices, scan the network, create entities). Flows requiring a browser step (OAuth) or an asynchronous provider step error out at that step with a structured error instead of completing. Reconfigure edits the settings a live integration connects with: a wrong host or credential takes it offline, and there is no automatic rollback — the returned rollback metadata describes repeating the official flow by hand with the previous values, which this tool cannot read back. The preflight does not validate config keys against the integration's form; wrong field names surface on the confirm call. EXAMPLES: - Disable: ha_set_integration(entry_id="abc123", enabled=False) - Add: ha_set_integration(domain="workday", config={"name": "Workday"}) - Update options: ha_set_integration(entry_id="abc123", config={"scan_interval": 30}) - Reconfigure preflight: ha_set_integration(entry_id="abc123", reconfigure=True, config={"host": "10.0.0.5"}) - Reconfigure apply: repeat that call adding confirm_token="sha256:..."
Parameters
entry_id-Config entry ID of an existing integration (enable/disable and options-update modes). Omit when adding via 'domain'.(str | None)= nullenabled-True to enable, False to disable the entry. Requires entry_id; mutually exclusive with 'domain' and 'config'.(bool | None)= nulldomain-Integration domain to add (e.g. 'workday', 'local_calendar') — starts and drives that domain's config flow. Pass the flow's form fields in 'config'.(str | None)= nullconfig-Flow form data. With 'domain': input for the new integration's config flow. With 'entry_id' alone: input for the entry's options flow (updat…
es its options). Updating an existing entry — options or reconfigure — is a patch: a field you omit keeps its current value, and a field set to null is cleared where the integration's schema allows that field to be empty. Multi-step flows consume keys per step. A field two steps declare gets your one value both times; pass step_values={'<step_id>': {'<field>': <value>}} to give a step its own value, or to leave the field out of that step; a LIST of those objects supplies one per encounter when the flow presents a step more than once. Menu steps take 'next_step_id' — a string, or a list of successive selections for flows that present more than one menu (e.g. a menu revisited after each branch, ending in a finish option). The step's data_schema is returned on validation errors so field names can be corrected.nullreconfigure-Use the existing config entry's official reconfigure flow (its connection settings) instead of its options flow. Without confirm_token this is a read-only preflight that returns one.(bool)= falseexpected_device_id-Requires reconfigure=True. Device registry ID the entry must still own, before and after the change.(str | None)= nullexpected_unique_id-Requires reconfigure=True, AND the ha_mcp_tools custom component: Home Assistant does not expose a config entry's unique_id over its API. Without the component this is rejected — anchor on expected_device_id, expected_mac or expected_entity_ids instead.(str | None)= nullexpected_mac-Requires reconfigure=True. MAC or IEEE the entry's device must still report.(str | None)= nullexpected_entity_ids-Requires reconfigure=True. Exact entity IDs that must remain associated with the entry.(list[str] | None)= nullconfirm_token-Requires reconfigure=True. A token from a reconfigure preflight; applies the change. Any token still matching the entry's current state and the same requested config is accepted, so a token stays valid while nothing moves.(str | None)= nullLabels & 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
scoperequired-Domain scope for categories (e.g., 'automation', 'script', 'scene', 'helpers').(str)category_id-ID of the category to retrieve. If omitted, lists all categories for the scope.(str | None)= nullDescription
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, ha_set_device(labels=[...]) for devices, or ha_set_area_or_floor(kind="area", labels=[...]) for areas.
Parameters
label_id-ID of the label to retrieve. If omitted, lists all labels.(str | None)= nullDescription
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
scoperequired-Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').(str)category_idrequired-ID of the category to delete(str)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_idrequired-ID of the label to delete(str)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
namerequired-Display name for the category(str)scoperequired-Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').(str)category_id-Category ID for updates. If not provided, creates a new category.(str | None)= nullicon-Material Design Icon (e.g., 'mdi:tag', 'mdi:label')(str | None)= nullDescription
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") - Create and apply to areas: ha_config_set_label("Site Home", areas=["kitchen", "living_room"]) After creating a label, use ha_set_entity(labels=["label_id"]) to assign it to entities, ha_set_device(labels=["label_id"]) for devices, or ha_set_area_or_floor(kind="area", labels=["label_id"]) for areas (replaces the area's set). Pass areas=["kitchen"] here to add the label onto those areas without replacing others.
Parameters
namerequired-Display name for the label(str)label_id-Label ID for updates. If not provided, creates a new label.(str | None)= nullcolor-Color for the label (e.g., 'red', 'blue', 'green', or hex like '#FF5733')(str | None)= nullicon-Material Design Icon (e.g., 'mdi:tag', 'mdi:label')(str | None)= nulldescription-Description of the label's purpose(str | None)= nullareas-Area IDs to apply this label to (adds the label without removing existing ones). Omit to leave area assignments unchanged; an empty list is a no-op (assigns nothing and removes nothing). To clear an area's labels use ha_set_area_or_floor(kind='area', labels=[]).(str | list[str] | None)= nullMatter
Description
Manage Home Assistant radios — Z-Wave, Zigbee, Matter, and Thread. For read-only inspection prefer ha_get_device / ha_get_system_health, which mirror the 'diagnostics' and 'network_status' actions; use this tool for writes and the active 'ping' probe (unique to this tool). Write actions perform inclusion/commissioning, removal, healing, reconfiguration, firmware updates and credential provisioning. Caveats: destructive actions (e.g. remove_device, network restore, change_channel, hard_reset, remove_fabric) require confirm=True. Long-running actions (inclusion, rebuild routes, firmware) start the operation and return immediately with long_running=true; completion happens out-of-band. Interactive Z-Wave S2 secure inclusion (read-the- PIN pairing) is not scriptable — use SmartStart/QR provisioning here or the HA UI.
Parameters
radiorequired-Which radio to manage.(Literal['zwave', 'zigbee', 'matter', 'thread'])actionrequired-Operation to perform. Actions vary per radio; an unknown action returns the supported list for that radio. Common: 'diagnostics', 'network_status', 'ping', 'add'/'commission', 'remove_device', 'reinterview'/'reconfigure', 'firmware_update'.(str)device_id-Target device (node) for node-scoped actions.(str | None)= nullentity_id-Resolve the device from this entity for node-scoped actions.(str | None)= nullparams-Action-specific parameters (e.g. code, pin, channel, property, value). An unknown action returns that radio's supported action list with one-line summaries.(dict[str, Any] | None)= nullconfirm-Required (True) to run destructive actions.(bool)= falseScenes
Description
Get a scene's complete configuration, or list and search scenes without scene_id. Use ha_search for cross-domain discovery and dependency searches. For ordinary scene discovery, use this tool and pass a returned scene_id back to retrieve the complete entities dict and config_hash for editing. Listing returns compact metadata. Integration-managed scenes have no editable storage config or scene_id. Optional content search reads full storage bodies; partial results explicitly report unread configs and are not exhaustive. EXAMPLES: - Get scene: ha_config_get_scene("movie_night") - Get scene: ha_config_get_scene("bedroom_dim") - Find scenes: ha_config_get_scene(query="movie") - Find attribute values: ha_config_get_scene(query="rainbow", search_in_config=True) 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-Scene identifier; omit to list or search scenes(str | None)= nullquery-Filter scene names or IDs(str | None)= nullsearch_in_config-Also search full stored scene attribute values within a bounded scan(bool)= falselimit-Maximum scenes per page(int)ge: 1, le: 100= 20offset-Pagination offset(int)ge: 0= 0Description
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_idrequired-Scene identifier to delete (e.g., 'old_scene')(str)wait-Wait for scene to be fully removed before returning. Default: True.(bool)= trueDescription
Create or update a Home Assistant scene. MUST call ha_get_skill_guide OR refer to your locally installed skills 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_idrequired-Scene identifier (e.g., 'movie_night')(str)config-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.(dict[str, Any] | None)= nullpython_transform-Python expression to transform existing scene config. Mutually exclusive with config. Requires config_hash for validation. WARNING: Expressi…
ons 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']" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire listnullconfig_hash-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).(str | None)= nullcategory-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.(str | None)= nullwait-Wait for scene to be queryable before returning. Default: True. Set to False for bulk operations.(bool)= trueMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullScripts
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_idrequired-Script identifier — bare storage key ('morning_routine') or entity_id form ('script.morning_routine'); a leading 'script.' prefix is stripped before lookup.(str)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_idrequired-Script identifier to delete — bare storage key ('old_script') or entity_id form ('script.old_script'); a leading 'script.' prefix is stripped before lookup.(str)wait-Wait for script to be fully removed before returning. Default: True.(bool)= trueDescription
Create or update a Home Assistant script. MUST call ha_get_skill_guide OR refer to your locally installed skills 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 three modes: full config replacement, Python transformation, or take_control_of_blueprint (see below). WHEN TO USE WHICH MODE: - python_transform: RECOMMENDED for edits to existing scripts. Surgical updates. - config: Use for creating new scripts or full restructures. - take_control_of_blueprint: converts a blueprint-backed script into a standalone one. Takes no config of its own. 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" } } }) TAKE CONTROL OF A BLUEPRINT SCRIPT: take_control_of_blueprint=True converts a blueprint-backed script into a standalone one — the UI's "Take control". The blueprint is rendered with the script's CURRENT inputs and the result is saved over the same script, which keeps its script_id, alias and description but gains its own sequence and loses 'use_blueprint'. ha_config_set_script( script_id="notification_script", take_control_of_blueprint=True, ) This is one-way: the script is no longer linked to the blueprint, so later blueprint edits stop reaching it. To change an input value, update 'use_blueprint.input' instead (see the example above) — that keeps the link. Taking control does NOT free the blueprint: Home Assistant goes on counting a converted script as a user of it, so deleting that blueprint stays refused until the script itself is removed. To see the rendering WITHOUT writing anything, call ha_manage_blueprints(action="substitute", domain="script", path=..., input=...). ha_manage_blueprints also lists, imports, saves and deletes blueprints, and action="get" reports which scripts use one. 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_idrequired-Script identifier — bare storage key ('morning_routine') or entity_id form ('script.morning_routine'); a leading 'script.' prefix is stripped before lookup.(str)config-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.(dict[str, Any] | None)= nullpython_transform-Python expression to transform existing script config. Mutually exclusive with config. Requires config_hash for validation. WARNING: Express…
ions 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" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire listnullconfig_hash-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).(str | None)= nulltake_control_of_blueprint-Convert a blueprint-backed script into an editable standalone one -- the UI's "Take control". Renders the blueprint with its current inputs…
and saves the result over the same script, which then has its own sequence and no 'use_blueprint'. Mutually exclusive with config and python_transform. Irreversible: the link to the blueprint is gone afterwards, so edit inputs instead if you only want to change a value. Does NOT free the blueprint: Home Assistant keeps counting the converted script as a user, so deleting that blueprint stays refused until the script is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute", domain="script").falsecategory-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.(str | None)= nullwait-Wait for script to be queryable before returning. Default: True. Set to False for bulk operations.(bool)= trueMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullSearch & 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). Requests composed only of system_info, notification, repair, or server metadata fields also skip the unrelated state, service, and registry reads. Do not use this tool to inspect a known entity or a narrow set of entities. Use ha_get_state for one entity, ha_get_entity for registry metadata, or ha_search with a domain or area filter. An unprojected overview collects system-wide state, service, and registry data and can be expensive on large Home Assistant installations. 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 standalone HTTP / Docker modes, when an HTTP settings prefix is advertised, there is no sidecar URL file and the server can't know its externally reachable host. The response instead carries a ``settings_url_hint`` string telling the user where the page is mounted and how to find or construct the full URL. 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-'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(Literal['minimal', 'standard', 'full'])= "minimal"domains-Filter to specific domains (e.g. 'light,sensor' or ['light','sensor']). None = all domains. Useful to avoid context window overload.(str | list[str] | None)= nulllimit-Max total entities across all domains (default: unlimited for minimal, 200 for standard/full). Counts and states always complete. Use with offset for pagination.(int | None)ge: 1= nulloffset-Number of entities to skip for pagination (default: 0)(int)ge: 0= 0max_entities_per_domain-Override default entity cap per domain (minimal=10, standard/full=unlimited). 0 = no limit on entities or states.(int | None)= nullinclude_state-Include state field for entities (None = auto based on level). Full defaults to True.(bool | None)= nullinclude_entity_id-Include entity_id field for entities (None = auto based on level). Full defaults to True.(bool | None)= nullinclude_notifications-Include active persistent notifications (default: True). Set False to skip.(bool | None)= trueinclude_dismissed_repairs-Include user-dismissed/ignored repairs (default: False). Matches the HA Repairs UI which hides dismissed items by default. To dismiss/ignore a repair, call ha_call_service with ws_command="repairs/ignore_issue" and data={"domain": ..., "issue_id": ..., "ignore": true}.(bool | None)= falsefields-Return only the specified top-level response keys to reduce response size (e.g. ["system_info", "domain_stats"]). None = full response (defa…
ult). 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`` (standalone HTTP/Docker 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.nullDescription
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_idrequired-Entity ID or list of entity IDs to retrieve state for (e.g., 'light.kitchen' or ['light.kitchen', 'sensor.temperature'])(str | list[str])fields-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.(str | list[str] | None)= nullattribute_keys-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).(str | list[str] | None)= nullDescription
Search for entities (lights, sensors, switches, climate, etc.) by name, domain, or area — AND inside automation/script/scene/helper/dashboard configurations — in one call. Two surfaces run in parallel and return tagged results: - **entities**: entity-registry matches (entity_id, friendly name, area). Filter with `domain_filter`/`area_filter`/`state_filter`; omit `query` to enumerate a domain, area, or state. - **automations / scripts / scenes / helpers / dashboards**: matches *inside* config definitions — triggers, actions, sequences, scene entity-sets, helper bodies, dashboard cards. Driven by `query`; narrow with `search_types`. Use dedicated get/list tools first for a known resource type, including ha_config_get_scene for scene listing and content search. Use this for broader discovery or deep searches across resource types. For control requests with exclusions such as "except", "excluding", or "but not", include `is_group` and `member_entity_ids` in `result_fields`. Do not control an aggregate whose members include an excluded entity; prefer leaf entities when the exception cannot be verified safely. A withheld member list still returns is_group=true; absence of member_entity_ids must not be interpreted as a leaf entity. When NOT to use: - To read a known entity_id's state: use `ha_get_state` (cheaper). - To inspect one automation/script/scene config by id: use the matching `ha_config_get_*`. - To list installed Apps (add-ons): use `ha_get_app`. Config-body search is skipped when `domain_filter`/`area_filter`/ `state_filter` signal entity-only intent (keeping name lookups off the expensive backend); a `warnings[]` entry names the skip. Repeat without entity filters to search configuration contents too. Explicit legacy `search_types=[...]` calls search configs only and skip entities. Caveats: - `partial: True` means results are NOT exhaustive — a surface raised, or the config-body branch lost data (per-id time budget exhausted, an individual fetch failed, or a helper-type list fetch failed). Empty buckets with `partial: True` mean "search failed", not "no results". The cause is in `partial_reason`, also mirrored into `warnings[]` with an "incomplete results: " prefix. Do not treat a partial response as complete. - `count` is items in this response (post-pagination), not corpus totals — use `entity_total_matches` + `config_total_matches`. - `limit`/`offset` apply per-surface. Flat `has_more`/`next_offset` page the next call (iterate `offset = next_offset`); per-surface `entity_*`/`config_*` variants show which surface still has results. For parameters, schema, and worked examples, see ha_get_skill_guide. Examples: - List sensors in an area: ha_search(domain_filter="sensor", area_filter="Living Room") - Find a light by name: ha_search("kitchen", domain_filter="light") - Find lights safely before an "all except one" control request: ha_search("living room", domain_filter="light", result_fields=["entity_id", "friendly_name", "is_group", "member_entity_ids"]) - Which automations use an entity: ha_search("light.bed_light") - Scenes touching a light: ha_config_get_scene(query="light.kitchen", search_in_config=True) - Narrow the response to the entity bucket: ha_search("kitchen", fields=["entities"]) - All unavailable entities: ha_search(state_filter="unavailable")
Parameters
query-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 dedicated get/list tools first for known resource types; use this for broader discovery or deep searches. Pass the exact entity_id, not a name fragment, when checking what a rename or delete would break: that form reports automations, scripts and scenes referencing it even when their configuration could not be read. Omit `query` to enumerate by `domain_filter`, `area_filter`, and/or `state_filter` alone (registry-listing mode); configuration-body search is skipped in that mode because there is no term to match against.nulldomain_filter-Narrow entity-registry results to a single domain (e.g. 'light', 'sensor'). Does not affect configuration search.(str | None)= nullarea_filter-Narrow entity-registry results to an area (id, name, or alias), an exact floor (id, name, or alias), or an unambiguous close-spelling floor match; a floor match expands to all areas on that floor. Does not affect configuration search.(str | None)= nullsearch_types-Configuration types to include in body search: 'automation', 'script', 'scene', 'helper', 'dashboard'. Explicitly providing this selects configuration-only search and skips entities. Omit it for entity discovery. Default = automation+script+scene+helper. Pass as list or JSON-array string.(str | list[str] | None)= nulllimit-Maximum results per surface (entities, configs). Default: 10.(int)ge: 1= 10offset-Number of results to skip for pagination.(int)ge: 0= 0exact_match-Exact substring matching (default). Set False for fuzzy matching when the query may have typos.(bool)= trueinclude_hidden-Include hidden entities in registry results (with a score penalty so they sort below visible matches). Set False to exclude entirely.(bool)= trueinclude_config-Include full configuration bodies in body-search results. Default: False (summary only).(bool)= falsegroup_by_domain-Group entity-registry results by domain (entity-side only). Adds a `by_domain` map to the response.(bool)= falseper_domain_limit-When `group_by_domain=True`, cap entity-registry results per domain to this number. Ignored otherwise.(int | None)= nullstate_filter-Filter entity-registry results to a specific state (e.g. "on", "off", "unavailable"). Case-insensitive. Can be used standalone (no query/domain/area) to enumerate every entity in that state; entity_total_matches reflects the filtered count.(str | None)= nullresult_fields-Project each entity-registry record to only the specified keys (e.g. ["entity_id", "state"]). None = full records. Base keys: entity_id, fri…
endly_name, domain, state, score, match_type. Opt-in enrichment/membership keys (computed on request): area, floor, labels, aliases, is_group, member_entity_ids. Membership is recognized only when HA explicitly exposes a valid group_entities or legacy entity_id collection; member IDs are sorted, direct (not recursively expanded), and omitted if visibility/include_hidden excludes a member. is_group remains true when member IDs are withheld; requesting member_entity_ids also retains is_group. An unknown key is rejected.nullfields-Project the response to the named top-level keys (e.g. ["entities", "automations"]); None = full response. Diagnostic / pagination keys are…
always retained so projection cannot hide partial / error state. Distinct from `result_fields` (which projects each entity record's keys). 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.nullconfig_time_budget-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. 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.(float | None)ge: 0.001, le: 300= nullService & Device Control
Description
Manage explicit operations or one deterministic structural bulk action. When NOT to use: use ``ha_call_service`` for service-specific payloads or backend-native group targeting, and ``ha_search`` for fuzzy name discovery. **Operations mode** (``operations``, no ``selector``): put every target in this one call. Parallel execution is the default, and invalid items are reported without aborting valid operations in the same batch — but a batch in which every item fails validation dispatches nothing and fails the call. A batch that targets a group/aggregate entity together with one or more of its own individual members also fails closed (nothing dispatched): Home Assistant applies the action to every member when the group is targeted regardless of what else is listed, so a member row cannot exclude that member from the group's own action. Use selector mode with ``exclude_entity_ids`` when a group action must exclude specific members. **Selector mode** (``selector`` + ``action``): use exact area or floor IDs when exclusions must be applied after recursively expanding generic aggregate membership. Resolves a frozen visible leaf set before dispatch; it is not transactional, so Home Assistant may still report per-leaf failures. A selector resolving to more than 100 entities (``MAX_SELECTOR_ENTITIES``) fails closed instead of dispatching a partial/oversized batch — narrow it (a more specific area/floor, or add ``exclude_entity_ids``) and retry. Set ``dry_run`` to preview the resolved set without changing state.
Parameters
operations-Explicit entity operations. Use this or selector, never both. Each item requires exact entity_id and action. Use action='off', not service='turn_off'.(list[SkipValidation[BulkControlOperation]])parallel-(bool)= trueselector-Optional exact structural scope using domain plus area_ids and/or floor_ids, with optional exclude_entity_ids.(SkipValidation[BulkControlSelector] | None)= nullaction-One device action applied to every resolved leaf.(str | None)= nullparameters-Optional action parameters for selector mode.(dict[str, Any] | None)= nulltimeout_seconds-(float | None)ge: 0, le: 60, allow_inf_nan: false, strict: true= nullvalidate_first-(bool)strict: true= truedry_run-(bool)strict: true= falseDescription
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_typerequired-(str)data-(dict[str, Any] | None)= nullDescription
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:** - **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 changed-state records, 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. **WebSocket command escape hatch (advanced):** A few Home Assistant operations are WebSocket-only commands, not registered services — most notably dismissing a Repairs issue. Pass ``ws_command`` (instead of domain/service) to send one, with its parameters in ``data``: ```python # Dismiss a repair (get domain/issue_id from ha_get_overview repairs # or ha_get_system_health include="repairs") ha_call_service(ws_command="repairs/ignore_issue", data={"domain": "sun", "issue_id": "abc", "ignore": True}) ``` Only one-shot request/response commands are supported; streaming/two-phase and service-invoking commands are rejected, and the other service parameters (entity_id, return_response, etc.) don't apply. Unavailable in Read Only Mode, including read-like services and WebSocket commands. Use dedicated read tools while that mode is enabled.
Parameters
domain-Service domain (e.g. 'light', 'climate', 'automation'). Required for a service call; must be omitted when ws_command is set.(str | None)= nullservice-Service name within domain (e.g. 'turn_on', 'set_temperature', 'trigger'). Required for a service call; must be omitted when ws_command is set.(str | None)= nullentity_id-Entity ID(s) the service call targets — one ID ('light.living_room') or several comma-separated ('light.a,light.b'). Optional for services that don't target a specific entity. Must be omitted when ws_command is set.(str | None)= nulldata-Extra service-call parameters beyond entity_id (e.g. {'temperature': 22} for climate.set_temperature). Also carries the raw command payload when ws_command is set. If entity_id is also present in data, the entity_id parameter wins.(dict[str, Any] | None)= nullreturn_response-If True, the service's response data is returned once, as the top-level 'service_response' key — never nested inside 'result' (default: False). Must stay False when ws_command is set.(bool)= falsewait-If True (default), wait for the entity state to change before returning. Applies only to state-changing services called with a single entity_id. A comma-separated multi-target does not get confirmed by this: it falls through to a legacy path that polls for the literal composite entity_id and times out after 10s. Set wait=False for multi-target calls.(bool)= trueverbose-Return HA's raw changed-state records unchanged (default: False). Use as an escape hatch when you need the full propagation chain or raw attribute payload (debug / inspection). With return_response=True the response data still surfaces once as the top-level service_response key, never nested in result. WARNING: brings back token-bloat for nested-group targets — prefer result_fields / result_attribute_keys for targeted control.(bool)= falseresult_fields-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.(str | list[str] | None)= nullresult_attribute_keys-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).(str | list[str] | None)= nullws_command-Advanced escape hatch: send a raw one-shot Home Assistant WebSocket command that is NOT a registered service (e.g. 'repairs/ignore_issue' to dismiss a Repairs issue). When set, omit domain/service and the other service params; put the command's parameters in data. Streaming/two-phase and service-invoking commands (call_service, execute_script) are rejected.(str | None)= nullDescription
Get the 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 wait window bounds both modes. Bulk checks poll all operations concurrently under one shared window and report per-item failures inside detailed_results instead of aborting the batch. 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_idrequired-Single operation ID or list of operation IDs to check. Use a single string for one operation, or a list for bulk status checks.(str | list[str])timeout_seconds-(float)ge: 0, allow_inf_nan: false= 10Description
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)= nullquery-(str | None)= nulllimit-Max services to return per page (default: 50)(int)ge: 1, le: 200= 50offset-Number of services to skip for pagination (default: 0)(int)ge: 0= 0detail_level-'summary': service name + description only (default). 'full': include parameter field schemas.(Literal['summary', 'full'])= "summary"service_fields-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).(str | list[str] | None)= nullfields-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.(str | list[str] | None)= nullSystem
Description
Get the current YAML fragment under a key, from one config file or across a glob. Use before ha_config_set_yaml to inspect what a key currently holds, and to find which file defines it — the returned ``file`` + ``yaml_path`` are exactly the arguments that address the same fragment for an edit. Not for storage-mode items: automations, scripts and scenes created through the UI live outside YAML — read those with ha_config_get_automation, ha_config_get_script and ha_config_get_scene. This tool sees only what is written in the config files themselves. Reads the file, so it reflects what is on disk rather than what Home Assistant currently has loaded; a fragment edited but not yet reloaded differs from the running config. Comments and HA tags survive as written and a ``!secret`` is never resolved to its value, so ``content`` can be handed back to ha_config_set_yaml unchanged. Files outside the read allowlist stay unreadable, and secrets.yaml reads back with its values masked.
Parameters
yaml_pathrequired-Dotted YAML key path to look up, e.g. 'alert2', 'mqtt', 'template'. Any key is readable — unlike ha_config_set_yaml, which only writes an allowlisted set.(str)file-Config-relative file to read. Accepts an fnmatch glob to search several files at once — 'packages/*.yaml' matches one directory level, not a nested tree. Use the glob to find which file defines a key.(str)= "configuration.yaml"include_content-Return the round-trip YAML text of each match. Set False to discover only which files define the key.(bool)= trueinclude_parsed-Additionally return each match as structured data. HA tags stay in source form ('!secret api_key'), never resolved.(bool)= falseDescription
Update raw YAML configuration in configuration.yaml, packages/*.yaml, or themes/*.yaml (LAST RESORT). MUST call ha_get_skill_guide OR refer to your locally installed skills 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 30 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. Two-step confirmation (default ON, toggle ENABLE_YAML_EDIT_CONFIRM / Server Settings): the first call returns ``preview: true`` with a unified ``diff`` of exactly what would change on disk and a ``confirm_token`` — nothing is written. Review the diff for changes outside the requested edit, then repeat the identical call adding ``confirm_token`` to apply. Every applied write also returns the final ``diff``. A token mismatch means the file changed since the preview (or the token was wrong); use the freshly returned token. ``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_pathrequired-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.actionrequired-Action to perform: 'add' (insert/merge content under key), 'replace' (overwrite key with new content), or 'remove' (delete the key entirely).(str)content-YAML content for the value under yaml_path. Required for 'add' and 'replace' actions. Must be valid YAML.(str | None)= nullfile-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).(str)= "configuration.yaml"confirm_token-Confirmation token from a prior preview response. When the YAML edit confirmation flow is enabled (default), the first call writes nothing and returns a unified diff plus confirm_token; repeat the identical call with that token to apply the edit.(str | None)= nullMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullDescription
Polymorphic backup tool. See the tool description for the routing matrix.
Parameters
scoperequired-'snapshot' for full HA tarballs; 'edits' for per-entity auto-backups.(Literal['snapshot', 'edits'])actionrequired-Operation to perform. Valid (scope, action) combinations are listed in the tool description.(Literal['create', 'restore', 'list', 'view', 'diff', 'delete'])name-(snapshot.create) Tarball name. Auto-generated if not provided.(str | None)= nullbackup_id-(snapshot.restore / snapshot.delete) Tarball ID (e.g. 'dd7550ed').(str | None)= nullrestore_database-(snapshot.restore) Include database in the restore. Default false (config-only).(bool)= falseconfirm-(snapshot.delete) Must be True to confirm deletion — a safety measure against accidental calls.(bool)= falsedomain-(edits.create / edits.list / edits.delete) Filter auto-backups by domain (e.g. 'automation', 'helper_timer'). Required for edits.create.(str | None)= nullentity_id-(edits.create / edits.list / edits.delete) Filter auto-backups by entity ID. Required for edits.create.(str | None)= nullbackup_name-(edits.view / edits.restore / edits.delete) Auto-backup filename (format '<domain>.<entity_id>.<timestamp>[_NN].yaml'). Not a tarball ID.(str | None)= nullolder_than_days-(edits.delete) Bulk-delete auto-backups older than this many days.(int | None)ge: 0= nulllimit-(edits.list / snapshot.list) Maximum number of entries to return.(int)ge: 1, le: 10000= 200Description
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)= nulljustification-(str | None)= nullsave_as-(str | None)= nullrun_saved-(str | None)= nulllist_saved-(bool)= falseDescription
Manage the tool security policy that gates high-stakes tool calls behind user approval. When NOT to use: this tool cannot see or decide pending approval requests — listing, approving, and denying them is developer-mode only (ha_dev_manage_server). For ordinary Home Assistant entity, automation, or dashboard work use the ha_config_* tools. When to use: to read the current policy, to add or remove per-tool approval rules and their matching conditions, and to change the approval wait time or how long an approval is remembered. Caveats: set replaces the WHOLE document, so send back an edited copy of what get returned, not a fragment. Writes are version-guarded: pass the version from the last get (or leave it in the policy body) and a concurrent edit is rejected instead of silently overwritten. Rule edits apply to the running server immediately and can remove approval gates. Whether this tool itself is registered is a separate setting that takes effect on restart. EXAMPLES: ha_manage_security_policy("get") ha_manage_security_policy("set", policy={"wait_seconds": 60, "approval_ttl_minutes": 5, "rules": [{"tool_name": "ha_call_service"}], "version": 3})
Parameters
actionrequired-get: read the whole policy document. set: replace it with the supplied one.(Literal['get', 'set'])policy-set: the full policy object {wait_seconds, approval_ttl_minutes, rules, version}(dict[str, Any] | None)= nullexpected_version-set: the version from your last get, for optimistic-concurrency safety (else the policy's own version field is used)(int | None)= nullDescription
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). SCREENSHOT-ENGINE ACTIONS (per-user, not the backend default): Taking a dashboard screenshot makes the Puppet engine write the saved theme of the Home Assistant user its token belongs to, which also flips that user's live web and mobile sessions. The screenshot tools are read-only and only *report* this; use action='set_engine_theme' with the value quoted in their warning to put it back, and action='get_engine_theme' to inspect it. These act on that engine account's per-user profile via frontend/set_user_data, which is a different layer from the backend default that action='set' changes. Giving the engine its own dedicated user and token avoids the issue entirely. 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") - Inspect the engine account's theme: ha_manage_theme( action="get_engine_theme") - Undo a screenshot's theme change (pass BOTH values from the warning, so a theme changed since then is not overwritten): ha_manage_theme(action="set_engine_theme", value={"theme": "", "dark": False}, expected_current={"theme": "default", "dark": True})
Parameters
actionrequired-Theme operation: 'list' installed themes, 'set' the backend default theme, or read/restore the screenshot engine account's own per-user theme with 'get_engine_theme' / 'set_engine_theme' (a different layer from the backend default).(ThemeAction)theme_name-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.(str | None)= nullexpected_current-Guard for action='set_engine_theme': the stored theme is read immediately before the write and the write is skipped if it no longer equals this. Omitting this value or passing null both mean 'expect no stored theme', enforced like any other value; the guard is always applied unless force is set. Best-effort, not atomic -- Home Assistant exposes no conditional write, so a change landing between that read and the write is not caught. Pass the expected_current value quoted in the screenshot tool's warning.(dict[str, Any] | None)= nullforce-action='set_engine_theme' only: skip the expected_current guard and overwrite unconditionally. Leave false unless you intend to discard whatever is stored.(bool)= falsevalue-Frontend user-data theme object when action='set_engine_theme', e.g. {'theme': '', 'dark': False}. An empty dict restores default/auto behavior. Take this verbatim from the warning a screenshot tool emitted.(dict[str, Any] | None)= nullmode-Which mode the theme applies to when action='set'. Defaults to light.(Literal['light', 'dark'] | None)= nullDescription
Manage Home Assistant updates -- list, read details, batch install, skip, or un-skip. Covers Core, OS, supervisor, apps (add-ons), device firmware, and HACS update entities. In Read Only Mode the read actions ('list', 'get') stay available; write actions are blocked. Installs run asynchronously in Home Assistant and can take minutes: 'install' returns once the service calls are accepted, with per-entity results. Poll action='list' to watch in_progress until installed_version reaches latest_version. EXAMPLES: - List all updates: ha_manage_updates() - Pre-update analysis: ha_manage_updates(action="get", entity_ids=["update.home_assistant_core_update"], include_release_notes=True) - Update everything pending in a category: ha_manage_updates(action="install", categories=["addons", "hacs"]) RETURNS (action='list'): updates_available, updates, categories, and ha_mcp_update -- this MCP server's own update status {current, latest, update_available}, so a newer ha-mcp release can be flagged. RETURNS (action='get'): update details, release notes; with include_release_notes=True on Core also breaking_changes.entries[], multi_version_release_notes[], and installed_integrations.
Parameters
action-'list' (all pending updates, default), 'get' (details/release notes for one update), 'install' (apply pending updates), 'skip' (hide the offered version), or 'clear_skipped' (re-offer a skipped version).(str)= "list"entity_ids-Update entity_id(s) to act on. 'get' takes exactly one; skip/clear_skipped require at least one; for install, mutually exclusive with categories.(list[str] | str | None)= nullcategories-For install: apply every pending update in these categories ('addons', 'hacs', 'devices', 'other'). Mirrors the HA 2026.7 'Update all' button: core/os/supervisor are excluded by design (target those individually via entity_ids) and skipped updates are never included.(list[str] | str | None)= nullinclude_skipped-For list: include updates that have been skipped (default: False).(bool)= falseinclude_release_notes-For get on 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.(bool)= falsebackup-For install: create a backup before installing where the update entity supports it (apps/add-ons). Default: False.(bool)= falseDescription
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 - entry_id: Reload a SINGLE config entry (one integration instance) instead of sweeping subsystems — the fast path after editing a custom component on disk. Pass it alone (leave `target` at its "all" default); combining it with an explicit `target` is a validation error. Find the id via ha_get_integration. **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"entry_id-(str | None)= nullDescription
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)= falseTodo 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-Todo list entity ID (e.g., 'todo.shopping_list'). If omitted, lists all todo list entities.(str | None)= nullstatus-Filter items by status: 'needs_action' for incomplete, 'completed' for done. Only applies when entity_id is provided.(Literal['needs_action', 'completed'] | None)= nullDescription
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_idrequired-Todo list entity ID (e.g., 'todo.shopping_list')(str)itemrequired-Item to remove - can be the item UID or the exact item summary/name(str)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_idrequired-Todo list entity ID (e.g., 'todo.shopping_list')(str)summary-Item text/name. Required when creating a new item. Ignored in update mode — use 'rename' to change the item name.(str | None)= nullitem-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.(str | None)= nullstatus-Item status: 'completed' to mark done, 'needs_action' to mark incomplete. Only used in update mode.(Literal['needs_action', 'completed'] | None)= nulldescription-Detailed description for the item(str | None)= nulldue_date-Due date in YYYY-MM-DD format (e.g., '2024-12-25')(str | None)= nulldue_datetime-Due datetime in ISO format (e.g., '2024-12-25T14:00:00'). Overrides due_date if both provided.(str | None)= nullrename-New name/summary for an existing item. Only used in update mode.(str | None)= nullUtilities
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
templaterequired-(str)timeout-(int)= 3report_errors-(bool)= trueDescription
Get diagnostic information and templates 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. The full response is LARGE (the captured logs appear in the raw log keys AND inside each template) — pass fields=... to fetch only the keys you need once you know which template applies. 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) - `core_error_log` — Home Assistant error log (home-assistant.log) over REST; carries auth / integration errors that don't show in addon_logs - `missing_tool_hint` — check this FIRST when the report is about a missing/unavailable tool; a stale client tool list (not a bug) is the usual cause, and refreshing the MCP connection is the fix - `suggested_title`, `duplicate_check_urls`, `anonymization_guide`
Parameters
tool_call_count-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)(int)ge: 1, le: 16= 10fields-Return only the specified top-level response keys — the full response (both templates + logs + diagnostics, with log content repeated across…
the raw keys and templates) is very large. None = full response. Typical for a runtime bug: 'runtime_bug_template,suggested_title,runtime_bug_submit_url,duplicate_check_urls,anonymization_guide,missing_tool_hint,known_client_issues_hint,instructions'; for agent feedback swap in agent_behavior_template and agent_behavior_submit_url. The templates already embed the relevant logs, so the raw log keys are only needed for your own analysis. Available keys: diagnostic_info, recent_logs, startup_logs, addon_logs, core_error_log, log_count, startup_log_count, formatted_report, runtime_bug_template, agent_behavior_template, anonymization_guide, suggested_title, runtime_bug_submit_url, agent_behavior_submit_url, duplicate_check_urls, missing_tool_hint, known_client_issues_hint, instructions.nullZones
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:** With the ha_mcp_tools custom component installed, YAML-defined zones — including the auto-synthesized 'home' zone — are included and marked ``editable=false`` / ``source="yaml"`` (storage zones created via UI/API are ``source="storage"``). Without the component, only storage zones are listed and YAML-defined zones such as 'home' will not appear.
Parameters
zone_id-Zone ID to get details for (from ha_get_zone() list). If omitted, lists all zones.(str | None)= nullDescription
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_idrequired-Zone ID to remove (use ha_get_zone to find IDs)(str)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-Display name for the zone (required for create)(str | None)= nulllatitude-Latitude coordinate of the zone center (required for create)(float | None)= nulllongitude-Longitude coordinate of the zone center (required for create)(float | None)= nullzone_id-Zone ID to update (omit to create new zone, use ha_get_zone to find IDs)(str | None)= nullradius-Radius of the zone in meters (must be > 0, defaults to 100 on create)(float | None)= nullicon-Material Design Icon (e.g., 'mdi:briefcase', 'mdi:school')(str | None)= nullpassive-Passive mode - if True, zone will not trigger enter/exit automations (defaults to False on create)(bool | None)= null