OpenSlot MCP
# 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.
[](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
Scored across 3 tools
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.
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.
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.
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.