Skip to main content
Glama
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`.