Getting started
From curl to an agent that can see and drive your Mac, in about three minutes. Four steps: install, connect your client, grant two permissions, ask it to do something.
On this page
curl -fsSL https://macos-mcp.sh | sh # install (verifies checksum + Apple signature)
macos-mcp install --client claude-code # or claude-desktop, cursor, vscode, codex
macos-mcp doctor # prints what to grant, with deep-links
Then restart your client and ask it: “Take a screenshot of my screen.” If that works, everything below is detail.
You need: an Apple Silicon Mac on macOS 15 or later, and an MCP client — Claude Code, Claude Desktop, Cursor, VS Code or Codex. No Python, no Node, no Homebrew required: macos-mcp is a single signed Swift binary.
1 Install
curl -fsSL https://macos-mcp.sh | sh
The installer resolves the latest release, downloads the archive, verifies the SHA-256 checksum and the Apple Developer ID signature and notarization before trusting the binary (it fails closed on any mismatch), installs to /usr/local/bin/macos-mcp, and runs doctor so you see what to grant immediately. Read the script first if you like: it is what macos-mcp.sh serves, byte for byte from the repository.
Prefer to build from source? git clone https://github.com/Ark0N/macos-mcp && cd macos-mcp && swift build (Xcode 26 / Swift 6.1). The binary is then at .build/debug/macos-mcp — use that path everywhere below.
2 Connect your client
One command per client. install merges a stdio server entry into the client's own config file and backs up whatever was there; --dry-run prints the target file and the resulting config without writing anything. Pick yours:
Claude Code
Claude Code owns its config through its own CLI, so install prints the command rather than editing a file behind it:
macos-mcp install --client claude-code
# what it prints, and what you run:
claude mcp add --transport stdio macos-mcp -- /usr/local/bin/macos-mcp serve
claude mcp list # macos-mcp: ✓ Connected
Claude Desktop
macos-mcp install --client claude-desktop
# writes ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"macos-mcp": { "command": "/usr/local/bin/macos-mcp", "args": ["serve"] }
}
}
Quit Claude Desktop completely and reopen it — ⌘Q, not just closing the window. The tools then appear under the tools icon in the composer.
Cursor
macos-mcp install --client cursor
# writes ~/.cursor/mcp.json
{
"mcpServers": {
"macos-mcp": { "command": "/usr/local/bin/macos-mcp", "args": ["serve"] }
}
}
Restart Cursor; the server shows up under Settings → MCP.
VS Code
macos-mcp install --client vscode
# writes ~/Library/Application Support/Code/User/mcp.json
{
"servers": {
"macos-mcp": { "type": "stdio", "command": "/usr/local/bin/macos-mcp", "args": ["serve"] }
}
}
VS Code nests under servers and wants an explicit type — that is the one client whose shape differs.
Codex
macos-mcp install --client codex
# merges into ~/.codex/config.toml
[mcp_servers.macos-mcp]
command = "/usr/local/bin/macos-mcp"
args = ["serve"]
The TOML merge is textual, not parse-and-reserialize: your comments and every other block survive byte for byte.
Anything else
Any MCP client that speaks stdio works. Give it the command and one argument:
command: /usr/local/bin/macos-mcp
args: ["serve"]
Optional flags go in args alongside serve — for example ["serve", "--profile", "readonly"] or ["serve", "--toolsets", "ui,screen,system,files,apps"].
3 Grant two permissions
macOS gates the interesting parts behind two grants: Accessibility for the ui_* tools, and Screen Recording for screenshot, ocr and screen_find. Ask the binary what is missing:
macos-mcp doctor
# Accessibility MISSING needed by: ui
# fix: open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
# Screen Recording MISSING needed by: screen
# fix: open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
Grants attach to the app that hosts the agent, not to the macos-mcp binary. If your agent runs in Claude Desktop, grant Claude Desktop. If it runs in Terminal or iTerm, grant that terminal. Which also means doctor answers for wherever you ran it: a green report in Terminal says nothing about Claude Desktop's grants.
Then restart the client. Screen Recording in particular only binds to processes started after the grant, so a client that was already running keeps failing until it is quit and reopened.
doctor --json is the same report for tooling, and its exit code is the number of missing grants. Three more grants arrive later, only if you use them: Calendars, Reminders and Contacts prompt through the system dialog the first time the apps toolset touches them, and scripting another app with an AppleScript recipe asks for Automation once per app. There is no plus button for Automation — asking is the prompt, and it waits for a click on that Mac's own screen.
4 Ask it to do something
Four prompts that each prove a different part of the stack. Say them to your agent exactly as written:
-
“Take a screenshot of my screen.”
Proves Screen Recording, the server and the client wiring end to end. If this works, the install is good.
-
“Snapshot the frontmost app and click the Save button.”
The accessibility path:
ui_snapshotmaps the app into element ids, thenui_clickpresses one by id — no pointer movement, no focus stolen, and the result says which path it used. -
“Find the word ‘Cancel’ on screen and tell me where it is.”
screen_find: OCR with fuzzy matching, returning clickable points. This is the bridge for apps with no accessibility support. -
“What's on my calendar this week?”
The agent notices
appsis switched off, callsenable_toolset, macOS prompts once, andcalendar_searchanswers. That is gating and first-use consent in a single turn.
Twenty more things to ask it →
Did it work?
Three checks, in the order worth running when something looks wrong:
| Check | Good answer |
|---|---|
macos-mcp doctor — in the app that hosts the agent | No MISSING lines; exit code 0. |
Your client's server list (claude mcp list, or the client's MCP settings pane) | macos-mcp listed as connected. |
| Ask the agent to list its tools | 24 tools across ui, screen, system and files, plus enable_toolset. A short list is gating, not a bug. |
Everything a tool did is on the record: macos-mcp audit tail replays the last calls, audit summary totals them by tool and failure code.
How a call actually runs
Worth knowing once, because it explains most of the results you will see:
-
Your client calls a tool by name
One MCP surface, dispatching by name. Toolset gating, the permission profile and the audit log all sit in that one place — including for Apple's own Safari tools, which are mounted and re-exported rather than reimplemented.
-
The tool acts through the accessibility tree first
An element's own accessibility action needs no pointer, no raised window and no focus. Synthetic events are the fallback, and the result says which one ran as
method. -
The effect is read back
macOS reports success for accessibility writes and synthetic events that did nothing at all. So the field value, the window frame, the pasteboard or the running process is re-read — and a write that changed nothing comes back as a typed
action_unverifiederror, never a green check.
Toolsets and gating
Tools live in named toolsets. Four are on by default — ui, screen, system, files (24 of 44 tools) — which keeps the tool list small enough that models pick well. apps, intents, ios and web are one call away: the agent calls enable_toolset and the server sends list_changed, or you start with more:
macos-mcp serve --toolsets ui,screen,system,files,apps,intents,ios,web
A real tool in a disabled toolset returns a typed pointer to the toolset that has it; an unknown name stays a protocol error. The full tool reference →
Permission profiles
macos-mcp serve --profile readonly # observation only
macos-mcp serve --profile standard # default: destructive tools need a confirmation token
macos-mcp serve --profile full # trusts the caller; raw applescript_eval allowed
Under standard, a destructive tool (trash, app_quit, applescript_eval, and any mounted Apple tool that declares itself destructive) first answers confirmation_required with a one-shot token bound to that exact call; the action runs only when re-issued with the token. The safety model in full →
CLI reference
| Command | What it does |
|---|---|
macos-mcp serve | Run the MCP server over stdio. Flags: --toolsets, --profile readonly|standard|full, --gateway auto|off (mount Apple's Safari/Xcode MCP servers), --gateway-budget-seconds N (how long an upstream gets to handshake; raise it for the first Xcode mount, where a human clicks Allow inside Xcode), --config (default ~/.macos-mcp/config.json). |
macos-mcp doctor | Permission status per toolset with the System Settings deep-link for each missing grant. --json for tooling; exit code = missing grants. |
macos-mcp install --client … | Register with claude-code, claude-desktop, cursor, vscode or codex. --dry-run shows the change; the previous config is backed up. |
macos-mcp audit tail / audit summary | Read the audit log: recent calls, or totals by tool and failure code. |
macos-mcp eval run / eval list | Run the bundled agent-task suite against the real server and print the score (--driver claude has a real model solve the tasks). Skips are reported, never counted as passes. |
macos-mcp schema dump / schema check --baseline … | Snapshot the public tool surface, or diff a build against a released snapshot — the freeze that makes tool names API. |
macos-mcp vm … | Disposable macOS VMs on this host: list, clone, start, stop, destroy, status, screenshot, ip, input, grant, verify. These are CLI subcommands, not MCP tools — the tool surface is unchanged. Disposable macOS VMs → |
Troubleshooting
| What you see | What it means, and the fix |
|---|---|
permission_missing | The error carries the exact System Settings deep-link in fix. Grant it to the hosting app — your client or terminal, not the binary — then restart that app. |
| Screen Recording is granted, screenshots still fail | The grant binds to new processes only. Quit and relaunch the client (or the terminal that runs it). |
| An AppleScript recipe hangs with an Automation hint | macOS asked “Terminal wants access to control Safari” on that Mac's own screen and is waiting for a click. There is no plus button for Automation; asking is the prompt. |
action_unverified | The tool ran, the macOS API reported success, and the UI did not change. That is macOS being honest through us. Re-snapshot and try the element's own action, or the synthetic fallback, as the error suggests. |
| The tool list looks short | That is gating, not a failure. Ask the agent to enable_toolset, or pass --toolsets at launch. |
| The server is not listed in the client at all | Re-run macos-mcp install --client … --dry-run to see which file it targets, confirm the binary path in that file exists, and restart the client. |
refused on a password field | Working as intended, permanently. A secure text field is never read and never typed into; the error names an alternative instead. |
Uninstall
rm /usr/local/bin/macos-mcp
rm -r ~/.macos-mcp # config + audit log
# then remove the macos-mcp entry from your client's MCP config
# (install left a backup next to it), and revoke the grants in
# System Settings → Privacy & Security if you no longer want them.
Next
- Things to ask it — twenty prompts that work, and the tools behind each.
- Tool reference — every argument, default and error code.
- Safety model — profiles, confirmation tokens, audit, hard lines.
- The iPhone toolset — driving a real handset through iPhone Mirroring.
- Disposable Macs — give an agent a Mac it may destroy.
- Migrating from apple-mcp — the tool map.