Install
$ agentstack add mcp-omarshahine-apple-pim ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Apple PIM: Calendar, Reminders, Contacts, Mail
PIM = Personal Information Manager.
[](https://github.com/omarshahine/apple-pim) [](https://github.com/omarshahine/apple-pim/blob/main/LICENSE)
GitHub: github.com/omarshahine/apple-pim
Native macOS integration for Calendar, Reminders, Contacts, and Mail using EventKit, Contacts, SQLite, and JXA frameworks. Works with Claude Code (via MCP) and OpenClaw (via native tool registration).
Features
- Calendar Management: List calendars, create/read/update/delete events, search by date/title, attendee support (add/replace attendees via CalDAV invitation emails)
- Reminder Management: List reminder lists, create/complete/update/delete reminders, search
- Contact Management: List groups, create/read/update/delete contacts, search by name/email/phone, birthday support (with or without year)
- Mail Integration: List accounts/mailboxes, read/search/send/reply/move/delete messages, update flags, attachment support (metadata, save-to-disk, send/reply with attachments), verify sender authentication
- Fast Local Mail Reads: Read commands query Apple Mail's local Envelope Index (SQLite) directly — 10–200× faster than AppleScript automation, and they work even when Mail.app is closed. Automatic fallback to JXA when Full Disk Access isn't granted. See [Direct SQLite read path](#direct-sqlite-read-path---engine-sqlite)
- Recurrence Rules: Create recurring events and reminders (daily, weekly, monthly, yearly)
- Batch Operations: Create multiple events or reminders in a single efficient transaction
- Per-Domain Control: Enable or disable entire domains (calendars, reminders, contacts, mail) independently
- Multi-Agent Isolation: Per-call config/profile overrides for workspace isolation
- Works with Claude Code and OpenClaw: Same Swift CLIs, different integration layers
Prerequisites
- macOS 13.0 or later
- Swift 5.9 or later (comes with Xcode 15+)
- Node.js 18+ (for MCP server or OpenClaw plugin)
- Mail.app must be running for mail mutations, sends, and content search (it is not launched automatically). Mail reads use the local SQLite index and work with Mail.app closed.
Installation
Swift CLI Tools (Required for both platforms)
# Build the Swift CLIs
./setup.sh
# Optional: install to PATH for system-wide access
./setup.sh --install
# Add to your shell profile (if not already there)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
The --install flag copies the binaries into ~/.local/bin/, so the install keeps working even if you later move, rename, or clean this checkout. After a rebuild, re-run ./setup.sh --install to refresh them.
Developing the CLIs themselves? ./setup.sh --install --link symlinks instead, so every swift build -c release updates the installed commands in place — at the cost that moving or renaming the repo breaks the links.
Verify any install with:
scripts/doctor.sh # add --fix to reap a stuck helper process
Claude Code Plugin
Inside Claude Code, run:
/plugin marketplace add omarshahine/apple-pim
/plugin install apple-pim@apple-pim
Then build the Swift CLIs (once, from a shell — the glob tolerates any marketplace name and version):
bash ~/.claude/plugins/cache/*/apple-pim/*/setup.sh --install
Restart Claude Code to load the MCP server. The pim-assistant agent triggers automatically when you mention scheduling, reminders, contacts, or email.
OpenClaw Plugin
# Install from ClawHub
openclaw plugins install apple-pim-cli
# Or install from npm
npm install -g apple-pim-cli
Under the hood, the plugin spawns native macOS Swift binaries (calendar-cli, reminder-cli, contacts-cli, mail-cli) that interact with EventKit, Contacts, and Mail.app via Apple's frameworks.
Prerequisites: Swift CLIs must be on PATH (run ./setup.sh --install first).
Optionally, configure the binary location if the CLIs are not on PATH:
# In your OpenClaw config:
# plugins.entries.apple-pim-cli.config.binDir = "/path/to/swift/.build/release"
Post-Installation (both platforms)
Grant permissions: On first use, macOS will prompt for Calendar, Reminders, and Contacts access. Grant these permissions in System Settings > Privacy & Security.
Mail.app Automation: For mail features, you also need to grant Automation permission:
- System Settings > Privacy & Security > Automation
- Allow Terminal (or your IDE) to control Mail.app
Full Disk Access (optional, recommended): Mail read commands use a fast direct-SQLite path (see "Direct SQLite read path" below) that requires Full Disk Access for Terminal (or the MCP host); without it, --engine auto silently falls back to the slower Mail.app Automation path and --engine sqlite reports the missing grant as an error. update, move, delete, batch-update and batch-delete consult that same index for the message's row-id before writing — the write itself always goes through Mail.app either way — so the grant governs their lookup on the same terms, with Mail.app's mailbox-by-mailbox scan as the auto fallback.
Development Installation
git clone https://github.com/omarshahine/apple-pim.git
cd apple-pim
./setup.sh
Then inside Claude Code, add the local checkout as a marketplace:
/plugin marketplace add /absolute/path/to/apple-pim
/plugin install apple-pim@apple-pim
For OpenClaw (loads TypeScript directly, no build step):
openclaw plugins install -l ./openclaw
Configuration
The plugin includes a full access control system (PIMConfig) that lets you restrict which calendars, reminder lists, and domains each agent can see. This is useful for:
- Access control — allowlist or blocklist specific calendars and reminder lists
- Privacy — hide calendars you don't need the agent to see
- Reducing noise — only show relevant reminder lists
- Avoiding conflicts — disable mail here if you use a separate email MCP
- Multi-agent setups — give each agent a profile with different access
- Read-only calendars — let agents see but not modify certain calendars
Interactive Setup (Claude Code)
/apple-pim:configure
CLI Config Commands
# Show current effective configuration
calendar-cli config show
reminder-cli config show
# Initialize config from available calendars/lists
calendar-cli config init
reminder-cli config init
Manual Configuration
Config files are stored at ~/.config/apple-pim/:
~/.config/apple-pim/
├── config.json # Base configuration
└── profiles/
├── work.json # Work agent profile
└── personal.json # Personal agent profile
Base config (~/.config/apple-pim/config.json):
{
"calendars": {
"enabled": true,
"mode": "allowlist",
"items": ["Personal", "Work"]
},
"reminders": {
"enabled": true,
"mode": "allowlist",
"items": ["Reminders", "Shopping"]
},
"contacts": {
"enabled": true,
"mode": "all",
"items": []
},
"mail": {
"enabled": true
},
"default_calendar": "Personal",
"default_reminder_list": "Reminders"
}
Configuration Options
| Option | Values | Description | |--------|--------|-------------| | enabled | true, false | Enable or disable an entire domain | | mode | allowlist, blocklist, all | How to filter items (calendars/reminders/contacts) | | items | List of names | Calendar/list names to allow or block (emoji prefixes are matched fuzzy) | | default_calendar | Calendar name | Where new events are created when no calendar is specified | | default_reminder_list | List name | Where new reminders are created when no list is specified |
Filter Modes
- allowlist: Only listed items are accessible
- blocklist: All EXCEPT listed items are accessible
- all: No filtering (default if no config file exists)
Filtering applies per domain: calendars filter by calendar name, reminders by list name, contacts by account container name (e.g. "iCloud", "Work Exchange", "Personal Gmail"). Use contacts-cli containers to see available account names.
Profiles
Profiles let you give different agents different access to your PIM data. Each profile overrides specific domain sections from the base config — fields not in the profile are inherited from the base.
Profile selection (in priority order):
--profile workCLI flag (on the subcommand)APPLE_PIM_PROFILE=workenvironment variable- Tool parameter
profile: "work"(OpenClaw only) - No profile — base config only
Example profile (~/.config/apple-pim/profiles/work.json):
{
"calendars": {
"enabled": true,
"mode": "allowlist",
"items": ["Work"]
},
"contacts": {
"enabled": true,
"mode": "allowlist",
"items": ["Work Exchange"]
},
"mail": {
"enabled": false
},
"default_calendar": "Work"
}
Domain Enable/Disable
Set enabled: false on any domain to disable it. When disabled, CLI commands for that domain return an access denied error.
Sender Authentication (mail auth-check)
The auth_check action verifies email sender identity by parsing DKIM and SPF results from Authentication-Results headers and cross-referencing against a trusted senders list.
Config file: ~/.config/apple-pim/trusted-senders.json
{
"version": 1,
"trustedSenders": [
{
"name": "Alice",
"emails": ["alice@example.com"],
"expectedDkimDomains": ["example.com", "messagingengine.com"],
"requireSpf": true
},
{
"name": "Bob (relaxed SPF)",
"emails": ["bob@company.com"],
"expectedDkimDomains": ["company.com"],
"requireSpf": false
}
]
}
Fields per sender:
| Field | Type | Default | Description | |-------|------|---------|-------------| | name | string | required | Display name for the verdict | | emails | string[] | required | Email addresses to match (case-insensitive) | | expectedDkimDomains | string[] | [] | DKIM signing domains to accept (matches exact or subdomain) | | requireSpf | boolean | true | Require SPF pass for "verified" verdict |
expectedDkimDomains is what makes verified reachable: a DKIM signature from a domain you listed for that sender is the only evidence that binds a mailbox to a signer. An enrolled sender with no signing domains listed can never be verified, and auth-check says so in its warnings.
There is deliberately no requireDkim. SPF authenticates an envelope domain, so on any shared domain (gmail.com, a company domain, a hosting provider) every user of it passes aligned SPF and would authenticate as every other. Waiving DKIM would leave nothing per-sender behind a verified verdict. requireSpf: false is still supported, because relaying breaks SPF while DKIM survives it, and DKIM already carried the mailbox claim. Existing config files carrying requireDkim still load; the key is ignored.
Verdicts:
| Verdict | Meaning | |---------|---------| | verified | DKIM and SPF checks pass per sender config | | suspicious | Sender is trusted but authentication failed or domain mismatch | | untrusted | Sender email not found in trusted-senders.json | | unknown | Cannot determine (missing headers, parse failure, no config file) |
Usage:
# CLI
mail-cli auth-check --id ""
mail-cli auth-check --id "" --trusted-senders ~/custom/senders.json
# MCP tool
mcp__plugin_apple-pim_apple-pim__mail({ action: "auth_check", id: "" })
# OpenClaw tool
apple_pim_mail({ action: "auth_check", id: "" })
The --trusted-senders flag overrides the default config path. If the file doesn't exist, the verdict is unknown with a warning.
Date Output Format
Set APPLE_PIM_DATE_FORMAT to control how CalendarCLI formats dates in JSON output. This helps LLM agents avoid wasting tokens computing day-of-week from raw ISO dates.
| Preset | Example | |--------|---------| | utc (default) | 2026-03-20T14:00:00Z | | local | 2026-03-20T07:00:00-07:00 | | day-utc | Friday, 2026-03-20T14:00:00Z | | day-local | Friday, 2026-03-20T07:00:00-07:00 |
# Set in your shell profile or agent environment
export APPLE_PIM_DATE_FORMAT=day-local
No env var = utc (current behavior, fully backwards compatible). CalendarCLI only; ReminderCLI uses a different date codepath.
Notes
- Config is read fresh on each CLI invocation — changes take effect immediately
- No config file = all domains enabled, all items accessible (backwards compatible)
- Write operations to blocked calendars/lists fail with a descriptive error message
- Profile names are validated — path traversal attempts are rejected
Multi-Agent Setup
When running multiple agents, each can have its own profile or config directory for isolated PIM access. See [docs/multi-agent-setup.md](docs/multi-agent-setup.md) for the full guide.
Quick start: Create profiles in ~/.config/apple-pim/profiles/ and assign them per agent:
# Environment variable
APPLE_PIM_PROFILE=travel
# OpenClaw tool parameter (per-call isolation)
apple_pim_calendar({ action: "list", profile: "travel" })
apple_pim_calendar({ action: "list", configDir: "~/agents/travel/apple-pim" })
Usage
Claude Code Commands
/apple-pim:calendars list # List all calendars
/apple-pim:calendars events # Events for next 7 days
/apple-pim:calendars search "team meeting"
/apple-pim:reminders lists # List all reminder lists
/apple-pim:reminders items --filter overdue
/apple-pim:contacts search "John"
/apple-pim:mail messages --filter unread
Natural language works via the pim-assistant agent:
- "What's on my calendar tomorrow?"
- "Remind me to call the dentist"
- "What's John's email address?"
OpenClaw Tools
| Tool | Example | |------|---------| | apple_pim_calendar | apple_pim_calendar({ action: "events", nextDays: 7 }) | | apple_pim_reminder | apple_pim_reminder({ action: "items", filter: "today" }) | | apple_pim_contact | apple_pim_contact({ action: "search", query: "John" }) | | apple_pim_mail | apple_pim_mail({ action: "messages", filter: "unread" }) | | apple_pim_system | apple_pim_system({ action: "status" }) |
Direct CLI
calendar-cli list
calendar-cli events --from today --to tomorrow
calendar-cli create --title "Lunch" --start "tomorrow 12pm" --duration 60
reminder-cli lists
reminder-cli items --list "Personal" --filter overdue
contacts-cli containers # List contact account containers
contacts-cli search "John"
contacts-cli search "John" --profile work # Scoped to work contacts only
mail-cli messages --mailbox INBOX --limit 10
mail-cli send --to "user@example.com" --subject "Hello" --body "Message"
mail-cli send --to "user@example.com" --subject "Report" --body "See attached" --attachment ~/report.pdf
mail-cli reply --id "" --body "Thanks!"
mail-cli save-attachment --id "" --dest-dir ~/Downloads
mail-cli auth-check --id ""
# Native SMTP send (no Mail.app — see "Direct SMTP path" below)
mail-cli secrets set smtp.icloud.password
mail-cli smtp-send --to "user@example.com" --subject "Hello" \
--from "me@icloud.com" --html-file ./body.html
calendar-cli create --title "Meeting" --start "tomorrow 2pm" --attendees '[{"email":"a@example.com"}]'
Direct SQLite read path (--engine sqlite)
Read commands (accounts, mailboxes, messages, get, search) default to --engine auto: they read Apple Mail's local Envelope Index SQLite database (~/Library/Mail/V*/MailData/Envelope Index) directly, falling back to JXA if the database isn't readable. Message bodies come straight from the on-disk .emlx files. This is ~10–200× faster than the JXA path (subject
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: omarshahine
- Source: omarshahine/apple-pim
- License: MIT
- Homepage: https://www.npmjs.com/package/apple-pim-cli
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.