Skip to main content
Glama
caldav-todo-mcp
by dansimau

caldav-todo-mcp

An MCP server that lets an AI assistant read and write TODO items over CalDAV. Your tasks live on your own server (Nextcloud Tasks, Radicale, iCloud Reminders, Fastmail, Baïkal, …) and every other CalDAV client you use sees the same data.

Lists are CalDAV collections; items are RFC 5545 VTODO components. The server exposes tools to list, create, update, complete, move and delete items, plus a sync_todos tool that pushes a whole batch to the remote server in one call.

It speaks both MCP transports: stdio for local clients that spawn it, and streamable HTTP for running it as a service, e.g. in Docker.

Requirements

  • Python 3.11+

  • A CalDAV account (URL, username, password or app-specific password)

Related MCP server: Task / To-Do Manager — MCP Server

Install

# with uv (recommended)
uv tool install caldav-todo-mcp        # once published; or from a checkout:
uv tool install /path/to/caldav-todo-mcp

# or with pip
pip install /path/to/caldav-todo-mcp

For development:

uv venv && uv pip install -e ".[dev]"
uv run pytest

Configuration

The server reads its settings from environment variables (see .env.example):

Variable

Required

Meaning

CALDAV_URL

yes

Base URL of the CalDAV server, e.g. https://cloud.example.com/remote.php/dav/

CALDAV_USERNAME

usually

Login name

CALDAV_PASSWORD

usually

Password or app-specific password

CALDAV_DEFAULT_LIST

no

List id or name used when a tool call omits list_id

CALDAV_SSL_VERIFY

no

false to accept self-signed certificates (default true)

CALDAV_ALLOW_INSECURE_HTTP

no

true to allow plain http:// URLs (local servers only)

CALDAV_TIMEOUT

no

HTTP timeout in seconds (default 30)

Typical URLs:

Provider

CALDAV_URL

Nextcloud

https://HOST/remote.php/dav/

Radicale

https://HOST/USERNAME/

iCloud

https://caldav.icloud.com/ (use an app-specific password)

Fastmail

https://caldav.fastmail.com/dav/

Baïkal

https://HOST/dav.php/

Running in Docker (HTTP transport)

The image serves MCP over streamable HTTP on port 8000 at /mcp, and a liveness probe at /health. CalDAV credentials are passed as environment variables; nothing is baked into the image.

cp .env.example .env          # fill in CALDAV_URL / CALDAV_USERNAME / CALDAV_PASSWORD
docker compose up -d --build
curl http://127.0.0.1:8000/health

Or without compose:

docker build -t caldav-todo-mcp .
docker run -d --name caldav-todo-mcp -p 127.0.0.1:8000:8000 \
  -e CALDAV_URL=https://cloud.example.com/remote.php/dav/ \
  -e CALDAV_USERNAME=alice \
  -e CALDAV_PASSWORD=app-password \
  -e CALDAV_DEFAULT_LIST=Tasks \
  -e MCP_AUTH_TOKEN=change-me \
  caldav-todo-mcp

Then point your client at http://127.0.0.1:8000/mcp:

claude mcp add --transport http caldav-todo http://127.0.0.1:8000/mcp \
  --header "Authorization: Bearer change-me"

Server-side settings (all optional):

Variable

Default

Meaning

MCP_TRANSPORT

stdio (image: http)

stdio or http

MCP_HOST

127.0.0.1 (image: 0.0.0.0)

Bind address for HTTP

MCP_PORT

8000

HTTP port

MCP_PATH

/mcp

HTTP endpoint path

MCP_AUTH_TOKEN

unset

When set, /mcp requires Authorization: Bearer <token>; /health stays open

MCP_ALLOWED_HOSTS

unset

Comma-separated Host allow-list (e.g. localhost:*,todo.lan); turns on DNS-rebinding protection

MCP_STATELESS

false

Stateless streamable HTTP mode (no server-side sessions)

MCP_JSON_RESPONSE

false

Plain JSON responses instead of SSE

MCP_LOG_LEVEL

INFO

Log level

The same options are available as CLI flags (caldav-todo-mcp --transport http --port 8000), which take precedence over the environment. The compose file binds the port to 127.0.0.1 only; if you expose it more widely, set MCP_AUTH_TOKEN, because whoever can reach the endpoint can read and write your tasks.

Using it with a local (stdio) MCP client

Claude Code

claude mcp add caldav-todo \
  -e CALDAV_URL=https://cloud.example.com/remote.php/dav/ \
  -e CALDAV_USERNAME=alice \
  -e CALDAV_PASSWORD=app-password \
  -e CALDAV_DEFAULT_LIST=Tasks \
  -- caldav-todo-mcp

Claude Desktop / other stdio clients

{
  "mcpServers": {
    "caldav-todo": {
      "command": "caldav-todo-mcp",
      "env": {
        "CALDAV_URL": "https://cloud.example.com/remote.php/dav/",
        "CALDAV_USERNAME": "alice",
        "CALDAV_PASSWORD": "app-password",
        "CALDAV_DEFAULT_LIST": "Tasks"
      }
    }
  }
}

You can also run it directly with python -m caldav_todo_mcp, or serve HTTP outside Docker with caldav-todo-mcp --transport http.

Tools

Tool

What it does

list_todo_lists

List the TODO-capable collections on the server

create_todo_list

Create a new list

delete_todo_list

Delete a list and everything in it

list_todos

List items (open first, by due date then priority); filters: include_completed, due_before, category

get_todo

Fetch one item by UID

create_todo

Create an item: summary, description, due, start, priority, categories, status

update_todo

Change any of those fields; clear_due / clear_start / clear_description remove a value

complete_todo / reopen_todo

Toggle completion (sets COMPLETED and PERCENT-COMPLETE)

delete_todo

Delete an item

move_todo

Move an item to another list

sync_todos

Upsert a batch of items by UID; delete_missing=true makes the server mirror the batch

export_todos

Export a list as an .ics document

import_todos

Import VTODOs from .ics text, replacing items with the same UID

Every tool takes an optional list_id, which may be the collection id, its display name (case-insensitive) or its URL. When omitted, CALDAV_DEFAULT_LIST is used; if that is unset and the server holds exactly one TODO list, that list is used.

Dates are ISO 8601 strings: 2026-11-10 for an all-day due date, 2026-11-10T17:00:00+01:00 for a specific time. Priority follows RFC 5545 (1 highest … 9 lowest).

Syncing a local list to the server

sync_todos is designed for "push my list to the server" workflows. Give each local item a stable uid and send the whole list:

{
  "list_id": "Tasks",
  "delete_missing": true,
  "items": [
    {"uid": "local-1", "summary": "Renew passport", "due": "2026-12-01", "priority": 2},
    {"uid": "local-2", "summary": "Call the bank", "status": "COMPLETED"}
  ]
}

The response reports which UIDs were created, updated, unchanged and deleted. Without delete_missing the call never removes anything. To pull the server state instead, use list_todos (structured) or export_todos (iCalendar).

Library use

The CalDAV layer is usable without MCP:

from caldav_todo_mcp import Settings, TodoStore
from caldav_todo_mcp.models import TodoInput

store = TodoStore(
    Settings(
        url="https://cloud.example.com/remote.php/dav/", username="alice", password="app-password"
    )
)
for lst in store.list_lists():
    print(lst.id, lst.name)
store.create_todo(TodoInput(summary="Water the plants", due="2026-11-10"), "Tasks")

Tests

The test suite starts a throwaway Radicale server and runs the full create/update/complete/sync/import flow against it: in-process, over a real stdio MCP connection, and over streamable HTTP (including the bearer-token check):

uv run pytest

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A persistent todo list server that enables AI assistants to manage tasks across different platforms using the Model Context Protocol. It provides tools for creating, listing, updating, and deleting todos with support for priorities, tags, and due dates.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Connects AI assistants to CalDAV servers so they can list calendars and browse, create, update, or delete events and to-do tasks within a chosen timeframe. Supports scheduling of recurring and all-day events, task filtering and pagination, and completion tracking through natural language.
    1,412 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to connect to CalDAV servers and manage calendars, events, and tasks through tools for listing, creating, updating, completing, and deleting items.
    1,412 npm
    MIT