Skip to main content
Glama
YHRen
by YHRen
README.md
# OpenSlot MCP

A read-only [Model Context Protocol](https://modelcontextprotocol.io/) server that turns one or more Outlook, iCloud, or Google calendars into privacy-safe availability queries. It accepts HTTPS ICS feeds and iCloud-style `webcal://` subscription links.

[![asciicast](https://asciinema.org/a/1264556.svg)](https://asciinema.org/a/1264556)

It intentionally never returns event titles, descriptions, locations, attendees, organizers, or raw calendar data — only busy/free time intervals.

## Requirements

- Node.js 20.6 or newer.
- An HTTPS or `webcal://` URL to an ICS calendar feed.

No installation is needed: MCP clients can launch the server straight from this repository with `npx -y github:YHRen/openslot-mcp`. The first launch clones and builds the package (expect several seconds); after that it runs from the npx cache. To get reproducible installs, pin a tag or commit, e.g. `github:YHRen/openslot-mcp#v0.1.0`.

## Get a calendar URL

The server uses each provider's read-only calendar feed. It does not ask for account credentials or request write access.

| Provider | How to get the ICS feed | Typical allowlist host |
| --- | --- | --- |
| Outlook / Microsoft 365 | In Outlook on the web, open **Calendar → View → Calendar settings → Calendar → Shared calendars**. Under **Publish a calendar**, select a calendar and permission level, select **Publish**, then copy the **ICS** link. Choose busy-only visibility when available. [Microsoft instructions](https://support.microsoft.com/en-us/outlook/sharing/share-an-outlook-calendar-as-view-only-with-others) | `outlook.office365.com` or `outlook.live.com` |
| iCloud Calendar | On a computer or tablet, open [Calendar on iCloud.com](https://www.icloud.com/calendar/), select the calendar's information button, turn on **Public Calendar**, then select **Copy**. Use the copied `webcal://` link as-is; the server converts it to HTTPS. [Apple instructions](https://support.apple.com/guide/icloud/mm6b1a9479/icloud) | The exact numbered host in the link, such as `p123-caldav.icloud.com` |
| Google Calendar | On a computer, open **Settings → Settings for my calendars → _calendar name_ → Integrate calendar**, then copy **Secret address in iCal format**. For an intentionally public calendar, its **Public address in iCal format** also works. [Google instructions](https://support.google.com/calendar/answer/37648) | `calendar.google.com` |

iCloud's public-calendar option makes the calendar readable to anyone who has its link; private iCloud shares are not anonymous ICS feeds and are not supported. Google Workspace administrators may disable secret iCal addresses.

## Connect an MCP client

**Claude Code:**

```bash
claude mcp add openslot \
  --env CALENDAR_ICS_URL="https://outlook.office365.com/owa/calendar/<token>/calendar.ics" \
  --env CALENDAR_ALLOWED_HOSTS="outlook.office365.com" \
  -- npx -y github:YHRen/openslot-mcp
```

**Claude Desktop** (`claude_desktop_config.json`) and other JSON-configured hosts:

```json
{
  "mcpServers": {
    "openslot": {
      "command": "npx",
      "args": ["-y", "github:YHRen/openslot-mcp"],
      "env": {
        "CALENDAR_ICS_URL": "https://outlook.office365.com/owa/calendar/<token>/calendar.ics",
        "CALENDAR_ALLOWED_HOSTS": "outlook.office365.com"
      }
    }
  }
}
```

**OpenAI Codex CLI** (`~/.codex/config.toml`):

```toml
[mcp_servers.openslot]
command = "npx"
args = ["-y", "github:YHRen/openslot-mcp"]

[mcp_servers.openslot.env]
CALENDAR_ICS_URL = "https://outlook.office365.com/owa/calendar/<token>/calendar.ics"
CALENDAR_ALLOWED_HOSTS = "outlook.office365.com"
```

**Google Antigravity** (agy CLI and IDE) — add to the shared `~/.gemini/config/mcp_config.json`, or `.agents/mcp_config.json` to scope it to one workspace:

```json
{
  "mcpServers": {
    "openslot": {
      "command": "npx",
      "args": ["-y", "github:YHRen/openslot-mcp"],
      "env": {
        "CALENDAR_ICS_URL": "https://outlook.office365.com/owa/calendar/<token>/calendar.ics",
        "CALENDAR_ALLOWED_HOSTS": "outlook.office365.com"
      }
    }
  }
}
```

Other MCP hosts follow the same pattern: launch `npx -y github:YHRen/openslot-mcp` over stdio with the `CALENDAR_*` environment variables set.

Replace the sample Outlook URL with the iCloud or Google URL you copied. Set `CALENDAR_ALLOWED_HOSTS` to the exact hostname in that URL—for example, `calendar.google.com` for Google or the numbered `p…-caldav.icloud.com` hostname present in an iCloud link.

To combine providers, configure multiple named feeds:

```json
[
  { "id": "work", "url": "https://outlook.office365.com/owa/calendar/<token>/calendar.ics" },
  { "id": "personal", "url": "webcal://p123-caldav.icloud.com/published/2/<token>" },
  { "id": "family", "url": "https://calendar.google.com/calendar/ical/<calendar-and-token>/basic.ics" }
]
```

Pass the compact JSON as `CALENDARS_JSON` and set `CALENDAR_ALLOWED_HOSTS=outlook.office365.com,p123-caldav.icloud.com,calendar.google.com`.

To sanity-check your calendar URL outside a client, run the server directly — it speaks MCP over stdio, so it will sit waiting for a client; starting without an error means the configuration is valid (Ctrl-C to exit):

```bash
CALENDAR_ICS_URL="https://…/calendar.ics" npx -y github:YHRen/openslot-mcp
```

## Tools

- `get_availability(start, end, timezone?, calendars?, includeTentative?)`: merged busy intervals only.
- `find_free_slots(start, end, durationMinutes, timezone?, calendars?, includeTentative?, bufferMinutes?, workingHours?)`: openings matching a required duration, working hours, and optional meeting buffer.
- `get_calendar_status()`: cache/source health without event content.

Example — `find_free_slots` with `{"start": "2026-09-01T00:00:00-04:00", "end": "2026-09-03T00:00:00-04:00", "durationMinutes": 30}` returns:

```json
{
  "timezone": "America/New_York",
  "start": "2026-09-01T00:00:00-04:00",
  "end": "2026-09-03T00:00:00-04:00",
  "durationMinutes": 30,
  "slots": [
    { "start": "2026-09-01T10:00:00-04:00", "end": "2026-09-01T11:00:00-04:00", "durationMinutes": 60 }
  ]
}
```

Queries use offset-bearing ISO-8601 input timestamps, output the caller's requested IANA timezone, and are capped at 90 days. All-day and floating (TZID-less) events are interpreted in the requested timezone. ICS refreshes are cached for five minutes by default and use conditional HTTP requests when the provider supports them. If a refresh fails, cached data is served with a `staleCalendars` field in the response and retries are backed off for 30 seconds.

## Configuration

Set environment variables through your MCP host's `env` block (or `.env` for local runs):

- `CALENDAR_ICS_URL` — a single HTTPS or `webcal://` calendar feed.
- `CALENDARS_JSON` — multiple named calendars, including feeds from different providers. Takes precedence over `CALENDAR_ICS_URL`.
- `CALENDAR_ALLOWED_HOSTS` — optional comma-separated host allowlist; strongly recommended.
- `CALENDAR_CACHE_TTL_SECONDS` — feed cache lifetime, default `300`.
- `CALENDAR_DEFAULT_TIMEZONE` — IANA timezone used when a query does not specify one, default `America/New_York`.

## Privacy and security

A published ICS URL—including Google's Secret address—acts like a bearer credential: anyone with it may read the calendar view. Store it only in secret or local environment configuration, do not commit it, and rotate/revoke it if exposed. The server converts `webcal://` links to HTTPS, rejects embedded URL credentials and non-443 ports, validates every redirect hop against the same rules and the host allowlist, restricts feed size to 5 MiB, and does not log the source URL or calendar contents.

## Development

```bash
git clone https://github.com/YHRen/openslot-mcp.git
cd openslot-mcp
npm install     # also compiles dist/ via the prepare script
cp .env.example .env   # put your calendar URL in .env (do not commit it)

npm test        # vitest, synthetic fixtures only
npm run check   # typecheck
npm run build   # compile to dist/
node --env-file=.env dist/index.js                  # run the built server
node --env-file=.env --import tsx src/index.ts     # run from source
```

To point an MCP client at a local checkout instead of the npm package, use `node /absolute/path/to/openslot-mcp/dist/index.js` as the command.

TDQS

A3.7/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a clearly distinct purpose: one returns busy intervals, one returns free slots, and one returns only health/cache metadata. There is no overlap in what an agent would select them for.

Naming Consistency5/5

All three tool names follow a consistent lowercase snake_case verb_noun pattern: get_availability, find_free_slots, get_calendar_status. The pattern is predictable and readable across the set.

Tool Count5/5

Three tools is a well-scoped size for a focused calendar-availability server. Each tool maps to a distinct operation an agent would need, with no redundancy or bloat.

Completeness5/5

For the stated purpose of privacy-safe availability, the server covers the essential operations: busy intervals, free slots, and source/cache health. There are no obvious dead ends or missing core capabilities within this narrow domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues