MCP-AppleScript
by frouaix
README.md
# MCP-AppleScript
A local MCP server that exposes controlled AppleScript automation tools to MCP clients on macOS.
> [!CAUTION]
> **This software can read, create, modify, and delete your personal data** across Notes, Calendar, Reminders, Mail, Contacts, Messages, Photos, Music, Finder, and Safari.
>
> By running this server you are granting an AI model the ability to interact with your macOS applications on your behalf. Although multiple safety layers exist (operation modes, per-app allowlists, destructive-action confirmation), **no automated safeguard is foolproof**. An unexpected prompt, a misconfigured policy, or a model hallucination could result in **data loss, disclosure of private information, or unintended actions** such as sending messages or emails.
>
> **You are solely responsible for:**
> - Reviewing and understanding the [configuration](#configuration) and [policy model](#policy-model) before enabling any app
> - Starting in `readonly` mode and only escalating when you understand the consequences
> - Keeping the number of enabled apps to the minimum you actually need
> - Never running in `full` mode unattended
>
> This project is provided **as-is, with no warranty**. See [LICENSE](LICENSE).
## Overview
MCP-AppleScript provides a secure bridge between the [Model Context Protocol](https://modelcontextprotocol.io/) and macOS automation via AppleScript. It consists of two components:
- **MCP Server (TypeScript/Node.js)**: Handles the MCP protocol, tool schemas, configuration, validation, logging, and policy enforcement
- **Swift Executor**: Executes AppleScript commands via `NSAppleScript` and returns structured JSON results
## Tools
All 10 Apple apps are accessed through generic `app.*` tools with an `app` parameter:
| Tool | Mode | Description |
|------|------|-------------|
| `applescript.ping` | readonly | Health check — returns server version and supported apps |
| `applescript.get_mode` | readonly | Get current operation mode and enabled tools |
| `applescript.set_mode` | readonly | Change operation mode (readonly/create/full) |
| `app.list_containers` | readonly | List containers (folders, calendars, mailboxes, playlists, etc.) |
| `app.list` | readonly | List items in a container with pagination |
| `app.get` | readonly | Get a single item by ID |
| `app.search` | readonly | Search/filter items |
| `app.create` | create | Create a new item |
| `app.action` | create | App-specific actions (send, play, complete, do_javascript, etc.) |
| `applescript.run_template` | create | Execute a registered template by ID (policy-gated) |
| `app.update` | full | Update an item (confirmation required) |
| `app.delete` | full | Delete an item (confirmation required) |
| `applescript.run_script` | full | Execute raw AppleScript (confirmation required) |
### Supported Apps
Notes, Calendar, Reminders, Mail, Contacts, Messages, Photos, Music, Finder, Safari
## Operation Modes
The server starts in **readonly** mode by default. Use `applescript.set_mode` to change modes on-the-fly:
| Mode | Description | Available Tools |
|------|-------------|-----------------|
| **readonly** | No creation, editing, or deleting | ping, get_mode, set_mode, app.list/get/search/list_containers |
| **create** | Readonly + creation allowed | + app.create, app.action, run_template |
| **full** | All operations, potentially destructive | + app.update, app.delete, run_script (requires confirmation) |
When the mode changes, the client is notified via `notifications/tools/list_changed` and will only see tools available in the current mode.
### Destructive Action Confirmation
In **full** mode, destructive tools (`app.update`, `app.delete`, `run_script`) require user confirmation:
1. If the MCP client supports **elicitation**, a confirmation dialog is shown
2. Otherwise, a **confirmation token** is returned — pass it back in a second call to confirm
## Requirements
- macOS 12.0 or later
- Node.js 20+ (only for building from source)
- Swift 5.9+ (only for building from source)
- pnpm 8+ (only for building from source)
## Installation
### Option 1: Download pre-built binary (.dmg)
Download the latest `.dmg` from [GitHub Releases](https://github.com/frouaix/MCPAppleScript/releases):
1. Open the `.dmg` and copy `mcp-applescript` to `/usr/local/bin/`:
```bash
sudo cp /Volumes/MCP-AppleScript\ */mcp-applescript /usr/local/bin/
```
2. Create a config file:
```bash
mkdir -p ~/.config/applescript-mcp
cat > ~/.config/applescript-mcp/config.json << 'EOF'
{
"defaultMode": "readonly",
"apps": {
"com.apple.Notes": { "enabled": true },
"com.apple.iCal": { "enabled": true },
"com.apple.reminders": { "enabled": true },
"com.apple.mail": { "enabled": true },
"com.apple.Contacts": { "enabled": true }
}
}
EOF
```
3. Add to your MCP client config (see Claude Desktop below)
The pre-built binary is a self-contained executable with Node.js and the Swift executor embedded — no runtime dependencies required.
### Option 2: Build from source
```bash
git clone https://github.com/frouaix/MCPAppleScript.git
cd MCPAppleScript
./install.sh
```
The install script will:
1. Install Node.js dependencies
2. Build the TypeScript MCP server
3. Build and install the Swift executor to `/usr/local/bin/`
4. Create a default config at `~/.config/applescript-mcp/config.json`
### Claude Desktop Integration
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"applescript": {
"command": "/usr/local/bin/mcp-applescript"
}
}
}
```
If building from source, use the dev path instead:
```json
{
"mcpServers": {
"applescript": {
"command": "node",
"args": ["/path/to/MCPAppleScript/packages/mcp-server/dist/index.js"]
}
}
}
```
## Configuration
Configuration lives at `~/.config/applescript-mcp/config.json` (override via `APPLESCRIPT_MCP_CONFIG` env var):
```json
{
"executorPath": "/usr/local/bin/applescript-executor",
"defaultTimeoutMs": 12000,
"defaultMode": "readonly",
"modes": {
"readonly": ["applescript.ping", "applescript.get_mode", "applescript.set_mode", "app.list_containers", "app.list", "app.get", "app.search"],
"create": ["app.create", "app.action", "applescript.run_template"],
"full": ["app.update", "app.delete", "applescript.run_script"]
},
"apps": {
"com.apple.Notes": { "enabled": true },
"com.apple.iCal": { "enabled": true },
"com.apple.reminders": { "enabled": true },
"com.apple.mail": { "enabled": true },
"com.apple.Contacts": { "enabled": true },
"com.apple.MobileSMS": { "enabled": true },
"com.apple.Photos": { "enabled": true },
"com.apple.Music": { "enabled": true },
"com.apple.finder": { "enabled": true },
"com.apple.Safari": { "enabled": true }
},
"runScript": {
"enabled": false,
"allowedBundleIds": []
},
"logging": {
"level": "info",
"redact": ["email", "content", "body"]
}
}
```
### Modes
The `modes` section controls which tools are available at each operation mode level. Modes are cumulative — `create` includes all `readonly` tools, `full` includes all `create` tools. You can customize this to promote tools to a lower mode or restrict them to a higher one.
### Policy Model
- **Per-app allowlists**: Each app must be explicitly configured and enabled
- **Per-tool permissions**: Control which tools can target which apps
- **Per-mode tool gating**: Each tool requires a minimum mode level (configurable via `modes`)
- **`run_script` disabled by default**: Raw AppleScript execution requires explicit opt-in
- **Timeouts enforced**: All operations are time-bounded
## Automation Permissions (TCC)
On first use, macOS will prompt for automation permissions:
1. Open **System Settings** → **Privacy & Security** → **Automation**
2. Find your terminal or the executor binary
3. Enable permissions for the apps you want to automate (Notes, Calendar, Reminders, Mail, Contacts, etc.)
If you see `AUTOMATION_DENIED` errors, check these permissions.
## Architecture
```
MCP Client (Claude, etc.)
↕ stdio (JSON-RPC)
TypeScript MCP Server
↕ JSON over stdin/stdout
Swift Executor (applescript-executor)
↕ Apple Events
macOS Apps (Notes, Calendar, Reminders, Mail, Contacts, Messages, Photos, Music, Finder, Safari)
```
The Node process is the only MCP-facing component. Swift is a helper invoked locally for each tool call. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for details.
## Development
```bash
# Install dependencies
pnpm install
# Build everything
pnpm build
# Run unit tests (150 tests)
pnpm test:unit
# Run integration tests (4 tests, requires macOS)
pnpm test:integration
# Build Swift executor
cd packages/executor-swift && swift build
# Run the server in development mode
cd packages/mcp-server && pnpm dev
```
### Building the standalone binary
```bash
# Build self-contained binary (Node.js SEA + embedded Swift executor)
pnpm build:sea
# Package as .dmg
pnpm build:dmg
```
Output: `dist/mcp-applescript` (~107MB, ~40MB as .dmg)
## Security
- **Three operation modes** (readonly → create → full) with safe default
- **Destructive action confirmation** via MCP elicitation or confirmation tokens
- **Template-based execution** prevents arbitrary script injection
- **Per-app, per-tool permission model** with explicit allowlists
- **Input validation** with Zod schemas on all tool parameters
- **Sensitive data redaction** in logs (configurable)
- **Timeout enforcement** on all executor operations
- **Stable error codes** for all failure modes
## Project Structure
```
MCPAppleScript/
packages/
mcp-server/ # TypeScript MCP server
src/
index.ts # Stdio entrypoint
server.ts # MCP server + tool registration
sea.ts # SEA binary support (executor extraction)
adapters/ # ResourceAdapter pattern: per-app adapters (10 apps)
config/ # Configuration loading + Zod schemas
mode/ # Operation mode manager + confirmation
policy/ # Allowlist/denylist enforcement
exec/ # Executor spawning + IPC
util/ # Errors, logging, JSON utils
executor-swift/ # Swift executor CLI
Sources/Executor/
main.swift # JSON dispatcher
AppleScriptRunner.swift # Template dispatch to per-app modules
{App}Templates.swift # Per-app AppleScript templates (10 files)
AppTargeting.swift # Bundle ID handling
Errors.swift # Error code mapping
JsonIO.swift # Stdin/stdout JSON I/O
scripts/
build-sea.sh # Build self-contained binary (Node.js SEA)
build-dmg.sh # Package binary as .dmg
docs/ # Architecture documentation
install.sh # One-step installer (build from source)
```
## License
MIT — see [LICENSE](LICENSE)
## Author
François Rouaix
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues