MCP

The MCP server

preen-mcp — the tools Claude Code uses to publish widgets and drive the display.

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

ToolWhat it does
pair_statusIs an iPhone paired, and currently connected? Returns { paired, connected, deviceName? }.
publish_widgetCreate a widget from HTML and show it. Returns { id }.
update_widgetReplace a widget’s HTML (and optionally its icon); a note annotates the cut in version history.
set_widget_iconSet/replace a widget’s icon without re-sending HTML.
list_widgetsList widgets + which is active.
set_active_widgetChoose which widget shows fullscreen.
delete_widgetRemove a widget.
list_versionsA widget’s code history (newest first).
set_widget_versionSet which version is live — selects that snapshot (history stays intact).
push_dataSet the live JSON feed for a widget (details).
get_data_endpointGet a URL + token to POST data from a script (details).
store_putAppend (or replace) a record in a widget’s datastore collection. Returns { id, ts }.
store_queryRead records from a widget’s datastore collection. Returns { records: [{ id, ts, body }] }.
store_deleteDelete one datastore record, or clear a collection. Returns { deleted }.
register_actionMake a widget interactive — proposes a capability; you approve it on the Mac before it can fire (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.
  • actions (optional) — declare interactive actions at creation time instead of a separate register_action call.

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 version history. Pass a short note on updates (“added sparkline”) so each cut is legible later. Inspect the history with list_versions (isCurrent marks the live version; builtInVersion labels cuts that are pristine copies of a shipped built-in template) and roll back with set_widget_version — it selects that snapshot as live without appending anything, so history stays intact and you can flip back and forth freely. A snapshot is the whole widget cut — its HTML, icon, and action set — so reverting restores all of it (an action set wider than what’s currently approved re-prompts for consent on the Mac):

// update_widget
{ "id": "wgt_abc123", "html": "<!doctype html>…", "note": "added sparkline" }
// list_versions → { "versions": [ { "version": 3, "htmlHash": "…", "updatedAt": …,
//                                    "note"?, "isCurrent", "builtInVersion"? }, … ] }
// 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 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.