Skip to main content
Glama

calendula

License: MIT Python PyPI version

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.exe and powershell.exe through WSL interop

  • Python 3.11 or later

Related MCP server: Microsoft Calendar MCP Server

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: true always 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 calling restart_outlook)

  • Outlook gave no answer: check that Outlook (classic) is running

  • calendula 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

MIT

Available Tools

3 tools
get_eventA
Read-onlyIdempotent

Details of the event at a time: location, organizer, attendees, status, and the body.

If no event is running at that time, returns the timed event that starts nearest to it (and says so).

NameRequiredDescriptionDefault
dateNoDay as YYYY-MM-DD (or MM-DD for this year). Default: today.
timeYesTime as HH:MM (24-hour).
refreshNoFetch from Outlook even if a recent copy is cached.

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description's real contribution is the fallback rule — if no event is running it returns the nearest upcoming timed event and discloses that fact — which is behavior an agent could not infer from annotations or schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the returned fields and then the fallback caveat. No filler; only minor awkwardness in phrasing such as 'the body'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description need not explain return values, yet it still summarizes the payload. Its main gap is the absence of any pointer to list_events for date-range browsing, but for a three-parameter read tool this is otherwise complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and each parameter carries its own format note (YYYY-MM-DD, HH:MM, cache refresh), so the schema does the heavy lifting. The description reinforces the semantics by stressing lookup 'at a time', matching the required time parameter, but adds no new format or constraint detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource ('Details of the event at a time') and enumerates what is returned: location, organizer, attendees, status, body. It is clearly a single-event lookup rather than a listing, so an agent can separate it from list_events without opening either schema, though it never names that sibling explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'at a time' — the tool is for pinpointing one event at a moment. There is no explicit statement of when to prefer it over list_events, nor any prerequisite guidance about needing a recent Outlook sync.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_eventsA
Read-onlyIdempotent

List events (time, subject, location) day by day. Use get_event for attendees and the body.

NameRequiredDescriptionDefault
dateNoDay as YYYY-MM-DD (or MM-DD for this year). Default: today.
daysNoNumber of days from `date`.
refreshNoFetch from Outlook even if a recent copy is cached.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds that the listing is per-day and scoped to time/subject/location (no attendees or body), but says nothing about caching, result volume, or ordering beyond what the schema's refresh parameter implies.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with zero filler; the core purpose is front-loaded and the sibling redirect is a compact second sentence.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, full parameter documentation, and annotations covering safety, the description covers what the agent needs to select and call the tool. It only lacks a note on calendar scope or result ordering, which is a minor gap given the structured fields.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and each parameter already documents its format and default (date YYYY-MM-DD, days 1-31, refresh ignoring cache). The phrase "day by day" loosely echoes the days parameter but adds no syntax or edge-case meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource ("List events") and names the fields returned (time, subject, location), so the agent knows exactly what it gets. It also implicitly separates itself from get_event, which covers the detail view.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly routes the agent: use get_event for attendees and the body. That names the alternative and the condition that selects it, leaving no inference needed for the common choice between the two calendar tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

restart_outlookA
Destructive

Force-quit Outlook (classic) and start it again, to recover a broken COM connection.

Unsaved drafts are lost. Call it only after the user has agreed, and only after a fetch failed with a COM error.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true, but the description adds the concrete consequence the annotation cannot express: unsaved drafts are lost. It also discloses the consent requirement and the fact that the restart is a force-quit, which implies abrupt termination rather than graceful shutdown.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and rationale, followed by the risk and the trigger condition. No filler or restatement of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a parameterless, side-effecting tool with an output schema present, the description covers action, motivation, data-loss risk, consent requirement, and the exact diagnostic precondition. Nothing an agent needs to decide whether to call it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; the schema is trivially complete. Baseline for a parameterless tool applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a concrete verb pair (force-quit and restart) on a specific resource (Outlook classic) and names the failure mode it addresses (broken COM connection). An agent can immediately tell this apart from sibling read tools like list_events and get_event.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit preconditions: only after the user has agreed, and only after a fetch failed with a COM error. This is a clear when-to-use gate with an implicit when-not (do not call speculatively or without consent).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedget_event
    • First observedlist_events
    • First observedrestart_outlook

TDQS

A4/5.0

Scored across 3 tools

Disambiguation4/5

list_events (day-by-day summaries) and get_event (full details for a specific time) have a clear summary-vs-detail split, and restart_outlook is unmistakably a maintenance operation. The only mild blur is get_event's fallback of returning the nearest starting event, which makes it behave slightly like a lookup rather than a strict fetch, but the description calls this out explicitly.

Naming Consistency5/5

All three tools follow a clean verb_noun snake_case pattern: list_events, get_event, restart_outlook. No mixed conventions or vague verbs, and the naming maps directly onto the described action.

Tool Count4/5

Three tools is a deliberate, well-scoped surface for a read-only Outlook calendar reader plus one recovery utility; nothing feels redundant. It is on the thin side of the typical 3-15 range, but each tool clearly earns its place.

Completeness3/5

The surface covers reading a day's events and drilling into one event, but there are notable gaps: no create/update/delete or RSVP/response operations, no cross-calendar search or range queries beyond day-by-day listing, and no way to list available calendars. Agents needing to modify a calendar will hit a dead end.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers