A fast MCP server for Apple Mail, using optimized JXA scripts with batch property fetching for 87x faster performance.
JXA Mail MCP
A fast MCP (Model Context Protocol) server for Apple Mail, using optimized JXA (JavaScript for Automation) scripts with batch property fetching for 87x faster performance.
Features
- list_accounts - List all configured email accounts
- list_mailboxes - List mailboxes for an account
- get_emails - Fetch emails from any mailbox with pagination
- get_todays_emails - Fetch all emails received today
- get_unread_emails - Fetch unread emails
- get_flagged_emails - Fetch flagged emails
- search_emails - Search emails by subject or sender
- fuzzy_search_emails - Typo-tolerant search using trigram + Levenshtein matching
Installation
With pipx (recommended)
pipx install jxa-mail-mcp
From source
Requires Python 3.13+ and uv:
git clone https://github.com/imdinu/jxa-mail-mcp
cd jxa-mail-mcp
uv sync
Usage
Add to Claude Code
After installing with pipx:
{
"mcpServers": {
"mail": {
"command": "jxa-mail-mcp"
}
}
}
Or from source:
{
"mcpServers": {
"mail": {
"command": "uv",
"args": ["run", "--directory", "/path/to/jxa-mail-mcp", "jxa-mail-mcp"]
}
}
}
Run directly
jxa-mail-mcp
Configuration
Set default account and mailbox via environment variables:
export JXA_MAIL_DEFAULT_ACCOUNT="Work"
export JXA_MAIL_DEFAULT_MAILBOX="Inbox"
Or in Claude Code config:
{
"mcpServers": {
"mail": {
"command": "jxa-mail-mcp",
"env": {
"JXA_MAIL_DEFAULT_ACCOUNT": "Work"
}
}
}
}
Test in Python
from jxa_mail_mcp.server import get_todays_emails, search_emails, fuzzy_search_emails
emails = get_todays_emails(account="iCloud", mailbox="Inbox")
results = search_emails("meeting", account="Work", limit=10)
# Fuzzy search - tolerates typos
results = fuzzy_search_emails("meetting nottes", limit=10) # finds "meeting notes"
Architecture
src/jxa_mail_mcp/
├── __init__.py # Exports mcp instance and main()
├── server.py # FastMCP server and MCP tools
├── config.py # Environment variable configuration
├── builders.py # QueryBuilder for constructing JXA scripts
├── executor.py # JXA script execution utilities
└── jxa/
├── __init__.py # Exports MAIL_CORE_JS
└── mail_core.js # Shared JXA utilities library
Design Principles
- Separation of concerns: Python handles logic/types, JavaScript handles Mail.app interaction
- Builder pattern:
QueryBuilderconstructs optimized JXA scripts programmatically - Shared JS library:
mail_core.jsprovides reusable utilities injected into all scripts - Type safety: Python type hints ensure correct usage
Performance
The Problem
Naive AppleScript/JXA iteration is extremely slow:
// SLOW: ~54 seconds for a few hundred messages
for (let msg of inbox.messages()) {
results.push({
from: msg.sender(), // IPC call to Mail.app
subject: msg.subject(), // IPC call to Mail.app
});
}
Each property access triggers a separate Apple Event IPC round-trip.
The Solution: Batch Property Fetching
JXA supports fetching a property from all elements at once:
// FAST: ~0.6 seconds (87x faster)
const msgs = inbox.messages;
const senders = msgs.sender(); // Single IPC call returns array
const subjects = msgs.subject(); // Single IPC call returns array
for (let i = 0; i < senders.length; i++) {
results.push({ from: senders[i], subject: subjects[i] });
}
Benchmark Results
| Method | Time | Speedup |
|---|---|---|
| AppleScript (per-message) | 54.1s | 1x |
| JXA (per-message) | 53.9s | 1x |
| JXA (batch fetching) | 0.62s | 87x |
Fuzzy Search Performance
Fuzzy search uses trigrams for fast candidate selection and Levenshtein distance for accurate ranking. Tested on a mailbox with ~6,000 emails:
| Search Type | Time | Overhead |
|---|---|---|
| Regular search | ~360ms | - |
| Fuzzy search | ~480ms | +33% |
The trigram pre-filtering keeps fuzzy search fast by avoiding expensive Levenshtein calculations on non-matching words.
Example: Searching for "reserch studies" (typo) correctly finds "research studies" with 0.94 similarity score.
Development
uv sync
uv run ruff check src/
uv run ruff format src/
# Test
uv run python -c "
from jxa_mail_mcp.server import list_accounts, get_todays_emails
print('Accounts:', len(list_accounts()))
print('Today:', len(get_todays_emails()))
"
License
GPL-3.0-or-later
Tools (9)
list_accountsList all configured email accountslist_mailboxesList mailboxes for an accountget_emailsFetch emails from any mailbox with paginationget_todays_emailsFetch all emails received todayget_unread_emailsFetch unread emailsget_flagged_emailsFetch flagged emailssearch_emailsSearch emails by subject or senderfuzzy_search_emailsTypo-tolerant search using trigram + Levenshtein matchingget_todays_emailsFetch all emails received todayEnvironment Variables
JXA_MAIL_DEFAULT_ACCOUNTDefault email account to useJXA_MAIL_DEFAULT_MAILBOXDefault mailbox to useConfiguration
{
"mcpServers": {
"mail": {
"command": "jxa-mail-mcp"
}
}
}