# MCP-HA-Connect A production-ready Model Context Protocol (MCP) server for Home Assistant integration with AI assistants like Claude and LM Studio. [![npm version](https://img.shields.io/npm/v/%40coffeerunhobby%2Fmcp-ha-connect)](https://www.npmjs.com/package/@coffeerunhobby/mcp-ha-connect) [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue)](https://github.com/coffeerunhobby/mcp-ha-connect/pkgs/container/mcp-ha-connect) [![Discord](https://img.shields.io/badge/Discord-join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/zeQ6aWVFgS) See [docs/QUICK_START.md](docs/QUICK_START.md) for Docker, HTTP server mode, n8n integration, and more installation options. ## Key Features ### Core Functionality - Smart Device Control - Lights: Brightness, color temperature, RGB color, transitions - Climate: Temperature, HVAC modes, fan modes, humidity - Covers: Position and tilt control - Switches: On/off control - Sensors & Contacts: State monitoring - Media Players: Playback control, volume, source selection - Fans: Speed, oscillation, direction - Locks: Lock/unlock control - Vacuums: Start, stop, return to base - Cameras: Motion detection, snapshots ### Automation Management - List all automations with status - Trigger automations manually with variables - Enable/disable/toggle automations - Create new automations via API - Delete automations - View automation execution traces - Reload automations from configuration ### System Features - Direct Home Assistant API integration - Long-lived access token authentication - Entity state management - Service call support - Advanced entity search - AI-powered sensor analysis with Ollama - Multiple transport modes (stdio, Streamable HTTP) - HTTP server with health checks - CORS support for browser-based clients - Structured logging (plain, JSON, GCP-JSON formats) - Comprehensive configuration validation with Zod - Full type safety with TypeScript - Stateful and stateless session support - JWT authentication with role-based access control - MCP Server Instructions for improved LLM tool selection ### Network Monitoring & Security (TP-Link Omada) Bring your home network into the same assistant that runs your home. These are the high-value questions an LLM can actually answer about your network: - **Internet & WAN status** — "Is my internet up? What's my WAN IP and uplink speed?" Live per-WAN status, plus multi-WAN / ISP health and load-balancing for the gateway. - **Event & alert logs** — "What happened on my network?" The site event and alert logs, time-windowed (defaults to the last 7 days), to correlate with Home Assistant history. - **Wireless security** — rogue-AP scan results and (on Omada Pro) wireless intrusion detection (WIDS) for genuine network-security awareness. - **Controller & device health** — dashboard overview plus top devices by CPU and memory usage, HA-style monitoring of your network stack. - **Speed tests** — surface the controller's own speed-test results, pairing with AI-powered `analyzeSensors` for anomaly detection. - **Pending devices** — "Is there a new device waiting to be adopted?" - **Firmware awareness** — per-device firmware info and a controller-wide critical-upgrade overview, mirroring the Home Assistant `checkUpdates` philosophy across the network. - **Client management** — list/inspect connected clients, traffic activity, and apply rate-limit profiles, with presence correlation against Home Assistant device trackers. > A discoverable resource-graph mode (`MCP_TOOL_REGISTRATION_MODE=graph`) exposes all of the > above through two tools — `omada_browse` + `omada_read` — keeping the tool-schema budget > small for low-context models. See the Omada tool tables below for the full eager surface. ## Available Tools (69 Total) ### Home Assistant Tools (38) #### State & Entity Tools | Tool | Description | |------|-------------| | `getStates` | Get all entity states (paginated, 50/page default). Use `includeAttributes=true` for full data | | `getState` | Get the state of a specific entity by entity_id | | `getEntitiesByDomain` | Get entities for a domain (paginated, 50/page default). Use `includeAttributes=true` for full data | | `searchEntities` | Search entities by name/entity_id (paginated, 50/page default). Use `includeAttributes=true` for full data | | `getAllSensors` | Get all sensors (paginated, 50/page default). Use `includeAttributes=true` for full data | | `listEntities` | List entities with filtering (domain, state, search, limit). Use `includeAttributes=true` for full data | | `getDomainSummary` | Get summary statistics for a domain | | `getHistory` | Get historical data for an entity | | `listPersons` | List all household members with location state (home/away) | #### Service & Control Tools | Tool | Description | |------|-------------| | `callService` | Call any Home Assistant service | | `entityAction` | Simple turn_on/turn_off/toggle actions | #### Device Control Tools | Tool | Description | |------|-------------| | `controlLight` | Control lights with brightness, color, temperature | | `controlClimate` | Control thermostats with temperature, HVAC mode | | `controlMediaPlayer` | Control media players with playback, volume | | `controlCover` | Control covers/blinds with position, tilt | | `controlFan` | Control fans with speed, oscillation, direction | | `activateScene` | Activate a Home Assistant scene | | `runScript` | Run a Home Assistant script | | `sendNotification` | Send notifications with full mobile app support (actions, priority, images) | | `listNotificationTargets` | Discover available mobile app notification targets | #### Owner Questions (approval on the phone) Ask the owner a question as a phone notification with up to 3 buttons and read back the tap. HTTP mode only; needs an **admin** Home Assistant token (button taps are admin-only events) and `MCP_AUTH_SECRET` so open questions survive a restart. Everything except an explicit approving answer counts as NO. | Tool | Description | |------|-------------| | `askOwner` | Send the question (plain text, max 256 characters; 1-3 buttons, default Approve / Deny; open up to 7 days) and return a `requestId` at once | | `getOwnerAnswer` | Wait up to 50 s per call: `answered` (button index and label, time, HA user), `pending`, `timeout`, `cancelled` or `unknown` | | `cancelOwnerQuestion` | Withdraw an open question; the notification changes to "Cancelled" | Taps are verified with signed button ids and must come from the mobile app (an automation cannot fake one); set `MCP_OWNER_QUESTIONS_HA_USERS` so only your HA user can answer. Only the client that asked can read the answer; at most 3 open questions and 30 per hour per client. Known limit: cancellations and answers are kept in memory. After a restart, someone with a Home Assistant login could re-send an old button id through HA's own API and answer a question that had been cancelled or denied (the phone no longer shows its buttons). That access already allows controlling the house directly. #### Automation Tools | Tool | Description | |------|-------------| | `listAutomations` | List all automations with status | | `triggerAutomation` | Manually trigger an automation | | `enableAutomation` | Enable a disabled automation | | `disableAutomation` | Disable an automation | | `toggleAutomation` | Toggle automation state | | `reloadAutomations` | Reload automations from config | | `createAutomation` | Create a new automation | | `deleteAutomation` | Delete an automation | | `getAutomationTrace` | Recent runs of an automation, newest first: trigger, result, errors (automations with an `id` only) | #### System Tools | Tool | Description | |------|-------------| | `getVersion` | Get Home Assistant version and config info | | `restartHomeAssistant` | Restart the Home Assistant server | | `getSystemLog` | Get system log entries from logbook | | `checkUpdates` | Check for available updates | #### Calendar Tools | Tool | Description | |------|-------------| | `listCalendars` | List all calendar entities | | `getCalendarEvents` | Get events from one or all calendars | ### Omada Network Tools (49) #### Site Tools | Tool | Description | |------|-------------| | `omada_listSites` | List all sites configured on the Omada controller | #### Device Tools | Tool | Description | |------|-------------| | `omada_listDevices` | List all network devices (APs, switches, gateways) in a site | | `omada_getDevice` | Get detailed information about a specific network device | | `omada_searchDevices` | Search for devices globally across all sites | | `omada_getSwitchStackDetail` | Get detailed information about a switch stack | | `omada_listDevicesStats` | Get statistics for global adopted devices with filtering | #### Client Tools | Tool | Description | |------|-------------| | `omada_listClients` | List all connected clients (devices/users) in a site | | `omada_getClient` | Get detailed information about a specific connected client | | `omada_listMostActiveClients` | Get the most active clients sorted by total traffic | | `omada_listClientsActivity` | Get client activity statistics over time | | `omada_listClientsPastConnections` | Get historical client connection data | #### Rate Limit Tools | Tool | Description | |------|-------------| | `omada_getRateLimitProfiles` | Get available rate limit profiles for a site | | `omada_setClientRateLimit` | Set custom bandwidth limits for a client (download/upload in Kbps) | | `omada_setClientRateLimitProfile` | Apply a rate limit profile to a client | | `omada_disableClientRateLimit` | Remove bandwidth limits from a client | #### Client and Device Actions | Tool | Description | |------|-------------| | `omada_blockClient` | Block a client from the network | | `omada_unblockClient` | Unblock a previously blocked client (explains when Omada's API can no longer reach an offline client) | | `omada_listBlockedClients` | List blocked clients, offline ones included | | `omada_deleteClient` | Delete a client's record (name, history, block) to clear a block unblock can't reach (needs `CONFIGURE`) | | `omada_cyclePoePort` | Power-cycle PoE switch ports (remote-reboots APs/cameras) | | `omada_setSsidEnabled` | Turn an SSID (e.g. guest WiFi) on or off by name | #### Client Diagnosis and MAC Groups | Tool | Description | |------|-------------| | `omada_diagnoseClient` | Explain why a device (by MAC) can't get on: block state, MAC groups and the SSID allow/deny lists using them, site MAC filter, IP-MAC binding, DHCP reservation, and for ADMIN callers the audit log (detects an orphaned block). Read-only | | `omada_listMacGroups` | List MAC groups with their entries and which SSIDs use them as allow or deny lists (graph mode: `omada_read /profiles/mac-groups`) | | `omada_setMacGroupEntry` | Add a MAC to a MAC group, or rename its entry (needs `CONFIGURE`) | | `omada_removeMacGroupEntry` | Remove a MAC from a MAC group (needs `CONFIGURE`) | #### Access Control (schedules, groups, gateway ACLs) Lets the controller enforce a schedule itself (e.g. a curfew) instead of a daily block/unblock loop. All writes need `CONFIGURE`. | Tool | Description | |------|-------------| | `omada_listAccessControl` | Time ranges (readable windows), IP groups and gateway ACL rules with names and schedules (graph mode: `omada_read` `/profiles/time-range`, `/profiles/ip-groups`, `/network/acls/gateway`) | | `omada_createTimeRange` / `omada_updateTimeRange` / `omada_deleteTimeRange` | Weekly schedules in quarter hours, e.g. `{days:["mon","tue"], start:"14:30", end:"19:00"}`; `invertWindows` turns allowed hours into the blocked ones. At most 7 windows per profile (a curfew needs a morning and an evening profile). Delete is refused while a rule uses it | | `omada_createGroup` / `omada_deleteGroup` | IP groups (ACL sources/destinations) and MAC groups (SSID allow/deny lists); delete is refused for built-in groups and groups in use | | `omada_setDhcpReservation` / `omada_removeDhcpReservation` | Fixed IP for a device (so an ACL can match it); refuses an address reserved for another device | | `omada_createGatewayAcl` / `omada_updateGatewayAcl` / `omada_deleteGatewayAcl` / `omada_moveGatewayAcl` | Gateway (router) LAN-to-internet rules: allow/deny a source (IP group, LAN network or SSID), optionally only during a time range; covers wired and Wi-Fi | | `omada_setSsidMacFilter` | Turn an SSID's MAC filter on (MAC group as allow or deny list) or off; reports and refuses to disconnect connected clients unless `allowLockout`; `dryRun` previews | #### Security Tools | Tool | Description | |------|-------------| | `omada_getThreatList` | Get security threat management list | #### Network Configuration Tools | Tool | Description | |------|-------------| | `omada_getInternetInfo` | Get internet connection configuration for a site | | `omada_getPortForwardingStatus` | Get port forwarding rules (User or UPnP) | | `omada_getLanNetworkList` | Get LAN network configuration list | | `omada_getLanProfileList` | Get LAN profile configuration list | | `omada_getWlanGroupList` | Get WLAN group configuration list | | `omada_getSsidList` | Get SSID list for a WLAN group | | `omada_getSsidDetail` | Get detailed SSID configuration | | `omada_getFirewallSetting` | Get firewall configuration for a site | | `omada_getSiteNtpStatus` | Get the site's NTP server configuration and status | | `omada_setSiteNtpServers` | Replace the site's NTP server list (needs `CONFIGURE`; `dryRun` previews the change) | ### Vendor-neutral Network Tools (9) The `net_` tools describe networks the same way for every controller vendor: one shared model, with an adapter per vendor (TP-Link Omada today; UniFi and MikroTik are planned). Each adapter says per feature whether it is native, built from several objects, or unsupported (`net_capabilities`), and refuses what it can't do instead of dropping it. Every change is planned first: `dryRun` shows the steps, what they mean and which online devices would be cut off; applying refuses if the controller changed in between, or if devices would be cut off without `allowLockout`. Wi-Fi passwords are never taken or shown: a new or reset Wi-Fi network gets a random temporary password, and the real one is set in the controller's web UI. Reads need `QUERY`, changes `CONFIGURE`. | Tool | Description | |------|-------------| | `net_capabilities` | What the controller can do, per feature | | `net_listNetworks` | Networks (VLANs): VLAN, gateway, DHCP, IPv6, protected | | `net_createNetwork` / `net_updateNetwork` / `net_deleteNetwork` | Create or change a VLAN with its gateway and DHCP range. The network the controller is on is protected; delete is refused while a Wi-Fi network, port profile, reservation or rule uses it | | `net_listWifi` | Wi-Fi networks: network, bands, security, isolation, MAC filter | | `net_createWifi` / `net_updateWifi` / `net_deleteWifi` | Create or change an SSID on a given network (bands, WPA2/WPA3, PMF, isolation, hidden, 802.11r, MAC filter), or reset its password to a random one | On Omada, network isolation, a "guest" network type, switching IPv6 on, and WPA2/WPA3 mixed mode are not available through the API and are refused. Rules arrive in 2.4.6, switch ports and mDNS in 2.4.7. ### AI Tools (1) | Tool | Description | |------|-------------| | `analyzeSensors` | Analyze sensor data using AI (Ollama) to detect issues and provide recommendations | ### MCP Resources | Resource URI | Description | |--------------|-------------| | `hass://entities` | List all entities grouped by domain | | `hass://entities/{entity_id}` | Get state of a specific entity | | `hass://entities/{entity_id}/detailed` | Get detailed entity info with all attributes | | `hass://entities/domain/{domain}` | Get all entities for a domain | | `hass://search/{query}/{limit}` | Search entities with result limit | ## Prerequisites - **Node.js 20+** (tested with v20.19.6) - for npm/npx methods - **npm 10.8+** (tested with v10.8.2) - for npm/npx methods - **Docker** (optional) - for Docker method - Home Assistant instance with long-lived access token - Network access to your Home Assistant instance - (Optional) Ollama for AI-powered analysis ## Getting Your Home Assistant Token 1. Log into your Home Assistant instance 2. Click on your profile (bottom left) 3. Scroll down to "Long-Lived Access Tokens" 4. Click "Create Token" 5. Give it a name (e.g., "MCP Server") 6. Copy the token (shown only once!) ## Environment Variables ### Required | Variable | Description | Example | |----------|-------------|---------| | `HA_URL` | Home Assistant URL | `http://homeassistant.10.0.0.19.nip.io:8123` | | `HA_TOKEN` | Long-lived access token | `eyJhbGciOiJIUzI1NiIsInR5cCI...` | ### Optional - Home Assistant | Variable | Default | Description | |----------|---------|-------------| | `HA_PLUGIN_ENABLED` | `true` | Enable Home Assistant plugin | | `HA_STRICT_SSL` | `true` | Validate SSL certificates | | `HA_TIMEOUT` | `30000` | API request timeout (ms) | ### Optional - MCP Server | Variable | Default | Description | |----------|---------|-------------| | `MCP_SERVER_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | | `MCP_SERVER_LOG_FORMAT` | `plain` | Log format: `plain`, `json`, `gcp-json` | | `MCP_SERVER_USE_HTTP` | `false` | Enable HTTP mode (vs stdio) | | `MCP_SERVER_STATEFUL` | `false` | Enable stateful sessions in HTTP mode | ### Optional - HTTP Mode | Variable | Default | Description | |----------|---------|-------------| | `MCP_HTTP_PORT` | `3000` | HTTP server port | | `MCP_HTTP_BIND_ADDR` | `127.0.0.1` | Bind address | | `MCP_HTTP_PATH` | `/mcp` | MCP endpoint path | | `MCP_HTTP_ENABLE_HEALTHCHECK` | `true` | Enable health check endpoint | | `MCP_HTTP_HEALTHCHECK_PATH` | `/health` | Health check path | | `MCP_HTTP_ALLOW_CORS` | `true` | Enable CORS | | `MCP_HTTP_ALLOWED_ORIGINS` | `127.0.0.1,localhost` | Allowed CORS origins | ### Optional - Chat face (OpenAPI/REST surface) The server also serves an OpenAPI/REST face (`/openapi.json` + `/api/*`) for chat UIs like Open WebUI. By default it exposes the classic 8 Home Assistant tools. Tools marked chat-eligible in code can be activated per category with read/write granularity: | Variable | Default | Description | |----------|---------|-------------| | `MCP_CHAT_TOOLS` | unset (= legacy 8 HA tools) | Category slice, e.g. `ha-core:rw,ha-history:r,omada-read:r`. Categories: `ha-core`, `ha-history`, `ha-automations`, `omada-read`, `omada-write`. Config can only narrow what the code marks chat-eligible; ADMIN tools (`invokeAction`) can never appear on this face. | | `MCP_REST_ACTIONS` | unset (= tool not registered) | JSON map of pre-registered REST actions for the `invokeAction` MCP tool (ADMIN; MCP face only), e.g. `{"deploy":{"url":"http://host:8425/v1/update","bearerToken":"...","cooldownMs":300000}}`. Each action is rate-limited: min interval between firings = `cooldownMs` (default 60000; `0` disables) — caps deploy-storm blast radius if a model loops a legitimate action | Activated chat tools appear in the generated `/openapi.json` as `POST /api/tools/` and dispatch through the same handlers, validation, and RBAC as the MCP face. ### Optional - SSE Events | Variable | Default | Description | |----------|---------|-------------| | `MCP_SSE_EVENTS_ENABLED` | `true` | Enable real-time SSE events | | `MCP_OWNER_QUESTIONS_ENABLED` | `true` | Enable `askOwner` / `getOwnerAnswer` / `cancelOwnerQuestion` (HTTP mode with Home Assistant) | | `MCP_OWNER_QUESTIONS_HA_USERS` | (any HA user) | Comma-separated Home Assistant user ids allowed to answer owner questions (the `answeredBy` value of an answer) | | `MCP_SSE_EVENTS_PATH` | `/subscribe_events` | SSE endpoint path | ### Optional - Rate Limiting | Variable | Default | Description | |----------|---------|-------------| | `MCP_RATE_LIMIT_ENABLED` | `false` | Enable rate limiting | | `MCP_RATE_LIMIT_WINDOW_MS` | `60000` | Rate limit window (ms) | | `MCP_RATE_LIMIT_MAX_REQUESTS` | `100` | Max requests per window | ### Optional - Authentication | Variable | Default | Description | |----------|---------|-------------| | `MCP_AUTH_METHOD` | `none` | Auth method: `none` or `bearer` | | `MCP_AUTH_SECRET` | - | JWT signing secret (min 32 chars, required for `bearer` method) | | `MCP_PERMISSIONS_CONFIG` | - | JSON config for user roles and permissions | #### JWT Authentication When `MCP_AUTH_METHOD=bearer`, clients must provide a JWT token in the Authorization header: ``` Authorization: Bearer ``` Generate tokens with your `MCP_AUTH_SECRET` using HS256 algorithm. The JWT payload should include: - `sub` (subject): User identifier for permission lookup - `exp` (optional): Expiration timestamp #### Permission System The server uses role-based access control with binary permission masks: | Permission | Flag | Description | |------------|------|-------------| | `ADMIN` | 1 | System operations (restart, updates) | | `CONFIGURE` | 2 | Create/modify automations, scripts | | `CONTROL` | 4 | Control devices (lights, climate, etc.) | | `QUERY` | 8 | Read entity states, history, lists | | `NOTIFY` | 16 | Send notifications | | `AI` | 32 | Use AI analysis features | **Predefined Roles:** | Role | Permissions | |------|-------------| | `NONE` | No permissions | | `READONLY` | QUERY only | | `OPERATOR` | QUERY + CONTROL + NOTIFY | | `CONTRIBUTOR` | QUERY + CONTROL + NOTIFY + CONFIGURE | | `ADMIN` | All six permissions (QUERY, CONTROL, NOTIFY, CONFIGURE, AI, ADMIN) | | `SUPERUSER` | All permissions | **Configuration Example:** ```json { "users": [ { "sub": "admin@example.com", "role": "admin" }, { "sub": "user@example.com", "role": "operator" }, { "sub": "viewer@example.com", "role": "readonly" } ], "defaultRole": "NONE" } ``` Set via environment variable (escape quotes for shell): ```bash MCP_PERMISSIONS_CONFIG='{"users":[{"sub":"admin","role":"admin"}],"defaultRole":"NONE"}' ``` **Recommended: `defaultRole: "NONE"` plus one `users` entry per client.** `defaultRole` applies to *every* validly signed token whose `sub` is not listed — a non-NONE default silently gives unmapped clients real access. The server logs a warning at startup when `defaultRole` grants anything. Map each client (Open WebUI, n8n, agents…) explicitly before switching an existing deployment to `NONE`. Clients only see the tools they may use: `tools/list` is filtered by the caller's permissions, so a `readonly` token is never offered control or admin tools (calls are still checked per tool). ### Optional - AI Provider | Variable | Default | Description | |----------|---------|-------------| | `AI_PLUGIN_ENABLED` | `true` | Enable AI plugin | | `AI_PROVIDER` | `ollama` | AI provider: `ollama`, `openai`, or `none` | | `AI_URL` | `http://localhost:11434` | AI server URL (Ollama) | | `AI_MODEL` | `llama2` | AI model name | | `AI_TIMEOUT` | `60000` | AI request timeout (ms) | ### Optional - Omada Network Controller | Variable | Default | Description | |----------|---------|-------------| | `OMADA_PLUGIN_ENABLED` | `false` | Enable Omada plugin | | `OMADA_BASE_URL` | - | Omada controller URL (e.g., `https://10.0.0.1:8043`) | | `OMADA_CLIENT_ID` | - | OAuth2 client ID from Omada controller | | `OMADA_CLIENT_SECRET` | - | OAuth2 client secret from Omada controller | | `OMADA_OMADAC_ID` | - | Omada controller ID | | `OMADA_SITE_ID` | - | Default site ID for operations | | `OMADA_STRICT_SSL` | `true` | Validate SSL certificates | | `OMADA_TIMEOUT` | `30000` | API request timeout (ms) | ## Advanced Configuration Examples ### With AI Analysis (Ollama) ```json { "mcpServers": { "homeassistant": { "command": "npx", "args": ["-y", "@coffeerunhobby/mcp-ha-connect"], "env": { "HA_URL": "http://homeassistant.10.0.0.19.nip.io:8123", "HA_TOKEN": "your_token", "AI_PROVIDER": "ollama", "AI_URL": "http://ollama.10.0.0.17.nip.io:11434", "AI_MODEL": "llama2" } } } } ``` ### Disable AI Features ```json { "mcpServers": { "homeassistant": { "command": "npx", "args": ["-y", "@coffeerunhobby/mcp-ha-connect"], "env": { "HA_URL": "http://homeassistant.10.0.0.19.nip.io:8123", "HA_TOKEN": "your_token", "AI_PROVIDER": "none" } } } } ``` ### Docker with Network Configuration For Linux, you may need to add `host.docker.internal`: ```json { "mcpServers": { "homeassistant": { "command": "docker", "args": [ "run", "--rm", "-i", "--add-host=host.docker.internal:host-gateway", "-e", "HA_URL=http://host.docker.internal:8123", "-e", "HA_TOKEN=your_token", "ghcr.io/coffeerunhobby/mcp-ha-connect:latest" ] } } } ``` ## Usage Modes ### Mode 1: MCP Client (Claude Desktop / LM Studio) Default stdio mode for MCP clients. See [Quick Start](#quick-start) section for configuration. For detailed configuration options, see [docs/LOCAL_CLIENT.md](docs/LOCAL_CLIENT.md). ### Mode 2: HTTP Server Start as standalone HTTP server for web applications: ```bash # Using npx npx @coffeerunhobby/mcp-ha-connect # Or with environment variables MCP_SERVER_USE_HTTP=true \ MCP_HTTP_PORT=3000 \ HA_URL=http://homeassistant.10.0.0.19.nip.io:8123 \ HA_TOKEN=your_token \ npx @coffeerunhobby/mcp-ha-connect ``` #### Streamable HTTP Transport Configure your MCP client to use: - **URL:** `http://localhost:3000/mcp` - **Transport:** Streamable HTTP (MCP 2025-03-26) #### SSE Event Subscription Subscribe to real-time events: ```javascript const eventSource = new EventSource( 'http://localhost:3000/subscribe_events?domain=light&entity_id=light.living_room' ); eventSource.addEventListener('state_changed', (event) => { const data = JSON.parse(event.data); console.log('Entity changed:', data.entity_id, data.new_state); }); ``` See [docs/SSE_API.md](docs/SSE_API.md) for complete SSE documentation. ### Mode 3: Docker Standalone ```bash # Pull image docker pull ghcr.io/coffeerunhobby/mcp-ha-connect:latest # Run in HTTP mode docker run -d --name mcp-ha-connect \ -e HA_URL=http://homeassistant.10.0.0.19.nip.io:8123 \ -e HA_TOKEN=your_token \ -e MCP_SERVER_USE_HTTP=true \ -p 3000:3000 \ ghcr.io/coffeerunhobby/mcp-ha-connect:latest ``` ### Mode 4: Docker Compose ```yaml version: '3.8' services: mcp-ha-connect: image: ghcr.io/coffeerunhobby/mcp-ha-connect:latest container_name: mcp-ha-connect restart: unless-stopped ports: - "3000:3000" environment: - HA_URL=http://homeassistant.10.0.0.19.nip.io:8123 - HA_TOKEN=${HA_TOKEN} - MCP_SERVER_USE_HTTP=true - MCP_HTTP_BIND_ADDR=0.0.0.0 networks: - home-automation networks: home-automation: driver: bridge ``` ## Integration Examples ### With Claude Desktop Once configured, you can ask Claude: - "Show me all lights in my house" - "Turn on the kitchen lights to 50% brightness" - "Set the living room temperature to 72 degrees" - "Create an automation that turns off all lights at midnight" - "What automations are currently enabled?" - "Trigger the morning routine automation" - "Search for entities with 'bedroom' in their name" - "Get all climate entities" - "Analyze my sensors for any issues" (requires Ollama) - "Check the history of my thermostat" ### With n8n Workflows The server can be integrated with n8n for automated home monitoring. Pre-built workflows are available in the `n8n/` directory: 1. **ai-agent-safety-monitor** - Critical safety monitoring every 5 minutes #### Importing Workflows **Method 1: n8n UI** 1. Open n8n: `http://127.0.0.1:5678` 2. Click **Workflows** → **Import from File** 3. Select JSON file from `n8n-files/` 4. Activate the workflow **Method 2: Docker CLI** ```bash # Copy workflow to n8n container docker cp "n8n-files/AI Agent Safety Monitor - Updated.json" n8n:/files/workflow.json # Import using n8n CLI docker exec -it n8n n8n import:workflow --input=/files/workflow.json # Verify import docker exec -it n8n n8n list:workflow ``` ### With Web Applications Use the HTTP transport with your MCP-compatible web client: 1. Start server in HTTP mode 2. Configure client to use `http://localhost:3000/mcp` 3. Set transport to Streamable HTTP (MCP 2025-03-26) 4. Enable CORS by adding your origin to `MCP_HTTP_ALLOWED_ORIGINS` ### With Remote MCP Proxy (mcp-remote) Connect a stdio-only client (Claude Desktop, LM Studio) to a remote HTTP MCP server using [`mcp-remote`](https://www.npmjs.com/package/mcp-remote). On Windows, launch it through `cmd /c` and include `-y` so the first run never blocks on the npx install prompt: ```json { "mcpServers": { "homeassistant": { "command": "cmd", "args": [ "/c", "npx", "-y", "mcp-remote", "https://your-server.example/mcp", "--header", "Authorization: Bearer " ] } } } ``` > ⚠️ **Do not use `${VAR}` placeholders in the `--header` value.** A config like > `"Authorization: Bearer ${MCP_AUTH_TOKEN}"` with a matching `env` block does **not** > work on Windows: `cmd /c` only expands `%VAR%` (not `${VAR}`), and mcp-remote does > not substitute it either. The literal text `${MCP_AUTH_TOKEN}` is sent as the token, > the server replies `401`, and mcp-remote then falls into a failing OAuth flow that > surfaces as **"Server disconnected"**. Inline the actual JWT instead. (On macOS/Linux > you can instead let your shell expand the variable before the client launches.) ## Network Configuration Notes - **`host.docker.internal`** - Use this to access services on your host machine from Docker - Works on Windows and macOS by default - On Linux, add `--add-host=host.docker.internal:host-gateway` to docker args - **Direct IP** - Use your machine's IP address (e.g., `http://10.0.0.19:8123`) - **nip.io domains** - Use wildcard DNS for easy local development (e.g., `http://homeassistant.10.0.0.19.nip.io:8123`) ## Security Best Practices ### Network Security - **Network Segmentation**: Run MCP server on private LAN only - **Docker Networks**: Use Docker networks for container isolation - **VLAN**: Consider VLAN for IoT devices - **No Internet Exposure**: Never expose MCP server directly to internet ### Token & Configuration Security - Store `.env` securely (contains access token) - **Never** commit `.env` to version control - Set `.env` permissions: `chmod 600 .env` - Use `HA_STRICT_SSL=true` for HTTPS in production - Rotate access tokens periodically - Restrict `MCP_HTTP_ALLOWED_ORIGINS` (avoid wildcard `*`) - Bind to `127.0.0.1` for local-only access - Enable rate limiting in production (`MCP_RATE_LIMIT_ENABLED=true`) ### MCP Client Configuration Security ⚠️ **Important:** Storing tokens directly in MCP client config files (Claude Desktop, LM Studio) exposes them to anyone with filesystem access. Consider: - Using `.env` files with restricted permissions for Docker/local deployments - Setting file permissions on config files (e.g., `chmod 600`) - Using environment variable substitution if supported by your client - Never committing config files with real tokens to version control ## Server Instructions The server provides MCP Server Instructions during initialization to help LLMs understand how to optimally use the available tools. Instructions are dynamically generated based on enabled plugins and include: - **Entity Queries**: Guidance on pagination, attribute inclusion, and using specialized tools like `listPersons` - **Device Control**: When to use specialized control tools vs generic `callService` - **Automation Workflows**: Best practices for creating and testing automations - **Omada Operations**: Site context requirements, client vs device distinction, rate limiting safety - **Cross-Plugin Integration**: How HA presence detection correlates with Omada network clients Instructions follow MCP best practices: concise, actionable, and focused on tool relationships rather than repeating individual tool descriptions. ## Troubleshooting ### Claude Desktop / LM Studio Not Connecting 1. **Check config file location** - Verify you're editing the correct file 2. **Restart the application** - Close and reopen after config changes 3. **Check logs** - Look for error messages in the application logs 4. **Test connection manually**: ```bash # Test npx npx @coffeerunhobby/mcp-ha-connect # Test Docker docker run --rm -i \ -e HA_URL=http://homeassistant.10.0.0.19.nip.io:8123 \ -e HA_TOKEN=your_token \ ghcr.io/coffeerunhobby/mcp-ha-connect:latest ``` ### mcp-remote Shows "Server disconnected" (401) If `/health` returns `200` but the client shows **"Server disconnected"**, the auth header is almost certainly malformed. Most common cause: a `${VAR}` placeholder in the `--header` value that never got expanded (see the warning under [With Remote MCP Proxy](#with-remote-mcp-proxy-mcp-remote)). Verify the endpoint accepts your token directly: ```bash curl -i -X POST https://your-server.example/mcp \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"diag","version":"1.0"}}}' ``` A `200` with an `initialize` result means the server and token are fine — fix the client config (inline the literal token). A `401` means the token itself is wrong. ### Docker `host.docker.internal` Not Working (Linux) Add host gateway: ```json { "args": [ "run", "--rm", "-i", "--add-host=host.docker.internal:host-gateway", "-e", "HA_URL=http://host.docker.internal:8123", ... ] } ``` ### Home Assistant Connection Issues 1. **Verify URL** - Try accessing `http://your-ha-url:8123/api/` in browser 2. **Check token** - Ensure it's a valid long-lived access token 3. **Network access** - Verify MCP server can reach Home Assistant 4. **SSL issues** - Try `HA_STRICT_SSL=false` for testing ### AI Analysis Not Working 1. **Verify Ollama is running** - `curl http://your-ollama-url:11434/api/tags` 2. **Check model is available** - Ensure model is pulled: `ollama pull llama2` 3. **Set AI_PROVIDER** - Must be set to `ollama` (not `none`) ## Development ### Setup ```bash # Clone repository git clone https://github.com/coffeerunhobby/mcp-ha-connect.git cd mcp-ha-connect # Install dependencies npm install # Copy environment template cp .env.example .env # Edit .env with your Home Assistant details nano .env # Build npm run build # Run npm start ``` ### Available Scripts ```bash npm run build # Build TypeScript to dist/ npm run dev # Run in development mode with watch npm start # Run built version npm test # Run tests npm run lint # Run ESLint npm run format # Format code with Prettier ``` ## License MIT ## Author Coffee Run Hobby ([github.com/coffeerunhobby](https://github.com/coffeerunhobby)) ## Links - **GitHub**: [https://github.com/coffeerunhobby/mcp-ha-connect](https://github.com/coffeerunhobby/mcp-ha-connect) - **npm**: [https://www.npmjs.com/package/@coffeerunhobby/mcp-ha-connect](https://www.npmjs.com/package/@coffeerunhobby/mcp-ha-connect) - **Docker**: [https://github.com/coffeerunhobby/mcp-ha-connect/pkgs/container/mcp-ha-connect](https://github.com/coffeerunhobby/mcp-ha-connect/pkgs/container/mcp-ha-connect) ## Documentation - [Quick Start Guide](docs/QUICK_START.md) - [Changelog](docs/CHANGELOG.md) ## Contributing Contributions are welcome! Please feel free to submit a Pull Request.