Skip to main content
Glama
anmolol117

AppleScript Automation MCP

by anmolol117
README.md
# AppleScript Automation MCP

A local [MCP](https://modelcontextprotocol.io) server that exposes macOS
automation actions (AppleScript + a few CLIs) as tools, so any MCP client
running on your Mac (Claude Desktop, Cursor, etc.) can drive them.

It runs locally over stdio — `osascript` needs the real machine, so this can't
be a remote/hosted server. Non-destructive only: no delete/erase/shutdown/trash.

## Quickstart

macOS, Python 3.10+. Clone and run the installer — it sets up a venv and wires
the server into Claude Desktop for you:

```bash
git clone https://github.com/anmolol117/applescript-automation-mcp.git
cd applescript-automation-mcp
bash install.sh
```

Then quit Claude Desktop completely (Cmd-Q) and reopen it. That's it — the rest
of this README is for manual setup, HTTP mode, and permissions.

`install.sh` backs up your existing `claude_desktop_config.json` (to `.bak`) and
merges in the `applescript` server without touching other servers.

## Install the skill (recommended)

The server gives Claude the ability to act on your Mac. The bundled
`mac-control` skill teaches it *when* and *how* — known-good AppleScript
recipes, the lookup-then-execute pattern for music, and when to confirm
before acting. The tools work without it, but Claude reaches for them
less reliably.

`install.sh` builds `dist/mac-control.zip`. In Claude Desktop, go to
**Customize → Skills → "+"** and upload it, then toggle it on. Skills
install at the account level, so it follows you to claude.ai too.

To package it by hand:

```bash
cd skills && zip -r ../dist/mac-control.zip mac-control
```

The zip must contain `mac-control/` at the top level — zipping from the
repo root buries it under `skills/` and the upload will fail.

The installed skill is a copy, not a link: pulling a new version of this
repo won't update it. Re-upload after an update.


## Install (manual)

```bash
pip install -r requirements.txt        # or: pip install "mcp>=1.2.0"
python server.py                       # sanity check; Ctrl-C to stop
```

Python 3.10+.

## Connect a client

**Claude Desktop (local).** Install into a venv, then point the config at that
venv's Python (Claude Desktop launches with a minimal environment, so bare
`python3` usually won't find `mcp`):

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` with
**absolute** paths for both the interpreter and the script:

```json
{
  "mcpServers": {
    "applescript": {
      "command": "/ABSOLUTE/PATH/applescript-automation-mcp/.venv/bin/python",
      "args": ["/ABSOLUTE/PATH/applescript-automation-mcp/server.py"]
    }
  }
}
```

Quit Claude Desktop completely and reopen it. Connection status and logs live
under **Settings → Developer**. Restart after any config edit.

Other MCP clients (Cursor, etc.) take the same `command`/`args` shape.

## Run as an HTTP URL

The same file can serve over HTTP instead of stdio, for URL-based clients:

```bash
MCP_TRANSPORT=streamable-http python3 server.py
# serves at http://127.0.0.1:8000/mcp
```

Override `MCP_HOST` / `MCP_PORT` if needed. This still runs on *your* Mac —
it's a local URL, not a hosted one.

## Using with Claude connectors — read this first

Claude custom connectors expect a **public HTTPS MCP endpoint**. This server
controls your Mac via `osascript`, so it can only ever run **on your Mac** — a
hosted connector on Anthropic's servers would have no Mac to control and every
tool would fail. There is no way to make this a normal hosted connector.

Two real options:

1. **Local client (recommended).** Use the stdio config above with a client
   that runs on your Mac (Claude Desktop, Cursor). No URL, no tunnel, nothing
   exposed to the internet.
2. **Tunnel the local HTTP server (advanced, risky).** Run the HTTP mode and
   expose it with a tunnel (e.g. `cloudflared tunnel --url http://localhost:8000`
   or `ngrok http 8000`), then give Claude the public `…/mcp` URL. This works
   only while your Mac and the tunnel stay up, and it **exposes full control of
   your Mac to a public URL** — anyone who reaches it can run these tools. Only
   do this behind authentication you add yourself, and never leave it running.

## Push to GitHub

```bash
git init && git add . && git commit -m "AppleScript automation MCP server"
git branch -M main
git remote add origin https://github.com/anmolol117/applescript-automation-mcp.git
git push -u origin main
```

The repo is the *source*; users clone it and connect via stdio (option 1). A
GitHub URL is not itself an MCP endpoint.

## Permissions (required)

The first time a tool controls an app, macOS prompts for permission. Approve
them, or pre-grant under **System Settings → Privacy & Security**:

- **Automation** — the client app (or the terminal launching the server) must be
  allowed to control Messages, Notes, Mail, Spotify, Photos, Contacts,
  System Events, etc.
- **Accessibility** — needed by tools that send keystrokes (`search_photos`).
- **Screen Recording** — needed for `screenshot`.

If a tool returns a permission error, it's one of these grants.

## Tools

Actions: `run_applescript`, `open_app`, `close_app`, `set_volume`, `mute`,
`unmute`, `toggle_dark_mode`, `lock_screen`, `set_clipboard`, `notify`, `say`,
`open_url`, `search_google`, `search_youtube`, `screenshot`, `compose_mail`,
`send_imessage`, `facetime_call`, `create_note`, `create_reminder`,
`maps_directions`, `search_photos`, `spotify_play`, `spotify_control`,
`run_shortcut`.

Read-only: `frontmost_app`, `list_running_apps`, `get_volume`, `is_muted`,
`battery_status`, `get_ip`, `get_datetime`, `disk_usage`, `memory_stats`,
`get_clipboard`, `lookup_contact`, `now_playing`, `list_shortcuts`.

`run_applescript` is the primary tool — most actions are better expressed as a
script than as a wrapper. The wrappers exist for things AppleScript can't reach
directly (`screenshot`, `battery_status`, clipboard, Shortcuts) and for the
handful of recipes worth having pinned.

## Music

Playback targets **Spotify**, not the Music app. `spotify_play` takes a Spotify
URI:

```
spotify_play(track_uri="spotify:track:3WFgRhgECxQYyy8Ldhk2PG")
```

Resolve the URI first — the Spotify connector's search, or any Spotify client's
"Copy Spotify URI". `spotify_control` handles play/pause/next/previous, and
`now_playing` reports the current Spotify track.

## Model guidance

Two things steer how a model uses this server:

- The `INSTRUCTIONS` string in `server.py`, returned on initialize and in
  context every session. It says the server is for **executing**, not
  describing, and that other connectors are for lookup while this one acts.
- Tool docstrings, written as **triggers** ("use when the user says play, put
  on, open…") rather than labels. This matters more than it looks — a model
  picks tools by matching intent against descriptions.

- The bundled `mac-control` skill, which carries the longer recipes and user
  defaults that don't fit in `INSTRUCTIONS`. See
  [Install the skill](#install-the-skill-recommended).
  
## Safety

`run_applescript` refuses destructive constructs — `delete`, `erase`,
`empty trash`, `shutdown`, `restart`, `sudo`, `rm -rf`, `diskutil`. The check
strips string literals before matching, so an email body containing the word
"delete" is fine while `delete note 1` is blocked.

This is a guardrail against accidents, **not a security boundary**. It can't see
inside `do shell script`, so a destructive shell command in a quoted string will
pass. Anyone who can reach this server can run arbitrary AppleScript on your
Mac — which is the whole point, and the reason not to expose it publicly.

## Notes

- `run_shortcut` is the escape hatch: build anything in the Shortcuts app —
  Focus modes, HomeKit, timers — and call it by name. Input passing isn't wired
  up; add `-i <file>` in `run_shortcut` if a shortcut needs input. Names must
  match exactly; check with `list_shortcuts` first.
- `compose_mail` opens a populated compose window via AppleScript and does
  **not** send. It used to build a `mailto:` URL, which turned spaces into `+`
  and truncated long bodies.
- `send_imessage` uses the classic `service type = iMessage` selector, which can
  break on some macOS versions if no iMessage account is active; check
  Messages is signed in if it errors.
- `create_note` writes to the iCloud account's "Notes" folder. The Notes `body`
  is HTML, so line breaks are `<div>` wrapped rather than `\n`.
- All AppleScript string interpolation goes through `_as()`, which keeps
  non-ASCII intact — plain `json.dumps` emits `\uXXXX` escapes that AppleScript
  doesn't decode, mangling accents and emoji.
- Every tool returns `{ok, output, error}` and never hangs — subprocess calls
  time out instead of blocking the client.

TDQS

A3.5/5.0

Scored across 39 tools

Disambiguation5/5

Each tool has a clear, distinct purpose covering system info, app management, media, communication, and utilities. There is no overlap that would confuse an agent; even closely related tools like mute and is_muted or set_volume and get_volume are easily distinguishable by their action vs. query nature.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., close_app, get_volume, send_imessage). There are no mixed conventions or abbreviations, making the naming predictable and easy to understand.

Tool Count4/5

With 39 tools, the server is extensive but still well-scoped for a general macOS automation tool. While slightly high, each tool addresses a distinct operation, and the count feels appropriate for the broad domain covered.

Completeness3/5

The tool set covers many common automation tasks (system info, apps, media, messaging, clipboard, reminders, shortcuts), but it lacks tools for file operations, calendar, browser control beyond opening URLs, and system preferences beyond volume/dark mode. These gaps limit its ability to handle some typical workflows.

Maintenance

ActivitySlowing
ResponsivenessNo issues