caldav-mcp
by friisconsult
README.md
# caldav-mcp
An MCP server that gives Claude (Desktop/Cowork/Code) access to plain
CalDAV calendars — the on-prem/self-hosted calendars (SOGo, Nextcloud,
Radicale, Baïkal, Synology, …) not covered by the official Google/M365
connectors. Sibling project to [imap-mcp](../imap) and built on the same
safety model.
## Safety model
All guardrails live in `accounts.json` — not in prompts or tool
descriptions — so they hold even if calendar content tries prompt
injection:
1. **Read tools** always work: list calendars, search events, read an
event.
2. **Write tools** (create/update/cancel events) require
`"allow_writes": true` on the account; without it they refuse before
any network connection.
3. **No delete.** `cancel_event` only sets `STATUS:CANCELLED` (reversible
with `cancelled=false`); nothing is ever removed from the server.
4. **No invitations.** Events can never carry attendees, so the CalDAV
server can never send scheduling emails (iTIP/iMIP) on Claude's
behalf.
## Setup
```bash
bash setup.sh # macOS/Linux — creates .venv, installs deps, registers in Claude Desktop
.\setup.ps1 # Windows
```
Then edit `accounts.json` (gitignored) with your CalDAV URL and username,
store the password in the OS keychain, and verify:
```bash
.venv/bin/python server.py --set-password '<account>'
.venv/bin/python server.py --check
```
Restart Claude Desktop. For Claude Code, register with:
```bash
claude mcp add caldav -- "$PWD/.venv/bin/python" "$PWD/server.py"
```
### Account config
```json
{
"accounts": {
"crewcast": {
"url": "https://mail.crewcast.dk/SOGo/dav/",
"username": "you@crewcast.dk",
"allow_writes": false
}
}
}
```
| Key | Required | Meaning |
|---|---|---|
| `url` | yes | The CalDAV base URL (e.g. SOGo: `https://host/SOGo/dav/`) |
| `username` | yes | Login username |
| `allow_writes` | no | `true` enables create/update/cancel (default: read-only) |
| `password` | no | Discouraged — prefer the OS keychain via `--set-password` |
## Tools
| Tool | Tier | Description |
|---|---|---|
| `list_accounts` | read | Configured account names (no connection) |
| `list_calendars` | read | Calendars on an account |
| `search_events` | read | Events in a time window (default: next 30 days), recurring events expanded, optional text filter, one or all calendars |
| `get_event` | read | Full event details by UID |
| `create_event` | write | New event (timed or all-day); no attendees, ever |
| `update_event` | write | Change summary/time/location/description; moving start keeps duration |
| `cancel_event` | write | Set/clear `STATUS:CANCELLED` — the only "removal" offered |
## Tests
```bash
.venv/bin/python test_server.py
```
Offline — no calendar server or credentials needed; prints per-check
status and `ALL CHECKS PASSED`.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues