ha-mcp
# ha-mcp
A self-hosted [Model Context Protocol](https://modelcontextprotocol.io) server for
[Home Assistant](https://www.home-assistant.io), in TypeScript. It gives an MCP client
(Claude Desktop, Claude Code, Codex CLI, …) the **full** Home Assistant control surface:
read any entity's state, call any service, query history, render templates, and resolve
rooms/areas - over a single local stdio connection.
## Why
Home Assistant ships an official MCP Server integration, but it's deliberately narrow: it
only exposes the **Assist** intent layer over entities you've explicitly *exposed* to voice
assistants - no raw states, no arbitrary service calls, no history, no templates, no area
registry. `ha-mcp` wraps the documented [REST](https://developers.home-assistant.io/docs/api/rest/)
and [WebSocket](https://developers.home-assistant.io/docs/api/websocket/) APIs directly, so
the model can work with everything in your instance.
## Tools
| Tool | What it does |
|------|--------------|
| `ha_get_states` | List entity states; optional `domain` and/or `area` filter |
| `ha_get_entity` | Full state + attributes for one `entity_id` |
| `ha_call_service` | Call any service (`light.turn_on`, `climate.set_temperature`, `automation.trigger`, `scene.turn_on`, …) |
| `ha_get_history` | State-change history for an entity over the last N hours |
| `ha_render_template` | Render a Jinja2 template against live state |
| `ha_get_config` | HA version, location, unit system, components |
| `ha_get_error_log` | The error log, for troubleshooting |
| `ha_list_areas` | All areas/rooms |
| `ha_get_entities_by_area` | Entities in a room, resolving device→area inheritance |
> Listing automations is just `ha_get_states` filtered to `automation.*`; triggering one is
> `ha_call_service('automation','trigger')`. Scenes and scripts work the same way - no
> separate tools needed.
## Requirements
- Node.js ≥ 20
- A Home Assistant instance and a **long-lived access token**
(HA → your profile → *Security* → *Long-lived access tokens* → *Create token*).
## Install
```bash
git clone https://github.com/qubit999/ha-mcp.git
cd ha-mcp
npm install
npm run build
```
This produces `dist/index.js` - the executable the clients launch.
## Configuration
The server reads two environment variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `HASS_URL` | `http://localhost:8123` | Base URL of your Home Assistant instance |
| `HASS_TOKEN` | _(required)_ | A long-lived access token |
The same token authenticates both the REST calls and the WebSocket (used for the area
registry). For local development you can drop these in a `.env` file and run `npm run dev`.
## Connect a client
All three clients launch the server locally over stdio. Use the **absolute path** to
`dist/index.js`.
### Claude Desktop - one-click (recommended)
Download the latest `ha-mcp.mcpb` from the
[Releases page](https://github.com/qubit999/ha-mcp/releases) (no build step needed), or
build it yourself with `npm run bundle`.
Then in Claude Desktop: **Settings → Extensions → Install Extension…** (or just drag
`ha-mcp.mcpb` into the window). Claude shows a form for **Home Assistant URL** and
**token** (the token is masked and stored in your OS keychain), then launches the server
for you. Restarting the app or re-pasting config is not needed.
### Claude Desktop - manual (alternative)
If you'd rather not bundle, edit the config directly - `Settings → Developer → Edit Config`
opens the file (path: `~/Library/Application Support/Claude/claude_desktop_config.json` -
note the space in "Application Support", so quote it in a shell). Add the block below and
fully quit + relaunch the app:
```json
{
"mcpServers": {
"ha-mcp": {
"command": "node",
"args": ["/Users/alex/ha-mcp/dist/index.js"],
"env": {
"HASS_URL": "http://localhost:8123",
"HASS_TOKEN": "your-long-lived-token"
}
}
}
}
```
### Claude Code
```bash
claude mcp add ha-mcp \
--scope user \
--env HASS_URL=http://localhost:8123 \
--env HASS_TOKEN=your-long-lived-token \
-- node /Users/alex/ha-mcp/dist/index.js
```
Verify with `claude mcp list` / `claude mcp get ha-mcp`.
### Codex CLI
Add to `~/.codex/config.toml`:
```toml
[mcp_servers.ha-mcp]
command = "node"
args = ["/Users/alex/ha-mcp/dist/index.js"]
env = { HASS_URL = "http://localhost:8123", HASS_TOKEN = "your-long-lived-token" }
```
Then ask: *"Turn on the kitchen light"* or *"What's the temperature in the living room?"*
## Security
A long-lived token grants **full control of your Home Assistant** (and thus your home). This
server is designed to run **locally over stdio** - the client launches it as a subprocess; no
network port is opened and the token never leaves your machine. Keep the token out of version
control (the included `.gitignore` excludes `.env`). If you scope it into a shared
`.mcp.json`, use a non-admin HA user or environment variables instead. There is intentionally
**no** remote/HTTP transport here.
## Development
```bash
npm run dev # run from source with tsx (+ .env)
npm run build # type-check and emit dist/
```
Tools are thin wrappers over `src/ha.ts` (REST helpers + a short-lived WebSocket client for
the registries). The WebSocket connection is opened per registry fetch and cached briefly -
there's no long-lived socket to manage.
## License
MIT © qubit999
TDQS
Scored across 9 tools
Each tool has a clearly distinct purpose: service calls, configuration retrieval, entity queries by area or ID, error logs, history, state listing, area listing, and template rendering. No overlap in functionality.
All tools follow a consistent 'ha_{verb}_{noun}' pattern with underscores, using verbs like 'call', 'get', 'list', and 'render'. The prefix 'ha_' identifies the server, and naming is predictable.
With 9 tools covering essential Home Assistant operations (service calls, state queries, configuration, history, error logs, areas, and template rendering), the count is well-scoped for an MCP server without being excessive.
The tool set covers core Home Assistant interactions: calling any service, retrieving states, config, history, error logs, and area information. The inclusion of template rendering adds flexibility. No obvious gaps for common tasks.