Skip to main content
Glama
ICS Calendar MCP

ICS Calendar MCP

by mihirkavi

Why ICS Calendar MCP

To plan a meeting, an AI assistant needs to know when you're free. Most calendar integrations hand it far more than that: meeting titles, notes, who you're meeting, where, and often write access too.

ICS Calendar MCP gives the assistant only time ranges. It reads the private subscription links that iCloud, Google and most calendar apps can publish, strips every event down to a start and an end, merges your calendars, and returns busy blocks or free slots. Event details and feed URLs never leave the server.

Ask things like "Find me a free hour on Thursday afternoon" or "Am I free Friday between 2 and 4?"

Related MCP server: google-calendar-mcp

The three tools

All three are read-only.

Tool

What it returns

list_calendars

Configured calendar IDs, display names and timezones, plus the current time. Never feed URLs or secrets.

get_free_busy

Merged busy intervals (busy or tentative) across the chosen calendars for up to 93 days, with per-calendar coverage.

find_free_slots

Free windows of at least N minutes inside working hours (default 09:00–17:00 on weekdays), with an optional buffer around meetings.

  • Timed, all-day, multi-day and overnight events

  • Recurrence (RRULE, RDATE, EXDATE), modified and cancelled occurrences, and RANGE=THISANDFUTURE

  • STATUS:CANCELLED and TRANSP:TRANSPARENT events are skipped; STATUS:TENTATIVE events are reported as tentative

  • IANA timezones, embedded VTIMEZONE definitions, floating times (read in the calendar's default timezone) and DST transitions

  • Overlapping events from any number of calendars are merged

Quick start

You need Node.js 22 or newer (24 recommended; see .nvmrc).

git clone https://github.com/mihirkavi/ics-calendar-mcp.git
cd ics-calendar-mcp
npm install
npm test        # 125 tests in the Workers runtime (Miniflare), no network access

npm run check type-checks and npm run lint runs ESLint. To run the Worker locally, copy .dev.vars.example to .dev.vars, fill it in and run npm run dev. The sign-in cookies need HTTPS, so use wrangler dev --local-protocol https or test sign-in against a deployed Worker.

How it works

MCP client ──OAuth 2.1──▶ Cloudflare Worker
                           ├─ OAuth provider (/authorize, /token, /register, discovery)
                           │    └─ GitHub sign-in + numeric-ID allowlist
                           └─ /mcp (stateless Streamable HTTP)
                                └─ CalendarService
                                     ├─ registry (IDs → secret bindings)
                                     ├─ fetch policy (HTTPS, exact host, limits)
                                     ├─ ICS adapter (ICAL.js → detail-free intervals)
                                     └─ interval merge + free-slot search

The server stores no event data: each request fetches the feeds live, turns them into bare intervals and throws the rest away.

Privacy by design

  • No event content. The ICS adapter reads only start, end, duration, recurrence, status and transparency.

  • Callers can't choose URLs. Feeds come only from an operator-defined registry and Worker secrets. Each fetch needs HTTPS and an exact hostname, rejects embedded credentials, custom ports and redirects, times out after 10 seconds and stops at 5 MiB.

  • Bounded work. Ranges are capped at 93 days, 10,000 busy occurrences per calendar and 100,000 recurrence candidates.

  • Locked-down sign-in. PKCE (S256), exact redirect-URI checks, a consent page that can't be framed, single-use browser-bound state, and tokens stored only as hashes in Cloudflare KV. The GitHub token is discarded after the identity check.

  • Redacted errors. Public errors carry a stable code and a generic message, with URLs, credentials and calendar text scrubbed.

Treat calendar subscription links like passwords. Anyone with the link can read the full calendar. Keep them in Worker secrets, and regenerate them in your calendar app if they're ever exposed.

Deploy your own

One Cloudflare Worker (the free plan is enough to try it), a GitHub account and a GitHub OAuth App.

npx wrangler kv namespace create OAUTH_KV          # put the ID in wrangler.jsonc
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put ALLOWED_GITHUB_USER_ID     # numeric: gh api user --jq .id
npx wrangler secret put CALENDAR_REGISTRY          # JSON array, see below
npx wrangler secret put CALENDAR_1_ICS_URL         # one per calendar
npm run deploy

Your MCP endpoint is https://<your-worker-host>/mcp. Set the OAuth App's callback URL to https://<your-worker-host>/callback.

Without configuration, the server uses one synthetic example calendar (example-calendar, read from the CALENDAR_EXAMPLE_ICS_URL secret, host calendar.example.com). To use your own, set CALENDAR_REGISTRY to a JSON array:

[
  {
    "id": "work",
    "name": "Work",
    "enabled": true,
    "timezone": "America/New_York",
    "secretBinding": "CALENDAR_1_ICS_URL",
    "allowedHostname": "calendar.example.com"
  }
]
  • id: lowercase letters, digits and hyphens. MCP clients pass it as calendarIds.

  • timezone: an IANA zone, used for all-day and floating events and as the default output zone.

  • secretBinding: the name of a separate Worker secret that holds the feed URL.

  • allowedHostname: the exact host the feed URL must use.

Feed URLs must be https://. If your calendar app gives you a webcal:// link, replace webcal with https.

If you don't want the KV namespace ID in Git, copy wrangler.jsonc to wrangler.local.jsonc (ignored by Git), edit the copy, and pass -c wrangler.local.jsonc to Wrangler. npm run deploy:local does this for you. Each secret put prompts for its value, so secrets never land in your shell history or in files.

OAuth discovery is published at /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server, and clients can register dynamically at /register.

  • ChatGPT: add a custom MCP connector with the /mcp URL and OAuth, then sign in with GitHub. Whether custom connectors (and connectors in mobile and Voice modes) are available depends on your plan, workspace settings and OpenAI's rollout.

  • MCP Inspector: run npx @modelcontextprotocol/inspector, choose Streamable HTTP, enter the /mcp URL and complete the OAuth flow.

  • Other clients: any client that supports remote Streamable HTTP servers with OAuth 2.1 discovery and dynamic client registration should work.

Path

Responsibility

src/index.ts

Worker entry: OAuth provider wiring and the protected /mcp handler

src/auth/

Consent page, GitHub sign-in, numeric-ID allowlist

src/mcp/

Tool schemas, registration and text summaries

src/calendars/

Registry, service, ICS adapter, interval math, timezone helpers

src/security/

Feed fetch policy and error redaction

Honest status

v0.1 is an early, read-only foundation. It works end to end and has been verified with a standalone MCP client against one private deployment (record), but it hasn't been tested across many calendar providers, and ChatGPT connector use is not yet verified.

  • Read-only: no creating, editing or deleting events, and no CalDAV.

  • One allowed GitHub account per deployment; no multi-user access.

  • Feeds are fetched live on every request with no cache. A slow or offline provider makes that calendar unavailable (reported, never silently empty).

  • Published ICS feeds can lag behind the calendar app by several minutes.

  • Working hours can't cross midnight, and find_free_slots returns at most 100 windows.

  • A calendar that needs more than 100,000 recurrence candidates, or has more than 10,000 busy occurrences in the range, is reported as unavailable for that request.

  • Windows-style timezone names such as Pacific Standard Time work only when the feed includes their definition.

Ideas, not commitments (none implemented): short-lived caching with explicit stale coverage, per-calendar working hours, and CalDAV as another source.

Contributing

See CONTRIBUTING.md and the changelog. Please report vulnerabilities privately as described in SECURITY.md, not in public issues.

License

MIT © 2026 Mihir Kavishwar

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides AI assistants with intelligent access to Google Calendar data, enabling natural language queries about availability, upcoming events, schedule conflicts, and meeting summaries through context-aware calendar integration.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to read and manage a user's Google Calendar, including listing calendars, searching, creating, updating, and deleting events, and checking free/busy availability via OAuth 2.0.
    109 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables privacy-safe calendar availability queries across Outlook, iCloud, and Google ICS feeds, exposing only busy/free intervals and free-slot search without revealing event details.
    3
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables assistants and agents to query calendar availability from Google, Outlook, iCloud, or ICS feeds and return free time slots for specified days, date ranges, or pasted messages. It exposes read-only tools for finding availability and checking calendar readability without exposing calendar addresses.
    23 PyPI
    Apache 2.0