Fast MCP server for Apple Mail via optimized JXA scripts
This project has been archived.
The maintainers of this project have marked this project as archived. No new releases are expected.
Project description
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_email - Fetch a single email with full body content
- 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
- search_email_bodies - Fuzzy search within email body content
Installation
No installation required
Use pipx run to run directly from PyPI:
pipx run jxa-mail-mcp
With pipx (optional)
For faster startup, install globally:
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
Using pipx run (no installation required):
{
"mcpServers": {
"mail": {
"command": "pipx",
"args": ["run", "jxa-mail-mcp"]
}
}
}
Or if installed with pipx install jxa-mail-mcp:
{
"mcpServers": {
"mail": {
"command": "jxa-mail-mcp"
}
}
}
Run directly
pipx run jxa-mail-mcp
# or after installing
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,
search_email_bodies,
get_email,
)
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"
# Search within email bodies (slower but searches full content)
results = search_email_bodies("project deadline", account="Work", limit=10)
# Get full email content
email = get_email(message_id=12345, account="Work", mailbox="INBOX")
print(email["content"]) # Full body text
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.
Body Search Performance
Body search (search_email_bodies) fetches full email content, which is slower than metadata-only search but enables searching within email text:
| Search Type | Time (20 emails) | Notes |
|---|---|---|
| Metadata search | ~0.1s | Subject/sender only |
| Body search | ~7s | Full content fetch + fuzzy match |
| Single email fetch | ~0.3s | get_email() with full body |
Body search uses a tiered matching approach:
- Exact substring (score 0.95) - Fast, catches most searches
- Trigram similarity (score 0.25-0.72) - Tolerates typos without expensive Levenshtein on long text
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jxa_mail_mcp-0.2.0.tar.gz.
File metadata
- Download URL: jxa_mail_mcp-0.2.0.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55d80b8a92c51d2f77a1233131769e3e9058a72d1e8e11549a00a2a120cc557e
|
|
| MD5 |
e09a9e004d874ce7488ac298c03659c2
|
|
| BLAKE2b-256 |
673692e0049b3290183b06e6c7b647125d627816737ddc9667f215cef95a8484
|
File details
Details for the file jxa_mail_mcp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: jxa_mail_mcp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a948663131c02ec7984013ed3dfac20227d69fee2c43e07c47dcee081f4cd635
|
|
| MD5 |
d78f896e7ff45eda10c1d6acf1c4bddf
|
|
| BLAKE2b-256 |
60542ee7278fbcf8c2f584617847e13543d53858ff07dd2fefe9a8b11201745d
|