calendar-mcp
by jbeker
README.md
# calendar-mcp
An [MCP](https://modelcontextprotocol.io) server that exposes **read-only**
calendar data from iCal (`.ics`) feed URLs to an MCP-compatible agent.
You point it at one or more iCal URLs. It fetches them on a regular interval,
expands recurring events, caches everything in memory, and offers tools an agent
can call to answer questions like *"what's on my calendar this week?"*, *"am I free
Thursday afternoon?"*, or *"find events mentioning 'dentist'"*.
It never writes to any calendar — iCal feeds are pull-only sources.
## Install
Requires Python ≥ 3.11 and [`uv`](https://docs.astral.sh/uv/).
```bash
uv sync
```
## Configure
Copy the example config and edit it:
```bash
cp calendars.example.toml calendars.toml
```
```toml
refresh_interval_minutes = 15 # how often to re-fetch (min 5)
default_timezone = "America/New_York" # applied to floating times & agenda math
max_results = 200 # cap on events returned per tool call
[[calendars]]
name = "Work"
url = "https://example.com/work.ics"
[[calendars]]
name = "Family"
url = "https://calendar.google.com/.../basic.ics"
```
> **Feed URLs are secrets.** Google/Apple/Outlook "private address" URLs embed an
> access token. The server never logs or exposes the URL — only the friendly
> `name` appears in output. `calendars.toml` is git-ignored; keep it private.
## Run
```bash
uv run calendar-mcp calendars.toml
```
The server speaks the MCP **stdio** protocol, so it's normally launched by your MCP
client rather than by hand. Register it like this:
```json
{
"mcpServers": {
"calendar": {
"command": "uv",
"args": ["run", "calendar-mcp", "/absolute/path/to/calendars.toml"]
}
}
}
```
## Tools
| Tool | What it does |
| --- | --- |
| `list_calendars` | Each feed's refresh state, event count, last success (no URLs). |
| `list_events` | Events in a window (recurrences expanded), sorted by start. |
| `get_event` | Full detail (description, attendees) for one event UID. |
| `search_events` | Case-insensitive match on summary/description/location. |
| `free_busy` | Merged busy intervals and the free gaps between them. |
| `agenda` | Convenience: `today` \| `tomorrow` \| `week` \| `next`. |
| `refresh` | Force an immediate re-fetch of all feeds. |
Times are ISO-8601. If you omit a window, a sensible default is used. Results are
capped at `max_results` and flagged `truncated` when trimmed, so large calendars
don't overrun the agent's context.
## Design notes
- **Recurrence** (`RRULE`/`EXDATE`/`RECURRENCE-ID` overrides) is expanded by
[`recurring-ical-events`](https://pypi.org/project/recurring-ical-events/).
- **Timezones**: UTC, `TZID`, and floating times are all normalized to
timezone-aware ISO-8601; floating times get `default_timezone`.
- **Resilience**: a feed that fails to fetch or parse keeps its last-known-good
data and records the error in its status — it never wipes the cache or crashes
the refresh loop.
- **HTTP efficiency**: `ETag`/`Last-Modified` are stored and sent as conditional
requests, so an unchanged feed returns `304` and skips re-parsing.
## Develop
```bash
uv run pytest # unit tests + fixtures
```
## Not in scope (v1)
Writing/creating events, tasks (`VTODO`) and reminders (`VALARM`), an HTTP/daemon
transport, and on-disk cache persistence across restarts.
## License
Copyright (C) 2026 Jeremy Beker.
This program is free software: you can redistribute it and/or modify it under
the terms of the [GNU Affero General Public License](LICENSE) as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU Affero General Public License for more details.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues