mac-pilot-mcp
<p align="center">
<h1 align="center">mac-pilot-mcp</h1>
<p align="center">
<strong>Self-learning macOS automation for AI agents</strong>
</p>
<p align="center">
The only macOS MCP server that <em>remembers</em>. Save what works, skip what doesn't.
</p>
<p align="center">
<a href="https://www.npmjs.com/package/mac-pilot-mcp"><img src="https://img.shields.io/npm/v/mac-pilot-mcp?color=cb3837&label=npm" alt="npm version"></a>
<a href="https://www.npmjs.com/package/mac-pilot-mcp"><img src="https://img.shields.io/npm/dm/mac-pilot-mcp?color=cb3837" alt="npm downloads"></a>
<a href="https://github.com/leesgit/mac-pilot-mcp"><img src="https://img.shields.io/github/stars/leesgit/mac-pilot-mcp?style=social" alt="GitHub stars"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
<a href="#"><img src="https://img.shields.io/badge/platform-macOS-000?logo=apple&logoColor=white" alt="macOS"></a>
<a href="#"><img src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white" alt="Node.js"></a>
</p>
</p>
---
## The Problem
There are 7+ macOS automation MCP servers. **None of them remember anything.** Every session starts from zero — same trial and error, same failures, same wasted tokens.
## The Solution
Mac-Pilot is different. It **learns from every interaction**:
- **Success?** The pattern is auto-saved as app-specific knowledge
- **Failure?** The error is recorded so it won't repeat the same mistake
- **Multi-step workflow?** Save it as a recipe — replay it in one call next time
Ships with **21 built-in recipes** so you're productive from the first run.
---
## Quick Start
### Install
```bash
npm install -g mac-pilot-mcp
```
### Connect to your AI client
<details>
<summary><strong>Claude Code</strong></summary>
```bash
claude mcp add mac-pilot -- mac-pilot-mcp
```
Or manually add to `~/.claude.json`:
```json
{
"mcpServers": {
"mac-pilot": {
"command": "mac-pilot-mcp"
}
}
}
```
</details>
<details>
<summary><strong>Claude Desktop</strong></summary>
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"mac-pilot": {
"command": "npx",
"args": ["-y", "mac-pilot-mcp"]
}
}
}
```
</details>
<details>
<summary><strong>Cursor</strong></summary>
Add to your MCP settings (`.cursor/mcp.json`):
```json
{
"mcpServers": {
"mac-pilot": {
"command": "npx",
"args": ["-y", "mac-pilot-mcp"]
}
}
}
```
</details>
<details>
<summary><strong>Windsurf</strong></summary>
Add to your MCP config:
```json
{
"mcpServers": {
"mac-pilot": {
"command": "npx",
"args": ["-y", "mac-pilot-mcp"]
}
}
}
```
</details>
### Grant Permissions
Mac-Pilot needs **Accessibility** access for UI automation:
1. **System Settings** → **Privacy & Security** → **Accessibility**
2. Toggle **ON** for your terminal app (Terminal, iTerm2, VS Code, Cursor, etc.)
3. Restart the terminal
---
## How It Works
```
You: "Export the current Figma frame as PNG"
┌─ First time ─────────────────────────────────────────┐
│ │
│ 1. mac_recipe_search("Figma export") → No matches │
│ 2. mac_state() → Figma is frontmost │
│ 3. mac_run(applescript) → File > Export > PNG │
│ 4. mac_recipe_save("export-figma-png", steps=[...]) │
│ │
│ ✓ Worked! Pattern saved automatically. │
└───────────────────────────────────────────────────────┘
┌─ Next time ───────────────────────────────────────────┐
│ │
│ 1. mac_recipe_search("Figma export") │
│ → Found: export-figma-png (100% success rate) │
│ 2. mac_recipe_run("export-figma-png") │
│ → Done instantly │
│ │
│ ⚡ 4 steps → 2 steps. No trial and error. │
└───────────────────────────────────────────────────────┘
```
---
## Tools
| Tool | What it does |
|------|-------------|
| **`mac_run`** | Execute AppleScript, JXA, shell commands, open apps/URLs, click, type, keypress |
| **`mac_state`** | Query system state — frontmost app, windows, clipboard, running apps |
| **`mac_find_ui`** | Find UI elements via Accessibility API (buttons, fields, menus) |
| **`mac_screenshot`** | Capture screen/window/region as base64 PNG |
| **`mac_recipe_save`** | Save a working action sequence as a reusable recipe |
| **`mac_recipe_run`** | Replay a saved recipe with parameter substitution |
| **`mac_recipe_search`** | Full-text search across recipes and action history |
---
## Built-in Recipes
21 recipes ship out of the box — no setup needed:
| Category | Recipes | Example |
|----------|---------|---------|
| **System** | `toggle-dark-mode` `set-volume` `mute-toggle` `lock-screen` `show-desktop` `screenshot-desktop` `empty-trash` `get-dark-mode` | `mac_recipe_run { name: "toggle-dark-mode" }` |
| **Finder** | `new-finder-window` `get-selected-files` | `mac_recipe_run { name: "new-finder-window", params: { path: "/tmp" } }` |
| **Safari** | `safari-current-url` `safari-current-title` | `mac_recipe_run { name: "safari-current-url" }` |
| **Clipboard** | `get-clipboard` `set-clipboard` | `mac_recipe_run { name: "set-clipboard", params: { text: "Hello" } }` |
| **Notifications** | `notify` | `mac_recipe_run { name: "notify", params: { title: "Done", message: "Build passed" } }` |
| **Terminal** | `open-terminal-at` `kill-process` | `mac_recipe_run { name: "open-terminal-at", params: { path: "~/dev" } }` |
| **Windows** | `list-windows` `close-front-window` | `mac_recipe_run { name: "close-front-window" }` |
| **Music** | `music-play-pause` `music-next-track` | `mac_recipe_run { name: "music-play-pause" }` |
---
## Examples
### AppleScript
```json
{ "actionType": "applescript", "script": "tell application \"Finder\" to get name of every window" }
```
### JXA (JavaScript for Automation)
```json
{ "actionType": "jxa", "script": "Application('Safari').documents[0].url()" }
```
### Shell command
```json
{ "actionType": "shell", "command": "ls -la ~/Desktop" }
```
### Open an app or URL
```json
{ "actionType": "open", "target": "Safari" }
{ "actionType": "open", "target": "https://github.com" }
```
### Type text
```json
{ "actionType": "type", "text": "Hello World" }
```
### Keyboard shortcut
```json
{ "actionType": "keypress", "text": "cmd+c" }
{ "actionType": "keypress", "text": "cmd+shift+4" }
```
### Find UI elements
```json
mac_find_ui { "app": "Safari", "role": "AXButton" }
mac_find_ui { "app": "Finder", "searchText": "Downloads" }
```
**Electron apps (VSCode / Cursor / Slack / Discord)** — macOS Accessibility
exposes a thin tree for Electron, so Mac-Pilot can optionally fall back to
Chrome DevTools Protocol when the app is launched with
`--remote-debugging-port=<PORT>`. See [docs/ELECTRON-SUPPORT.md](https://github.com/leesgit/mac-pilot-mcp/blob/main/docs/ELECTRON-SUPPORT.md).
```json
mac_find_ui {
"app": "Visual Studio Code",
"searchText": "Run Test",
"useElectronFallback": "auto"
}
```
### Take a screenshot
```json
mac_screenshot { "target": "screen", "scale": 0.3 }
mac_screenshot { "target": "window", "windowName": "Safari" }
```
### Save a custom recipe
```json
mac_recipe_save {
"name": "open-project",
"description": "Open VS Code at project directory",
"steps": [
{ "actionType": "shell", "params": { "command": "code {{path}}" }, "description": "Open VS Code" }
],
"parameters": [
{ "name": "path", "description": "Project directory path" }
],
"tags": ["dev", "vscode"]
}
```
---
## Security
Mac-Pilot blocks dangerous operations before they execute:
| Layer | Protection |
|-------|-----------|
| **Hard block** | `sudo`, `rm -rf /`, `curl\|sh`, `dd if=`, `$()` subshell injection, keychain access, `csrutil disable`, `diskutil erase`, and 20+ patterns |
| **Risk classification** | Every action is rated `low` / `medium` / `high` / `blocked` |
| **Audit log** | All actions (including blocked ones) are logged to SQLite |
| **Dry run** | Test any action with `dryRun: true` before executing |
| **Auto-cleanup** | Action logs older than 30 days are pruned automatically |
---
## Architecture
```
~/.mac-pilot/pilot.db (SQLite, WAL mode)
├── action_log — Every action executed, with timing + success/failure
├── action_log_fts — Full-text search index over action history
├── recipes — Saved automation sequences
├── recipes_fts — Full-text search index over recipes
├── app_knowledge — Per-app quirks, selectors, workarounds (auto-learned)
└── security_log — Blocked command audit trail
```
Built-in recipes are auto-loaded on first run. Your custom recipes and learned knowledge persist across sessions.
---
## Comparison
| Feature | mac-pilot-mcp | Other MCP servers |
|---------|:---:|:---:|
| Self-learning (auto-saves knowledge) | **Yes** | No |
| Reusable recipes with parameters | **Yes** | No |
| Built-in recipe library | **21** | 0 |
| JXA + AppleScript | **Both** | Usually one |
| Full-text search (recipes + history) | **Yes** | No |
| Security audit log | **Yes** | Rare |
| Risk classification (4 levels) | **Yes** | No |
| Dry run mode | **Yes** | Rare |
| Action log auto-cleanup | **Yes** | No |
---
## Troubleshooting
<details>
<summary><strong>"Accessibility access not allowed"</strong></summary>
Your terminal needs Accessibility permission:
1. **System Settings** → **Privacy & Security** → **Accessibility**
2. Toggle **ON** for your terminal
3. **Restart** the terminal app completely
</details>
<details>
<summary><strong>"Application not found or not running"</strong></summary>
The target app must be running. Open it first:
```json
mac_run { "actionType": "open", "target": "AppName" }
```
</details>
<details>
<summary><strong>Screenshots are too large / slow</strong></summary>
Reduce the scale (default is 0.5):
```json
mac_screenshot { "target": "screen", "scale": 0.3 }
```
</details>
<details>
<summary><strong>Recipe not found</strong></summary>
Recipe names are case-sensitive. Search first:
```json
mac_recipe_search { "query": "your keyword" }
```
</details>
<details>
<summary><strong>Command blocked unexpectedly</strong></summary>
Use dry run to check the risk classification:
```json
mac_run { "actionType": "shell", "command": "your-command", "dryRun": true }
```
</details>
---
## Requirements
- **macOS** (darwin only)
- **Node.js** >= 18
- **Accessibility** permission for UI automation
---
## Contributing
Issues and PRs welcome at [github.com/leesgit/mac-pilot-mcp](https://github.com/leesgit/mac-pilot-mcp).
```bash
git clone https://github.com/leesgit/mac-pilot-mcp.git
cd mac-pilot-mcp
npm install
npm run build
npm test # 144 tests
```
---
## License
MIT - [Byeongchang Lee](https://github.com/leesgit)
TDQS
Scored across 11 tools
Each tool targets a distinct aspect of macOS automation: mac_run executes commands, mac_state reads system state, mac_clipboard manages the clipboard, mac_find_ui inspects UI elements, mac_screenshot captures the screen, and the recipe* tools cover the full recipe lifecycle. There is no meaningful overlap; even mac_run and mac_recipe_run are clearly separated by ad-hoc vs saved workflows.
All tools share the mac_ prefix and use snake_case, but the verb/noun order varies: mac_run and mac_find_ui are verb-first, while mac_state and mac_permissions are noun-first, and recipe tools use recipe_verb (e.g., mac_recipe_export). This is a minor deviation from a fully consistent verb_noun pattern but remains predictable and readable.
With 11 tools, the server is well-scoped for macOS automation. It covers command execution, state inspection, clipboard, UI inspection, screenshots, permissions, and a complete recipe subsystem without bloating. The count sits comfortably in the ideal 3-15 range and each tool earns its place.
The tool surface covers core automation actions (run, state, UI, screenshot, clipboard) and a full recipe lifecycle (save, run, search, export, import). Minor gaps exist, such as no explicit recipe deletion or a dedicated list-all tool, but these can be worked around via search or export, so agent workflows are not severely hindered.