An MCP server for interacting with Apple Reminders on macOS
Claude Desktop config.json'a ekle
{
"mcpServers": {
"fradser-mcp-server-apple-events": {
"command": "node",
"args": [
"~/.mcp/mcp-server-apple-events/index.js"
]
}
}
} Kaynak kodu al ve yerel olarak çalıştır
git clone https://github.com/FradSer/mcp-server-apple-events.git ~/.mcp/mcp-server-apple-events
cd ~/.mcp/mcp-server-apple-events English | 简体中文
A Model Context Protocol (MCP) server that provides native integration with Apple Reminders and Calendar on macOS. This server allows you to interact with Apple Reminders and Calendar Events through a standardized interface with comprehensive management capabilities.
[!NOTE] Looking ahead: event — a pure Swift CLI for Apple Reminders and Calendar on macOS.
For scripting, automation, and direct terminal usage, we now recommend the standalone
eventCLI. It exposes the same EventKit-backed reminder/calendar/list/subtask/tag operations this server uses today, with first-class Markdown and JSON output. Future versions ofmcp-server-apple-eventsare planned to depend on theeventCLI in place of the bundledEventKitCLIbinary, so both projects can share a single, well-tested Swift implementation.
Apple now separates Reminders and Calendar permissions into write-only and full-access scopes. The Swift bridge declares the following privacy keys so Claude can both read and write data when you approve access:
NSRemindersUsageDescriptionNSRemindersFullAccessUsageDescriptionNSRemindersWriteOnlyAccessUsageDescriptionNSCalendarsUsageDescriptionNSCalendarsFullAccessUsageDescriptionNSCalendarsWriteOnlyAccessUsageDescriptionWhen the CLI detects a notDetermined authorization status it calls requestFullAccessToReminders / requestFullAccessToEvents, which in turn triggers macOS to show the correct prompt. If the OS ever loses track of permissions, rerun ./check-permissions.sh to re-open the dialogs.
If a Claude tool call still encounters a permission failure, see Desktop MCP clients below for the responsible-process attribution problem and the recommended workarounds.
If you see Failed to read calendar events, verify Calendar is set to Full Calendar Access:
System Settings > Privacy & Security > CalendarsYou can also re-run ./check-permissions.sh (it now validates both Reminders and Calendars access).
macOS attributes Reminders and Calendar access to the responsible process — the desktop app that launched the MCP server, not the EventKitCLI subprocess. For the EventKit prompt to appear, the responsible app’s bundle must ship the NSRemindersUsageDescription / NSCalendarsUsageDescription keys (and on Sonoma+ the matching write-only or full-access variants). If those keys are missing, TCC refuses the request before EventKit is even reached, and the Swift CLI returns:
Reminder permission denied. Unknown error
— even though running the same binary from Terminal works. See issue #93 for the full TCC log; Codex Desktop today ships only NSAppleEventsUsageDescription, which is why it hits this wall.
This is a macOS-level constraint that an MCP server alone cannot resolve — the desktop client itself needs to declare those usage strings in its Info.plist. The workarounds below are about making the server usable while you wait for the upstream fix:
Reliable workaround — run the server from a terminal-based MCP client. Codex CLI, Claude Code, and similar terminal-launched clients inherit Terminal’s (or iTerm2’s) own kTCCServiceReminders / kTCCServiceCalendar grant, so EventKit calls succeed without changes to this server:
# from inside Terminal / iTerm2, where the responsible app holds the EventKit grants
codex
# or
claude
Partial workaround — AppleScript routing (only if the desktop app already declares NSAppleEventsUsageDescription). Running:
osascript -e 'tell application "Reminders" to get name of lists'
osascript -e 'tell application "Calendar" to get name of calendars'
triggers an Automation prompt (kTCCServiceAppleEvents) so the responsible app can control com.apple.reminders and com.apple.iCal. This does not create a kTCCServiceReminders / kTCCServiceCalendar grant on its own, so a Swift CLI that calls EventKit directly will still be refused if the host bundle is missing the usage strings. It only helps if your client can fall back to AppleScript end-to-end (this server does not today).
Verification command
pnpm test -- src/swift/Info.plist.test.ts
The test suite ensures all required usage-description strings are present before shipping the binary.
could not build module 'Foundation' on macOS 26 (Tahoe)If pnpm build fails with could not build module 'Foundation' (or SDK is not supported by the compiler), your Swift toolchain is older than the macOS 26 SDK requires. The macOS 26+ SDK ships a Foundation.swiftinterface that needs Swift 6.3 or newer; the Command Line Tools that shipped with the first macOS 26 point releases include Swift 6.2.x, which cannot parse it. See issue #85.
pnpm build:swift now detects this mismatch and prints the same remediation, but if you hit it manually:
softwareupdate --list
sudo softwareupdate -i "Command Line Tools for Xcode-<latest>"
xcode-select at the full Xcode:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
Verify with:
xcrun swiftc --version # should report Apple Swift version 6.3 or newer
xcrun --show-sdk-version # should match your macOS major version
You can run the server directly using npx:
npx mcp-server-apple-events
Open Cursor
Open Cursor settings
Click on “MCP” in the sidebar
Click “Add new global MCP server”
Configure the server with the following settings:
{
"mcpServers": {
"apple-reminders": {
"command": "npx",
"args": ["-y", "mcp-server-apple-events"]
}
}
}
stdioapple-remindersmcp-server-apple-eventsYou need to configure Claude Desktop to recognize the Apple Events MCP server. There are two ways to access the configuration:
claude_desktop_config.jsonFor macOS:
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
For Windows:
code %APPDATA%\Claude\claude_desktop_config.json
Add the following configuration to your claude_desktop_config.json:
Option A: Using npx (recommended)
{
"mcpServers": {
"apple-reminders": {
"command": "npx",
"args": ["-y", "mcp-server-apple-events"]
}
}
}
Option B: Using local build
If you have built the project locally, use node with the path to dist/index.js:
{
"mcpServers": {
"apple-reminders": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-apple-events/dist/index.js"]
}
}
}
For more information on connecting local MCP servers, see the official MCP documentation.
For the changes to take effect:
Once configured, you can ask Claude to interact with your Apple Reminders. Here are some example prompts:
Create a reminder to "Buy groceries" for tomorrow at 5 PM.
Add a reminder to "Call mom" with a note "Ask about weekend plans".
Create a reminder in my "Work" list to "Submit report" due next Friday.
Create a reminder with URL "Check this website: https://google.com".
Create a high priority reminder to "Finish quarterly report" due Friday.
Add an urgent high-priority reminder to "Call client back" for today.
Create a medium priority reminder to "Review documents".
Create a daily reminder to "Take medication" at 9 AM.
Add a weekly reminder every Monday to "Team standup meeting".
Create a monthly reminder on the 1st to "Pay rent".
Set up a yearly reminder on March 15 to "File taxes".
Remind me to "Buy milk" when I arrive at the grocery store.
Create a reminder to "Check mailbox" when I get home.
Add a reminder to "Submit timesheet" when I leave the office.
Create a reminder "Review PR" with tags work and urgent.
Add a reminder "Buy birthday gift" tagged personal and shopping.
Create a reminder with tags: project-alpha, backend, review.
Create a reminder "Grocery shopping" with subtasks: milk, eggs, bread, butter.
Add a reminder "Pack for trip" with checklist items: passport, charger, clothes, toiletries.
Create "Sprint planning" with subtasks: review backlog, estimate stories, assign tasks.
Show subtasks for my "Grocery shopping" reminder.
Mark the "milk" subtask as complete.
Add a new subtask "cheese" to my grocery list reminder.
Reorder the subtasks in my packing list.
Show me all high priority reminders.
Show reminders tagged with "work".
Show recurring reminders only.
Find location-based reminders.
Show reminders with incomplete subtasks.
Update the reminder "Buy groceries" with a new title "Buy organic groceries".
Update "Call mom" reminder to be due today at 6 PM.
Update the reminder "Submit report" and mark it as completed.
Change the notes on "Buy groceries" to "Don't forget milk and eggs".
Set priority to high on my "Finish report" reminder.
Add the tag "urgent" to my "Review PR" reminder.
Show me all my reminders.
List all reminders in my "Shopping" list.
Show my completed reminders.
Show all my reminder lists.
Show reminders from my "Work" list.
The server will:
The server ships with a consolidated prompt registry exposed via the MCP ListPrompts and GetPrompt endpoints. Each template shares a mission, context inputs, numbered process, constraints, output format, and quality bar so downstream assistants receive predictable scaffolding instead of brittle free-form examples.
today_focus (what you most want to accomplish today) input produces a same-day execution blueprint that keeps priority work balanced with recovery time. Supports intelligent task clustering, focus block scheduling, automatic reminder list organization, and auto-creates calendar time blocks when many due-today reminders need fixed slots. Quick Win clusters become 15-minute “Focus Sprint — [Outcome]” holds that finish at each reminder’s due timestamp, while Standard tasks map to 30-, 45-, or 60-minute events anchored to the same due-time window.task_idea (a short description of what you want to do) generates an optimally scheduled reminder structure.review_focus (e.g., overdue or a list name) to audit and optimize existing reminders.user_ideas (your thoughts and ideas for what you want to accomplish this week) guides a Monday-through-Sunday reset with time blocks tied to existing lists.pnpm test -- src/server/prompts.test.ts to assert metadata, schema compatibility, and narrative assembly each time you amend prompt copy.This server now exposes service-scoped MCP tools that mirror Apple Reminders and Calendar domains. Use the identifier that matches the resource you want to manipulate:
Tool Name: reminders_tasks
Manages individual reminder tasks with full CRUD support, including priority, alarms, recurrence rules, start/due/completion dates, location triggers, tags, and subtasks.
Actions: read, create, update, delete
Main Handler Functions:
handleReadReminders() - Read reminders with filtering optionshandleCreateReminder() - Create new remindershandleUpdateReminder() - Update existing remindershandleDeleteReminder() - Delete remindersRead Action (action: "read"):
id (optional): Unique identifier of a specific reminder to readfilterList (optional): Name of the reminder list to showshowCompleted (optional): Include completed reminders (default: false)search (optional): Search term to filter reminders by title or contentdueWithin (optional): Filter by due date range (“today”, “tomorrow”, “this-week”, “overdue”, “no-date”)filterPriority (optional): Filter by priority level (“high”, “medium”, “low”, “none”)filterRecurring (optional): Filter to only show recurring reminders when truefilterLocationBased (optional): Filter to only show location-based reminders when truefilterTags (optional): Filter by tags (reminders must have ALL specified tags)Create Action (action: "create"):
title (required): Title of the reminderstartDate (optional): Start date in format ‘YYYY-MM-DD’ or ‘YYYY-MM-DD HH:mm:ss’dueDate (optional): Due date in format ‘YYYY-MM-DD’ or ‘YYYY-MM-DD HH:mm:ss’targetList (optional): Name of the reminders list to add tonote (optional): Note text to attach to the reminderurl (optional): URL to associate with the reminderlocation (optional): Location text (EKCalendarItem.location) (not a geofence trigger)priority (optional): Priority level (0=none, 1=high, 5=medium, 9=low)alarms (optional): Array of alarm objects (see Alarm Object below)recurrenceRules (optional): Array of recurrence rules (see Recurrence Rules below)recurrence (optional): Legacy single recurrence rule object (shorthand for one-item recurrenceRules)locationTrigger (optional): Location trigger object (see Location Triggers section below)tags (optional): Array of tags to add to the remindersubtasks (optional): Array of subtask titles to create with the reminderUpdate Action (action: "update"):
id (required): Unique identifier of the reminder to updatetitle (optional): New title for the reminderstartDate (optional): New start datedueDate (optional): New due date in format ‘YYYY-MM-DD’ or ‘YYYY-MM-DD HH:mm:ss’note (optional): New note texturl (optional): New URL to attach to the reminderlocation (optional): New location text (set to empty string to clear)completed (optional): Mark reminder as completed/uncompletedcompletionDate (optional): Set an explicit completion date/timetargetList (optional): Name of the list containing the reminderpriority (optional): New priority level (0=none, 1=high, 5=medium, 9=low)alarms (optional): Replace alarms with this arrayclearAlarms (optional): Set to true to remove all alarmsrecurrenceRules (optional): Replace recurrence rules with this arrayrecurrence (optional): Legacy single recurrence rule (shorthand for one-item recurrenceRules)clearRecurrence (optional): Set to true to remove recurrencelocationTrigger (optional): New location triggerclearLocationTrigger (optional): Set to true to remove location triggertags (optional): Replace all tags with this arrayaddTags (optional): Tags to add (merges with existing)removeTags (optional): Tags to removeDelete Action (action: "delete"):
id (required): Unique identifier of the reminder to delete{
"relativeOffset": -900, // Seconds (relative to due/start); negative = before
"absoluteDate": "2025-11-04T09:00:00+08:00", // Absolute trigger time (optional)
"locationTrigger": { // Geofence trigger (optional)
"title": "Office",
"latitude": 37.7749,
"longitude": -122.4194,
"radius": 100,
"proximity": "enter"
}
}
Each alarm must specify exactly one of relativeOffset, absoluteDate, or locationTrigger.
recurrenceRules){
"frequency": "daily" | "weekly" | "monthly" | "yearly",
"interval": 1, // Every N periods (default: 1)
"endDate": "YYYY-MM-DD", // Optional end date
"occurrenceCount": 10, // Optional max occurrences
"daysOfWeek": [1, 3, 5], // 1=Sunday, 7=Saturday (for weekly)
"daysOfMonth": [1, 15], // 1-31 (for monthly)
"monthsOfYear": [3, 6] // 1-12 (for yearly)
}
{
"title": "Home", // Location name
"latitude": 37.7749, // Latitude coordinate
"longitude": -122.4194, // Longitude coordinate
"radius": 100, // Geofence radius in meters (default: 100)
"proximity": "enter" // "enter" or "leave"
}
{
"action": "create",
"title": "Buy groceries",
"dueDate": "2024-03-25 18:00:00",
"targetList": "Shopping",
"note": "Don't forget milk and eggs",
"priority": 1,
"tags": ["shopping", "errands"],
"subtasks": ["Milk", "Eggs", "Bread"]
}
{
"action": "create",
"title": "Team standup",
"dueDate": "2024-03-25 09:00:00",
"recurrence": {
"frequency": "weekly",
"interval": 1,
"daysOfWeek": [2, 3, 4, 5, 6]
}
}
{
"action": "create",
"title": "Buy milk",
"locationTrigger": {
"title": "Grocery Store",
"latitude": 37.7749,
"longitude": -122.4194,
"radius": 200,
"proximity": "enter"
}
}
{
"action": "read",
"filterList": "Work",
"showCompleted": false,
"dueWithin": "today",
"filterPriority": "high",
"filterTags": ["urgent"]
}
{
"action": "delete",
"id": "reminder-123"
}
Tool Name: reminders_subtasks
Manages subtasks/checklists within reminders. Subtasks are stored in the notes field using a human-readable format visible in the native Reminders app.
Actions: read, create, update, delete, toggle, reorder
Main Handler Functions:
handleReadSubtasks() - List all subtasks for a reminderhandleCreateSubtask() - Add a new subtaskhandleUpdateSubtask() - Modify a subtaskhandleDeleteSubtask() - Remove a subtaskhandleToggleSubtask() - Flip completion statushandleReorderSubtasks() - Change subtask orderRead Action (action: "read"):
reminderId (required): Parent reminder IDCreate Action (action: "create"):
reminderId (required): Parent reminder IDtitle (required): Subtask titleUpdate Action (action: "update"):
reminderId (required): Parent reminder IDsubtaskId (required): Subtask ID to updatetitle (optional): New titlecompleted (optional): New completion statusDelete Action (action: "delete"):
reminderId (required): Parent reminder IDsubtaskId (required): Subtask ID to deleteToggle Action (action: "toggle"):
reminderId (required): Parent reminder IDsubtaskId (required): Subtask ID to toggleReorder Action (action: "reorder"):
reminderId (required): Parent reminder IDorder (required): Array of all subtask IDs in desired order{
"action": "read",
"reminderId": "reminder-123"
}
{
"action": "create",
"reminderId": "reminder-123",
"title": "Pick up dry cleaning"
}
{
"action": "toggle",
"reminderId": "reminder-123",
"subtaskId": "a1b2c3d4"
}
Subtasks are stored in the notes field with this human-readable format:
User notes here...
---SUBTASKS---
[ ] {a1b2c3d4} First task
[x] {e5f6g7h8} Completed task
[ ] {i9j0k1l2} Another task
---END SUBTASKS---
This format ensures subtasks are visible in the native Reminders app while enabling programmatic access.
Tool Name: reminders_lists
Manages reminder lists - view existing lists or create new ones for organizing reminders.
Actions: read, create, update, delete
Main Handler Functions:
handleReadReminderLists() - Read all reminder listshandleCreateReminderList() - Create new reminder listshandleUpdateReminderList() - Update existing reminder listshandleDeleteReminderList() - Delete reminder listsRead Action (action: "read"):
Create Action (action: "create"):
name (required): Name for new reminder listUpdate Action (action: "update"):
name (required): Current name of the list to updatenewName (required): New name for the reminder listDelete Action (action: "delete"):
name (required): Name of the list to delete{
"action": "create",
"name": "Project Alpha"
}
Tool Name: calendar_events
Handles EventKit calendar events (time blocks) with CRUD capabilities.
Actions: read, create, update, delete
Main Handler Functions:
handleReadCalendarEvents() - Read events with optional filtershandleCreateCalendarEvent() - Create calendar eventshandleUpdateCalendarEvent() - Update existing eventshandleDeleteCalendarEvent() - Delete calendar eventsRead Action (action: "read"):
id (optional): Unique identifier of an event to readfilterCalendar (optional): Calendar name filtersearch (optional): Keyword match against title, notes, or locationavailability (optional): Filter by availability (“busy”, “free”, “tentative”, “unavailable”, “not-supported”)startDate (optional): Filter events starting on/after this dateendDate (optional): Filter events ending on/before this dateCreate Action (action: "create"):
title (required): Event titlestartDate (required): Start date/timeendDate (required): End date/timetargetCalendar (optional): Calendar name to create innote, location, structuredLocation, url, isAllDay (optional): Additional metadataavailability (optional): Availability (“busy”, “free”, “tentative”, “unavailable”)alarms (optional): Array of alarm objects (see Alarm Object above)recurrenceRules (optional): Array of recurrence rules (see Recurrence Rule Object above)Update Action (action: "update"):
id (required): Event identifierclearAlarms (optional): Set to true to remove all alarmsclearRecurrence (optional): Set to true to remove all recurrence rulesspan (optional): Scope for recurring event changes: "this-event" or "future-events"Delete Action (action: "delete"):
id (required): Event identifier to removespan (optional): Scope for recurring event deletes: "this-event" or "future-events"Tool Name: calendar_calendars
Returns the available calendars from EventKit. This is useful before creating or updating events to confirm calendar identifiers. Optional date range filters return only calendars that have events in the range and include event counts.
Actions: read
Optional Parameters:
startDate: Range start for scoped calendar discoveryendDate: Range end for scoped calendar discoveryfilterAccount: Account name such as "Google" or "Exchange"Main Handler Function:
handleReadCalendars() - List all calendars with IDs and titles, or scoped active calendars when a date range is suppliedExample Usage
{
"action": "read"
}
{
"action": "read",
"startDate": "2026-05-04",
"endDate": "2026-05-11",
"filterAccount": "Google"
}
Example Response
{
"content": [
{
"type": "text",
"text": "### Calendars (Total: 3)\n- Work (ID: cal-1)\n- Personal (ID: cal-2)\n- Shared (ID: cal-3)"
}
],
"isError": false
}
Success Response:
{
"content": [
{
"type": "text",
"text": "Successfully created reminder: Buy groceries"
}
],
"isError": false
}
Reminder with Enhanced Features:
When reading reminders, the output includes visual indicators for enhanced features:
Example output:
- [ ] Buy groceries 🏷️📋
- List: Shopping
- ID: reminder-123
- Priority: high
- Tags: #shopping #errands
- Subtasks (1/3):
- [x] Milk
- [ ] Eggs
- [ ] Bread
- Due: 2024-03-25 18:00:00
Note about URL fields: The url field is fully supported by EventKit API. When you create or update a reminder with a URL parameter, the URL is stored in two places for maximum compatibility:
url property (visible in Reminders app detail view via the “i” icon)Dual Storage Approach:
Reminder note content here...
URLs:
- https://example.com
- https://another-url.com
This ensures URLs are accessible both in the Reminders app UI and through the API/notes for parsing.
URL Extraction: You can extract URLs from reminder notes using regex:
// Extract URLs from notes using regex
const urlsRegex = reminder.notes?.match(/https?:\/\/[^\s]+/g) || [];
Benefits of Structured Format:
List Response:
{
"reminders": [
{
"title": "Buy groceries",
"list": "Shopping",
"isCompleted": false,
"dueDate": "2024-03-25 18:00:00",
"priority": 1,
"tags": ["shopping", "errands"],
"subtasks": [
{ "id": "a1b2c3d4", "title": "Milk", "isCompleted": true },
{ "id": "e5f6g7h8", "title": "Eggs", "isCompleted": false }
],
"subtaskProgress": { "completed": 1, "total": 2, "percentage": 50 },
"notes": "Don't forget the organic options",
"url": null
}
],
"total": 1,
"filter": {
"list": "Shopping",
"showCompleted": false
}
}
The server provides intelligent reminder organization capabilities through four built-in strategies:
Automatically categorizes reminders based on priority keywords:
Organizes reminders based on their due dates:
Intelligently categorizes reminders by content analysis:
Simple binary organization:
Organize all reminders by priority:
Organize my reminders by priority
Categorize work-related reminders:
Organize reminders from Work list by category
Sort overdue items:
Organize overdue reminders by due date
Tags provide cross-list categorization for reminders. They are stored in the notes field using the [#tag] format, which keeps them human-readable in the native Reminders app.
Tags are stored at the end of notes:
User notes here...
[#work] [#urgent] [#project-alpha]
Create with tags:
{
"action": "create",
"title": "Review code",
"tags": ["work", "code-review", "urgent"]
}
Filter by tags:
{
"action": "read",
"filterTags": ["work", "urgent"]
}
Update tags (add/remove):
{
"action": "update",
"id": "reminder-123",
"addTags": ["completed"],
"removeTags": ["urgent"]
}
pnpm install
pnpm build
pnpm test
pnpm exec biome check
The CLI entry point includes a project-root fallback, so you can start the server from nested paths (for example dist/ or editor task runners) without losing access to the bundled Swift binary. The bootstrapper walks up to ten directories to find package.json; if you customise the folder layout, keep the manifest reachable within that depth to retain the guarantee.
pnpm build - Build TypeScript and Swift binary (required before running)pnpm build:swift - Build the Swift binary onlypnpm test - Run the Jest test suitepnpm check - Run Biome formatting and TypeScript type checkingRuntime Dependencies:
@modelcontextprotocol/sdk ^1.29.0 - MCP protocol implementationexit-on-epipe ^1.0.1 - Graceful process termination handlingzod ^4.4.3 - Runtime type validationDevelopment Dependencies:
typescript ^6.0.3 - TypeScript compiler@types/node ^25.8.0 - Node.js type definitions@types/jest ^30.0.0 - Jest type definitionsjest ^30.4.2 - Testing framework@swc/core ^1.15.33 - SWC compiler@swc/jest ^0.2.39 - SWC Jest transformer@biomejs/biome 2.4.15 - Code formatting and lintingBuild Tools:
MIT
Contributions welcome! Please read the contributing guidelines first.
Browser automation capabilities using Puppeteer, both support local and remote browser connection.
Official Microsoft Playwright MCP server, enabling LLMs to interact with web pages through structured accessibility snapshots
Automates browser-based workflows using LLMs and computer vision — navigate pages, fill forms, extract data, handle authentication, and automate any website via natural language
Automate your local Chrome browser
An MCP server using Playwright for browser automation and webscrapping
Automate browser interactions in the cloud (e.g. web navigation, data extraction, form filling, and more)