Skip to main content
Glama

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, and calendar.

  • What is indexed:

    • Mail: every .emlx file 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-text attributedBody field. 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_search chooses 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-mcp

Or from source:

git clone https://github.com/sfls1397/Apple-Tools-MCP.git
cd Apple-Tools-MCP
npm install

A 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

  1. In Terminal, run which node and copy the path it prints.

  2. Open System Settings → Privacy & Security → Full Disk Access.

  3. Click +.

  4. Press Cmd+Shift+G, paste the path, and press Enter.

  5. Select node and 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) permissions

From 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) permissions

Allow 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-mcp

4. 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-indexer

That 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.plist

The 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-index

Your 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.lock

Start 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.log

Client logs depend on the app. Claude Desktop:

tail -f ~/Library/Logs/Claude/mcp-server-apple-tools.log

Audit coverage against the source databases:

npm run audit
npm run audit -- --reporter=verbose > audit-report.txt

Your client can also call audit_index.

Available tools

Tool

Description

smart_search

Search across mail, messages, and calendar. Picks the sources from the question.

person_search

Find communication with one person across Mail, Messages, and Calendar.

Email

Tool

Description

mail_search

Semantic search, with filters for sender, recipient, attachments, and mailbox.

mail_recent

Most recent emails. Can limit to unread.

mail_date

Emails from a date such as "today", "yesterday", or "Nov 13".

mail_read

Full email content by file path.

mail_senders

Most frequent senders.

mail_thread

Every email in a conversation.

mail_find

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.

mail_links

The links in one email (by its mail_find id), decoded, with each link's visible text. Use it for the URL behind a button, such as a sign-in link; mail_read drops link targets. Does not wait on the index.

Messages

Tool

Description

messages_search

Semantic search for iMessage and SMS, with filters.

messages_recent

Most recent messages.

messages_conversation

Full conversation with a contact.

messages_contacts

Contacts you have messaged.

Calendar

Tool

Description

calendar_search

Semantic search for events, with filters.

calendar_date

Events on a date, read live from Calendar rather than the search index.

calendar_upcoming

Next upcoming events.

calendar_week

Events for this week or a future week.

calendar_free_time

Open time on a date, read live from Calendar rather than the search index.

calendar_recurring

Recurring events.

Contacts

Tool

Description

contacts_search

Search by name, email, phone, or organization.

contacts_lookup

Full details for one contact.

Index

Tool

Description

rebuild_index

Rebuild the search index for one source or all of them.

audit_index

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

dry_run

Preview only. Nothing is sent or changed. This wins even if confirm is also set.

confirm

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.

Mail

Tool

Arguments

Confirm

mail_send

to[] (required), cc[], bcc[], subject (required), body (required), body_format (plain or html), idempotency_key

when recipients > 1

mail_send_reconcile

action (begin/commit), exact claim binding, deployment_ref, approval_ref; commit also needs fence_id, evidence_ref, outcome and reason code

protected authority, not confirm

mail_draft

to[] (required), cc[], bcc[], subject, body, body_format

no (saved to Drafts)

mail_reply

message_id or file_path, body (required), reply_all, save_as_draft

when reply_all is true

mail_forward

message_id or file_path, to[] (required), body, save_as_draft

when recipients > 1

mail_mark

message_id or file_path, status (read or unread, default read)

no

mail_archive

message_id or file_path

no

mail_trash

message_id or file_path, or message_ids[] (up to 500)

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

messages_send

to[] or chat_id, text, attachment_path, service (auto, imessage, or sms)

several handles, or a group chat

  • to is a phone number in E.164 form (+15551234567) or an Apple ID email.

  • chat_id is an existing conversation id, such as iMessage;-;+15551234567 or iMessage;+;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; imessage and sms pin the service.

  • attachment_path is an absolute path to a file that already exists on this Mac. Text and a file can go together.

Calendar

Tool

Arguments

Confirm

calendar_list_calendars

none

no. Lists calendars so you can pick one.

calendar_add

calendar_name (required), title (required), start (required), end, all_day, location, notes, recurrence, alerts_minutes_before[]

no

calendar_edit

event_id (required), optional eventkit_id from add, plus any of title, start, end, location, notes, recurrence, alerts_minutes_before[], replace_alerts

no

calendar_remove

event_id (required), optional eventkit_id from add, optional calendar_name

yes

calendar_rsvp

event_id (required), response (accept, decline, or tentative), attendee_email

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

frequency

daily, weekly, monthly, yearly

interval

1–366

count

1–1000 occurrences. Do not combine with until.

until

local datetime

by_day

MO TU WE TH FR SA SU

recurrence

a raw rule such as FREQ=WEEKLY;INTERVAL=1;COUNT=10

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

contacts_add

first_name, last_name, organization, job_title, emails[], email_label, phones[], phone_label

no

contacts_edit

contact_id (required), any of the add fields, replace_emails, replace_phones

yes, when replacing with an empty list

contacts_remove

contact_id (required)

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 -- --apply

npm 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:package

Before 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 tools
audit_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_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.

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_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_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_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.

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.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 41 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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).

    Tool Count3/5

    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.

    Completeness4/5

    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

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      A
      maintenance
      Privacy-first local document search using semantic search. Runs entirely on your machine with no cloud services, supporting PDF, DOCX, TXT, and Markdown files.
      9
      3,817 npm
      429
      MIT
    • F
      license
      Not graded
      quality
      Not graded
      maintenance
      Enables 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
      -
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI assistants to search and retrieve memories from your Mac, including screen captures, meeting transcripts, and browsing history, all locally and privately.
      1
      Elastic 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables 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 npm
      8
      MIT