ESPHome MCP Bridge
Drive a full ESPHome development cycle from an AI agent (e.g. Claude Code via Home Assistant's MCP server). This repo ships a Home Assistant custom integration that registers a custom LLM API exposing ESPHome tools, backed by a small reusable PyPI client library.
AI agent ── MCP ──► Home Assistant ──► ESPHome Builder LLM API
│
┌───────────────┴───────────────┐
▼ ▼
/config/esphome (files) ESPHome dashboard add-on
(stable / beta / dev) via
Supervisor internal network
Why this works without sidecars
The integration runs inside Home Assistant Core, so it has:
- Native filesystem access to
/config/esphomefor reading, creating, and writing YAML — reliable and independent of which add-on is installed. - Internal network access to the ESPHome dashboard add-on through the Supervisor, reaching the dashboard's container directly (no ingress auth, no exposed ports) for validate / compile / upload / run / logs.
No changes to the ESPHome add-on and no external containers required.
The development-cycle tools
The ESPHome Builder LLM API exposes these tools:
| Tool | Purpose |
|---|---|
esphome_list_addons |
Discover installed ESPHome channels (stable/beta/dev), versions, state |
esphome_list_devices |
Inventory devices: name, version, platform, integrations, online status |
esphome_read_yaml |
Read a config from /config/esphome |
esphome_create_config |
Create a new config (fails if it exists) |
esphome_write_yaml |
Overwrite an existing config |
esphome_patch_yaml |
Edit an existing config via exact-match {old_string, new_string} replacements, without resending the whole file |
esphome_add_secret |
Insert a key into secrets.yaml — insert-only, write-only (never reads/returns values; errors if the key exists) |
esphome_validate |
Validate a config without building |
esphome_compile |
Compile firmware (runs to completion, returns log + exit code) |
esphome_upload |
Flash firmware to a device (port defaults to OTA) |
esphome_run |
Compile and flash in one step (dashboard "Install") |
esphome_logs |
Capture live device logs, filtered by level, tag and text — streams directly from the device (works on classic dashboard and Device Builder) when it's adopted in HA, else falls back to the dashboard. Up to 30 s directly, or in the background (see below) |
esphome_clean |
Remove cached build artifacts |
esphome_job_status |
Poll a background compile/upload/run or log capture; stop=true ends a log capture early |
A typical agent flow: list devices → read/create config → write → validate → compile → run → check logs.
Logs
esphome_logs options:
| Option | Meaning |
|---|---|
level |
Minimum severity: error, warn, info, config, debug, verbose, very_verbose (default). In device mode the device itself filters at this level |
tags / exclude_tags |
Component tags to keep or drop, with * globs, e.g. ["race", "esp32_ble*"] |
search |
Case-insensitive text, or /regex/ (/regex/i for case-insensitive) |
context |
Lines kept before and after each search match (-- separates groups) |
include_config_dump |
Include the device's startup config dump (skipped by default) |
no_states |
Dashboard mode: don't print entity state changes |
max_lines |
Lines to keep after filtering (default 500), so a narrow filter can wait for matches |
max_seconds |
Capture window. Capped at 30 s unless background=true (up to 600 s) |
background |
Return a job_id at once and keep capturing; poll esphome_job_status, and pass stop=true once you've seen what you need |
Responses report lines_received, lines_returned, the filters applied, and
ended_by (window, max_lines, stopped or closed), plus a note when
nothing came back.
Why 30 seconds: proxies in front of Home Assistant (Nabu Casa remote access,
reverse proxies) cut off requests at roughly 45 s with HTTP 502. Anything that
would take longer — slow compiles, long log captures — should use
background=true.
Each build tool accepts an optional addon_slug to target a specific channel;
omit it to use the default (stable preferred over beta over dev).
Multi-channel discovery
esphome_list_addons queries the Supervisor for every installed add-on whose
slug or name matches esphome (5c53de3b_esphome, …_esphome-beta,
…_esphome-dev). The bridge resolves each add-on's internal hostname and port
from the Supervisor, so it adapts automatically rather than hard-coding a URL.
Requirements
- Home Assistant with the Supervisor (HA OS or Supervised install)
- An ESPHome dashboard add-on installed (classic dashboard or the new Device Builder — both speak the protocol this bridge uses)
- Home Assistant 2024.4.0+ (LLM API helpers)
Installation
HACS (integration)
-
Click the badge above (or add this repository as a custom HACS repository, type Integration).
-
Install ESPHome MCP Bridge and restart Home Assistant.
-
Add the integration — click the badge below, or go to Settings → Devices & Services → Add Integration, search ESPHome MCP Bridge, and select Submit. There's nothing to configure — it's a single-instance, confirm-only setup.
-
The
ESPHome BuilderLLM API is now registered. To actually expose it to an AI agent, point Home Assistant's MCP Server at it — see Connecting an AI agent below.
Manual
Copy custom_components/esphome_mcp_bridge/ into your HA
config/custom_components/, restart, then add it from Settings → Devices &
Services → Add Integration as above.
Home Assistant installs the
esphome-mcp-clientPyPI dependency automatically (declared in the integration manifest).
Connecting an AI agent (MCP)
Installing this integration only registers the ESPHome Builder LLM API — it does not expose it to your agent by itself. Home Assistant's separate Model Context Protocol Server integration is what serves an API's tools to MCP clients (Claude Desktop, Claude Code, …).
- Install and add ESPHome MCP Bridge (above) so the ESPHome Builder API is registered.
- Add the Model Context Protocol Server integration: Settings → Devices & Services → Add Integration → Model Context Protocol Server.
- In its dialog, under Control Home Assistant, tick ESPHome Builder (you can leave Assist ticked too — it's multi-select), then Submit.
- Point your MCP client at Home Assistant's MCP server endpoint (the MCP Server
integration's docs give the SSE URL + token), then refresh its tool list. The
ESPHome tools (
esphome_list_devices,esphome_write_yaml,esphome_compile, …) will appear.
Already had the MCP Server set up before installing this? The API list is read when that integration is configured, so ESPHome Builder won't be listed. Delete the Model Context Protocol Server integration and re-add it so the checkbox appears.
If ESPHome Builder is missing from the checkbox list in step 3, the integration isn't loaded — check the logs and confirm it's on the latest version.
Security
- File tools are confined to
/config/esphome; directory traversal (..,/,\) is rejected outright. secrets.yaml/secrets.ymlare blocked from read, write, create, and build.- Writes/creates/patches accept only
.yaml/.yml. - Supervisor requests use the
SUPERVISOR_TOKENinjected at runtime — no credentials are stored.
Repository layout
esphome_mcp_client/— reusable async transport library → published to PyPI asesphome-mcp-client(see PYPI_SETUP.md).custom_components/esphome_mcp_bridge/— the Home Assistant integration (LLM tool definitions + API registration).tests/— unit tests for the client library.
Development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check esphome_mcp_client tests
pytest # unit tests (fast)
pip install esphome # only needed for the lines below
pytest tests/integration -m integration # smoke tests vs a real dashboard
Testing approach
Two layers, both deliberately avoiding fakes of the things most likely to drift:
Unit tests run against a real in-process aiohttp server, not a mocking
library. We originally used aioresponses,
but aiohttp 3.12 added a required stream_writer argument to ClientResponse
that aioresponses doesn't pass — and there is no fixed release. Worse, it passed
locally (our dev venv happened to pin an older aiohttp transitively) and only
failed in CI on the latest aiohttp. A mock that reimplements aiohttp's
internals can silently fall out of sync with aiohttp; a tiny real server
(tests/conftest.py) cannot. So we serve canned responses from an actual
aiohttp.web app on a loopback port and exercise the real client against it.
Integration smoke tests run against a real esphome dashboard
(tests/integration/), launched on a temp config dir, exercising the REST
endpoints and the WebSocket spawn protocol end to end. They auto-skip when
ESPHome isn't installed, so the unit run is unaffected.
Detecting upstream API drift
The dashboard protocol is an external contract ESPHome controls, so we watch it actively rather than waiting for user reports:
- Scheduled smoke workflow (
smoke-test.yml) runs the integration tests weekly across the stable / beta / dev ESPHome channels. If ESPHome changes the API, a build goes red before users hit it. (Thedevchannel is allowed-failure;compileis gated behind a manual dispatch as it downloads a full toolchain.) - Defensive client logging —
DashboardClient.inventory()logs a warning if the/devicesresponse loses its expected shape, andversion()surfaces the dashboard's ESPHome version, so drift becomes a visible log line for triage.
License
MIT © Justin Rigling
Metadata
Release files for esphome-mcp-client 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| esphome_mcp_client-0.5.0.tar.gz | 42.2 kB | Without provenance | Details for esphome_mcp_client-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| esphome_mcp_client-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for esphome_mcp_client-0.5.0-py3-none-any.whl |
Total release size: 70.5 kB
Release files / esphome_mcp_client-0.5.0.tar.gz
| Download URL | esphome_mcp_client-0.5.0.tar.gz |
|---|---|
| Size | 42.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c831844417780a6b1e3f552c634c67612970702a81993801693bf84cda2005ae
|
|
BLAKE2b-256 checksum How to use checksums |
48c776ec363f775d785fa3fa0bf303b528d5178db965bf5b1987949029292a08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / esphome_mcp_client-0.5.0-py3-none-any.whl
| Download URL | esphome_mcp_client-0.5.0-py3-none-any.whl |
|---|---|
| Size | 28.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9bcb76145d83a817f9d002298722446b2ebbb09eef6b5f5e269ad94ef6fd61c9
|
|
BLAKE2b-256 checksum How to use checksums |
90af38bb4d62e652a6520cbbcaa10a15b6c03755f75ce057adecff4dbc6a7cb5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Security for esphome-mcp-client 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.