local-apple-mcp
README.md
# local-apple-mcp
A **local, offline [MCP](https://modelcontextprotocol.io) server** that lets Claude — or any MCP client — work with your Apple apps on macOS: read across multiple Mail accounts, check your Calendar, find and view Photos, and drop finished notes into Apple Notes.
**Nothing leaves your Mac.** No cloud service, no API keys, no accounts to sign up for, no third-party dependencies. Every tool runs through Apple's own frameworks (EventKit, PhotoKit) and AppleScript on the machine you run it on.
Built for people who juggle **multiple email accounts** and want an assistant that can actually read their **native Apple Mail / Calendar / Photos** to help plan a day — without handing that data to a web API.
## Why local?
Most "connect your email/calendar to AI" tools route your data through a hosted service. This one doesn't. The server is a single Node file plus two small Swift helpers, all running under your own macOS user account and permissions. You can read every line of it in a few minutes.
- **Read-only by default.** Four of the five tools only read. The one write tool (`create_note`) is *create-only* — it makes a new note and can't edit, append to, or delete anything.
- **No secrets.** There's nothing to configure but your own account names and dates, which live in a gitignored `config.json`.
- **Auditable.** ~750 lines of Node, zero npm dependencies, two readable Swift files.
## Tools
| Tool | Access | What it does |
| --- | --- | --- |
| `read_mail` | read-only | Unread + flagged messages across one or more Apple Mail accounts (sender, subject, date, preview). |
| `read_calendar` | read-only | Upcoming events via EventKit, with recurring events correctly expanded. Also surfaces configured birthdays/anniversaries/holidays within 14 days. |
| `search_photos` | read-only | Search the Photos library by **metadata only** (album, favorites, date, filename, GPS). No image bytes. Cheap first pass. |
| `view_photos` | read-only | Export a shortlist of specific photos as downscaled JPEGs (to a temp folder) and return them as viewable images. |
| `create_note` | **create-only** | Make a **new** Apple Note (plain text or light Markdown). Never edits or deletes existing notes. |
## Requirements
- **macOS 14 (Sonoma) or later** — the Calendar helper uses the modern EventKit full-access API.
- **Node.js 18+** — `node --version` to check. No packages to install.
- **Xcode Command Line Tools** — provides the `swiftc` compiler used by `build.sh`. Install with `xcode-select --install`.
- An MCP client, e.g. **Claude Desktop** or **Claude Code**.
## Install
```bash
git clone https://github.com/armyrunner9916/local-apple-mcp.git
cd local-apple-mcp
# 1. Build the two Swift helpers (compiles calendar_helper + photos_helper)
./build.sh
# 2. Create your config from the template
cp config.example.json config.json
# then edit config.json (see "Configuration" below)
```
There is no `npm install` — the server has no dependencies.
## Configuration
All of your personal settings live in `config.json` (which is gitignored, so it never gets committed). Copy `config.example.json` and edit:
```json
{
"mail": {
"accounts": ["you@gmail.com", "you@work.com"]
},
"calendar": {
"excludedCalendars": ["Holidays in United States", "Siri Suggestions", "Birthdays"]
},
"notes": {
"defaultFolder": "Claude Notes",
"scriptsFolder": "Claude Scripts"
},
"specialDates": {
"birthdays": [{ "name": "Alex Doe", "month": 3, "day": 14 }],
"anniversaries": [{ "name": "Wedding Anniversary", "month": 6, "day": 8, "year": 2020 }],
"holidays": [
{ "name": "Mother's Day", "month": 5, "dynamic": "second_sunday" },
{ "name": "New Year's Day", "month": 1, "day": 1 }
]
}
}
```
**Field notes:**
- `mail.accounts` — must match **exactly** what Apple Mail shows in **Settings → Accounts** (e.g. `"Google"`, or the address). If a name is wrong, that account is skipped with an error. Leave the array empty and `read_mail` will just tell you to configure it.
- `calendar.excludedCalendars` — calendar titles to hide (holiday/suggestion noise). Anything not listed is included.
- `notes.defaultFolder` — where `create_note` puts notes when no folder is passed. Created automatically if it doesn't exist.
- `specialDates.holidays` — each holiday is either a **fixed date** (`month` + `day`) or **computed** via `dynamic`, which supports: `"second_sunday"`, `"third_sunday"`, `"first_sunday_after_labor_day"`.
If `config.json` is missing, the server still runs on sensible defaults (Mail simply reports that no accounts are configured).
## Granting macOS permissions (the important part)
macOS gates access to Mail, Calendar, and Photos behind its privacy system (TCC). The **process that launches the MCP server** — your MCP client (Claude Desktop, Claude Code, or the terminal you run it from) — is what needs permission. This is where most setup problems happen, so do this carefully.
The first time each tool runs, macOS should prompt. If it doesn't, or you denied it, grant access manually in **System Settings → Privacy & Security**:
1. **Full Disk Access** — add and enable your MCP client (or terminal app). This is the most reliable way to let it read the Mail store. *(System Settings → Privacy & Security → Full Disk Access.)*
2. **Calendars → Full Access** — enable your MCP client. Required for `read_calendar`.
3. **Photos** — enable your MCP client. See the note below on why this asks for "Full Access."
4. **Automation** — the first `create_note` (and Mail read) may pop an Automation prompt to control **Mail** / **Notes**. Allow it. *(System Settings → Privacy & Security → Automation.)*
After changing any of these, **fully quit and reopen your MCP client** so it picks up the new grant.
### Why Photos asks for "Full Access" even though this is read-only
Apple's PhotoKit exposes only **two** permission levels: *"Add Photos Only"* and *"Full Access"* (read/write). **There is no read-only tier.** To read photo metadata or export a thumbnail at all, an app is required to request Full Access — there simply is no narrower permission to ask for.
This server never writes to your Photos library. `search_photos` reads metadata; `view_photos` exports *downscaled copies to a temp folder on disk*. No asset is ever created, edited, or deleted (the code calls zero PhotoKit mutating APIs — see the `READ-ONLY GUARANTEE` comment in [`photos_helper.swift`](photos_helper.swift)). The permission label is Apple's; the behavior is read-only.
## Wiring it into your MCP client
### Claude Desktop
Edit your `claude_desktop_config.json`:
- macOS path: `~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"local-apple": {
"command": "node",
"args": ["/absolute/path/to/local-apple-mcp/server.js"]
}
}
}
```
Use the **absolute** path to `server.js`. Restart Claude Desktop, and the tools appear.
### Claude Code
```bash
claude mcp add local-apple -- node /absolute/path/to/local-apple-mcp/server.js
```
## Security model
- **Transport is stdio.** The server speaks JSON-RPC over stdin/stdout to the client that launched it. It opens no network sockets and makes no outbound connections.
- **Read-only, except one create-only write.** `read_mail`, `read_calendar`, `search_photos`, and `view_photos` cannot change anything. `create_note` can only *add* a new note.
- **Runs as you.** It has exactly the access your macOS user + the granted TCC permissions allow — nothing more.
- **Your data stays in config.json**, which is gitignored.
## Limitations
- **macOS only.** It depends on Apple frameworks and AppleScript.
- **Mail reading uses AppleScript**, which requires the Mail app and can be slow on very large inboxes (tune `days_back`). Account names must match exactly.
- **Photo scene/keyword search is filename-only.** PhotoKit doesn't expose Apple's on-device scene labels via a public query, so `keyword` matches filenames. For real subject matching, let the model *look* at a shortlist via `view_photos`.
- **`create_note` can't dedupe.** Because it can't read existing notes, every call makes a new note — it never detects duplicates.
- **First-run permission prompts** can be finicky; see the permissions section.
## Project layout
```
local-apple-mcp/
├── server.js # the MCP server (Node, no dependencies)
├── config.example.json # copy to config.json and fill in
├── calendar_helper.swift # EventKit helper (build with ./build.sh)
├── photos_helper.swift # PhotoKit helper (build with ./build.sh)
├── build.sh # compiles both Swift helpers
├── package.json
├── .gitignore
├── LICENSE # MIT
└── README.md
```
## More from Armyrunner Studios
Built by [Armyrunner Studios](https://armyrunner-studios.com). Stop by to see the other apps I've shipped.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues