Mail MCP
by atfinke
README.md
# Mail MCP
Local MCP server for macOS Mail reads plus visible unsent compose, reply, and forward drafts, and constrained single-message moves.
Built entirely by OpenAI GPT-5.4 via Codex.
## Overview
This project exposes Apple Mail accounts, mailboxes, inbox messages, and direct message reads through MCP.
Supported tools:
- `mail_check_access`
- `mail_list_accounts`
- `mail_list_mailboxes`
- `mail_list_mailbox_messages`
- `mail_list_inbox_messages`
- `mail_get_message`
- `mail_compose_message`
- `mail_reply_to_message`
- `mail_forward_message`
- `mail_move_message`
The primary read workflow is `mail_list_inbox_messages`, which returns messages for all inboxes and supports `headersOnly: true` when you only need headers and metadata.
The compose workflow is intentionally narrow: the server can open visible unsent compose, reply, and forward drafts, but it does not expose any send tool.
The move workflow is intentionally constrained: it supports moving one message within the same account, but it does not allow moves to Trash or deleted-message mailboxes.
## Path Conventions
- Tool inputs use explicit segment arrays:
- `mailboxPathSegments: ["Inbox"]`
- `mailboxPathSegments: ["Archive", "2026"]`
- `destinationMailboxPathSegments: ["Receipts"]`
- Mailbox results include both `path` and `pathSegments`. Use `path` for display and `pathSegments` when calling tools.
- Message results include both `mailboxPath` and `mailboxPathSegments`. Use `mailboxPath` for display and `mailboxPathSegments` when calling tools.
- Message results use `id`. Tools that operate on a specific message require that same value under the input name `messageId`.
Example round trip:
1. Call `mail_list_mailboxes` and pick a mailbox result such as:
```json
{
"accountId": "account-1",
"name": "Receipts",
"path": "Archive / 2026 / Receipts",
"pathSegments": ["Archive", "2026", "Receipts"]
}
```
2. Call `mail_list_mailbox_messages` with:
```json
{
"accountId": "account-1",
"mailboxPathSegments": ["Archive", "2026", "Receipts"]
}
```
3. Take a returned message object such as:
```json
{
"id": 4812,
"accountId": "account-1",
"mailboxPath": "Archive / 2026 / Receipts",
"mailboxPathSegments": ["Archive", "2026", "Receipts"]
}
```
4. Call `mail_get_message` with:
```json
{
"accountId": "account-1",
"mailboxPathSegments": ["Archive", "2026", "Receipts"],
"messageId": 4812
}
```
## Requirements
- Node.js 20+
- macOS
- Mail.app configured with at least one account
- Automation permission to control Mail
- A signed `MailMCPHelperApp` build
## Setup
1. Install dependencies:
```bash
npm install
```
2. Copy `.env.example` to `.env` if you want to override defaults:
```bash
cp .env.example .env
```
3. Build the helper app:
```bash
npm run build:helper-app
```
The build script compiles the app unsigned, then signs the finished bundle with the first local `Apple Development` identity. Override that selection with `MAIL_MCP_CODESIGN_IDENTITY` if needed.
4. Run local quality checks:
```bash
npm run verify
```
5. Run a real Mail smoke test:
```bash
npm run test:live
```
Optional environment variables:
- `MAIL_MCP_REQUEST_TIMEOUT_MS`
- `MAIL_MCP_HELPER_TIMEOUT_MS`
- `MAIL_MCP_HELPER_APP_PATH`
## Run
Start the server in development:
```bash
npm run dev
```
Build production output:
```bash
npm run build
npm start
```
## Permissions
The server launches a signed `MailMCPHelperApp` and runs Mail automation inside that app process. This keeps the macOS Automation/TCC boundary attached to the helper app instead of Terminal or the client process.
Run `mail_check_access` once after installation. On first use, macOS may prompt for permission to control Mail.
If the prompt does not appear automatically in your client, launch the helper app directly once:
```bash
open MailMCPHelperApp/build/Build/Products/Release/MailMCPHelperApp.app
```
The server starts even before access is granted. Use `mail_check_access` to confirm permission state and get an actionable error message if macOS Automation consent is still pending.
To re-run the consent flow on a test machine, reset Mail Apple Events permission for the helper app and then run `mail_check_access` again:
```bash
tccutil reset AppleEvents com.andrewfinke.mailmcphelper
```
After the reset, confirm that the macOS prompt names `MailMCPHelperApp`, then confirm System Settings shows `MailMCPHelperApp -> Mail`.
## Verify
```bash
npm run verify
```
## Notes
- The filesystem-backed `~/Library/Mail` approach is intentionally not used here because it requires broader privacy access and depends on undocumented Mail storage details.
- Message bodies are returned for inbox and mailbox listing tools unless `headersOnly: true` is set.
- Compose, reply, and forward tools always open visible drafts. Hidden draft mode is intentionally avoided because Mail does not clean up invisible compose objects reliably through automation.
- Raw headers are optional for list tools and default to `false`.
- Attachment metadata is included, but not attachment bytes.
- The helper app executes JavaScript for Automation in-process via `OSAKit`.
## MCP Setup
The server runs over stdio.
Example client configuration for the built server:
```json
{
"mcpServers": {
"mail": {
"command": "node",
"args": ["/absolute/path/to/mail-mcp/dist/index.js"],
"cwd": "/absolute/path/to/mail-mcp",
"env": {
"MAIL_MCP_HELPER_APP_PATH": "/absolute/path/to/mail-mcp/MailMCPHelperApp/build/Build/Products/Release/MailMCPHelperApp.app"
}
}
}
}
```
For local development without building first:
```json
{
"mcpServers": {
"mail": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"cwd": "/absolute/path/to/mail-mcp"
}
}
}
```
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues