calendula
An MCP server that lets Claude Code (or any MCP client) read your Outlook (classic) calendar from WSL.
- Read-only: list the events of a day (or several), or look up the event at a given time with its attendees and body
- Talks to Outlook on Windows through PowerShell and COM: no Microsoft Graph, no admin consent, no tokens
- Keeps what it fetched in memory only; nothing is written to disk (see Caching)
Requirements
- Windows with Outlook (classic) running. The new Outlook has no COM interface and does not work
- WSL: calendula runs in the Linux distribution and calls
wscript.exeandpowershell.exethrough WSL interop - Python 3.11 or later
Install
uv tool install calendula # or: pipx install calendula
calendula list # prints today's events (checks that Outlook answers)
Register it with Claude Code for all your projects:
claude mcp add --scope user calendula -- calendula
Other MCP clients: run calendula as a stdio server.
Then ask things like "What's on my calendar tomorrow afternoon?" or "Who is in the 14:00 meeting?".
To let Claude read the calendar without asking each time, allow the two read-only tools in ~/.claude/settings.json:
{
"permissions": {
"allow": ["mcp__calendula__list_events", "mcp__calendula__get_event"]
}
}
Tools
| Tool | Arguments | Returns |
|---|---|---|
list_events |
date (YYYY-MM-DD, default today), days (1-31, default 1), refresh |
Each day's events: time, subject, location |
get_event |
time (HH:MM), date, refresh |
The event running at that time (or the one starting nearest to it, with a note): location, organizer, attendees, status, body |
restart_outlook |
none | Force-quits Outlook and starts it again, to recover a broken COM connection. Unsaved drafts are lost, so the model is told to ask you first |
Every answer starts with the day and the time it was fetched:
2026-11-05 (Thu) fetched 2026-11-05 10:32
09:00-10:00 Weekly sync [Room A]
13:30-14:30 Customer interview
An event that spans several days (a conference, a meeting past midnight) appears on each day it covers. Bodies are kept whole except for blank lines and the Teams join block.
Caching
Fetching takes a few seconds (PowerShell starts, then Outlook answers over COM), so fetched days are kept in the server's memory:
- Today and later are reused for up to 1 hour, then fetched again
- Past days fetched after they ended are kept for the life of the server (one Claude Code session)
refresh: truealways fetches; ask for it when the calendar has just changed- A request fetches only the days that are missing or stale, in one call
- When fetching fails, the error is returned as is; older copies are never shown instead
Nothing is written to disk. Set CALENDULA_TTL_MINUTES to change the 1 hour.
Command line
calendula run the MCP server on stdio (same as `calendula serve`)
calendula list [DATE] [DAYS] print events, fetched from Outlook now
calendula show TIME [--date DATE] print the event at a time
calendula --version
Troubleshooting
CO_E_SERVER_EXEC_FAILURE(0x80080005) and similar COM errors: Outlook's COM connection often breaks after sleep or a network change. Restart Outlook (Claude will ask before callingrestart_outlook)Outlook gave no answer: check that Outlook (classic) is runningcalendula needs WSL ...: calendula only works inside WSL on a Windows PC with Outlook
How it works
For each fetch, calendula writes get_calendar.ps1 (as UTF-16 with a BOM, which Windows PowerShell 5.1 reads correctly) into a temporary directory under /tmp, and runs it hidden through wscript.exe and run_hidden.vbs, using the \\wsl.localhost\... path. The script reads the default calendar folder through Outlook.Application, including recurring events, and writes events.json, which calendula parses.
Development
uv sync
uv run pytest # needs neither Windows nor Outlook
CALENDULA_WINDOWS_TESTS=1 uv run pytest # on WSL: also the PowerShell side, still without Outlook
uv run ruff check && uv run ruff format --check
What is tested where, and why, is in docs/test-design.md; the behavior it checks is specified in docs/spec.md.
Releases: bump version in pyproject.toml, then push a tag vX.Y.Z. GitHub Actions tests, builds, publishes to TestPyPI, installs from there, publishes to PyPI (Trusted Publishing), and creates a GitHub release.
License
Metadata
Release files for calendula 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| calendula-0.1.0.tar.gz | 13.8 kB | With provenance | Details for calendula-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| calendula-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for calendula-0.1.0-py3-none-any.whl |
Total release size: 30.4 kB
Release files / calendula-0.1.0.tar.gz
| Download URL | calendula-0.1.0.tar.gz |
|---|---|
| Size | 13.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79b6030430de44eb2a2946c38e6044f2794d7a1f23e1933e335f2f8ef7d5f4ad
|
|
BLAKE2b-256 checksum How to use checksums |
cd8d2eb0bf5e1f452825763f293e7ec8b32151c10572b88e677245f52b049fe1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / calendula-0.1.0-py3-none-any.whl
| Download URL | calendula-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
475695dabf9d4d625c9fc74af88fa553712ca6a594f941b76fe8a3b005eeeb5b
|
|
BLAKE2b-256 checksum How to use checksums |
0789e60ff15ee7e3f84d60d2dc379decf98bbad0954d91756d155e3d7c57f00c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logSecurity for calendula 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for calendula-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.