Apple Tools MCP
This MCP server lets you search, read, and write Apple Mail, Messages, Calendar, and Contacts on macOS using plain-language queries, with local vector search and confirmation-gated writes.
Universal search:
smart_searchacross mail, messages, and calendar;person_searchfor all communication with one person.Email: semantic search with filters (sender, recipient, attachments, mailbox, dates, flagged), recent/date/thread views, full read, link extraction, exact database lookup, and frequent senders.
Messages: semantic search with contact/group/attachment filters, recent messages, full conversations, and a list of messaged contacts.
Calendar: semantic search, events by date, upcoming events, weekly view, free-time finder, and recurring events.
Contacts: search by name/email/phone/organization and full contact lookup.
Write actions: send, draft, reply, forward, mark, archive, and trash mail; send iMessages/SMS with attachments; add/edit/remove/RSVP calendar events with recurrence and alerts; add/edit/remove contacts.
Safety:
dry_runpreviews,confirm: truerequired for deletes and multi-recipient sends, idempotency keys and reconciliation for mail sends.Index management:
rebuild_indexandaudit_indexfor the local LanceDB vector index.Access: stdio for local clients or HTTP with a bearer token for other machines.
Provides semantic search and data access across Apple Mail, Messages, Calendar, and Contacts on macOS, enabling natural language queries to find emails, iMessages, calendar events, and contact information stored locally on the device.
Enables semantic search of iMessages and SMS conversations, retrieving recent messages, viewing full conversation histories, and listing message contacts through natural language queries.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Apple Tools MCP Find emails from John about the quarterly report"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apple-tools-mcp
An MCP (Model Context Protocol) server for Apple Mail, Messages, Calendar, and Contacts on macOS. Search them in plain language, send mail and messages, and create, edit, or remove calendar events and contacts. It talks to any MCP client over stdio, or over HTTP with a bearer token. Plain-language search is retrieval-augmented: your mail, messages, and calendar events are embedded on your Mac and kept in a local LanceDB vector database (see How search works). The embeddings and the write bridge stay on your Mac.
How search works
Search runs against a local vector index, not a cloud service.
Embedding model:
Xenova/all-MiniLM-L6-v2, a 384-dimension sentence-embedding model (mean pooling, normalized), run on your Mac with Transformers.js. The model files are downloaded from Hugging Face the first time the model loads and cached after that. Your mail, messages, and events are never sent anywhere to be embedded.Vector database: LanceDB, embedded in the process (no server), stored in
~/.apple-tools-mcp/vector-index/with three tables:emails,messages, andcalendar.What is indexed:
Mail: every
.emlxfile under~/Library/Mail, including partly downloaded ones. The embedded text is From, To, Subject, and the start of the body (1,000 characters in all).Messages: iMessage and SMS from
~/Library/Messages/chat.db, including text that macOS stores only in the rich-textattributedBodyfield. The embedded text is the sender and the message (500 characters).Calendar: events from Calendar's
Calendar.sqlitedb. The embedded text is title, calendar, location, and notes (500 characters).Contacts are not embedded. The contact tools and name matching read the Contacts (AddressBook) database directly.
At query time: the question is embedded with the same model. Up to three variants of it (the original, one with time words removed, and one with a common synonym swapped in) each run a nearest-neighbour search. The result lists are merged with Reciprocal Rank Fusion (k = 60), then re-ranked by 70% vector similarity and 30% keyword match. If nothing comes back, one broader query is tried.
smart_searchchooses which tables to search from the question.Not from the index:
mail_find,mail_links,calendar_date,calendar_free_time, the contact tools, and every write tool read Mail, Calendar, Contacts, or Messages live, so they work while the index is rebuilding.
The indexer (below) refreshes the index incrementally, every 5 minutes by default. The search tools return matching items for your MCP client's model to read and answer from.
Related MCP server: MCP Apple Notes
Requirements
macOS
Node.js 18 or later
The permissions below, on the Mac that holds the Mail, Messages, Calendar, and Contacts data
Setup
1. Install
npm install -g apple-tools-mcpOr from source:
git clone https://github.com/sfls1397/Apple-Tools-MCP.git
cd Apple-Tools-MCP
npm installA global install uses npx in the client config below. A clone uses node and the full path to index.js.
After you install or upgrade, run the permissions command in the next section. npm install only prints a reminder. It cannot click Allow for you.
2. Permissions
Two grants, and they do different jobs.
Full Disk Access lets the server read the Mail, Messages, Calendar, and Contacts databases.
Automation lets it send mail, send messages, and change calendar events and contacts through those apps. Mail compose also needs Accessibility, because the message body is pasted into Mail's window through System Events.
Full Disk Access
In Terminal, run
which nodeand copy the path it prints.Open System Settings → Privacy & Security → Full Disk Access.
Click +.
Press Cmd+Shift+G, paste the path, and press Enter.
Select
nodeand turn its toggle on.
Automation, Accessibility, and System Events
Run this from Terminal.app, so the prompts attach to node and not to your editor or chat app:
$(which node) $(which apple-tools-mcp) permissionsFrom a clone, npm run permissions does the same thing. npx apple-tools-mcp permissions also works.
The command prints process.execPath. That path is the node binary macOS will ask about. Click Allow for that binary. If it warns that the command you typed started a different node, run it again with the printed path in front:
/absolute/path/to/node $(which apple-tools-mcp) permissionsAllow node to control Mail, Messages, Contacts, Calendar, and System Events. Each app is its own switch. Allowing Contacts does not allow Mail.
Then open System Settings → Privacy & Security → Accessibility and turn on the same node binary. Without that, Mail compose cannot paste the body.
The command does real work so the prompts can appear. It opens a Mail compose window and throws it away (nothing is sent), lists Messages accounts (nothing is sent), creates and deletes a throwaway contact, lists calendars without creating events, and checks System Events. It exits with an error until Mail, Messages, Contacts, Calendar, and System Events are all allowed.
dry_run on mail_send or messages_send does not talk to those apps, so a dry run will not pop the prompts and will not tell you whether Automation is allowed. A timeout alone does not prove an Automation denial; only an explicit permission error does.
If you clicked Don't Allow, macOS will not ask again. Open System Settings → Privacy & Security → Automation, turn the switch on for that node and that app, then run apple-tools-mcp permissions again.
Do not add node with the + button under Privacy & Security → Contacts or Calendars. Those lists are not how these writes are granted, and on current macOS they often have no Add button.
Leave Mail, Messages, and Contacts running. If they are quit, writes to them often fail even when Automation is allowed. Calendar does not need to stay open.
If your chat app cannot hold the grants
macOS treats these actions as coming from the app that launched the server. A chat app that starts a short-lived MCP process is that app. Some of them cannot be granted Contacts or Calendar access, no matter what you allow for node.
Run the indexer in the next section. It is a normal node process, so the Automation grants you gave node apply. Your MCP client sends writes to it through a local socket at ~/.apple-tools-mcp/writer.sock. When a prompt appears, allow node, not the chat app.
3. Connect your MCP client
Any stdio MCP client can run the server. The command is the same; only the client's settings screen changes.
Global install:
{
"mcpServers": {
"apple-tools": {
"command": "npx",
"args": ["-y", "apple-tools-mcp"]
}
}
}From a clone, use node and the absolute path to index.js instead of npx.
Claude Desktop keeps this in ~/Library/Application Support/Claude/claude_desktop_config.json. Quit the client completely and reopen it after you save.
The MCP process is short-lived: it exits when the client disconnects. Ongoing indexing belongs on the indexer, not on a wrapper that holds this process open.
When stdin closes, requests already received still get their replies before the process exits (up to two minutes). A script can pipe a whole session in one go:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"script","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mail_search","arguments":{"query":"invoice","limit":5}}}' \
| apple-tools-mcp4. Indexer
On first use the server builds a search index of your mail, messages, and calendar events. That can take a while if you have a lot of mail. The index is stored in ~/.apple-tools-mcp/vector-index/.
Run the indexer so the index stays current and so writes go through node:
apple-tools-indexerThat command is node index.js --mode=indexer. A global install provides apple-tools-indexer (bin/apple-tools-indexer.js). From a clone, use npm run indexer.
To keep it running across logins, use a LaunchAgent. LaunchAgents do not see your shell PATH, so put in the absolute paths from which node and npm root -g.
Save this as ~/Library/LaunchAgents/com.apple-tools-mcp.indexer.plist and replace both paths:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple-tools-mcp.indexer</string>
<key>ProgramArguments</key>
<array>
<string>/absolute/path/to/node</string>
<string>/absolute/path/to/node_modules/apple-tools-mcp/index.js</string>
<string>--mode=indexer</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/apple-tools-indexer.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/apple-tools-indexer.err.log</string>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/com.apple-tools-mcp.indexer.plistThe refresh interval is chosen once, when the process starts. The default is 5 minutes. Set INDEX_INTERVAL_MS (30s, 1m, 1h, or a number of milliseconds) or add this to ~/.apple-tools-mcp/config.json:
{
"indexInterval": "1m"
}Values are clamped to 15 seconds through 6 hours. A missing config file is fine; the 5-minute default is used.
The indexer keeps its memory bounded for long runs. The vector database's caches are capped at a 64 MB index cache and a 256 MB metadata cache; set APPLE_TOOLS_LANCE_INDEX_CACHE_MB or APPLE_TOOLS_LANCE_METADATA_CACHE_MB (whole megabytes, 1 to 8192) in the LaunchAgent's EnvironmentVariables to change them. Indexed IDs stay in memory between passes, so a pass with no new mail does not re-read the index. Once a day, after a pass that added or removed something, it compacts the index files and removes table versions older than an hour.
If you do not run the indexer, the MCP process indexes when it starts, then exits when the client disconnects. Writes then run inside that process, and macOS attributes them to the app that launched it.
5. Another computer (optional)
The same tools are available over HTTP, with a bearer token on every request. stdio on this Mac does not use the token. The token is a random secret created on first use and kept in your login Keychain (service apple-tools-mcp-http); apple-tools-mcp http-token prints it.
apple-tools-http
apple-tools-mcp http-token~/.apple-tools-mcp/config.json (the same file as the index interval):
{
"httpHost": "127.0.0.1",
"httpPort": 8421
}127.0.0.1 accepts connections only on this Mac. Set httpHost to a LAN or Tailscale address when a client on another machine should connect. APPLE_TOOLS_HTTP_HOST and APPLE_TOOLS_HTTP_PORT override the file. Do not send the token over an open network.
An example LaunchAgent is examples/com.apple-tools-http.plist. How to point Claude and other clients at it, including apple-tools-http-proxy, is in examples/mcp-client.md.
Index commands
Stop the indexer before a manual rebuild so two processes are not writing the index at once.
# Rebuild (all email history)
npm run build-index
# Optional shorter email lookback
APPLE_TOOLS_INDEX_DAYS_BACK=30 npm run build-indexYour client can also call rebuild_index.
Force a clean rebuild when the index is corrupt or out of date:
launchctl unload ~/Library/LaunchAgents/com.apple-tools-mcp.indexer.plist
rm -rf ~/.apple-tools-mcp/vector-index
rm -f ~/.apple-tools-mcp/index-meta.json
rm -f ~/.apple-tools-mcp/indexer.lockStart the indexer or your MCP client again so it builds a new index.
Watch progress. With the LaunchAgent above:
tail -f /tmp/apple-tools-indexer.err.logClient logs depend on the app. Claude Desktop:
tail -f ~/Library/Logs/Claude/mcp-server-apple-tools.logAudit coverage against the source databases:
npm run audit
npm run audit -- --reporter=verbose > audit-report.txtYour client can also call audit_index.
Available tools
Universal search
Tool | Description |
| Search across mail, messages, and calendar. Picks the sources from the question. |
| Find communication with one person across Mail, Messages, and Calendar. |
Tool | Description |
| Semantic search, with filters for sender, recipient, attachments, and mailbox. |
| Most recent emails. Can limit to unread. |
| Emails from a date such as "today", "yesterday", or "Nov 13". |
| Full email content by file path. |
| Most frequent senders. |
| Every email in a conversation. |
| Exact lookup in Mail's own database by subject prefix or text, sender, recipient, mailbox, and date. Returns every copy with its Message-ID. Does not wait on the index. |
| The links in one email (by its |
Messages
Tool | Description |
| Semantic search for iMessage and SMS, with filters. |
| Most recent messages. |
| Full conversation with a contact. |
| Contacts you have messaged. |
Calendar
Tool | Description |
| Semantic search for events, with filters. |
| Events on a date, read live from Calendar rather than the search index. |
| Next upcoming events. |
| Events for this week or a future week. |
| Open time on a date, read live from Calendar rather than the search index. |
| Recurring events. |
Contacts
Tool | Description |
| Search by name, email, phone, or organization. |
| Full details for one contact. |
Index
Tool | Description |
| Rebuild the search index for one source or all of them. |
| Check index health and coverage. |
Write tools
Write tools change your data. They do not use the search index, so they work while the indexer is rebuilding.
Every write tool accepts:
Argument | Meaning |
| Preview only. Nothing is sent or changed. This wins even if |
| Approves an action that is otherwise blocked. |
Deletes need confirm: true: mail_trash, calendar_remove, contacts_remove, and contacts_edit when it clears every email or phone. These take one id per call, except mail_trash, which also takes a list (message_ids).
mail_send supports a stable idempotency_key per intended delivery. Accepted delivery is replayed without sending again; changed content with the same key is refused. Without a key, identical accepted content is suppressed without expiry. A new key can name an intentional new delivery only after accepted or proven-unstarted outcomes; changing/removing keys cannot bypass identical pending or terminal not_sent content. Uncertain sends never expire or automatically retry. Missing Sent rows cannot prove a queued send will not finish. Claims persist across processes/restarts in the private ~/.apple-tools-mcp/mail-send-claims directory, containing only hashes and outcome metadata. Dry runs create no claims.
mail_send_reconcile is a separate no-send operation with exact claim/attempt/revision/content identity, protected administrator-issued authority, a persistent cross-process writer fence, and fresh complete Sent and Outbox evidence collected after fencing. It preserves original attempts and appends authoritative audit/state records. not_sent is terminal, not retryable: any later retry requires a separate single-use authorization consumed durably before the existing transport handler. Legacy claims are always ineligible. Installation does not create authority, resolve any historical claim, retry mail, or alter a page ledger. By default, reconciliation refuses without the independently established administrative exclusion/evidence boundary. See the full protocol and transition table.
Send and reconciliation results expose structuredContent.mail_send (schema version 1), separating suppression without current handler entry, attempted-but-unresolved transport, accepted historical replay, terminal suppression, identity/writer/evidence refusal and uncertain bridge handoff. Suppression and uncertainty authorize no retry or page-ledger update. Historical send replay also sets MCP isError so text-only clients cannot mistake it for a newly sent message. Global writer locks/fences do not expire or auto-recover after a crash; availability recovery requires a separate explicit maintenance decision.
A bridge request that expires before execution is rejected. After a handoff with an unknown outcome, local fallback is suppressed. The System Events preflight allows 10 seconds (12-second process cap); timeout reports probe_timeout and unknown permission status, separately from an explicit permission denial.
Sends to more than one person need confirm: true: mail_send and mail_forward when To, Cc, and Bcc add up to more than one address, mail_reply with reply_all: true, and messages_send to several people or to a group chat. A single recipient sends on the first call. mail_draft never sends, so it does not need that confirm.
A dry run or a call that still needs confirm does not deliver anything. The result is ok: false, planned: true, delivered: false, and the client may mark it as an error. The text says DRY RUN or CONFIRMATION REQUIRED. Run it again with dry_run false, and with confirm: true when the tool asked for it.
Responses name the action, ids, and recipients. They do not echo message bodies.
A missing or malformed id or recipient is refused. Calendar times are local datetimes, YYYY-MM-DD HH:MM. Phrases like "next Tuesday" are rejected on writes.
Tool | Arguments | Confirm |
|
| when recipients > 1 |
|
| protected authority, not |
|
| no (saved to Drafts) |
|
| when |
|
| when recipients > 1 |
|
| no |
|
| no |
|
| yes |
Pass the RFC822 Message-ID, or the file_path from mail_search or mail_recent and the server reads the Message-ID from the file. mail_archive moves the message to that account's Archive (or All Mail). mail_trash moves it to Trash.
mail_trash with message_ids moves every copy of each message, for example the INBOX copy and the Sent copy of a mail you sent yourself, to that account's Trash. It finds each copy through Mail's database (mail_find uses the same lookup) and checks its Message-ID again before it moves it. One call stops starting new moves after about 35 seconds and reports remaining. Run it again for the rest. Copies that are already in Trash are left alone. Nothing is deleted for good. Mail empties Trash on its own schedule.
body_format: "html" pastes the text into Mail's compose window. Mail may still create its own HTML version. New mail is a new message, not a quoted reply. Reply and forward still include the original.
A send is successful only after the message is in Sent or still in Outbox. If the call times out, check Sent before you try again. Retrying a message that already went out sends a second copy. A timeout is not the same thing as a denied Automation prompt.
Compose needs the Accessibility and System Events grants from the permissions section.
Messages
Tool | Arguments | Confirm |
|
| several handles, or a group chat |
tois a phone number in E.164 form (+15551234567) or an Apple ID email.chat_idis an existing conversation id, such asiMessage;-;+15551234567oriMessage;+;chat123456789. It is checked against Messages before anything is sent. That lookup is also how group chats are detected.service:auto(default) tries iMessage and can fall back to SMS;imessageandsmspin the service.attachment_pathis an absolute path to a file that already exists on this Mac. Text and a file can go together.
Calendar
Tool | Arguments | Confirm |
| none | no. Lists calendars so you can pick one. |
|
| no |
|
| no |
|
| yes |
|
| no |
calendar_date and calendar_add report the event id. Pass that id to edit, remove, and RSVP. Run calendar_list_calendars first so a new event lands on the calendar you mean. calendar_add also returns eventkit_id; pass it back on edit and remove.
Recurrence is either structured fields or one raw rule:
Argument | Values |
|
|
| 1–366 |
| 1–1000 occurrences. Do not combine with |
| local datetime |
|
|
| a raw rule such as |
alerts_minutes_before takes up to five values, from 0 to 40320 minutes (four weeks). On edit, supplying alerts replaces the existing ones. replace_alerts: true removes them without adding new ones.
calendar_rsvp sets your response on an invitation. If that macOS version will not write it, the tool says so instead of pretending it worked.
Contacts
Tool | Arguments | Confirm |
|
| no |
|
| yes, when replacing with an empty list |
|
| yes |
Use the contact id from contacts_search, contacts_lookup, or contacts_add. Creating a contact needs at least one of first_name, last_name, or organization. Writes go through Contacts, which keeps iCloud in sync.
Examples
{ "name": "mail_send", "arguments": { "to": ["a@example.com"], "subject": "Status", "body": "All good", "dry_run": true } }
{ "name": "mail_send", "arguments": { "to": ["a@example.com"], "subject": "Status", "body": "All good" } }
{ "name": "mail_send", "arguments": { "to": ["a@example.com", "b@example.com"], "subject": "Status", "body": "All good", "confirm": true } }
{ "name": "calendar_add", "arguments": { "calendar_name": "Work", "title": "Standup", "start": "2026-09-21 09:00", "end": "2026-09-21 09:15", "frequency": "weekly", "by_day": ["MO","TU","WE","TH","FR"], "alerts_minutes_before": [10] } }
{ "name": "calendar_remove", "arguments": { "event_id": "EVT-UID", "confirm": true } }This package does not include Reminders, Notes, FaceTime, or Files.
Example questions
"Find emails from John about the quarterly report"
"What messages did I get from Mom last week?"
"When is my next dentist appointment?"
"Search for emails about the AWS bill from November"
"Find all calendar events with Zoom links"
"What's Sarah's phone number?"
"Show me all communication with David from last month"
Privacy
Search and the index read your mail, messages, calendar, and contacts on this Mac. Write tools send messages and change events and contacts. Deletes and multi-recipient sends wait for confirm.
Embeddings are computed locally. Nothing is uploaded. The write bridge is a socket in your home directory, not a network port. The server stores no passwords; it uses the Apple apps you are already signed into. The index stays in ~/.apple-tools-mcp/.
Troubleshooting
"Authorization denied" or the databases will not open. Full Disk Access is missing or it was granted to a different node than the one that is running. Repeat the Full Disk Access steps with which node.
A write is denied, or sending mail hangs. Automation was not granted for the node that is actually running. Run apple-tools-mcp permissions from Terminal.app and allow the printed process.execPath for Mail, Messages, Contacts, Calendar, and System Events. Allow node, not the chat app. If you previously clicked Don't Allow, turn the switch on in Automation and run the command again. dry_run does not talk to Mail, so it will not catch this.
"CONFIRMATION REQUIRED". The safety check worked. Pass confirm: true, or use dry_run: true if you only wanted a preview.
Search returns nothing. Check ls ~/.apple-tools-mcp/vector-index/. If it is missing or stale, rebuild with the commands above.
The server does not show up in the client. Confirm the config is valid JSON, then quit the client completely and reopen it.
Development
git clone https://github.com/sfls1397/Apple-Tools-MCP.git
cd Apple-Tools-MCP
npm install
npm test
npm run indexer
npm run build-index
npm run audit
npm run smoke:writes
npm run smoke:writes -- --applynpm run smoke:writes checks the write path without creating lasting contacts or events. npm run smoke:writes -- --apply performs real creates, edits, and deletes, then removes the test items. The extra -- is required so npm forwards --apply. --apply fails if Mail, Messages, Contacts, or Calendar Automation is missing.
License
MIT License. See LICENSE.
Acknowledgments
Privacy checks for contributors
Install Gitleaks, then enable the committed hook once in each clone:
git config core.hooksPath .githooks
npm run scan:packageBefore a pull request is merged, python3 .security/local-gate.py <PR number> runs
Gitleaks, the production npm audit, offline Semgrep, and the unit and security
tests on the exact PR head and records the result as the local-gate status.
The hook scans staged changes and fails closed if Gitleaks is unavailable. CI scans
all fetched Git history and the exact npm pack --dry-run file list. Release CI
repeats the package scan before npm Trusted Publishing. Credential rules are
extended with private home paths, relay email, private hostnames and phone shapes.
Use neutral example paths and fictional 555 numbers in fixtures. A narrow
exception covers the deliberately fake bearer token in authentication unit tests.
Host-specific settings remain in ~/.apple-tools-mcp/config.json; upgrading the
global package preserves them. Permission examples derive from the current home
and runtime Node binary, so no personal paths belong in the package.
Available Tools
41 toolsaudit_indexB
Audit search index against source data with 0% tolerance. Reports missing items, orphaned entries, and duplicates with detailed file paths and remediation suggestions. Validates 100% of source data.
Inputs: sources, max_items.
calendar_addB
Create a calendar event. Times must be explicit local datetimes (YYYY-MM-DD HH:MM). Supports recurrence (FREQ=DAILY/WEEKLY/MONTHLY/YEARLY with INTERVAL, COUNT or UNTIL, BYDAY) and alerts in minutes before the start. Returns the new event id.
Inputs: start (required), title (required), calendar_name (required), end, +12 more.
calendar_dateA
Get all events on a specific date. Supports natural language dates like 'today', 'tomorrow', 'next Tuesday', 'Jan 15'.
Inputs: date (required).
calendar_editA
Update an existing event by id. Pass only the fields to change. Setting alerts_minutes_before replaces the event's existing alerts.
Inputs: event_id (required), end, count, notes, +13 more.
calendar_free_timeB
Find free time slots on a specific date. Analyzes calendar to find available time windows.
Inputs: date (required), end_hour, start_hour, calendar_name.
calendar_list_calendarsA
List the calendars in Calendar.app with their writability, so events land on the intended calendar instead of the default.
Inputs: no named parameters.
calendar_recurringB
List recurring events (events that appear multiple times). Shows upcoming occurrences.
Inputs: limit.
calendar_removeA
Delete a calendar event by id. Destructive: requires confirm=true, otherwise the call only reports what would be deleted.
Inputs: event_id (required), confirm, dry_run, eventkit_id, +1 more.
calendar_rsvpB
Respond to a calendar invitation: accept, decline, or tentative.
Inputs: event_id (required), response (required), confirm, dry_run, +1 more.
calendar_searchB
Semantic search for calendar events using AI embeddings. Finds events by meaning. Supports filtering by calendar name and all-day events.
Inputs: query (required), limit, sort_by, days_back, +3 more.
calendar_upcomingA
Get next N upcoming events across all calendars. Simpler than calendar_search for quick schedule overview.
Inputs: limit.
calendar_weekA
Get all events for the current week or a future week. Shows events grouped by day.
Inputs: week_offset.
contacts_addC
Create a contact in Contacts.app. Returns the new contact id.
Inputs: emails, phones, confirm, dry_run, +6 more.
contacts_editB
Update a contact by id. Emails and phones are added unless replace_emails / replace_phones is set; replacing with an empty list removes them and requires confirm=true.
Inputs: contact_id (required), emails, phones, confirm, +9 more.
contacts_lookupB
Look up a specific contact by email, phone number, or name. Returns full contact details including all emails and phone numbers.
Inputs: identifier (required).
contacts_removeA
Delete a contact by id. Destructive: requires confirm=true, otherwise the call only reports what would be deleted.
Inputs: contact_id (required), confirm, dry_run.
contacts_searchB
Search your contacts by name, email, phone, or organization. Returns matching contacts with all their details.
Inputs: query (required), limit.
mail_archiveC
Move an email to its account's Archive mailbox.
Inputs: confirm, dry_run, file_path, message_id.
mail_dateB
Get all emails from a specific date. Supports natural language like 'today', 'yesterday', 'November 13', 'last Friday'. Use this when the user asks for emails on a specific date.
Inputs: date (required), include_junk.
mail_draftA
Save an email to Drafts without sending it. Drafts are never delivered, so no recipient confirmation is required.
Inputs: to (required), cc, bcc, body, +4 more.
mail_findA
Exact email lookup in Mail's own database (not the semantic index, so it never waits on indexing and matches filters exactly). Returns JSON: one row per copy (the same Message-ID in INBOX and Sent is two rows) with message_id, subject, from, to, received, mailbox, account_id. Needs at least one of subject_prefix, subject_contains, from, to, message_ids. Trash is excluded unless include_trash or mailboxes asks for it. Pair with mail_trash message_ids to clear what it finds.
Inputs: to, from, limit, from_me, +9 more.
mail_forwardA
Forward an existing email to new recipients. An optional note is pasted above the forwarded original in a visible Mail window (same System Events prepend as mail_reply; AppleScript content is not set). More than one recipient requires confirm=true. A real send is verified in Sent/Outbox before success. A send hang is a timeout, not TCC, unless Mail reports -1743/-10004; hang recover matches intended To plus Fwd: subject or original Message-ID. Clients must Sent-check before retrying a timed-out send.
Inputs: to (required), body, confirm, dry_run, +3 more.
mail_linksA
Links in one email, decoded: each http(s) link with its visible text, from the HTML anchors (quoted-printable and base64 decoded, & unescaped) and bare URLs in the plain-text part. Use the id from mail_find. mail_read strips HTML, so it loses link targets; use this when you need the URL behind a button such as a sign-in or confirm link. Reads Mail's message file directly and never waits on the index. downloaded:false means Mail has not saved the file yet.
Inputs: id (required).
mail_markC
Mark an email as read or unread.
Inputs: status, confirm, dry_run, file_path, +1 more.
mail_readA
Read full email content. Use the file_path from mail_search or mail_recent results.
Inputs: file_path (required).
mail_recentA
Get most recent emails without semantic search. Use this when the user asks for 'recent emails', 'latest emails', 'what emails did I get', or 'unread emails'.
Inputs: limit, days_back, unread_only, include_junk.
mail_replyA
Reply to an existing email. The reply text is pasted above the quoted original in a visible Mail window (System Events focus on the message body, then Command-Up and clipboard paste — not Cmd-A, which would replace the quote; AppleScript content is not set because Mail 16 leaves it empty). reply_all=true fans out to every original recipient and requires confirm=true. A real send is verified in Sent/Outbox before success. A send hang is a timeout, not TCC, unless Mail reports -1743/-10004; hang recover uses In-Reply-To. Clients must Sent-check before retrying a timed-out send.
Inputs: body (required), confirm, dry_run, file_path, +3 more.
mail_searchB
Semantic search for emails using AI embeddings. Finds emails by meaning, not just keywords. Supports filtering by sender, recipient, attachments, mailbox, sent/received, and flagged.
Inputs: query (required), limit, sender, mailbox, +7 more.
mail_sendA
Send an email through Mail.app. Success requires Sent/Outbox verification, not merely an AppleScript return. Multiple recipients require confirm=true. Durable claims never expire uncertainty or automatically retry; changing/removing a key cannot bypass pending or terminal not_sent lineage. Read structuredContent.mail_send: suppression/replay is not a new delivery and cannot advance a page ledger. Legacy pending claims cannot be reconciled. A later retry after terminal not_sent needs separate protected single-use authorization, not confirm or a new key.
Inputs: to (required), body (required), subject (required), cc, +7 more.
mail_sendersB
List most frequent email senders. Helps identify who you communicate with most.
Inputs: limit, days_back, include_junk.
mail_send_reconcileB
No-send claim adjudication. Legacy claims are always ineligible. begin establishes a persistent global writer fence before fresh evidence is collected; commit requires protected administrator-provisioned approval, complete Sent AND Outbox evidence, and exclusion of all old/queued/script writers. Missing authority refuses. Neither action sends, retries, wakes Mail, changes the page ledger, or issues retry permission.
Inputs: action (required), attempt (required), claim_id (required), revision (required), +11 more.
mail_threadB
Get all emails in a conversation thread. Finds related emails by matching subject lines.
Inputs: file_path (required), limit.
mail_trashA
Move email to Trash. Destructive: requires confirm=true, otherwise the call only reports what would be trashed. One message: message_id or file_path. Many: message_ids (up to 500, e.g. from mail_find) moves every copy of each (INBOX, Sent, Archive...) to that account's Trash; each copy's Message-ID is re-checked before it moves. A bulk call stops starting new moves after ~35s and reports 'remaining'; run it again for the rest.
Inputs: confirm, dry_run, file_path, message_id, +1 more.
messages_contactsB
List all contacts you've messaged, sorted by most recent. Shows message count and last message date.
Inputs: limit.
messages_conversationA
Get full conversation history with a specific contact. Shows messages in chronological order.
Inputs: contact (required), limit.
messages_recentA
Get most recent messages without semantic search. Use this when the user asks for 'recent messages', 'latest texts', or 'what messages did I get'. My own messages show a To: line naming the contact or group chat they were sent to.
Inputs: limit, days_back.
messages_searchB
Semantic search for iMessages/SMS using AI embeddings. Finds messages by meaning. Supports filtering by contact, group chats, specific group name, and attachments.
Inputs: query (required), limit, contact, sort_by, +4 more.
messages_sendA
Send an iMessage or SMS. Address it with 'to' (E.164 phone numbers like +15551234567, or Apple ID emails) or with 'chat_id' (a chat GUID from chat.db, e.g. 'iMessage;-;+15551234567' or 'iMessage;+;chat123456'). Group chats and multiple recipients require confirm=true.
Inputs: to, text, chat_id, confirm, +3 more.
person_searchA
Search ALL communication with a specific person across Mail, Messages, and Calendar. Automatically finds their emails and phone numbers from Contacts to search all sources.
Inputs: name (required), limit.
rebuild_indexA
Rebuild the search index for one or more data sources. This clears the existing index and re-indexes all content from scratch. Use this if search results are stale, missing, or if the index is corrupted. Can rebuild emails, messages, calendar, or all sources at once.
Inputs: sources.
smart_searchA
Intelligent search across Mail, Messages, and Calendar. Automatically determines which sources to search based on your query. Returns results grouped by time when multiple sources match. Use this for complex queries that might span multiple data sources.
Inputs: query (required), limit, synthesize.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 41 tools
Most tools have sharply distinct purposes and the descriptions explicitly disambiguate the risky pairs (mail_search semantic vs mail_find exact vs mail_recent vs mail_date; calendar_search vs calendar_date vs calendar_week vs calendar_upcoming). Remaining friction is the sheer number of near-sibling retrieval tools per domain, plus contacts_search vs contacts_lookup and person_search vs smart_search, but the descriptions give clear selection cues in each case.
There is a strong, predictable domain-prefix scheme (mail_*, messages_*, calendar_*, contacts_*) which makes grouping obvious. Suffixes mix patterns though — noun_verb (mail_search, calendar_search) alongside noun_adj/noun_noun (mail_recent, calendar_date, mail_links) and unprefixed outliers (smart_search, person_search, rebuild_index, audit_index).
41 tools is heavy for what is four Apple data domains plus index maintenance; each domain carries 7-10 tools including many overlapping read paths. Some are clearly niche (mail_send_reconcile with a dense claim-adjudication contract, rebuild_index, audit_index) and inflate the surface beyond what most agents need.
Coverage is genuinely broad: mail read/search/send/draft/reply/forward/archive/trash/mark, messages read/search/send, calendar full CRUD plus RSVP, recurring, free-time and calendar listing, and contacts CRUD plus search/lookup. Gaps are minor but real — no flag/star setter despite a flagged search filter, no draft listing/retrieval or sending an existing draft, no mail move (only archive/trash), and no contact list-all.
Maintenance
Related MCP Connectors
Mac & Windows: let ChatGPT, Claude & Cursor use your email, calendar, iMessage, Teams, files.
Mail, Calendar, Messages, Notes, Contacts, Slack on your Mac for AI. Remote needs Pro or free trial.
One semantic search across your sites, Drive, Notion, email, files and Basecamp.
Personal memory layer: files, notes, messages, tasks and email, semantically indexed.
Related MCP Servers
- AlicenseAqualityAmaintenancePrivacy-first local document search using semantic search. Runs entirely on your machine with no cloud services, supporting PDF, DOCX, TXT, and Markdown files.93,817 npm429MIT
- FlicenseNot gradedqualityNot gradedmaintenanceEnables AI assistants like Claude to search and reference your Apple Notes using semantic search and RAG capabilities, with fully local execution and no API keys required.4,261 npm-
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to search and retrieve memories from your Mac, including screen captures, meeting transcripts, and browsing history, all locally and privately.1Elastic 2.0
- AlicenseNot gradedqualityCmaintenanceEnables semantic search, connection discovery, and grounded synthesis across your Apple Notes using on-device embeddings and optional LLM synthesis, with direct SQLite access for fast indexing.7 npm8MIT