mcp-mac-control
# mcp-mac-control
[](https://www.npmjs.com/package/@dockndevai/mcp-mac-control)
[](https://github.com/dockndevai/mcp-mac-control/actions/workflows/ci.yml)
[](LICENSE)
A [Model Context Protocol](https://modelcontextprotocol.io) server that gives an AI agent **full control of a Mac — like a person sitting at it**. Shell, AppleScript, files, processes, and **human-like GUI control**: move, click, double/right-click, **drag**, **scroll**, type, and press key combos — with a screenshot + window/screen-size perception loop.
> ⚠️ **This server is full-control by default.** It starts in `admin` mode with command execution, deletes, and GUI input all enabled. That is powerful and dangerous: anything the agent reads (a web page, an email, a file) could contain a prompt injection that then runs arbitrary code on your Mac. Only connect it to an agent and content you trust. Set **`MACCTL_SAFE_MODE=true`** to flip the whole thing to safe-by-default. If you want safe-by-default as the baseline, use the sibling [`@dockndevai/mcp-macos`](https://github.com/dockndevai/mcp-macos) instead.
Part of the [dockndevai MCP server suite](https://dockndevai.github.io/).
## What it gives an agent (26 tools)
**Perceive** — `screenshot`, `get_screen_size`, `list_windows`, `get_frontmost_app`, `list_apps`, `system_info`, `list_directory`, `read_file`, `list_processes`, `get_clipboard`
**Operate the desktop like a human** — `move_mouse`, `click` (left/right/double), `drag`, `scroll`, `type_text`, `key_press` (with ⌘/⌥/⌃/⇧), `activate_app`, `quit_app`, `open`, `set_clipboard`, `notify`, `write_file`
**Full power** — `run_command` (any program, no shell unless you ask for one), `run_applescript` (AppleScript/JXA — drive any scriptable app), `delete_path` (→ Trash), `kill_process`
The classic loop: `screenshot` → decide → `click`/`type`/`drag`/`scroll` → `screenshot` again.
## Install
```bash
npx -y @dockndevai/mcp-mac-control
```
macOS only. You'll need to grant the host app (Terminal, your IDE, Claude Desktop, …) macOS permissions the first time each capability is used:
- **Screen Recording** → for `screenshot`
- **Accessibility** → for GUI input (`click`, `type_text`, `drag`, `scroll`, `key_press`) and `list_windows`
- **Automation** → for AppleScript / app control
- Mouse control uses **cliclick**: `brew install cliclick`
## Configure (Claude Code)
```bash
claude mcp add mac-control -- npx -y @dockndevai/mcp-mac-control
```
That's it — it's full-control by default. To scope it down, add env flags (see below). See [docs/CLIENTS.md](docs/CLIENTS.md) for Claude Desktop / Cursor / Codex / VS Code / Windsurf, and [.env.example](.env.example) for every variable.
## Dialing the control up or down
Full control needs no configuration. Everything below is about **restricting** it:
| Variable | Default | Effect |
|---|---|---|
| `MACCTL_SAFE_MODE` | `false` | `true` → read-only, every power gated, confirmations on (safe-by-default) |
| `MACCTL_MODE` | `admin` | `read-only` / `read-write` / `admin` — caps which tools are registered |
| `MACCTL_ALLOW_EXEC` | `true` | shell / AppleScript / kill |
| `MACCTL_ALLOW_DELETE` | `true` | delete to Trash |
| `MACCTL_ALLOW_INPUT` | `true` | GUI input (mouse/keyboard) |
| `MACCTL_CONFIRM` | `false` | `true` → destructive ops pause for human approval via MCP elicitation |
| `MACCTL_PATH_ALLOWLIST` | (empty = anywhere) | confine file ops to these roots |
| `MACCTL_PROTECTED_PATHS` | (empty) | roots readable but never modified/deleted |
| `MACCTL_COMMAND_ALLOWLIST` | (empty = any) | restrict `run_command` to these programs |
| `MACCTL_DRY_RUN` | `false` | validate + log writes without executing |
| `MACCTL_AUDIT_LOG` | `true` | JSON audit line per guarded op, to stderr |
The policy engine ([`src/security.ts`](src/security.ts)) is the same graduated model as the rest of the suite — this server just ships it wide open by default. See [SECURITY.md](SECURITY.md).
## AI risk guard (optional)
For an extra layer on top of the static rules, this server can consult a local
[**laya-guard**](https://github.com/dockndevai/laya-guard) daemon before running a high-risk tool
(`run_command`, `run_applescript`, `delete_path`). The guard classifies the actual command —
deterministic patterns plus a local decision model — as **allow / confirm / block**. It runs *after*
the deterministic policy and can only **tighten** (add a confirm or block), never grant.
| Variable | Default | Effect |
|---|---|---|
| `MACCTL_GUARD_MODE` | `off` | `monitor` (log what it would do) / `enforce` (block or require confirm) |
| `MACCTL_GUARD_URL` | `http://127.0.0.1:8799` | the local laya-guard daemon |
| `MACCTL_GUARD_TIMEOUT_MS` | `2000` | per-check timeout |
| `MACCTL_GUARD_FAIL_CLOSED` | `confirm` | when the daemon is unreachable in enforce mode: `confirm` or `allow` |
Run the daemon with `pipx install laya-guard && laya-guard`. Start in `monitor` to see what it catches,
then switch to `enforce`.
## Developing
```bash
npm install
npm run build
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js # list tools
npm test
```
## Licence
MIT
TDQS
Scored across 26 tools
Most tools target clearly distinct actions and resources, so an agent can generally pick the right one. The main ambiguity is between open (with app=true) and activate_app for launching apps, and run_command vs run_applescript overlap in arbitrary execution.
The set mostly follows predictable verb_prefix patterns: list_*, get_*, read/write, run_*, and *_app. A few bare verbs like screenshot, notify, open, click, and scroll, plus system_info as a noun, break strict consistency but the groups remain readable.
26 tools is on the heavy end, but the server's domain—macOS UI automation, file access, process control, clipboard, apps, and system info—is genuinely broad. Each tool has an identifiable purpose and none feel redundant.
The surface covers the core macOS automation lifecycle: inspect, interact, input, file read/write/delete, process list/kill, app management, clipboard, and notifications. Minor gaps exist such as no directory creation, no file copy/move, and no arbitrary window picker, but agents can work around these.