caldav-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@caldav-mcplist my meetings for tomorrow"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
caldav-mcp
A self-hosted, read-only MCP server for Lark / Feishu calendars. Ask an MCP client to list meetings or retrieve event details without passing large iCalendar payloads into the conversation.
The server exposes two tools over Streamable HTTP, checks calendar freshness on every request, and supports Docker deployment behind Cloudflare Tunnel. It has been tested against Lark CalDAV; compatibility with other CalDAV providers is not guaranteed. This is an independent project, not an official Lark or Feishu product.
Features
Compact event lists with organizer, attendee count, meeting link, and your RSVP when identifiable.
Full descriptions, attendees, and recurrence rules available through a separate detail tool.
Local expansion of recurring events, exceptions, rescheduled instances, all-day events, and time zones.
In-memory caches validated by ctag, with explicitly marked stale responses during upstream outages.
Mandatory access-key validation through a Bearer header or a URL key for clients without custom headers.
Related MCP server: ical-mcp
Requirements
Python 3.12 or later and uv.
A Lark / Feishu CalDAV username and dedicated CalDAV password.
Docker Compose and a Cloudflare Tunnel token for the container deployment below.
The service selects one calendar. Dates and output timestamps use Asia/Shanghai. It does not create, edit, or delete calendar events.
Quick start
gh repo clone jokerlin/caldav-mcp
cd caldav-mcp
cp .env.example .env
uv sync --frozen --python 3.12
openssl rand -hex 32Edit .env. Set CALDAV_USERNAME, CALDAV_PASSWORD, and MCP_BEARER_TOKEN (use the generated value). Keep these values in the local file.
Start a local server:
uv run uvicorn caldav_mcp.server:create_app --factory --host 127.0.0.1 --port 8080 --no-access-logThe endpoint is http://127.0.0.1:8080/mcp. The caldav-mcp console command binds to 0.0.0.0 for container networking; the command above explicitly binds to loopback.
Configuration
Variable | Required | Default / meaning |
| No |
|
| Yes | CalDAV account username |
| Yes | Dedicated CalDAV password |
| Yes | Shared access key generated with |
| No | Empty: discover the account's single calendar |
| No | Empty: use the calendar display name for RSVP identification |
| No |
|
| For Compose | Credential for the dedicated tunnel |
| For verification scripts | Set explicitly to the local or public |
The Cloudflare management fields in .env.example are optional deployment bookkeeping. The running MCP container does not receive the Cloudflare API token.
Deploy with Docker and Cloudflare Tunnel
Create a dedicated Cloudflare Tunnel and put its token in
CLOUDFLARE_TUNNEL_TOKEN.Route a hostname such as
calendar.example.comtohttp://mcp:8080. If you changeMCP_PORT, update the tunnel's origin port as well.Start the containers:
docker compose up -d --build
docker compose psCompose exposes the host port only on 127.0.0.1. The tunnel reaches mcp over the Docker network. Both containers restart unless explicitly stopped. The host and Docker must stay running; caches are cleared when the service restarts.
See deployment notes for authentication and verification details. All domains and identities in public examples are placeholders, not a shared calendar service.
Connect an MCP client
For clients with custom headers, configure:
URL: https://calendar.example.com/mcp
Authorization: Bearer <MCP_BEARER_TOKEN>For clients without a Bearer field, including the ChatGPT connection flow tested with this project, choose None / No Authentication in the client and use:
https://calendar.example.com/mcp?key=<MCP_BEARER_TOKEN>The server still validates the URL key. Missing or invalid keys return HTTP 401. An explicit Authorization header takes precedence. Treat a URL containing the key as a credential: do not share it or include it in screenshots, issues, or logs. The service does not implement OAuth, and Cloudflare credentials are not MCP access keys.
After connecting, enable the plugin in a conversation and ask it to list your meetings, then retrieve one meeting's details.
Tools
Tool | Arguments | Result |
|
| Sorted, expanded event instances without attendee lists or descriptions |
|
| Event fields plus full description, attendees, RRULE, creation and modification times |
Example tool arguments:
{"start":"2026-09-14","end":"2026-09-21","keyword":"review"}start and end are dates in YYYY-MM-DD format. The range is [start, end) and must span 1 to 90 days. Keywords match summary or location, ignoring case. Cancelled events are excluded by default.
An instance ID combines the UID and its actual start time, for example example-event@2026-09-15T11:00:00+08:00. Detail lookup works with a cold cache and reports a missing instance if that start time no longer exists.
For all-day events, the displayed end is midnight on the final included day, converted from iCalendar's exclusive end date. Floating times are interpreted in Asia/Shanghai. my_response is null when the account's attendee cannot be identified reliably.
Every request checks ctag before reusing cached results. Upstream outages can return available cached data with stale: true; authentication failures remain errors. Invalid event components are counted in skipped.
Development and verification
uv sync --frozen
uv run pytest -q
uv run ruff check --config pyproject.toml src tests scripts
uv run ruff format --config pyproject.toml --check src tests scriptsSynthetic tests run without credentials. The optional private-fixture test is skipped in a fresh clone. To capture your own fixtures, configure .env and run:
uv run python scripts/capture_fixtures.py 2026-09-08 2026-09-15Captures and identity metadata stay in the ignored tests/fixtures/ directory. Do not attach real captures to bug reports.
With the server running:
MCP_URL=http://127.0.0.1:8080/mcp scripts/smoke.sh
MCP_URL=https://calendar.example.com/mcp scripts/smoke.sh --auth query
MCP_URL=https://calendar.example.com/mcp uv run python scripts/benchmark.py --samples 20The benchmark checks an unchanged ctag and measures local and public requests separately. Warm-cache targets are median ≤300 ms / P95 ≤500 ms locally and median ≤1 s / P95 ≤2 s publicly, under normal network conditions. These exclude model reasoning time. See the measured performance report for the sample size and limitations.
Contributing
Bug reports and pull requests are welcome. Use GitHub Issues with reproducible steps and synthetic calendar data. Run the checks above before submitting a change. Never include account credentials, access-key URLs, colleague names, or private calendar content.
The design document describes the protocol constraints and module boundaries.
License
MIT, copyright 2026 jokerlin.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted Google Calendar MCP server for AI agents. No self-hosting or Google Cloud setup.
Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.
WhatsApp® reminders and rescheduling for Calendly. Public read-only MCP endpoint.
MCP server for Cronofy — read calendars, events and free/busy, and create, update or delete events.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceA read-only MCP server that exposes iCalendar feeds as queryable tools for LLM agents, enabling calendar event retrieval and filtering.MIT
- AlicenseAqualityBmaintenanceMCP server for Apple Calendar and CalDAV providers. Enables listing, creating, updating, deleting events, and checking free/busy status with per-calendar write protection.6MIT
- FlicenseNot gradedqualityDmaintenanceEnables MCP clients to manage Google Calendar events, including CRUD operations, recurring events, free/busy queries, and push notifications via webhooks.-
- AlicenseBqualityDmaintenance将飞书日历 API 封装为 MCP 服务器,支持日历、日程、订阅和忙碌状态的查询与管理。1411 npmMIT