# Agents & API

Ask an AI agent to set up your docks and it does it, the same way you would by hand. Moor includes an **MCP server** for agents (Claude Code, Codex, Cursor and any other MCP client) and a **local API** for scripts. Both can read and change everything you can change in Moor: profiles, Custom Docks and their items, themes, widgets, settings and commands.

[Video: Claude Code builds a dock through Moor's MCP server.](https://moorapp.com/video/ai-agent.mp4)

It's on your Mac only. Nothing listens on a network port, and nothing leaves your Mac.

## What you can ask for

Once it's set up, just ask:

- “Put a dock on the left of my second display with Xcode, Terminal and the Agents widget.”
- “Make a Writing profile that hides Apple's Dock and shows only Pages, Safari and a clock.”
- “Switch to my Work profile.” Then: “Undo that.”
- “Add my Downloads folder to the Coding dock as a grid, newest first.”
- “Duplicate the Glass theme, give it rounder corners, and use it on every dock.”
- “Show the build status in my Custom widget: 3 failing, in red.”

The agent starts by reading your current setup (profiles, docks and themes), so it can use the names you already have.

## Setting it up

The MCP server is the `moor` command, so install it first: **Settings → Advanced → Install Command-Line Tool…** (see [Command line](/docs/automation/#command-line-moor)). Moor must be running, and **Settings → Automation → Let apps and agents control Moor** must be on (it's on unless you turned it off).

**Claude Code**:

```sh
claude mcp add moor -- moor mcp
```

**Codex**: add this to `~/.codex/config.toml`:

```toml
[mcp_servers.moor]
command = "moor"
args = ["mcp"]
```

**Cursor**: add this to `~/.cursor/mcp.json` (or a project's `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "moor": { "command": "moor", "args": ["mcp"] }
  }
}
```

**Any other MCP client**: run `moor mcp` as a stdio server. If the client doesn't find `moor` on your PATH, use the full path the installer showed you, like `/usr/local/bin/moor` or `~/.local/bin/moor`.

If Moor isn't running, or the setting is off, the agent is told so and can tell you.

## Tools

| Tool | Does |
|---|---|
| `moor_get_state` | The active profile, and every profile, dock and theme by id and name. Agents call this first |
| `moor_list_profiles` | Every profile in full |
| `moor_switch_profile` | Switch to a profile |
| `moor_create_profile` | New profile: from the current Dock, empty, or a copy of another |
| `moor_update_profile` | Rename, Dock mode, which docks it shows, Dock settings, color, symbol |
| `moor_list_docks` · `moor_get_dock` | Every Custom Dock, or one, with its items |
| `moor_create_dock` | New dock with items, placement, behavior and theme, optionally attached to a profile |
| `moor_update_dock` | Rename, move to another edge or display, auto-hide and other behavior, theme |
| `moor_add_item` · `moor_update_item` · `moor_remove_item` · `moor_move_item` | Change a dock's items |
| `moor_list_themes` · `moor_create_theme` · `moor_update_theme` | Dock themes (built-in themes are duplicated, not edited) |
| `moor_widget_catalog` | Every widget, with its sizes and default settings |
| `moor_set_custom_widget` | Update the [Custom widget](/docs/automation/#custom-widget) |
| `moor_get_settings` · `moor_update_settings` | Moor's settings |
| `moor_run_command` | Next or previous profile, back to the last one, undo, picker, search, edit mode, or any `moor://` link |

Wherever a tool asks for a profile, dock or theme, an agent can give its name instead of its id.

## HTTP API

Scripts can use the same API directly. It's HTTP with JSON, over a Unix socket at `~/Library/Application Support/Moor/moor.sock` (or in `MOOR_STORE_ROOT` if you set it).

With `curl`:

```sh
SOCK="$HOME/Library/Application Support/Moor/moor.sock"

curl --unix-socket "$SOCK" http://localhost/v1/state

curl --unix-socket "$SOCK" -X POST http://localhost/v1/docks \
  -H "Content-Type: application/json" \
  -d '{"name": "Coding", "placement": {"edge": "left"}, "items": [{"type": "app", "bundleID": "com.apple.dt.Xcode"}, {"type": "app", "bundleID": "com.apple.Terminal"}]}'

curl --unix-socket "$SOCK" -X POST http://localhost/v1/profiles/Work/activate
```

Or with the `moor` command, which finds the socket for you and prints the reply as formatted JSON:

```sh
moor api GET /v1/state
moor api POST /v1/docks '{"name": "Coding"}'
moor api PATCH /v1/docks/Coding @dock.json      # body from a file
echo '{"index": 0}' | moor api POST /v1/docks/Coding/items/<item id>/move -
```

`moor api` exits with status 1 when Moor answers with an error (the message goes to stderr), 2 for bad arguments, and 3 when Moor isn't running or the API is off.

### Endpoints

| Method | Path | Does |
|---|---|---|
| GET | `/v1` | Moor's version and the API version |
| GET | `/v1/state` | Active profile; profiles, docks and themes by id and name |
| GET | `/v1/profiles` · `/v1/profiles/{id}` | Every profile, or one |
| POST | `/v1/profiles` | Create: `{name, from?, mode?}`. `from` is `current` (the Dock as it is now), `empty`, or a profile to copy |
| PATCH | `/v1/profiles/{id}` | Change `name`, `mode`, `customDockIDs`, `settings`, `color`, `symbol`, display rules |
| DELETE | `/v1/profiles/{id}` | Delete (not the last one) |
| POST | `/v1/profiles/{id}/activate` | Switch to it |
| GET | `/v1/docks` · `/v1/docks/{id}` | Every Custom Dock, or one |
| POST | `/v1/docks` | Create: `{name, items?, placement?, behavior?, themeID?, profile?}` |
| PATCH | `/v1/docks/{id}` | Change `name`, `placement`, `behavior`, `themeID` |
| DELETE | `/v1/docks/{id}` | Delete (and remove it from profiles) |
| POST | `/v1/docks/{id}/items` | Add `{item, index?}` (at the end without an index) |
| PATCH | `/v1/docks/{id}/items/{itemID}` | Change an item's label, a widget's size or settings, a folder's view, sort or display |
| DELETE | `/v1/docks/{id}/items/{itemID}` | Remove an item |
| POST | `/v1/docks/{id}/items/{itemID}/move` | Move it: `{index}` |
| GET | `/v1/themes` · `/v1/themes/{id}` | Every theme, or one |
| POST | `/v1/themes` | Create: `{name, from?, …}` |
| PATCH | `/v1/themes/{id}` | Change one of your themes (`light`, `dark` and `layout` are merged) |
| DELETE | `/v1/themes/{id}` | Delete one of your themes (docks using it go back to Glass) |
| GET | `/v1/widgets/catalog` | Every widget: kind, name, sizes, default size and settings, whether it needs an account |
| POST | `/v1/widgets/custom/{key}` | Update the Custom widget (`value`, `title`, `badge`, `color`, `symbol`) |
| GET · PATCH | `/v1/settings` | Moor's settings |
| POST | `/v1/commands` | `{command}`: `next`, `prev`, `toggle`, `undo`, `picker`, `search`, `edit-docks`, `focus-off`, `snapshot`. Or `{url}` with any `moor://` link |

`{id}` is an id or a name (not case-sensitive). Replies are the same JSON Moor saves, so you can `GET` something, change it, and send it back. `PATCH` changes only the keys you send.

### Items

Add items in this short form:

| Item | JSON |
|---|---|
| App | `{"type": "app", "bundleID": "com.apple.Safari"}` or `{"type": "app", "path": "/Applications/Safari.app"}` |
| Folder | `{"type": "folder", "path": "~/Downloads", "view": "grid", "sort": "dateAdded", "display": "stack"}`. `view`: `auto`, `grid`, `fan`, `list`; `sort`: `dateAdded`, `name`, `kind`, `dateModified`, `dateCreated`; `display`: `stack`, `folder` |
| File | `{"type": "file", "path": "~/Documents/plan.pdf"}` |
| Link | `{"type": "url", "url": "https://moorapp.com", "label": "Moor"}` |
| Widget | `{"type": "widget", "kind": "time.clock", "size": "wide", "config": {}}`. Kinds, sizes and settings are in `/v1/widgets/catalog`; leave out `config` for the defaults |
| Shortcut | `{"type": "shortcut", "name": "Start Focus"}` |
| Divider, spacer, Trash | `{"type": "divider"}`, `{"type": "spacer", "small": false}`, `{"type": "trash"}` |
| Open apps | `{"type": "runningApps"}`: apps that are open but not in the dock |
| Group | `{"type": "group", "label": "Design", "items": [ … ]}` |

Apps must be installed. Labels default to what Moor would show.

### Errors

Errors come back with an HTTP status and a message written for a person:

```json
{"error": {"code": "not_found", "message": "There's no dock named “Lefty”."}}
```

| Status | `code` | When |
|---|---|---|
| 400 | `bad_request` | Something in the request is wrong (the message says what) |
| 404 | `not_found` | No profile, dock, theme or item with that id or name |
| 409 | `conflict` | Two things have that name (use the id), or the change isn't allowed, like deleting the last profile |
| 503 | `disabled` | The API is turned off |
| 500 | `internal` | Something went wrong in Moor |

## Privacy and safety

- **On your Mac only.** The API is a Unix socket only your user account can open. Nothing listens on a network port, so other computers and web pages can't reach it.
- **API keys stay private.** Keys and accounts for widgets are in your Keychain, and the API can't read or change them. An agent can add a revenue widget, but you connect the account yourself.
- **Turn it off** with **Settings → Automation → Let apps and agents control Moor**. Moor then stops listening.
- **Same as doing it yourself.** Every change goes through the same steps as Moor's own windows and menus, and syncs through iCloud if that's on. A profile switch can be undone (**Undo** in the menu, or ask the agent to undo it). Your original Dock is always kept, as described in [Privacy & safety](/docs/privacy-and-safety/).
- **Want a safety net?** Before letting an agent rework your setup, save a backup with **Settings → Advanced → Export All Profiles…**.

---
Source: https://moorapp.com/docs/api/
