One consolidated MCP server for native macOS apps (Calendar, Reminders, Mail, ...).
Project description
macos-apps-mcp
One consolidated MCP server for native macOS apps — Calendar & Reminders read/write, Messages & Notes content search over the native stores, id-first Mail with draft-and-open replies, plus read-only context and a few actions across Contacts, Photos, Safari, and Shortcuts. Python + FastMCP 2.0, managed with uv.
Replaces the pile of Apple MCP servers a life-cockpit otherwise juggles with a single modular adapter layer you own. Every read returns pointers (id + one-line summary + open-in-app deeplink), never full bodies — so it structurally avoids the context-bloat bug of the archived flagship server; bodies are a separate, bounded, opt-in fetch.
macOS only. See DESIGN.md for the rationale and CHANGELOG.md for what's landed.
Install
Requires macOS and Python ≥ 3.11.
From source (works today):
git clone https://github.com/elfensky/macos-apps-mcp && cd macos-apps-mcp
uv sync
Then point your MCP client at the project's own venv python — deterministic, and it carries the locked PyObjC wheels:
{
"mcpServers": {
"macos-apps": {
"command": "/absolute/path/to/macos-apps-mcp/.venv/bin/python",
"args": ["-m", "macos_apps_mcp"]
}
}
}
From PyPI (once published — see the CHANGELOG): uvx macos-apps-mcp runs the server with no clone,
and the MCP config becomes "command": "uvx", "args": ["macos-apps-mcp"] (same "macos-apps" key).
Permissions (macOS TCC)
Grant access when macOS prompts — the first call to each app triggers its dialog:
- EventKit — Calendar + Reminders (read/write).
- Automation (per app) — Mail, Notes, Contacts, Photos, Safari, Messages actions/reads.
- Full Disk Access — required for Messages content (
chat.db) and the fast Notes path (NoteStore.sqlite). Notes degrades to Automation without it; Messages content raises a clear, typed error telling you to grant it. Run thedoctortool to see what's granted.
Tools
Reads return pointers; results are capped per adapter. Bodies/content are a separate bounded
fetch. Writes/actions are skipped entirely when MACOS_APPS_READ_ONLY is set (see below).
Calendar & Reminders — read/write (EventKit)
| Tool | Args | Notes |
|---|---|---|
events |
when = today | week | YYYY-MM-DD |
list events as pointers |
free_busy |
start, end (ISO), optional calendars ids |
merged busy intervals + free gaps in the window; no event details |
reminders |
due = today | overdue | this-week | a list name |
list reminders as pointers |
calendars / reminder_lists |
— | containers (id + name) to target writes |
create_event / update_event |
title, start, end (ISO), calendar, location, notes, all_day, recurrence |
update is a full replace by id |
delete_event |
id, span, dry_run |
dry_run previews without deleting |
create_reminder / update_reminder |
title, due, list_name, notes, priority (0–9), start, recurrence |
update is a full replace by id |
complete_reminder |
id | marks complete |
A write targets its container by name or Pointer.id; an ambiguous name raises rather than
guessing. Recurrence is an RFC 5545 RRULE (FREQ/INTERVAL/COUNT/UNTIL subset, e.g.
FREQ=WEEKLY;INTERVAL=2;COUNT=10); a recurring reminder needs a due date; unsupported parts
(BYDAY, …) are rejected, not ignored.
Mail — id-first read + draft-and-open (Automation)
Nothing here ever sends — replies and drafts open a compose window for you to review.
| Tool | Args | Notes |
|---|---|---|
mail |
subject-OR-sender substring | inbox matches; id = stable RFC822 message-id, message:// deeplink |
mail_body |
id | one message's plaintext, bounded + truncation-marked |
mail_attachments |
mailbox (inbox/sent/drafts/trash/junk), optional query |
attachment name/size/downloaded per message; works on Drafts |
create_draft |
to, subject, body | opens a draft for review — never sends; returns a locator |
mail_reply |
message_id, reply_body, include_quote |
native threaded reply (sets In-Reply-To/References), quoted original, opens for review — never sends |
Messages — content via chat.db (read-only; Full Disk Access)
| Tool | Args | Notes |
|---|---|---|
messages_chats |
— | conversation list (id + name); no content, no FDA needed |
messages_search |
query, limit |
search message text (decodes attributedBody), newest first |
messages_with |
contact (phone/email), country, limit |
recent messages with one person; country = calling code or 2-letter region (locale default, never +1) |
message_body |
id | full text of one message by guid |
Notes — NoteStore.sqlite reads + opt-in bodies
| Tool | Args | Notes |
|---|---|---|
notes |
title/snippet substring | matching notes (id + snippet); diacritic- & smart-punctuation-insensitive |
notes_all |
— | every live note (id + "Account / Folder" + snippet), Recently Deleted excluded |
note_bodies |
ids (≤ 50) | opt-in plaintext hydration → [{id, body}] |
delete_note |
id, expect_title, dry_run |
moves to Recently Deleted; dry_run previews |
Other read-only context
| Tool | Args | Returns |
|---|---|---|
contacts |
name substring | cards (name, org, first phone + email) |
photos |
search string | media (filename); matches the Photos search field |
safari_tabs |
— | every open tab (url + title) |
shortcuts |
name substring (empty = all) | the user's Shortcuts; id = stable UUID, shortcuts:// deeplink |
ping / now / doctor |
— | health check / time / permission diagnostics |
Actions (writes)
| Tool | Args | Notes |
|---|---|---|
create_contact |
given_name, family_name, organization | |
run_shortcut |
name or id, optional input_text |
runs a Shortcut; returns a bounded output snippet |
safari_open |
url | opens in a new tab; bare host → https://; only http/https allowed |
Read-only mode
Set MACOS_APPS_READ_ONLY=1 (or true / yes) to register reads only — every write and action
tool is skipped, a safe-deploy guard. (Reads may still open apps / read local stores.)
Develop
uv sync
uv run pytest # unit tests (mock at the adapter boundary)
uv run pytest -m integration # real macOS / EventKit / TCC — run manually, never in CI
uv run ruff check . # lint (config in pyproject.toml)
uv run ruff format . # format
uv run macos-apps-mcp # run the server (stdio)
ruff (lint + format, line-length 88) and pytest gate CI — full workflow in CONTRIBUTING.md.
Prior art & credits
macos-apps-mcp builds on prior work — the Apple Mail MCP it draws from, the EventKit/Photos servers it references, the project that pioneered the unified-Apple-MCP pattern, and FastMCP / PyObjC / the MCP spec it depends on. See CREDITS.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file macos_apps_mcp-0.7.0.tar.gz.
File metadata
- Download URL: macos_apps_mcp-0.7.0.tar.gz
- Upload date:
- Size: 346.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3ba4b2213ea7ca993199a8ab11dc4e7b98e5e769d6d36e8af82721198da9cd7
|
|
| MD5 |
4c26fd1ad70135d77dfe673a41c8e674
|
|
| BLAKE2b-256 |
cb860a0abe43163097783e25e0dbed4b80d98be932e23be4e7d263c74a2791cf
|
File details
Details for the file macos_apps_mcp-0.7.0-py3-none-any.whl.
File metadata
- Download URL: macos_apps_mcp-0.7.0-py3-none-any.whl
- Upload date:
- Size: 92.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5847b37bb19be7c55c1aa8647620419baccee8c1526bccc797494d66c9eafce
|
|
| MD5 |
ca78d83d4a93b2b4327b4856e0648659
|
|
| BLAKE2b-256 |
6c2b297799d6aab62009dede4dcf5735aa06e3938588e40cf3c7a600ac8636e9
|