macos-mcp
# macos-mcp
Local MCP server for **macOS native apps**: Mail, Calendar, Reminders, Notes, Messages, and Contacts. One stdio process, zero npm dependencies, Node 20+.
**Windows and Linux are not supported.** Calendar **reads** use EventKit (`bin/eventkit-cal`); Calendar writes and other modules use AppleScript/JXA. No Gmail API, no OAuth, no cloud credentials.
## What it is
> One MCP server = one mental model: read and organize your Mac life.
| Module | App | Prefix |
|--------|-----|--------|
| Mail | Mail.app | `mail_*` |
| Calendar | Calendar.app | `calendar_*` |
| Reminders | Reminders.app | `reminders_*` |
| Notes | Notes.app | `notes_*` |
| Messages | Messages.app (`chat.db`) | `messages_*` |
| Contacts | Contacts.app | `contacts_*` |
**Email is draft-only.** `mail_draft_email` opens a visible draft in Mail.app; it never sends. You review and send manually.
## Requirements
- macOS with Mail, Calendar, Reminders, Notes, Messages configured
- Node.js ≥ 20
- [Cursor](https://cursor.com) (or any MCP client that supports stdio)
## Install
```bash
git clone <your-repo-url> macos-mcp
cd macos-mcp
bash scripts/build-eventkit-cal.sh # required for fast calendar reads
node scripts/install-mcp.js
```
Restart Cursor. The installer registers `macos-local` in `~/.cursor/mcp.json` (backs up existing config to `.bak`).
Manual registration:
```json
{
"mcpServers": {
"macos-local": {
"command": "node",
"args": ["/absolute/path/to/macos-mcp/bin/macos-mcp.js"]
}
}
}
```
### Signatures (mail drafts)
```bash
bash scripts/install-mail-signatures.sh
```
Edit `signatures/manifest.json` and `signatures/*.txt` locally. Real signatures are gitignored.
## macOS permissions
Grant in **System Settings → Privacy & Security**:
| Permission | Needed for |
|------------|------------|
| **Calendars** — Full access for **Cursor** (MCP) and/or **Terminal** (CLI smoke tests); the helper may also appear as `eventkit-cal` | `calendar_list_*` / `calendar_search_events` via EventKit |
| **Automation** — allow Cursor to control Mail, Calendar, Reminders, Notes, Contacts | JXA/AppleScript modules (calendar create/update/delete, Mail, etc.) |
| **Full Disk Access** — Cursor (or Terminal during testing) | Messages module (`~/Library/Messages/chat.db`) |
On first calendar read, macOS may prompt for Calendar access. If the helper exits with “Calendar Full Access required”:
1. Open **System Settings → Privacy & Security → Calendars**
2. Find **Cursor** (MCP) and/or **Terminal** (CLI smoke tests)
3. Enable **Full Access** (not Write Only / Add Only)
4. Re-run `npm run smoke:calendar` or restart the MCP server
Cursor’s Calendar TCC was previously Write Only on this machine; Full Access is required for EventKit reads. Calendar create/update/delete still use Automation (AppleScript) and do not need EventKit Full Access.
## Tool index
### Mail (`mail_*`)
| Tool | Description |
|------|-------------|
| `mail_list_accounts` | List Mail accounts |
| `mail_list_mailboxes` | List mailboxes (optional account filter) |
| `mail_list_messages` | List messages in mailbox |
| `mail_search_messages` | Search subject/sender |
| `mail_get_message` | Full message by id |
| `mail_save_attachments` | Save message attachments to a local directory |
| `mail_draft_email` | Open new compose draft (never sends) |
| `mail_draft_reply` | Open reply draft in thread (never sends) |
| `mail_archive_messages` | Move to All Mail |
| `mail_trash_messages` | Move to Trash |
| `mail_mark_read` | Mark read/unread |
| `mail_move_messages` | Move to mailbox |
### Calendar (`calendar_*`)
| Tool | Description |
|------|-------------|
| `calendar_list_calendars` | List calendars |
| `calendar_list_events` | Events in date range |
| `calendar_search_events` | Search title/location |
| `calendar_get_event` | Full event |
| `calendar_create_event` | Create event (optional `attendees` email list) |
| `calendar_update_event` | Update event |
| `calendar_delete_event` | Delete event |
### Reminders (`reminders_*`)
| Tool | Description |
|------|-------------|
| `reminders_list_lists` | List reminder lists |
| `reminders_list` | List reminders in a list |
| `reminders_search` | Search name/notes |
| `reminders_create` | Create reminder |
| `reminders_complete` | Mark complete |
| `reminders_delete` | Delete reminder |
### Notes (`notes_*`)
| Tool | Description |
|------|-------------|
| `notes_list_folders` | List folders |
| `notes_list` | List notes |
| `notes_search` | Search title/body |
| `notes_get` | Get note body |
| `notes_create` | Create note |
| `notes_append` | Append to note |
On newer macOS, note body extraction may fail; `notes_get` returns `body_unavailable: true` when scripting is restricted.
Calendar **list/search** use EventKit (typically under a second for a week range). Create/update/delete still use Calendar.app scripting. Build the helper with `npm run build:eventkit` if `bin/eventkit-cal` is missing.
### Messages (`messages_*`)
| Tool | Description |
|------|-------------|
| `messages_get_thread` | Thread by phone/email substring |
| `messages_list_chats` | Recent chats |
Requires **Full Disk Access**. Reads iMessage/SMS locally; no network.
### Contacts (`contacts_*`)
| Tool | Description |
|------|-------------|
| `contacts_list` | List contacts (optional: missing email only) |
| `contacts_search` | Search by name/email/phone |
| `contacts_get` | Full contact card |
| `contacts_create` | Create contact |
| `contacts_update` | Update contact |
| `contacts_delete` | Delete contact |
| `contacts_import_csv` | Import Google Contacts CSV (upsert by email) |
## Architecture
```
bin/macos-mcp.js → entrypoint
bin/eventkit-cal → EventKit CLI (built; not committed)
native/EventKitCal/ → Swift source + Info.plist
src/mcp/server.js → JSON-RPC MCP (protocol 2024-11-05)
src/mcp/transport.js → stdio NDJSON
src/mcp/tools.js → aggregates all module tools
src/runtime/eventkit.js → spawns eventkit-cal for calendar reads
src/runtime/jxa.js → generic JXA runner (Application per app)
src/runtime/applescript.js → AppleScript runner + draft email
src/mail/ → Mail module
src/calendar/ → Calendar module (reads → EventKit; writes → AS/JXA)
...
```
**EventKit vs JXA vs AppleScript**
- **EventKit** (`bin/eventkit-cal`): `calendar_list_calendars`, `calendar_list_events`, `calendar_search_events`.
- **JXA** (`osascript -l JavaScript`): read/list/search most other app data; calendar alarm attach after create; generic `runJxa(appName, body, args)`.
- **AppleScript**: `mail_draft_email`, calendar create/update/delete/get, and contacts create/update (JXA `make` is unreliable on modern Calendar.app and Contacts.app).
**Response shapes:** lists include `count`; domain keys vary (`items`, `events`, `reminders`, `notes`, `chats`, `contacts`). Errors throw and return MCP `isError: true`.
## Mail.app quirks
- Saved drafts may retain a leading newline Mail adds on save; the compose window is usually correct.
- AppleScript does not auto-attach Mail.app signature prefs; this server appends from `signatures/` files instead.
## What it is NOT
- No Gmail API, Microsoft Graph, or OAuth
- No auto-send email
- No npm publish (`private: true`)
- No telemetry or network calls in server code
## Development
```bash
npm run build:eventkit # compile bin/eventkit-cal
npm run smoke:calendar # time calendars + week event list (default 2026-07-27→2026-08-03)
npm start # stdio server (stderr: [macos-mcp] ready; N tools; macOS only)
```
Test a tool via MCP client or send JSON-RPC to stdin.
## License
MIT
TDQS
Scored across 40 tools
Each tool has a distinct domain prefix (calendar_, contacts_, mail_, etc.) and a specific action, making it impossible to confuse tools across domains. Even within a domain like mail, tools like mail_draft_email and mail_draft_reply are clearly differentiated.
All tools follow a strict verb_noun pattern with snake_case, e.g., calendar_create_event, mail_list_messages, notes_append. The naming is uniform and predictable across all domains.
40 tools is on the high side, but the server covers multiple macOS apps (Calendar, Contacts, Mail, Messages, Notes, Reminders), each requiring several operations. While the count is heavy, it is not excessive for such a broad scope, though it may overwhelm agents.
Most domains have CRUD-like coverage, but notable gaps exist: Mail lacks a 'send' tool (only draft), Messages cannot send new messages, Notes lacks delete and update (only append), and Reminders lacks field updates. These omissions may hinder agent workflows.