210 lines
7.4 KiB
Markdown
210 lines
7.4 KiB
Markdown
# homey-mcp
|
|
|
|
An MCP (Model Context Protocol) server that exposes a local **Homey Pro** instance to LLM clients (Claude Desktop, VS Code / GitHub Copilot Chat, MCP Inspector, …).
|
|
|
|
It talks to Homey's local HTTP Web API (`/api/manager/...`) using a Personal Access Token, and optionally subscribes to Socket.IO realtime events.
|
|
|
|
## Features
|
|
|
|
Tools (read-only ones carry `readOnlyHint`; zone/home-wide and flow tools are marked `destructiveHint`):
|
|
|
|
- Connectivity: `ping`
|
|
- Devices: `list_devices(zone_id?)`, `find_devices(query, zone?)`, `get_device(device_id)`
|
|
- Zones: `list_zones`
|
|
- Status views: `get_zone_status(zone)`, `get_house_status(class_filter?)`, `get_temperatures`
|
|
- Capabilities: `get_capability`, `set_capability`, `set_light`, `set_capabilities_bulk`
|
|
- Zone / home control: `control_zone(zone, capability, value)`, `control_all(capability, value, device_class?)`
|
|
- Flows: `list_flows`, `find_flows(query)`, `trigger_flow(flow_id)`
|
|
- Logic variables: `list_variables`, `set_variable`
|
|
- Notifications: `send_notification(title, excerpt?)`
|
|
- Events: `list_recent_events(limit, kind?)` — realtime events from the in-memory buffer
|
|
|
|
Zone names resolve exact match → prefix → substring, so `Wohnzimmer` never picks
|
|
`Wohnzimmer Ecke`. IDs are URL-encoded before they reach Homey's API.
|
|
|
|
Resources:
|
|
|
|
- `homey://devices`
|
|
- `homey://zones`
|
|
- `homey://events/recent`
|
|
|
|
Transport: **stdio** (default, for local LLM hosts like Javis) or **streamable-http / sse** (for remote MCP clients over the LAN, e.g. the Odysseus AI helper) — see [Run on the LAN](#run-on-the-lan-remote-mcp-clients) below.
|
|
|
|
## Prerequisites
|
|
|
|
- Python **3.10+**
|
|
- A Homey Pro on the local network
|
|
- A **Personal Access Token** for the Homey Web API — create one at the Homey Developer Tools: <https://tools.developer.homey.app/> (My Homey → API Keys / Personal Access Token).
|
|
|
|
## Install
|
|
|
|
From this folder:
|
|
|
|
```powershell
|
|
python -m venv .venv
|
|
.\.venv\Scripts\Activate.ps1
|
|
pip install -e .
|
|
```
|
|
|
|
`uv` works equivalently:
|
|
|
|
```powershell
|
|
uv venv
|
|
uv pip install -e .
|
|
```
|
|
|
|
## Configure
|
|
|
|
Copy `.env.example` to `.env` and fill in:
|
|
|
|
```ini
|
|
HOMEY_HOST=192.168.1.10
|
|
HOMEY_TOKEN=eyJhbGciOi...your PAT...
|
|
HOMEY_SCHEME=http
|
|
HOMEY_PORT=4859
|
|
HOMEY_TIMEOUT=10
|
|
HOMEY_EVENT_BUFFER=200
|
|
HOMEY_REALTIME=true
|
|
```
|
|
|
|
## Run
|
|
|
|
```powershell
|
|
homey-mcp
|
|
# or
|
|
python -m homey_mcp
|
|
# or
|
|
.\run.bat
|
|
```
|
|
|
|
### Inspect with the MCP Inspector
|
|
|
|
```powershell
|
|
uv run mcp dev src/homey_mcp/server.py
|
|
# or
|
|
npx -y @modelcontextprotocol/inspector homey-mcp
|
|
```
|
|
|
|
### Run on the LAN (remote MCP clients)
|
|
|
|
To let a remote MCP client on the same network (e.g. **Odysseus**) connect over
|
|
HTTP instead of stdio, set the transport env vars and run `run-lan.bat`
|
|
(or set them yourself and run `homey-mcp` / `python -m homey_mcp`):
|
|
|
|
```ini
|
|
HOMEY_MCP_TRANSPORT=streamable-http # or: sse
|
|
HOMEY_MCP_HOST=0.0.0.0 # bind address (or specific LAN IP)
|
|
HOMEY_MCP_ADVERTISED_HOST=10.10.10.50 # advertised host for MCP validation (required for 0.0.0.0 bind)
|
|
HOMEY_MCP_PORT=8080
|
|
HOMEY_MCP_AUTH_MODE=none # compatible trusted-LAN default
|
|
HOMEY_MCP_AUTH_TOKEN=<a long random secret>
|
|
```
|
|
|
|
```powershell
|
|
.\run-lan.bat
|
|
```
|
|
|
|
The remote client connects to `http://<this-machine-ip>:8080/mcp`
|
|
(streamable-http) or `/sse`. Streamable HTTP clients that only accept a base
|
|
URL may use `http://<this-machine-ip>:8080/`; MCP traffic at `/` is forwarded
|
|
to `/mcp`, while an ordinary browser GET returns service discovery.
|
|
|
|
**Important for MCP transport over LAN:** requests are only accepted when their
|
|
`Host` header is `HOMEY_MCP_ADVERTISED_HOST`, `localhost` or `127.0.0.1`
|
|
(otherwise `421 Misdirected Request`). This blocks DNS-rebinding attacks, where a
|
|
web page in any browser on your LAN rebinds its domain to this server. If you
|
|
bind to `0.0.0.0`, set `HOMEY_MCP_ADVERTISED_HOST` to the IP clients use; add
|
|
further names (e.g. a DNS name) to `HOMEY_MCP_ALLOWED_HOSTS` (comma-separated,
|
|
`*` disables the check). Without either, the check is off and a warning is logged.
|
|
|
|
`HOMEY_MCP_AUTH_MODE=none` is the compatible mode for OAuth-negotiating MCP
|
|
clients such as Odysseus, because this server is not an OAuth authorization
|
|
server. Use it only on a trusted, firewall-restricted LAN. Set the mode to
|
|
`bearer` only when the MCP client can be configured to send
|
|
`Authorization: Bearer <HOMEY_MCP_AUTH_TOKEN>` directly; OAuth discovery and
|
|
dynamic registration are not provided. Generate a token with
|
|
`python -c "import secrets; print(secrets.token_urlsafe(32))"`.
|
|
|
|
When `HOMEY_MCP_AUTH_TOKEN` is set, the OpenAPI REST routes always require it,
|
|
regardless of the MCP auth mode. In `bearer` mode every MCP route requires it
|
|
too (`/mcp`, the root alias, and `/sse` + `/messages/` for the SSE transport);
|
|
only `GET /` (discovery) and `/openapi.json` stay public.
|
|
|
|
This is independent of Javis's own connection: Javis keeps talking to
|
|
homey-mcp over stdio via `mcp_servers.json` as before — run the LAN instance
|
|
as a second, separate process for other clients.
|
|
|
|
#### Odysseus API integration
|
|
|
|
If Odysseus exposes integrations through its `app_api` tool rather than a
|
|
native MCP client, import this server's OpenAPI document:
|
|
|
|
- Schema URL: `http://<this-machine-ip>:8080/openapi.json`
|
|
- Authentication: Bearer token
|
|
- Token: the value of `HOMEY_MCP_AUTH_TOKEN`
|
|
|
|
The API provides reads at `/api/system`, `/api/zones`, and `/api/devices`,
|
|
plus writes at `/api/devices/{device_id}/capabilities/{capability}` and
|
|
`/api/zones/control`.
|
|
|
|
Odysseus must invoke its registered `app_api` tool as a structured tool call.
|
|
It must not print or execute Python such as `api_call(...)`; `api_call` is not
|
|
a Python function and is not the registered tool name. Use this integration
|
|
instruction in Odysseus:
|
|
|
|
```text
|
|
For Homey requests, always call the app_api tool with integration="Homey".
|
|
Never write api_call(...) or place tool calls in Python/code blocks.
|
|
Use GET /api/system to check connectivity, GET /api/zones and GET /api/devices
|
|
to discover IDs, PUT /api/devices/{device_id}/capabilities/{capability} with
|
|
{"value": ...} for one device, and POST /api/zones/control with
|
|
{"zone": "...", "capability": "onoff", "value": true|false} for a room.
|
|
After the tool returns, summarize the actual result. Never claim success before
|
|
receiving a successful tool response.
|
|
```
|
|
|
|
### Use from Claude Desktop / VS Code
|
|
|
|
Add to your MCP client config (path varies per client):
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"homey": {
|
|
"command": "homey-mcp",
|
|
"env": {
|
|
"HOMEY_HOST": "192.168.1.10",
|
|
"HOMEY_TOKEN": "your-personal-access-token"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
If `homey-mcp` is not on PATH, use the full venv path, e.g. `C:\\local\\Code\\PRIVAT\\homey-mcp\\.venv\\Scripts\\homey-mcp.exe`.
|
|
|
|
## Notes & limitations
|
|
|
|
- Local API only (no cloud OAuth).
|
|
- LAN MCP transport authentication is controlled by `HOMEY_MCP_AUTH_MODE`.
|
|
Never expose `none` mode beyond a trusted, firewall-restricted LAN.
|
|
- Realtime events are best-effort — if the Socket.IO connection fails the REST tools still work. Set `HOMEY_REALTIME=false` to disable. The server performs Homey's `handshakeClient` and subscribes to `homey:manager:devices` plus each `homey:device:<id>` (capability changes).
|
|
- No admin operations (zone/user/app management) are exposed.
|
|
- Capability values are typed as `bool | int | float | str`; the model picks based on the capability (`onoff` → bool, `dim` → 0..1 float, etc.).
|
|
|
|
## Project layout
|
|
|
|
```
|
|
homey-mcp/
|
|
pyproject.toml
|
|
README.md
|
|
.env.example
|
|
run.bat
|
|
src/homey_mcp/
|
|
__init__.py
|
|
__main__.py
|
|
config.py
|
|
homey_client.py
|
|
server.py
|
|
```
|