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.
Parameters
source-App (add-on) source. 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 (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, and do not infer private app API schemas. Use exactly one mode: lifecycle/store action, configuration fields, ``path`` proxy, or ``path`` with ``array_patch``. Requires Home Assistant OS or Supervised. When ha-mcp itself runs as an app it cannot update its own running slug; update ha-mcp from the Home Assistant Apps UI. ``options`` merges top-level keys and one nested mapping level; supply complete values for deeper nested mappings because they are replaced. 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 except for action='add_repository' / 'remove_repository' (which take 'repository' instead) and 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. ESPHome 2026.10+: POST JSON-RPC to '/api/mcp' (initialize, notifications/initialized, tools/list, then tools/call); check response.error first, then response.result.isError.(str | None)= nullmethod-Proxy mode only. HTTP method: GET, POST, PUT, DELETE, PATCH.(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).(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 and ESPHome, reject direct access unless their leave_front_door_open option is enabled and the app is restarted.(int | None)= nulloffset-Proxy mode only. HTTP: skip this many items in a JSON array response.(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.(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.(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.(int | None)= nullmessage_offset-Proxy mode only. WebSocket: drop this many messages from the start of the collected list before returning.(int)= 0summarize-Proxy mode only. WebSocket: when True, 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)].(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')); 'rebuild' is for a local app whose source changed but whose version did not. 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 (e.g. after editing a local app's config.yaml) — it takes neither 'slug' nor 'repository', refreshes available metadata only, and installs nothing; follow with action='update' for a newer version, or action='rebuild' for a local app whose source changed without a version change. Returns 'changed' and 'updates_available', null when the store could not be read (see 'warnings').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. 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. 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. 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, temperature_entity_id, humidity_entity_id) 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="area", id="kitchen", temperature_entity_id="sensor.kitchen_temp", humidity_entity_id=None) 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 (use ha_list_floors_areas to find IDs)(str | None)= nullfloor_id-Floor assignment when kind='area' (null or empty string to clear; omit to leave unchanged).(str | None)level-Numeric level when kind='floor' (0=ground, 1=first, -1=basement).(int | None)= nullicon-Material Design Icon (e.g., 'mdi:sofa', 'mdi:home-floor-1'; null or empty string to remove; omit to leave unchanged)(str | None)aliases-Alternative names for voice assistant recognition (e.g., ['lounge'], empty list to clear)(str | list[str] | None)= nullpicture-Picture URL when kind='area' (null or empty string to remove; omit to leave unchanged).(str | None)labels-Label IDs when kind='area' (replaces the area's label set; empty list to clear). Omit to leave labels unchanged. Floors have no labels.(str | list[str] | None)= nulltemperature_entity_id-Sensor the area reports temperature from, when kind='area' (a sensor.* entity with device_class temperature; null or empty string to clear). Omit to leave unchanged.(str | None)humidity_entity_id-Sensor the area reports humidity from, when kind='area' (a sensor.* entity with device_class humidity; null or empty string to clear). Omit to leave unchanged.(str | None)Assist
Description
Manage Home Assistant Assist pipelines. Use action='list' to discover pipeline IDs, action='get' to inspect one pipeline, action='create' or action='update' to write pipeline settings, 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. Even with pipeline_id, the sentence 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: - Get the preferred pipeline: ha_manage_pipeline(action="get", pipeline_id="preferred") - Create by cloning a pipeline: ha_manage_pipeline(action="create", base_pipeline_id="preferred", name="Local Assist", conversation_engine="conversation.local_llm") - Update the agent and clear the TTS voice: ha_manage_pipeline(action="update", pipeline_id="preferred", conversation_engine="conversation.local_llm", tts_voice="") - Run a sentence 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>") 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'.(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. Ignored for other actions.(bool)= falseAutomations
Description
Get a Home Assistant automation's stored configuration. `config` is the body Home Assistant stores, as it stores it; its `config_hash` stays the same across reads of an unchanged automation. `entity` is the automation's entity registry row, `validation` holds Home Assistant's status and error when the automation failed to load (None when it loaded), and `references` lists the entities, devices, areas, floors, labels and blueprint it uses. `references` is None without the ha_mcp_tools component's automation_config bridge and while Home Assistant has not loaded the automation (disabled, or it failed to load); `warnings` says which. `automation_id` is the resolved entity_id, falling back to the input. EXAMPLES: - Get automation: ha_config_get_automation("automation.morning_routine") - Get by config id: ha_config_get_automation("1712345678901") For comprehensive automation documentation, use ha_get_skill_guide.
Parameters
entity_idrequired-The automation's entity_id (e.g. 'automation.morning_routine') or its config id(str)Description
Delete a Home Assistant automation permanently. A disabled automation is removed too. The returned `automation_id` is the resolved entity_id, falling back to the input. EXAMPLES: - Delete automation: ha_config_remove_automation("automation.old_automation") - Delete by config id: ha_config_remove_automation("1712345678901")
Parameters
entity_idrequired-The automation's entity_id (e.g. 'automation.old_automation') or its config id(str)wait-Wait for automation to be fully removed before returning.(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 triggers/conditions/actions over templates in logic positions; templates belong only in `data.*`, notification text, `event_data` and `variables`. The best-practice checker reports violations under `best_practice_warnings` — fix them before re-submitting. `triggers-and-conditions.md`, `automation-actions.md` and `template-guidelines.md` ship under `skill_content` by default. Test any unavoidable template with ha_eval_template first. Consider a dedicated tool first: a state snapshot with no trigger -> ha_config_set_scene; a value derived from other entities -> ha_config_set_helper(helper_type='template'); a counter / timer / schedule / boolean -> ha_config_set_helper. MODES (pick one; each can also take `enabled`, which alone with `entity_id` turns the automation on or off without touching config): - python_transform: surgical edits to an existing automation. Requires entity_id and config_hash from ha_config_get_automation(). Operates on the stored config exactly as ha_config_get_automation returns it, e.g. python_transform="config['triggers'].append({'trigger': 'state', 'entity_id': 'binary_sensor.motion', 'to': 'on'})" - config: new automations or full restructures. Regular automations have triggers and actions; blueprint automations have use_blueprint {path, input}. Home Assistant validates the config, and a rejection carries its own error text. - take_control_of_blueprint: convert a blueprint-backed automation into a standalone one. Takes no config of its own. IDENTITY: omit entity_id and config['id'] to create with a generated ID. Reusing an entity_id targets the same automation even if the alias changes; to rename or replace it, read it first and pass its config_hash — a changed alias without that hash is rejected before writing. `operation` says whether the write created or updated it. EXAMPLES: - Create: ha_config_set_automation(config={"alias": "Morning Lights", "triggers": [{"trigger": "time", "at": "07:00:00"}], "actions": [{"action": "light.turn_on", "target": {"area_id": "bedroom"}}]}) - From a 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"}}}}) - Update: current = ha_config_get_automation(entity_id="automation.x"); ha_config_set_automation(entity_id="automation.x", config_hash=current["config_hash"], config={...}) - Disable: ha_config_set_automation(entity_id="automation.x", enabled=False) - Run now: ha_config_set_automation(entity_id="automation.x", run_actions=True) - Take control: ha_config_set_automation(entity_id="automation.x", take_control_of_blueprint=True) TAKE CONTROL is one-way: later blueprint edits stop reaching the automation; to change an input value, update 'use_blueprint.input' instead. It 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 (an automation reload does not clear it). The response names the blueprint in `took_control_of_blueprint`. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute", path=..., input=...).
Parameters
config-Complete automation configuration, written as given. 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)= nullentity_id-The automation's entity_id (automation.x) or its config id; omit to create. An unused config id creates the automation under that id; values such as 'new' are literal ids, not placeholders.(str | None)= nullpython_transform-Python expression to transform existing automation config. Mutually exclusive with config. WARNING: Expressions with infinite loops will han…
g 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, index, count, sort, reverse - Dict methods: update, get, setdefault, keys, values, items, pop, clear - 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, lstrip, rstrip, removeprefix, removesuffix, split, join, replace, count, index - 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 insteadnullconfig_hash-Config hash from ha_config_get_automation for optimistic locking. Required for python_transform and 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 inputs and saves the result over the same automation, which keeps its entity_id, alias and description and then has its own triggers/conditions/actions and no 'use_blueprint'. Requires entity_id; mutually exclusive with config and python_transform.(bool)= 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. Set to False for bulk operations.(bool)= trueenabled-Turn the automation on (True) or off (False) after an optional config update; None leaves it unchanged. Not written into the stored config: Home Assistant keeps the state across restarts, but a config 'initial_state' overrides it whenever the automation is reloaded or HA starts.(bool | None)= nullrun_actions-Run the automation's actions now, skipping its triggers and conditions (automation.trigger, the UI's Run actions). Applied after `enabled` and after any config write. Can be used standalone with entity_id and no config.(bool)= falseMandatoryBPS-(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. 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 (the error lists the consumers), 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. 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 those tools' ``take_control_of_blueprint=True``: it renders with that item's own current inputs and saves the result over itself in one call. EXAMPLES: - 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") - 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) - Render standalone: ha_manage_blueprints(action="substitute", path="user/motion.yaml", input={"motion_sensor": "binary_sensor.hall"}) RELATED TOOLS: ``ha_config_remove_automation`` / ``ha_config_remove_script`` to clear consumers blocking a delete, ``ha_search`` to find them.
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 (the UI's "Take control")(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
Get calendar events from a calendar entity within a time range. Returns each event's summary, start, end, description and location, plus the uid, recurrence_id and rrule when the calendar provides them (the edit and remove tools use uid and recurrence_id). To find calendar entities, use ha_search(domain_filter='calendar'). EXAMPLES: - Next week (defaults): ha_config_get_calendar_events("calendar.family") - Date range: ha_config_get_calendar_events("calendar.work", start="2024-01-01T00:00:00", end="2024-01-31T23:59:59")
Parameters
entity_idrequired-Calendar entity ID (e.g., 'calendar.family')(str)start-Start datetime in ISO format; omit it to start at the current time(str | None)= nullend-End datetime in ISO format (default: 7 days from now, not from start; pass end whenever you pass 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. Get the event UID from ha_config_get_calendar_events(). EXAMPLES: - Delete a single event: ha_config_remove_calendar_event("calendar.family", uid="event-12345") - Delete one occurrence and all later ones: ha_config_remove_calendar_event("calendar.work", uid="recurring-event-67890", recurrence_id="20240115T100000", recurrence_range="THISANDFUTURE")
Parameters
entity_idrequired-Calendar entity ID (e.g., 'calendar.family')(str)uidrequired-Unique identifier of the event to delete(str)recurrence_id-Recurrence ID for recurring events(str | None)= nullrecurrence_range-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. 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. 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. EXAMPLES: - Create: ha_config_set_calendar_event("calendar.family", summary="Doctor appointment", start="2024-01-15T14:00:00", end="2024-01-15T15:00:00") - Recurring (every Monday, 10 occurrences): 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") - Update this and all later occurrences: 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")
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-Event description(str | None)= nulllocation-Event location(str | None)= nullrrule-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.(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
Get a snapshot image from a Home Assistant camera entity. Fetches the current camera image and returns it directly for visual analysis (security checks, delivery verification, confirming a garage door actually closed), in the format Home Assistant serves (JPEG, PNG, or GIF), alongside a short text block stating the served snapshot's size and retrieval time in Home Assistant local time. Do not use it to inspect the camera's state or attributes — use ha_search or ha_get_state for those; it returns an image, not state. Use it when you need to see the current scene, and on high-resolution cameras pass width and height to reduce token usage. EXAMPLE: ha_get_camera_image(entity_id="camera.backyard", width=640, height=480)
Parameters
entity_idrequired-Camera entity ID (e.g., 'camera.front_door')(str)width-Width for the resized image. Home Assistant rescales only when both width and height are given and the camera serves JPEG.(int | None)= nullheight-Height for the resized image. Home Assistant rescales only when both width and height are given and the camera serves JPEG.(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. The engine returns 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. WPE Screenshot Engine applies frontend preferences temporarily. Puppet may persist requested theme or dark-mode changes on the engine account; ha-mcp reports detected changes in warnings but does not undo them (see ha_manage_theme action='set_engine_theme'). To change the screenshot engine app's own options or restart it, 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). Height limits are engine-specific: Puppet caps at 4000 px; WPE's configured limit can be up to 12000 px, with a width-dependent effective cap. Raise wait_ms for lazy cards.(bool)= falsetheme-Installed Home Assistant frontend theme name, applied to this render.(str | None)= nulldark_mode-Render the requested theme in dark mode.(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 deletes the dashboard and all its configuration; with auto-backup on, ha_manage_backup(scope='edits', action='restore') re-creates it. YAML-mode dashboards are defined in configuration.yaml and cannot be deleted via the API. HA internal IDs may differ from url_path (e.g. hyphens → underscores); the tool resolves either form to the actual registry ID before deletion. 'default' is an alias for 'lovelace'. EXAMPLES: - Delete dashboard: ha_config_delete_dashboard("mobile-dashboard")
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.(str)Description
Get Lovelace dashboards: list them, read one config, or search their cards. Not for a render alone: ha_get_dashboard_screenshot (screenshot beta feature) returns images without the config. Not for entity discovery: use ha_search. The parameters pick the mode: - list_only=True: storage and YAML dashboards alike. - Any of query / entity_id / card_type / heading searches cards and header cards, AND-ing the criteria, in url_path or, when it is omitted, every storage dashboard. View badges answer entity_id and query, and heading excludes them. Cards nested at any depth count, including custom cards' own keys such as groups[].cards[].card. Nesting is found heuristically, under the keys cards, card, custom_fields and states; a card under any other key is read only as its parent's text, by query. Each match has a jq_path and a python_path to append to `config` in ha_config_set_dashboard(python_transform=...), e.g. f'config{m["python_path"]}["icon"] = "mdi:x"'. Each match carries its dashboard's url_path and config_hash; a one-dashboard search also returns the config_hash at the top level. - Otherwise the full config of url_path (the default dashboard when omitted) is returned; with view_path only that view, while config_hash still covers the full config. stored_config is false (config null) when Home Assistant holds no config for the dashboard. Caveats: config_hash stays the same across reads of an unchanged config. Searches stop at 200 matches (`truncated`). A search across dashboards skips YAML dashboards (HA resolves !secret in them) and, without a current ha_mcp_tools component, the default dashboard (with a warning); name either by url_path. Strategy dashboards have no stored cards to search. An unknown view_path errors and lists the available ones. EXAMPLES: - List all dashboards: ha_config_get_dashboard(list_only=True) - Get one view only: ha_config_get_dashboard(url_path="lovelace-mobile", view_path="office") - Find cards by entity (wildcards allowed): ha_config_get_dashboard(url_path="my-dash", entity_id="sensor.temperature_*") - Find heading: ha_config_get_dashboard(url_path="my-dash", heading="Climate", card_type="heading") - Which dashboards use an entity: ha_config_get_dashboard(query="light.bedroom") - Fields of a card type before writing one (omit card_type to list types): ha_config_get_dashboard(card_type="tile", describe=True)
Parameters
url_path-Dashboard URL path (e.g. 'lovelace-home'); 'default' for the default dashboard.(str | None)= nulllist_only-List every dashboard's metadata instead of reading one; ignored when a search parameter is given.(bool)= falseforce_reload-Get only: bypass the config cache (searches always read fresh config).(bool)= falseentity_id-Search: cards whose entity/entities field holds this entity ID; '*' matches any run of characters (including none) and the whole ID must match (e.g. 'sensor.temperature_*').(str | None)= nullcard_type-Search: cards of this type, e.g. 'tile'; 'badge' selects view badges instead (alone, it lists them all). With describe=True: the type to describe.(str | None)= nullheading-Search: cards whose heading/title contains this text (case-insensitive).(str | None)= nullinclude_config-Search: include each matched card's own config. A container's body includes its nested cards, which are also matches, so nested stacks multiply the payload. Withheld, with a warning, for a dashboard not provably storage-mode (a YAML config may carry resolved !secret values).(bool)= falseinclude_screenshot-Get mode only: also return rendered image(s) of the dashboard for visual verification. Requires the 'dashboard screenshot' beta feature + engine app (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.(bool)= falseview_path-Get mode: return ONLY the view whose Lovelace views[].path matches (response carries 'view' + 'view_index' instead of the full 'config'). With include_screenshot, also selects the view to render.(str | None)= nullquery-Search: text or entity ID contained in any value a card holds (case-insensitive); the matching values are listed under `matched`.(str | None)= nulldescribe-Return card_type's fields from HA's card editor(bool)= falseDescription
List Lovelace dashboard resources (custom cards, themes, CSS/JS). Returns one page of registered resources; `total_count` and `has_more` report the full set, and `inline_count` and `by_type` summarise every resource, not just this page. Each resource has a unique ID for update/delete operations. 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. `resource_mode` is Core's lovelace resource mode; in 'yaml' mode resources cannot be created, updated or deleted through the API. EXAMPLES: - Next page: ha_config_list_dashboard_resources(offset=100) - List with full content: ha_config_list_dashboard_resources(include_content=True) - Verify an edit by hash: ha_config_list_dashboard_resources(include_hash=True) Home Assistant 2026.6+ exposes resource management in the UI by default; API access works regardless of UI availability.
Parameters
include_content-Include full decoded content for inline resources in the "_content" field. When False, a 150-char preview is shown instead. Rows past the per-response content budget carry '_content_truncated': True instead of '_content'; request a smaller page before copying content into an update.(bool)= falseinclude_hash-Add "_config_hash" (SHA-256 hex digest of the decoded UTF-8 content) to each inline resource, e.g. to verify an edit or to pass as config_hash without fetching the content.(bool)= falselimit-Max resources to return per page(int)ge: 1, le: 500= 100offset-Number of resources to skip for pagination(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. `dashboard-guide.md` ships under `skill_content` by default. A card's fields: ha_config_get_dashboard(card_type=..., describe=True). MODES (pick one): - patch: edit known paths with literal values using JSON Patch add/remove/replace/test and config_hash, e.g. patch=[{"op": "replace", "path": "/views/0/title", "value": "Home"}]. move/copy are unsupported. Full guide: https://github.com/homeassistant-ai/ha-mcp/blob/master/docs/dashboard-edits.md - python_transform: loops or pattern-based changes across cards and views, e.g. 'config["views"][0]["cards"].append({"type": "button", "entity": "light.bedroom"})'. After delete/add operations indices shift, so chain multiple ops in ONE expression where possible; a later call needs a fresh config_hash from ha_config_get_dashboard(). - config: new dashboards only, or a full restructure. Replaces everything. Omit it to create a dashboard without initial config. Use ha_config_get_dashboard(entity_id=...) to get the path of any card, and ha_search / ha_get_overview to find entity IDs — never guess them. For visual re-checks after the write, use ha_get_dashboard_screenshot (when available) instead of re-sending config. title/icon/require_admin/show_in_sidebar can be updated in a metadata-only call or alongside a full config replacement; with python_transform or patch, update metadata in a separate call (combining it with patch is rejected). Strategy dashboards (config {"strategy": {...}}) cannot be converted to custom dashboards here; use "Take Control" in the Home Assistant UI. EXAMPLE: 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"}]}]}]}) This tool manages storage-mode dashboards only. YAML-mode dashboards (a .yaml file registered under ``lovelace: dashboards:`` in configuration.yaml) and dashboards inlined under ``lovelace:`` are edited in their .yaml file; ``ha_config_set_yaml`` can update the ``lovelace:`` registration but not the dashboard body.
Parameters
url_pathrequired-Dashboard URL path (e.g., 'my-dashboard'). Use 'default' or 'lovelace' for the default dashboard. Home Assistant requires a hyphen in new dashboard paths.(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, index, count, sort, reverse - Dict methods: update, get, setdefault, keys, values, items, pop, clear - 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, lstrip, rstrip, removeprefix, removesuffix, split, join, replace, count, index - 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 insteadnullconfig_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-Sidebar name; required when creating a dashboard(str | None)= nullicon-MDI icon name (e.g., 'mdi:home', 'mdi:cellphone'). Omit to store none (the sidebar then shows mdi:view-dashboard); '' clears a stored icon.(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. Requires the 'dashboard screenshot' beta feature + engine app (add-on)/sidecar; if unavailable, the write result is returned with a warning.(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. Mutually exclusive with config and python_transform. 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: - edits: Search/replace edits on an existing inline resource. Any failing edit (or a config_hash mismatch) aborts the whole call and saves nothing. Prefer this over content= for small changes to a large card: only the changed text is sent. - content: Inline JavaScript or CSS code (embedded in the resource URL as a data: URI — no file storage or external hosting involved). Supports 'module' and 'css' types only; URLs are deterministic (same content = same URL); up to ~128KB. 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.) - url: External resource URL — /local/... (/config/www/; Marketplace cards: /local/community/...), /hacsfiles/... (HACS cards) or https://... (CDN). Supports all types: 'module', 'js', 'css', 'html'. EXAMPLES: - Inline: ha_config_set_dashboard_resource(content="class MyCard extends HTMLElement { ... } customElements.define('my-card', MyCard);", resource_type="module") - Store cards need no call: in storage mode HACS and the Marketplace register the resource on install (adding it again duplicates it); with YAML-mode resources, list it under lovelace: resources: in configuration.yaml instead. - Update: ha_config_set_dashboard_resource(url="/local/my-card-v2.js", resource_type="module", resource_id="abc123") - Patch: ha_config_set_dashboard_resource(resource_id="abc123", edits=[{"old": "color: red", "new": "color: blue"}], config_hash="<_config_hash from the list>") Inline writes return the stored content's config_hash. After adding a resource, a browser cache clear or hard refresh (Ctrl+Shift+R) is needed to load it.
Parameters
content-JavaScript or CSS code to host inline.(str | None)= nullurl-URL of the resource.(str | None)= nullresource_type-Resource type, validated by Home Assistant: 'module' for ES6 modules (modern cards), 'js' for legacy JavaScript (older custom cards), 'css' for stylesheets (themes, global styles), 'html' for HTML imports(_RESOURCE_TYPE)= "module"resource_id-Resource ID to update. If omitted, creates a new resource. Get IDs from ha_config_list_dashboard_resources()(str | None)= nulledits-Search/replace edits for an existing inline resource, applied in order. Requires resource_id; resource_type is ignored and the stored type is kept.(list[ResourceEdit] | None)min_length: 1, max_length: 100= nullconfig_hash-With edits: refuse the patch unless the stored content has this hash when read, just before the write (the list's _config_hash with include_hash=True, or a previous write response's config_hash). A preflight check only: a change made by another client after that read is not detected and can be overwritten.(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. 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). approve/deny require the separate 'dev_tools_security_policy_access' setting, off by default. EXAMPLES: 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") 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 pip spec (or clear it) and reinstall it; 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'])pip_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 returns to the server the component release pins instead of being treated as a requirement. 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, the Tools tab, the Tool Security Policies editor, and agent-editable backup config. Snapshot Actions and Backup Read Only are human-only controls. 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: toggle the tool's unconditional security-policy rule. In a require-approval list gated=true adds it (every call needs approval); in an allow list that rule approves the tool, so gated=true removes it. Conditional rules are left in place and still apply. gated=true is refused while an allow list's unconditional '*' rule approves every tool(bool | None)= nullpolicy-set_policy: the full policy object {rule_effect, wait_seconds, approval_ttl_minutes, event_decisions_enabled, rules, version, schema_version}. Replaces the WHOLE document: a field you omit reverts to its default, so send back an edited copy of get_policy rather than a fragment (omitting event_decisions_enabled switches the event-bus approval channel off; omitting rule_effect while the stored policy is an allow list is refused)(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.(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(int)ge: 1, le: 200= 50offset-Number of devices to skip for pagination(int)ge: 0= 0detail_level-'summary': basic device info and protocol identifiers. 'full': in list mode also include each device's entities. Single-device lookups always return full detail, including radio metrics, node status and Matter diagnostics.(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 Works only when the integration supports removing devices. 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 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). '' clears name_by_user and reverts to the integration-provided name. A whitespace-only value acts like '', for clients that cannot send an empty string.(str | None)= nullarea_id-Area/room ID to assign the device to. Use empty string '' to unassign. A whitespace-only value acts like '', for clients that cannot send an empty string.(str | None)= nulldisabled_by-Set to 'user' to disable the device, or '' (empty string) to enable it. Omit to leave the disabled state unchanged. A whitespace-only value acts like '', for clients that cannot send an empty string.(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: grid / solar / battery / gas / water energy sources, device consumption sensors for electricity and water, and cost tariffs. 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. 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. - mode='add_source': append a single entry to ``energy_sources``. Same optimistic read-modify-write semantics. RELATED TOOLS: Use ha_get_history(source="statistics") with the returned statistic IDs for consumption, totals, and trends. Metadata comes directly from the running Core, including stored and display units. 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. - The per-key ``config_hash`` form lets an agent submit only the top-level key it wants to change: ``config`` keys must equal the dict keys, and a per-key submission still fully replaces that key's value. A mismatch on any locked key returns ``RESOURCE_LOCKED`` with the offending keys in ``mismatched_keys``. - ``dry_run=True`` skips the hash check entirely for both forms. - Writes need an administrator token; Home Assistant rejects the save otherwise. - After a successful write, the tool calls ``energy/validate`` and returns residual issues (missing stats, unit mismatches) as ``post_save_validation_errors``; the save persists regardless — correct the config and write again if needed. - 'add_source' rejects duplicates by ``(type, stat_energy_from)`` for solar/battery/gas/water; grid entries are appended without a duplicate check (multiple grid variants are legitimate), so the caller de-duplicates grid sources. EXAMPLES: - ha_manage_energy_prefs(mode="get", include_schema=True) - Preview a device: ha_manage_energy_prefs(mode="add_device", stat_consumption="sensor.fridge_energy", dry_run=True) - Remove a device: ha_manage_energy_prefs(mode="remove_device", stat_consumption="sensor.fridge_energy")
Parameters
moderequired-Operation mode. Primitives: 'get' reads the current prefs; 'set' writes a full prefs payload.(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 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. Ignored by convenience modes.(str | dict[str, str] | None)= nulldry_run-If True, preview without saving. With the component, validates the complete proposal through Core's registered save schema. Without that capability, explicitly reports proposal validation unavailable. Semantic energy/validate checks persisted state only.(bool)= 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-Display name for mode='add_device'; ignored otherwise.(str | None)= nullincluded_in_stat-'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'.(bool)= falsesource-Single native energy_sources entry for mode='add_source'. Use mode='get', include_schema=True to inspect the running Core's fields and requirements; dry_run=True validates without saving.(dict[str, Any] | None)= nullinclude_schema-With mode='get', describe the running Core's save schema. Unavailable without the component.(bool)= falseinclude_statistics-With mode='get', include native recorder metadata and resolved output units for all configured statistic references. Ignored for other modes.(bool)= falseEntity Registry
Description
Get entity registry information for one or more entities. Returns detailed entity registry metadata: area, custom name/icon, original name, disabled_by/hidden_by (with enabled/hidden shorthands), aliases (a null entry = the entity's own name), labels, categories, device_class override, per-domain options, platform, device_id, config_entry_id and unique_id. 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). When config_entry_id is 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. Voice-assistant exposure is stored under options but is set via ha_set_entity(expose_to=...). 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), `floor`, and `label_names`. This tool's base `labels` carries the label ids; ha_search result_fields and ha_get_entity_exposure instead emit the resolved names under `labels`. RELATED TOOLS: ha_set_entity() to modify these properties; ha_get_state() for current state/attributes; ha_search() to find entities. EXAMPLES: - Single entity: ha_get_entity("sensor.temperature") - Multiple entities: ha_get_entity(["light.living_room", "switch.porch"]) - Resolve a unique_id: ha_get_entity(unique_id="00:11:22:33", domain="sensor")
Parameters
entity_id-Entity ID or list of entity IDs to retrieve (e.g., 'sensor.temperature' or ['light.living_room', 'switch.porch']).(str | list[str] | None)= nullunique_id-Stable integration unique_id (entity_id is mutable, unique_id is not). Mutually exclusive with entity_id.(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 the entities that have explicit exposure settings for voice assistants (Alexa, Google Assistant, Assist) as `exposed_entities`, plus a per-assistant `summary` count. An entity absent from that list uses the default exposure settings. With an entity_id: returns `exposed_to` (assistant -> True/False from the entity's explicit settings), `is_exposed_anywhere`, and `has_custom_settings`. When has_custom_settings is False the defaults apply and exposed_to is all False; the default values themselves are not computed. EXAMPLES: - Filter by assistant: ha_get_entity_exposure(assistant="cloud.alexa") - Get specific entity: ha_get_entity_exposure(entity_id="light.living_room") 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.(str | None)= nullassistant-Filter by assistant: 'conversation', 'cloud.alexa', or 'cloud.google_assistant'.(str | None)= nullDescription
Remove one or more entities from the Home Assistant entity registry. Permanently removes the entity registration; the entity will no longer appear in the UI or be available to automations. Use only for orphaned or stale entity entries: if the underlying device, integration or stored helper is still there, the entity may be re-added on the next HA restart or reload; for a stored helper's entity a single-id removal names the tool that removes the helper. Added again, it gets back its registry settings while its config entry exists, and for 30 days after that entry, or an entity without one, was removed. For most use cases, consider disabling instead: ha_set_entity(entity_id="sensor.old", enabled=False). 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). A lost connection stops the run; not_attempted lists the ids never sent (the failing one too if it never left), safe to retry. Only single-id removal is auto-backed-up. EXAMPLES: - Remove orphaned sensor: ha_remove_entity("sensor.old_temperature") - Bulk cleanup: ha_remove_entity(["sensor.orphan_1", "sensor.orphan_2"]) Use ha_search / ha_get_entity to verify an entity before removing it.
Parameters
entity_idrequired-Entity ID, or a list of entity IDs, to remove from the entity registry (e.g., 'sensor.old_temperature').(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, use_device_name, icon, device_class, options, enabled, hidden, aliases, use_entity_name_alias, new_entity_id, new_device_name) require single entity. SHOW AS / DEVICE CLASS: a device_class change applies instantly, no reload needed. REGISTRY OPTIONS: multi-domain options 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: 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: new_device_name 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: - Assign to area: ha_set_entity("sensor.temp", area_id="living_room") - Set Show As: ha_set_entity("binary_sensor.zone_10", device_class="window") - Set sensor precision: ha_set_entity("sensor.power", options={"sensor": {"display_precision": 2}}) - Rename entity and device: ha_set_entity("light.old", new_entity_id="light.new", new_device_name="New Lamp") - Add labels to multiple: ha_set_entity(["light.a", "light.b"], labels=["new"], label_operation="add") - Expose to Alexa: ha_set_entity("light.lamp", expose_to={"cloud.alexa": True}) ENABLED/DISABLED WARNING: A disabled entity 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. For automations, use ha_config_set_automation(entity_id="automation.xxx", enabled=False) (or enabled=True to re-enable). For scripts, ha_config_set_script(script_id="script.xxx", run="stop") only stops a currently running execution; it does not disable the script. Home Assistant has no script runtime enable/disable service.
Parameters
entity_idrequired-Entity ID or list of entity IDs to update.(str | list[str])area_id-Area/room ID to assign the entity to. Use empty string '' to unassign from current area. A whitespace-only value acts like '', for clients that cannot send an empty string.(str | None)= nullname-Display name for the entity. Use empty string '' to remove custom name and revert to default. A whitespace-only value acts like '', for clients that cannot send an empty string.(str | None)= nulluse_device_name-HA 2026.10's 'Use device name' switch. True names the entity after its device and follows device renames; False restores the integration's default name. Omit to leave it as is; not combinable with name.(bool | None)= nullicon-Icon for the entity (e.g., 'mdi:thermometer'). Use empty string '' to remove custom icon. A whitespace-only value acts like '', for clients that cannot send an empty string.(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. Examples: 'window', 'door', 'motion' for binary_sensor; 'temperature', 'humidity' for sensor. A whitespace-only value acts like '', for clients that cannot send an empty string.(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 device_class; for voice-assistant exposure use expose_to, not options.<assistant>.should_expose.(dict[str, dict[str, Any]] | None)= nullenabled-True to enable the entity, False to disable it. 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.(bool | None)= nullhidden-True to hide the entity from UI, False to show it.(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.(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.(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}.(dict[str, str | None] | None)= nulllabels-List of label IDs for the entity. Behavior depends on label_operation parameter. Use [] with label_operation='set' to clear.(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}.(dict[str, bool] | None)= nullnew_entity_id-New entity ID to rename to (e.g., 'light.new_name'). Domain must match the original.(str | None)= nullnew_device_name-New display name for the associated device. If provided, both entity and device are updated in one operation. Empty or whitespace-only never changes the device name: it is reported under warnings when other fields are given and is an error when it is the only update. To clear the device name use ha_set_device(name='').(str | None)= nullFiles
Description
Delete a file from allowed directories in the Home Assistant config. ha_manage_backup(scope="edits") can restore a text file from its most recent auto-backup snapshot; a binary file has no snapshot and its deletion is permanent. Allowed directories: `www/`, `themes/`, `custom_templates/`, `dashboards/`, plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`) configured in the ha-mcp settings UI; `/ssl` is read-only on HAOS and Supervised. Configuration files cannot be deleted; path traversal (../) is blocked. EXAMPLE: ha_delete_file(path="www/deprecated-style.css", confirm=True)
Parameters
pathrequired-File path in an allowed delete directory; pass the absolute path for a HAOS sibling volume. Example: 'www/old-file.css'(str)confirm-Must be True to confirm deletion.(bool)= falseDescription
List files in a directory within the Home Assistant config directory. Returns file names, sizes, and modification times for one of the 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) - `packages/`, plus the folder bound by `homeassistant: packages:` when it has another name - Plus any custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`) configured in the ha-mcp settings UI Path traversal (../) is blocked. EXAMPLE: ha_list_files(path="www/", pattern="*.css")
Parameters
pathrequired-Directory path. Relative to the config dir for the built-in allowlist; absolute for a configured custom directory or HAOS sibling volume. 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. Some files have special handling: - `secrets.yaml`: Values are masked for security - `home-assistant.log` / `home-assistant.log.fault`: 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) - `*.yaml` at any depth under `packages/` and under the folder bound by `homeassistant: packages:` - `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`) configured in the ha-mcp settings UI Path traversal (../) is blocked. EXAMPLE: 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. Examples: 'configuration.yaml', 'www/custom.css', '/share/llm/notes.md'(str)tail_lines-For log files, return only the last N lines. 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 (null when the key is absent); comments and HA tags ('!secret', '!include') survive as written and a '!secret' is never resolved.(str | None)= nullDescription
Write a file to allowed directories in the Home Assistant config. Creates or updates files (custom CSS/JS for dashboards, Jinja2 macro templates, theme YAML) in restricted directories only. **Allowed Write Directories:** - `www/` - Web assets for dashboards - `themes/` - Theme YAML files - `custom_templates/` - Jinja2 template files - `dashboards/` - YAML-mode dashboard files - Plus custom directories OR HAOS sibling volumes (`/share`, `/media`, `/ssl`) from the ha-mcp settings UI; `/ssl` is read-only on HAOS and Supervised 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. EXAMPLE: ha_write_file(path="custom_templates/formatters.jinja", content="{% macro shout(text) %}{{ text | upper }}{% endmacro %}")
Parameters
pathrequired-File path in an allowed write directory; pass the absolute path for a HAOS sibling volume. Example: 'www/custom.css', '/share/llm/out.txt'(str)contentrequired-The content to write to the file.(str)overwrite-Whether to overwrite if the file exists.(bool)= falsecreate_dirs-Whether to create parent directories if they don't exist.(bool)= trueGroups
Description
List Home Assistant entity groups with their member entities. Returns one page of old-style groups created via the group.set service or YAML configuration (platform-specific groups such as light or cover groups are separate entities); `total_count` and `has_more` report the full set. Each group carries Core's state fields and attributes, its members as `entity_ids`, and its object_id. `auto` is true for a group created by ha_config_set_group (group.set) and false for a YAML group. Core does not expose a group's all/any mode. EXAMPLE: ha_config_list_groups(offset=100)
Parameters
limit-Max groups to return per page(int)ge: 1, le: 500= 100offset-Number of groups to skip for pagination(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 come back on group.reload or a restart. - This only removes old-style groups, not platform-specific groups.
Parameters
object_idrequired-Group identifier: a lowercase slug without the 'group.' prefix (e.g., 'living_room_lights')(str)wait-Wait for group to be fully removed before returning.(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. They survive group.reload but not a restart. Core creates group.<object_id> when it does not exist (members from entities or add_entities) and updates it otherwise. entities, add_entities, and remove_entities are mutually exclusive. EXAMPLES: - Create: ha_config_set_group("bedroom_lights", entities=["light.lamp", "light.ceiling"], name="Bedroom Lights") - Add entities: ha_config_set_group("lights", add_entities=["light.extra"])
Parameters
object_idrequired-Group identifier: a lowercase slug without the 'group.' prefix (e.g., 'living_room_lights')(str)entities-List of entity IDs for the group. When updating, replaces all entities; an empty list empties the group, or creates it without members.(list[str] | None)= nullname-Friendly display name; a new group without one is named after object_id(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. Core does not expose the mode, so it cannot be read back and a backup does not restore it.(bool | None)= nulladd_entities-Entities to add to the group's members(list[str] | None)= nullremove_entities-Entities to remove from the group's members(list[str] | None)= nullwait-Wait for group to be queryable before returning. Set to False for bulk operations.(bool)= trueHACS
Description
Get HACS (Home Assistant Community Store) data — search the store or fetch repository details. On Home Assistant 2026.11+ HACS is the built-in Marketplace; the tool uses whichever the instance has. Use ``action="search"`` to search/browse/list store repositories, or ``action="info"`` for one repository's full details as the store reports them (the README is in ``additional_info``). 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") - Before an update: ha_get_hacs_info(action="info", repository_id="owner/repo", include_releases=True) **Caveats:** ``info`` fetches full repository detail from GitHub, so it can hit GitHub rate limits without a connected GitHub account; ``search`` reads the locally cached repository index.
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'). One of the categories the store reports (search's store.categories); 'lovelace' means 'plugin' (dashboard cards).(str | None)= nullinstalled_only-Only return installed repositories (action='search')(bool)= falsemax_results-Maximum number of results (action='search')(int)ge: 1, le: 100= 10offset-Results to skip for pagination (action='search')(int)ge: 0= 0repository_id-Numeric HACS ID or 'owner/repo' path (action='info')(str | None)= nullinclude_releases-Add the repository's GitHub releases and the release notes of versions newer than the installed one (action='info')(bool)= falseDescription
Manage HACS (Home Assistant Community Store) — install/update, remove, add custom repositories, or refresh repository information. On Home Assistant 2026.11+ HACS is the built-in Marketplace; the tool uses whichever the instance has. 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 ("Refresh information" in the Marketplace) — a forced re-fetch of one repository's release data from GitHub, so a pending update becomes visible to the store 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. ``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 (the Marketplace only with a connected GitHub account), so ``update_information`` is the way to surface a just-published release. The Marketplace refuses installs until the connected user has accepted its first-run warning, and adding a repository needs a connected GitHub account; both are steps the user takes in the Marketplace panel.
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'). One of the categories the store reports (search's store.categories); 'lovelace' means 'plugin' (dashboard cards).(str | None)= nullconfirm_replace_built_in-Marketplace only (action='download'): install a repository that replaces the built-in integration of the same domain. Set it only after the user agrees.(bool | 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 configuration for its helper: 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, and area and label assignments. A flow helper record carries entry_id and options instead of id; a custom helper integration's options are withheld, marked `options_withheld`. 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). 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. Listing flow types requires the ha_mcp_tools custom component (>= 1.1.0); without it, such a listing returns a COMPONENT_NOT_INSTALLED error. With helper_type="all", 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. describe=True returns each field's name, type, required flag, options, default and (with helper_id) current value, or the menu_options a menu-based helper needs a menu_choice from. Call it before ha_config_set_helper to learn the config keys for a type. EXAMPLES: - List all counters: ha_config_list_helpers("counter") - List every helper type at once: ha_config_list_helpers("all") - Next page: ha_config_list_helpers("input_boolean", offset=100) - Fields for a template sensor: ha_config_list_helpers("template", describe=True, menu_choice="sensor") For detailed helper documentation, use ha_get_skill_guide.
Parameters
helper_typerequired-Helper type to list: a storage type (input_boolean, counter, timer, ...), a helper flow Home Assistant lists (template, group, utility_meter, ...), or 'all' for every type in one call.(StorageHelperType | Literal['all'] | str)limit-Max helpers to return per page(int)ge: 1, le: 500= 100offset-Number of helpers to skip for pagination(int)ge: 0= 0describe-Instead of listing, return the fields ha_config_set_helper accepts in config for helper_type, as Home Assistant reports them (the form the HA UI shows).(bool)= falsemenu_choice-describe only: sub-type of a menu-based helper (template, group), e.g. 'sensor'.(str | None)= nullhelper_id-describe only: an existing helper (config entry id for flow helpers, entity_id or id otherwise); each field then carries its current value.(str | None)= nullDescription
Create or update Home Assistant helpers: storage types, every helper flow HA lists, and config subentries. MUST call ha_get_skill_guide OR refer to your locally installed skills first. ``helper-selection.md`` ships under ``skill_content`` by default. SIMPLE types (pass `config` dict): input_boolean, input_button, input_select, input_number, input_text, input_datetime, counter, timer, schedule, zone, person, tag. Create requires `name`; update requires `helper_id`. FLOW types (pass `config` dict, Config Entry Flow API): every helper flow Home Assistant lists, read from it at call time — template, group, utility_meter, derivative, min_max, threshold, integration, statistics, trend, ... and custom helper integrations. Create requires `name`; for updates pass the existing entry_id as `helper_id` (options flows reject the `name` key). Do not create `otp` here although Home Assistant lists it: the user sets it up in the HA UI, since its secret is a credential they enroll in an authenticator app. CONFIG_SUBENTRY type (Config Subentry Flow API): pass `entry_id`, `subentry_type` and `config`; pass `subentry_id` to reconfigure an existing subentry, omit it to create one. Behavior: - UPDATE preserves type-specific fields not re-passed (a rename never wipes initial/icon/etc.); flow-helper and config subentry updates behave the same way (see `config`). - Omitted `action` falls back to the `helper_id`-presence discriminator (SIMPLE/FLOW) or the `subentry_id`-presence discriminator (config subentries). - For flow-based helpers, config keys not declared by any step's data_schema are silently ignored by HA. Validation errors carry the helper's `data_schema` (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: - input_number: ha_config_set_helper(helper_type="input_number", name="Target", config={"min": 0, "max": 100, "step": 5}) - 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: ha_config_set_helper(helper_type="group", name="Kitchen Lights", config={"group_type": "light", "entities": ["light.a", "light.b"]}) - config subentry: ha_config_set_helper(helper_type="config_subentry", entry_id="01HXYZ...", subentry_type="conversation", config={"name": "Local agent", "model": "gemma3:27b"})
Parameters
helper_typerequired-Type of helper to create or update.(StorageHelperType | Literal['config_subentry'] | str)name-Display name for simple/flow helper creation. 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 ignored because options flows don't expose renaming; change the resulting entity's display name with ha_set_entity(name=...).(str | None)= nullhelper_id-The helper's storage id ('my_button'), not the name of its entity, or its 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'.(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'); '' or ' ' clears it, except a zone's stored icon (the icon in the zone's own config, as the Home As…
sistant UI stores it; ha_get_zone shows it), which cannot be removed. On create, a storage helper's icon goes into its stored config when Home Assistant's schema for the type has an icon field, otherwise (person, tag, a new zone, or without the ha_mcp_tools component) into the entity registry; an update always sets the registry icon, and the stored icon too where the schema has one. Restoring a storage helper's automatic backup also puts back its registry icon, unless the backup predates registry icons being recorded; a flow helper's backup holds no icon, so its restore does not set onenullarea_id-Area ID for the helper; '' or ' ' clears it(str | None)= nulllabels-Labels to categorize the helper(str | list[str] | None)= nullmin_value-(float | None)= nullmax_value-(float | None)= nullstep-(float | None)= nullunit_of_measurement-(str | None)= nulloptions-(str | list[str] | None)= nullinitial-(str | bool | int | float | None)= nullmode-(str | None)= nullhas_date-(bool | None)= nullhas_time-(bool | None)= nullrestore-(bool | None)= nullduration-(str | int | float | None)= nullmonday-(list[dict[str, Any]] | None)= nulltuesday-(list[dict[str, Any]] | None)= nullwednesday-(list[dict[str, Any]] | None)= nullthursday-(list[dict[str, Any]] | None)= nullfriday-(list[dict[str, Any]] | None)= nullsaturday-(list[dict[str, Any]] | None)= nullsunday-(list[dict[str, Any]] | None)= nulllatitude-(float | None)= nulllongitude-(float | None)= nullradius-(float | None)= nullpassive-(bool | None)= nulluser_id-(str | None)= nulldevice_trackers-(list[str] | None)= nullpicture-(str | None)= nulltag_id-(str | None)= nulldescription-(str | None)= nullpattern-(str | None)= nullcategory-Category ID for this helper (ha_config_get_category(scope='helpers') lists them, ha_config_set_category() creates one); '' or ' ' clears it.(str | None)= nullconfig-Type-specific fields. On update it is a patch: a field you omit keeps its current value. SIMPLE types take these keys: input_select: options…
(list, required), initial. input_number: min, max (required), step, unit_of_measurement, mode ('box'/'slider'), initial. input_text: min, max (length), mode ('text'/'password'), initial, unit_of_measurement, pattern (regex the value must match). input_datetime: has_date, has_time, initial. input_boolean: initial. counter: initial (starting value), minimum, maximum, step, restore (default true). timer: duration ('HH:MM:SS' or seconds), restore (default false). schedule: monday..sunday, each a list of {'from': 'HH:MM', 'to': 'HH:MM'} with optional 'data' dict of extra attributes. zone: latitude, longitude (both required), radius (meters, default 100), passive (won't trigger person state changes). person: user_id, device_trackers (device_tracker entity IDs), picture (URL). tag: tag_id (omit on create to auto-generate), description. On input_* types, `initial` — even false/0 — disables last-state restore and forces that value on every HA restart. ha_config_list_helpers(helper_type, describe=True) lists a type's fields. FLOW types and config_subentry take the flow's fields; 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.nullwait-Wait for helper entity to be queryable before returning. Set to False for bulk operations.(bool)= trueaction-Explicit intent: 'create' a new helper or 'update' an existing one. Pass it so a helper_id typo fails as 'helper not found' instead of creating a helper.(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; helper_type picks the path, or the entity registry does when an entity_id comes without one. 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. - ha-mcp's own ha_mcp_tools entries are refused: manage them in the HA UI, or (server settings, developer mode on) ha_dev_manage_settings / ha_dev_manage_server. ROUTING: - SIMPLE helper_type (input_button, input_boolean, input_select, input_number, input_text, input_datetime, counter, timer, schedule, zone, person, tag) + bare helper_id (its storage id) or entity_id → websocket delete. - FLOW helper_type (any helper flow Home Assistant lists: template, group, utility_meter, derivative, ...; custom helper integrations too) + full 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. An entity registered by another integration is refused with VALIDATION_INVALID_PARAMETER and nothing is deleted. - helper_type=None + entity_id → the entity's registry entry names its helper (storage type or helper flow); an entity of any other integration is refused with VALIDATION_INVALID_PARAMETER. - helper_type=None + entry_id → direct config entry delete (any integration). - helper_type="config_subentry" + parent entry_id + subentry_id → delete one config subentry. A target that cannot be removed raises a structured error rather than returning silent success: ENTITY_NOT_FOUND when the entity_id is absent from both the state machine and the entity registry, or for a bare helper_id no stored helper has (a bare helper_id on a FLOW target also raises it — FLOW resolution needs a full entity_id); RESOURCE_NOT_FOUND when it exists but is not storage- or registry-managed (a YAML-configured helper, or an entity with a state but no registry entry), for a config entry the backend reports as 404, or for a missing config subentry. Calling N times gives the same response. Transient connectivity failures 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 whatever helper owns an entity: ha_remove_helpers_integrations(target="sensor.energy_peak", 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. ha_manage_backup(scope="edits", action="restore") recreates removed FLOW helpers whose options could be snapshotted, and config subentries. It cannot recreate custom helper integrations (their options are withheld) or otp (no stored options); re-add those yourself, otp through the user in the HA UI.
Parameters
targetrequired-What to remove. One of: (a) bare helper_id for SIMPLE helpers: the helper's storage id, not the name of its entity, e.g. 'my_button'; (b) full entity_id, e.g. 'input_button.my_button' or 'sensor.my_meter'; (c) config entry_id for any integration, with helper_type omitted, e.g. value from ha_get_integration(); (d) parent config entry_id for a config subentry.(str)helper_type-Helper type of target; ROUTING says which value takes which removal path.(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 for a direct config entry delete (an entry_id target) or helper_type='config_subentry' (no entity poll, require_restart returned).(bool)= trueHistory & Statistics
Description
Get execution traces for automations and scripts to debug issues. Traces show what triggered a run, which conditions passed or failed, which actions executed (for 'choose', which branch was taken), any errors, and variable values during execution. Home Assistant keeps only the most recent runs of each automation or script (its stored_traces setting), so older runs are not available. The 'state' field shows 'stopped' (completed), 'running', or an error state. USAGE MODES: 1. List recent traces (omit run_id): returns a summary of recent runs with timestamps, triggers, and status. Use `offset` to page deeper when `has_more` is true. ha_get_automation_traces("automation.motion_light") 2. Get a detailed trace (provide run_id): full execution details including trigger info, condition results, action trace with timing, and context variables. ha_get_automation_traces("automation.motion_light", run_id="1705312800.123456") 3. Add logbook entries and context metadata: detailed=True. Script-style action paths (sequence/, numeric) are always matched regardless of this flag. 4. Variables at every step (trigger sets and null entries are still omitted): deduplicate=False.
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.(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. Set to False to record the variables at every step; steps whose variables carry 'trigger', and null-valued entries, are omitted either way.(bool)= truedetailed-Include extra diagnostic data: logbook entries and context metadata.(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' returns most-recent first; 'oldest' returns chronological-first.(Literal['newest', 'oldest'])= "newest"Description
Get historical data from Home Assistant's recorder. WHEN NOT TO USE: For current values, use ha_get_state. To inspect Energy Dashboard preferences and configured statistics, use ha_manage_energy_prefs(mode="get", include_statistics=True). WHEN TO USE: Use source="history" (default) to troubleshoot why a value changed, check event sequences, or analyze recent patterns. Use summary=True when the question needs a count or a duration ("was it ever unavailable?") rather than the rows. Use source="statistics" for long-term trends and period averages. CAVEATS: History rows rename Core's compact keys once: s -> state, a -> attributes, lu -> last_updated, lc -> last_changed. History event times are ISO strings in HA's timezone (UTC if unavailable). When Core omits lc, last_changed falls back to last_updated; this may not be the actual state-change time with significant_changes_only (default) or a window-start snapshot. Attributes and unknown fields pass through unchanged. Statistics timestamps use Unix milliseconds. Minimal history may omit attributes. No duplicate compact aliases are added. include_schema=True retains core_contract even when fields selects other data keys. Output units come from Core recorder metadata and reflect its display-unit conversion; unresolved units include a reason. statistics_metadata preserves Core's native fields. 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. With source="history", summary=True does not combine with offset > 0. 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. EXAMPLES: - ha_get_history(entity_ids="sensor.bedroom_temperature", start_time="24h") - Paginate oldest-first: ha_get_history(entity_ids="sensor.temperature", start_time="7d", limit=100, offset=100, order="asc") - Time per state: ha_get_history(entity_ids="binary_sensor.door", start_time="7d", summary=True) - ha_get_history(source="statistics", entity_ids="sensor.living_room_temperature", start_time="6m", period="month", statistic_types=["mean", "min", "max"])
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" for raw state changes at full resolution (~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 a look-back from now with unit min, h, d, w or m (= 30 days), e.g. '30min' = 30 minutes ago. Default: 24h ago for history, 30d ago for statistics(str | None)= nullend_time-End time: same formats as start_time. Omit it to end at the current time(str | None)= nullminimal_response-Return only states/timestamps without attributes. Ignored when source="statistics"(bool)= truesignificant_changes_only-Filter to significant state changes only. Ignored when source="statistics"(bool)= truelimit-Max entries per entity. Default: 100. For source="history": state rows; with summary=True, the states listed in summary.states, longest first. For source="statistics": aggregated rows. With multiple entity_ids, 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.(int | None)ge: 0= nullperiod-Aggregation period: "5minute", "hour", "day", "week", "month", "year". Ignored when source="history"(str)= "day"statistic_types-Native Core statistics types (for example "sum", "change", "last_reset"). Default: Core chooses all. Use include_schema=True to discover the installed contract. Ignored when source="history"(str | list[str] | None)= nullorder-Sort order for history entries. "desc": newest first. "asc": oldest first. Ignored when source="statistics".(Literal['asc', 'desc'])= "desc"summary-Return per-entity totals instead of state rows: changes (switches to a different state; the state at the window start is not one, but without that state, e.g. with core_options {'include_start_time_state': false} or purged history, the first recorded state counts as one), first_change and last_change (null without changes), and per state its periods (separate spells) and duration_s (seconds, up to end_time or now, whichever is earlier). distinct_states counts every state, listed or not. Ignored when source="statistics".(bool)= falsefields-Return only the specified keys within data to reduce response size. None = full response. History keys: success, source, entities, period, query_params. Statistics keys: success, source, entities, period_type, time_range, statistic_types, query_params. Warnings and timezone metadata remain outside data and are always retained.(str | list[str] | None)= nullinclude_schema-Include the running Core's native request schema when the component supports discovery.(bool)= falsecore_options-Additional native Core request fields, e.g. {'units': {'energy': 'MWh'}} for statistics. Core validates them. Cannot override fields controlled by the tool's parameters or query limits.(dict[str, Any] | None)= nullDescription
Get Home Assistant logs from various sources. Prefer source='system' for triage: it returns HA's own deduplicated system_log entries with counts, first_occurred and the full exception traceback, and its counts run since each error first occurred. error_log with structured=True counts only what is inside the fetched window (reported as window_start/window_end; every install reads a capped window) and drops tracebacks; structured=False returns the raw records instead, so it covers what falls outside system_log's WARNING+ in-memory buffer. Use structured=True for the per-component rollup; in structured mode limit/order do not apply: issues are ranked by count, then severity, then recency, over a fixed deep window. Raw-text sources (error_log, supervisor, system_service) read a bounded window per call, so level/search filter and limit slice within that window only; window_lines reports the size requested. Logbook responses carry has_more plus a pagination_hint; error_log and fault_log carry has_more with a next_offset to pass back while it stays true.
Parameters
source-'logbook': entity state-change history. 'system': HA's structured system_log entries (errors, warnings) with full exception tracebacks; sear…
ch also matches those exceptions. 'error_log': raw log text (home-assistant.log on container/pip installs; HA Core's journald stream on Supervisor-backed installs), grouped into records: a header line starts one and every following line that is not itself a header joins it — traceback frames, the blank lines around chained exceptions, and non-HA banners such as s6-overlay restart lines. 'supervisor': app (add-on) container logs (needs slug). 'system_service': Supervisor-managed system service logs (needs slug). 'logger': effective log level per integration (confirms ha_set_integration(log_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, which never reaches journald or error_log; empty on a healthy install (crash_recorded=False); whole crash blocks are ordered with each block's lines kept in place; reads through the 'HA-MCP File & YAML Tools' entry."logbook"limit-Max entries/lines to return. For source='error_log' with structured=False it bounds returned lines: whole records are selected within it, and a record longer than it is cut (header and last lines kept) with a warning naming how many lines were cut. Does not apply to source='error_log' with structured=True.(int | None)= nullsearch-Keyword filter on entries/lines. For source='system' it matches message, name and exception text; for the raw error_log path it matches any line of a record, so a traceback frame brings back its header and siblings. Structured error_log mode matches message and logger name only; source='logger' matches the integration domain.(str | None)= nullorder-Sort order for time-ordered sources (logbook, system, error_log, supervisor, system_service, fault_log): 'newest' returns most-recent first; 'oldest' returns chronological-first. For raw-text sources it sets the read direction of the most-recent window; fault_log orders whole crash blocks; error_log orders whole records and keeps each record's lines in place. Ignored for source='logger', and for source='error_log' with structured=True.(Literal['newest', 'oldest'])= "newest"hours_back-Logbook only: how many hours back to read.(int)ge: 1= 1entity_id-Logbook only: restrict to one entity, or a comma-separated list of entity IDs.(str | None)= nulldevice_id-Logbook only: native Core device filter - events attributed to this device and the context chains they caused. State changes of the device's entities are not included; filter by entity_id for those. Requires the WebSocket path.(str | None)= nullcontext_id-Logbook only: only events sharing this context - 'what caused this'. Cannot be combined with entity_id or device_id; works on both transports when used alone.(str | None)= nullend_time-Logbook only: end of the window (ISO datetime).(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, or is the opaque token a previous page returned in 'next_offset' on Supervisor/journald installs, which keeps paging exact while new log lines arrive (`structured=True` accepts and returns that token too); 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 | str)= 0compact-logbook / system only: save context. Logbook strips attribute dicts; system keeps the start and end of each message string within 2000 characters (counted in 'truncated_messages'). search still matches the full message; text inside the cut is not shown. To read one message whole, pass False with a narrow search and limit=1.(bool)= truelevel-system / error_log only: keep only entries at exactly this level (ERROR, WARNING, INFO, DEBUG, CRITICAL); it is not a threshold. For source='error_log' it matches the record's header only, so matching text in a traceback frame does not satisfy it.(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. Ignored for other sources.(bool)= falsetop_n-Max distinct issues to return when structured=True (default 20, capped at 500).(int | None)ge: 1= nullslug-source='supervisor': app slug, e.g. 'core_mosquitto' (use ha_get_app() to list installed slugs). source='system_service': service name, one of supervisor, host, core, dns, audio, multicast — here 'supervisor' is the Supervisor service's own logs, not an app with that name.(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 / search: ha_get_integration(query="zigbee") - Get an entry with its editable fields: ha_get_integration(entry_id="abc123", include_schema=True) - Diagnostics dump, paged along a list-valued path: ha_get_integration(entry_id="abc123", include_diagnostics=True, diagnostics_data_path="<list-valued path>", diagnostics_data_limit=10, diagnostics_data_offset=20) - Inspect a subentry reconfigure schema: ha_get_integration(entry_id="abc123", include_subentry_schema=True, subentry_type="conversation", subentry_id="sub123") - List template entries with their options: 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. Values that match a ``secrets.yaml`` entry are returned as ``"**redacted**"``. Use ``include_schema=True`` to see every editable field and its default/type. The shape depends on the read path, which the response does not name: when the ha_mcp_tools custom component serves the read, ``options`` are the stored mapping as-is, so a field never set may be absent and a section's fields stay nested under the section key (e.g. a template helper's ``additional_options``). Otherwise they are read from the options flow form, which shows unset fields at their schema default and lists a section's fields at the top level. Check both places for a section field. Each entry carries ``log_level``: the canonical Python logger level name (``DEBUG``/``INFO``/``WARNING``/``ERROR``/``CRITICAL``) when the integration has a log-level override (set one with ``ha_set_integration(log_level=...)``), or ``"DEFAULT"`` (uppercase sentinel) when no override is set; and ``log_level_raw``: the original numeric level (e.g. ``10`` for DEBUG) when HA returned an int, ``None`` otherwise. This is distinct from the app 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.(str | None)= nullquery-When listing, search by domain or title.(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). Only applies when supports_options=true.(bool)= falseinclude_subentries-When entry_id is set, include config subentries for the integration entry.(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. Set to False for fuzzy matching when the query may contain typos.(bool)= truelimit-Max entries to return per page in list mode(int)ge: 1, le: 200= 50offset-Number of entries to skip for pagination(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 same artifact as the UI's 'Download diagnostics'. Payloads can be large (Hue ~290 KB, ZHA/MQTT/ESPHome several MB) — pair with diagnostics_fields or diagnostics_truncate_at_bytes.(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 GA table is not in the diagnostics dump; per-entity GA assignments are (config_store / configuration_yaml). Ignored (with a warning) when the entry is not a KNX integration. KNX exposes a single project, so the result is the same for every KNX entry_id.(bool)= falsedevice_id-With include_diagnostics=True, return the device-scoped diagnostics dump for this device instead of the full integration dump. Some integrations only expose config-entry-level dumps.(str | None)= nulldiagnostics_fields-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 when include_diagnostics=True and the data payload is a dict. Unknown keys are dropped and listed under omitted_fields.(list[str] | str | None)= nulldiagnostics_truncate_at_bytes-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-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. Only applies when include_diagnostics=True.(str | None)= nulldiagnostics_data_offset-Pagination start index 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. 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. Optional sections are selected with `include`. The result may include ``ha_mcp_update`` ``{current, latest, update_available}``. When update_available is true, tell the user a newer ha-mcp release is available. EXAMPLES: - ha_get_system_health(include="repairs,zha_network,zwave_network,config_check") - Page a list-valued diagnostics sub-tree: ha_get_system_health(include="diagnostics", config_entry_id="abc", diagnostics_data_path="<list-valued path>", diagnostics_data_limit=10), then repeat with diagnostics_data_offset=10 while has_more is true.
Parameters
include-Comma-separated extra sections: 'repairs' (Repair items, active only unless include_dismissed_repairs=True); 'zha_network' (ZHA devices with…
radio signal summary: name, LQI, RSSI); 'zha_network_full' (all ZHA device details; large on 100+ device networks); 'zwave_network' (Z-Wave JS status and node summary: status, security, routing); 'thread_network' (per border-router channel, extended_pan_id and border_agent_id; not per-node Thread health); 'matter_network' (Matter integration presence: config_entry_id, state, title; per-node health is in Matter node diagnostics); 'themes' (installed theme names, count, default_theme, default_dark_theme); 'diagnostics' (per-integration diagnostics dump, integration-defined JSON; REQUIRES config_entry_id; payloads can be large — pair with diagnostics_fields or diagnostics_truncate_at_bytes); 'config_check' (validate the HA configuration, the pre-restart check ha_restart runs automatically; returns {result: valid|invalid, is_valid, errors}); 'dead_entities' (orphaned/stale entity-registry entries: config_entry_orphans whose owning integration instance is gone, and stale_restored entries HA restored on startup that the loaded integration no longer provides, each with entity_id + platform for cleanup via ha_remove_entity; unknown-state entities and merely-offline devices are excluded).nullinclude_dismissed_repairs-Include user-dismissed/ignored repairs. Only meaningful when 'repairs' is in include. Dismiss or restore one with ha_manage_updates(action='ignore_repair' / 'unignore_repair').(bool | None)= falseconfig_entry_id-Config entry ID of the integration (find via ha_get_integration). Required when include contains 'diagnostics'.(str | None)= nulldevice_id-With include='diagnostics', return the device-scoped dump for this device instead of the full integration dump. Some integrations only expose config-entry-level dumps.(str | None)= nulldiagnostics_fields-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'.(list[str] | str | None)= nulldiagnostics_truncate_at_bytes-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). Recommended starting point: 20000. Only applies with include='diagnostics'.(int | None)ge: 1= nulldiagnostics_data_path-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 with include='diagnostics'.(str | None)= nulldiagnostics_data_offset-Pagination start index for list-valued diagnostics_data_path results. Only applies with include='diagnostics'.(int | None)ge: 0= 0diagnostics_data_limit-Pagination window for list-valued diagnostics_data_path results; data becomes {path, items, offset, limit, total, has_more}. Only applies with include='diagnostics'.(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). - Reconfigure: entry_id + reconfigure=True + config — connection settings such as host, port, credentials. Repeat with the token the preflight returns as confirm_token to apply. - Log level: log_level + domain (or entry_id) — sets how much the integration logs; read it back with ha_get_integration's log_level. WHEN NOT TO USE: - Helpers (template, group, utility_meter, ...): use ha_config_set_helper. `otp` is the user's to set up in the HA UI: its secret is a credential enrolled in an authenticator app; do not create it. - Config subentries: use ha_config_set_helper(helper_type='config_subentry'). - Removing an entry: use ha_remove_helpers_integrations. - ha-mcp's own ha_mcp_tools entries are refused: manage them in the HA UI, or (server settings, developer mode on) ha_dev_manage_settings / ha_dev_manage_server. 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. 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 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: - 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"}), then repeat adding confirm_token="sha256:..." - Debug logging: ha_set_integration(domain="zha", log_level="DEBUG"), then log_level="DEFAULT" to stop
Parameters
entry_id-Config entry ID of an existing integration.(str | None)= nullenabled-True to enable, False to disable the entry. Mutually exclusive with 'domain' and 'config'.(bool | None)= nulldomain-Integration domain to add (e.g. 'workday', 'local_calendar').(str | None)= nullconfig-Flow form data. 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. 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)= nulllog_level-Set the integration's log level, like the integration page's Enable/Disable debug logging: pass the integration with 'domain' (no config flow runs) or 'entry_id', and nothing else. Lasts through the next Home Assistant restart. DEFAULT clears the override: the integration then logs at the inherited default level, and a level set for it in configuration.yaml returns only after a restart.(Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'DEFAULT'] | 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. EXAMPLES: - 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.(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. 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.(str | None)= nullDescription
Delete a Home Assistant category. Removes the category from the category registry for the given scope. 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.(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 cross-domain tagging system for entities, devices, and areas. EXAMPLES: - Create: ha_config_set_label("Security", color="red", icon="mdi:shield", description="Security-related devices") - Update: 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).
Parameters
namerequired-Display name for the label(str)label_id-Label ID for updates.(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 node diagnostics and network summaries prefer ha_get_device / ha_get_system_health, which mirror 'diagnostics' and 'network_status'. This tool also exposes active 'ping' probes and parameter reads. Z-Wave 'get_config_params' lists cached configuration values/metadata; 'get_config_param' reads one from the cache or explicitly from the device. Neither enables or creates entities. Write actions perform inclusion/commissioning, removal, healing, reconfiguration, firmware updates and credential provisioning. Caveats: 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. HA administrator access is required for parameter reads.
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). Z-Wave get_config_param: property (parameter number), endpoint (default 0), property_key (partial-parameter bit mask), refresh (default False; full root parameters only).(dict[str, Any] | None)= nullconfirm-Required (True) to run destructive actions (e.g. remove_device, network restore, change_channel, hard_reset, remove_fabric).(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 (pass that hash to ha_config_set_scene for python_transform updates). Listing returns compact metadata. Integration-managed scenes have no editable storage config or scene_id. Partial content-search results explicitly report unread configs and are not exhaustive. EXAMPLES: - Get scene: ha_config_get_scene("movie_night") - Find scenes: ha_config_get_scene(query="movie") - Find attribute values: ha_config_get_scene(query="rainbow", search_in_config=True) For detailed scene configuration help, use ha_get_skill_guide.
Parameters
scene_id-Scene storage ID from listing (e.g. 'movie_night') or the scene's entity_id; an entity_id is resolved to the storage ID.(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. EXAMPLE: ha_config_remove_scene("old_scene") Only scenes created via the Home Assistant UI can be deleted. Scenes defined in YAML configuration files (scenes.yaml or configuration.yaml) cannot be deleted through the API and return a 405 Method Not Allowed error; edit the configuration file directly instead. 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.(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. Either mode can also take ``activate``, which can be passed alone with ``scene_id`` to activate a scene without touching its config. WHEN TO USE: - ``python_transform``: surgical edits to an existing scene (add/remove/update a single entity entry). - ``config``: creating a new scene, or wholesale replacement. - ``activate`` (with scene_id, no config): activate any scene, e.g. ha_config_set_scene(scene_id="scene.movie_night", activate=True). WHEN NOT TO USE: - To list or look up existing scenes, use ha_config_get_scene. SCENE SHAPE: 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. WARNING: Expressions with infinite loops will hang the…
server. Examples: Add entity: python_transform="config['entities']['light.bed'] = {'state': 'on'}" Update brightness: python_transform="config['entities']['light.kitchen']['brightness'] = 50" Remove entity: python_transform="del config['entities']['light.kitchen']" 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, index, count, sort, reverse - Dict methods: update, get, setdefault, keys, values, items, pop, clear - 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, lstrip, rstrip, removeprefix, removesuffix, split, join, replace, count, index - 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 insteadnullconfig_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. Set to False for bulk operations.(bool)= trueactivate-Activate the scene (scene.turn_on). Alone with scene_id it activates any scene entity, including integration and YAML scenes (pass their entity_id). With config or python_transform it runs after Home Assistant has reloaded scenes from the write.(bool)= falseMandatoryBPS-(bool)= trueBestPracticeKey-(BestPracticeKeyParam)= nullScripts
Description
Get Home Assistant script configuration. Returns the complete configuration for a script, including sequence, mode, fields, and other settings. The returned `config_hash` stays the same across consecutive reads of an unchanged config. 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. Prefix handling matches `ha_config_get_automation` in behavior (mechanism differs: automations resolve through the entity registry; 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. EXAMPLE: ha_config_remove_script("old_script") Only scripts created via the Home Assistant UI can be deleted. Scripts defined in YAML configuration files (scripts.yaml or configuration.yaml) cannot be deleted through the API and return a 405 Method Not Allowed error; edit the configuration file directly instead. 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.(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 (`choose` / `if`, `wait_for_trigger`, `repeat`, `for:`) over templates in logic positions; templates belong only in `data.*`, notification text, `event_data` and `variables`. The best-practice checker reports violations under `best_practice_warnings`. `automation-actions.md`, `triggers-and-conditions.md` and `template-guidelines.md` ship under `skill_content` by default. Scripts use 'sequence', NOT 'trigger' or 'action'; for trigger-based execution use ha_config_set_automation. MODES (pick one): - python_transform: surgical edits to an existing script. Requires config_hash from ha_config_get_script(), e.g. python_transform="config['sequence'].append({'delay': {'seconds': 5}})" - config: new scripts or full restructures. Needs 'sequence' (regular) or 'use_blueprint' {path, input} (blueprint-based). - take_control_of_blueprint: convert a blueprint-backed script into a standalone one. Takes no config of its own. - run (alone with script_id): 'start' runs the script (optional variables), 'stop' stops its running executions. Write config changes in a separate call first: Home Assistant signals no completion of the script reload a write triggers. EXAMPLES: - Create: ha_config_set_script(script_id="blink_light", config={"alias": "Light Blink", "sequence": [{"action": "light.turn_on", "target": {"entity_id": "light.living_room"}}, {"delay": {"seconds": 2}}, {"action": "light.turn_off", "target": {"entity_id": "light.living_room"}}]}) - From a blueprint: ha_config_set_script(script_id="notification_script", config={"alias": "My Notification Script", "use_blueprint": {"path": "notification_script.yaml", "input": {"message": "Hello World"}}}) - Take control: ha_config_set_script(script_id="notification_script", take_control_of_blueprint=True) TAKE CONTROL is one-way: later blueprint edits stop reaching the script; to change an input value, update 'use_blueprint.input' instead. It 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", path=..., input=...).
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. Mutually exclusive with python_transform.(dict[str, Any] | None)= nullpython_transform-Python expression to transform existing script config. Mutually exclusive with config. WARNING: Expressions with infinite loops will hang th…
e 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, index, count, sort, reverse - Dict methods: update, get, setdefault, keys, values, items, pop, clear - 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, lstrip, rstrip, removeprefix, removesuffix, split, join, replace, count, index - 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 insteadnullconfig_hash-Config hash from ha_config_get_script for optimistic locking. Required for python_transform; 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 keeps its script_id, alias and description and then has its own sequence and no 'use_blueprint'. Mutually exclusive with config and python_transform.(bool)= 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. Set to False for bulk operations.(bool)= truerun-Run control, used alone with script_id (no config, python_transform or take_control_of_blueprint): 'start' runs the script (script.turn_on; returns once it has started, without waiting for it to finish), 'stop' stops its running executions (script.turn_off). Stopping does not disable the script; Home Assistant has no script enable/disable.(Literal['start', 'stop'] | None)= nullvariables-With run='start': values for the script's fields, passed to the run as its variables.(dict[str, Any] | None)= nullMandatoryBPS-(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. Requests whose fields= are composed only of system_info, notification, repair, or server metadata fields 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 the ha-mcp settings-UI sidecar is running (stdio mode, e.g. Claude Desktop / Claude Code) the response carries ``settings_url``, the local URL of the tool-configuration page; in standalone HTTP / Docker modes with an HTTP settings prefix it instead carries ``settings_url_hint``, saying where the page is mounted and how to construct the full URL. Hand whichever is present to the user when they ask how to enable or disable tools or change server settings. The response also carries ``ha_mcp_update`` ``{current, latest, update_available}`` (PyPI for pip/Docker, the Supervisor store for the app) — proactively tell the user when ``update_available`` is true. Omitted for the ``unknown`` version, when ``HA_MCP_DISABLE_UPDATE_CHECK`` is set, or when the update check itself failed. ``mcp_client`` reports the MCP protocol version, client info and client capabilities this call arrived with, for diagnosing client-specific behaviour. ``capabilities: null`` means the request carried none to read, not that the client declared none.
Parameters
detail_level-'minimal': 10 entities/domain, top-5 states; 'standard': 200 entities/page, top-10 states (use offset for more); 'full': 200 entities/page + entity_id + state + full states.(Literal['minimal', 'standard', 'full'])= "minimal"domains-Filter to specific domains (e.g. 'light,sensor' or ['light','sensor']). None = all domains.(str | list[str] | None)= nulllimit-Max total entities across all domains (default: unlimited for minimal, 200 for standard/full).(int | None)ge: 1= nulloffset-Number of entities to skip for pagination(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.(bool | None)= trueinclude_dismissed_repairs-Include user-dismissed/ignored repairs. Dismiss or restore one with ha_manage_updates(action='ignore_repair' / 'unignore_repair').(bool | None)= falsefields-Return only the specified top-level response keys to reduce response size (e.g. ["system_info", "domain_stats"]). None = full response. Avai…
lable 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, mcp_client. 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), ``ha_mcp_update`` (when an update check applies) and ``mcp_client`` are emitted regardless of ``fields=`` projection.nullDescription
Get current status, state, and attributes of one or more entities (lights, switches, sensors, climate, covers, locks, fans, etc.). Pass a string entity_id for one entity, or a list (max 100, duplicates deduplicated) for several, fetched in parallel. A bulk call returns success=True if at least one state was retrieved; check 'error_count' for failed lookups. `fields=` projects the per-entity record keys, NOT the outer bulk response wrapper: in single-entity mode it filters the returned record; in bulk mode it filters each record inside `states[entity_id]` while 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 and a `warnings` list is emitted at the top level of the result (sibling of `data`/`metadata`), outside the projected record(s), so `fields=["state"]` still returns a record with only `state`. EXAMPLES: - Single: ha_get_state("light.kitchen") - Multiple: ha_get_state(["light.kitchen", "light.living_room", "sensor.temperature"]) - 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. 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. Unknown keys are silently dropped.(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`. - **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. 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. Examples: - Find a light by name: ha_search("kitchen", domain_filter="light") - List sensors in an area: ha_search(domain_filter="sensor", area_filter="Living Room") - 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") - All unavailable entities: ha_search(state_filter="unavailable")
Parameters
query-What to search for (entity name fragment, free-text config term, entity_id). 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.(str | None)= nulldomain_filter-Narrow entity-registry results to a single domain (e.g. 'light', 'sensor').(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.(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.(str | list[str] | None)= nulllimit-Maximum results per surface (entities, configs).(int)ge: 1= 10offset-Number of results to skip for pagination.(int)ge: 0= 0exact_match-Exact substring matching. 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. Otherwise summaries 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. 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. 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 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. Each item requires exact entity_id and action. Use action='off', not service='turn_off'.(list[SkipValidation[BulkControlOperation]])parallel-Dispatch operations concurrently (default) or one at a time when False.(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-Selector mode only: confirmation wait in seconds for every resolved entity. In operations mode set timeout_seconds on each operation instead; a top-level value is rejected in operations mode.(float | None)ge: 0, le: 60, allow_inf_nan: false, strict: true= nullvalidate_first-Selector mode only: report ENTITY_NOT_FOUND for a target that does not exist. In operations mode set validate_first on each operation instead; a top-level False is rejected in operations mode.(bool)strict: true= truedry_run-Selector mode only; True is rejected in operations mode.(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. To run an automation now, use ha_config_set_automation(entity_id=..., run_actions=True). 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; a successful dispatch reports the bus accepted the event and how many listeners were registered for it, not that any of them acted. A timeout means dispatch is unconfirmed. listener_count covers the exact event type; wildcard_listener_count covers match-all listeners (the recorder is one, and it only stores the event). The counts come from the component's dispatch reply when it serves the fire, otherwise from a GET /api/events lookup taken before dispatch; a route that cannot report them omits both fields. The response carries the event's context_id (and context) when Home Assistant reports it. An automation triggered by this event records that id as the parent_id of its run's context, so looking up that automation trace proves whether a listener acted — which the counts cannot show, because an event trigger that filters on event_data still registers as a listener when the payload does not match. Events fire in-process through the ha_mcp_tools component when it is installed, otherwise over Home Assistant's own WebSocket fire_event command; both carry the event type in the message body, so any type Core accepts fires as written and Core's rejection comes back as the error text (for example its 64-character event-type limit). state_changed alone keeps the REST route, because only that route rebuilds its old_state/new_state payloads into State objects; there event_type must be a plain name with no / \ ? # or % characters.
Parameters
event_typerequired-Event type to fire, e.g. 'my_custom_event'.(str)data-Optional event payload, delivered to subscribers as the event's data (trigger.event.data in an automation).(dict[str, Any] | None)= nullDescription
Call any Home Assistant service or one-shot WebSocket command: the catch-all escape hatch. When NOT to use: a dedicated tool that covers the job. It validates the request, reports the outcome in its own terms, and stays available where a security policy gates this tool. For example: turn an automation on or off or run it now with ha_config_set_automation (enabled / run_actions); start or stop a script with ha_config_set_script (run); activate a scene with ha_config_set_scene (activate); manage apps (add-ons) with ha_manage_app; handle updates and Repairs issues with ha_manage_updates; set an integration's log level with ha_set_integration (log_level). When to use: a service no dedicated tool covers. Services follow the pattern domain.service (e.g., light.turn_on, climate.set_temperature); ha_list_services lists them with their fields. EXAMPLES: - ha_call_service("light", "turn_on", entity_id="light.living_room") - ha_call_service("climate", "set_temperature", entity_id="climate.thermostat", data={"temperature": 22}) 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``). For detailed service documentation, use ha_get_skill_guide. **WebSocket command escape hatch (advanced):** Some Home Assistant operations are WebSocket-only commands, not registered services. Pass ``ws_command`` (instead of domain/service) to send one no dedicated tool covers, with its parameters in ``data``: ha_call_service(ws_command="<command type>", data={...}). Only one-shot request/response commands are supported, 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.(str | None)= nullservice-Service name within domain (e.g. 'turn_on', 'set_temperature', 'trigger'). Required for a service call.(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.(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'.(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. With return_response=True the response data still surfaces once as the top-level service_response key, never nested in result. Large for nested-group targets — prefer result_fields / result_attribute_keys.(bool)= falseresult_fields-Project each record in 'result' to only these top-level keys (e.g. ['entity_id', 'state']). 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']). 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 and that no dedicated tool covers. When set, omit domain/service and the other service params. 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. Bulk checks poll all operations concurrently under one shared timeout_seconds window and report per-item failures inside detailed_results instead of aborting the batch. Use this to track the operation_ids that ha_bulk_control returns. For current entity states, use ha_get_state instead.
Parameters
operation_idrequired-Single operation ID, or a list of IDs for a bulk status check.(str | list[str])timeout_seconds-Seconds to wait for a pending operation to finish before returning its status. 0 returns the current status at once.(float)ge: 0, allow_inf_nan: false= 8Description
List available Home Assistant services with optional pagination and detail control. Discovers services/actions that can be called via ha_call_service. EXAMPLES: - Light services with full parameter details: ha_list_services(domain="light", detail_level="full") - Search: ha_list_services(query="temperature") - Next page: ha_list_services(offset=50)
Parameters
domain-Filter by domain (e.g., 'light', 'switch', 'climate').(str | None)= nullquery-Search in service names and descriptions.(str | None)= nulllimit-Max services to return per page(int)ge: 1, le: 200= 50offset-Number of services to skip for pagination(int)ge: 0= 0detail_level-'summary': name, description, domain, service, and target when the service has one. 'full': additionally 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. Unknown keys yield empty records. Available keys: name, description, domain, service, target (when present), fields (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. 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 — when ha_config_set_yaml accepts this ``yaml_path`` for the returned ``file``, the two are exactly the arguments that address the same fragment for an edit. To change a value nested below such a key, read the whole key and replace it. 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. When the component can load Core's config, key discovery follows packages and include directories recursively.(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. ``template-guidelines.md`` ships under ``skill_content`` by default. Destructive, disabled by default. Prefer the dedicated tools: ha_config_set_helper for template sensors and every other helper type, ha_config_set_automation / ha_config_set_script / ha_config_set_scene for storage-mode items. 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``. 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). ``automation``, ``script`` and ``scene`` keys are accepted only when ``file`` is a ``packages/*.yaml`` and the operator has turned on that key's packages setting (each is off by default); writes of those keys to ``configuration.yaml`` are rejected; use the storage-mode tools. 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, 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.
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).(str)actionrequired-Action to perform: 'add' (insert content if yaml_path is absent; otherwise merge one level deep: a list gets content's items appended, a mapping gets content's top-level keys set, overwriting same-named ones, so a nested mapping or list under such a key is replaced, not merged; mismatched types are rejected. For a nested edit, read the whole value of yaml_path, not a nested path below it, and use 'replace' with the complete edited value), 'replace' (overwrite yaml_path with content), or 'remove' (delete yaml_path entirely).(str)content-YAML content for the value under yaml_path. Required for 'add' and 'replace' actions.(str | None)= nullfile-Relative path to the YAML config file. Also supports 'packages/*.yaml' and 'themes/*.yaml'.(str)= "configuration.yaml"confirm_token-Confirmation token from a prior preview response.(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. Home Assistant names a full backup made with its automatic-backup settings itself.(str | None)= nullbackup_id-(snapshot.restore / snapshot.delete) Tarball ID (e.g. 'dd7550ed').(str | None)= nullrestore_database-(snapshot.restore) Include the database in the restore. Default: restore it when the backup contains it. Must be false for a backup without one; on Supervisor installs it must match the backup.(bool | None)= nullrestore_addons-(snapshot.restore) Slugs of apps (add-ons) in the backup to restore, from its `addons` in the snapshot list. Default: none. Supervisor installs only.(list[str] | None)= nullrestore_folders-(snapshot.restore) Folders in the backup to restore, from its `folders` in the snapshot list (`share`, `addons/local`, `ssl`, `media`). Default: none. Supervisor installs only.(list[str] | None)= nullinclude_homeassistant-(snapshot.create) Custom backup: include Home Assistant's configuration.(bool | None)= nullinclude_database-(snapshot.create) Custom backup: include the database (history); needs include_homeassistant=True.(bool | None)= nullinclude_addons-(snapshot.create) Custom backup: slugs of the installed apps (add-ons) to include. Supervisor installs only.(list[str] | None)= nullinclude_all_addons-(snapshot.create) Custom backup: include every app (add-on); not together with include_addons. Supervisor installs only.(bool | None)= nullinclude_folders-(snapshot.create) Custom backup: folders to include (`share`, `addons/local`, `ssl`, `media`). Supervisor installs only.(list[str] | None)= nullsafety_backup-(snapshot.restore) Back up what the restore replaces (configuration, plus the database, apps and folders it restores) first, as a manual backup on the default location. Off by default; offer it to the user, who decides. Needs the default backup password.(bool)= falseconfirm-(snapshot.delete) Must be True to confirm deletion.(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): ``code`` + ``justification``; ``run_saved``; ``list_saved=True``. **Available functions in sandbox:** - ``api_get(endpoint)`` / ``api_post(endpoint, data)`` — HA REST API, for operations not covered by existing tools - ``ws_send(message)`` — send a HA WebSocket command, for operations only available over WebSocket (most registry CRUD, template rendering, Lovelace operations). ``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. Returns ``{"deleted": True, "name": name}`` or ``{"error": ...}``. Saved tools persist across server restarts when ``CODE_MODE_SAVED_TOOLS_PATH`` is set (the app sets this by default to ``/data/saved_tools.json``). 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}) lights = result.get("entities", []) for e in lights: await call_tool("ha_call_service", {"domain": "light", "service": "turn_off", "entity_id": e["entity_id"]}) {"turned_off": len(lights)} ```
Parameters
code-Python code to execute. Last expression is the return value.(str | None)= nulljustification-Why no existing tool works.(str | None)= nullsave_as-Save the tool under this name for reuse (alphanumeric/underscores, max 64 chars).(str | None)= nullrun_saved-Name of a previously saved tool to re-run.(str | None)= nulllist_saved-Set True to list all saved tools.(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, to switch the rule list between gating and approving the calls it matches, 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. An omitted field is not left alone, it reverts to its default: dropping event_decisions_enabled switches off approving from Home Assistant events, and the response says so in a warning when it does, while dropping rule_effect from an allow list is refused. Writes are version-guarded: 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, "event_decisions_enabled": False, "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 {rule_effect, wait_seconds, approval_ttl_minutes, event_decisions_enabled, rules, version}. rule_effect 'require_approval' (default): a matching rule gates the call; 'allow': a matching rule approves the call and every other call requires approval(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 (the Marketplace on 2026.11+, both via 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'.(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: a change landing between that read and the write is not caught.(dict[str, Any] | None)= nullforce-action='set_engine_theme' only: skip the expected_current guard and overwrite unconditionally.(bool)= falsevalue-Frontend user-data theme object when action='set_engine_theme', e.g. {'theme': '', 'dark': False}. An empty dict restores default/auto behavior.(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, details, install, skip) and Repairs issues (ignore). Covers Core, OS, supervisor, apps (add-ons), device firmware, and HACS update entities, plus the Repairs issues in Settings > System > Repairs. Repairs are listed by ha_get_overview (not here); pass their domain and issue_id to 'ignore_repair' / 'unignore_repair'. In Read Only Mode the read actions ('list', 'get') stay available; write actions are blocked. 'install' blocks until each update finishes (minutes). One that outlasts the request timeout ends the call with a connection timeout while it keeps running, and a Core update restarts Home Assistant; neither means failure, but later batch targets were not started. Confirm per entity with action='get'; finished updates drop out of 'list', which also returns ha_mcp_update — this MCP server's own update status {current, latest, update_available}, so a newer ha-mcp release can be flagged. EXAMPLES: - 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"]) - Dismiss a repair: ha_manage_updates(action="ignore_repair", repairs=[{"domain": "sun", "issue_id": "abc"}])
Parameters
action-'list' (all pending updates, default), 'get' (details/release notes for one update), 'install' (apply pending updates), 'skip' (hide the offered version), 'clear_skipped' (re-offer a skipped version), 'ignore_repair' (dismiss Repairs issues, as the Repairs UI's Ignore does), or 'unignore_repair' (show them again).(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'). As with the HA 'Update all' button, core/os/supervisor are excluded (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.(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. 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)).(bool)= falserepairs-For ignore_repair / unignore_repair: the Repairs issues to act on, as [{'domain': ..., 'issue_id': ...}] taken from ha_get_overview's repairs.(list[dict[str, Any]] | None)= nullDescription
Execute a Home Assistant configuration reload without a full restart. Reloads specific configuration components so changes take effect without restarting Home Assistant — much faster than a full restart. Use it after editing automation/script YAML, adding YAML helpers, or changing customize.yaml or themes. EXAMPLES: - ha_reload_core(target="automations") - ha_reload_core(entry_id="abc123")
Parameters
target-What to reload: 'all', 'automations', 'scripts', 'scenes', 'groups', 'input_booleans', 'input_numbers', 'input_texts', 'input_selects', 'input_datetimes', 'input_buttons', 'timers', 'templates', 'persons', 'zones', 'core' (customize, packages) or 'themes'.(str)= "all"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 'all'); combining it with an explicit target is a validation error. Find the id via ha_get_integration.(str | None)= nullDescription
Execute a Home Assistant restart. WARNING: restarts the entire Home Assistant instance; all automations are unavailable during the restart, which typically takes 1-5 minutes. Config is validated automatically before the restart proceeds (to pre-check, call ha_get_system_health(include="config_check")). For configuration changes, consider ha_reload_core() instead, which reloads specific components without a full restart. When this server runs inside Home Assistant (embedded), it replies before the restart starts and is unreachable until Home Assistant is back; with safe_mode it stays down until Home Assistant is next restarted normally, because it is itself a custom integration. EXAMPLE: ha_restart(confirm=True)
Parameters
confirm-Must be True to confirm the restart.(bool)= falsesafe_mode-Restart in safe mode: no custom integrations or custom frontend resources load until the next normal restart.(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 every entity in the 'todo' domain (shopping lists and other todo-type integrations) with entity_id, friendly_name and state (number of incomplete items or current status). With an entity_id: returns that list's items as Home Assistant reports them — uid, summary, status, and description, due and completed where set. EXAMPLES: - List all todo lists: ha_get_todo() - Get incomplete items: ha_get_todo("todo.shopping_list", status="needs_action")
Parameters
entity_id-Todo list entity ID (e.g., 'todo.shopping_list').(str | None)= nullstatus-Only items with this status, or with any of a list of statuses: 'needs_action' (open) or 'completed' (done). Only applies when entity_id is provided.(str | list[str] | None)= nullDescription
Remove items from a Home Assistant todo list. Give `item` or `remove_completed=True`, not both. Removal is permanent; to mark an item done instead, use ha_set_todo_item() with status="completed". `removed` lists the items the call asked for that are gone afterwards; with remove_completed=True that includes a completed item another client removed meanwhile. With auto-backup on, each item is backed up before it is removed; `warnings` names a backup not taken because the item could not be read or the backup could not start, and with remove_completed=True an item gone afterwards that was not completed when the list was read, so was not backed up. EXAMPLES: - ha_remove_todo_item("todo.shopping_list", "Buy milk") - ha_remove_todo_item("todo.shopping_list", ["Buy milk", "Bread"]) - ha_remove_todo_item("todo.shopping_list", remove_completed=True)
Parameters
entity_idrequired-Todo list entity ID (e.g., 'todo.shopping_list')(str)item-Item to remove, or a list of items - each the item UID (from ha_get_todo) or the exact item summary/name. A string that names no item but holds a JSON array of strings is that list.(str | list[str] | None)= nullremove_completed-Remove every completed item(bool)= falseDescription
Create, update or reorder a todo item in Home Assistant. Without `item` (create mode): creates a new item from summary. With `item` (update mode): changes the fields given, and moves the item when `move_after` is given, alone or together with other changes. Home Assistant refuses a description or due date the list does not support, and says which. The reply carries `todo_item`, the item as Home Assistant stores it afterwards, uid included, or null with a warning when it cannot be read back. With auto-backup on, an updated item is backed up first; `warnings` names a backup that was not taken because the item could not be read or the backup could not start. EXAMPLES: - Add item: 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") - Move to top: ha_set_todo_item("todo.tasks", item="Pay bills", move_after="")
Parameters
entity_idrequired-Todo list entity ID (e.g., 'todo.shopping_list')(str)summary-Item text/name. 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.(str | None)= nullstatus-Item status: 'completed' to mark done, 'needs_action' to mark incomplete. Update mode only: to add a done item, create it, then update it.(str | None)= nulldescription-Detailed description for the item. An empty string clears it on update.(str | None)= nulldue_date-Due date in YYYY-MM-DD format (e.g., '2024-12-25'). An empty string clears the due date on update.(str | None)= nulldue_datetime-Due datetime in ISO format (e.g., '2024-12-25T14:00:00'). An empty string clears the due date on update.(str | None)= nullrename-New name/summary for an existing item.(str | None)= nullmove_after-Move the item directly below this item (UID or exact summary). An empty string moves it to the top of the list.(str | None)= nullUtilities
Description
Execute a Jinja2 template render, or an automation condition check, in Home Assistant. Renders templates with Home Assistant's template engine (all states, functions and filters available), or checks a condition block the way an automation would and returns true/false. When to use: any one-shot answer DERIVED from current HA state (an average across sensors, a count of entities matching a condition, a rendered message with live values), and testing a template or condition before embedding it in an automation. One render beats fetching N states and doing the math yourself. When NOT to use: a plain single-entity value is ha_get_state / ha_search. Templates in automation `condition:` / `trigger:` positions or templated action names: prefer native constructs, which are validated at config load, where a template only errors at runtime and a non-truthy render is silently false — see ha_get_skill_guide for the anti-pattern list. Test the native condition here with `condition`. Results: a render that works but hits something suspect (an undefined variable, a missing attribute) still returns its result with the messages in `warnings`; strict=true makes those hard errors. A failed render returns Home Assistant's error text and, with the ha_mcp_tools component installed, the template `line` and `source_line` it failed on when the error names one. EXAMPLES: - ha_eval_template(template="{{ states.light | selectattr('state', 'eq', 'on') | list | count }}") - ha_eval_template(template="Hello {{ name }}", variables={"name": "Alice"}) - ha_eval_template(condition={"condition": "numeric_state", "entity_id": "sensor.temperature", "above": 20})
Parameters
template-Jinja2 template to render. Omit when using 'condition'.(str | None)= nullcondition-Automation condition to test instead of a template, e.g. {'condition': 'numeric_state', 'entity_id': 'sensor.temp', 'above': 20}. Test several at once with {'condition': 'and', 'conditions': [...]}.(dict[str, Any] | None)= nullvariables-Variables available to the template or condition, e.g. sample trigger data: {'trigger': {'to_state': {'state': 'on'}}}(dict[str, Any] | None)= nullstrict-Template only: fail on undefined variables instead of rendering them as empty (reported in warnings when report_errors is on)(bool)= falsetimeout-Template only: maximum render time in seconds(int)ge: 1, le: 60= 3report_errors-Template only: have Home Assistant report render errors and warnings. With false, a failed render is only written to Home Assistant's log.(bool)= trueDescription
Get diagnostics and a finished GitHub issue for a bug, agent feedback or a feature request. Use it before filing any GitHub issue about ha-mcp: a bug report without this report is closed after 24 hours. `report_type` picks the kind of report; if it is unclear which, ask: "Are you reporting a bug in ha-mcp, giving feedback on how I used the tools, or requesting a feature?" Pass the report text in the call: the server combines it with the diagnostics it collects into `issue_title`, `issue_body` (the full report with logs) and `issue_url` (a new-issue link with title and body filled in, log sections left out to fit GitHub's URL limit; error messages stay in, with secrets redacted). A call without text still returns diagnostics, with placeholders in the body. The response is LARGE; `fields=` narrows it. Read `instructions` before showing anything to the user: it covers the missing-tool and known-client pre-checks, the duplicate check, the mandatory anonymisation step, and how to file the issue. Check `missing_tool_hint` FIRST when the report is about a missing tool: a stale client tool list (not a bug) is the usual cause.
Parameters
report_type-'runtime_bug' when ha-mcp errored or behaved wrongly; 'agent_behavior' when the user says you used the wrong tool or worked inefficiently; 'feature_request' when the user wants ha-mcp to do something it cannot do yet.(Literal['runtime_bug', 'agent_behavior', 'feature_request'])= "runtime_bug"title-One-line summary of what broke or what is requested, for the issue title.(str | None)= nulldescription-What went wrong in markdown: steps to reproduce, expected and actual behavior. For agent_behavior: what you did and what you should have done. For feature_request: what ha-mcp should do, why the user needs it, and what you tried.(str | None)= nulluser_prompt-The user message that led to the problem, verbatim.(str | None)= nulluser_comment-The user's own words on what went wrong or what bothered them, verbatim. Ask the user for it; never write it yourself.(str | None)= nulltool_calls-The tool call(s) that produced the problem, verbatim: name, arguments and the (shortened) response.(str | None)= nullai_model-Your own model identity, as specific as you know it. Do not invent a version.(str | None)= nullclient_app-The app and version the user runs you in, as the USER states it (usually on the app's About screen or from its --version command). Never guess it.(str | None)= nulltool_call_count-Number of ha_* tool calls made since the issue started; determines how many log entries to include.(int)ge: 1, le: 16= 10fields-Return only the specified top-level response keys. None = full response. Typical: 'issue_title,issue_body,issue_url,duplicate_check_urls,anonymization_guide,missing_tool_hint,known_client_issues_hint,instructions'. issue_body already embeds 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, issue_title, issue_body, issue_url, anonymization_guide, duplicate_check_urls, missing_tool_hint, known_client_issues_hint, instructions.(str | list[str] | None)= 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. EXAMPLES: - List all zones: ha_get_zone() - Get specific zone: ha_get_zone(zone_id="office") - Same zone by entity_id: ha_get_zone(zone_id="zone.office") Zones outside the zone storage, the home zone (from the general settings) and YAML zones, are listed with ``editable=false`` / ``source="yaml"``; editable means ha_set_zone and ha_remove_zone can change the zone. Each zone carries its entity_id.
Parameters
zone_id-Zone ID or entity_id to get details for (from the ha_get_zone() list).(str | None)= nullDescription
Remove a Home Assistant zone. EXAMPLES: - Remove zone: ha_remove_zone("office") - Remove by entity_id: ha_remove_zone("zone.office") **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:** Zones defined in YAML, and the home zone, cannot be removed here.
Parameters
zone_idrequired-Zone ID or entity_id of the zone to remove(str)wait-Wait for the zone to be gone before returning. Set to False for bulk operations.(bool)= trueDescription
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). Zones defined in YAML, and the home zone, cannot be changed here. EXAMPLES: - Create: ha_set_zone(name="Office", latitude=40.7128, longitude=-74.0060, radius=150, icon="mdi:briefcase") - Update: ha_set_zone(zone_id="office", radius=200) - Update by entity_id: ha_set_zone(zone_id="zone.office", radius=200)
Parameters
name-Display name for the zone(str | None)= nulllatitude-Latitude coordinate of the zone center(float | None)= nulllongitude-Longitude coordinate of the zone center(float | None)= nullzone_id-Zone ID or entity_id of the zone to update (from ha_get_zone)(str | None)= nullradius-Radius of the zone in meters; Home Assistant uses 100 when it is omitted on create(float | None)= nullicon-Material Design Icon (e.g., 'mdi:briefcase'). On update, '' clears it, except an icon stored in the zone itself (as the Home Assistant UI stores it; ha_get_zone shows it), which cannot be removed(str | None)= nullpassive-Passive mode: the zone is hidden in the frontend and not used for device tracker state, but automations can still use it (false when omitted on create)(bool | None)= nullwait-Wait for the zone to be queryable before returning. Set to False for bulk operations.(bool)= true