Docs / Reference / Tools
Tools reference
Phoenix MCP implements all 23 native Home Assistant MCP tools with identical names and responses, then adds 108 of its own, 128 in all. Entity reads and native controls are scoped to the token's permission tree; the ones below note the capability they additionally require. Automation, script, and other authoring/system tools are governed by capability gates instead of the entity tree (see below).
No tools match your search.
check_configvalidate_configDiagnostics
check_config validates the HA configuration files. validate_config validates an automation or script config without saving and reports, per referenced entity, whether it is accessible to the token (entities outside the token's scope are reported as inaccessible and not visible).
- params
check_config takes none. validate_config: type (required; automation or script), config (required; the config to validate)
compare_stateSearch & discovery
Compare accessible entities' states between two times, backed by the recorder (the tool fails when the Recorder integration is disabled).
- params
entity_id (required), t1 (required; ISO or relative like 24h), t2 (default now)
describe_areaSearch & discovery
One area, its floor, and accessible entities grouped by domain.
- params
area (required; an area name, alias, or area_id)
describe_entitySearch & discovery
Comprehension summary of one entity: state, area, the services in its domain, what references it, and its MESA control_mode when MESA is active. A configuration or diagnostic entity is flagged with entity_category, so an agent can tell a device's setup and health entities from its primary controls.
- params
entity_id (required)
find_available_actionsSearch & discovery
The services in an entity's domain and whether the token can invoke each one now, accounting for write access, physical and dual gates, and the entity's MESA control_mode.
- params
entity_id (required)
get_automation_tracesAutomation traces
Execution traces for an accessible automation. Optional run_id and summary (highlights the error and last step).
- params
automation_id (required), run_id, summary
get_overviewSearch & discovery
Compact home summary: total accessible entities, counts by domain and area, the unavailable count, and the deployment mesa_mode. When MESA is active and the token has Config Read, it also includes the operator-set prohibited and read-only entities (the “do not operate” list), so the agent learns what is off-limits in one call.
get_relationshipsSearch & discovery
Reverse references (automations, scripts, scenes that reference the entity) and forward references, scoped to what the token can access.
- params
entity_id (required)
get_system_healthDiagnostics
Home Assistant version and per-integration system health. Secret-keyed values and URL-embedded credentials in the integration data are scrubbed before they are returned, as are network-topology details (LAN IP addresses, hostnames inside URLs, and filesystem paths) that Phoenix MCP withholds elsewhere. This is an elevated read; treat cap_diagnostics accordingly.
get_esphome_overviewDiagnostics
Status of the ESPHome devices this token can see: firmware version and build time, online state, Bluetooth proxy connection slots (free of limit), and the user-defined API actions each device declares along with the Home Assistant action name each maps to. When the ESPHome Device Builder add-on is set up, each device also reports its configuration filename and whether a firmware update is available. A device appears only if the token can access at least one of its entities; MAC addresses, the API encryption key, and the device's network address are never returned, and Device Builder configurations belonging to devices outside the token's scope are reported as a count only.
get_radio_networkget_radio_deviceDiagnostics
Radio-network health (Zigbee2MQTT or ZHA, auto-detected). get_radio_network reports every radio network present: channel, PAN id, coordinator, and whether the pairing window is open. get_radio_device reports one device's signal quality (LQI/RSSI), availability, power source, interview state, and (ZHA) mesh neighbors, with out-of-scope neighbor identifiers redacted. Network keys never appear in any response.
- params
get_radio_network: none; get_radio_device: device_id
list_areaslist_floorslist_zoneslist_devicesget_deviceRegistry read
Registry enumeration restricted to areas, floors, zones, and devices that contain at least one accessible entity. get_device returns one device plus its accessible entities; it takes device_id (required), the list tools take none.
list_automationslist_scriptslist_sceneslist_helpersRegistry read
Enumerate accessible automations, scripts, scenes, and helpers. The automation and script listings carry the id each one's read and edit tools take (get_automation / get_script), alongside the entity id, alias, and current state.
recent_activitySearch & discovery
Accessible entities that changed in the last N minutes, newest first.
search_entitiesSearch & discovery
Filter accessible entities by query, domain, area, device class, state, and staleness. A multi-word query matches entities containing all the words and results are ranked by relevance, so the best matches lead. If that exact matching finds nothing, a typo-tolerant fallback retries against entity id and friendly name, tagging the response fuzzy_fallback: true. Each row carries control_mode when its MESA nature is non-default, so restricted entities are visible without a follow-up call, and entity_category when the entity is a configuration or diagnostic entity rather than a primary control.
- params
query, domain, area, device_class, state, unavailable, stale_hours, limit (all optional)
whatifSearch & discovery
Predict which automations would fire if an accessible entity changed to a hypothetical state, without changing anything. State and numeric_state triggers are evaluated best-effort.
- params
entity_id (required), hypothetical_state (required)
Files, YAML, integrations & system
get_esphome_yamlset_esphome_yamlESPHome device YAML
Read and edit ESPHome device configuration files in their own jail under esphome/, deliberately separate from cap_filesystem: device YAML embeds C++ lambdas and, in practice, inline credentials, so widening a filesystem grant must never reach it. Call get_esphome_yaml with no argument to list the files this token can see, or with file to read one.
Credentials are masked, not exposed. API encryption keys, wifi/OTA/MQTT passwords, anything under a credential-shaped key, and any value that also appears in the ESPHome secrets.yaml come back as __PHOENIX_REDACTED__<path>__ placeholders. Leave a placeholder where it is and the real value is spliced back on write, so an edit round-trips without the agent, the approval record, or the diff ever holding the secret. Comments and formatting outside those spans survive byte-for-byte.
A credential cannot be changed to a new literal value. Replacing one with a !secret reference is allowed and is the supported migration; the key must already exist in the ESPHome secrets.yaml, which defined_secrets lists. That file is never readable or writable through any tool. The freeze is not only about disclosure: an API encryption key must keep matching what Home Assistant stored, and an OTA password governs the next upload, so a blind change can leave a device unreachable until someone reflashes it over a cable.
Writing never flashes anything. The file lands on disk and the device keeps running its current firmware until a build is installed, which is a separate tool behind a separate capability. content_hash is taken over the real file, so expected_hash still guards against a change during an approval window, and every write is versioned for rollback in the Changes tab.
New credentials are generated, not invented. A device that does not have an API encryption key or OTA password yet is a real problem for an agent: it needs a value, and a language model cannot produce cryptographic randomness, so anything it makes up is guessable. Writing !phoenix_generate as the value solves it from the other side. Phoenix substitutes a genuinely random one as the file is written, so the secret exists only on disk, is masked on every later read, and never reaches the agent, the approval record, or the diff, which shows the tag and therefore the intent. You read the value from the ESPHome dashboard when Home Assistant asks for it. It is refused on a credential that already exists, because rotating a key a device is currently running strands it until Home Assistant is re-keyed, and refused outright on the wifi ssid and password, which belong to the house rather than the device.
- params
get_esphome_yaml: file (omit to list); set_esphome_yaml: file, content, expected_hash
validate_esphome_yamlget_esphome_boardget_esphome_componentget_esphome_automationsESPHome device YAML
Four read-only lookups served by the ESPHome Device Builder add-on, which turn ESPHome authoring from recall into reference. They are hidden from a token's tool list entirely when the add-on is not installed, and a call made while it is merely stopped fails with a clear message rather than a silent empty answer. Phoenix MCP speaks a hardcoded command allowlist to the add-on; the add-on has no permission model of its own, so that allowlist is the boundary and is pinned by test.
validate_esphome_yaml runs ESPHome's configuration check over one device file, without compiling or flashing. It validates the file as it is on disk, never caller-supplied content, and that is a security boundary rather than a convenience: ESPHome evaluates configuration at validation time and a config carrying external_components fetches and executes remote code to do it, so accepting an arbitrary payload would be an unreviewed code-execution path. A file on disk got there through set_esphome_yaml, which is capability-gated and can require approval. The validator quotes offending lines back, which can include a credential the file carries inline, so its output is scrubbed of every known secret before it is returned.
get_esphome_board returns a board's full pin map (which GPIOs exist, which are already occupied, and any warnings) plus its hardware and platform details, or searches the board catalog. get_esphome_component returns a component's real configuration schema, searches the catalog, or lists categories. get_esphome_automations lists the triggers, actions and conditions available inside one device's YAML, scoped to the components that device actually loads. The two file-bound tools follow the same jail and entity scoping as get_esphome_yaml, and an out-of-scope file is indistinguishable from a missing one. The automation catalog is projected through a per-section field allowlist, dropping prose, editor-only flags, and display names that merely restate the id: the raw payload measured 64KB for one real device, which is enough to swamp an agent's context. Every entry is kept, only the fields are trimmed.
- params
validate_esphome_yaml: file; get_esphome_board: board_id, query, platform, offset, limit; get_esphome_component: component_ids, query, category, exclude_category, platform, board_id, offset, limit; get_esphome_automations: file
compile_esphome_firmwareinstall_esphome_firmwareget_esphome_jobwait_for_esphome_jobcancel_esphome_jobESPHome device YAMLESPHome firmware flashing
Build the firmware an agent just authored, and put it on the device. These close the loop that validation leaves open: validation catches schema and pin errors, but the C++ and lambda mistakes that actually break a model-written ESPHome config only surface in a real build.
Building and flashing are separate capabilities. compile_esphome_firmware rides the same capability as editing the YAML, because compiling produces a binary and touches no device: the capability that authorized writing the file authorizes proving it builds. Flashing is its own grant, ESPHome firmware flashing, which is denied by default in every persona except esphome, where it is set to Confirm. So a token can iterate on firmware all day without being able to put any of it on hardware.
A build takes minutes, so nothing here waits for one. Both tools enqueue a job in the Device Builder and return a job_id straight away; get_esphome_job polls it. That is not only about agent latency: approving a flash runs it inline in the panel's request, and a tool that blocked for eight minutes would hang the Approve button. The jobs live in the add-on and survive its restart, so Phoenix MCP keeps no build state of its own.
A finished compile is not a flashed device. An install enqueues two jobs, a compile and an upload held behind it, and returns only the first. get_esphome_job resolves the second and reports flashed as true only once that upload has actually completed. If the device was offline, there is no upload job at all and the flash is armed for its next wake, which is reported as exactly that rather than as success. Phoenix MCP always installs over the air and ignores any port a caller supplies.
Watching it happen. The build tools return instantly, so wait_for_esphome_job is what holds a call open while the job runs. It waits up to five minutes, returns the moment the job is done, and reports lines like Compiling living-room: 70% as it goes. For an install it follows the whole chain, compile and then upload, so a single call covers the lot and returning means the device really was flashed. In Agent Chat those lines appear live under the running tool, visible even with verbose output switched off. An external MCP client sees them only if it asks for progress on the call and renders progress notifications; where it does not, the percentage is still in every poll for the agent to relay in its own words.
Two smaller safeguards worth knowing. Starting a second build for a file that is already building does not queue behind the first, it cancels it and discards its log, so Phoenix MCP refuses and points at the running job instead. And a job_id is scoped by the file it names, so it is not a way to read another device's build log: an id that does not exist and an id you cannot see return the same answer. Build logs are scrubbed of every known credential and bounded, since a real one runs thousands of lines.
- params
compile_esphome_firmware, install_esphome_firmware: file (required). get_esphome_job: job_id, or file for that file's newest job (one of the two is required; a job id only exists in the conversation that started the build, so a voice or Assist follow-up asks by file). cancel_esphome_job: job_id (required). wait_for_esphome_job: job_id (required), timeout.
delete_esphome_yamlclean_esphome_buildrename_esphome_deviceESPHome device YAMLESPHome firmware flashing
The housekeeping that otherwise sends you to the ESPHome panel in the middle of a task: removing a configuration you no longer want, clearing a build that has gone wrong in a way a rebuild cannot fix, and renaming a device.
Deleting is restorable, which is why Phoenix MCP owns it. delete_esphome_yaml snapshots the file into version history before removing it, so a mistaken delete is an ordinary Restore in the Changes tab. The add-on's own delete would bypass that entirely. Deleting a configuration does not touch the device: it keeps running its current firmware, keeps its Home Assistant entities, and is not unadopted. Both the result and the approval card say so, because that is the consequence people expect to be different from what it is.
Cleaning is cheap to authorize, not free to run. clean_esphome_build discards a configuration's cached build artifacts, changes no file and reaches no device, so it needs no approval. It is not fully contained, though: it also clears the shared PlatformIO cache, so the next build of every device is slower, which is why it is worth trying the obvious explanations before reaching for it. If a build is currently running it refuses rather than killing it, and points at cancel_esphome_job. The add-on's fleet-wide equivalent, which cancels every job and wipes the shared build environment, is permanently unreachable.
Renaming compiles and flashes, so it needs the flashing capability, not the YAML one. Entities, entity ids, areas, history, and MESA profiles all survive a rename, because Home Assistant identifies ESPHome entities by the device's MAC address rather than its name. What does not survive is the device's user-defined actions: their service names are built from the device name, so esphome.old_name_action becomes esphome.new_name_action and any automation calling one silently stops working. The approval card names those services. The device must be reachable: the add-on copies the configuration, compiles it, then flashes over the air, and if that flash fails it deletes the new copy so nothing is renamed at all. Both configurations are listed while the build runs, and only one survives it.
- params
delete_esphome_yaml, clean_esphome_build: file; rename_esphome_device: file, new_name
get_esphome_device_logsdecode_esphome_backtraceESPHome device YAML
What happened after the flash. get_esphome_device_logs captures a bounded window (15 seconds by default, 60 at most) of a device's live console, which is far more detailed than what reaches Home Assistant. The stream runs until it is killed rather than ending on its own, so the capture window is the request; an empty result means the device logged nothing, not that anything failed.
decode_esphome_backtrace turns the hex panic dump an ESP32 prints when it crashes into a real stack trace with function names and source lines, resolved against that device's last build. It reports stale_build when the firmware has moved on since, so a trace is never silently read against the wrong binary. Both are reads needing only read access to the device, and both are scrubbed.
- params
get_esphome_device_logs: file, seconds; decode_esphome_backtrace: file, lines
get_yaml_configset_yaml_configRaw YAML edit
Read and edit configuration.yaml directly. Confirm-eligible. get_yaml_config reads configuration.yaml by default, and with the optional file argument any other .yaml/.yml file in the configuration directory (an !include target, a packages file); secrets.yaml and hidden directories such as .storage are refused, and a symlink cannot launder either past the check. The optional key argument returns just one dotted mapping key instead of the whole file, tag-preserving, naming the target file when the value is an !include. Files are returned verbatim, so any inline secret enters the agent's context; grant this only to fully-trusted clients, and prefer !secret references (which Phoenix MCP preserves) over inline values.
Concurrency guard: the read returns a content_hash fingerprint of the whole file, and set_yaml_config accepts an optional expected_hash; if the file changed since the read (including during an approval window), the write is refused with a re-read-and-reapply error instead of overwriting the other change. Omitting expected_hash skips the check.
Trust-boundary keys are never writable. A write is refused outright if it adds, removes, or changes homeassistant: auth_providers / auth_mfa_modules / packages, http: trusted_proxies / use_x_forwarded_for / cors_allowed_origins / ip_ban_enabled / login_attempts_threshold, frontend: extra_module_url, or lovelace: resources. Copying them through unchanged always passes. These are not merely powerful keys (shell_command stays writable): each one redefines how Home Assistant itself authenticates, whom it trusts, or what JavaScript loads into your authenticated dashboard. The content must also parse as YAML, so a write cannot leave a syntactically broken file behind; that is a parse check only, not configuration validation, so YAML that parses but configures Home Assistant incorrectly is still written. The tool result tells the agent to run check_config afterward, which is the step that catches invalid configuration before a restart applies it.
Deletions have to be declared. A write that removes a top-level key present in the current file is refused unless the optional remove_keys argument names that key. Intentional removals still work, they just have to be stated. This catches the whole-file-replace accident that nothing else would: dropping automation: !include automations.yaml disables every automation while automations.yaml sits untouched and check_config passes. Two cases are exempt from this check (both still go through the trust-boundary refusal above): a file on disk that is missing or does not parse, so a broken configuration stays repairable, and restoring an earlier version from the Changes tab. When a write does remove keys, the approval summary names the keys the write actually drops, not the ones it claimed.
list_backupscreate_backupBackup
List and create backups. Restore is intentionally never exposed. Confirm-eligible. list_backups reports total, returned and a truncated flag, so a clipped page is distinguishable from the whole list.
list_filesread_filewrite_fileFilesystem access
Read and write files under www/, themes/, and custom_templates/. Confirm-eligible. Same concurrency guard as the YAML tools: read_file returns a content_hash, write_file accepts an optional expected_hash and refuses if the file changed since the read (a file that does not exist yet fingerprints as the empty string, so read-then-create is guarded too).
- params
path (required for read/write; relative to the allowed directories), content (required for write), expected_hash (write, optional)
list_integrationsset_integration_enabledIntegration management
List integrations and enable or disable them. Confirm-eligible.
- params
set_integration_enabled: entry_id (required; from list_integrations), enabled (required; boolean)
set_entitydelete_entityRegistry write
Edit an entity's registry metadata (friendly name, icon, area) or delete a stale/duplicate registry entry. Requires write access to the entity and cap_registry_write; Confirm-eligible and version-captured. Renaming the entity_id is not exposed; a deleted entry cannot be re-created through Phoenix MCP.
- params
set_entity: entity_id + one of name / icon / area_id; delete_entity: entity_id
permit_zigbee_joinreconfigure_zigbee_deviceremove_zigbee_deviceRadio management
Manage the Zigbee network (Zigbee2MQTT or ZHA, auto-detected). Open the pairing window for up to 254 seconds or close it with duration 0, re-interview a misbehaving device, or remove one from the network (re-pairing required to rejoin). Requires cap_radio_write; Confirm-eligible. Device-scoped writes need write access to at least one of the device's entities, and the coordinator can never be targeted.
- params
permit_zigbee_join: duration (0-254, default 60), optional device_id (router) and backend; others: device_id
restart_haHA restart / stop
Restart Home Assistant. Pass-through-exempt: even pass-through tokens must have this flag enabled.