A native Model Context Protocol (MCP) server that runs directly on your Hubitat Elevation hub. Instead of running a separate Node.js server on another machine, this runs natively on the hub itself — with a built-in rule engine and 105 MCP tools (31 on tools/list via category gateways).
BETA SOFTWARE: This project is ~99% AI-generated ("vibe coded") using Claude. It's a work in progress — contributions and bug reports are welcome!
This app lets AI assistants like Claude control your Hubitat smart home through natural language. Just talk to it:
"Turn on the living room lights"
"What's the temperature in the bedroom?"
"Create a rule that turns off all lights at midnight"
"When motion is detected in the hallway, turn on the hallway light for 5 minutes"
"When the temperature stays above 78 for 5 minutes, turn on the AC"
"Turn on outdoor lights at sunset"
"When the bedroom button is double-tapped, toggle the bedroom lights"
"What's the hub's health status?"
Behind the scenes, the AI uses MCP tools to control devices, create automation rules, manage rooms, query system state, and administer the hub. The server exposes 105 tools total — 11 core tools are always visible, while the rest are organized behind 20 domain-named gateways to keep the tool list manageable. If your client handles long tool lists well, you can disable the gateways via the Consolidate tools behind category gateways setting and every tool is exposed individually instead. (Counts here describe the shipped catalog; the runtime count on tools/list varies based on enabled settings.)
- Hubitat Elevation C-7, C-8, or C-8 Pro
- Hubitat firmware 2.3.0+ (for OAuth and internal API support)
If you don't have Hubitat Package Manager (HPM) installed yet, follow the HPM installation instructions to set it up first.
Once HPM is installed:
- Open HPM > Install
- Search for "MCP"
- Select MCP Rule Server and install
That's it! HPM will install the parent app, the child app, and the required Groovy libraries (delivered as a bundle, shown under Libraries Code) automatically in the same install/update, and notify you when updates are available.
Alternate HPM method: You can also use HPM > Install > From a URL and paste:
https://raw.githubusercontent.com/kingpanther13/Hubitat-local-MCP-server/main/packageManifest.json
The parent app #includes Groovy libraries, which are all shipped together in one bundle (mcp-libraries.zip). Install that bundle first — otherwise the parent app fails to compile when you Save it. Install in this order: the libraries bundle, then the parent app, then the child app.
1. Install the libraries bundle:
In the Hubitat web UI go to Bundles > Import, and import the bundle from this repo:
https://raw.githubusercontent.com/kingpanther13/Hubitat-local-MCP-server/main/bundles/mcp-libraries.zip
If your hub's Bundle Manager only accepts a file upload, download that .zip first and upload it. Importing the bundle installs every library the app needs in one step (they appear under Libraries Code) — there's no need to add libraries individually. (HPM / Option A does this automatically.)
2. Install the Parent App (MCP Rule Server):
- Go to Hubitat web UI > Apps Code > + New App
- Click Import and paste this URL:
https://raw.githubusercontent.com/kingpanther13/Hubitat-local-MCP-server/main/hubitat-mcp-server.groovy - Click Import > OK > Save
- Click OAuth > Enable OAuth in App > Save
3. Install the Child App (MCP Rule):
- Go to Apps Code > + New App
- Click Import and paste this URL:
https://raw.githubusercontent.com/kingpanther13/Hubitat-local-MCP-server/main/hubitat-mcp-rule.groovy - Click Import > OK > Save
- (No OAuth needed for the child app)
- Go to Apps > + Add User App > MCP Rule Server
- Select devices you want accessible via MCP
- Click Done
- Open the app to see your endpoint URLs and manage rules
The app shows two endpoint URLs:
-
Local Endpoint — for use on your local network:
http://192.168.1.100/apps/api/123/mcp?access_token=YOUR_TOKEN -
Cloud Endpoint — for remote access (requires Hubitat Cloud subscription):
https://cloud.hubitat.com/api/YOUR_HUB_ID/apps/123/mcp?access_token=YOUR_TOKEN
Transport: This server uses Streamable HTTP (not SSE or stdio). Your MCP client must support HTTP transport — most do by default.
Claude Code (CLI)
Add to your MCP settings file (~/.claude.json or project .mcp.json):
{
"mcpServers": {
"hubitat": {
"type": "http",
"url": "http://192.168.1.100/apps/api/123/mcp?access_token=YOUR_TOKEN"
}
}
}For remote access, use the Hubitat Cloud URL instead.
Alternatively, some people have had luck just simply giving Claude access to its own directory, giving it the URL and asking it to set up its own connection.
Claude Desktop
Claude Desktop only launches stdio MCP servers from its config file — it does not accept a plain "url"/"type": "url" entry (those are silently ignored). Because this server speaks HTTP, you bridge it to stdio with a small proxy. Use the one that matches your OS: mcp-proxy on Windows (via uv's uvx), mcp-remote on macOS (via Node.js's npx). Install the matching runtime first.
Easiest option: skip the config file entirely and add the server in Claude.ai instead (see the Claude.ai section below). Connectors you add there automatically show up in Claude Desktop when signed in to the same account — no JSON editing, and it works from any chat.
To edit the config file manually, open Claude Desktop > Settings > Developer > Edit Config (this opens claude_desktop_config.json).
Windows — Microsoft Store / MSIX install: the Edit Config button opens
%APPDATA%\Claude\claude_desktop_config.json, but the Store build does not read that file (MSIX redirects it to a virtualized copy), so edits there are silently ignored and your server never loads. Edit this file instead:%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.jsonThe regular installer (
.exe) build does use%APPDATA%\Claude\…. (tracking bug)
Add the block for your OS — use your local URL (http://YOUR_HUB_IP/apps/api/123/mcp?access_token=YOUR_TOKEN) or your cloud URL (https://cloud.hubitat.com/api/YOUR_HUB_ID/apps/123/mcp?access_token=YOUR_TOKEN) where shown. If you already have other MCP servers configured, merge the hubitat block into your existing mcpServers object instead of pasting over the whole file.
Windows — mcp-proxy (via uvx). Same block for the local or cloud URL:
{
"mcpServers": {
"hubitat": {
"command": "uvx",
"args": [
"mcp-proxy",
"--transport",
"streamablehttp",
"http://YOUR_HUB_IP/apps/api/123/mcp?access_token=YOUR_TOKEN"
]
}
}
}macOS — mcp-remote (via npx). Add --allow-http for a local (plain-HTTP) URL; omit it for the cloud (HTTPS) URL:
{
"mcpServers": {
"hubitat": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://YOUR_HUB_IP/apps/api/123/mcp?access_token=YOUR_TOKEN",
"--transport",
"http-only",
"--allow-http"
]
}
}
}Save the file, then fully restart Claude Desktop (Quit from the system tray / menu bar — closing the window is not enough). The Hubitat tools appear under the tools (🔨) icon.
Claude.ai (Connectors)
Claude.ai supports MCP servers through Connectors:
- Go to claude.ai > Settings > Connectors
- Add a new connector with your Hubitat endpoint URL
- Use the Cloud Endpoint URL for remote access, or use a Cloudflare Tunnel URL
With Hubitat Cloud, you can control your smart home from claude.ai anywhere — no local setup required!
Tip: a connector added in Claude.ai also shows up automatically in Claude Desktop when you're signed in to the same account — so this is the easiest way to get Hubitat into Claude Desktop without editing any config file, and it works from any chat thread.
NOTE: when connecting on claude.ai, you will see a message stating "Couldn't reach the MCP server. You can check the server URL and verify the server is running. If this persists, share this reference with support: "ofid_1234"".
This is a known bug with Claude.ai's UI. Click on "configure" after you see this message and you will find that you are actually connected. Try asking claude to check the health of your Hubitat in a chat, and you will see it work its magic!
Other AI Services
Any AI service that supports MCP servers via HTTP URL can use this server. Use either:
- Hubitat Cloud URL — no additional setup needed with a Hubitat Cloud subscription
- Cloudflare Tunnel — for free self-hosted remote access (see Remote Access)
An Agent Skill is a knowledge pack that teaches Claude best practices for using this MCP server — device safety protocols, rule creation patterns, tool usage tips, and more. It's not required (Claude works fine without it), but it helps Claude make better decisions, especially around safety-critical operations like device authorization and hub admin tools.
To install:
- Download the
agent-skill/hubitat-mcp/folder from this repository - Zip it so the folder is the root:
hubitat-mcp.zip>hubitat-mcp/>SKILL.md, etc. - Go to claude.ai > Settings > Features > Skills
- Upload the zip file
The skill works alongside the MCP connector — the connector gives Claude the tools, and the skill teaches Claude how to use them well.
For Claude Code users: You can also copy the skill folder to
~/.claude/skills/hubitat-mcp/for automatic loading.
Option 1: Hubitat Cloud (Easiest)
If you have a Hubitat Cloud subscription:
- The cloud endpoint URL is shown directly in the app
- Use that URL in your MCP client configuration
- No additional setup required!
Option 2: Cloudflare Tunnel (Free, Self-Hosted)
For free remote access without a Hubitat Cloud subscription:
- Install cloudflared
- Create a tunnel:
# cloudflared config.yml ingress: - hostname: hubitat-mcp.yourdomain.com service: http://YOUR_HUB_IP:80 - service: http_status:404
- Use your tunnel URL:
https://hubitat-mcp.yourdomain.com/apps/api/123/mcp?access_token=YOUR_TOKEN
The server has 105 tools total. To keep the MCP tools/list manageable, 11 core tools are always visible and the remaining tools are organized behind 20 domain-named gateways (7 read-only hub_read_* gateways + 13 write-bearing hub_manage_* gateways). The AI sees 31 items on tools/list (11 + 20 gateways). A tool may appear under more than one gateway — read tools inside a mixed hub_manage_* gateway are also surfaced in a pure-read hub_read_* gateway. Each gateway's description includes tool summaries (always visible to the AI), and calling a gateway with no arguments returns full parameter schemas on demand.
System (5) — Hub modes, HSM, and info
| Tool | Description |
|---|---|
hub_get_info |
Comprehensive hub info: hardware, health, MCP stats. PII (name, IP, location) is included whenever the Read master is ON (the default) |
hub_list_modes |
List location modes |
hub_set_mode |
Change location mode (Home, Away, Night, etc.) |
hub_get_hsm_status |
Get Home Security Monitor status |
hub_set_hsm |
Change HSM arm mode |
Virtual Devices (1) — MCP-managed virtual devices
| Tool | Description |
|---|---|
hub_manage_virtual_device |
Create or delete an MCP-managed virtual device (action: "create", "delete") -- supports built-in deviceType OR customDriver={namespace, name} (Write master). To list MCP-managed virtual devices with states, use hub_list_devices with filter='virtual'. |
Hub Utilities (3) — Backup, updates, and diagnostics
| Tool | Description |
|---|---|
hub_create_backup |
Create full hub backup (required before admin writes) |
hub_update_firmware |
Install the hub's pending platform/firmware update (version/update reads fold into hub_get_info) |
hub_report_issue |
Generate comprehensive diagnostic report |
Reference (2)
| Tool | Description |
|---|---|
hub_get_tool_guide |
Full tool reference from the MCP server itself |
hub_search_tools |
Natural-language search across all tools (BM25 ranking); returns matches with their gateway location |
Call a gateway with no arguments to see full parameter schemas. Call with tool='<name>' and args={...} to execute a specific tool. Gateways split into 7 read-only hub_read_* gateways (every sub-tool is read-only) and 12 write-bearing hub_manage_* gateways (mixed read+write or write-only). A tool may be listed in more than one gateway — reads inside a mixed hub_manage_* gateway are also surfaced in a hub_read_* gateway.
hub_read_apps_code (11) — App/driver/library listing, source, backups, and HPM (read-only)
| Tool | Description |
|---|---|
hub_list_apps |
List apps on the hub. scope='types' lists installed app types (code); scope='instances' enumerates all app instances (built-in + user) with parent/child tree, filterable by builtin/user/disabled/parents/children. |
hub_list_drivers |
List all installed drivers on the hub |
hub_get_source |
Get Groovy source code for an app, driver, or library (type: "app", "driver", "library"; id). Supports chunked reading via offset/length. |
hub_list_libraries |
List all installed Groovy libraries (id, name, namespace) |
hub_list_bundles |
List installed code bundles — the Bundles page, distinct from Libraries Code (id, name, namespace, contents) |
hub_list_backups |
List auto-created source code backups |
hub_get_backup |
Get source from a backup |
hub_list_device_dependents |
Find all apps that reference a specific device (Room Lighting, Rule Machine, Groups, Mode Manager, dashboards, Maker API, etc.) |
hub_get_app_config |
Read an installed app's configuration page (Rule Machine, Room Lighting, Basic Rules, HPM, etc.) — sections, inputs, values. Multi-page apps via pageName. Read-only. Read master. |
hub_list_app_pages |
List known page names for a multi-page app (HPM, Room Lighting, etc.). Returns curated directory + live primary page. Use before hub_get_app_config on multi-page apps to avoid guessing page names. Read master. |
hub_list_hpm_packages |
List all packages tracked by HPM — name, version, beta flag, author, and full component inventory (apps, drivers, files with heIDs). Top-level count and echoed hpmAppId. Auto-discovers HPM's app ID. Pass includeDrift=true to also cross-reference HPM-tracked packages against installed apps and drivers (surfacing missing-required components, orphan apps, and orphan drivers under a drift key; optional packageFilter substring; surfaces orphanDetection / orphanDriverDetection when registry fetches fail; data-quality warning types: heid-whitespace-normalized, heid-non-scalar-dropped, empty-heid, skipped-malformed-component — see hub_get_tool_guide for full details). Read master. |
hub_list_device_dependents, hub_get_app_config, hub_list_app_pages, and hub_list_hpm_packages are gated by the Read master (ON by default). HPM itself must be installed on the hub.
hub_read_devices (4) — Query devices (read-only)
| Tool | Description |
|---|---|
hub_list_devices |
List accessible devices (pagination, server-side labelFilter/capabilityFilter, format=ids, field projection; filter='virtual' lists only MCP-managed virtual devices) |
hub_get_device |
Full device details: attributes, commands, capabilities |
hub_get_device_attribute |
Get a specific attribute value. Pass expectedValue (or expectedValues) to block-poll the attribute until it matches or times out — timeoutMs in MILLISECONDS (default 5000ms = 5 seconds, max 60000ms). Polling BLOCKS the MCP request; use sparingly and prefer event-driven flows when available. |
hub_list_device_events |
Recent events for a device. Add hoursBack for a time window (up to 7 days of device or location event history); omit deviceId for mode/HSM/hub-variable/sendLocationEvent location events. |
hub_read_diagnostics (9) — Diagnostics, metrics, memory, radio details (read-only)
| Tool | Description |
|---|---|
hub_get_logs |
Hub log entries (most recent first) with level/source/regex filters, multi-pattern AND/OR, time-window (since/until, max 30d relative -- throws if exceeded; use ISO-8601 for longer ranges), and server-side deviceId/appId scoping. pattern matches the message field only; pathological regex like (.*)* may hang the matcher. (Device/location event history is in hub_list_device_events via hoursBack.) |
hub_get_performance_stats |
Device/app performance stats (count, % busy, total ms, state size, events, large-state flag) |
hub_get_jobs |
Scheduled jobs, running jobs, and hub actions |
hub_get_debug_logs |
Retrieve MCP debug log entries. Pass mode='status' to get logging system status and capacity instead. |
hub_get_metrics |
Retrieve hub metrics with CSV trend history (read-only by default; does not record a new snapshot) |
hub_get_memory_history |
Free OS memory and CPU load history with summary stats (Read master) |
hub_get_device_health |
Find stale/offline devices |
hub_get_radio_details |
Radio info — Z-Wave (firmware, devices) or Zigbee (channel, PAN ID, devices). radio: "zwave" or "zigbee"; omit for both. |
hub_list_captured_states |
List saved device state snapshots |
Monitoring tools are gated by the Read master (ON by default).
hub_read_files (2) — Hub File Manager (read-only)
| Tool | Description |
|---|---|
hub_list_files |
List all files in File Manager |
hub_read_file |
Read a file's contents |
hub_read_rooms (2) — Room listing (read-only)
| Tool | Description |
|---|---|
hub_list_rooms |
List all rooms with IDs, names, and device counts |
hub_get_room |
Get room details with assigned devices |
hub_read_rules (6) — Rule introspection (read-only)
| Tool | Description |
|---|---|
hub_get_custom_rule |
Full custom-engine rule details (triggers, conditions, actions). Omit ruleId to list all custom-engine rules with status; pass detailed=true for comprehensive diagnostics on a specific rule. |
hub_test_custom_rule |
Dry-run a custom-engine rule without executing actions |
hub_list_rules |
List all Rule Machine rules (RM 4.x + 5.x) via official hubitat.helper.RMUtils API |
hub_get_rule_health |
Read-only health check on any installed app — surfaces broken markers (with per-marker counts in brokenMarkerCounts), multiple-flag poison, configPage errors. |
hub_list_rule_local_variables |
List a Rule Machine rule's local variables (name/type/value) from state.allLocalVars. Distinct from hub_list_variables (hub globals). type is RM's internal token (see TOOL_GUIDE for the addLocalVariable translation). |
hub_get_visual_rule |
List Visual Rules Builder rules (omit appId) or read one rule's full JSON definition + format (classic whenNodes/thenNodes/elseNodes or graph nodes/edges). |
hub_read_variables (3) — Hub variable reads (read-only)
| Tool | Description |
|---|---|
hub_list_variables |
List all hub connector and rule engine variables |
hub_get_variable |
Get a variable value and metadata |
hub_list_variable_changes |
Recent hub-variable changes since the MCP app last started |
hub_manage_custom_rules (8) — Custom rule administration
| Tool | Description |
|---|---|
hub_get_custom_rule |
Full custom-engine rule details (triggers, conditions, actions). Omit ruleId to list all custom-engine rules with status; pass detailed=true for comprehensive diagnostics on a specific rule. (Also in hub_read_rules.) |
hub_create_custom_rule |
Create a new custom-engine automation rule (separate from native Rule Machine) |
hub_update_custom_rule |
Update custom-engine rule triggers, conditions, actions, or enabled state (enabled=true/false) |
hub_delete_custom_rule |
Permanently delete a custom-engine rule (auto-backs up first) |
hub_test_custom_rule |
Dry-run a custom-engine rule without executing actions (also in hub_read_rules) |
hub_export_custom_rule |
Export custom-engine rule to JSON and persist it to File Manager via saveAs (write) |
hub_import_custom_rule |
Import custom-engine rule from exported JSON |
hub_clone_custom_rule |
Clone an existing custom-engine rule (starts disabled) |
hub_manage_devices (7) — Control and query devices
| Tool | Description |
|---|---|
hub_call_device_command |
Send a command (on, off, setLevel, etc.) |
hub_call_device_swap |
Replace a device across ALL apps/rules that reference it (built-in Swap Device tool; compatible replacements only) |
hub_update_device |
Update device properties (label, room, preferences, etc.) |
hub_list_devices |
List accessible devices (pagination, server-side labelFilter/capabilityFilter, format=ids, field projection; filter='virtual' lists only MCP-managed virtual devices) (also in hub_read_devices) |
hub_get_device |
Full device details: attributes, commands, capabilities (also in hub_read_devices) |
hub_get_device_attribute |
Get a specific attribute value. Pass expectedValue (or expectedValues) to block-poll the attribute until it matches or times out — timeoutMs in MILLISECONDS (default 5000ms = 5 seconds, max 60000ms). Polling BLOCKS the MCP request; use sparingly and prefer event-driven flows when available. (also in hub_read_devices) |
hub_list_device_events |
Recent events for a device. Add hoursBack for a time window (up to 7 days of device or location event history); omit deviceId for mode/HSM/hub-variable/sendLocationEvent location events. (also in hub_read_devices) |
hub_manage_variables (8) — Hub variables
| Tool | Description |
|---|---|
hub_list_variables |
List all hub connector and rule engine variables (also in hub_read_variables) |
hub_get_variable |
Get a variable value and metadata (also in hub_read_variables) |
hub_set_variable |
Set a variable value |
hub_create_variable |
Create a new hub variable |
hub_delete_variable |
Permanently delete a hub variable (DESTRUCTIVE) |
hub_create_connector |
Create a virtual-device connector for a hub variable |
hub_delete_connector |
Remove the connector device for a hub variable |
hub_list_variable_changes |
Recent hub-variable changes since the MCP app last started (also in hub_read_variables) |
hub_manage_rooms (5) — Room management
| Tool | Description |
|---|---|
hub_list_rooms |
List all rooms with IDs, names, and device counts (also in hub_read_rooms) |
hub_get_room |
Get room details with assigned devices (also in hub_read_rooms) |
hub_create_room |
Create a new room (Write master + confirm) |
hub_delete_room |
Permanently delete a room (Write master + confirm) |
hub_update_room |
Rename a room (Write master + confirm) |
hub_manage_destructive_ops (4) — Destructive hub operations
| Tool | Description |
|---|---|
hub_reboot |
Reboot the hub (1-3 min downtime) |
hub_shutdown |
Power OFF the hub (requires physical restart) |
hub_delete_device |
Permanently delete any device (no undo) |
hub_call_destructive_radio |
Destructive radio operations — Z-Wave/Zigbee reset/wipe and radio firmware update (no undo) |
All operations are disruptive. These tools are gated by the Write master (ON by default) and enforce a three-layer safety gate: Write master enabled + hub backup within 24 hours + explicit confirm=true.
hub_manage_code (11) — Install, update, delete apps/drivers/libraries/bundles and restore backups
| Tool | Description |
|---|---|
hub_create_app |
Install new app from Groovy source or File Manager file (source or sourceFile). Verifies install succeeded. |
hub_create_driver |
Install new driver from Groovy source or File Manager file (source or sourceFile). Bulk mode: installs=[{sourceFile},...]. Verifies each install succeeded. |
hub_update_app |
Modify existing app code (source, sourceFile, or resave) |
hub_update_driver |
Modify existing driver code (single-driver or bulk updates array) |
hub_delete_item |
Permanently delete an app, driver, or library (type: "app", "driver", "library"; auto-backs up first) |
hub_restore_backup |
Restore app/driver to backed-up version (libraries: see hub_update_library). Rule snapshots (incl. Visual Rules) recreate a deleted rule. |
hub_create_library |
Install new Groovy library (#include namespace.Name) |
hub_update_library |
Modify existing library code |
hub_install_bundle |
Install a code bundle (.zip) from a URL the HPM way — unpacks into Libraries/Apps/Drivers Code |
hub_delete_bundle |
Delete an installed code bundle by id (the container; delivered code may remain in Code) |
hub_export_bundle |
Export an installed bundle's .zip to the hub File Manager (downloadable at /local/<fileName>) |
Source code is automatically backed up before any modify/delete operation.
hub_manage_logs (6) — Logs, performance stats, and log configuration
| Tool | Description |
|---|---|
hub_get_logs |
Hub log entries (most recent first) with level/source/regex filters, multi-pattern AND/OR, time-window (since/until, max 30d relative -- throws if exceeded; use ISO-8601 for longer ranges), and server-side deviceId/appId scoping. pattern matches the message field only; pathological regex like (.*)* may hang the matcher. (Device/location event history is in hub_list_device_events via hoursBack.) (also in hub_read_diagnostics) |
hub_get_performance_stats |
Device/app performance stats (count, % busy, total ms, state size, events, large-state flag) (also in hub_read_diagnostics) |
hub_get_jobs |
Scheduled jobs, running jobs, and hub actions (also in hub_read_diagnostics) |
hub_get_debug_logs |
Retrieve MCP debug log entries. Pass mode='status' to get logging system status and capacity instead. (also in hub_read_diagnostics) |
hub_delete_debug_logs |
Clear all MCP debug logs |
hub_set_log_level |
Set MCP log level (debug/info/warn/error) |
Read tools are gated by the Read master; clear/set-level writes by the Write master (both ON by default).
hub_manage_diagnostics (7) — Diagnostics, memory, radio details, and state capture
| Tool | Description |
|---|---|
hub_get_metrics |
Retrieve hub metrics with CSV trend history (read-only by default; pass recordSnapshot=true to also record a new snapshot). (also in hub_read_diagnostics) |
hub_get_memory_history |
Free OS memory and CPU load history with summary stats (Read master) (also in hub_read_diagnostics) |
hub_call_gc |
Force JVM garbage collection; returns before/after free memory (Write master) |
hub_get_device_health |
Find stale/offline devices (also in hub_read_diagnostics) |
hub_get_radio_details |
Radio info — Z-Wave (firmware, devices) or Zigbee (channel, PAN ID, devices). radio: "zwave" or "zigbee"; omit for both. (also in hub_read_diagnostics, hub_manage_radio) |
hub_list_captured_states |
List saved device state snapshots (also in hub_read_diagnostics) |
hub_delete_captured_state |
Delete a captured device state snapshot. Omit stateId to delete all snapshots. |
hub_manage_radio (6) — Z-Wave, Zigbee, and Matter radio administration
| Tool | Description |
|---|---|
hub_get_radio_details |
Radio info — Z-Wave (firmware, devices) or Zigbee (channel, PAN ID, devices). radio: "zwave" or "zigbee"; omit for both. (also in hub_read_diagnostics, hub_manage_diagnostics) |
hub_set_zwave |
Configure Z-Wave radio settings |
hub_call_zwave |
Z-Wave radio operations including network repair (5-30 min) via action |
hub_set_zigbee |
Configure Zigbee radio settings |
hub_call_zigbee |
Zigbee radio operations via action |
hub_call_matter |
Matter radio operations via action |
Non-destructive radio operations. Destructive radio ops (reset/wipe, firmware update) live in hub_manage_destructive_ops as hub_call_destructive_radio. Read tools are gated by the Read master; writes by the Write master (both ON by default).
hub_manage_files (4) — Hub File Manager
| Tool | Description |
|---|---|
hub_list_files |
List all files in File Manager (also in hub_read_files) |
hub_read_file |
Read a file's contents (also in hub_read_files) |
hub_write_file |
Create or update a file (auto-backs up existing) |
hub_delete_file |
Delete a file (auto-backs up first) |
Write/delete require the Write master + confirm + a recent backup.
hub_manage_rule_machine (11) — Rule authoring (Rule Machine + Visual Rules Builder) and RM runtime control
| Tool | Description |
|---|---|
hub_set_rule |
Create or edit a Rule Machine rule (RM 5.1) — the full authoring surface (omit appId to create). Structured shortcuts: addTrigger / addAction / addRequiredExpression / replaceRequiredExpression / walkStep / patches and more. Auto-snapshots before every write. |
hub_list_rules |
List all Rule Machine rules (RM 4.x + 5.x) via official hubitat.helper.RMUtils API (also in hub_read_rules) |
hub_call_rule |
Trigger an RM rule (action: "rule"/"actions"/"stop") |
hub_set_rule_paused |
Pause or resume an RM rule (value=true pauses, value=false resumes; reversible) |
hub_set_rule_private_boolean |
Set an RM rule's private boolean variable |
hub_get_rule_health |
Read-only health check on any installed app — surfaces broken markers, multiple-flag poison, configPage errors. (also in hub_read_rules) |
hub_list_rule_local_variables |
List a Rule Machine rule's local variables (name/type/value) from state.allLocalVars; distinct from hub_list_variables (hub globals). (also in hub_read_rules) |
hub_delete_native_app |
Delete any classic native app incl. RM rules (auto-snapshot first; force=true for hard delete; also in hub_manage_native_rules_and_apps) |
hub_get_visual_rule |
List Visual Rules Builder rules (omit appId) or read one rule's full JSON definition + format (also in hub_read_rules) |
hub_set_visual_rule |
Create or update a Visual Rules Builder rule — VRB is the primary rule engine; one JSON write with if/then/else gating. Use hub_set_rule only for complex automations (nested logic/loops/variables). |
hub_delete_visual_rule |
Delete a Visual Rules Builder rule (type-gated; returns the pre-delete definition for recovery) |
Reads (hub_list_rules, hub_get_rule_health, hub_get_visual_rule) are gated by the Read master; the writes by the Write master (both ON by default). hub_set_rule, hub_delete_native_app, hub_set_visual_rule, and hub_delete_visual_rule additionally require confirm=true + a backup within 24h.
hub_manage_native_rules_and_apps (11) — Rule Machine interop (RMUtils) + native CRUD on any classic SmartApp (RM, Room Lighting, Button Controllers, Basic Rules, Notifier, etc.)
| Tool | Description |
|---|---|
hub_list_rules |
List all Rule Machine rules (RM 4.x + 5.x) via official hubitat.helper.RMUtils API (also in hub_read_rules) |
hub_call_rule |
Trigger an RM rule (action: "rule"/"actions"/"stop") |
hub_set_rule_paused |
Pause or resume an RM rule (value=true pauses, value=false resumes; reversible) |
hub_set_rule_private_boolean |
Set an RM rule's private boolean variable |
hub_set_native_app |
Create or edit any classic native app (omit appId to create; appType enum covers Button Controllers / Notifier / Groups+Scenes / Visual Rule / Basic Rules; default rule_machine). Create a Button Rule under its controller via buttonRule. Returns appId. Generic upsert; walkStep (generic classic-page walker) also works here. |
hub_set_rule |
Author a Rule Machine rule by appId (omit appId to create) — triggers, actions, required expressions, settings, structured shortcuts. Auto-snapshots before every write. clearActions / replaceActions commit the delete synchronously via a full selectActions page-form submit (runs RM's trashActs handler in-band), so the actions are gone when the call returns. A thin defensive verify-retry remains: on the rare residual it returns partial:true, asyncCommitLikely:true with stage + safeRecovery -- verify via hub_get_app_config rather than rolling back. |
hub_delete_native_app |
Delete a classic native app (auto-snapshot to File Manager before deleting). |
hub_clone_native_app |
Clone an existing classic SmartApp via Hubitat's appCloner endpoint. Returns the new appId. |
hub_export_native_app |
Export a classic SmartApp to JSON and persist it to File Manager (round-trippable with hub_import_native_app). |
hub_import_native_app |
Import previously-exported app JSON into a new instance. Returns the new appId. |
hub_get_rule_health |
Read-only health check on any installed app — surfaces broken markers, multiple-flag poison, configPage errors. (also in hub_read_rules) |
Reads are gated by the Read master; create/update/delete by the Write master (with confirm=true + a recent backup). Both masters are ON by default.
hub_manage_mcp (1) — Developer Mode self-administration
| Tool | Description |
|---|---|
hub_update_mcp_settings |
Update one or more of the MCP rule app's own settings (toggles, log level, tuning params). Allowlist-gated. |
First gateway under the Developer Mode pattern — for LLM-agent and CI/CD pipelines that need to manage the MCP rule app's own configuration without manual UI intervention. Additional self-admin tools (device-access management, true Hub Variables namespace support, artifact cleanup) are planned as follow-ups under the same toggle. Requires opt-in Enable Developer Mode Tools setting (default OFF). Each successful write is logged at WARN level for audit.
Create automations via natural language — the AI translates your request into rules with triggers, conditions, and actions. You can also manage rules through the Hubitat web UI.
Supported Triggers (6 types)
| Type | Description |
|---|---|
device_event |
When a device attribute changes (with optional duration for debouncing) |
button_event |
Button pressed, held, double-tapped, or released |
time |
At a specific time, or relative to sunrise/sunset with offset |
periodic |
Repeat at intervals (minutes, hours, or days) |
mode_change |
When hub mode changes |
hsm_change |
When HSM status changes |
Supported Conditions (14 types)
| Type | Description |
|---|---|
device_state |
Check current device attribute value |
device_was |
Device has been in state for X seconds (anti-cycling) |
time_range |
Within a time window (supports sunrise/sunset) |
mode |
Current hub mode |
variable |
Hub or rule-local variable value |
days_of_week |
Specific days |
sun_position |
Sun above or below horizon |
hsm_status |
Current HSM arm status |
presence |
Presence sensor status |
lock |
Lock status |
thermostat_mode |
Thermostat operating mode |
thermostat_state |
Thermostat operating state |
illuminance |
Light level (lux) with comparison |
power |
Power consumption (watts) with comparison |
Supported Actions (29 types)
| Type | Description |
|---|---|
device_command |
Send command to device |
toggle_device |
Toggle device on/off |
activate_scene |
Activate a scene device |
set_level |
Set dimmer level with optional duration |
set_color |
Set color on RGB devices |
set_color_temperature |
Set color temperature |
lock / unlock |
Lock or unlock a device |
set_variable |
Set a global variable |
set_local_variable |
Set a rule-scoped variable |
set_mode |
Change hub mode |
set_hsm |
Change HSM arm mode |
delay |
Wait before next action (with optional ID for cancellation) |
if_then_else |
Conditional branching |
cancel_delayed |
Cancel pending delayed actions |
repeat |
Repeat actions N times |
stop |
Stop rule execution |
log |
Write to Hubitat logs |
capture_state / restore_state |
Save and restore device states |
send_notification |
Push notification |
set_thermostat |
Thermostat mode, setpoints, fan mode |
http_request |
HTTP GET/POST (webhooks, external APIs) |
speak |
Text-to-speech with optional volume |
comment |
Documentation-only (no-op) |
set_valve |
Open or close a valve |
set_fan_speed |
Set fan speed |
set_shade |
Open, close, or position window shades |
variable_math |
Arithmetic on variables |
Rule Examples
Motion-activated light:
{
"name": "Motion Light",
"triggers": [
{ "type": "device_event", "deviceId": "123", "attribute": "motion", "value": "active" }
],
"conditions": [
{ "type": "time_range", "startTime": "sunset", "endTime": "sunrise" }
],
"actions": [
{ "type": "device_command", "deviceId": "456", "command": "on" },
{ "type": "delay", "seconds": 300, "delayId": "motion-off" },
{ "type": "device_command", "deviceId": "456", "command": "off" }
]
}Temperature with debouncing:
{
"name": "AC On When Hot",
"triggers": [
{ "type": "device_event", "deviceId": "1", "attribute": "temperature",
"operator": ">", "value": "78", "duration": 300 }
],
"actions": [
{ "type": "device_command", "deviceId": "8", "command": "on" }
]
}Button state machine with local variables:
{
"name": "Smart Button Toggle",
"localVariables": { "lastScene": "natural" },
"triggers": [
{ "type": "button_event", "deviceId": "80", "action": "pushed" }
],
"actions": [
{
"type": "if_then_else",
"condition": { "type": "variable", "variableName": "lastScene",
"operator": "equals", "value": "natural" },
"thenActions": [
{ "type": "activate_scene", "sceneDeviceId": "nightlight-scene" },
{ "type": "set_local_variable", "variableName": "lastScene", "value": "nightlight" }
],
"elseActions": [
{ "type": "activate_scene", "sceneDeviceId": "natural-scene" },
{ "type": "set_local_variable", "variableName": "lastScene", "value": "natural" }
]
}
]
}Every MCP tool is gated by two universal masters — Read and Write — both ON by default. Turn one OFF to remove that entire class of tools from the MCP client and reject any cached call.
The Read / Write masters
- Open Apps > MCP Rule Server in the Hubitat web UI
- Under Tool Access (Read / Write masters), toggle:
- Enable Read Tools — exposes every read-only / non-destructive tool (list/get/search/diagnostics, hub info, app/driver/source reads). With it OFF, those tools vanish from the client and a cached call is rejected with "Read tools are disabled…".
- Enable Write Tools — exposes every state-changing tool (device control, modes, variables, rooms, files, native rules, hub admin). With it OFF, those tools vanish and a cached call is rejected with "Write tools are disabled…".
- If your hub has Hub Security enabled, also configure:
- Hub Security Username and Password under the Hub Security section
Both masters default ON — only an explicit OFF hides or blocks a class. (This replaces the former separate "Enable Hub Admin Read/Write Tools" and "Enable Built-in App Tools" toggles.)
Destructive write safety gate
Beyond the Write master, the destructive/sensitive write tools (backup, reboot, shutdown, Z-Wave repair, delete-device, file write/delete, app/driver/native-rule CRUD, etc.) enforce a three-layer safety gate:
- The Write master must be enabled
- The AI must pass
confirm=trueexplicitly - A full hub backup must exist within the last 24 hours (enforced automatically)
Additionally, tools that modify or delete existing apps/drivers automatically back up the item's source code before making changes.
Advanced: Per-tool Overrides (deny-only)
Under Settings > Advanced: Per-tool Overrides, you can disable individual tools or whole gateways below the masters — these only turn things OFF, never re-enable. A disabled tool (or every tool inside a disabled gateway, including tools shared across gateways) drops from tools/list and hub_search_tools, and a cached call returns a distinct error: "…is disabled in Advanced settings (Per-tool Overrides)…". A disabled tool stays documented in hub_get_tool_guide. Use Reset all overrides to clear them.
Each picker entry shows the bare tool name, its friendly name, a [read]/[write] marker, and a one-sentence description — so you can tell what you're disabling without decoding the bare names. The same friendly names are published on tools/list as MCP annotations.title, so clients that honor that field — claude.ai among them — display them in their tool lists too.
Item Backup & Restore
When you use hub_update_app, hub_update_driver, or hub_delete_item (type: app|driver), the server automatically saves the original source code before making changes.
- Backups stored as
.groovyfiles in the hub's local File Manager - Named
mcp-backup-app-<id>.groovyormcp-backup-driver-<id>.groovy - Persist even if the MCP app is uninstalled
- Downloadable at
http://<your-hub-ip>/local/<filename> - Max 20 kept; oldest pruned automatically
- 1-hour protection window: multiple edits preserve the pre-edit original
Restore via MCP:
hub_list_backupsto see available backupshub_restore_backupwith the backup key andconfirm=true
Restore manually (without MCP):
- Go to Hubitat web UI > Settings > File Manager
- Download the backup file (e.g.,
mcp-backup-app-123.groovy) - Go to Apps Code (or Drivers Code) > select the app > paste source > Save
Hub Security Support
If your hub has Hub Security enabled (login required for the web UI), the MCP server handles authentication automatically:
- Configure your Hub Security username and password in the app settings
- The server caches the session cookie for 30 minutes
- Stale cookies are automatically cleared and re-authenticated
- If Hub Security is not enabled, no credentials are needed
Hub Hardware Recommendations
| Hub Model | Recommendation |
|---|---|
| C-7 | Works for basic use, may be slow with large device lists or complex rules |
| C-8 | Good for most users |
| C-8 Pro | Best for heavy use, large device counts (100+), or complex automations |
Known Limits
hub_list_deviceswithdetailed=true— Can be slow on 50+ devices. Use pagination:hub_list_devices(detailed=true, limit=25, offset=0). UselabelFilterorcapabilityFilterto narrow server-side before pagination. Usefields=[...]to skip expensive hub reads --currentStatesandattributestrigger per-device hub reads and are the ones worth projecting out;capabilitiesandcommandsare in-memory and cheap.- Duration triggers — Maximum of 2 hours (7200 seconds)
- Captured states — Default limit of 20 (configurable 1-100 in settings)
- Hubitat Cloud responses — 128KB maximum (AWS MQTT limit). Use pagination for large device lists.
- No real-time event streaming (MCP responses only)
- Sunrise/sunset times are recalculated daily
Device not found
Make sure the device is selected in the app's "Select Devices for MCP Access" setting.
OAuth token not working
- Open Apps Code > MCP Rule Server
- Click OAuth > Enable OAuth in App
- Save
- Re-open the app in Apps to get the new token
Rules not triggering
- Check that "Enable Rule Engine" is on in app settings
- Enable "Debug Logging" and check Hubitat Logs
- Verify the trigger device is selected for MCP access
- For duration-based triggers, ensure the condition stays true for the full duration
Button events not working
- Make sure you're using
button_eventtrigger type (notdevice_event) - Verify the button action type:
pushed,held,doubleTapped, orreleased
hub_list_devices(detailed=true) fails over Hubitat Cloud
Hubitat Cloud has a 128KB response size limit (AWS MQTT limitation). Use pagination and server-side filtering to stay under the limit:
hub_list_devices(detailed=true, limit=25, offset=0) // First 25 devices
hub_list_devices(detailed=true, limit=25, offset=25) // Next 25 devices
hub_list_devices(capabilityFilter='Switch', limit=25, offset=0) // Only Switch devices
hub_list_devices(fields=['id','label','currentStates'], limit=50) // Slim payload
The response includes total, hasMore, and nextOffset to help with pagination.
Rules from v0.0.x not showing
Version 0.1.0 uses a new parent/child architecture. Old rules stored in state.rules are not migrated automatically. You'll need to recreate rules either through the UI or via MCP.
Reporting bugs
For easier bug reporting:
- Set debug log level: Settings > MCP Debug Log Level > "Debug", or ask your AI to
hub_set_log_levelto "debug" - Reproduce the issue
- Ask your AI to use the
hub_report_issuetool — it will gather diagnostics and format a ready-to-submit report - Submit at GitHub Issues
Blue-sky ideas — everything below is speculative and needs further research to determine feasibility. None of these features are guaranteed or committed to.
Status key:
[ ]= not started |[~]= in progress / partially done |[x]= completed |[?]= needs research / feasibility unknownFeasibility research conducted February 2025. Each item now includes a difficulty rating (1–5), effort estimate, and implementation notes based on a thorough codebase analysis.
Difficulty key:
1= trivial |2= straightforward |3= moderate |4= complex |5= extremely complexEffort key:
S= small (hours) |M= medium (1–3 days) |L= large (4+ days)
-
Conditional triggers (evaluate at trigger time) —
Difficulty: 1 | Effort: SAlready implemented. The
evaluateTriggerCondition()method evaluates a per-triggerconditionfield using the full condition system. Every trigger handler already calls this. May benefit from better documentation and MCP tool schema updates to make the feature more discoverable. -
Cron/periodic triggers —
Difficulty: 1 | Effort: SAlready implemented. The
periodictrigger type internally generates cron expressions viaschedule(). To expose raw cron support, add acronsub-type that passes user-provided cron expressions directly. Hubitat accepts standard 7-field cron expressions. User-provided strings would need validation. -
Endpoint/webhook triggers —
Difficulty: 3 | Effort: MFeasible. Add a single dispatcher endpoint (e.g.,
/mcp/webhook?ruleKey=<key>) in the parent app'smappingsblock. The parent looks up the child rule by key and callsexecuteRule("webhook", webhookEvt)with the request body/params as event data. Per-rule dynamic paths are not possible sincemappingsare compile-time, but a shared endpoint with a query parameter works. The OAuth access token already protects the path.Implementation plan:
- Add
GET/POST /webhookmapping to parent app - Add
webhooktrigger type to child app'ssubscribeToTriggers() - Parent dispatches to matching child rule via key lookup
- Package request body, headers, and query params into pseudo-event for variable substitution
- Add
-
Hub variable change triggers — Closed differently than originally planned (issue #92).
Originally scoped as a
variable_changetrigger type for the legacy MCP rule engine. With the legacy engine now frozen and native Rule Machine providing variable triggers natively, the equivalent capability for MCP/AI consumers ships as observation tooling instead: the parent app subscribes tovariable:*location events on install/update, buffers the last 200 changes inatomicState.variableHistory, and exposes them viahub_list_variable_changes. TherenameVariable(oldName, newName)callback keeps the buffer consistent across UI renames. See PR closing #92 / #96. -
System start trigger —
Difficulty: 2 | Effort: SFeasible. Hubitat supports
subscribe(location, "systemStart", handler). Add asystem_starttrigger type. After hub reboot, the app restores,initialize()→subscribeToTriggers()runs, and the systemStart event fires the rule. Minor edge case: the event may fire before all apps finish restoring — needs testing on hardware.Implementation plan:
- Add
system_starttrigger type to child app - Subscribe to
location "systemStart"event insubscribeToTriggers() - Handler calls
executeRule("system_start")
- Add
-
Date range triggers —
Difficulty: 2 | Effort: SFeasible. Better implemented as a condition than a trigger. A
date_rangecondition type checksnew Date()against start/end dates, following the same pattern astime_rangeanddays_of_week. If implemented as a trigger, useschedule()to fire at range start.java.util.Calendaris available in the sandbox.Implementation plan:
- Add
date_rangecondition type toevaluateCondition() - Accept
startDateandendDate(ISO format) - Optionally add a
date_rangetrigger that schedules a one-time event at range start
- Add
-
Required Expressions (rule gates) with in-flight action cancellation —
Difficulty: 4 | Effort: LFeasible but complex. A "gate" is a condition continuously monitored during action execution. If it becomes false, in-flight delayed actions are cancelled. The existing
cancelledDelayIdsmechanism provides the foundation for cancellation. The gate needs its ownsubscribe()calls for relevant devices, with a handler that checks the gate condition and marks all pending delay IDs as cancelled. This is the most architecturally complex enhancement — it requires asynchronous monitoring during delayed action chains and careful state management.Implementation plan:
- Add
requiredExpressionsarray to rule structure alongsideconditions - Subscribe to gate-relevant device events separately
- Gate handler evaluates conditions and cancels all active delays if false
- Track all active delay IDs per rule in
atomicState.activeDelayIds - Add cleanup logic when rule execution completes normally
- Add
-
Full boolean expression builder (AND/OR/XOR/NOT with nesting) —
Difficulty: 4 | Effort: LFeasible. Replace the flat conditions array +
conditionLogictoggle with a recursive tree structure:{operator: "AND", operands: [...]}. RewriteevaluateConditions()as a tree walker. NOT wraps a single operand; XOR checks exactly one true. The existingevaluateCondition()for leaf nodes is already modular. Main challenge is MCP tool ergonomics and backward compatibility — the flat array format would need migration logic.Implementation plan:
- Define tree data structure with
operatorandoperandsfields - Implement recursive
evaluateConditionTree()method - Support both legacy flat format and new tree format (migration path)
- Update
hub_create_custom_rule/hub_update_custom_ruletool schemas - Update
describeCondition()for recursive formatting
- Define tree data structure with
-
Private Boolean per rule —
Difficulty: 2 | Effort: SFeasible. Already achievable via
atomicState.localVariables. Add a dedicatedset_rule_booleanaction type that callsparent.setRuleBooleanOnChild(targetRuleId, boolName, value), which updates the target child's local variables. Add arule_booleancondition type that reads a specific rule's local variables. The parent mediates cross-rule access viagetChildAppById().Implementation plan:
- Add
set_rule_booleanaction type to child app - Add
rule_booleancondition type toevaluateCondition() - Add
setRuleBooleanOnChild()method to parent app - Update MCP tool schemas
- Add
-
Fade dimmer over time —
Difficulty: 3 | Effort: MFeasible. Many dimmers natively support
setLevel(level, duration)which the codebase already uses. For a software fade: calculate step size and interval, then userunIn()with incremental steps. Example: 0→100 over 60s = 5% every 3 seconds (20 steps). Limit steps to ~20–30 to avoid overwhelming the hub's scheduler. Share the stepping utility with color temperature fading and ramp actions.Implementation plan:
- Add
fade_dimmeraction type withstartLevel,endLevel,durationSeconds - Implement
rampValue()utility for steppedrunIn()scheduling - Use
[overwrite: false]onrunIn()for non-conflicting callbacks - Fall back to native
setLevel(level, duration)when device supports it
- Add
-
Change color temperature over time —
Difficulty: 3 | Effort: MFeasible. Same pattern as fade dimmer. Use the shared
rampValue()utility withsetColorTemperature()calls at each step. Some devices supportsetColorTemperature(temp, level, duration)natively. Temperature ranges vary by device (typically 2000K–6500K).Implementation plan:
- Add
fade_color_temperatureaction type withstartTemp,endTemp,durationSeconds - Reuse the
rampValue()utility from fade dimmer - Handle integer rounding for temperature steps
- Add
-
Per-mode actions —
Difficulty: 2 | Effort: SFeasible. Add a
per_modeaction type containing a map of mode-name to action-list. At execution time, checklocation.modeand execute the matching action list. Structurally similar toif_then_elsebut with multiple branches. The existingif_then_elsewith amodecondition already provides this capability less ergonomically.Implementation plan:
- Add
per_modeaction type withmodeActionsmap - Look up
location.modeat execution time - Execute matching action list using existing
executeAction()infrastructure
- Add
-
Wait for Event / Wait for Expression —
Difficulty: 5 | Effort: LPartially feasible. Requires pausing execution mid-stream until an external event occurs. In Hubitat's single-threaded model, there is no blocking wait. Implementation: save current execution state (action index, context) to
atomicState, subscribe to the target event, return from execution, then resume when the event fires — similar to thedelaypattern but event-triggered. A mandatory timeout is essential. Race conditions between the wait subscription and other triggers are possible.Implementation plan:
- Add
wait_for_eventaction type with target device/attribute and mandatory timeout - Save execution state to
atomicState(same pattern asdelay) - Create temporary subscription for the target event
- On event fire or timeout, resume execution from saved state
- Clean up subscription after resume
- Add
-
Repeat While / Repeat Until —
Difficulty: 4 | Effort: LPartially feasible. Extend the existing
repeataction to evaluate conditions before/after each iteration. Critical safety: hard cap at 100 iterations (matching existingrepeatcap), mandatory max iteration parameter, and loop guard protection. If the loop body contains delays, each iteration needs the save-state/resume pattern, making implementation extremely complex. Recommend supporting synchronous loops only (no delays in body) initially.Implementation plan:
- Add
repeat_whileandrepeat_untilaction types - Evaluate condition via
evaluateCondition()each iteration - Enforce mandatory
maxIterationscap (≤100) - Phase 1: synchronous loops only (no delay actions in body)
- Phase 2 (future): delay-compatible loops with state persistence
- Add
-
Rule-to-rule control —
Difficulty: 2 | Effort: MFeasible. Add
enable_rule,disable_rule, andtrigger_ruleaction types. Child calls parent, which looks up the target child and invokes existingenableRule()/disableRule()/executeRule(). To prevent cross-rule ping-pong loops, pass a "trigger chain depth" counter and refuse execution beyond depth 5.Implementation plan:
- Add
enable_rule,disable_rule,trigger_ruleaction types - Implement
triggerChildRule(targetRuleId, depth)in parent - Add depth counter to
executeRule()to prevent cascading loops - Validate target rule exists via
getChildAppById()
- Add
-
File write/append/delete —
Difficulty: 2 | Effort: SFeasible. The parent already has
uploadHubFile()/downloadHubFile()wrappers. New action typesfile_write,file_append,file_deletecall parent methods. Append does a read-modify-write cycle (not atomic). Requires the Write master gate. Variable substitution in content enables dynamic log files.Implementation plan:
- Add
file_write,file_append,file_deleteaction types - Child calls
parent.writeFileFromRule(fileName, content, mode) - Validate filenames against
[A-Za-z0-9._-]pattern - Apply the Write master gate for safety
- Add
-
Music/siren control —
Difficulty: 2 | Effort: SFeasible. Convenience wrappers around existing
device_command. Hubitat hascapability.musicPlayer(play, pause, stop, setVolume, playTrack) andcapability.alarm(both, siren, strobe, off). The existingspeakaction demonstrates the pattern for TTS with volume. These would be ergonomic action types with built-in validation for the right capabilities.Implementation plan:
- Add
play_musicaction type with device, command, volume, track params - Add
activate_sirenaction type with device, mode (siren/strobe/both/off) - Validate device has required capability before execution
- Add
-
Custom Action (any capability + command) —
Difficulty: 1 | Effort: SAlready implemented. The existing
device_commandaction type accepts any device ID, command, and parameters via dynamic invocation (device."${command}"(*params)). This is the "any capability + command" feature. -
Disable/Enable a device —
Difficulty: 1 | Effort: SFeasible (partially done). The
hub_update_deviceMCP tool already supports theenabledproperty via the internal/device/disableendpoint. A newset_device_enabledrule action type wraps the same call. Requires the Write master. Should warn if the target device is used in active rule triggers.Implementation plan:
- Add
set_device_enabledaction type withdeviceIdandenabledboolean - Call
parent.setDeviceEnabled(deviceId, enabled) - Check if device is used in any rule triggers and warn
- Add
-
Ramp actions (continuous raise/lower) —
Difficulty: 3 | Effort: MPartially feasible. Same software-stepping pattern as fade dimmer/color temp. A generic
rampaction targets any numeric attribute. True continuous raise/lower (like holding a physical dimmer button) usesstartLevelChange(direction)/stopLevelChange()but can't be time-bounded from software. Shares therampValue()utility with fade actions.Implementation plan:
- Add
rampaction type with device, attribute, start, end, duration, steps - Reuse
rampValue()utility from fade actions - For devices with
startLevelChange/stopLevelChange, offer a hardware ramp option
- Add
-
Ping IP address (ICMP) — folded into
hub_get_device_health(issue #91).Rather than a standalone
ping_hosttool, ICMP ping was integrated into the existinghub_get_device_healthtool viapingHosts(max 5 IPv4) andpingCount(1–5) parameters. Each host is pinged throughhubitat.helper.NetworkUtils.ping()and reported underpingResultswithreachable,rttAvg,rttMin,rttMax,packetsTransmitted,packetsReceived,packetLoss. The custom MCP rule engine is legacy-only, so no rule-action half was added. -
HTTP reachability check —
Difficulty: 3 | Effort: MFeasible — complementary to ICMP ping above. An HTTP GET against a target URL still has value for hosts that don't respond to ICMP or when you need to verify HTTP-layer health, not just network reachability. Keep as a secondary action type alongside the native ping.
Ordering caveat.
asynchttpGet()is non-blocking: any actions afterhttp_checkin a rule's action list would otherwise run before the response arrived and before the result variables (reachable,statusCode,responseTimeMs) were populated. A correct implementation must save rule execution state on the dispatch and resume from the async callback — the same save-state/resume pattern used by the existingdelayaction. Mandatory timeout required so a stalled request doesn't leave the rule suspended.Implementation plan:
- Add
http_checkaction type with target URL, optional expected status code, and mandatory timeout - Dispatch via
asynchttpGet()(non-blocking, does not tie up the hub executor) - Save action-index + context to
atomicStatebefore dispatch, mirroring thedelayaction's resume pattern - In the async callback, populate result fields (
reachable,statusCode,responseTimeMs) into rule/local variables and resume action execution from the saved index - Enforce a hard timeout that resumes with
reachable=falseif no response arrives in time - Synchronous
httpGet()is not an acceptable shortcut — it blocks the hub event executor and can stall other apps
- Add
-
Hub Variable Connectors (expose as device attributes) —
Difficulty: 4 | Effort: LPartially feasible. Hubitat's built-in Variable Connector feature (firmware ≥ 2.3.4) already handles hub variables. For MCP rule engine variables, exposing them as device attributes requires: (1) a custom virtual device driver, (2) the parent creating an instance via
addChildDevice(), (3) the parent updating attributes viasendEvent()on variable writes. The custom driver adds a third file to the project and install complexity.Implementation plan:
- Create
MCP Variable Connectordriver (new Groovy file) - Parent auto-creates a child device on first variable write (or on demand)
- Extend
setRuleVariable()to alsosendEvent()on the connector device - Add driver to HPM package manifest
- Document that hub variables should use Hubitat's built-in connectors instead
- Create
-
[~] Variable change events — Hub-variable half closed under issue #92; MCP-rule-engine half deferred (legacy engine frozen).
Hub-variable change observation now ships via
hub_list_variable_changes(see "Hub variable change triggers" above). The MCP-rule-engine half — sending aruleVariableChangedlocation event whensetRuleVariable()writes — would require new code in the legacy child app, which is no longer being extended. New rule-variable consumers should use native Rule Machine, which has variable triggers built in. -
Local variable triggers —
Difficulty: 2 | Effort: SFeasible. After
set_local_variableorvariable_mathmodifies a local variable, check for matchinglocal_variable_changetriggers and re-trigger asynchronously viarunIn(0, handler). High risk of infinite loops if a rule triggers itself — recommend only firing from external changes (another rule setting this rule's local variable via rule-to-rule control). The loop guard provides a safety net.Implementation plan:
- Add
local_variable_changetrigger type - After local variable modification, schedule async re-evaluation
- Add
triggerSourcetracking to prevent self-triggering loops - Rely on loop guard as safety net
- Add
Philosophy: prefer native Hubitat apps. The MCP server was built to complement Hubitat, not replace it. These native apps (Room Lighting, Mode Manager, Button Controller, etc.) are well-maintained, have proper UIs, and are battle-tested. The MCP can already interact with the effects of these apps — it can read/set modes, control devices, trigger on device events, and see virtual devices they create.
The AI assistant is the wizard. Rather than building dedicated wizard tools that generate MCP rules to replicate what native apps already do, the AI can compose rules on the fly using existing
hub_create_custom_ruleand the full rule engine. Dedicated MCP tooling for these patterns is low priority and would only be implemented if the MCP genuinely cannot interact with the native app's functionality in some way. Each item will be reviewed on a case-by-case basis.
-
Room Lighting (room-centric lighting with vacancy mode) —
Low priorityNative app preferred. Hubitat's built-in Room Lighting app handles this well. The MCP can already control all the same devices, trigger on motion events, and use
if_then_else/delay/cancel_delayedto build equivalent logic viahub_create_custom_ruleif needed. No dedicated MCP tool required unless a gap is identified where MCP cannot interact with Room Lighting's behavior. -
Zone Motion Controller (multi-sensor zones) —
Low priorityNative app preferred. Hubitat's built-in Zone Motion Controller creates a virtual motion device that aggregates multiple sensors. If the user adds this virtual device to MCP's selected devices, MCP can already see and trigger on it. The AI can also replicate the logic using
create_virtual_device+hub_create_custom_rulewith multi-device triggers if needed. Only implement if MCP cannot adequately interact with the native app's output device. -
Mode Manager (automated mode changes) —
Low priorityNative app preferred. Hubitat's built-in Mode Manager handles time-based and presence-based mode changes. The MCP can already read/set modes via
hub_list_modes/hub_set_mode, trigger onmode_change, and build time/presence-triggered rules that callset_mode. No dedicated tool needed unless a specific interaction gap is found. -
Button Controller (streamlined button-to-action mapping) —
Low priorityNative app preferred. Hubitat's built-in Button Controller handles this natively. The MCP rule engine already has
button_eventtriggers with full support for button numbers (1–20) and action types (pushed/held/doubleTapped/released). The AI can create these rules directly viahub_create_custom_rule. No dedicated tool needed. -
Thermostat Scheduler (schedule-based setpoints) —
Low priorityNative app preferred. Hubitat's built-in Thermostat Scheduler handles schedule-based setpoints. The MCP rule engine already has
timetriggers,set_thermostatactions,modeanddays_of_weekconditions — the AI can compose schedule rules directly. No dedicated tool needed unless MCP cannot interact with the native scheduler's effects. -
Lock Code Manager —
Low priority — review neededMay warrant a dedicated tool. Hubitat's built-in Lock Code Manager handles code management via a UI. The MCP can already send lock code commands via
hub_call_device_command(setCode,deleteCode) and readlockCodes/lastCodeNameattributes, so basic interaction is possible today. However, the native app's internal code inventory and temporary code scheduling are not directly accessible. A dedicated tool may add value for programmatic code management if the native app's outputs prove insufficient. Needs case-by-case review. -
[ ] Groups and Scenes (Zigbee group messaging)—Not feasibleNot feasible. The
zigbeeobject andsendHubCommand()withProtocol.ZIGBEEare only available in drivers, not apps. The MCP server is an app and cannot send raw Zigbee commands or manage Zigbee group IDs. Zigbee group management is handled by closed-source platform internals with no documented HTTP API endpoints.Alternatives already available:
- Leverage built-in Groups and Scenes app: Guide users to create Zigbee groups via the built-in app, then control the resulting group activator device through MCP's
hub_call_device_command(group activator devices are regular switch/dimmer devices that MCP can already control) - Software-level group control: Create rules that send commands to multiple devices sequentially — already possible via multi-device rules or
device_commandactions - Scene capture/restore: The existing
capture_state/restore_stateactions provide scene-like functionality across multiple devices
- Leverage built-in Groups and Scenes app: Guide users to create Zigbee groups via the built-in app, then control the resulting group activator device through MCP's
-
Search HPM repositories by keyword —
Difficulty: 2 | Effort: SFeasible. HPM uses a public GraphQL API at
https://hubitatpackagemanager.azurewebsites.net/graphql. Asearch_hpm_packagestool sends a GraphQL query viahttpPost()and returns matching packages with name, description, author, and manifest URL. The master repository list athttps://raw.githubusercontent.com/HubitatCommunity/hubitat-packagerepositories/master/repositories.jsonprovides offline browsing.Implementation plan:
- Create
search_hpm_packagesMCP tool - Send GraphQL
Searchquery to the Azure endpoint - Parse and return results with pagination for large result sets
- Cache results in
stateto reduce API calls
- Create
-
Install/uninstall packages via HPM —
Difficulty: 4 | Effort: LPartially feasible. HPM has no programmatic API — it's purely UI-driven. Bypass approach: fetch the package manifest JSON, download each app/driver source, and install via existing
hub_create_app/hub_create_drivertools. However, packages installed this way won't appear in HPM's "Installed" list, creating a fragmented experience. Uninstall requires removing running app instances (not just code) via poorly documented/installedapp/endpoints.Implementation plan:
- Create
install_packageMCP tool using the bypass approach - Fetch manifest → download sources → install via existing tools
- Track installed packages in File Manager for update checking
- Document the limitation: HPM won't know about these installations
- For uninstall:
hub_delete_item(type: app|driver) for code, investigate/installedapp/disablefor instances
- Create
-
Check for updates across installed packages —
Difficulty: 3 | Effort: MPartially feasible. For MCP-tracked packages (from the install tool above): fetch each manifest URL and compare versions -- same pattern as the existing
checkForUpdate()for the MCP server itself. For HPM-managed packages: HPM's installed-package state IS readable via hub-internal endpoints (/installedapp/statusJson/+/hub2/appsList), as demonstrated byhub_list_hpm_packages(includeDrift=true; drift nests under adriftkey). Acheck_package_updatestool could cross-reference HPM's recorded manifest URLs and versions against live manifest files to detect available updates.Implementation plan:
- Create
check_package_updatesMCP tool - Read MCP-tracked package list from File Manager (for MCP-installed packages)
- For HPM-managed packages: use
_hpmFetchManifeststo get recorded manifest URLs and versions; fetch each live URL and compare version fields - Return list of packages with available updates
- Handle fetch failures gracefully (GitHub rate limiting, network issues)
- Create
-
Search for official integrations not yet enabled —
Difficulty: 3 | Effort: MPartially feasible. No documented endpoint for enumerating available built-in apps. The
hub_list_appstool returns user-installed app types, not built-in ones. Practical approach: maintain a hardcoded catalog of known official integrations (Hue Bridge, Sonos, Alexa, Google Home, HomeKit, etc.) and check which ones have running instances. The list only changes with firmware updates.Implementation plan:
- Create
list_available_integrationsMCP tool - Maintain hardcoded catalog of official integrations with descriptions
- Check installed app instances to identify which are already enabled
- Return available-but-not-enabled integrations with setup guidance
- Update catalog with each MCP server release
- Create
-
Discover community apps/drivers from GitHub, forums, etc. —
Difficulty: 3 | Effort: MPartially feasible. Primary mechanism: HPM repository search (above). GitHub API search (
https://api.github.com/search/repositories?q=hubitat+driver) works but is rate-limited to 10 req/min unauthenticated. The Hubitat community forum has no search API. A curated list in File Manager is a practical fallback.Implementation plan:
- Create
search_community_packagesMCP tool - Primary: search HPM repositories via GraphQL
- Secondary: optional GitHub API search with rate-limit handling
- Return combined results with source attribution
- Optionally maintain a curated popular-packages list in File Manager
- Create
- Create, modify, delete dashboards programmatically —
Difficulty: 4 | Effort: LFeasible via internal HTTP endpoints (needs empirical testing). Hubitat's dashboard system uses a parent-child app pattern: "Hubitat Dashboard" is the parent, each individual dashboard is a child app. Deep research uncovered the
/installedapp/createchild/internal endpoint that the web UI uses.Key discoveries:
- Dashboard listing:
GET /dashboard/all?pinToken=<token>or viaGET /hub2/appsListfiltering for dashboard children - Dashboard creation:
GET /installedapp/createchild/hubitat/Dashboard/parent/{dashboardParentAppId}— creates a new child dashboard under the parent app. Returns a redirect to/installedapp/configure/{newChildId} - Dashboard layout read:
GET /apps/api/<parentAppId>/dashboard/<childAppId>/layout?access_token=<token> - Dashboard layout write:
POST /apps/api/<parentAppId>/dashboard/<childAppId>/layoutwith Bearer token auth - Dashboard deletion: Likely
POST /installedapp/configure/{childId}with remove action, orGET /installedapp/remove/{childId}(exact endpoint needs testing) - Child app type: namespace
hubitat, nameDashboard(confirmed from error messages in community forums)
Important caveats:
addChildApp()in Groovy cannot create dashboard children from the MCP app (parent mismatch), so the HTTP endpoint approach is required- The
createchildendpoint returns an HTTP redirect (302), not JSON — need to extract new child ID from the Location header - Post-creation configuration (dashboard name, authorized devices) requires a separate POST to
/installedapp/configure/{id}with form-encoded data - The Dashboard parent app must already be installed on the hub
- The
pinTokenfor/dashboard/allneeds to be obtained from the Dashboard parent app or may not be required for local API calls - Firmware ≥ 2.3.9 introduced "Easy Dashboard" as an alternative — its internal structure may differ
- All endpoints are undocumented and may change across firmware versions
Implementation plan:
- Discover Dashboard parent app ID via
GET /hub2/appsList(filter by app name) - Create
create_dashboardtool: callGET /installedapp/createchild/hubitat/Dashboard/parent/{parentId}, extract new child ID from redirect - Configure new dashboard:
POST /installedapp/configure/{newId}with name and authorized devices - Create
list_dashboardstool via/dashboard/allor/hub2/appsList - Create
get_dashboard_layout/update_dashboard_layouttools for layout JSON read/write - Create
delete_dashboardtool: test/installedapp/remove/{childId}endpoint - Add Dashboard parent app ID and access token to MCP app preferences
- Requires the Write master gate for safety
- Phase 1: Implement list + read/modify layout (known-working endpoints)
- Phase 2: Implement create/delete (requires empirical testing on hub hardware)
- Dashboard listing:
Feasibility confirmed — creating/modifying RM rules is not possible (closed-source, undocumented format). However, controlling existing RM rules IS feasible via the
hubitat.helper.RMUtilsclass, which is available in the Hubitat app sandbox.
-
List all RM rules via
RMUtils.getRuleList()—Difficulty: 1 | Effort: SFeasible. Confirmed working.
RMUtils.getRuleList("5.0")returns RM 5.x rules;getRuleList()returns legacy rules. Returns a list suitable for enum options with rule IDs and names.Implementation plan:
- Add
import hubitat.helper.RMUtilsto parent app - Create
hub_list_rulesMCP tool - Call both
getRuleList("5.0")andgetRuleList()for full coverage - Handle the case where Rule Machine is not installed
- Add
-
Enable/disable RM rules —
Difficulty: 2 | Effort: SFeasible. Uses
RMUtils.sendAction(ruleIds, "pauseRule"/"resumeRule", app.label, "5.0"). "Pause" is equivalent to "disable" in RM terminology.Implementation plan:
- Create
control_rm_ruleMCP tool withactionparameter - Support actions:
pause(disable),resume(enable) - Validate rule ID exists via
getRuleList()first
- Create
-
Trigger RM rule actions via
RMUtils.sendAction()—Difficulty: 2 | Effort: SFeasible. Supported actions:
"runRuleAct"(execute actions, skip conditions),"runRule"(evaluate conditions then run),"stopRuleAct"(cancel delayed/repeating actions). Note:"runRule"not applicable to Rule 4.x+.Implementation plan:
- Add
run_actions,evaluate,stop_actionsto thecontrol_rm_ruletool - Map to RM action strings internally
- Document which actions work with which RM versions
- Add
-
Pause/resume RM rules —
Difficulty: 2 | Effort: SFeasible. Same mechanism as enable/disable above. Can be combined into the unified
control_rm_ruletool. -
Set RM Private Booleans —
Difficulty: 2 | Effort: SFeasible. Uses
RMUtils.sendAction(ruleIds, "setRuleBooleanTrue"/"setRuleBooleanFalse", app.label, "5.0"). Straightforward API.Implementation plan:
- Add
set_boolean_trueandset_boolean_falsetocontrol_rm_ruletool - Accept rule ID and boolean value, map to appropriate sendAction call
- Add
-
Hub variable bridge for cross-engine coordination —
Difficulty: 2 | Effort: SAlready ~90% implemented. The existing
hub_set_variable/hub_get_variabletools work with Hubitat's global connector variables viagetGlobalConnectorVariable()/setGlobalConnectorVariable(). These are the same variables Rule Machine reads/writes. Variables set via MCP are immediately visible to RM and vice versa. To formalize: document the convention that shared variables should use hub connector variables.
-
Event streaming / webhooks (real-time POST of device events) —
Difficulty: 3 | Effort: MFeasible. Subscribe to device events and
asynchttpPost()payloads to registered URLs. The rule engine already implementshttp_requestactions viahttpPost(). UseasynchttpPost(non-blocking) to avoid blocking the event queue during bursts. Rate limiting is important.Implementation plan:
- Create
configure_webhookMCP tool to register endpoint URLs and event filters - Store webhook configs in
state.webhookSubscriptions - Subscribe to relevant device events in
initialize() - Event handler checks filters, formats payload, and POSTs asynchronously
- Include rate limiting (configurable events per minute per webhook)
- Payload format:
{deviceId, label, attribute, value, timestamp, description}
- Create
-
[ ] MQTT client (bridge to Node-RED, Home Assistant, etc.)—Difficulty: 4 | Effort: LNot directly feasible from an app. Hubitat's
interfaces.mqttAPI is only available in drivers, not apps. The Groovy sandbox also doesn't allowjava.net.Socketor arbitrary Java imports for a custom MQTT implementation.Alternative: Companion driver approach (added below)
-
MQTT via companion driver (alternative to direct MQTT) —
Difficulty: 4 | Effort: LFeasible via workaround. Create a custom
MCP MQTT Bridge Driverthat usesinterfaces.mqttto connect to a broker. The MCP app creates this driver as a child device viaaddChildDevice(). Communication flows through device commands (app→driver:publish(topic, message)) and events (driver→app:sendEvent()+subscribe()). This adds a third Groovy file to the project.Implementation plan:
- Create
MCP MQTT Bridgedriver withinterfaces.mqtt - Driver exposes commands:
connect,disconnect,publish,subscribe - Driver fires events on incoming messages and connection status
- Parent app creates driver instance via
addChildDevice() - Add
mqtt_publish,mqtt_subscribe,mqtt_statusMCP tools - Add driver to HPM package manifest
Alternative (simpler): Use the existing
http_requestaction to bridge via HTTP-to-MQTT gateways (Node-RED HTTP-in nodes, HiveMQ REST API, EMQX REST API). - Create
These patterns don't require new MCP tools — the AI assistant can already compose them using existing
hub_create_custom_rule,hub_set_variable,create_virtual_device, and other tools. They're documented here as reference patterns showing what's achievable today with the current rule engine. No dedicated wizard tools are planned unless a specific gap is identified.
-
Occupancy / room state machine —
No new tools neededAlready achievable. The AI can compose this using existing primitives: a hub variable
roomState_<room>holds state (vacant/occupied/engaged/checking).device_eventtriggers on motion/contact sensors feed intoif_then_elsechains withset_variableactions for state transitions. Duration-based triggers handle timeouts. Other rules check room state viavariableconditions. No dedicated tool required — the AI can build this pattern on request usinghub_create_custom_ruleandhub_set_variable. -
Presence-based automation (first-to-arrive, last-to-leave) —
No new tools neededAlready achievable. The AI can compose this: a hub variable
homeCounttracks present people.device_eventtriggers on presence sensors increment/decrement viavariable_math. Rules withvariableconditions fire whenhomeCounttransitions 0→1 (first arrive) or 1→0 (last leave). All building blocks exist today. -
Weather-based triggers —
No new tools neededAlready achievable with weather device drivers. Many Hubitat users have weather drivers installed (OpenWeatherMap, Weather Underground, etc.) that expose weather data as device attributes. If the user selects the weather device in MCP, it's already triggerable via
device_eventtriggers anddevice_stateconditions — zero code changes needed. For users without a weather driver, the AI could create aperiodicrule withhttp_requestactions to poll a weather API and store results in hub variables. -
Vacation mode (random light cycling, auto-lock, energy savings) —
No new tools neededAlready achievable. The AI can compose this using existing primitives:
mode_changetrigger →capture_state+ lock commands + thermostat setpoints. Aperiodictrigger withmodecondition cycles random lights (the rule engine hasrepeatactions anddelaywith variable offsets). Mode return triggersrestore_state.new Random()is available in the sandbox for randomized timing. All building blocks exist today.
-
Device health watchdog —
Difficulty: 2 | Effort: SFeasible. The existing
hub_get_device_healthtool is on-demand only. Enhancement: add a scheduled background check (every 4–6 hours) that proactively detects stale/offline devices and low batteries. Push alerts via notification devices. Write results to a CSV for trend analysis. Fire amcpDeviceHealthAlertlocation event for rule integration.Implementation plan:
- Add
schedule("0 0 */6 ? * *", "runHealthWatchdog")to parent app - Reuse
toolDeviceHealthCheck()logic - Add battery monitoring: check
device.currentValue("battery")for low levels - Send notifications for new stale/low-battery devices
- Fire
sendLocationEvent(name: "mcpDeviceHealthAlert")for rule integration - Log to CSV for trend tracking
- Add
-
Z-Wave ghost device detection —
Difficulty: 3 | Effort: MPartially feasible (detection only). Fetch Z-Wave node table via
/hub/zwaveDetails/json, cross-reference with the device list, and identify nodes with no matching device or with failed states. Automated removal is not possible — ghost removal requires the hub's web UI Z-Wave Details page.Implementation plan:
- Create
detect_zwave_ghostsMCP tool (requires the Read master) - Fetch Z-Wave node table and device list
- Cross-reference: nodes with no deviceId or failed states are ghosts
- Return report with ghost nodes and recommended manual actions
- Link to hub UI Z-Wave Details page for remediation
- Create
-
Event history / analytics —
Difficulty: 3 | Effort: M–LPartially feasible. The 7-day limit on
eventsSince()is a platform constraint. For longer history: add a background scheduler that periodically samples device states and writes to CSV files in File Manager. For analytics: compute event frequency, state duration percentages, min/max/avg for numeric attributes from available history data. Cross-device correlation is computationally expensive in the sandbox.Implementation plan:
- Create
analyze_device_historyMCP tool for analytics on existing 7-day data - Add scheduled CSV logging for long-term device state sampling
- Compute statistics: event frequency, state duration %, numeric min/max/avg
- Return structured analytics per device
- Note: computation-heavy analytics may time out with many devices
- Create
-
Hub performance trend monitoring —
Difficulty: 1 | Effort: SMostly already implemented. The
hub_get_metricstool records snapshots to CSV, maintains a 500-point rolling window, returns configurable trend points, and includes threshold warnings. Incremental enhancement: add scheduled periodic sampling (every 4 hours) instead of only recording when the AI calls the tool. Add trend direction analysis (rate of change, declining memory detection).Implementation plan (incremental):
- Add
schedule("0 0 */4 ? * *", "recordPerformanceSnapshot")to parent - Add trend analysis: compare latest N points for direction/rate of change
- Alert on sustained declining trends (not just current thresholds)
- Add
-
Pushover integration with priority levels —
Difficulty: 2 | Effort: MFeasible. Simple
httpPost()tohttps://api.pushover.net/1/messages.json. The Hubitat ecosystem already has a built-in Pushover driver, but a direct API integration gives more control (priority levels -2 to 2, sounds, supplementary URL, emergency retry/expire).Implementation plan:
- Add Pushover API key and User key to app preferences
- Create
send_pushoverMCP tool with message, title, priority, sound params httpPost()to Pushover API with form-encoded body- For emergency priority (2), include
retryandexpireparameters - Add as a notification channel for rule actions
-
Email notifications via SendGrid —
Difficulty: 2 | Effort: MFeasible. JSON POST to
https://api.sendgrid.com/v3/mail/sendwith Bearer token auth. The existing code already does JSON POST with custom headers (room save code usesrequestContentType: "application/json").Implementation plan:
- Add SendGrid API key, sender email, default recipient to app preferences
- Create
send_emailMCP tool with to, subject, body, optional html params httpPost()to SendGrid v3 API with JSON body and Bearer auth header- Add as a notification channel for rule actions
-
Rate limiting / throttling —
Difficulty: 2 | Effort: SFeasible. Pure in-app logic using
state/atomicStateandnow(). Store timestamps of recent notifications per channel. Check cooldown before sending. Follow the existing loop guard pattern for sliding-window rate limiting. Configurable per-channel cooldown and max-per-hour limits.Implementation plan:
- Add
state.notificationHistorytracking per-channel timestamps - Check cooldown before each notification send
- Add configurable thresholds in app preferences
- Return throttle status in tool response when rate-limited
- Add
-
Notification routing by severity —
Difficulty: 3 | Effort: MFeasible. Define severity levels (info/warning/critical/emergency). Map each severity to notification channels in app preferences. Dispatch to appropriate channels. Depends on Pushover and SendGrid being implemented first.
Implementation plan:
- Define severity levels: info, warning, critical, emergency
- Add severity-to-channel routing in app preferences
- Create
send_alertMCP tool with message and severity params - Routing logic reads config and dispatches to matching channels
- Each channel (Pushover, SendGrid, hub notification device) is a separate method
-
Standalone virtual device creation (independent of MCP app) —
Difficulty: 2 | Effort: S–MFeasible but unverified. The hub internal API
POST /device/savemay support device creation withid=""(Grails convention). The created device would appear in the regular Devices section and persist even if MCP is uninstalled. However: this endpoint is documented for updates, not creation — needs empirical testing. Built-in driver type IDs may not be discoverable via/hub2/userDeviceTypes.Implementation plan:
- Test
/device/savewithid=""on actual hub hardware - Discover built-in driver type IDs for Virtual Switch, Virtual Dimmer, etc.
- Create
create_standalone_deviceMCP tool (Write master required) - Note: device won't auto-appear in MCP's device list without user selection
- Fallback: use existing
create_virtual_devicewithisComponent: false
- Test
-
Scene management (create/modify beyond activate_scene) —
Difficulty: 3 | Effort: MPartially feasible. Native Groups and Scenes CRUD is not possible (no API). However, the existing
capture_state/restore_statesystem is already a de facto scene manager. Enhancement: wrap with scene-oriented terminology —create_scene(capture),list_scenes(list captures),activate_scene(restore),delete_scene(delete capture). Extend captured attributes beyond switch/level/color to include fan speed, shade position, thermostat setpoints.Implementation plan:
- Create scene alias tools wrapping existing captured state tools
- Extend
capture_stateto save additional attributes (fan speed, shade position, thermostat) - Add
modify_scenetool that re-captures specific devices in an existing scene - Document that these are MCP-managed scenes, not native Hubitat scenes
-
Energy monitoring dashboard —
Difficulty: 3 | Effort: MFeasible. Create a
get_energy_summarytool that aggregatespowerandenergyattributes across all PowerMeter/EnergyMeter capable devices. Add scheduled CSV logging for trend data. "Dashboard" is a JSON summary in MCP context (no UI rendering), but could optionally write an HTML file to File Manager accessible athttp://<HUB_IP>/local/energy-dashboard.html.Implementation plan:
- Create
get_energy_summaryMCP tool - Find all devices with PowerMeter/EnergyMeter capabilities
- Aggregate current power (W) and cumulative energy (kWh)
- Return per-device breakdown and totals
- Add scheduled CSV logger for energy trend data
- Optionally: generate static HTML file for visual dashboard
- Create
-
Scheduled automated reports —
Difficulty: 3 | Effort: MFeasible. Scheduled via
schedule()with cron expressions. Reports aggregate data from existing monitoring tools (device health, hub performance, rule activity). Save to File Manager as JSON. Push summary via notification devices. For email delivery, use SendGrid integration (if implemented) orhttpPost()to external services.Implementation plan:
- Add
configure_report_scheduleMCP tool - Define report templates: hub health, device status, rule activity, energy
- Schedule via
schedule()with user-configured cron expression - Aggregate data from existing tool methods
- Save full report to File Manager as JSON
- Push summary notification via configured channels
- Add
-
[ ] Device pairing assistance (Z-Wave, Zigbee, cloud)—Difficulty: 4 | Effort: LNot feasible for active pairing. Device pairing (Z-Wave inclusion, Zigbee pairing) is an interactive, real-time, radio-level operation. The hub's Z-Wave/Zigbee inclusion mode endpoints are undocumented and pairing is a multi-step, timing-sensitive process incompatible with MCP's request-response model. Cloud integrations require OAuth UI flows.
Alternative: Pairing guidance tool (added below)
-
Device pairing guidance (alternative to active pairing) —
Difficulty: 2 | Effort: SFeasible. A
guide_device_pairingtool that provides step-by-step textual instructions for using the Hubitat web UI to pair devices. After pairing, the AI can help configure the device (driver selection, room assignment, label, preferences) using existing MCP tools likehub_update_device,hub_call_device_command, and room management tools.Implementation plan:
- Create
guide_device_pairingMCP tool - Accept device type (Z-Wave, Zigbee, cloud) and optional model info
- Return step-by-step instructions with direct links to hub UI pages
- After pairing, offer to configure via existing MCP tools
- Create
The following items have been determined to be not achievable due to platform constraints. They are listed here with explanations and the alternatives that have been added to the plan above.
-
Groups and Scenes (Zigbee group messaging)— Thezigbeeobject andsendHubCommand(Protocol.ZIGBEE)are driver-only APIs. No HTTP endpoint exists for Zigbee group management. → Use software group commands or the built-in Groups and Scenes app's activator devices viahub_call_device_command -
MQTT client (direct)—interfaces.mqttis driver-only. No raw TCP sockets in apps. → Companion driver approach added as alternative -
Device pairing assistance (active)— Radio inclusion is interactive and undocumented. MCP's request-response model can't handle multi-step pairing flows. → Pairing guidance tool added as alternative
Based on the ratio of user value to implementation effort. Excludes items that the AI can already compose using existing tools (occupancy, presence, vacation mode, weather, and native app equivalents like Mode Manager, Button Controller, etc.).
- Rule Machine Interoperability (list, control, trigger, booleans) — All use
RMUtils, implement as 1–2 tools - Native hub variable change triggers —
subscribe(location, "variable:<name>", handler)+addInUseGlobalVar()registration - ICMP ping — done; folded into
hub_get_device_healthviapingHosts/pingCount(issue #91) - Search HPM repositories — Public GraphQL API, immediate discovery value
- Rate limiting / throttling — Pure in-app logic, enables safer notifications
- System start trigger — Single
subscribe()call - Date range condition — Follows existing condition patterns
- Device health watchdog — Add scheduled task to existing tool
- Private Boolean per rule — Cross-rule coordination via parent mediation
- Disable/Enable a device action — Wraps existing
hub_update_devicecapability - File write/append/delete actions — Wraps existing parent file methods
- Music/siren control actions — Convenience wrappers around
device_command
- Webhook triggers — New endpoint + trigger type
- Event streaming / webhooks — Subscribe + async POST
- Pushover integration — Simple API integration
- Email via SendGrid — Simple API integration
- Fade dimmer / color temp — Shared ramp utility
- Rule-to-rule control — Parent mediation, existing methods
- Notification routing — Layer on top of Pushover/SendGrid
- Z-Wave ghost detection — Node table cross-reference
- Variable change events — Location event on variable write
- Per-mode actions — Multi-branch action type
- Boolean expression builder — Recursive tree evaluation + migration
- Required expressions / gates — Continuous monitoring architecture
- MQTT via companion driver — Third file, driver development
- Dashboard create/modify/delete — Internal endpoint approach, needs hub testing
- Scheduled reports — Aggregation + scheduling + delivery
- Energy monitoring — Aggregate power/energy across devices
- Scene management — Scene-oriented wrappers around captured state
- Wait for Event / Expression — Complex state persistence
- Repeat While / Until — Loop safety concerns
- Hub Variable Connectors — Custom driver dependency
- Install packages via HPM — HPM sync fragmentation
- Standalone virtual devices — API endpoint unverified
- Event history / analytics — Platform 7-day limit
- Room Lighting — Use native app; review if MCP can't interact with its effects
- Zone Motion Controller — Use native app; MCP can already see its virtual device
- Mode Manager — Use native app; MCP already reads/sets modes
- Button Controller — Use native app; MCP already has
button_eventtriggers - Thermostat Scheduler — Use native app; MCP already has
set_thermostatactions - Lock Code Manager — Use native app; review if
hub_call_device_commandproves insufficient
- v2.7.3 - fix: e2e-surfaced tool fixes (404 degrade, button-rule page, zigbee, watchdog jobs) + e2e runner/suite refactor. PRs: #298
- v2.7.2 - feat: rule-local variable write, list, and remove. PRs: #294
- v2.7.1 - feat: add hub_update_firmware to install pending hub firmware. PRs: #297
- v2.7.0 - feat: hub_manage_radio gateway — full Z-Wave/Zigbee/Matter radio control. PRs: #295
- v2.6.1 - fix: lean hub_update_mcp_settings description to restore flat tools/list budget; feat: bulk form for hub_create_variable + RM variable-source doc notes. PRs: #293, #287
- v2.6.0 - ci: stop the e2e required check from blocking docs-only PRs (also completes hub2-source inventory); docs: fix Claude Desktop MCP setup instructions (mcp-remote bridge); docs: choose the Claude Desktop bridge proxy by OS; fix: make outputSchema publishing opt-in (default off) so strict MCP clients work; feat: all-hub device scope, app enable/disable tool, and mesh topology (#257). PRs: #286, #288, #292, #291, #289
- v2.5.0 - refactor: route the variable-name enum pickers through the canonical reader; feat: add walkStep drive mode and pare hub_set_rule prose into the tool guide. PRs: #281, #280
- v2.4.0 - refactor: extract native Rule Machine + classic-app tools into McpNativeRulesLib. PRs: #279
- v2.3.7 - feat: prefer compiled rule state for hub_get_rule_health (RM + Visual Rules + classic apps). PRs: #276
- v2.3.6 - feat: add device-attribute and variable-math source modes to setVariable. PRs: #277
- v2.3.5 - fix: align replaceRequiredExpression result-contract docs with behavior + post-delete safety net. PRs: #278
- v2.3.4 - feat: add replaceRequiredExpression shortcut to hub_set_rule. PRs: #270
- v2.3.3 - fix: enum-shadowing Custom Attribute + no-value changed comparator (#195). PRs: #271
- v2.3.2 - ci: make the e2e bundle skip honest — verify/heal the hub, and re-target the restore at current main. PRs: #275
- v2.3.1 - ci: unify bundle delivery on the bundle-artifacts branch so e2e installs exactly what HPM serves. PRs: #274
- v2.3.0 - refactor: split every remaining tool domain into #include libraries with co-located per-tool metadata (#209); fix: rebuild-bundle pushes with the release deploy key + carry the rebuilt 18-library zip. PRs: #269, #273
- v2.2.2 - fix: VRB-first rule guidance; rebuild e2e restore on the HPM importUrl path. PRs: #268
- v2.2.1 - feat: per-app JSON reads, direct-alias app resolution, and hub_call_device_swap. PRs: #267
- v2.2.0 - docs: list string/decimal for runCommand parameter types; feat: add Visual Rules Builder tools to the rule machine gateway. PRs: #266, #265
- v2.1.7 - feat: friendly tool titles, descriptive overrides menu, and the full four-hint annotation surface. PRs: #264
- v2.1.6 - feat: update app/driver code via saveOrUpdateJson instead of legacy ajax/update. PRs: #263
- v2.1.5 - Wire device-relative compareToDevice to the real RM 5.1.8 control (isDev_). PRs: #262
- v2.1.4 - feat: register basic_rule, add Button Rule creation + walkStep to the native-app tool. PRs: #260
- v2.1.3 - feat: hub_update_package full HPM-repair deploy, top-level dev-mode tool. PRs: #261
- v2.1.2 - fix: enum-recognized Custom Attribute false-partial across all RM 5.1 wizard surfaces. PRs: #244
- v2.1.1 - chore: vendor classic-UI dynamicPage engine as RM wire-format reference; ci: speed up the watchdog e2e ~23% (bundle-only deploy, batched fixtures, deferred deletes); docs: point AGENTS.md at resources/hub2-source as the reverse-engineering reference; feat: surface pending hub firmware update + health alerts from /hub2/hubData. PRs: #253, #251, #255, #256
- v2.1.0 - ci: auto-run e2e on trusted fork PRs, gate other contributors; lease waits for a busy hub; ci: e2e dead-man watchdog v2 as a 2nd MCP server — drive deploy through it (kills self-update 504s); docs: standalone-watchdog e2e architecture + pull_request_target trigger gotcha; feat: modularize Rooms + bundle tools into #include libraries + bundle-management tools (#209). PRs: #246, #248, #249, #247
- v2.0.4 - feat: installAsUserApp install-commit fix + #include smoke test + e2e dead-man watchdog (#209). PRs: #243
- v2.0.3 - feat: hub_update_package dev tool — one-call app+library deploy at a git ref (#209). PRs: #242
- v2.0.2 - feat: add hub_list_libraries read tool. PRs: #241
- v2.0.1 - docs: hub firmware-update research notes + resilience plan; chore: HPM bundle smoke-test library for issue #209 modularization. PRs: #240, #239
- v2.0.0 - build(deps): bump the gradle-dependencies group with 2 updates (#223, @app/dependabot); refactor: hub_ rename + consolidation of MCP tool surface (issue #105 PR1A); docs: add issue #105 PR2 backend/server audit game plan; feat: split MCP tool gateways into read-only (hub_read_) and write-bearing (hub_manage_); feat: add outputSchema to every MCP tool (issue #105 PR1C); fix: PR2a — correctness & security fixes for the MCP server backend (issue #105); ci: add additive Groovy 2.5 Spock runtime lane (issues #227, #230); feat: PR2b/2c backend robustness, performance, and cleanup (issue #105); feat: universal Read/Write permission masters + per-tool/gateway overrides; fix: PR2-legacy — child-app bug fixes + state hygiene (issue #105); feat: split native-app CRUD into hub_set_rule (RM) + hub_set_native_app (generic) (#137). PRs: #223, #224, #226, #225, #229, #231, #232, #233, #235, #234, #236
- v1.4.5 - fix: complete RM 5.1 Periodic Schedule trigger support. PRs: #222
- v1.4.4 - chore: pin commons-collections to 3.2.2 (test-only deserialization fix); fix: compareToVariable on the RM condition walker + addTrigger partial-filter + compareToDevice guard. PRs: #221, #220
- v1.4.3 - docs: introducing security policy; Potential fix for code scanning alerts: Workflow does not contain permissions; fix: commit clearActions/replaceActions delete synchronously via full page-form submit (closes #172). PRs: #218, #219, #217
- v1.4.2 - feat: importUrl + installAsUserApp + triggerUpdated for app/driver/library install + update tools. PRs: #213
- v1.4.1 - Revert "refactor: flat-mode tool surface reduction (103 → 95 tools)". PRs: #216
- v1.4.0 - refactor: flat-mode tool surface reduction (103 → 95 tools). PRs: #208
- v1.3.15 - docs: codify MCP tool design rules in AGENTS.md + derivative docs (part of #105); docs: vendor hub2 Vue SPA source as reference resource; docs: add PR1 (issue #105 tool audit) game plan to docs/; feat: per-capability reveal walker fixes Required Expression & ifThen Broken Conditions (issue #195 Group A). PRs: #210, #211, #214, #203
- v1.3.14 - fix: prevent (and detect) IF/END-IF + Repeat structural imbalance (#178). PRs: #206
- v1.3.13 - fix: drop top-level anyOf from import_native_app input_schema (Haiku 4.5 compat). PRs: #205
- v1.3.12 - feat: addAction Set-Variable, runCommand variable parameters, Set-Mode modeName. PRs: #196
- v1.3.11 - feat: MCP readOnlyHint + destructiveHint annotations on every tool. PRs: #202
- v1.3.10 - fix(get_tool_guide): expose schema-referenced reference sections + add drift lint. PRs: #201
- v1.3.9 - fix: release-notes generator was picking issue refs instead of PR numbers. PRs: #200
- v1.3.8 - docs: add CONTRIBUTING.md and link it from styleguide; fix: end beta-status paragraph with bug-report guidance, not dev jargon; PR #181. PRs: #197, #199
- v1.3.7 - test: close issue #141 Section A Spock coverage gaps; ci(hub-e2e): deploy PR source to test hub before running tests; PR #169. PRs: #193, #192
- v1.3.6 - PR #187; PR #174
- v1.3.5 - feat: optimistic-lock + self-update guard for update_app_code / update_driver_code. PRs: #189
- v1.3.4 - feat: fold location event history into get_device_history. PRs: #188
- v1.3.3 - feat(manage_virtual_device): allow custom-driver instantiation via {namespace, name}. PRs: #168
- v1.3.2 - test: cut gradle test suite time ~87% via per-JVM compile cache + strict-mode CI matrix; feat: add identify-hub LED option to get_hub_info and device_health_check; feat(tools/list, manage_hpm): cursor pagination + R7 doc/spec follow-up. PRs: #184, #186, #180
- v1.3.1 - feat: rework generate_bug_report for issue templates, scoped logs, public-safe mode. PRs: #182
- v1.3.0 - chore(sandbox_lint): enforce tool-count consistency + sync doc drift; feat(manage_hpm): HPM read-only gateway — list_hpm_packages + get_hpm_drift. PRs: #165, #167
- v1.2.1 - feat(manage_app_driver_code): library management (install/update/delete/get_source). PRs: #164
- v1.2.0 - feat: add clone/export/import_native_app via Hubitat appCloner. PRs: #158
- v1.1.2 - docs(futureplans): align rule-tool names with PR #134 custom_ rename; feat(manage_app_driver_code): driver-code lifecycle improvements -- sourceFile + bulk + token-economy. PRs: #162, #163
- v1.1.1 - ci: consolidate post-merge automation into release workflow (closes race). PRs: #160
- v1.1.0 - feat(devices): add poll_until_attribute -- block-poll until attribute matches; PR #92. PRs: #157
- v1.0.5 - docs: correct AGENTS.md falsehoods and auto-sync CLAUDE.md; feat(get-hub-logs): server-side regex / multi-pattern / time-window filters. PRs: #156, #155
- v1.0.4 - feat(list-devices): server-side label/capability filters + format/fields projection. PRs: #153
- v1.0.3 - feat(rule-tools): redirect hint when caller passes a built-in RM rule id (addresses #118 Option A). PRs: #135
- v1.0.2 - docs: add AGENTS.md for AI contributors; PR #91. PRs: #149
- v1.0.1 - feat: optional flat tool-list mode (toggle off category gateways). PRs: #136
- v1.0.0 - ci(hub-e2e): self-configuring CI workflow against test hub (closes #77); feat(rm-native): native Rule Machine tools and classic-app CRUD + custom_ rename of MCP rule engine tools. PRs: #148, #134
- v0.11.1 - build(deps): bump gradle-wrapper from 9.4.1 to 9.5.0 in the gradle-dependencies group (#143, @app/dependabot); feat(release): author-curated, minor-scoped HPM release notes + PR review tooling. PRs: #143, #146
- v0.11.0 - fix(release): positive-match the skip cascade so recursion guard short-circuits cleanly; PR #120; tests: RM 5.1 native BAT suite — acceptance gate for #120; docs: add Gemini testing results for PR #134; fix(manage_virtual_device): rename "Virtual Presence Sensor" enum entry to "Virtual Presence"; feat(developer-mode): add manage_mcp_self gateway + delete_variable. PRs: #123, #133, #138, #144, #145
- v0.10.1 - test(server): unit-test manage_destructive_hub_ops / manage_apps_drivers / manage_app_driver_code gateways; test(server): unit-test manage_logs / manage_diagnostics / manage_files gateways; PR #75; test(rules): breadth coverage for conditions, actions, triggers, loop guard, error paths (closes #75); test: backfill regression specs from CHANGELOG / release-notes history (closes #76); PR #76; Add get_app_config + list_app_pages (manage_installed_apps gateway); test: backfill sunrise/sunset silent-failure fix + broader silent-device-not-found coverage (#76); PR #77; test(integration): in-harness dispatch drive-through for handleMcpRequest + subscribe/fire (#77); fix(release): push via deploy key to bypass main-branch ruleset. PRs: #110, #111, #115, #116, #112, #117, #119, #122
- v0.10.0 - docs: re-collapse Future Plans + refresh MCP tools list; build(deps): bump the gradle-dependencies group with 2 updates (#101, @app/dependabot); fix(lint): scan GString interpolations for sandbox violations; perf(test): cache HubitatAppSandbox parse per spec class (5m → 1.5m); Built-in app visibility + Rule Machine interop (2 new gateways, 7 tools); test(server): unit-test manage_rules_admin / manage_hub_variables / manage_rooms gateways; test(rm-interop): pin registerRmRule warn-log emission and type classification; fix(release): trigger on push to main (fork-PR bot-permission workaround); fix(release): cascade skip flags + retry PR lookup for indexing lag. PRs: #102, #101, #103, #104, #79, #106, #107, #108, #109
- v0.9.7 - build: add Groovy/Spock/HubitatCI test harness (#69); test: gateway proxy dispatch + JSON-RPC envelope + resolution paths; ci: silence Groovy 2.5 reflective warnings + run Gradle daemon on JDK 17; chore: migrate hubitat_ci to joelwetzel fork + Dependabot + version-check; docs: credit biocomp and joelwetzel for the test harness; build(deps): bump the github-actions group with 5 updates (#87, @app/dependabot); build(deps): bump gradle-wrapper from 8.10 to 9.4.1 in the gradle-dependencies group (#86, @app/dependabot); build: assignment syntax for url + exceptionFormat (Gradle 10 prep); docs(futureplans): correct two 'infeasible' claims contradicted by Hubitat docs; test: rule-engine primitive specs (closes #71); chore: migrate test harness from joelwetzel to eighty20results/hubitat_ci. PRs: #81, #82, #83, #85, #88, #87, #86, #89, #90, #98, #100
- v0.9.6 - fix: drop PR reference from packageManifest.json releaseNotes. PRs: #80
- v0.9.5 - feat: include PR main-commit extended description in release notes. PRs: #78
- v0.9.4 - fix: release workflow pushes the version tag explicitly; chore: drop unused json import in sandbox_lint.py. PRs: #67, #68
- v0.9.3 - Release automation: bot-driven version bumps + CHANGELOG + release notes sync. PRs: #66
- v0.9.2 - Enriched
list_devicessummary (new fields:disabled,deviceNetworkId,lastActivity,parentDeviceId) + server-sidefilterarg (enabled/disabled/stale:<hours>) applied before pagination — closes the N+1 roundtrip problem for common bulk questions. Fixget_hub_logsordering (now returns most recent entries first; previously returned oldest from the ring buffer) + newdeviceId/appIdserver-side scope args (~93% payload reduction when scoped). - v0.9.1 - New
search_tools: BM25 natural language search across all 74 MCP tools (core + gateway sub-tools). Searches tool names, descriptions, and parameter names. Returns matching tools ranked by relevance with gateway attribution so the LLM knows how to call them. Inspired by FastMCP 3.1 Tool Search transform. 74 MCP tools total (31 ontools/list). - v0.9.0 - New tools:
get_performance_stats(device/app performance stats — method call counts, % busy, cumulative total ms, state size, events, large state flag; sortable bypct/count/stateSize/totalMs/name) andget_hub_jobs(scheduled/running jobs, hub actions), both inmanage_logsgateway. Enhancedget_memory_history:limitparameter (default 100) to prevent response-too-large errors, now includes Java heap (totalJavaKB,freeJavaKB) and direct/NIO buffer memory (directJavaKB) per entry with min/max tracking in summary for leak detection. 73 MCP tools total (30 ontools/list). - v0.8.7 - Add memory diagnostic tools:
get_memory_historyandforce_garbage_collection, both inmanage_diagnosticsgateway. 71 MCP tools total. - v0.8.6 - Bug fix:
days_of_weekcondition crash (Date.format(String, Locale)not available in Hubitat sandbox).
Older versions (v0.7.0 – v0.8.5)
- v0.8.5 - Bug fixes: fix
send_commandMap-parameter handling (Hubitat's JSON parser chokes on nested JSON objects in parameter arrays, falling back to raw String — now extracts embedded JSON objects by brace-matching), fixget_hub_logssource filter (was checking timestamp field instead of message field — source searches never matched app/device names), add JSON string-to-Map parsing in rule action parameter converter - v0.8.2 - Critical bug fixes: fix rule action execution crash (
log.isDebugEnabled()not available in Hubitat sandbox — all rule actions were silently failing), fixsend_commandsetColor/map-parameter handling (JSON string parameters now auto-parsed into Maps for commands likesetColorthat expect Map arguments) - v0.8.1 - Bug fixes: remove dead code (toolEnableRule/toolDisableRule/toolToggleRule), fix stale tool references in messages and docs, update BAT-v2 for final 9-gateway architecture
- v0.8.0 - Category gateway proxy: consolidate 48 tools behind 9 domain-named gateways, reducing
tools/listfrom 69 to 30. 21 core tools stay ungrouped (devices, rules, modes, HSM, update_device, manage_virtual_device, list_virtual_devices, get_hub_info, create_hub_backup, check_for_update, generate_bug_report, get_tool_guide). Merged get_hub_health and get_hub_details into get_hub_info — comprehensive hub info (hardware, health, MCP stats) always available; PII/location data (name, IP, timezone, coordinates, zip code) gated behind Hub Admin Read. Merged enable_rule/disable_rule into update_rule (useenabled=true/false). Merged create_virtual_device/delete_virtual_device into manage_virtual_device (withactionenum). Promoted create_hub_backup, check_for_update, and generate_bug_report to core tools. Dissolved manage_hub_info gateway (radio details moved to manage_diagnostics). Renamed manage_hub_maintenance to manage_destructive_hub_ops, manage_code_changes to manage_app_driver_code. Each gateway shows tool summaries with parameter hints in its description (always visible to LLMs) and returns full schemas on demand. Gateways: manage_rules_admin, manage_hub_variables, manage_rooms, manage_destructive_hub_ops, manage_apps_drivers (read-only), manage_app_driver_code, manage_logs, manage_diagnostics, manage_files. Modeled after ha-mcp PR #637. Breaking change: proxied tools removed fromtools/listbut accessible via gateways. - v0.7.7 - Code review round 2: MCP protocol fix (tool errors use isError flag per spec), fix formatAge() singular grammar, short-circuit condition evaluation, fix CI sed double-demotion, consolidate redundant API calls, guard eager debug logging, deduplicate sunrise/sunset reschedule, fix variable_math double atomicState read, efficiency improvements
- v0.7.6 - Code review: fix hoursAgo calculation bug, fix variable shadowing, centralize version string, extract shared helpers (~90 lines reduced)
- v0.7.5 - Token efficiency: lean tool descriptions with progressive disclosure via
get_tool_guide(~27% token reduction) - v0.7.4 - Stability: configurable execution loop guard with push notifications, safe room move, resilient date parsing
- v0.7.3 - Documentation sync (SKILL.md section names match source code structure)
- v0.7.2 - Device authorization safety + optimized tool descriptions + get_tool_guide (74 tools)
- v0.7.1 - Auto-backup for delete_rule, testRule flag, bug fixes
- v0.7.0 - Room management: list_rooms, get_room, create_room, delete_room, rename_room (73 tools)
Older versions (v0.0.3 – v0.6.15)
- v0.6.15 - Room assignment fix: use 'roomId' field (not 'id'), remove from old room before adding to new
- v0.6.14 - Room assignment: POST /room/save with JSON content type, form-encoded, hub2/ prefix, Grails command object
- v0.6.13 - Room assignment: try PUT /room (Grails RESTful update), POST /room/save, POST /room/update, probe /room/list for endpoint discovery
- v0.6.12 - Fix room assignment: use GET /room/addDevice with query params + verification
- v0.6.11 - Fix room assignment: use /room/ controller endpoints (add device to room)
- v0.6.10 - Fix room assignment: use fullJson device data for /device/save (Vue.js SPA has no HTML forms)
- v0.6.9 - Room assignment: scrape device edit page HTML for correct form fields
- v0.6.8 - Room assignment: capture 500 error response body for diagnosis
- v0.6.7 - Fix room assignment: add Grails
versionfield for optimistic locking - v0.6.6 - Room assignment: diagnostic build with device JSON dump
- v0.6.5 - Fix room assignment: use
deviceTypeIdfield (nottypeId) - v0.6.4 - Fix room assignment: extract device data from nested
fullJson.device - v0.6.3 - Fix
update_deviceroom assignment (500) and enable/disable (404) bugs + debug logging - v0.6.2 - Add
update_devicetool (68 tools) - v0.6.1 - Fix BigDecimal.round() crash in version update checker (67 tools)
- v0.6.0 - Virtual device creation and management (67 tools)
- v0.5.4 - Fix BigDecimal arithmetic with pure integer math in
device_health_checkanddelete_device(64 tools) - v0.5.3 - Fix
BigDecimal.round()indevice_health_check(64 tools) - v0.5.2 - Fix
device_health_checkerror handling (64 tools) - v0.5.1 - Fix
get_hub_logsJSON array parsing (64 tools) - v0.5.0 - Monitoring tools and device management (64 tools)
- v0.4.8 - Fix Z-Wave and Zigbee endpoint compatibility (59 tools)
- v0.4.7 - Comprehensive bug fixes from code review (59 tools)
- v0.4.6 - Fix version mismatch bug in optimistic locking (59 tools)
- v0.4.5 - Smart large-file handling (59 tools)
- v0.4.4 - Fix bugs found during live Claude.ai testing (59 tools)
- v0.4.3 - Comprehensive bug fixes + item backup & file manager tools (59 tools)
- v0.4.2 - Response size safety limits (hub enforces 128KB cap)
- v0.4.1 - Bug fixes for Hub Admin tools + two-tier backup system
- v0.4.0 - Hub Admin Tools with Hub Security support (52 tools)
- v0.3.3 - Multi-device trigger support and validation fixes
- v0.3.2 - Comprehensive bug fixes (25 bugs verified and fixed)
- v0.3.1 - Bug fixes from comprehensive v0.3.0 testing
- v0.3.0 - Rule portability, new action types, conditional triggers (34 tools)
- v0.2.12 - Fourth code review: Critical UI bug fixes + 16 additional bug fixes
- v0.2.11 - Third code review (16 fixes)
- v0.2.10 - Fixed operator handling
- v0.2.9 - Critical bug fixes from second thorough review
- v0.2.8 - Thorough code review fixes
- v0.2.7 - Fixed StackOverflowError on app install/open
- v0.2.6 - Added
generate_bug_reporttool - v0.2.5 - Added UI control for MCP debug log level
- v0.2.4 - Added version field to
get_logging_status - v0.2.3 - Version bump for HPM release
- v0.2.2 - CRITICAL FIX: Rules with
enabled=truenow persist correctly - v0.2.1 - Fixed duplicate
formatTimestampmethod compilation error - v0.2.0 - MCP Debug Logging System (5 new diagnostic tools)
- v0.1.23 - Critical fix for rule creation order
- v0.1.22 - Major bug fixes: action returns, validation, type coercion
- v0.1.21 - Fixed negative index vulnerabilities, null safety improvements
- v0.1.20 - Added
handlePeriodicEvent(), fixedcancel_delayed, 7 missing condition types - v0.1.19 - Fixed
time_rangefield name compatibility - v0.1.18 - Removed
expressioncondition type (not allowed in sandbox) - v0.1.17 - UI/MCP parity: 6 condition types + 12 action types added to UI
- v0.1.16 - Fixed duration trigger re-arming
- v0.1.15 - Fixed "required fields" validation error on rule creation
- v0.1.14 - Fixed child app label not updating
- v0.1.13 - Fixed Hubitat sandbox compatibility
- v0.1.12 - Performance improvements, configurable captured states limit
- v0.1.11 - Added verification reminders to tool descriptions
- v0.1.10 - Fixed device label returning null
- v0.1.9 - Fixed missing condition type validations
- v0.1.8 - Fixed duration triggers firing repeatedly
- v0.1.7 - Fixed duration-based
device_eventtriggers - v0.1.6 - Fixed
repeataction parameter name - v0.1.5 - Fixed
capture_state/restore_stateacross rules - v0.1.4 - Added remaining documented actions
- v0.1.3 - Major rule engine fixes
- v0.1.2 - Fixed missing action types
- v0.1.1 - Added pagination for
list_devices - v0.1.0 - Parent/Child architecture
- v0.0.6 - Fixed trigger/condition/action save flow
- v0.0.5 - Bug fixes for device and variable tools
- v0.0.4 - Added full Rule Engine UI
- v0.0.3 - Initial release
Manual Testing Checklist
- Create a new rule via Hubitat Apps > MCP Rule Server > Add Rule
- Edit existing rule triggers through the UI
- Edit existing rule conditions through the UI
- Edit existing rule actions through the UI
- Enable/disable rules via the UI toggle
- Delete a rule with confirmation dialog
- Use "Test Rule" button (dry run) to verify rule logic
- Verify rule list displays correctly with status and last triggered time
- Add device_event trigger and select device/attribute
- Add button_event trigger with button number selection
- Add time trigger with time picker
- Add sunrise/sunset trigger with offset input
- Add periodic trigger with interval configuration
- Add mode_change trigger with mode selection
- Add hsm_change trigger with status selection
- Add device_state condition with operator selection
- Add time_range condition with start/end time pickers
- Add mode condition with multi-mode selection
- Add days_of_week condition with day checkboxes
- Add variable condition with variable name input
- Test conditionLogic toggle between "all" and "any"
- Add device_command action with command dropdown
- Add delay action with seconds input
- Add set_level action with level slider
- Add if_then_else action with nested action configuration
- Add set_variable action with scope selection
- Verify action reordering works correctly
The tests/ directory contains:
tests/BAT-v2.md— Behavior Acceptance Tests (BAT): scripted scenarios for hand-run validation against a live hub. Includes thewizard_probeusage docs and the wizard-state regression appendix.tests/sandbox_lint.py— fast structural lint of the Groovy sandbox patterns (forbidden calls, version-string consistency). Run viauv run --python 3.12 tests/sandbox_lint.py.tests/e2e_test.py— end-to-end smoke test against a live hub. Requirestests/e2e_config.json(gitignored). Run viauv run --python 3.12 --with requests tests/e2e_test.py.tests/wizard_probe.py— systematic Rule Machine wizard-state regression probe. Runs a 25-probe matrix that exercises suspected wizard-state-leak paths, and exposes aquick_probe()helper for one-off diagnostic investigation. See the wizard_probe appendix intests/BAT-v2.mdfor full usage.- Spock unit tests under
src/test/groovy/— run via the Gradle wrapper:
./gradlew testSee docs/testing.md for the full Spock harness overview, how to add new specs, and the RMUtils mocking recipe for hub_manage_native_rules_and_apps tools.
Contributions welcome! Fork the repo, create a feature branch, make your changes, and submit a pull request.
New MCP tools must ship with unit tests — both golden-path and error-path coverage. Tool handler tests go under src/test/groovy/server/; rule-engine tests under src/test/groovy/rules/. See docs/testing.md for the harness overview, the recipe for adding a new tool spec, and the RMUtils mocking pattern for hub_manage_native_rules_and_apps-style tools.
PRs that add tools without tests will be asked to add them before merge. CI (./gradlew test) runs on every PR via .github/workflows/unit-tests.yml.
MIT License - see LICENSE
- Built with assistance from Claude (Anthropic)
- Inspired by the Model Context Protocol
- Inspired by the Home Assistant MCP
- Thanks to the Hubitat community for documentation and examples
- biocomp/hubitat_ci (@biocomp) — original Hubitat Groovy unit-testing framework our harness is built on (Apache 2.0)
- eighty20results/hubitat_ci (@eighty20results) — actively-maintained Groovy 3.0 fork of the above, consumed as our test dependency via JitPack (Apache 2.0); previously we used joelwetzel/hubitat_ci (@joelwetzel) and migrated to eighty20results for Groovy 3 support and native sandbox mapping of
hubitat.helper.*helpers - @ashwinma14 - Fix for StackOverflowError on app install (#15)
- @level99 - Enriched
hub_list_devicessummary + server-sidefilterarg (#63) andhub_get_logsordering fix +deviceId/appIdserver-side scope (#64)
This software is provided "as is", without warranty of any kind. This is an AI-assisted project and may contain bugs or unexpected behavior. Always test automations carefully, especially those controlling critical devices. The authors are not responsible for any damage or issues caused by using this software.