@orellbuehler/homeassistant-mcp 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -12
- package/dist/server.js +3 -1
- package/dist/tools/automations.js +62 -0
- package/dist/tools/hacs.js +1 -1
- package/dist/tools/resources.js +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,20 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Home Assistant MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@orellbuehler/homeassistant-mcp)
|
|
4
|
+
[](https://www.npmjs.com/package/@orellbuehler/homeassistant-mcp)
|
|
4
5
|
[](https://github.com/OrellBuehler/homeassistant-mcp/actions/workflows/ci.yml)
|
|
5
6
|
[](https://nodejs.org)
|
|
6
7
|
[](./LICENSE)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
[
|
|
10
|
-
|
|
9
|
+
A [Model Context Protocol](https://modelcontextprotocol.io/) server that turns your
|
|
10
|
+
[Home Assistant](https://www.home-assistant.io/) instance into a safe workbench for AI agents —
|
|
11
|
+
built to **author and validate configuration and automations**, not to control your home.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
Point Claude (or any MCP client) at it and the agent can:
|
|
14
|
+
|
|
15
|
+
- **See what exists, live** — entities, services, events, areas, devices, labels, state history,
|
|
16
|
+
logbook.
|
|
17
|
+
- **Author configuration** — create/edit automations, configure the Energy dashboard, manage
|
|
18
|
+
Zigbee (ZHA) groups, register Lovelace resources, prune HACS repositories, rename entities,
|
|
19
|
+
reload YAML domains.
|
|
20
|
+
- **Validate its own work** — render Jinja2 templates against live state, run HA's config check,
|
|
21
|
+
read the error log, and inspect step-by-step execution traces of automations it wrote.
|
|
22
|
+
- **Never touch your devices** — there is deliberately no `call_service`, no `set_state`, and no
|
|
23
|
+
`fire_event`. The agent writes config; it cannot turn anything on or off.
|
|
24
|
+
|
|
25
|
+
42 tools, zero install (`npx`), works with Claude Code, Claude Desktop, Cursor, and any other MCP
|
|
26
|
+
client.
|
|
27
|
+
|
|
28
|
+
## Example prompts
|
|
29
|
+
|
|
30
|
+
> "Create an automation that turns on the porch light 30 minutes before sunset, but only when
|
|
31
|
+
> someone is home."
|
|
32
|
+
|
|
33
|
+
> "My 'goodnight' automation didn't fire last night. Figure out why."
|
|
34
|
+
|
|
35
|
+
> "Write a template sensor for net grid power and validate it against live state before I put it
|
|
36
|
+
> in my YAML."
|
|
37
|
+
|
|
38
|
+
> "Set up my Energy dashboard: grid import/export from these two sensors, solar from the inverter,
|
|
39
|
+
> and add every smart plug as an individual device."
|
|
40
|
+
|
|
41
|
+
> "Group the three living-room bulbs into one Zigbee group so they dim in sync."
|
|
42
|
+
|
|
43
|
+
> "I uninstalled a HACS card — find and remove the dangling Lovelace resource."
|
|
18
44
|
|
|
19
45
|
## Install
|
|
20
46
|
|
|
@@ -81,6 +107,10 @@ available immediately. Verify with `claude mcp list` (should show `homeassistant
|
|
|
81
107
|
|
|
82
108
|
## Tools
|
|
83
109
|
|
|
110
|
+
42 tools in 12 groups. REST tools go through the
|
|
111
|
+
[Home Assistant REST API](https://www.home-assistant.io/integrations/api/); WebSocket tools use the
|
|
112
|
+
config registries that are not exposed over REST.
|
|
113
|
+
|
|
84
114
|
**Entities & state** (REST)
|
|
85
115
|
|
|
86
116
|
| Tool | Description |
|
|
@@ -118,6 +148,21 @@ available immediately. Verify with `claude mcp list` (should show `homeassistant
|
|
|
118
148
|
| -------- | ------------------------------------------------------------------------------------------ |
|
|
119
149
|
| `reload` | Reload a reloadable domain so YAML edits apply without a restart (`all`, `automation`, …). |
|
|
120
150
|
|
|
151
|
+
**Automations** (REST, `/api/config/automation`)
|
|
152
|
+
|
|
153
|
+
| Tool | Description |
|
|
154
|
+
| ----------------------- | ------------------------------------------------------------------------------ |
|
|
155
|
+
| `get_automation_config` | Stored config (triggers/conditions/actions/mode) of one automation. |
|
|
156
|
+
| `upsert_automation` | Create a new automation or replace an existing one's config (validated by HA). |
|
|
157
|
+
| `delete_automation` | Delete a UI-managed automation. |
|
|
158
|
+
|
|
159
|
+
All three accept the `automation.*` entity id (the internal id is resolved automatically) or the
|
|
160
|
+
internal id itself. Writes require an **admin token**; HA validates the config, stores it in
|
|
161
|
+
`automations.yaml` and reloads automations automatically. `upsert_automation` replaces the whole
|
|
162
|
+
config, so edit via `get_automation_config` → modify → `upsert_automation`. Only automations with
|
|
163
|
+
an `id` (UI-created / `automations.yaml`) are reachable — automations defined elsewhere in YAML are
|
|
164
|
+
not. Debug the result with the trace tools below.
|
|
165
|
+
|
|
121
166
|
**Registries** (WebSocket)
|
|
122
167
|
|
|
123
168
|
| Tool | Description |
|
|
@@ -206,8 +251,9 @@ is globally in YAML mode. This is config authoring, not device control.
|
|
|
206
251
|
|
|
207
252
|
`list_hacs_repositories` maps each repo to `id` + `local_path`/`file_name`, so you can tell which
|
|
208
253
|
`/hacsfiles/…` Lovelace resource belongs to a plugin. `remove_hacs_repository` calls
|
|
209
|
-
`hacs/repository/remove` (**admin token**) and deletes the plugin's files
|
|
210
|
-
|
|
254
|
+
`hacs/repository/remove` (**admin token**) and deletes the plugin's files; when HACS manages
|
|
255
|
+
resources (storage mode) it also drops the plugin's Lovelace resource for you, so verify with
|
|
256
|
+
`list_lovelace_resources` and only `delete_lovelace_resource` one that's left behind. Removing an
|
|
211
257
|
integration you still reference in YAML will break that config, so check usage first.
|
|
212
258
|
|
|
213
259
|
## Safety boundary
|
|
@@ -216,6 +262,11 @@ integration you still reference in YAML will break that config, so check usage f
|
|
|
216
262
|
The server cannot turn things on/off.
|
|
217
263
|
- **`reload` is restricted** to a fixed allowlist of `*.reload` / `homeassistant.reload_*` services.
|
|
218
264
|
It restarts reloadable domains (e.g. re-reads `automations.yaml`) but cannot control devices.
|
|
265
|
+
- **Automation configs are writable** via the `/api/config/automation` REST API
|
|
266
|
+
(`upsert_automation` / `delete_automation`). This is the server's core purpose — authoring
|
|
267
|
+
automations — and needs an admin token. Be aware that an automation, once written, runs its
|
|
268
|
+
actions whenever its triggers fire, so review configs before writing. The server still cannot run
|
|
269
|
+
services directly: no `call_service`, `set_state`, or `fire_event`.
|
|
219
270
|
- **Energy dashboard config is writable** via `energy/save_prefs` (the `*_energy_*` tools). This is
|
|
220
271
|
configuration authoring — the dashboard's sources and Individual-devices list — not device control,
|
|
221
272
|
and it needs an admin token. No `call_service`, `set_state`, or `fire_event` is added.
|
package/dist/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import { registerSystemTools } from "./tools/system.js";
|
|
|
5
5
|
import { registerTemplateTools } from "./tools/templates.js";
|
|
6
6
|
import { registerHistoryTools } from "./tools/history.js";
|
|
7
7
|
import { registerReloadTools } from "./tools/reload.js";
|
|
8
|
+
import { registerAutomationTools } from "./tools/automations.js";
|
|
8
9
|
import { registerRegistryTools } from "./tools/registry.js";
|
|
9
10
|
import { registerEnergyTools } from "./tools/energy.js";
|
|
10
11
|
import { registerTraceTools } from "./tools/trace.js";
|
|
@@ -12,13 +13,14 @@ import { registerZhaTools } from "./tools/zha.js";
|
|
|
12
13
|
import { registerHacsTools } from "./tools/hacs.js";
|
|
13
14
|
import { registerResourceTools } from "./tools/resources.js";
|
|
14
15
|
export function createServer(client, wsClient) {
|
|
15
|
-
const server = new McpServer({ name: "homeassistant-mcp", version: "0.
|
|
16
|
+
const server = new McpServer({ name: "homeassistant-mcp", version: "0.7.0" });
|
|
16
17
|
registerEntityTools(server, client);
|
|
17
18
|
registerServiceTools(server, client);
|
|
18
19
|
registerSystemTools(server, client);
|
|
19
20
|
registerTemplateTools(server, client);
|
|
20
21
|
registerHistoryTools(server, client);
|
|
21
22
|
registerReloadTools(server, client);
|
|
23
|
+
registerAutomationTools(server, client);
|
|
22
24
|
registerRegistryTools(server, wsClient);
|
|
23
25
|
registerEnergyTools(server, wsClient, client);
|
|
24
26
|
registerTraceTools(server, wsClient, client);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ok, err } from "../hass/format.js";
|
|
3
|
+
async function resolveId(automation, client) {
|
|
4
|
+
if (!automation.startsWith("automation."))
|
|
5
|
+
return automation;
|
|
6
|
+
const state = (await client.fetch(`/api/states/${encodeURIComponent(automation)}`));
|
|
7
|
+
const id = state.attributes?.id;
|
|
8
|
+
if (id === undefined || id === null || id === "") {
|
|
9
|
+
throw new Error(`Automation ${automation} has no 'id' attribute, so it is not stored in automations.yaml and cannot be read or edited via the config API (it is defined elsewhere in YAML without an id)`);
|
|
10
|
+
}
|
|
11
|
+
return String(id);
|
|
12
|
+
}
|
|
13
|
+
const configPath = (id) => `/api/config/automation/config/${encodeURIComponent(id)}`;
|
|
14
|
+
export function registerAutomationTools(server, client) {
|
|
15
|
+
server.tool("get_automation_config", "Get the stored configuration of an automation (alias, description, triggers, conditions, actions, mode) from automations.yaml via GET /api/config/automation/config/{id}. Accepts the automation.* entity id (the internal id is resolved automatically) or the internal id itself. Only automations with an 'id' (UI-created, stored in automations.yaml) are accessible.", {
|
|
16
|
+
automation: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("automation.* entity id, or the automation's internal id (the 'id' attribute)"),
|
|
19
|
+
}, async ({ automation }) => {
|
|
20
|
+
try {
|
|
21
|
+
const id = await resolveId(automation, client);
|
|
22
|
+
return ok(await client.fetch(configPath(id)));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return err(e);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
server.tool("upsert_automation", "Create a new automation or replace an existing one via POST /api/config/automation/config/{id} (requires an admin token). Home Assistant validates the config, writes it to automations.yaml and reloads automations automatically — no separate reload needed. The config REPLACES the stored one entirely, so to edit call get_automation_config first and send back the modified object. Omit 'automation' to create a new one (an id is generated and returned); its entity id becomes automation.<slugified alias>. Note that once its triggers fire, an automation runs its actions — review the config before writing.", {
|
|
29
|
+
config: z
|
|
30
|
+
.record(z.unknown())
|
|
31
|
+
.describe("Full automation config as stored in automations.yaml: alias, description?, triggers, conditions?, actions, mode? (the legacy singular trigger/condition/action keys are also accepted)"),
|
|
32
|
+
automation: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Existing automation.* entity id or internal id to replace; omit to create a new automation"),
|
|
36
|
+
}, async ({ config, automation }) => {
|
|
37
|
+
try {
|
|
38
|
+
const id = automation ? await resolveId(automation, client) : String(Date.now());
|
|
39
|
+
const result = await client.fetch(configPath(id), {
|
|
40
|
+
method: "POST",
|
|
41
|
+
body: JSON.stringify(config),
|
|
42
|
+
});
|
|
43
|
+
return ok({ id, created: !automation, result });
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return err(e);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
server.tool("delete_automation", "Delete an automation from automations.yaml via DELETE /api/config/automation/config/{id} (requires an admin token). Accepts the automation.* entity id or the internal id. Only UI-managed automations (those stored in automations.yaml) can be deleted; Home Assistant reloads automations automatically afterwards.", {
|
|
50
|
+
automation: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe("automation.* entity id, or the automation's internal id (the 'id' attribute)"),
|
|
53
|
+
}, async ({ automation }) => {
|
|
54
|
+
try {
|
|
55
|
+
const id = await resolveId(automation, client);
|
|
56
|
+
return ok(await client.fetch(configPath(id), { method: "DELETE" }));
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return err(e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/tools/hacs.js
CHANGED
|
@@ -36,7 +36,7 @@ export function registerHacsTools(server, ws) {
|
|
|
36
36
|
return err(e);
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
|
-
server.tool("remove_hacs_repository", "Uninstall a HACS repository by id (hacs/repository/remove) — deletes its downloaded files from the config (for a plugin, the JS under www/community/…) and unregisters it from HACS. Requires an admin token.
|
|
39
|
+
server.tool("remove_hacs_repository", "Uninstall a HACS repository by id (hacs/repository/remove) — deletes its downloaded files from the config (for a plugin, the JS under www/community/…) and unregisters it from HACS. Requires an admin token. When HACS manages resources (storage mode) it also removes the plugin's /hacsfiles/… Lovelace resource for you; verify with list_lovelace_resources and use delete_lovelace_resource only if one is left behind. Get the id from list_hacs_repositories. Removing an installed integration you still reference in YAML will break that config, so confirm nothing uses it first.", {
|
|
40
40
|
repository_id: z
|
|
41
41
|
.string()
|
|
42
42
|
.describe("The repository id from list_hacs_repositories (a numeric string, e.g. '172733314')."),
|
package/dist/tools/resources.js
CHANGED
|
@@ -42,7 +42,7 @@ export function registerResourceTools(server, ws) {
|
|
|
42
42
|
return err(e);
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
server.tool("delete_lovelace_resource", "Delete a Lovelace resource (lovelace/resources/delete) by id — removes the JS/CSS URL from every dashboard. Config authoring; requires an admin token and storage-mode resources. Use this
|
|
45
|
+
server.tool("delete_lovelace_resource", "Delete a Lovelace resource (lovelace/resources/delete) by id — removes the JS/CSS URL from every dashboard. Config authoring; requires an admin token and storage-mode resources. Use this for manually-registered resources (e.g. /local/…) or a /hacsfiles/… resource that HACS did not auto-remove after remove_hacs_repository. Get the resource_id from list_lovelace_resources.", {
|
|
46
46
|
resource_id: z.string().describe("The resource id from list_lovelace_resources."),
|
|
47
47
|
}, async ({ resource_id }) => {
|
|
48
48
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orellbuehler/homeassistant-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Model Context Protocol server for Home Assistant: introspect entities, services, events, registries, render templates and validate configuration to help AI agents author HA configs and automations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,11 +26,16 @@
|
|
|
26
26
|
},
|
|
27
27
|
"keywords": [
|
|
28
28
|
"mcp",
|
|
29
|
+
"mcp-server",
|
|
29
30
|
"model-context-protocol",
|
|
30
31
|
"home-assistant",
|
|
31
32
|
"homeassistant",
|
|
32
33
|
"hass",
|
|
33
34
|
"home-automation",
|
|
35
|
+
"smart-home",
|
|
36
|
+
"automation",
|
|
37
|
+
"claude",
|
|
38
|
+
"ai-agents",
|
|
34
39
|
"llm",
|
|
35
40
|
"ai"
|
|
36
41
|
],
|