macOS Ecosystem MCP Server
# macOS Ecosystem MCP Server
A fast, native Model Context Protocol (MCP) server for Claude that provides direct access to macOS productivity apps: **Reminders**, **Calendar**, and **Notes**.
## Why This Rewrite?
The original Node.js/TypeScript server shelled out to `osascript` for **every** tool call ā adding 300ā800 ms of process-startup overhead each time.
This Swift rewrite:
- Uses **EventKit** natively for Reminders and Calendar (no subprocess)
- Only shells out to `osascript` for Notes (Notes.framework is private)
- Compiles to a **single self-contained binary** ā no Node.js runtime required
- Speaks the MCP stdio protocol directly via the [official Swift SDK](https://github.com/modelcontextprotocol/swift-sdk)
## Features
### šļø Reminders (4 tools ā EventKit)
- `reminders_add` ā Create reminders with title, notes, due date, priority
- `reminders_list` ā List reminders, filter by list and completion status
- `reminders_complete` ā Mark a reminder as completed (by ID or title)
- `reminders_search` ā Search reminders by keyword
### š
Calendar (5 tools ā EventKit)
- `calendar_create_event` ā Create events with location, notes, alerts
- `calendar_list_events` ā List events within a date range
- `calendar_find_free_time` ā Find available time slots
- `calendar_update_event` ā Modify existing events
- `calendar_delete_event` ā Delete events
### š Notes (3 tools ā osascript)
- `notes_create` ā Create notes with title and body (HTML supported)
- `notes_append` ā Append content to existing notes
- `notes_search` ā Search notes by keyword
### š¬ iMessage (4 tools ā SQLite reads + osascript sends)
- `imessage_list_chats` ā List recent conversations with participants and a last-message preview
- `imessage_read` ā Read a conversation by `chat_guid` or `handle`; includes attachment paths
- `imessage_search` ā Search message text across all conversations
- `imessage_send` ā Send text and/or a file attachment to an **allowlisted** contact
Reads query the local `~/Library/Messages/chat.db` directly (read-only). Sends go
through AppleScript and are gated by a **fail-closed contact allowlist** ā sending
is disabled until you configure one:
- Env var: `MACOS_MCP_IMESSAGE_ALLOWLIST="+15551234567,friend@example.com"`
- And/or file `~/.config/macos-mcp/imessage-allowlist.json`:
```json
{ "allow": ["+15551234567", "friend@example.com"] }
```
Phone numbers are matched loosely (last 10 digits), so formatting/country-code
differences still match. Attachments from protected directories (the config dir,
`~/Library/Messages`) are refused.
## Requirements
- macOS 13 Ventura or later
- Xcode 15+ / Swift 5.9+ (build only)
- Reminders, Calendar, Contacts, and Notes access granted in **System Settings ā Privacy & Security**
- **Full Disk Access** for the app running this server (Terminal, your MCP client, etc.) ā
required to read `chat.db` and to send via Messages. Grant it under
**System Settings ā Privacy & Security ā Full Disk Access**. Without it, the iMessage
tools return an actionable error.
## Build from Source
```bash
git clone https://github.com/neverprepared/macos-ecosystem-mcp.git
cd macos-ecosystem-mcp
swift build -c release
```
The binary is placed at:
```
.build/release/macos-mcp
```
Install it system-wide:
```bash
sudo cp .build/release/macos-mcp /usr/local/bin/macos-mcp
```
## macOS Permissions
The first time the binary runs it will request access. You can also grant it in advance:
1. **System Settings ā Privacy & Security ā Reminders** ā add `macos-mcp`
2. **System Settings ā Privacy & Security ā Calendars** ā add `macos-mcp`
3. **System Settings ā Privacy & Security ā Automation ā Notes** ā add `macos-mcp`
## Claude Desktop Configuration
Add this to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"macos-ecosystem": {
"command": "/usr/local/bin/macos-mcp"
}
}
}
```
Or using the local build path:
```json
{
"mcpServers": {
"macos-ecosystem": {
"command": "/path/to/macos-ecosystem-mcp/.build/release/macos-mcp"
}
}
}
```
Restart Claude Desktop after editing the config.
## Tool Reference
### reminders_add
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | ā | ā | Reminder title |
| list | string | | "Reminders" | List name |
| notes | string | | ā | Body text |
| dueDate | string | | ā | ISO 8601 date-time |
| priority | none\|low\|medium\|high | | "none" | Priority |
### reminders_list
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| list | string | | ā | Filter by list |
| includeCompleted | boolean | | false | Include completed |
| limit | integer | | 50 | Max results (1ā100) |
### reminders_complete
| Parameter | Type | Required | Description |
|---|---|---|---|
| reminderId | string | (one of) | EventKit calendarItemIdentifier |
| title | string | (one of) | Reminder title |
| list | string | | Narrow search to list |
### reminders_search
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| query | string | ā | ā | Search keyword |
| list | string | | ā | Restrict to list |
| includeCompleted | boolean | | false | Include completed |
| limit | integer | | 20 | Max results |
### calendar_create_event
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | ā | ā | Event title |
| startDate | string | ā | ā | ISO 8601 start |
| endDate | string | ā | ā | ISO 8601 end |
| calendar | string | | system default | Calendar name |
| location | string | | ā | Location |
| notes | string | | ā | Description |
| allDay | boolean | | false | All-day event |
| alerts | integer[] | | ā | Alert offsets in minutes |
### calendar_list_events
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| startDate | string | ā | ā | ISO 8601 range start |
| endDate | string | ā | ā | ISO 8601 range end |
| calendar | string | | ā | Filter by calendar |
| limit | integer | | 50 | Max results |
### calendar_find_free_time
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string | ā | ā | ISO 8601 day to search |
| duration | integer | ā | ā | Required slot (15ā480 min) |
| workingHoursStart | integer | | 9 | Start hour (0ā23) |
| workingHoursEnd | integer | | 17 | End hour (0ā23) |
| calendar | string | | ā | Restrict to calendar |
### calendar_update_event
| Parameter | Type | Required | Description |
|---|---|---|---|
| eventId | string | ā | EKEvent eventIdentifier |
| title | string | | New title |
| startDate | string | | New ISO 8601 start |
| endDate | string | | New ISO 8601 end |
| location | string | | New location |
| notes | string | | New description |
### calendar_delete_event
| Parameter | Type | Required | Description |
|---|---|---|---|
| eventId | string | (one of) | EKEvent eventIdentifier |
| title | string | (one of) | Event title |
| date | string | | ISO 8601 date to narrow search |
### notes_create
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | ā | ā | Note title |
| body | string | ā | ā | Note body |
| folder | string | | "Notes" | Folder name |
### notes_append
| Parameter | Type | Required | Description |
|---|---|---|---|
| noteId | string | (one of) | AppleScript note ID |
| title | string | (one of) | Note title |
| folder | string | | Narrow search |
| content | string | ā | Content to append |
### notes_search
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| query | string | ā | ā | Search keyword |
| folder | string | | ā | Restrict to folder |
| limit | integer | | 20 | Max results |
## Architecture
```
Sources/macos-mcp/
āāā App.swift # @main entry ā MCP server setup and tool dispatcher
āāā ToolDefinitions.swift # JSON Schema definitions for all 12 tools
āāā EventKitManager.swift # Swift actor wrapping EKEventStore (reminders + calendar)
āāā NotesHandler.swift # osascript runner for Notes operations
```
## Notes on Flagged Reminders
EventKit does not expose the "flagged" status of reminders (it is a Reminders-app-specific attribute not in the EventKit API). The `flagged` parameter is accepted but silently ignored when creating reminders.
## License
MIT
TDQS
Scored across 12 tools
Every tool has a clearly distinct purpose with no ambiguity, as each is scoped to a specific app (calendar, notes, reminders) and action (create, delete, update, list/search, etc.). For example, calendar_find_free_time is unique for scheduling, while notes_append is distinct from notes_create. The descriptions reinforce this by detailing specific functionalities, ensuring agents can easily differentiate between tools.
Tool names follow a highly consistent verb_noun pattern throughout, with a clear structure of app_action (e.g., calendar_create_event, notes_search). All tools use snake_case uniformly, and the naming convention is predictable, making it easy for agents to infer functionality. There are no deviations in style or verb usage across the set.
The 12 tools are well-scoped for the macOS ecosystem server, covering three key apps (calendar, notes, reminders) with a balanced set of operations. Each tool earns its place by providing essential CRUD and utility functions, such as calendar_find_free_time for scheduling. The count is neither too sparse nor bloated, fitting the domain appropriately.
The tool set offers complete CRUD/lifecycle coverage for each app domain, with no obvious gaps. For calendar, it includes create, delete, update, list, and find_free_time. For notes, it covers create, append, and search. For reminders, it has add, complete, list, and search. This comprehensive surface allows agents to handle all core workflows without dead ends.