preen-mcp is a Model Context Protocol server
bundled inside the Mac app. The app registers it with Claude Code on first launch,
so you can manage widgets in plain language. It talks to the hub’s admin surface
over localhost only, with a write-scoped token — never the LAN.
For the same operations from your terminal (plus a live-reload watch loop), the
preen CLI speaks the same admin contract.
Tools
| Tool | What it does |
|---|---|
pair_status | Is an iPhone paired, and currently connected? Returns { paired, connected, deviceName? }. |
publish_widget | Create a widget from HTML and show it. Returns { id }. |
update_widget | Replace a widget’s HTML (and optionally its icon). |
set_widget_icon | Set/replace a widget’s icon without re-sending HTML. |
list_widgets | List widgets + which is active. |
set_active_widget | Choose which widget shows fullscreen. |
delete_widget | Remove a widget. |
list_versions | A widget’s code history (newest first). |
set_widget_version | Set which version is live — re-applies that snapshot’s HTML as a new version. |
push_data | Set the live JSON feed for a widget (details). |
get_data_endpoint | Get a URL + token to POST data from a script (details). |
store_put | Append (or replace) a record in a widget’s datastore collection. Returns { id, ts }. |
store_query | Read records from a widget’s datastore collection. Returns { records: [{ id, ts, body }] }. |
store_delete | Delete one datastore record, or clear a collection. Returns { deleted }. |
register_action | Make a widget interactive (DEBUG builds only — see Actions). |
Publishing
publish_widget takes the full HTML document and an optional poll interval and
icon, and returns the new widget’s id:
// publish_widget
{
"name": "Temperature",
"html": "<!doctype html>…",
"refreshMs": 1500,
"icon": { "kind": "sfSymbol", "value": "thermometer" }
}
// → { "id": "wgt_abc123" }
name(required) — shown in the phone’s switcher and the Mac list.html(required) — the complete self-contained document.refreshMs(optional) — native poll interval; defaults to 1500 ms.icon(optional) — see Manifest & icons.
Widgets published through the MCP land in the Mac app’s gallery under Browse widgets ▸ Custom, labeled as MCP-published — your own space, alongside anything published with the CLI. The Built-in tab is reserved for the trusted templates that ship with the app itself.
Then point the phone at it:
// set_active_widget
{ "id": "wgt_abc123" }
Iterating safely
Every publish_widget and update_widget appends to a bounded, forward-only
version history. Inspect it with list_versions and roll back with
set_widget_version (which re-applies that snapshot as a new current version):
// list_versions → { "versions": [ { "version": 3, "htmlHash": "…", "updatedAt": …, "note"? }, … ] }
// set_widget_version
{ "id": "wgt_abc123", "version": 2 }
A note on scope
preen-mcp only reaches the hub on 127.0.0.1 with a role-3 write token. It can
manage widgets and push data, but the LAN-facing display surface (what the phone
talks to) is a separate, session-token-gated API.