Skip to main content
Glama
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