@orellbuehler/homeassistant-mcp 0.5.0 → 0.6.0
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 +35 -3
- package/dist/server.js +5 -1
- package/dist/tools/hacs.js +52 -0
- package/dist/tools/resources.js +56 -0
- package/dist/tools/zha.js +16 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ AI agents.
|
|
|
12
12
|
It is built to **help an agent author and validate Home Assistant configuration and automations** —
|
|
13
13
|
not to control your home. It tells the agent what exists live (entities, services, events, areas,
|
|
14
14
|
devices), validates the agent's work (render Jinja2 templates, check config, read the error log),
|
|
15
|
-
can reload reloadable domains after YAML edits,
|
|
16
|
-
|
|
17
|
-
fire events**.
|
|
15
|
+
can reload reloadable domains after YAML edits, can configure the Energy dashboard (sources and the
|
|
16
|
+
Individual-devices list), manage Lovelace resources, and prune HACS repositories. It deliberately has
|
|
17
|
+
**no tools to turn devices on/off, set states, or fire events**.
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -183,6 +183,33 @@ groupable ZHA endpoints (devices that support the Zigbee Groups cluster); `creat
|
|
|
183
183
|
`list_zha_groupable`, or you can pass the `{ieee, endpoint_id}` pairs directly. The HA group entity
|
|
184
184
|
is created asynchronously, so read its final `entity_id` from the entity registry afterwards.
|
|
185
185
|
|
|
186
|
+
**Lovelace resources** (WebSocket)
|
|
187
|
+
|
|
188
|
+
| Tool | Description |
|
|
189
|
+
| -------------------------- | -------------------------------------------------------------------------- |
|
|
190
|
+
| `list_lovelace_resources` | Registered dashboard resources (id, type, url), e.g. `/hacsfiles/…`. |
|
|
191
|
+
| `create_lovelace_resource` | Register a JS/CSS URL loaded on every dashboard (defaults to module). |
|
|
192
|
+
| `update_lovelace_resource` | Change a resource's url and/or type by id. |
|
|
193
|
+
| `delete_lovelace_resource` | Remove a resource by id (e.g. the dangling one left after a HACS removal). |
|
|
194
|
+
|
|
195
|
+
Resources are the JS/CSS bundles loaded into every dashboard (Settings → Dashboards → Resources).
|
|
196
|
+
The write tools call `lovelace/resources/*`, **require an admin token**, and only work when the
|
|
197
|
+
resource registry is in **storage mode** (`lovelace: mode: storage`); they are disabled if Lovelace
|
|
198
|
+
is globally in YAML mode. This is config authoring, not device control.
|
|
199
|
+
|
|
200
|
+
**HACS** (WebSocket)
|
|
201
|
+
|
|
202
|
+
| Tool | Description |
|
|
203
|
+
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
204
|
+
| `list_hacs_repositories` | HACS repositories (installed-only by default): id, category, versions, paths. |
|
|
205
|
+
| `remove_hacs_repository` | Uninstall a repository by id — deletes its downloaded files and unregisters it. |
|
|
206
|
+
|
|
207
|
+
`list_hacs_repositories` maps each repo to `id` + `local_path`/`file_name`, so you can tell which
|
|
208
|
+
`/hacsfiles/…` Lovelace resource belongs to a plugin. `remove_hacs_repository` calls
|
|
209
|
+
`hacs/repository/remove` (**admin token**) and deletes the plugin's files, but not its Lovelace
|
|
210
|
+
resource — pair it with `delete_lovelace_resource` to fully clean up a frontend plugin. Removing an
|
|
211
|
+
integration you still reference in YAML will break that config, so check usage first.
|
|
212
|
+
|
|
186
213
|
## Safety boundary
|
|
187
214
|
|
|
188
215
|
- **No device control.** There is no generic `call_service`, no `set_state`, and no `fire_event`.
|
|
@@ -195,6 +222,11 @@ is created asynchronously, so read its final `entity_id` from the entity registr
|
|
|
195
222
|
- **ZHA groups are writable** via `zha/group/*` (the `*_zha_group*` tools). Creating/editing a Zigbee
|
|
196
223
|
group changes which group entities exist (config authoring), not any device's on/off state, and it
|
|
197
224
|
needs an admin token. Still no `call_service`, `set_state`, or `fire_event`.
|
|
225
|
+
- **Lovelace resources are writable** via `lovelace/resources/*` (the `*_lovelace_resource` tools) —
|
|
226
|
+
which JS/CSS bundles load into dashboards. Config authoring, admin token, storage-mode only.
|
|
227
|
+
- **HACS repositories are removable** via `hacs/repository/remove` (`remove_hacs_repository`). This
|
|
228
|
+
uninstalls a plugin/integration/theme and deletes its files (admin token); there is no install tool
|
|
229
|
+
(the server never downloads third-party code). Still no `call_service`, `set_state`, or `fire_event`.
|
|
198
230
|
- **Read tools expose your home's data** (entity names, states, areas) to the agent/LLM. Use a
|
|
199
231
|
scoped HA user if that matters to you.
|
|
200
232
|
- Prefer `https` and a trusted network path to your instance.
|
package/dist/server.js
CHANGED
|
@@ -9,8 +9,10 @@ import { registerRegistryTools } from "./tools/registry.js";
|
|
|
9
9
|
import { registerEnergyTools } from "./tools/energy.js";
|
|
10
10
|
import { registerTraceTools } from "./tools/trace.js";
|
|
11
11
|
import { registerZhaTools } from "./tools/zha.js";
|
|
12
|
+
import { registerHacsTools } from "./tools/hacs.js";
|
|
13
|
+
import { registerResourceTools } from "./tools/resources.js";
|
|
12
14
|
export function createServer(client, wsClient) {
|
|
13
|
-
const server = new McpServer({ name: "homeassistant-mcp", version: "0.
|
|
15
|
+
const server = new McpServer({ name: "homeassistant-mcp", version: "0.6.0" });
|
|
14
16
|
registerEntityTools(server, client);
|
|
15
17
|
registerServiceTools(server, client);
|
|
16
18
|
registerSystemTools(server, client);
|
|
@@ -21,5 +23,7 @@ export function createServer(client, wsClient) {
|
|
|
21
23
|
registerEnergyTools(server, wsClient, client);
|
|
22
24
|
registerTraceTools(server, wsClient, client);
|
|
23
25
|
registerZhaTools(server, wsClient);
|
|
26
|
+
registerHacsTools(server, wsClient);
|
|
27
|
+
registerResourceTools(server, wsClient);
|
|
24
28
|
return server;
|
|
25
29
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ok, err } from "../hass/format.js";
|
|
3
|
+
function summarize(r) {
|
|
4
|
+
return {
|
|
5
|
+
id: r.id,
|
|
6
|
+
name: r.name,
|
|
7
|
+
full_name: r.full_name,
|
|
8
|
+
category: r.category,
|
|
9
|
+
installed_version: r.installed_version,
|
|
10
|
+
available_version: r.available_version,
|
|
11
|
+
pending_upgrade: r.pending_upgrade,
|
|
12
|
+
local_path: r.local_path,
|
|
13
|
+
file_name: r.file_name,
|
|
14
|
+
domain: r.domain,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function registerHacsTools(server, ws) {
|
|
18
|
+
server.tool("list_hacs_repositories", "List HACS repositories (hacs/repositories/list) — the frontend plugins, integrations and themes HACS manages. Defaults to installed-only, trimmed to the useful fields (id, name, full_name, category, versions, local_path, file_name, domain). Use the returned id with remove_hacs_repository. For a frontend plugin, local_path/file_name tell you which /hacsfiles/… Lovelace resource points at it (see list_lovelace_resources), so you can delete the dangling resource after removing the plugin.", {
|
|
19
|
+
installed_only: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Only repositories that are installed. Default true; set false to include the whole known store (large)."),
|
|
23
|
+
category: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Filter by HACS category, e.g. 'integration', 'plugin', 'theme'. Omit for all categories."),
|
|
27
|
+
}, async ({ installed_only, category }) => {
|
|
28
|
+
try {
|
|
29
|
+
const payload = category ? { categories: [category] } : {};
|
|
30
|
+
const repos = (await ws.command("hacs/repositories/list", payload));
|
|
31
|
+
const filtered = installed_only === false ? repos : repos.filter((r) => r.installed);
|
|
32
|
+
const repositories = filtered.map(summarize);
|
|
33
|
+
return ok({ count: repositories.length, repositories });
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
return err(e);
|
|
37
|
+
}
|
|
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. This does NOT remove the plugin's Lovelace resource entry; after removing a frontend plugin, also delete its /hacsfiles/… resource with delete_lovelace_resource. 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
|
+
repository_id: z
|
|
41
|
+
.string()
|
|
42
|
+
.describe("The repository id from list_hacs_repositories (a numeric string, e.g. '172733314')."),
|
|
43
|
+
}, async ({ repository_id }) => {
|
|
44
|
+
try {
|
|
45
|
+
await ws.command("hacs/repository/remove", { repository: repository_id });
|
|
46
|
+
return ok({ repository_id, removed: true });
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return err(e);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ok, err } from "../hass/format.js";
|
|
3
|
+
const resType = z
|
|
4
|
+
.enum(["module", "css", "js", "html"])
|
|
5
|
+
.describe("Resource type. Lovelace cards use 'module'; 'css' for stylesheets. Rarely 'js'/'html'.");
|
|
6
|
+
export function registerResourceTools(server, ws) {
|
|
7
|
+
server.tool("list_lovelace_resources", "List registered Lovelace (dashboard) resources (lovelace/resources) — the JS/CSS URLs, e.g. /hacsfiles/… bundles, loaded into every dashboard. Each item has an id (use it with update/delete), a type (module/css/js/html) and a url. Only works when the resource registry is in storage mode (lovelace: mode: storage); it is disabled if Lovelace is globally in YAML mode.", {}, async () => {
|
|
8
|
+
try {
|
|
9
|
+
return ok(await ws.command("lovelace/resources"));
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
return err(e);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
server.tool("create_lovelace_resource", "Register a new Lovelace resource (lovelace/resources/create) so a card bundle/stylesheet loads on every dashboard. Config authoring (like adding it under Settings → Dashboards → Resources); requires an admin token and storage-mode resources. Returns the created resource incl. its new id.", {
|
|
16
|
+
url: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Resource URL, e.g. '/hacsfiles/my-card/my-card.js' or '/local/aurora-cards.js'."),
|
|
19
|
+
res_type: resType.optional().describe("Defaults to 'module' (the type Lovelace cards use)."),
|
|
20
|
+
}, async ({ url, res_type }) => {
|
|
21
|
+
try {
|
|
22
|
+
return ok(await ws.command("lovelace/resources/create", { url, res_type: res_type ?? "module" }));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return err(e);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
server.tool("update_lovelace_resource", "Update an existing Lovelace resource (lovelace/resources/update) — change its url and/or type. Config authoring; requires an admin token and storage-mode resources. Get the resource_id from list_lovelace_resources.", {
|
|
29
|
+
resource_id: z.string().describe("The resource id from list_lovelace_resources."),
|
|
30
|
+
url: z.string().optional().describe("New URL. Omit to keep the current one."),
|
|
31
|
+
res_type: resType.optional().describe("New type. Omit to keep the current one."),
|
|
32
|
+
}, async ({ resource_id, url, res_type }) => {
|
|
33
|
+
try {
|
|
34
|
+
const payload = { resource_id };
|
|
35
|
+
if (url !== undefined)
|
|
36
|
+
payload.url = url;
|
|
37
|
+
if (res_type !== undefined)
|
|
38
|
+
payload.res_type = res_type;
|
|
39
|
+
return ok(await ws.command("lovelace/resources/update", payload));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return err(e);
|
|
43
|
+
}
|
|
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 to clear the dangling /hacsfiles/… resource left behind after remove_hacs_repository. Get the resource_id from list_lovelace_resources.", {
|
|
46
|
+
resource_id: z.string().describe("The resource id from list_lovelace_resources."),
|
|
47
|
+
}, async ({ resource_id }) => {
|
|
48
|
+
try {
|
|
49
|
+
await ws.command("lovelace/resources/delete", { resource_id });
|
|
50
|
+
return ok({ resource_id, deleted: true });
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return err(e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dist/tools/zha.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ok, err } from "../hass/format.js";
|
|
3
|
+
// An entity_id can be listed either on the groupable endpoint itself or, on some
|
|
4
|
+
// HA versions where the endpoint list comes back empty/null, only under the
|
|
5
|
+
// device. Check both.
|
|
6
|
+
function endpointHasEntity(ep, entityId) {
|
|
7
|
+
const onEndpoint = ep.entities?.some((e) => e?.entity_id === entityId);
|
|
8
|
+
const onDevice = ep.device?.entities?.some((e) => e?.entity_id === entityId);
|
|
9
|
+
return Boolean(onEndpoint || onDevice);
|
|
10
|
+
}
|
|
11
|
+
function endpointEntityIds(ep) {
|
|
12
|
+
const refs = [...(ep.entities ?? []), ...(ep.device?.entities ?? [])];
|
|
13
|
+
return [
|
|
14
|
+
...new Set(refs.filter((e) => !!e?.entity_id).map((e) => e.entity_id)),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
3
17
|
const memberSchema = z.union([
|
|
4
18
|
z.string(),
|
|
5
19
|
z.object({
|
|
@@ -16,7 +30,7 @@ async function resolveMembers(ws, members) {
|
|
|
16
30
|
return members.map((m) => {
|
|
17
31
|
if (typeof m !== "string")
|
|
18
32
|
return { ieee: m.ieee, endpoint_id: m.endpoint_id };
|
|
19
|
-
const hit = groupable.find((ep) => ep
|
|
33
|
+
const hit = groupable.find((ep) => endpointHasEntity(ep, m));
|
|
20
34
|
if (!hit) {
|
|
21
35
|
throw new Error(`'${m}' is not a groupable ZHA entity. A ZHA group member must be a ZHA entity on an endpoint that supports the Zigbee Groups cluster (see list_zha_groupable). Non-ZHA entities (Wi-Fi/Shelly/Hue-bridge/etc.) cannot join a ZHA group.`);
|
|
22
36
|
}
|
|
@@ -39,7 +53,7 @@ export function registerZhaTools(server, ws) {
|
|
|
39
53
|
ieee: ep.device?.ieee ?? null,
|
|
40
54
|
endpoint_id: ep.endpoint_id,
|
|
41
55
|
device_name: ep.device?.user_given_name ?? ep.device?.name ?? null,
|
|
42
|
-
entities: (ep
|
|
56
|
+
entities: endpointEntityIds(ep),
|
|
43
57
|
}));
|
|
44
58
|
return ok({ count: endpoints.length, endpoints });
|
|
45
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orellbuehler/homeassistant-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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": {
|