io.github.aernouddekker/mailappmcp
MCP server for macOS Mail.app β gives Claude native email access via AppleScript
Ask AI about io.github.aernouddekker/mailappmcp
Powered by Claude Β· Grounded in docs
I know everything about io.github.aernouddekker/mailappmcp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
macos-mcp
MCP servers for macOS native apps β gives Claude Code, Claude Desktop, and any MCP client native access to Mail, Numbers, Contacts, Calendar, Reminders, printing (CUPS), and FaceTime / phone calls.
No API keys, no OAuth, no cloud services. Talks directly to macOS apps via AppleScript, CUPS (lp/lpstat), and URL schemes (tel://, facetime://). Runs locally on your Mac.
Servers
Mail (mailappmcp) β 21 tools
Works with every email account configured in Mail.app β iCloud, Gmail, Outlook, Fastmail, you name it.
| Tool | Description |
|---|---|
list-mailboxes | List all mailboxes across all accounts with unread counts |
list-accounts | List configured mail accounts with email addresses and type |
list-signatures | List available email signatures |
search-messages | Search messages by subject or sender (empty query lists all) |
read-message | Read the full content of a specific email |
get-message-source | Get raw RFC822 source of a message |
list-attachments | List attachments on a message with name, MIME type, size |
save-attachment | Save email attachments to disk |
compose-message | Create a draft in Mail.app (does not send) β supports plain or HTML body via htmlBody |
send-message | Send an email immediately (supports from, attachments, plain or HTML body via htmlBody) |
reply-to-message | Reply or reply-all to a message |
forward-message | Forward a message to new recipients |
redirect-message | Redirect a message (preserves original sender) |
move-messages | Move messages between mailboxes |
delete-messages | Delete messages by Message-ID |
mark-as-read | Mark messages as read |
mark-as-junk | Mark/unmark messages as junk |
flag-message | Flag/unflag messages with color support |
set-message-color | Set background color of messages in the message list |
check-for-new-mail | Trigger a mail fetch for one or all accounts |
extract-email-address | Parse "John Doe <jdoe@example.com>" into name and address |
Numbers (numbersmcp) β 29 tools
Works with any open Numbers spreadsheet.
| Tool | Description |
|---|---|
list-spreadsheets | List all open Numbers documents |
create-document | Create a new Numbers document |
list-sheets | List sheets and tables in a document |
get-active-sheet | Get the currently active sheet |
read-range | Read cell values from a range (e.g. "A1:C10") |
read-table | Read an entire table as structured data |
write-cell | Write a value to a specific cell |
write-range | Write multiple values to a range |
clear-range | Clear contents and formatting of a cell range |
get-formula | Get the formula from a cell |
set-formula | Set a formula on a cell |
add-row | Append a row to a table |
delete-row | Delete a row from a table |
add-column | Add a column to a table |
delete-column | Delete a column from a table |
resize-row-column | Set row height or column width |
add-sheet | Add a new sheet to a document |
delete-sheet | Delete a sheet from a document |
rename-sheet | Rename a sheet |
add-table | Add a new table to a sheet |
delete-table | Delete a table from a sheet |
rename-table | Rename a table |
sort-table | Sort a table by a column |
transpose-table | Swap rows and columns of a table |
merge-cells | Merge a range of cells |
unmerge-cells | Unmerge previously merged cells |
set-cell-format | Set cell format (number, currency, date, percentage, etc.) |
set-cell-style | Set font, color, background, bold, italic, alignment |
export-document | Export to PDF, Excel, or CSV |
Contacts (@aernoud/contactsmcp) β 15 tools
Works with the system address book β all accounts synced to Contacts.app.
| Tool | Description |
|---|---|
search-contacts | Search contacts by name, email, or phone |
search-by-modification-date | Find contacts modified after a given date |
read-contact | Get full contact details |
get-my-card | Get the user's own contact card |
get-vcard | Export a contact as vCard 3.0 text |
create-contact | Create a new contact |
update-contact | Update contact fields |
delete-contact | Delete a contact |
list-groups | List all contact groups |
create-group | Create a new contact group |
rename-group | Rename a contact group |
delete-group | Delete a contact group |
add-to-group | Add a contact to a group |
remove-from-group | Remove a contact from a group |
list-group-members | List all contacts in a group |
Calendar (calendarmcp) β 25 tools
Works with every calendar configured in Calendar.app β iCloud, Google, Exchange, local, you name it.
| Tool | Description |
|---|---|
list-calendars | List all calendars with name, writable flag, description |
get-calendar | Get properties of a single calendar by name (incl. event count) |
create-calendar | Create a new calendar |
update-calendar | Rename a calendar or set its description |
delete-calendar | Not supported by Calendar.app β returns a descriptive error; calendars must be removed from the Calendar.app UI |
switch-view | Switch Calendar.app to day/week/month view, optionally jumping to a date |
reload-calendars | Force Calendar.app to refresh from accounts |
list-events | List events in a calendar between two ISO dates |
search-events | Search events by summary substring within a date window (defaults: β30d to +365d). Scope to a single calendar for speed |
get-event | Get full event details by uid |
create-event | Create an event with summary, start, end, optional location/description/url |
update-event | Patch any event field by uid |
delete-event | Delete an event by uid |
move-event | Move an event to another calendar (delete + recreate; new uid) |
duplicate-event | Duplicate an event into the same or another calendar |
today-events | List today's events in one calendar or across all |
upcoming-events | List events in the next N days |
list-attendees | List attendees on an event |
add-attendee | Add an attendee with email and optional display name |
remove-attendee | Remove an attendee by email |
list-alarms | List display, mail, and sound alarms on an event |
add-display-alarm | Add a display alarm N minutes before event start |
add-sound-alarm | Add a sound alarm N minutes before event start |
add-mail-alarm | Add a mail alarm N minutes before event start |
remove-alarm | Remove an alarm by 1-based index from list-alarms output |
Reminders (@aernoud/remindersmcp) β 22 tools
Works with every reminder list configured in Reminders.app β iCloud, Exchange, local, you name it.
| Tool | Description |
|---|---|
list-accounts | List all accounts in Reminders.app with name and id |
list-lists | List every reminder list, optionally scoped to one account |
get-list | Get properties of a single list (id, account, color, emblem, open + completed counts) |
create-list | Create a new list, optionally in a specific account |
update-list | Rename a list |
delete-list | Delete a list (Reminders.app supports this directly via AppleScript, unlike Calendar.app) |
show-list | Bring a list to the front in Reminders.app |
list-reminders | List reminders in a named list (excludes completed by default) |
search-reminders | Search reminders by name substring; scope to one list for speed |
get-reminder | Get full reminder details by id |
today-reminders | List reminders due today, scoped to one list or all |
upcoming-reminders | List reminders due in the next N days |
overdue-reminders | List reminders past their due date and not yet completed |
create-reminder | Create a reminder with body, due date or all-day due date, remind-me date, priority, flagged |
update-reminder | Patch any reminder field by id |
delete-reminder | Delete a reminder by id |
complete-reminder | Mark a reminder as completed (Reminders auto-stamps completion date) |
uncomplete-reminder | Mark a previously completed reminder as not completed |
move-reminder | Move a reminder to a different list (uses the native move verb β id is preserved) |
flag-reminder | Set or clear the flagged state |
set-priority | Set priority to none / high / medium / low (mapped to Reminders' 0/1/5/9 enum) |
show-reminder | Bring Reminders.app to the front and focus a specific reminder |
Print (printmcp) β 5 tools
Wraps the macOS CUPS print system (lp, lpstat, lpoptions, cancel). Discovers any printer the Mac knows about β local USB, network, AirPrint, shared from another Mac. No AppleScript involved.
| Tool | Description |
|---|---|
list-printers | List all CUPS printers with status, location, default flag |
get-printer-options | List supported PPD options for a printer (sides, media size, color mode, β¦) with current defaults |
print-file | Print a local file (PDF, plain text, JPEG/PNG, PostScript) with copies, duplex, paper size, page ranges, fit-to-page, and arbitrary lp options |
list-print-jobs | List active print jobs (optionally filtered to a printer) |
cancel-print-job | Cancel a queued or printing job by id |
Composes naturally with mailappmcp: e.g. "print the attachment of the latest email from Hadi" β search the message, save-attachment to a temp dir, then print-file.
FaceTime (facetimemcp) β 3 tools
Initiates calls by handing URL schemes to open. Phone calls require an iPhone paired via Continuity (so macOS can route them through your phone).
| Tool | Description |
|---|---|
call-phone | Place a cellular call via paired iPhone (tel://) |
call-facetime-audio | Start a FaceTime audio call to a phone number or Apple ID email |
call-facetime-video | Start a FaceTime video call to a phone number or Apple ID email |
Phone numbers are normalized + validated as E.164 (+15551234567); spaces, dashes, and parens are tolerated. macOS may show a confirmation prompt before dialing β there is no fully silent dial path, by design.
Requirements
- macOS (uses AppleScript β won't work on Linux/Windows)
- Node.js 18+
Install
From npm
npm install -g mailappmcp # Mail server
npm install -g numbersmcp # Numbers server
npm install -g @aernoud/contactsmcp # Contacts server
npm install -g @aernoud/calendarmcp # Calendar server
npm install -g @aernoud/remindersmcp # Reminders server
npm install -g @aernoud/printmcp # Print server (CUPS)
npm install -g @aernoud/facetimemcp # FaceTime / phone calls
From source
git clone https://github.com/aernouddekker/macos-mcp.git
cd macos-mcp
npm install
npm run build
Configure
Claude Code
Add to ~/.claude/settings.json or your project's .mcp.json:
{
"mcpServers": {
"mailappmcp": { "command": "npx", "args": ["-y", "mailappmcp"] },
"numbersmcp": { "command": "npx", "args": ["-y", "numbersmcp"] },
"contactsmcp": { "command": "npx", "args": ["-y", "@aernoud/contactsmcp"] },
"calendarmcp": { "command": "npx", "args": ["-y", "@aernoud/calendarmcp"] },
"remindersmcp": { "command": "npx", "args": ["-y", "@aernoud/remindersmcp"] },
"printmcp": { "command": "npx", "args": ["-y", "@aernoud/printmcp"] },
"facetimemcp": { "command": "npx", "args": ["-y", "@aernoud/facetimemcp"] }
}
}
Claude Desktop / Cowork
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"mailappmcp": { "command": "npx", "args": ["-y", "mailappmcp"] },
"numbersmcp": { "command": "npx", "args": ["-y", "numbersmcp"] },
"contactsmcp": { "command": "npx", "args": ["-y", "@aernoud/contactsmcp"] },
"calendarmcp": { "command": "npx", "args": ["-y", "@aernoud/calendarmcp"] },
"remindersmcp": { "command": "npx", "args": ["-y", "@aernoud/remindersmcp"] },
"printmcp": { "command": "npx", "args": ["-y", "@aernoud/printmcp"] },
"facetimemcp": { "command": "npx", "args": ["-y", "@aernoud/facetimemcp"] }
}
}
How it works
Each server runs locally over stdio. The Mail, Numbers, and Contacts servers build AppleScript strings, execute them via osascript, and parse the structured output back into JSON. The Print server shells out to CUPS (lp, lpstat, lpoptions, cancel); the FaceTime server hands tel:// / facetime:// URLs to open. A shared package (@mailappmcp/shared) provides the AppleScript runner, the generic command runner (runCommand), string escaping, and delimiter-based parsing.
App lifecycle β leave as found
Mail, Calendar, Contacts, and Reminders need their respective app running to respond to AppleScript. Rather than requiring you to keep those apps open, each server auto-launches them on first use and cleans up afterwards:
- On the first tool call that touches an app, the server checks (via
pgrep) whether the app is already running. If not, it launches it in the background (open -g -a) without stealing focus, and waits up to 5 seconds for the app to accept AppleEvents before running the tool. - That state is remembered in-process for the lifetime of the MCP server. Subsequent tool calls against the same app skip the probe and reuse the running app β no repeated launch penalty.
- On server shutdown (SIGTERM / SIGINT / SIGHUP / normal exit β e.g. when Claude Desktop disconnects the server or the chat ends), the server quits only the apps it launched. Apps you had open before the server started are left alone.
- Edge case: if the server is force-killed (
SIGKILL), the exit handler can't run, so any auto-launched apps stay running. Same as if you'd never had the server.
This means you can use the tools without worrying about a pile of half-launched apps lingering after the session, and without the servers killing apps you were actively using.
Safety
compose-messageopens a visible draft β you review before sendingsend-messageis a separate, explicit actionreply-to-messageandforward-messagedefault to draft mode (sendImmediately: false)delete-messagesmoves to Trash (standard Mail.app behavior)
HTML email
compose-message and send-message accept an optional htmlBody parameter. When supplied, the message is created as a rich-text/HTML message and the recipient sees rendered formatting (headings, bold, lists, clickable links) instead of raw tags. body is still required and is used as the plain-text fallback for clients that read plain content. Omit htmlBody for the existing plain-text behavior β fully backward compatible.
// send-message with HTML body
{
"to": ["alice@example.com"],
"subject": "Weekly update",
"body": "Highlights:\n- Shipped feature X\n- Fixed bug Y",
"htmlBody": "<h2>Highlights</h2><ul><li>Shipped <b>feature X</b></li><li>Fixed bug Y β see <a href=\"https://example.com/issue/42\">#42</a></li></ul>"
}
Under the hood, the HTML path uses JXA (osascript -l JavaScript) and Mail.app's runtime htmlContent setter on outgoing messages. The plain-text path still uses ordinary AppleScript.
Known limitations
General
osascripthas a 30-second timeout per call- Apps are auto-launched on first use and quit on server shutdown if they weren't running beforehand β see App lifecycle
content containssearches in AppleScript can be slow on large mailboxes β Mail server searches subject and sender by default
Numbers
- Numbers tools require an open document
Calendar
These are limitations of Calendar.app's AppleScript interface itself, not the MCP server:
- Calendars cannot be deleted via AppleScript.
delete calendarraises-10000(AppleEvent handler failed) on every modern macOS version. Thedelete-calendartool checks that the calendar exists and then returns a descriptive error β you must remove calendars manually from the Calendar.app sidebar (right-click β Delete). - Calendars have no usable id/uid. Calendar.app's
calendarclass does not expose a stable id via AppleScript (uid ofraises-10000). All calendar tools therefore identify calendars by name β make sure your calendar names are unique. whosefilters are O(n) over events. AppleScript scans every event in a calendar to evaluatewhose start date β₯ Xpredicates. On a busy multi-calendar store, even an empty 7-day query across all calendars can take 60+ seconds and exceed the 30 sosascripttimeout. Always pass acalendarNametolist-events,search-events,today-events, andupcoming-eventswhen possible to scope the scan.search-eventsadditionally enforces a date window (default: β30 days to +365 days).move-eventreassigns the uid. Calendar.app cannot reparent an event between calendars. The tool deletes the source event and creates a new one in the target calendar; the new event has a fresh uid (returned asnewUidalongsideoldUid).- Recurrence is exposed as raw RRULE strings (e.g.
FREQ=WEEKLY;INTERVAL=1) β read and write only, no parsing or expansion.
License
MIT
