# 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: (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= ``` ```powershell .\run-lan.bat ``` The remote client connects to `http://:8080/mcp` (streamable-http) or `/sse`. Streamable HTTP clients that only accept a base URL may use `http://: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 ` 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://: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:` (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 ```