{
  "markdown": "# zigbee2mqtt-mcp\n\n[![CI](https://github.com/alexpfau/zigbee2mqtt-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/alexpfau/zigbee2mqtt-mcp/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/zigbee2mqtt-mcp.svg)](https://www.npmjs.com/package/zigbee2mqtt-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![zigbee2mqtt-mcp MCP server](https://glama.ai/mcp/servers/alexpfau/zigbee2mqtt-mcp/badges/score.svg)](https://glama.ai/mcp/servers/alexpfau/zigbee2mqtt-mcp)\n\nA [Model Context Protocol](https://modelcontextprotocol.io) server for **administering** a Zigbee2MQTT estate.\n\nMost Zigbee integrations already let an assistant turn a light on. This one is for the layer underneath: mesh health, weak links, devices that keep rejoining, stale batteries, firmware updates, pairing, binding, reporting intervals and device options — the things you normally open the Zigbee2MQTT frontend for.\n\nIt talks directly to the Zigbee2MQTT [MQTT bridge API](https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html), so it works regardless of whether you use Home Assistant, Node-RED, openHAB or nothing at all. Tested against Zigbee2MQTT 2.x; several tools depend on bridge endpoints that only exist there, including `bridge/health`, `device/binds/clear` and the OTA scheduling topics.\n\n## Why this exists\n\nIf you already run Home Assistant, your Zigbee devices are exposed there and an assistant can control them. What Home Assistant does *not* expose is the bridge itself: link quality, mesh topology, interview state, OTA availability, `permit_join`, device options, bindings and reporting configuration. This server fills exactly that gap.\n\n## Design\n\n- **No database.** Zigbee2MQTT publishes its bridge topics as retained messages, so a fresh subscription yields a complete picture in a few hundred milliseconds. The server keeps only the latest payload per topic in memory.\n- **No daemon.** Pure stdio. It starts and dies with the MCP session.\n- **Correlated requests.** Every `bridge/request/*` carries a `transaction` id and is matched to its `bridge/response/*`, so concurrent calls cannot cross wires.\n- **Tiered writes.** Destructive operations are gated behind a write mode *and* an explicit `confirm` argument.\n\n## Install\n\nRequires Node.js 22 or newer and network access to the MQTT broker that Zigbee2MQTT uses.\n\n```bash\nnpx zigbee2mqtt-mcp\n```\n\n### VS Code / GitHub Copilot\n\nAdd to your MCP configuration:\n\n```jsonc\n{\n  \"servers\": {\n    \"zigbee2mqtt\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"zigbee2mqtt-mcp\"],\n      \"env\": {\n        \"Z2M_MQTT_URL\": \"mqtt://192.168.1.10:1883\",\n        \"Z2M_MQTT_USERNAME\": \"mqtt\",\n        \"Z2M_MQTT_PASSWORD\": \"${input:z2mPassword}\"\n      }\n    }\n  }\n}\n```\n\n### Claude Desktop\n\n```json\n{\n  \"mcpServers\": {\n    \"zigbee2mqtt\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"zigbee2mqtt-mcp\"],\n      \"env\": {\n        \"Z2M_MQTT_URL\": \"mqtt://192.168.1.10:1883\"\n      }\n    }\n  }\n}\n```\n\n> Point `Z2M_MQTT_URL` at your **MQTT broker**, not at the Zigbee2MQTT frontend port. If Zigbee2MQTT's `configuration.yaml` says `mqtt.server: mqtt://192.168.1.10:1883`, use that value verbatim.\n\n> Behind a corporate npm proxy? If `npm config get registry` is not `https://registry.npmjs.org/`, your proxy's upstream feed may not carry this package. Add an explicit override to the args rather than changing your global registry:\n> `\"args\": [\"-y\", \"--registry\", \"https://registry.npmjs.org/\", \"zigbee2mqtt-mcp\"]`\n\n## Configuration\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `Z2M_MQTT_URL` | *required* | Broker URL. `mqtt://`, `mqtts://`, `ws://`, `wss://` |\n| `Z2M_MQTT_USERNAME` / `Z2M_MQTT_PASSWORD` | – | Broker credentials |\n| `Z2M_BASE_TOPIC` | `zigbee2mqtt` | Must match `mqtt.base_topic` |\n| `Z2M_MQTT_CLIENT_ID` | `zigbee2mqtt-mcp-<pid>` | Override if your broker requires a fixed client ID |\n| `Z2M_WRITE_MODE` | `safe` | `off`, `safe` or `full` |\n| `Z2M_LOG_LEVEL` | `error` | Diagnostics on stderr |\n| `Z2M_WEAK_LINK_THRESHOLD` | `30` | Link quality below this is flagged |\n| `Z2M_LOW_BATTERY_THRESHOLD` | `20` | Battery percentage below this is flagged |\n| `Z2M_STALE_HOURS` | `24` | Hours of silence before a device is stale |\n| `Z2M_CONNECT_TIMEOUT_MS` | `10000` | Broker connect timeout |\n| `Z2M_REQUEST_TIMEOUT_MS` | `15000` | Default bridge request timeout |\n| `Z2M_MQTT_REJECT_UNAUTHORIZED` | `true` | Set `false` for self-signed TLS |\n| `Z2M_MQTT_CA` / `_CERT` / `_KEY` | – | Paths to TLS material |\n\n## Write modes\n\nTools above the active tier are not registered at all, so a model cannot reach for them.\n\n| Mode | Exposes |\n| --- | --- |\n| `off` | Read-only tools |\n| `safe` | **Default.** Read plus non-destructive writes: pairing, options, rename, configure, interview, binding, reporting, groups, state |\n| `full` | Everything, including device removal, OTA flashing, Touchlink and bridge restart |\n\nIrreversible tools (`z2m_remove_device`, `z2m_ota_update`, `z2m_touchlink`, `z2m_restart_bridge`, `z2m_set_bridge_options`) additionally require `confirm: true` on every call. Three `safe` tools reduce state rather than only adding to it, so they are annotated destructive: `z2m_rename_device`, `z2m_manage_group` and `z2m_bind`. Their reducing actions also require `confirm: true` — `z2m_manage_group` (`remove`, `remove_all_members`) and `z2m_bind` (`clear`). `z2m_rename_device` is annotated destructive because it breaks anything referencing the old name, but stays unconfirmed since renaming back restores it.\n\nEvery tool also advertises MCP [tool annotations](https://modelcontextprotocol.io/specification/server/tools) so a client can decide what may run without prompting:\n\n| Annotation | Meaning here |\n| --- | --- |\n| `readOnlyHint: true` | The nine read tools. They never change the network. |\n| `destructiveHint: true` | `z2m_remove_device`, `z2m_ota_update`, `z2m_touchlink`, `z2m_restart_bridge`, `z2m_set_bridge_options`, plus `z2m_rename_device`, `z2m_manage_group` and `z2m_bind` |\n| `idempotentHint` | True where repeating the call has no additional effect |\n| `openWorldHint: true` | Always — every tool reaches a live Zigbee network |\n\nAnnotations describe the tool, not the tier: a `safe` tool can still be destructive. The tier decides what is registered, the annotation tells the client what to ask about.\n\nOpt in to the destructive tier only when you want it:\n\n```jsonc\n\"env\": { \"Z2M_WRITE_MODE\": \"full\" }\n```\n\n## Tools\n\n### Read\n\n| Tool | Purpose |\n| --- | --- |\n| `z2m_connection_status` | Broker reachability, TLS posture and cached message counts — reports instead of throwing, even when nothing is configured yet |\n| `z2m_bridge_info` | Version, coordinator, channel, PAN ID, permit_join, restart_required, runtime stats |\n| `z2m_list_devices` | Filter and sort devices by type, availability, link quality, battery, pending update |\n| `z2m_get_device` | Exposes, settable options, endpoints, bindings, configured reportings, current state |\n| `z2m_health_report` | Whole-estate audit in one call |\n| `z2m_network_map` | Mesh topology with parent, depth, link quality, orphan detection |\n| `z2m_list_groups` | Groups, members and scenes |\n| `z2m_get_logs` | Bridge logs and lifecycle events, buffered or watched live |\n| `z2m_coordinator_check` | Routers missing from the coordinator's memory (Texas Instruments adapters only) |\n\n### Safe writes\n\n`z2m_check_updates`, `z2m_permit_join`, `z2m_set_device_options`, `z2m_rename_device`, `z2m_configure_device`, `z2m_interview_device`, `z2m_set_state`, `z2m_manage_group`, `z2m_bind`, `z2m_configure_reporting`\n\n### Full writes\n\n`z2m_remove_device`, `z2m_ota_update`, `z2m_restart_bridge`, `z2m_set_bridge_options`, `z2m_touchlink`\n\n## Data availability caveats\n\nSome fields depend on your Zigbee2MQTT configuration. The server detects what is available and tells you rather than silently returning nothing.\n\n| Field | Requires | If missing |\n| --- | --- | --- |\n| `last_seen` | `advanced.last_seen` set to e.g. `ISO_8601` (default is `disable`) | Staleness checks are skipped and a hint is returned |\n| `availability` | `availability.enabled: true` | Offline detection is skipped and a hint is returned |\n| `linkquality`, `battery`, `update` | Live device traffic — Zigbee2MQTT does not retain device state topics | Pass `collect_seconds` to listen briefly, or use `z2m_network_map` for authoritative link quality |\n| `z2m_coordinator_check` | An adapter that supports coordinator backups (TI zStack, Silicon Labs EmberZNet; not deCONZ or ZiGate) | Returns an error on unsupported adapters; `z2m_bridge_info` reports the expectation |\n\n## Security notes\n\n- The broker URL may carry credentials (`mqtt://user:pass@host`). They are redacted everywhere the URL is echoed back to a model or written to a log, and error text from the MQTT client is scrubbed before it leaves the process. Zigbee2MQTT logs its own broker URL at startup, so lines relayed by `z2m_get_logs` are scrubbed too.\n- `safe` mode can actuate anything on the mesh via `z2m_set_state`, including locks, valves and sirens. Use `off` if that matters.\n- `z2m_permit_join` opens the network to any nearby Zigbee device for the duration of the window. It is in `safe` because it is routine and reversible, but it is a security boundary.\n\n## Example prompts\n\n- \"Is my Zigbee network healthy?\"\n- \"Which devices have the weakest signal?\"\n- \"Which batteries need replacing?\"\n- \"Any firmware updates available?\"\n- \"Open the network for pairing via the kitchen router for two minutes.\"\n- \"This sensor stopped reporting temperature — fix it.\"\n- \"Bind the hallway remote to the hallway light so it works if the bridge is down.\"\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm run watch\n\n# Unit tests. No broker required; they run on every push across Node 22 and 24.\nnpm test\n\n# Manual smoke test against a real instance\nZ2M_MQTT_URL=mqtt://192.168.1.10:1883 node scripts/smoke.mjs\nZ2M_MQTT_URL=mqtt://192.168.1.10:1883 node scripts/smoke.mjs z2m_list_devices '{\"only_problems\":true}'\n\n# Sequential read-after-write test. Renames a device and creates a group, then\n# restores both, including on failure. Set Z2M_TEST_DEVICE to pick the device,\n# or ROUNDTRIP_GROUPS_ONLY=1 to skip the rename.\nZ2M_MQTT_URL=mqtt://192.168.1.10:1883 node scripts/roundtrip.mjs\n```\n\nNote that an MCP server may receive requests concurrently. When testing ordering,\nawait each response before sending the next, as `roundtrip.mjs` does — piping\nseveral requests at once will produce misleading results.\n\nThe unit tests cover health classification, capability detection, topic routing,\nwrite-mode gating and configuration parsing. They are broker-free by design, so\nanything that needs a live mesh belongs in `scripts/` instead.\n\nA passing suite only proves the tests ran, so `scripts/mutate.mjs` breaks the\nsource one change at a time and checks the suite notices each one:\n\n```bash\nnode scripts/mutate.mjs          # every mutant\nnode scripts/mutate.mjs redact   # only mutants whose name matches\n```\n\nA surviving mutant marks behaviour nothing actually tests, and exits non-zero.\nAdd a mutant alongside any fix worth keeping fixed.\n\nReleases are tag-driven: `npm version <patch|minor|major>` then\n`git push --follow-tags`. CI publishes to npm with provenance, rewrites\n`server.json`'s version from the tag and publishes to the MCP Registry, then\ncreates the GitHub release. The `version` committed in `server.json` is therefore\nnot authoritative — the tag is.\n\n## Safety\n\nThis server can remove devices from your network and flash firmware. Both are irreversible and OTA failures can brick hardware. Those tools live in the `full` tier, which is **not** enabled by default — you must opt in with `Z2M_WRITE_MODE=full`. Broker credentials are read from the environment and never logged.\n\n## Status\n\nEarly release. Developed and tested against a 50-device EmberZNet estate on Zigbee2MQTT 2.12.x. Other adapters (Texas Instruments, deCONZ/ConBee, zStack), TLS and WebSocket brokers, and large estates are unverified. Bug reports and pull requests are very welcome — please include your adapter type and Zigbee2MQTT version from `z2m_bridge_info`.\n\n## License\n\nMIT\n",
  "bytes": 12221,
  "sha": "dfb2bbb511f3c26cf38dab034e2da70ec179c23df751ec27e7002bd4d266f87d",
  "repo_slug": "alexpfau/zigbee2mqtt-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_alexpfau_zigbee2mqtt_mcp_b4ffd296/readme"
}