Metadata-Version: 2.5
Name: myrohome-helper-mcp
Version: 0.8.1
Summary: MCP server that lets an AI assistant read and control an HAI / Leviton Omni, NESS / ELK, Savant (preview) or Control4 Link (preview) system through Myro:Home Helper
Project-URL: Homepage, https://www.myrocontrol.com
Project-URL: Repository, https://gitlab.com/MyroControl/myrohome-helper-mcp
Project-URL: Myro:Home Helper (Mac App Store), https://apps.apple.com/app/id1334844181
Author: Myro Control, LLC
License: MIT
License-File: LICENSE
Keywords: control4,elk,hai,home-automation,leviton,mcp,myrohome,ness,omni,savant
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: keyring>=25.0
Requires-Dist: mcp<2,>=1.2
Description-Content-Type: text/markdown

# Myro:Home Helper MCP server

Lets an AI assistant (Claude Desktop, Claude Code, ChatGPT desktop, anything
that speaks [MCP](https://modelcontextprotocol.io)) read and control an
HAI / Leviton Omni controller, a NESS / ELK M1 panel, a Savant
system (preview), or a Control4 system through Control4 Link (preview, Helper 5.50),
through **Myro:Home Helper**, using the
Helper's HTTP API. The assistant sees typed tools with descriptions instead of guessing
URL shapes.

## What you need

- [Myro:Home Helper](https://apps.apple.com/app/id1334844181) (Mac App Store)
  4.06 or later on a Mac on your network (4.07 or later for
  the read tools: `list_objects`, `get_object`, `helper_log`).
- An API key for your controller: Helper → **API** tab → Generate.
- [`uv`](https://docs.astral.sh/uv/) on the machine running the assistant
  (`brew install uv` or the one-line installer). `uvx` fetches and runs this
  server on demand and keeps it updated.

## NESS / ELK

Helper 5.0 adds NESS / ELK M1 panels alongside HAI / Leviton Omni. The tools and
their parameters are the same for both; `controller_status` reports
`controllerFamily` (`"HAI / Leviton"` or `"NESS / ELK"`, derived from the
controller's `protocol`), and the live `scripting_reference` is scoped to that
family, so the assistant should read it before writing scripts. On ELK, user
codes may be 4 or 6 digits (`security-code set` accepts either), there is no
energy-cost level, and audio is a legacy M1XEP integration.

## Savant (preview) and Control4 Link (preview, Helper 5.50)

Savant and Control4 are room and activity systems, not alarm panels. They have
no zones, units, arming or panel messages, so the panel tools (`set_unit`,
`arm`, `set_thermostat` and the rest) do not apply to them.

- `controller_status` reports `controllerFamily` `"Savant"` or `"Control4"`,
  plus `backend`, `rooms`, `roomStates`, `inventory`, `homeKit`, `objectCounts`
  and `capabilities`. Unknown room fields are omitted rather than guessed.
- Automate them through scripts. Read `scripting_reference` first (it is scoped
  to the selected controller and lists the real rooms, activities and services),
  then `validate_script` and `save_script` (saved disabled). Savant scripts use
  `MyroSavant`; Control4 scripts use `MyroControl4`. Saving and enabling need
  an API key with **remote script management**.
- Sign-in credentials for either system live only in Helper's Connection pane,
  never in MCP or a conversation.

## Compatibility

| Adapter | Needs Helper |
|---|---|
| 0.8.1 `suggestions` described as Suggestions, Helper's name for the list since 5.51 | 5.50 or later |
| 0.8.0 `timeline`, `timeline_note`, `timeline_moment` | 5.50 or later |
| 0.7.0 `list_favorites`, `list_doorbells`, `web_capabilities`, `suggestions` | 5.50 or later (other tools keep their own minimums above) |
| 0.6.0 Pro 2.51 message, beeper, scene and lock tools | 5.01 or later |
| 0.5.0 typed `/control` tools | 4.07 or later (relative controls: 4.50) |
| 0.4.0 history and panel events | 4.50 or later |

A tool that needs a newer Helper than the one running answers with
`requiredHelper` instead of failing silently.

## Configure

Claude Desktop (`claude_desktop_config.json`) or any client that takes the
standard MCP config:

```json
{
  "mcpServers": {
    "myrohome-helper": {
      "command": "uvx",
      "args": ["myrohome-helper-mcp"],
      "env": {
        "MHH_URL": "http://192.168.1.76:9393",
        "MHH_API_KEY": "your-api-key"
      }
    }
  }
}
```

Claude Code:

```
claude mcp add myrohome-helper -e MHH_URL=http://192.168.1.76:9393 -e MHH_API_KEY=your-api-key -- uvx myrohome-helper-mcp
```

The controller ID is fetched from the Helper automatically. Set
`MHH_CONTROLLER_ID` only if you want to pin it.

## Tools

Read:

| Tool | What it returns |
|---|---|
| `helper_info` | Helper version and identity, and which controller the key is for |
| `controller_status` | Name, model, firmware, connection, power, battery, phone line, energy cost, troubles, `controllerFamily` |
| `controller_property(name)` | One controller property |
| `list_objects(type?)` | Every object with decoded state; filter by Zone, Unit, Flag, Output, Thermostat, Auxiliary Sensor, … |
| `get_object(type, number)` | One object; for a door lock, its locked state and the reader's last access |
| `helper_log(lines)` | The Helper's recent log lines |
| `ethernet_quality(since?)` | The controller connection's health record |
| `list_favorites(resolve?)` | Helper 5.50+: the objects starred in Helper's web control (shared by every signed-in browser), matched to their names and current state |
| `list_doorbells` | Helper 5.50+: the zones, flags and outputs marked "Treat as doorbell" in the web control, with their current state |
| `web_capabilities` | Helper 5.50+: which web-control and Apple TV features (favorites, doorbells, history, Timeline, Apple TV pairing, UniFi Protect cameras and camera events, Savant activities) Helper offers for this controller, and the Helper version |
| `suggestions(kind?, home?, at?, limit?, compose?)` | Helper 5.50+: the household's Suggestions list, the shared favorites first and then the things people in this home usually do at about this time on a day like today, ranked from the taps Myro:Home Pro and the Apple TV share with Helper, with object, name, room, suggested action, score, `favorite`, state, reasons, `why` and last use; `limit` 1 to 24, `compose: false` for the plain ranking (up to 12). Read-only; a `confirm: true` entry is a security action and only a suggestion |
| `timeline(hours?, kind?)` | Helper 5.50+: the home Timeline for the last 1 to 24 hours (default 8) as the browser control, Myro:Home Pro and the Apple TV show it: headline, moments (arrivals, departures, doorbell rings, alarms, scenes, music, busy rooms, named moments), events with their `source` (and `approximate: true` on a light the panel changed quietly), flags, gaps and the current security, lights and open doors; `kind` keeps only events of those kinds. Never pictures; the per-room drawing data is left out. Read-only |
| `connection_status` | Helper 4.07+: live connected/reconnecting state and the panel messages showing now |
| `list_logged_sensors` | Which sensors record history, with the type and number |
| `sensor_history(type, number, points)` | Logged readings for one sensor or thermostat |
| `history_window(type, number, start, end, points, remove_outliers, runtime_bucket, time_zone)` | Helper 4.50+: bounded live/archived samples, statistics before display reduction, and hourly/daily/weekly reported HVAC intervals with unknown coverage and archive warnings |
| `panel_events(...)` | Helper 4.50+: cached panel history with zone/area/type/month/day filters and user attribution |
| `refresh_panel_events(scope)` | Helper 4.50+: recent history by default; explicit `all` reads up to 2000 available panel records. Poll `panel_events` while `refreshing` is true; check `panelEndReached` and `refreshError` before claiming completeness |

### Door access (HAI / Leviton)

An access-control reader and the lock it releases share one number.
`list_objects("Access Control Lock")` and `get_object("Access Control Lock", n)`
return `locked` and, once the reader has reported, the last access:

| Field | Meaning |
|---|---|
| `lastAccessGranted` | `true` granted, `false` denied |
| `lastAccessUser` | Omni user number: 0 none, 251 Duress, 252 Keyswitch, 253 Quick Arm, 254 PC Access, 255 Programming |
| `lastAccessBy` | Added by this server: that number in words (`"user 7"`, `"Duress"`, `"none"`) |
| `lastAccessAt` | When Helper received the reader status (ISO 8601) |

`Access Control Reader` objects carry the same result as `granted`, `lastUser`
and `lastAccessAt`. The panel does not timestamp accesses, so `lastAccessAt` is
"last reported", not necessarily the moment of the swipe. Helper 5.01 and later
read reader status at startup and on every status refresh; older Helpers report
it only after the next badge or keypad code. User codes are never exposed.
Reading door access is a read: it adds no write tool, and unlocking stays
behind the owner opt-in below.

The new history/activity tools require adapter 0.4.0 and Helper 4.50. Panel event
dates contain no year; `receivedAt` is download time, not event time. History
indexing is incremental: retry when `indexing` is true. HVAC intervals are
estimates from reported action, and `unknownSeconds` must not be interpreted as
equipment being off. These additions do not change the existing security tools.
Recent event refreshes are limited to once every 30 seconds per controller;
full downloads to once every 10 minutes. A `429` means wait and use the cached
`panel_events` result, not repeat the download immediately.

Write (each changes the physical home; tool descriptions tell the assistant to confirm first):

| Tool | Effect |
|---|---|
| `set_unit(number, value, timer?)` | 0 off, 1 or 100 on, 2–99 dimmer level; optional timer 1–99 s for on/off |
| `set_blind(number, value, timer?)` | Blind/shade 0–100 |
| `set_flag(number, value, timer?)` | Flag to 0–255; optional timer 1–99 s for 0/1 |
| `increment_flag(number)` / `decrement_flag(number)` | Updated Helper 4.50+: one native counter step, without cached read/modify/write |
| `dim_unit(number, steps?)` / `brighten_unit(number, steps?)` | Updated Helper 4.50+: 1-9 native lighting steps (default 1), not percentage points |
| `set_output(number, on, timer?)` | Output relay on or off; optional timer 1–99 s |
| `set_thermostat(number, mode?, heat_setpoint?, cool_setpoint?, humidify_setpoint?, dehumidify_setpoint?, fan?, hold?)` | Mode off/heat/cool/auto/emergency, setpoints, fan auto/on, hold |
| `set_area_lights(on, area)` | All lights in an area on or off (area 0 = all) |
| `run_button(number)` | Run a controller button (macro) |
| `set_upb_link(number, state)` | UPB link on / off / store |
| `blink_upb_unit(number, stop?)` | Helper 5.01+: blink a UPB light for 30 s at 1 s to find it, or stop it (UPB units only, HAI / Leviton) |
| `say_panel_message(number)` / `log_panel_message(number)` | Helper 5.01+: speak a programmed message, or write it to the event log (HAI / Leviton) |
| `set_keypad_beepers(operation)` | Helper 5.01+: keypad beepers on / off, or stop (silence now) |
| `press_radiora2_phantom(number)` | Helper 5.01+: press a RadioRA 2 phantom button by number |
| `set_leviton_scene(number, state)` | Helper 5.01+: Leviton scene on / off / set |
| `lock_all_doors()` | Helper 5.01+: lock every door (there is no unlock-all tool) |
| `show_panel_message(number, presentation)` | Show a programmed message on the keypads: beep_led / led / silent |
| `clear_panel_message(number)` | Clear one message, or 0 for all |
| `lock_door(number)` | Lock an access-control lock (unlocking is opt-in, below) |
| `set_user_setting(number, value)` | User setting 1–255 to a raw value 0–65535 |
| `set_energy_cost(level)` | low / medium / high / critical (HAI / Leviton only) |
| `send_notification(message)` | Push to the Myro:Home apps |
| `send_test_notification` | The Helper's test push, to check delivery |
| `speak(message)` | Speak on the Mac |
| `log_message(message)` | Write to the Helper log |
| `log_to_file(filename, message)` | Append to a file in the Helper's Logs folder |
| `sync_now` | Re-sync from the controller |

Adapter 0.5.0 adds typed `/control` tools for blinds, thermostats, area lights,
buttons, UPB links, messages, locks, user settings and audio transport, plus
timers on units, flags and outputs (Helper 4.07 or later). Existing untimed
unit/flag/output, audio, energy, notification, speech, log and sync tools retain
their existing routes. Timed dim levels, intermediate blind positions and flag
counters are rejected because the panel ignores their timer. Thermostat
setpoints use the controller's temperature format; Helper enforces that format's
range. A controller ACK means acceptance, not physical completion. After an
error or timeout, read the state before considering another write; commands are
never automatically retried.

Timeline notes (Helper 5.50+; a note is a line in the home Timeline, not a physical
action: it never notifies, never takes a picture and never sends a command):

| Tool | Effect |
|---|---|
| `timeline_note(title, kind?, object?, room?, at?, importance?, detail?)` | One note in the Timeline with `source: "api"`: a title of up to 80 characters, filed under an event kind (`note` by default), optionally about one object (an id such as `Unit:12` or a name that matches one object), in a room, at an ISO 8601 time within the last 72 hours, with importance 0, 1 or 2 and up to 200 characters of detail |
| `timeline_moment(title, symbol?, tone?, objects?, start?, end?, detail?)` | A named moment card with exactly this title, an SF Symbol (`sparkles` by default), a tone (`info`, `good`, `warn`, `alert`), the objects it covers and an optional start and end (default now and open) |

Helper allows 120 notes and moments an hour per API key (`429` past that) and refuses a
bad value with one sentence (`400`). The tool descriptions tell the assistant to say what
it wrote. Notes cannot be changed or deleted through MCP, and the scripting rules that
quiet or rename automatic Timeline events (`MyroTimeline.mute`, `rename`, `mode`) have
no MCP form; a script saved with `save_script` can set them.

Scripts (save, enable and reload need a key with **remote script management** enabled;
listing, reading and validating do not):

| Tool | What it does |
|---|---|
| `list_scripts` | Every script: name, id, enabled, schedule |
| `get_script(identifier)` | One script's full source |
| `scripting_reference(compact)` | The live scripting API, with your real device names |
| `validate_script(code)` | Syntax and warnings, without running it |
| `save_script(name, code, script_id, enabled)` | Create or replace; disabled by default |
| `set_script_enabled(identifier, enabled)` | Enabling runs the script immediately |
| `reload_scripts` | Re-runs every enabled script |

### Helper 4.50 relative controls

Adapter 0.5.0 adds the four relative tools above, requiring an updated Helper
4.50 build with these actions. Earlier 4.50 builds may not support them. Discover the exact Flag or
Unit number first. They validate input locally and use the authenticated typed
control endpoint; the Helper also checks that the object has the right type.
Each counter call requests one step. Lighting steps are not percentage points;
device support and resulting levels depend on the panel and lighting technology.
The panel defines behavior at counter endpoints 0/255. Flags can trigger panel
programs, so confirm intent before changing them.

No automatic retries: an ACK timeout may mean the command already applied.
`accepted` means the panel acknowledged it, not that a particular final value was
observed. Check reported state after uncertainty. These additions do not expand
security permissions. Older Helpers return explicit failure with an updated
4.50 build requirement; the adapter never substitutes a cached-value write.

### Helper 4.50 scripting features

The live `scripting_reference` exposes `MyroLibraries`, `MyroControllers`, and
`MyroSystem.speakQueued` / `cancelSpeech` / `cancelAllSpeech`. MCP can generate,
validate and save scripts using these APIs through the existing script tools;
it does not need a separate tool for every JavaScript method.

Library files must be installed locally in Helper's Scripts/Libraries folder.
Cross-controller access requires local **Send To** and **Accept From** grants
at the respective controllers. MCP cannot upload library files, change grants,
or silently switch its controller API key. A grant extends the origin scripts'
authority, including remotely managed scripts, so enable it only deliberately.
Validation checks the script body without loading dependencies or proving
permissions, device state or speech playback. Save disabled and review before
enabling. Script-owned speech cancellation does not cancel the separate HTTP
speech queue used by MCP's `speak` tool.

Browser login, themes, live page navigation, Apple TV pairing, doorbell
marking, the home history, the Timeline (and its kept doorbell and camera
pictures) and UniFi Protect camera setup and events are UI and browser-session
features, not MCP tools: they are served under `/helper/web/*` for a signed-in
browser, or under `/helper/timeline` for a paired app's session token, and the
MCP API key cannot use them. MCP reads only the results an API key can reach:
`list_favorites`, the `doorbell` field that `list_objects` and `get_object`
carry on marked zones, flags and outputs (`list_doorbells` filters to them),
`web_capabilities`, which reads the public controller list the web control
and Myro:Home Pro for Apple TV use (no sign-in; only this controller's entry is
returned), `suggestions`, the household's usage-ranked Suggestions list
(`GET /helper/{controllerID}/suggestions`; the usage events themselves, their
upload and their deletion stay with the paired apps and the browser's **Clear
usage history**), and since 0.8.0 the Timeline itself through its keyed routes:
`timeline` reads `GET /helper/{controllerID}/timeline` (moments and events,
never the pictures) and `timeline_note` and `timeline_moment` write through
`POST /helper/{controllerID}/timeline/notes`. UniFi Protect sign-ins never pass
through MCP.
History queries include archived data automatically; archive deletion policy
remains local configuration, not an MCP write capability.

Audio, Lutron and displays:

| Tool | What it does |
|---|---|
| `list_audio_sources` | Sources and their cached metadata |
| `audio_source_metadata(source, refresh)` | What one source is playing |
| `set_audio_zone(zone, power, volume, mute, source)` | Control a zone |
| `audio_zone_key(zone, transport or key)` | Russound transport (play, pause, next, …) or a raw audio key 1–40 |
| `list_lutron` | Bridges, devices and Picos |
| `set_lutron_level(bridge_id, level, ...)` | Set a Lutron device 0-100 |
| `activate_lutron_scene(bridge_id, scene_href)` | Activate a scene |
| `press_lutron_button(bridge_id, integration_id, component)` | Press a keypad or Pico button |
| `list_displays` | Vestaboards and LaMetric clocks |
| `send_to_vestaboard(text | lines | characters, ...)` | Show text on a Vestaboard: auto-fit, per-row, or a raw code grid |
| `send_to_lametric(text, ...)` | Show a notification on a LaMetric |
| `sync_clock` | Set the controller's clock from this Mac |

Helper Mac:

| Tool | What it does |
|---|---|
| `beep` | System beep on the Helper Mac |
| `show_mac_alert(message)` | Alert dialog on the Helper Mac |
| `set_speech_delays(start_delay_ms, between_delay_ms)` | Tune the speech queue |
| `play_sound(filename, queue_with_speech)` | Play a sound from the Helper's Scripts folder |
| `stop_sound(filename)` | Stop it |

Security (all three need an API key with arm/disarm enabled; `arm` also needs a panel code stored locally):

| Tool | Effect |
|---|---|
| `arm(mode, area)` | day, night, away, vacation, day_instant, night_delayed |
| `bypass_zone(zone, bypassed, area)` | Bypass or restore one zone |
| `restore_all_zones(area)` | Restore every bypassed zone |

`arm` stays disabled until the owner stores a panel code on the machine
running this server. `bypass_zone` and `restore_all_zones` work without one on
Helper 4.13 or later, running as the panel's Programmed user; with a stored code
they run as that user instead. To store or remove the code:

```
uvx myrohome-helper-mcp security-code set      # prompts; nothing is echoed
uvx myrohome-helper-mcp security-code delete
```

The code goes into the operating system credential store (macOS Keychain,
Windows Credential Locker, Linux Secret Service) under the controller's
identity. It is never placed in the MCP config, an environment variable, or a
conversation, and it is read only when a security tool is actually called.
Helper validates it against the panel itself.

### Disarming and unlocking (off by default)

An assistant cannot turn the alarm off or unlock a door unless you allow it, on
the computer running this server:

```
uvx myrohome-helper-mcp allow disarm        # adds the disarm tool
uvx myrohome-helper-mcp allow unlock        # adds the unlock_door tool
uvx myrohome-helper-mcp disallow disarm     # takes it away again
```

Each asks you to type the word to confirm, and applies only to the controller
that API key belongs to. Restart your MCP client afterwards. Until you allow
them, the tools are not registered at all, so an instruction hidden in an
email, web page or document the assistant reads has nothing to call. Disarm
still also needs an API key with arm/disarm enabled and a stored panel code.
Revoking takes effect on the next call, without a restart.

AI assistants can be misled by text they read. Allow these only if you
understand that, and prefer the Myro:Home app for disarming.

## Security

This server runs on your LAN like every other Myro:Home client and holds one
controller's API key. Anyone who can run the assistant with this config can do
what the key allows. Keep it on trusted machines. The Helper's HTTP API is
plain HTTP on the local network; do not expose port 9393 to the internet.

## Developing

```
uv sync
MHH_URL=http://192.168.1.76:9393 MHH_API_KEY=... uv run myrohome-helper-mcp   # stdio server
uv run mcp dev src/myrohome_helper_mcp/server.py                             # MCP inspector
```

## Releasing

Bump `version` in `pyproject.toml` and `__init__.py`, commit, then tag and
push: `git tag v0.1.0 && git push origin v0.1.0`. The GitLab CI job builds
and publishes to PyPI through trusted publishing (one-time setup: PyPI
project -> Publishing -> add GitLab publisher `MyroControl/myrohome-helper-mcp`,
environment `pypi`).

MIT licensed. Myro Control, LLC.
