Skip to main content
Glama
ganhammar

MCP Server for Home Assistant (HTTP Transport)

by ganhammar
README.md
# MCP Server for Home Assistant (HTTP Transport)

A Home Assistant Custom Component that provides an MCP (Model Context Protocol) server using **HTTP transport**, allowing AI assistants like Claude to interact with your Home Assistant instance.

**Why HTTP transport with OAuth?** This project was built primarily to support MCP Streamable HTTP transport, enabling web-based clients like Claude that require OIDC and OAuth 2.0 Dynamic Client Registration (RFC 7591). Since Home Assistant already has an [official MCP integration](https://www.home-assistant.io/integrations/mcp_server/) that supports SSE transport, there wasn't a need to duplicate that. For local or custom client setups, Long-Lived Access Token authentication can be enabled as an alternative.

## Features

- 🌐 **HTTP transport** (not SSE) - works remotely, not just locally
- πŸ” **OAuth 2.0 authentication** with Dynamic Client Registration (via [hass-oidc-server](https://github.com/ganhammar/hass-oidc-server))
- πŸ”‘ **Long-Lived Access Token** authentication (opt-in) for local and custom client setups
- 🏠 Full Home Assistant API access (entities, services, areas, devices, history, statistics)
- πŸ”§ Easy HACS installation
- πŸ“ CRUD management of automations, scenes, scripts, and helper entities (input_boolean, counter, timer, schedule, and more)
- πŸ” Automation & script **traces** β€” inspect why a run fired, didn't fire, or took the wrong branch, step by step (read-only)
- πŸ“‹ Lovelace dashboard management (list, get/save/delete config, create/update/delete dashboards) with outline reads and JSON Patch edits so a single card can be changed without resending the whole dashboard
- 🩺 System administration tools (error log, config validation, restart, system status)
- πŸ“ YAML config file management β€” read, write, delete files with automatic backup before every change and built-in config validation (opt-in)
- πŸ“· Camera & image access β€” capture live camera frames and read saved image files for visual analysis (opt-in)
- πŸ“Š Resources, prompts, and completions for richer AI interactions
- 🧹 Optimization prompts for auditing automations, naming conventions, and scheduling

## Prerequisites

The integration supports two authentication methods:

- **OAuth 2.0** (default): Required for browser-based clients like Claude. Requires [hass-oidc-server](https://github.com/ganhammar/hass-oidc-server) to be installed and configured.
- **Long-Lived Access Tokens** (opt-in): For local agents and custom MCP clients that can't run an OAuth browser flow. No extra dependencies. Must be enabled in the integration settings.

You can use both methods at the same time. When both are active, the server tries OAuth first and falls back to the Long-Lived Access Token.

## Installation

### HACS (Recommended)

1. Open HACS in Home Assistant
1. Search for "MCP Server"
1. Click "Download"
1. Restart Home Assistant
1. Configure the integration (see Configuration section below)

### Manual Installation

1. Copy the `custom_components/mcp_server_http_transport` folder to your Home Assistant `custom_components` directory
1. Restart Home Assistant
1. Configure the integration (see Configuration section below)

## Configuration

1. Go to Settings > Devices & Services
1. Click "Add Integration"
1. Search for "MCP Server"
1. Choose your authentication method:
   - Leave "Enable native Home Assistant authentication" **unchecked** for OAuth-only (requires [hass-oidc-server](https://github.com/ganhammar/hass-oidc-server))
   - **Check** it to allow Long-Lived Access Tokens (can be used alongside OAuth or on its own)

You can change this setting later via Settings > Devices & Services > MCP Server > Configure.

## Usage with Claude in Browser (OAuth)

This requires the [hass-oidc-server](https://github.com/ganhammar/hass-oidc-server) integration to be installed.

The MCP server uses OAuth 2.0 Dynamic Client Registration (DCR), which allows Claude to automatically register itself without manual client setup.

1. In Claude (claude.ai):
   - Open Profile (bottom left corner)
   - Click Settings (gear icon)
   - Navigate to "Connectors"
   - Click "Add custom connector"
   - Enter your MCP server URL: `https://your-home-assistant.com/api/mcp`, or `https://your-home-assistant.com/api/mcp_http` if Home Assistant's built-in MCP Server integration is also installed (see [Another integration also serves /api/mcp](#another-integration-also-serves-apimcp))
   - Click "Connect"

2. Claude will automatically:
   - Discover your Home Assistant's OAuth endpoints
   - Register itself as an OAuth client
   - Redirect you to Home Assistant for authentication
   - Request access to your Home Assistant data

3. In Home Assistant:
   - Log in if not already authenticated
   - Review the permissions requested by Claude
   - Click "Authorize" to grant access

That's it! Claude will now be able to interact with your Home Assistant instance through the MCP server.

## Usage with Long-Lived Access Tokens

For local agents or MCP clients that can't run an OAuth browser flow, you can authenticate with a Home Assistant Long-Lived Access Token. This must be enabled first.

1. Enable native authentication: Settings > Devices & Services > MCP Server > Configure > check "Enable native Home Assistant authentication"
2. Create a token: go to your Home Assistant user profile > Long-Lived Access Tokens > Create Token
3. Configure your MCP client to send the token as a Bearer header to `http://your-home-assistant:8123/api/mcp` (or `/api/mcp_http`, see [Another integration also serves /api/mcp](#another-integration-also-serves-apimcp))

## Troubleshooting

### Another integration also serves /api/mcp

Home Assistant's built-in **Model Context Protocol Server** integration (`mcp_server`) serves its streamable HTTP transport on `/api/mcp`, the same path this integration uses. It has done so since Home Assistant 2025.11.

Home Assistant cannot share a path between two integrations: whichever registered it first at startup answers every request there, and the other is silently unreachable on that path. Nothing is logged, both integrations keep showing as healthy, and an MCP client connects successfully to whichever server won, so the only visible symptom is the client getting the wrong set of tools.

This integration therefore also answers on **`/api/mcp_http`**, which nothing else claims. If `/api/mcp` is already taken when this integration starts, it stays off that path entirely and serves only `/api/mcp_http`. Either way it raises a repair naming the conflict. Point your MCP client at `https://your-home-assistant/api/mcp_http`.

The repair is the reliable signal, and it is raised on every version. Failing that, look for **Model Context Protocol Server** under Settings > Devices & Services. A `405` from the probe below confirms it as well, but only from 2026.8 onwards, which is when the built-in server added its per-API paths; on 2025.11 to 2026.7 the probe returns `404` while the conflict is real, so a `404` proves nothing on its own. The `401` challenge on `/api/mcp` is no help either, because both servers use the same realm.

```bash
curl -o /dev/null -w '%{http_code}\n' https://your-home-assistant/api/mcp/assist
```

Deleting the built-in integration's config entry does not release the path on its own: Home Assistant binds HTTP routes at startup and cannot unbind them, so restart afterwards.

## MCP Capabilities

### Tools

**Entities & State**

| Tool | Description |
|------|-------------|
| `get_state` | Get the current state of any entity (optional `fields` to limit attributes) |
| `batch_get_state` | Get state for multiple entities in one call (max 50) |
| `list_entities` | List all entities, with optional `domain`, `detailed`, and `fields` parameters |
| `search_entities` | Search entities by friendly name, device class, domain, or area |
| `get_device_details` | Get a device and every entity registered to it (all domains), with optional states |
| `call_service` | Call any Home Assistant service |
| `create_calendar_event` | Create a one-off calendar event (local calendars; entity API) |
| `create_recurring_calendar_event` | Create a recurring calendar series with RRULE (local calendars) |
| `list_calendar_events` | List events in a time window (uid for deletes; descriptions omitted by default) |
| `delete_calendar_events` | Delete events by uid or summary filter (supports dry_run preview) |
| `fire_event` | Fire a custom event on the Home Assistant event bus |
| `get_history` | Get state history of an entity over a time range |
| `get_logbook` | Fetch logbook entries for an entity or time range |
| `get_statistics` | Fetch long-term statistics (energy, climate) with configurable period |
| `list_statistic_ids` | List statistic IDs and their metadata (source, unit, whether they carry a mean/sum) |
| `validate_statistics` | Report statistics issues the recorder detected β€” the Developer Tools "Fix issues" list |
| `adjust_statistics` | Correct a statistic's sum from a point in time onward (spike/bad reset); sum statistics only |
| `clear_statistics` | Permanently delete a statistic's history so it can start clean (irreversible; requires `confirm=true`) |
| `render_template` | Evaluate a Jinja2 template |

**Automations, Scenes & Scripts**

| Tool | Description |
|------|-------------|
| `list_automations` | List all automations with full configuration |
| `get_automation_config` | Get full configuration of a single automation |
| `create_automation` | Create a new automation |
| `update_automation` | Update an existing automation |
| `delete_automation` | Delete an automation |
| `list_scenes` | List all scenes with full configuration |
| `get_scene_config` | Get full configuration of a single scene |
| `create_scene` | Create a new scene |
| `update_scene` | Update an existing scene |
| `delete_scene` | Delete a scene |
| `list_scripts` | List all scripts with full configuration |
| `get_script_config` | Get full configuration of a single script |
| `create_script` | Create a new script |
| `update_script` | Update an existing script |
| `delete_script` | Delete a script |
| `list_traces` | List recent execution traces for an automation/script (or a whole domain), newest first |
| `get_trace` | Get the full step-by-step execution trace of one run β€” which trigger fired, which conditions passed/failed, and the variables at each step (`summary=true` for an outline of large traces) |

**Helpers**

| Tool | Description |
|------|-------------|
| `list_helpers` | List all helper entities, optionally filtered by domain |
| `get_helper_config` | Get the raw stored configuration of a UI-managed helper (experimental) |
| `create_helper` | Create a new helper (input_boolean, input_number, input_text, input_select, input_datetime, input_button, counter, timer, schedule) (experimental) |
| `update_helper` | Update an existing UI-managed helper by entity ID (experimental) |
| `delete_helper` | Delete a UI-managed helper by entity ID (experimental) |

**Config Files**

| Tool | Description |
|------|-------------|
| `list_config_files` | List YAML files in the config directory (first level by default, `recursive=true` for split configs; secrets excluded) |
| `get_config_file` | Read the contents of a YAML config file (max 1 MB) |
| `save_config_file` | Write or replace a YAML config file; auto-backs up all files first, then validates config |
| `delete_config_file` | Delete a YAML config file; auto-backs up all files first |
| `batch_edit_config_files` | Write and/or delete multiple YAML files in one call; one backup and one config check for the whole batch |
| `backup_config_files` | Manually snapshot all YAML files into `mcp_backups/<timestamp>/` |
| `list_config_backups` | List all available backup snapshots, newest first |
| `restore_config_backup` | Restore files from the latest or a specific backup; creates a pre-restore snapshot of the current state and runs config validation after restoring |
| `cleanup_config_backups` | Delete backup snapshots older than N days (default 30); keeps the folder from growing indefinitely |

**AppDaemon Files (opt-in)**

| Tool | Description |
|------|-------------|
| `list_appdaemon_files` | List regular files under the configured AppDaemon apps root with SHA-256 hashes |
| `get_appdaemon_file` | Read one relative AppDaemon app file with its SHA-256 hash |
| `save_appdaemon_file` | Atomically save one AppDaemon app file after creating a rollback snapshot |
| `delete_appdaemon_file` | Delete one AppDaemon app file after creating a rollback snapshot |
| `backup_appdaemon_files` | Create a controlled snapshot of the AppDaemon apps tree |
| `list_appdaemon_backups` | List available AppDaemon snapshots |
| `cleanup_appdaemon_backups` | Delete AppDaemon snapshots older than N days (default 30) |
| `restore_appdaemon_backup` | Restore a named AppDaemon snapshot with rollback protection |

AppDaemon file reads and save payloads are limited to 1 MB. Rollback snapshots
use a separate streaming-copy path with bounded memory and no per-file payload
limit, so an existing large history/data file does not make an unrelated save
or delete unusable. A snapshot is published only after every regular file has
been copied and source identity/metadata checked for changes during copying.

**Dashboards**

| Tool | Description |
|------|-------------|
| `list_dashboards` | List all Lovelace dashboards with metadata |
| `get_dashboard_config` | Get a dashboard configuration; `summary=true` returns an outline of views and cards, `path` returns just the part a JSON Pointer addresses |
| `patch_dashboard_config` | Apply JSON Patch operations to a dashboard: move, add, remove, or edit individual cards without resending the whole config |
| `save_dashboard_config` | Save (replace) full dashboard configuration |
| `delete_dashboard_config` | Reset a dashboard configuration to empty |
| `create_dashboard` | Create a new Lovelace dashboard (experimental) |
| `update_dashboard` | Update dashboard metadata (experimental) |
| `delete_dashboard` | Delete a dashboard and its config (experimental) |

**System & Infrastructure**

| Tool | Description |
|------|-------------|
| `get_config` | Get Home Assistant configuration (version, location, units, timezone) |
| `get_system_status` | System overview: version, domain counts, entity totals, problem entities |
| `get_domain_stats` | Aggregate stats for a single domain (count, state breakdown, examples) |
| `check_config` | Validate Home Assistant configuration without restarting |
| `restart_ha` | Restart Home Assistant (requires explicit confirmation) |
| `get_error_log` | Fetch the Home Assistant error log (last N lines) |
| `list_areas` | List all areas |
| `list_devices` | List devices, optionally filtered by area |
| `list_services` | List available services, optionally filtered by domain |
| `describe_service` | Get a service's full parameter schema: fields, selectors, examples, and targets |
| `list_integrations` | List installed integrations and their status |
| `list_labels` | List all labels for cross-domain grouping |

**KNX**

| Tool | Description |
|------|-------------|
| `knx_recent_telegrams` | Read Home Assistant's KNX group-monitor telegram history β€” recent bus telegrams incl. **source device** and decoded value; regex-filter by group address / name, with a result limit. Retrospective (reads the stored buffer), ideal for finding which KNX device wrote a given group address |
| `knx_get_base_data` | KNX connection + project info: bus connection status, gateway address, xknx version, loaded ETS project metadata, and UI-creatable platforms |
| `knx_get_entities` | List KNX group addresses and the entities bound to each (the KNX-specific group-address↔entity binding view); optional regex filter on the group address |
| `knx_create_entity` | Create a KNX entity in the KNX UI config (config_store) from `platform` + `data` (experimental) |
| `knx_update_entity` | Update a UI-managed KNX entity by `entity_id` (experimental) |
| `knx_delete_entity` | Delete a UI-managed KNX entity by `entity_id` (experimental) |

**Camera & Images**

These tools are disabled by default; enable them per capability via Settings β†’ Devices & Services β†’ MCP Server β†’ Configure. They return images directly to the model for visual analysis.

| Tool | Description |
|------|-------------|
| `get_camera_image` | Capture the current frame from a camera entity (optional `width`/`height` to downscale); no snapshot file is written (requires "Enable camera image access") |
| `get_image_file` | Read an image file (JPEG, PNG, GIF, WebP) from an allowed directory, e.g. a snapshot saved by `camera.snapshot` (requires "Enable image file access") |

### Resources

| URI | Description |
|-----|-------------|
| `hass://config` | Home Assistant configuration |
| `hass://areas` | All areas |
| `hass://devices` | All registered devices |
| `hass://services` | All available services by domain |
| `hass://floors` | All configured floors |
| `hass://entities` | All entities organized by domain |
| `hass://labels` | All labels |
| `hass://integrations` | Installed integrations with status |
| `hass://entity/{entity_id}` | State and attributes of a specific entity |
| `hass://dashboard/{url_path}` | Full configuration of a specific dashboard |
| `hass://entities/domain/{domain}` | Entities filtered by a specific domain |

### Prompts

| Prompt | Description |
|--------|-------------|
| `troubleshoot_device` | Diagnose issues with a specific entity |
| `daily_summary` | Summarize recent activity across all entities |
| `automation_review` | Review an automation's config for issues and improvements |
| `energy_report` | Summarize energy consumption data over a time range |
| `setup_guide` | Guided troubleshooting for an entity in a problem state |
| `automation_builder` | Step-by-step guided automation creation |
| `automation_debugger` | Debug why an automation is not firing or misbehaving |
| `automation_audit` | Audit all automations for conflicts, redundancies, and anti-patterns |
| `schedule_optimizer` | Analyze automation schedules and suggest timing improvements |
| `naming_conventions` | Scan entity names for inconsistencies and suggest standardization |
| `dashboard_builder` | Suggest a Lovelace dashboard layout for given entities or area |
| `change_validator` | Pre-flight check after creating or modifying configurations |
| `security_review` | Scan for security issues in entities, integrations, and configuration |

### Completions

Autocompletion is supported for `entity_id`, `entity_ids`, `domain`, `service`, `area_id`, `url_path`, `automation_id`, `scene_id`, script `key`, `trigger_type`, `period`, `config_type`, and helper `domain` arguments.

## FAQ

<details>
<summary>How do I list all automations, scenes, or scripts?</summary>

Use the dedicated list tools to get full configurations:

```
list_automations()   // all automations with triggers, conditions, actions
list_scenes()        // all scenes with entity states
list_scripts()       // all scripts with sequences
```

To get the configuration of a single item:

```
get_automation_config(automation_id="abc-123")
get_scene_config(scene_id="def-456")
get_script_config(key="morning_routine")
```

You can also use `list_entities(domain="automation")` to get entity states, but the tools above return the full YAML configuration.
</details>

<details>
<summary>How do I create an automation?</summary>

Use `create_automation` with a standard HA automation config:

```json
create_automation(config={
  "alias": "Turn on lights at sunset",
  "trigger": [{"platform": "sun", "event": "sunset"}],
  "action": [{"service": "light.turn_on", "target": {"entity_id": "light.living_room"}}]
})
```

The same pattern applies to scenes and scripts. Scripts use a `key` parameter instead of an auto-generated ID:

```json
create_script(key="morning_routine", config={
  "alias": "Morning Routine",
  "sequence": [{"service": "light.turn_on", "target": {"entity_id": "light.bedroom"}}]
})
```
</details>

<details>
<summary>How do I call a service like turning on a light?</summary>

Use `call_service` with the domain, service, and optionally an entity and extra data:

```json
call_service(domain="light", service="turn_on", entity_id="light.living_room", data={"brightness": 200})
```

To discover what services are available:

```
list_services()                    // all services
list_services(domain="light")      // just light services
```
</details>

<details>
<summary>How do I manage Lovelace dashboards?</summary>

Use `list_dashboards` to see all dashboards, then `get_dashboard_config` and `save_dashboard_config` to read and modify their content. Use `url_path="default"` for the main Overview dashboard:

```json
get_dashboard_config(url_path="default")
save_dashboard_config(url_path="default", config={"views": [{"title": "Home", "cards": [...]}]})
```

A real dashboard can easily be over 100 KB, which is too much to read and write back for a one-card change. For anything smaller than a rewrite, work through the outline instead.

`get_dashboard_config(url_path="default", summary=true)` returns each view and card as type, label, and entities, with a JSON Pointer for every entry:

```json
{
  "view_count": 2,
  "views": [
    {"pointer": "/views/0", "index": 0, "title": "Living Room", "path": "living", "card_count": 2,
     "cards": [
       {"pointer": "/views/0/cards/0", "index": 0, "type": "tile", "entity": "light.sofa"},
       {"pointer": "/views/0/cards/1", "index": 1, "type": "tile", "entity": "fan.air_purifier"}
     ]}
  ]
}
```

Pass one of those pointers as `path` to read a single card in full, and to `patch_dashboard_config` to change it. Operations follow [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) (`add`, `remove`, `replace`, `move`, `copy`, `test`) and apply in order, all-or-nothing, so a failing operation leaves the dashboard untouched:

```json
get_dashboard_config(url_path="default", path="/views/0/cards/1")

patch_dashboard_config(url_path="default", operations=[
  {"op": "test", "path": "/views/0/cards/1/entity", "value": "fan.air_purifier"},
  {"op": "move", "from": "/views/0/cards/1", "path": "/views/1/cards/-"}
])
```

A leading `test` operation makes the patch fail rather than edit the wrong card if the dashboard changed since it was read. End an array path with `/-` to append.

To create or delete dashboards themselves, use the experimental `create_dashboard` and `delete_dashboard` tools. These use internal HA APIs and may break with future HA updates.
</details>

<details>
<summary>How do I manage helper entities (input_boolean, counter, timer, etc.)?</summary>

Use `list_helpers` to see all helpers, optionally filtered by type:

```
list_helpers()                          // all helper types
list_helpers(domain="input_boolean")    // only toggles
```

To create a helper, specify the domain and a config with at least a `name`:

```json
create_helper(domain="input_boolean", config={"name": "Vacation Mode", "icon": "mdi:palm-tree"})
create_helper(domain="input_number", config={"name": "Target Temperature", "min": 15, "max": 30, "step": 0.5, "unit_of_measurement": "Β°C"})
create_helper(domain="input_select", config={"name": "House Mode", "options": ["Home", "Away", "Sleep"]})
create_helper(domain="counter", config={"name": "Motion Events", "initial": 0, "step": 1})
create_helper(domain="timer", config={"name": "Cooking Timer", "duration": "00:30:00"})
```

To update or delete, use the entity ID:

```json
update_helper(entity_id="input_boolean.vacation_mode", config={"name": "Vacation Mode", "icon": "mdi:airplane"})
delete_helper(entity_id="counter.motion_events")
```

> **Note:** These tools only manage UI-created helpers stored in Home Assistant's `.storage/` files. Helpers defined in YAML configuration are read-only from the perspective of these tools.
</details>

<details>
<summary>How do I read or edit YAML configuration files?</summary>

Use `list_config_files` to see all editable YAML files, then `get_config_file` and `save_config_file` to read and modify them:

```
list_config_files()
get_config_file(filename="automations.yaml")
save_config_file(filename="automations.yaml", content="...")
```

`save_config_file` automatically runs a full Home Assistant config validation after every save and reports any errors inline. Pass `run_check: false` to skip this.

To remove a custom file:

```
delete_config_file(filename="my_custom.yaml")
```

When editing multiple files in one session use `batch_edit_config_files` instead β€” it creates one backup snapshot before all changes and runs one config check at the end:

```
batch_edit_config_files(
    saves=[
        {"filename": "templates.yaml", "content": "..."},
        {"filename": "sensors.yaml",   "content": "..."},
    ],
    deletes=["binary_sensor.yaml", "old_lights.yaml"],
)
```

Both `saves` and `deletes` are optional β€” you can use either or both in the same call.

**Split configurations.** If your `configuration.yaml` splits config across folders β€” `!include_dir_merge_list`, `!include_dir_named`, `packages`, and friends β€” pass `recursive=true` to find those files, then read or edit them by their relative path:

```
list_config_files(recursive=True)
# β†’ ["configuration.yaml", "includes/templates/dishwasher.yaml", "packages/hvac.yaml", ...]

get_config_file(filename="includes/templates/dishwasher.yaml")
save_config_file(filename="includes/templates/dishwasher.yaml", content="...")
```

Recursive listing skips hidden folders (`.storage`, …), `custom_components`, `deps`, `www`, `node_modules`, `tts`, and `mcp_backups`. `save_config_file` will not create missing directories β€” create the folder in Home Assistant first.

> **Note:** Only `.yaml`/`.yml` files inside the config directory are accessible. Paths are resolved before use and must stay within the config directory β€” `..`, absolute paths, and symlinks pointing outside are rejected. The following are also blocked from direct edits β€” use the dedicated tools instead:
>
> - **`secrets.yaml`** β€” never readable, contains credentials. Blocked at **any** depth, so `includes/secrets.yaml` is protected too.
> - **`automations.yaml`**, **`scenes.yaml`**, **`scripts.yaml`** β€” owned by Home Assistant's storage layer; use `create_automation` / `update_automation` / `delete_automation` (and the equivalents for scenes and scripts) so UI-managed entries stay consistent. These files are still included in backups, so a restore never drops them. Only the **top-level** files are blocked β€” a split-config file that happens to be named `packages/scripts.yaml` is your own and stays editable.
> - Anything inside **`mcp_backups/`** β€” use `list_config_backups` / `restore_config_backup` instead.

> **Backup caveat:** the automatic snapshot taken before every save/delete covers **first-level** YAML files only. Edits to files in subdirectories are not captured by it, so there is no automatic rollback for those β€” keep your own copy (or use version control) before editing split config.
</details>

<details>
<summary>How do I let the AI see a camera or analyze an image?</summary>

Camera and image access are disabled by default. Enable them per capability via Settings β†’ Devices & Services β†’ MCP Server β†’ Configure: "Enable camera image access" for live camera frames, and "Enable image file access" for reading saved image files.

To analyze what a camera sees right now, capture the current frame directly β€” no snapshot file is needed:

```
get_camera_image(entity_id="camera.front_door")
get_camera_image(entity_id="camera.front_door", width=1024)
```

To analyze a snapshot already saved to disk (for example by the `camera.snapshot` service), read it back by path:

```
get_image_file(path="www/snapshots/front_door.jpg")
get_image_file(path="/config/www/snapshots/front_door.jpg")
```

The image is returned directly to the model for analysis. `get_image_file` supports JPEG, PNG, GIF, and WebP, and can only read from directories Home Assistant is allowed to access (the config directory and configured media dirs) β€” the same allowlist that governs where `camera.snapshot` can write.
</details>

<details>
<summary>How does the automatic backup work?</summary>

Every call to `save_config_file` and `delete_config_file` automatically creates a full snapshot of all first-level YAML files (excluding `secrets.yaml`) before making any change. Files in subdirectories are not included in this snapshot. When using `batch_edit_config_files`, only one backup is created for the entire batch β€” regardless of how many files are saved or deleted. Snapshots are stored in:

```
config/mcp_backups/2026-04-26_14-30-00-123456/
```

The backup path is included in the tool response so you always know where to look. You never need to remember to back up manually before an edit β€” it happens every time.

To create an additional manual snapshot before a larger operation:

```
backup_config_files()
```

To see all available snapshots:

```
list_config_backups()
```

To restore the latest snapshot (or a specific one by timestamp):

```
restore_config_backup()
restore_config_backup(timestamp="2026-04-26_14-30-00-123456")
```

`restore_config_backup` only overwrites files present in the backup β€” files created after the snapshot are left untouched. Before any files are overwritten it creates a **pre-restore snapshot** of the current state, so you can always roll back from a restore (the snapshot path is included in the response). A config check runs automatically after restoring.

> **If Home Assistant fails to start** after a bad edit: backup files are always accessible at `config/mcp_backups/<timestamp>/` and can be copied back manually via the filesystem or SSH.

**Restoring a single file:** `restore_config_backup` always restores all files from a snapshot at once β€” there is no tool to restore a single file. If you only need one file back, either restore the full snapshot and re-apply your other changes, or copy the file manually from `config/mcp_backups/<timestamp>/<filename>` via SSH, Samba, or the File Editor add-on.

**Cleaning up old backups:** Snapshots accumulate with every edit. Use `cleanup_config_backups` to remove old ones:

```
cleanup_config_backups()                    // delete backups older than 30 days (default)
cleanup_config_backups(older_than_days=7)   // delete backups older than 7 days
```

You can also delete entries from `config/mcp_backups/` manually via SSH, the Samba share, or the File Editor add-on.
</details>

<details>
<summary>What does "experimental" mean for some tools?</summary>

Some tools use internal Home Assistant APIs that are not publicly exposed and may break with future HA updates.

**Dashboard tools:** `create_dashboard`, `update_dashboard`, and `delete_dashboard` use `DashboardsCollection` and replicate side effects (panel registration, dashboards dict updates) that HA normally handles internally. The config-level tools (`list_dashboards`, `get/save/patch/delete_dashboard_config`) use stable public APIs and are not experimental.

**Helper tools:** `get_helper_config`, `create_helper`, `update_helper`, and `delete_helper` use `StorageCollection` internals to manage UI-created helpers. They only affect helpers stored in `.storage/` β€” helpers defined in YAML are read-only from the perspective of these tools. `list_helpers` uses public APIs and is not experimental.
</details>

<details>
<summary>How does bounded AppDaemon app-file access work?</summary>

AppDaemon file access is disabled by default. Enabling write access allows an
AI/MCP client to modify executable AppDaemon Python code, granting
code-execution-equivalent capability inside the AppDaemon environment and
whatever Home Assistant API access AppDaemon has. Enable it only after
reviewing the contents and secrets of the apps tree. The `appdaemon_apps_root` option defaults to the
legacy AppDaemon private apps path, `/addon_configs/a0d7b954_appdaemon/apps`,
for compatibility. On installations where Home Assistant Core cannot see an
add-on-private mount, configure the AppDaemon add-on and this integration to
use a shared root such as `/share/appdaemon/apps` or `/media/appdaemon/apps`.

Only the legacy root and paths below `/share/` or `/media/` are accepted.
Absolute paths, malformed components, `..` traversal, symlink components, and
paths escaping the configured root are rejected. All list, read, save, delete,
backup, and restore operations use the same validated root. Mutating operations
create controlled snapshots, and saves use atomic replacement where supported.
Only `.py`, `.yaml`, `.yml`, and `.json` files can be created, written, or
deleted. Backups are bounded by the AppDaemon root and can be pruned with
`cleanup_appdaemon_backups`. There is no direct MCP shell or AppDaemon
process-control tool, but writing AppDaemon Python files remains
code-execution-equivalent within the AppDaemon environment and its available
Home Assistant API permissions. Existing files with other extensions are
captured in backups but are reported as skipped during restore; they are never
made writable by restore.
</details>

## License

MIT

Maintenance

ActivityActive
ResponsivenessWithin a week