iCloud Reminders and Calendar MCP Server
README.md
# iCloud Reminders and Calendar MCP Server
Lets Claude read and manage your iCloud Reminders and Calendar through a custom MCP connector — ask Claude "what's on my groceries list" or "add milk and eggs" or "What do I need to do today"
from any Claude client, including the iPhone app.
## Architecture
```
Claude (iPhone/desktop/web)
| HTTPS + API key
v
Anthropic's cloud infrastructure
| HTTPS
v
EC2 instance (Caddy: auto HTTPS via sslip.io + Let's Encrypt)
| localhost
v
Python MCP server (this repo)
| CalDAV
v
iCloud (your Reminders)
```
Custom MCP connectors are reached by Anthropic's cloud, not by your device
directly — so the server has to be genuinely public, not just reachable on
your home network. This deployment gets that for **$0/month** (within AWS
free tier) using:
- **EC2 free tier** (750 hrs/month for 12 months) instead of a paid VPS
- **sslip.io** for a free "domain" (no purchase, no signup) instead of a
real registered domain
- **Caddy** for free automatic HTTPS via Let's Encrypt
- **CalDAV** to talk to iCloud directly — no Mac, no AppleScript required
## Why CalDAV instead of AppleScript
Apple Reminders has no public REST API. AppleScript works but only runs
locally on a Mac. CalDAV is an open standard Apple also supports for
Calendar/Reminders sync, reachable from any OS or cloud server with just an
Apple ID and an app-specific password — which is what makes a Mac-free,
cloud-hosted version of this possible at all.
---
## Setup
### 1. Generate an app-specific Apple ID password
1. Go to https://appleid.apple.com and sign in
2. **Sign-In and Security** → **App-Specific Passwords** → generate one
3. Save it — you'll need it in step 4. This is not your normal Apple ID
password, and can be revoked independently at any time.
### 2. Launch an EC2 instance
1. AWS Console → EC2 → **Launch instance**
2. Choose **Ubuntu Server** (latest LTS), instance type **t3.micro** or
**t2.micro** (free tier eligible)
3. Create or select a key pair (for SSH access) — download the `.pem` file
4. Security group: allow inbound **SSH (22)** from your IP, and
**HTTP (80)** + **HTTPS (443)** from anywhere (`0.0.0.0/0`) — port 80 is
needed briefly for Let's Encrypt's verification, Caddy handles the rest
5. Launch it
### 3. Attach an Elastic IP (so the address doesn't change on reboot)
1. EC2 → **Elastic IPs** → **Allocate Elastic IP address**
2. **Actions** → **Associate** → select your instance
3. Note this IP — you'll need it for the sslip.io hostname
### 4. SSH in and set up the server
```bash
ssh -i your-key.pem ubuntu@YOUR-ELASTIC-IP
sudo apt update
sudo apt install -y python3-venv python3-pip git
git clone https://github.com/YOUR_USERNAME/reminders-mcp.git
cd reminders-mcp
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
nano .env # fill in APPLE_ID, APPLE_APP_PASSWORD, and a generated MCP_API_KEY
```
Generate a strong API key with:
```bash
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```
### 5. Install and configure Caddy
```bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
```
Edit the Caddyfile to use your actual Elastic IP (dots replaced with
dashes), e.g. if your IP is `54.123.45.67`:
```bash
sudo nano /etc/caddy/Caddyfile
```
```
54-123-45-67.sslip.io {
handle_path /REPLACE_WITH_YOUR_MCP_API_KEY/* {
reverse_proxy localhost:8787 {
header_up x-api-key "YOUR_MCP_API_KEY"
}
}
respond 404
}
```
```bash
sudo systemctl restart caddy
```
Caddy will automatically get a free HTTPS certificate for that hostname the
first time it starts — no manual steps needed.
### 6. Set up the systemd service (so it survives reboots)
```bash
sudo cp reminders-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable reminders-mcp
sudo systemctl start reminders-mcp
sudo systemctl status reminders-mcp # confirm it's running
```
### 7. Test it's reachable
```bash
curl -i https://54-123-45-67.sslip.io/{YOUR_MCP_API_KEY}/mcp
```
{YOUR_MCP_API_KEY} will be extracted from the path and Caddy will inject it as a request header.
A response other than a connection error means it's working (a `406` here
is expected and fine — it just means the request wasn't a full valid MCP
handshake, which curl alone won't do).
### 8. Add the connector on claude.ai
Custom connectors can only be **added** via a browser or Claude Desktop —
not from the iPhone app directly. Once added, it's usable everywhere,
including mobile.
1. Go to claude.ai → **Customize → Connectors** → **"+"** → **Add custom
connector**
2. Name: `iCloud Reminders`
3. URL: `https://54-123-45-67.sslip.io/{YOUR_MCP_API_KEY}/mcp`
5. Click **Add**, then enable it in a conversation
### 9. Use it from your iPhone
Open the Claude app, start a conversation, enable the connector from the
"+" menu if it isn't already, and try:
- "What's on my Groceries list?"
- "Add milk and eggs to Groceries"
- "Mark milk as done on Groceries"
- "What do I need to do today?"
---
## Tools this server exposes
| Tool | What it does |
|---|---|
| `list_reminder_lists()` | Names of all your iCloud Reminders lists |
| `get_pending_reminders(list_name)` | Incomplete items in a given list |
| `add_reminder(list_name, title)` | Adds a new reminder to an **existing** list |
| `complete_reminder(list_name, title)` | Marks an item done |
Note: `add_reminder` requires the list to already exist — create it once in
the Reminders app on any Apple device and it syncs to iCloud automatically.
Reliably creating brand-new top-level lists via CalDAV varies across iCloud
accounts, so it's left as a one-time manual step.
## Security
- The server requires a valid `YOUR_MCP_API_KEY` header in every request path — without it, requests are rejected with 401. Treat this key like a password.
- Your Apple app-specific password only grants limited access and can be
revoked anytime from appleid.apple.com without touching your main
password.
- The EC2 security group only allows inbound 22/80/443 — the MCP server's
actual port (8787) is only reachable via `localhost`, through Caddy.
- Never commit your real `.env` file — `.gitignore` is already set up to
exclude it. Only `.env.example` (placeholder values) should be in git.
## Cost
Within the AWS Free Tier (first 12 months): **$0/month**.
- EC2 t3.micro/t2.micro: free (750 hrs/month covers 24/7 use)
- Elastic IP: free while attached to a running instance
- sslip.io hostname: free, no signup
- Let's Encrypt certificate via Caddy: free, auto-renewing
After free tier expires, EC2 alone runs roughly $7–9/month if left running
continuously — stop the instance when not in use to avoid ongoing charges.
## Troubleshooting
- **Caddy fails to get a certificate** → confirm port 80 is open in your
security group (Let's Encrypt needs it briefly for verification), and
that the sslip.io hostname matches your Elastic IP exactly.
- **401 Unauthorized from your own curl test** → double-check the
`YOUR_MCP_API_KEY` in URL `https://54-123-45-67.sslip.io/{YOUR_MCP_API_KEY}/mcp` matches `MCP_API_KEY` in `.env` exactly, no
trailing whitespace.
- **Service won't start** → `sudo journalctl -u reminders-mcp -e` to see
the actual error; usually a missing/incorrect `.env` value.
- **Claude says the connector won't connect** → confirm you can reach the
HTTPS URL from a browser or curl first, before troubleshooting on the
Claude side.
## Next steps
- Add a Calendar tool (`get_todays_events`) using the same CalDAV
connection, so Claude can start combining "what's on my calendar" with
"what's on my todo list" into a single daily briefing.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues