caldav-mcp-wrapper
Provides read and write tools for managing iCloud calendars via CalDAV, including listing calendars, events, and creating, updating, deleting events.
Click on "Install 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-mcp-wrapperlist my events 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-wrapper
A minimal, self-hosted MCP server that exposes read and write tools for a CalDAV calendar — designed for Apple iCloud (via an app-specific password), and compatible with any CalDAV server.
It is a CalDAV counterpart to smtp-mcp-wrapper and follows the same deployment and security model.
Tools
Read:
Tool | Purpose |
| List the calendars in the account (respecting the allowlist). |
| List events in a calendar within a start/end window. |
| Fetch a single event by UID. |
Write (disabled when READ_ONLY=true):
Tool | Purpose |
| Create an event (timed or all-day). |
| Update fields of an existing event by UID. |
| Delete an event by UID. |
Subscribed ICS feeds (see below):
Tool | Purpose |
| List the subscribed feeds and their last fetch result. |
| Subscribe to an ICS feed URL (validated on add). |
| Stop serving a feed (by id, URL, or name). |
Times are ISO 8601. Use YYYY-MM-DD with all_day: true for whole-day events.
Related MCP server: CalDAV MCP Server
Subscribed ICS calendars
Apple "subscribed calendars" (team/league schedules, holiday feeds) are stored
device-side and are not reachable over CalDAV — they never appear in
list_calendars and nothing you configure on the CalDAV side will surface them.
The underlying data is just an iCalendar document at an HTTP(S) URL, so this server can pull those URLs directly as a second, read-only source:
add_subscription(name="Team Schedule", url="webcal://example.com/team.ics")webcal:// links (what Apple hands out) are rewritten to https://. The feed is
fetched once at add-time so a bad URL fails immediately rather than silently
returning nothing later. After that the feed's events are readable through the
normal list_events / get_event tools, and list_calendars reports it with
"kind": "subscription" and "read_only": true.
Details worth knowing:
Recurrence is expanded. Team schedules lean on
RRULE; occurrences are expanded within the queried window so a weekly practice appears on every date.Feeds are cached for
ICS_CACHE_TTL(default 15 min) and then revalidated withETag/If-Modified-Sincerather than re-downloaded.Always read-only.
create_event/update_event/delete_eventreject a subscription target with a clear error.Identity is the feed URL/id, not the display name — pass the
idor URL fromlist_calendarswhen names collide.Persistence: the pull list is stored at
SUBSCRIPTIONS_FILE(default/data/subscriptions.json) on thecaldav_mcp_datavolume, so feeds added at runtime survive restarts and image updates. Declare feeds up front withSUBSCRIBED_ICSif you prefer config over the tool.SSRF guard:
add_subscriptionfetches an arbitrary URL, so private, loopback and link-local targets are refused (every redirect hop is re-checked). SetICS_ALLOW_PRIVATE_IPS=trueonly to subscribe to a LAN-hosted feed.Feeds do not depend on CalDAV. If iCloud is unreachable, subscribed feeds stay readable.
Managing feeds without the MCP tools
The MCP tools are the normal path, but they only work when the server and the proxy in front of it are healthy. The same pull list can be managed from the command line as a backup — it uses the same file and lock, so it works while the server is running and changes take effect immediately (no restart):
docker compose exec -T caldav-mcp python subscriptions.py list
docker compose exec -T caldav-mcp python subscriptions.py add "Team Schedule" "webcal://example.com/team.ics"
docker compose exec -T caldav-mcp python subscriptions.py inspect "Team Schedule"
docker compose exec -T caldav-mcp python subscriptions.py remove "Team Schedule"add validates by fetching the feed, the same as the tool, and reports its size
and event count; pass --no-validate to add a feed that is temporarily
unreachable. inspect re-fetches and shows what a feed actually contains — size,
VEVENT count, and the next occurrences — which is how you tell a broken URL from
a valid feed whose schedule simply isn't published yet. remove exits non-zero if
nothing matched. All three accept an id, a URL, or a display name.
Adding and removing feeds is logged at INFO, so docker compose logs caldav-mcp
shows why a feed appeared or vanished no matter which path changed it.
To declare feeds up front instead, set SUBSCRIBED_ICS to a JSON {"name": "url"}
map — it is merged into the pull list at startup (additive: it never removes
feeds added another way, and it never fetches, so a dead feed cannot block boot).
Security architecture — read this first
This server implements no authentication of its own, by design. It MUST be gated by an authorization service. Do not expose it directly to the internet.
The intended topology keeps the server on an internal network only, with every external request flowing through an identity-aware proxy:
edge tunnel → reverse proxy (TLS) → Pomerium (SSO + allowlist to a single identity) → caldav-mcp-wrapperAny equivalent identity-aware proxy works (Cloudflare Access, oauth2-proxy, etc.).
docker-compose.yml deliberately publishes no host ports: the container is
reachable only over the internal proxy network by container name.
Defense-in-depth beyond the proxy:
Calendar allowlist —
ALLOWED_CALENDARShard-limits which calendars any tool can touch, so even a misused tool cannot reach other calendars.Read-only mode —
READ_ONLY=truedisables all write tools.Optional Pomerium identity verification — set
REQUIRE_POMERIUM_IDENTITY=trueto cryptographically verify Pomerium's identity assertion (signature + expiry + audience) on every/mcprequest against Pomerium's JWKS. This blocks anything on the shared Docker network from bypassing Pomerium and reaching the app directly. When enabled, setpass_identity_headers: trueon the Pomerium route and providePOMERIUM_JWKS_URLandPOMERIUM_AUDIENCE.
iCloud setup
Sign in to account.apple.com → Sign-In and Security → App-Specific Passwords → generate one for this server.
Set
CALDAV_USERNAMEto your Apple ID email andCALDAV_PASSWORDto that app-specific password.Leave
CALDAV_URLat the defaulthttps://caldav.icloud.com/; the client discovers your calendars from there.
App-specific passwords require two-factor authentication on your Apple ID.
Configuration
All configuration is via environment variables — see .env.example
for the full annotated list. Secrets are injected at runtime and never baked into
the image. Key variables:
Variable | Default | Notes |
|
| CalDAV entry point. |
| — (required) | Apple ID / CalDAV username. |
| — (required) | App-specific password. |
| — | Calendar used when |
| — | Comma-separated allowlist; empty = all. |
|
| Disable write tools (incl. subscription management) when |
|
| Persisted ICS pull list; must be on a volume. |
| — | Optional JSON |
| — | Comma-separated allowlist of feed names/ids; empty = all. |
|
| Seconds a fetched feed is reused before revalidating. |
|
| Allow feeds on private/LAN addresses (SSRF guard off). |
|
| Log |
|
| Connect and list calendars at startup to verify config. |
| — | Allowed |
| — | Allowed |
Run
cp .env.example .env # fill in CALDAV_USERNAME / CALDAV_PASSWORD etc.
docker compose up -dThe image is built and published to GHCR by CI
(ghcr.io/jb09/caldav-mcp-wrapper:latest).
Maintenance
Dependabot opens weekly PRs for the Python deps, the Docker base image, and the GitHub Actions used in CI.
CI (
buildworkflow) builds the image on every push/PR, pushes to GHCR onmain, and does a weekly no-cache rebuild so OS/Python security patches land even without code changes.Smoke test (
scripts/smoke_test.sh, run by CI before the push step) starts the built image and drives a real MCPinitialize+tools/listagainst it using a non-localhostHostheader, then checks thatMCP_ALLOWED_HOSTSaccepts the route host and rejects others. A build alone cannot catch a server that binds the wrong interface or answers421to proxied requests — both keep/healthzgreen. Run it locally withdocker build -t caldav-mcp:smoke . && ./scripts/smoke_test.sh caldav-mcp:smoke.Auto-merge (
dependabot-automergeworkflow) enables auto-merge for patch/minor Dependabot bumps once required checks pass; major bumps are left for manual review.Watchtower (opt-in label in compose) pulls refreshed images automatically.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceTransforms macOS calendar management into a conversational experience using natural language, allowing users to create, manage, and update calendar events seamlessly through an MCP-compatible client.Last updated322MIT
- Flicense-qualityFmaintenanceEnables interaction with any CalDAV-compatible calendar server (Yandex, Google, Nextcloud, iCloud, etc.) to list calendars, create/manage events with reminders and attendees, search events, and handle recurring events through natural language.Last updated13
- Flicense-qualityDmaintenanceEnables users to view and create events in their iCloud Calendar using natural language through supported LLMs. It integrates with Apple's infrastructure via app-specific passwords to provide secure calendar management.Last updated1
- Alicense-qualityDmaintenanceEnables managing Google Calendar events including creating, listing, updating, and deleting events through natural language.Last updated28MIT
Related MCP Connectors
Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.
Connects ChatGPT to your Apple Calendar via a local Mac agent + Vercel relay
Schedule and manage Google Calendar events directly from your workspace. Check availability, view…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/JB09/caldav-mcp-wrapper'
If you have feedback or need assistance with the MCP directory API, please join our Discord server