MCP server for Claude to read and modify Radicale CalDAV/CardDAV (calendars, tasks, contacts)
Find a file
Victor Gabriel Savu d87976213e Update documentation
README: expand tool reference with field details, add typical workflow section,
document status/priority values and array types for contacts.

ARCHITECTURE.md: new file covering crate layout, request lifecycle, key design
decisions in each module (client-per-call pattern, ical text escaping,
raw-string update strategy, UID substring matching), auth model, and a guide
for adding new tools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:58:37 +02:00
src Add extended task integration tests and fix iCal text escaping 2026-05-27 22:52:59 +02:00
tests Add extended task integration tests and fix iCal text escaping 2026-05-27 22:52:59 +02:00
.gitignore Initial MVP: CalDAV MCP server with calendar, event and task tools 2026-05-27 19:37:24 +02:00
ARCHITECTURE.md Update documentation 2026-05-28 06:58:37 +02:00
Cargo.lock Add bearer token auth, README, and integration tests 2026-05-27 20:45:11 +02:00
Cargo.toml Add bearer token auth, README, and integration tests 2026-05-27 20:45:11 +02:00
config.example.toml Add bearer token auth, README, and integration tests 2026-05-27 20:45:11 +02:00
README.md Update documentation 2026-05-28 06:58:37 +02:00
RESEARCH.md Initial MVP: CalDAV MCP server with calendar, event and task tools 2026-05-27 19:37:24 +02:00

mcp_webcal

A self-hosted MCP server that gives Claude access to calendars, tasks, and contacts on a CalDAV/CardDAV server (tested with Radicale).

Works with both Claude Code (streamable HTTP) and the claude.ai web/mobile app (remote MCP connector).

Features

  • CalDAV: list, create, update, and delete calendar events (VEVENT) and tasks (VTODO)
  • CardDAV: list, create, update, and delete contacts (vCard 4.0)
  • Correct timezone handling — events and tasks are stored and displayed in the configured IANA timezone
  • Bearer token authentication with --no-auth escape hatch for local use

Build

cargo build --release
# binary: target/release/mcp_webcal

Configuration

Copy config.example.toml to config.toml:

[server]
host = "127.0.0.1"
port = 8000
# Bearer token required in Authorization header. Omit to allow unauthenticated access.
# auth_token = "change-me-to-a-random-secret"

[caldav]
url = "https://radicale.example.com/"
username = "alice"
password = "secret"
# IANA timezone name — all event/task times are stored and returned in this zone.
timezone = "Europe/Berlin"

The [caldav] section is used for both CalDAV and CardDAV; Radicale serves both from the same URL.

Running

mcp_webcal                        # reads config.toml in the current directory
mcp_webcal /path/to/config.toml  # explicit config path
mcp_webcal --no-auth              # disable bearer token check (for local use)

The server listens on http://<host>:<port>/mcp.

Authentication

When auth_token is set, every HTTP request must carry:

Authorization: Bearer <your-token>

Use --no-auth to skip the check entirely — useful for local testing or when a reverse proxy (e.g. Caddy, nginx, Cloudflare Tunnel) handles authentication upstream.

Connecting Claude

Claude Code

Add to .claude/settings.json (project) or ~/.claude.json (global):

{
  "mcpServers": {
    "webcal": {
      "type": "http",
      "url": "http://127.0.0.1:8000/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

claude.ai web / mobile

Go to Settings → Integrations → Add custom integration and enter:

  • URL: https://cal.example.com/mcp
  • Authentication: Bearer token → <your-token>

A Cloudflare Tunnel or similar is required to expose the server publicly over HTTPS.

Typical workflow

The server follows a discovery-first pattern. Hrefs returned by the discovery tools are the stable identifiers used by all other tools.

Events and tasks

  1. Call list_calendars → get a list of { href, display_name } objects.
  2. Use an href with list_events or list_tasks.
  3. Use the uid from a listed item to update or delete it.

Contacts

  1. Call list_address_books → get a list of { href, display_name } objects.
  2. Use an href with list_contacts.
  3. Use the uid from a listed contact to update or delete it.

Tool reference

CalDAV — calendars

Tool Inputs Returns
list_calendars [{ href, display_name }]

CalDAV — events

Times use YYYY-MM-DDTHH:MM:SS (local time in the configured timezone) or YYYY-MM-DD for all-day events.

Tool Key inputs Returns / notes
list_events calendar_href [{ uid, summary, start, end, timezone, all_day, description, location }]
create_event calendar_href, summary, start, end, description?, location? uid of created event
update_event calendar_href, uid, summary Updates the event title
delete_event calendar_href, uid

CalDAV — tasks

Due dates use the same format as event times.

Valid status values: NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED.

priority is an integer from 1 (highest) to 9 (lowest).

Tool Key inputs Returns / notes
list_tasks calendar_href [{ uid, summary, due, status, priority, description, timezone }]
create_task calendar_href, summary, due?, description?, priority? uid of created task
update_task calendar_href, uid, status Updates task status
delete_task calendar_href, uid

CardDAV — address books

Tool Inputs Returns
list_address_books [{ href, display_name }]

CardDAV — contacts

Tool Key inputs Returns / notes
list_contacts address_book_href [{ uid, full_name, phones, emails, org, note }]
create_contact address_book_href, full_name, phones?, emails?, org?, note? uid of created contact
update_contact address_book_href, uid, full_name Updates the display name (FN)
delete_contact address_book_href, uid

phones and emails are arrays of strings (a contact can have multiple of each).

Integration Tests

The integration tests require rootless podman. Each test spins up an isolated Radicale container automatically.

cargo test --test integration

Tests cover events (CRUD, all-day, timezone round-trip), tasks (CRUD, minimal, datetime due, all status transitions, priority values, multiple tasks), and contacts (CRUD, multiple phones/emails).