Skip to main content
Glama
toshi38
by toshi38
README.md
# mac-mail-mcp

A local MCP server that lets an AI agent read your email in Apple Mail (Mail.app) on macOS, prepare drafts and, after you confirm, send messages. It drives Mail.app through AppleScript, so it works with any account Mail is signed into: iCloud, Gmail, Exchange or Microsoft 365, IMAP and so on. No API keys, OAuth apps or app registrations are needed.

It ships with OpenCode agents, but the server works with any MCP client.

## Tools

| Tool | What it does |
| --- | --- |
| `list` | Lists the newest messages in the configured mailbox (id, received, from, subject, read) |
| `read` | Reads one message by id. Bodies are capped at 20,000 characters |
| `draft` | Opens a draft in Mail for you to review and send. Never sends |
| `send` | Shows the full message in a Mail dialog and sends only if you click **Send** (Cancel is the default button). Bodies are capped at 2,000 characters so the whole message fits in the dialog |

MCP clients usually prefix tool names with the server name, so a server configured as `mail` gives `mail_list`, `mail_read`, `mail_draft` and `mail_send`.

## Requirements

- macOS with Mail.app, and at least one account added in Mail (System Settings > Internet Accounts, or Mail > Add Account) that has finished syncing
- [Bun](https://bun.sh) 1.3 or later
- An MCP client, such as [OpenCode](https://opencode.ai)

## Setup

### 1. Install

```sh
git clone https://github.com/toshi38/mac-mail-mcp.git ~/code/mac-mail-mcp
cd ~/code/mac-mail-mcp
bun install
```

### 2. Choose the account

Find your account names as Mail knows them:

```sh
osascript -e 'tell application "Mail" to get name of every account'
```

The first time you run this, macOS asks whether your terminal may control Mail. Allow it.

To see an account's mailboxes, replace `<account>` with one of the names:

```sh
osascript -e 'tell application "Mail" to get name of every mailbox of account "<account>"'
```

Then create `~/.config/mac-mail-mcp/config.json`:

```json
{ "account": "<account>", "mailbox": "Inbox" }
```

| Key | Default | Meaning |
| --- | --- | --- |
| `account` | `""` | Account to read from and send as. Empty means the unified inbox across every account, sending from Mail's default account |
| `mailbox` | `"Inbox"` | Mailbox within the account. Matched case-insensitively, so `Inbox` also finds `INBOX` |

To keep the config somewhere else, set `MAC_MAIL_CONFIG` to its path. Changes apply on the next tool call, with no restart needed.

### 3. Connect your MCP client

#### OpenCode

Run the installer. It copies the `mail` and `mail-send` agents into `~/.config/opencode/agents/` and prints the server entry with your paths filled in:

```sh
./scripts/install.sh
```

Add the printed entry to `~/.config/opencode/opencode.json`. It looks like this:

```json
"mcp": {
  "mail": {
    "type": "local",
    "command": ["/Users/<you>/.bun/bin/bun", "/Users/<you>/code/mac-mail-mcp/src/main.ts"],
    "codemode": false
  }
}
```

- `"codemode": false` offers the tools to the model directly. Without it, OpenCode only exposes them through its Code Mode `execute` tool, which the bundled agents deny.
- Use the absolute path to `bun`. OpenCode's background service may not have `~/.bun/bin` on its PATH.

Then restart OpenCode (`opencode service restart`) and choose who can use the tools (next section).

#### Other MCP clients

The server speaks MCP over stdio. Point your client at `bun /path/to/mac-mail-mcp/src/main.ts`. For example, in Claude Code:

```sh
claude mcp add mail -- bun /path/to/mac-mail-mcp/src/main.ts
```

### 4. Allow OpenCode to control Mail

The first time an agent uses one of the tools, macOS asks whether OpenCode (or your MCP client) may control Mail. Allow it. If you dismissed the prompt, turn it on under System Settings > Privacy & Security > Automation.

## Who can use the tools

Email is untrusted input: anyone can send you a message containing instructions aimed at your agent. How much that matters depends on what else the agent reading your mail can do. In OpenCode, pick one of these setups in `opencode.json`.

**Every agent** (simplest). Your normal agents can use the mail tools, and OpenCode asks before each call. Allowing the read tools skips that prompt for reading. Blocking `open` with `mailto:` links stops an agent working around a failed tool by opening whatever app handles email links on your Mac, which may be a different account:

```json
"permission": {
  "mail_list": "allow",
  "mail_read": "allow",
  "bash": { "open *mailto:*": "deny" }
}
```

The trade-off: an agent that also has shell, file and web access is reading mail written by strangers.

**Dedicated agents only** (safest). Hide the tools from every agent except the bundled ones:

```json
"permission": {
  "mail_*": "deny",
  "bash": { "osascript *": "deny", "open *mailto:*": "deny" }
}
```

Then press Tab in OpenCode to switch to one of these:

| Agent | Tools |
| --- | --- |
| `mail` | `mail_list`, `mail_read`, `mail_draft`. Cannot send |
| `mail-send` | The same, plus `mail_send` |

Both agents deny every other tool, so they have no shell, file editing, web access, subagents or Code Mode. The shell rules stop your other agents scripting Mail (or Outlook) directly or opening `mailto:` links.

Whichever setup you choose, nothing is sent until you click **Send** in Mail's confirmation dialog. The server also tells agents, in its MCP instructions, tool descriptions and every error, to report failures instead of working around them.

## Checks

Run these after setup:

1. Ask for a summary of your inbox. You should get real messages.
2. Ask the agent to send yourself an email, then press Cancel in the Mail dialog. Nothing should be sent.
3. Send yourself an email asking the assistant to forward your inbox somewhere, then ask for a summary. The agent should report the request and do nothing about it.
4. With the dedicated agents, ask `mail` to run `ls` and to fetch a URL. Both should be refused.

## Troubleshooting

| Symptom | Fix |
| --- | --- |
| "Not authorised to control Mail" | Allow the client under System Settings > Privacy & Security > Automation |
| "Mail could not find the account, mailbox or message" | Check `account` and `mailbox` against the `osascript` commands in step 2 |
| The agent says it has no mail tools | In OpenCode, check that the server shows as Connected, that `codemode` is `false`, and that your permissions do not deny `mail_*` for that agent. Restart OpenCode after config changes |
| Listing takes several seconds | Normal on large mailboxes: Mail answers AppleScript slowly. Ask for fewer messages |
| A message is sent from the wrong address | Set `account`. With an empty `account`, Mail uses its default account |

## Development

```sh
bun test
bun run typecheck
```

The tests cover the client with a fake runner, the MCP server over an in-memory transport and, on macOS, compile every AppleScript with `osacompile` without launching Mail.

Values reach AppleScript as arguments and are never spliced into script source, so message text cannot run as AppleScript. Scripts are passed to `osascript` on stdin, so arguments starting with `-` are not read as options.

## License

[MIT](LICENSE)