Skip to main content

calendar-mcp

An MCP server that gives an LLM client read and write access to your Google Calendar. It runs as a single process — stdio by default, streamable HTTP optionally — and exposes 23 tools with structured output: listing and searching calendars and events, creating, updating, moving, RSVPing to and deleting events, free/busy queries, busyness analysis, recurring-event projection, and finding a mutual slot and booking it. On top of that it has a scheduling brain that knows your working hours: finding and booking focus time, detecting double-bookings across several accounts at once, proposing better times for a meeting, and auditing where your week actually went. Authentication is Google OAuth 2.0 (Desktop app flow); tokens are cached locally, per account, and refreshed automatically.

Quick start

1. Create Google OAuth credentials. In the Google Cloud console, enable the Google Calendar API, then create an OAuth client ID of type Desktop app. Copy the client ID and secret. (Details in Google Cloud setup.)

2. Set them in your environment, or in a .env file in the directory you run from (see example.env):

GOOGLE_CLIENT_ID='...'
GOOGLE_CLIENT_SECRET='...'

3. Sign in once, then add the server to your client:

uvx calendar-mcp-server auth

This opens a browser and saves a token in the config directory (see Accounts); if you already have a .gcp-saved-tokens.json or set TOKEN_FILE_PATH, that file is used instead. Verify it with calendar-mcp check. Add more Google accounts with calendar-mcp auth --account work. After that the server runs non-interactively — it never opens a browser on its own unless you set CALENDAR_MCP_ALLOW_BROWSER_AUTH=1.

The PyPI distribution is calendar-mcp-server. It installs two identical console scripts, calendar-mcp and calendar-mcp-server, so uvx calendar-mcp-server and a local calendar-mcp are the same command.

Client configuration

Claude Code

claude mcp add calendar \
  --env GOOGLE_CLIENT_ID=... \
  --env GOOGLE_CLIENT_SECRET=... \
  -- uvx calendar-mcp-server

Claude Desktopclaude_desktop_config.json:

{
  "mcpServers": {
    "calendar": {
      "command": "uvx",
      "args": ["calendar-mcp-server"],
      "env": {
        "GOOGLE_CLIENT_ID": "...",
        "GOOGLE_CLIENT_SECRET": "...",
        "TOKEN_FILE_PATH": "/absolute/path/to/.gcp-saved-tokens.json"
      }
    }
  }
}

Cursor.cursor/mcp.json (project) or ~/.cursor/mcp.json (global): same mcpServers block as above.

Any other client that speaks the standard mcpServers JSON takes the same entry. TOKEN_FILE_PATH is optional now that tokens default to the OS config directory, which does not depend on the working directory the client picks — but if you do set it, make it an absolute path.

Remote / HTTP mode

calendar-mcp --transport http --host 127.0.0.1 --port 8000

The MCP endpoint is then http://127.0.0.1:8000/mcp (change the path with --path).

There is no authentication layer on the HTTP transport yet. Anyone who can reach the endpoint gets full access to the calendar the saved token belongs to. Bind it to loopback, or expose it only behind a trusted reverse proxy that authenticates, or on a private network such as a tailnet. Do not put it on a public interface.

Tools

Read-only tools never change anything. "Writes" tools create or modify events; delete_event is the only one that destroys data.

Calendars and events

Tool Description
list_calendars read List the calendars the user can see, with IDs and timezones.
find_events read Search a calendar for events, expanding recurring series into instances.
check_attendee_status read Report who accepted, declined or has not answered an invitation.
query_free_busy read Busy intervals for one or more calendars, without event details.
analyze_busyness read Per-day event count and total scheduled minutes over a range.
project_recurring_events read Compute future occurrences from recurrence rules.
create_calendar writes Create a new secondary calendar.
create_event writes Create an event with explicit start/end times and optional attendees.
quick_add_event writes Create an event from a plain-English phrase, parsed by Google.
update_event writes Change fields on an event; omitted fields are left untouched.
move_event writes Reschedule an event, and/or move it to another calendar.
add_attendee writes Invite one or more people to an existing event.
respond_to_event writes Set your own RSVP (accepted/declined/tentative/needsAction).
schedule_mutual writes Find the first slot where everyone is free, then book it.
delete_event destroys Permanently delete an event. Asks the client to confirm via elicitation when supported.

Scheduling brain — these read your saved preferences (working hours, lunch, buffer, minimum focus block).

Tool Description
find_focus_time read Uninterrupted blocks in a window that could be used for deep work, longest first.
detect_conflicts read Double-bookings and too-tight transitions, across every signed-in account at once.
time_audit read Where the time went: meeting hours, by size, domain, recurrence and person.
suggest_reschedule writes (opt-in) Ranked better times for an existing meeting. Suggests only, unless apply: true.
block_focus_time writes Book the best free blocks as focus time. dry_run: true to preview.

Local configuration — no Google call, no account argument.

Tool Description
list_accounts read The accounts you have signed in, and which is the default.
get_preferences read Working hours, lunch, buffer, minimum focus block, focus calendar.
set_preferences writes Update and save those preferences. Local file only.

Every calendar tool takes an optional trailing account argument (detect_conflicts takes accounts, a list, because checking several at once is the point). All times are ISO 8601 strings — a naive timestamp is interpreted in the target calendar's own timezone.

Scheduling brain

The five scheduling tools share one idea: your calendar is not the same as your availability. They start from your working hours, subtract lunch, subtract what is already booked, and subtract the buffer you want around meetings — then reason about what is left.

Prompts that exercise them:

  • "Find me six hours of focus time next week and block it out — show me the times first."
  • "Is anything double-booked between my work and personal calendars this week?"
  • "My Thursday is back-to-back. Suggest better times for the design review."
  • "Where did my time go last month? Who am I spending it with?"
  • "I need a 90-minute deep work block before Friday. Is there one?"

find_focus_time reports what exists; block_focus_time defends it by creating events (busy, reminders off, nobody notified), trimming the last block so it books exactly the hours you asked for rather than a whole afternoon. detect_conflicts separates genuine overlaps from tight transitions that merely break your buffer, and ignores events you declined, events marked free, and (by default) all-day entries. suggest_reschedule keeps the meeting's duration, ranks candidate slots by fewest attendee conflicts and prefers the event's current day, and moves nothing unless you pass apply: true.

Time audit

time_audit answers "how much of my week is meetings?" in one pass, grouped by day or week:

2026-08-01 .. 2026-09-01, Europe/Berlin, grouped by week
  Meetings:         41.5 h across 63 meetings
  Working hours:    152.0 h available (lunch removed)
  In working hours: 38.0 h -> 25% of the week
  Busiest week:     2026-W34, 14.0 h
  Heaviest day:     2026-08-20, 6.5 h
  By size:          1:1 18.0 h | small 15.5 h | large 8.0 h
  By recurrence:    recurring 26.0 h | one-off 15.5 h
  Top people:       a.schmidt@example.com 9.0 h | j.lee@example.com 7.5 h

Declined meetings, events marked free and all-day entries are left out by default; include_declined and include_all_day bring them back.

Safety

  • delete_event is the only destructive tool. It asks the client to confirm through MCP elicitation when the client supports it, and proceeds normally when it does not.
  • block_focus_time takes dry_run. Run it with dry_run: true to see the exact blocks it would book before anything is written.
  • suggest_reschedule does not move anything by default. apply is false and has to be set explicitly, once the user has agreed to a time.
  • Everything else that writes is additive — creating or editing an event — and update_event leaves fields you omit untouched.
  • list_accounts, get_preferences and set_preferences never touch Google. They read and write local files in the config directory.

Accounts

You can sign in more than one Google account and pick between them per call.

calendar-mcp auth                    # the default account
calendar-mcp auth --account work     # a second, named account
calendar-mcp accounts                # list them, with token paths and sign-in state

Every calendar tool takes an optional account argument naming one of these ("what's on my work calendar tomorrow?"). Omit it and the server uses the default: CALENDAR_MCP_DEFAULT_ACCOUNT if set, otherwise the account named default, otherwise the only account you have signed in. list_accounts returns the same list the CLI prints, so the model can discover the names itself.

Account names must match [A-Za-z0-9][A-Za-z0-9_-]{0,63}.

Where things live. Tokens and preferences are stored in the OS user config directory — %LOCALAPPDATA%\calendar-mcp on Windows, ~/.config/calendar-mcp on Linux, ~/Library/Application Support/calendar-mcp on macOS — with one token file per account under accounts/. Override the whole directory with CALENDAR_MCP_CONFIG_DIR.

Back-compat. TOKEN_FILE_PATH still works and now means the default account's token. If it is set, or if a .gcp-saved-tokens.json exists in the working directory, that file is used for the default account and nothing moves. Named accounts always live in the config directory.

Preferences

The server remembers how you like your week to be shaped, so the scheduling tools do not have to guess. Read them with get_preferences and change them with set_preferences ("I start at 8 and I want 15 minutes between meetings"). Preferences are global — they describe you, not one account — and are stored as preferences.json in the config directory.

Field Default Meaning
timezone unset IANA zone the working hours are expressed in, e.g. Europe/Berlin.
working_hours Mon–Fri 09:00–17:00 Per weekday (monsun), a list of ["HH:MM", "HH:MM"] spans.
buffer_minutes 0 Gap to leave either side of a meeting when proposing times.
min_focus_block_minutes 60 Shortest free stretch that still counts as usable focus time.
lunch unset A daily break carved out of the working hours.
focus_calendar_id primary Calendar that focus blocks are booked on.
{
  "timezone": "Europe/Berlin",
  "working_hours": {
    "mon": [["09:00", "12:00"], ["13:00", "18:00"]],
    "tue": [["09:00", "17:00"]],
    "wed": [["09:00", "17:00"]],
    "thu": [["09:00", "17:00"]],
    "fri": [["09:00", "15:00"]],
    "sat": [],
    "sun": []
  },
  "buffer_minutes": 15,
  "min_focus_block_minutes": 90,
  "lunch": ["12:30", "13:15"],
  "focus_calendar_id": "primary"
}

set_preferences merges: only the arguments you pass change, and the merged result is validated before it is written, so a rejected change leaves the saved file untouched. The one exception is working_hours, which is a whole-schedule replacement — weekdays you leave out of the dict become non-working days. Pass clear_lunch: true to remove a lunch break.

Configuration

Variable Default Purpose
GOOGLE_CLIENT_ID OAuth client ID (required).
GOOGLE_CLIENT_SECRET OAuth client secret (required).
TOKEN_FILE_PATH .gcp-saved-tokens.json Where the default account's OAuth token is cached.
CALENDAR_MCP_CONFIG_DIR OS user config dir Directory holding per-account tokens (accounts/) and preferences.json.
CALENDAR_MCP_DEFAULT_ACCOUNT default Account used when a tool's account argument is omitted.
CALENDAR_SCOPES https://www.googleapis.com/auth/calendar Scope requested. Use .../auth/calendar.readonly for read-only.
OAUTH_CALLBACK_PORT 8080 Local port for the OAuth callback during calendar-mcp auth.
CALENDAR_MCP_ALLOW_BROWSER_AUTH unset Set to 1 to let the server itself open a browser when no token exists. Off by default so a stdio server never hangs.
CALENDAR_MCP_LOG_FILE unset Mirror the stderr log to this file.
CALENDAR_MCP_LOG_LEVEL INFO DEBUG/INFO/WARNING/ERROR/CRITICAL. Overrides --log-level.
HOST 127.0.0.1 Default for --host in HTTP mode.
PORT 8000 Default for --port in HTTP mode.

A .env file in the working directory is loaded on startup. Logs never go to stdout — stdout is the MCP protocol channel in stdio mode.

Commands

calendar-mcp [--transport {stdio,http}] [--host H] [--port P] [--path /mcp] [--log-level L]
calendar-mcp serve ...     # explicit form of the default
calendar-mcp auth [--account NAME] [--no-browser]
calendar-mcp accounts      # list known accounts; exit 1 if none is signed in
calendar-mcp check [--account NAME]   # token status + calendar list; exit 1 if no valid token
calendar-mcp --version

python -m calendar_mcp accepts the same arguments.

Google Cloud setup

  1. Create or select a project and enable the Google Calendar API.
  2. APIs & Services → Credentials → Create credentials → OAuth client ID → Application type: Desktop app. Copy the client ID and secret. There is no JSON download to keep.
  3. A Desktop app client has no "Authorized redirect URIs" field — Google permits http://localhost on any port for this client type, which is what the local callback on OAUTH_CALLBACK_PORT uses. Nothing to configure there.
  4. On the OAuth consent screen: User Type External, fill in the app name and contact emails, add the https://www.googleapis.com/auth/calendar scope, and add your own Google account as a test user. Without that last step the sign-in is rejected.

Development

git clone https://github.com/deciduus/calendar-mcp
cd calendar-mcp
uv venv
uv pip install -e ".[dev]"
pytest

Layout: calendar_mcp/server.py (the MCPServer, shared helpers and the credential provider), tools/ (one module per tool area — the tool functions themselves), calendar_actions.py (Google API calls), analysis.py, timeutil.py (pure interval maths), accounts.py (multi-account token paths), preferences.py (the saved schedule), models.py (pydantic input/output models), auth.py (OAuth), cli.py (the calendar-mcp command). scripts/smoke_stdio.py spawns a real stdio server and checks the handshake and tool list.

Upgrading from 1.0

Nothing breaks. All 15 original tools keep their names and their existing parameters; each simply gained an optional trailing account. Your existing TOKEN_FILE_PATH keeps working, and now names the default account's token. What is new: multiple accounts, saved scheduling preferences, and eight new tools (find_focus_time, block_focus_time, detect_conflicts, suggest_reschedule, time_audit, list_accounts, get_preferences, set_preferences).

Upgrading from 0.x

  • Package and command renamed. The distribution is now calendar-mcp-server and installs calendar-mcp (and an identical calendar-mcp-server alias). Point your client at uvx calendar-mcp-server instead of python /path/to/run_server.py.
  • run_server.py still works — it is now a thin shim over the CLI — but it is deprecated and will be removed in a future release.
  • The FastAPI/uvicorn HTTP API is gone. There are no REST endpoints, no /health, and no separate stdio bridge process; the server is one process on the MCP SDK. If you want HTTP, it is now MCP streamable HTTP at /mcp.
  • Authentication no longer happens implicitly. Run calendar-mcp auth once; the server will not open a browser unless CALENDAR_MCP_ALLOW_BROWSER_AUTH=1.
  • Tool names are unchanged, so existing prompts keep working. Results are now structured output rather than JSON stuffed into text.
  • Three new tools: move_event, respond_to_event, and project_recurring_events (the last previously existed only as internal logic).
  • Removed env vars: RELOAD, MCP_API_HOST. HOST/PORT now apply to the MCP HTTP transport.

License

This project is dual-licensed to support both open-source collaboration and sustainable development:

  1. GNU Affero General Public License v3.0 (AGPL-3.0):

    • This software is free to use, modify, and distribute under the terms of the AGPLv3 license.
    • Key conditions include that derivative works (including modifications used over a network) must also be licensed under AGPLv3 and their source code made available.
    • This license is suitable for open-source projects or internal use where AGPLv3 compliance is feasible.
    • See the LICENSE file for the full text.
  2. Commercial License:

    • If the terms of the AGPLv3 are not suitable for your specific use case (e.g., integrating this software into a proprietary, closed-source commercial product or service without complying with AGPLv3's source-sharing requirements), a separate commercial license is available.
    • Please contact deciduusleaf@gmail.com for inquiries regarding commercial licensing options.

By using, modifying, or distributing this software, you agree to be bound by the terms of either the AGPLv3 or a separately negotiated commercial license.

mcp-name: io.github.deciduus/calendar-mcp

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

calendar_mcp_server-1.1.0.tar.gz (108.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

calendar_mcp_server-1.1.0-py3-none-any.whl (113.2 kB view details)

Uploaded Python 3

File details

Details for the file calendar_mcp_server-1.1.0.tar.gz.

File metadata

  • Download URL: calendar_mcp_server-1.1.0.tar.gz
  • Upload date:
  • Size: 108.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for calendar_mcp_server-1.1.0.tar.gz
Algorithm Hash digest
SHA256 4d7733a6fdbc5c665f6a81ffd8cbb8f1a9c6886626435f5bc3d2dacfaee13547
MD5 3d062a63d2629b9e2f7ba7f311f63f5d
BLAKE2b-256 f58217361646dce7891dd091964223bb85277d414bd0e8b554d95c81f912a542

See more details on using hashes here.

Provenance

The following attestation bundles were made for calendar_mcp_server-1.1.0.tar.gz:

Publisher: release.yml on deciduus/calendar-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file calendar_mcp_server-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for calendar_mcp_server-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1bdde31dd0623a0e36815e7d95d5b1becb953b5b625419602505c85b591bcf81
MD5 5620ba40451f4521ae85de7c81a0b691
BLAKE2b-256 f9be48af58994197cb3a0674b71cce1f5fa47a58d622ac989ad486cd260b08f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for calendar_mcp_server-1.1.0-py3-none-any.whl:

Publisher: release.yml on deciduus/calendar-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page