Skip to content

MCP server ​

hypercal can serve the Model Context Protocol at /mcp, so an AI assistant can answer "what's on Thursday", find a slot that suits three people, and put the meeting in the calendar. It speaks streamable HTTP, authenticates with the same app-specific passwords CalDAV clients use, and is scoped to one account.

Off by default. An admin switches it on in Settings → Admin → AI agent access; until then /mcp answers 503. The setting is read per request, so it takes effect at once, with no restart.

Authentication. Authorization: Bearer <app password>. A read password gets only the read-only tools, and they are the only ones its client is even shown. Revoke from Settings → Account like any other app password.

Scoping. Every call runs as the credential's owner. The endpoint reaches the event, calendar and free-busy routes and nothing else: an agent cannot mint another app password, change the account password, read the encryption bundle, or touch admin settings.


Switching it on ​

Settings → Admin → AI agent access (MCP). Only an admin sees that section (the first registered account, plus anyone since made one). There is nothing to configure in the environment and nothing to restart.

Switching it back off is immediate too, including for a client that is already connected. While it is off the endpoint answers 503 without looking at the credential at all, so a disabled instance is not somewhere to test app passwords.

One thing to know before you do

Turning this on lets every existing app password on the instance reach /mcp, including ones minted long ago for a phone. The credential and its read/read-write scope are unchanged, but the surface it can reach is not. Each person still only ever reaches their own calendar. On an instance with other people on it, say so, or have everyone re-mint their passwords afterwards.

Connecting a client ​

Mint a password first: Settings → Account → CalDAV and agent access → New app password. Choose Read only if the agent should never change anything. The token is shown once.

For Claude Code:

bash
claude mcp add --transport http hypercal https://calendar.example.com/mcp \
  --header "Authorization: Bearer YOUR-APP-PASSWORD"

For a client configured by file, the equivalent is an HTTP server entry:

json
{
  "mcpServers": {
    "hypercal": {
      "type": "http",
      "url": "https://calendar.example.com/mcp",
      "headers": { "Authorization": "Bearer YOUR-APP-PASSWORD" }
    }
  }
}

Against a local development server the URL is http://localhost:3001/mcp. A fresh instance answers 503 there until an admin switches the endpoint on under Settings, Admin.

To check the endpoint without a client at all, the reference inspector works:

bash
npx @modelcontextprotocol/inspector

Point it at the URL, set the same Authorization header, and list the tools.

Tools ​

Times are ISO 8601 with an explicit offset (2025-03-03T09:00:00+01:00). All-day events use plain dates (2025-03-03) instead, with endDate being the last day, inclusive; the endpoint works out the local midnights the storage format wants, so an agent cannot get that subtly wrong.

Reading ​

ToolNotes
list_calendarsReports encrypted, readOnly and the caller's access role on each.
list_eventsA time range, with recurring series already expanded and per-occurrence edits applied. Maximum 62 days.
search_eventsText over titles, descriptions and locations, with paging.
get_eventOne stored event by id. For a series this is the master, with its rrule.
find_free_slotsGaps of at least durationMinutes, optionally across several people and confined to working hours. Maximum 31 days. Other people must be discoverable; an account that has opted out of discovery resolves to "No such user".
check_conflictsWhat already overlaps a proposed time.

Writing ​

Only with a read-write password.

ToolNotes
create_eventTimed or all-day, optionally recurring.
update_eventChanges only the fields passed. On a series, changes the whole series.
delete_eventTo the trash by default; permanent, or following to end a series early.
restore_eventBack out of the trash.
override_occurrenceEdit or cancel a single date of a series.
split_series"This and all following events".

Recurrence ​

rrule is an RFC 5545 rule body with no DTSTART, e.g. FREQ=WEEKLY;BYDAY=TU;COUNT=10. A malformed rule is refused with a message that names the part at fault. list_events returns each occurrence with a masterId and its occurrenceStart, which is the occurrence's original start and stays its identity even after an override moves it.

The three ways to change a series are worth keeping straight:

WantTool
Just this dateoverride_occurrence
This date and everything aftersplit_series
The whole seriesupdate_event

Encrypted calendars ​

A calendar marked encrypted holds content encrypted in the browser, and the server has no key for it (see Security model). Over MCP that means:

  • titles, descriptions and locations come back as null, with encrypted: true, rather than as a page of base64;
  • those events never match search_events, because matching needs a blind index the agent cannot compute;
  • their times are still visible, so they do count towards find_free_slots and check_conflicts, which is usually what you want: an agent can schedule around a private appointment without learning what it is;
  • create_event refuses to write into one.

If you want an assistant to work with a calendar's contents, keep that calendar unencrypted.

Limits and refusals ​

  • The endpoint is unavailable on an instance whose admin has not switched it on, and on the Android standalone build, which has no HTTP listener.
  • list_events is capped at 62 days per call and find_free_slots at 31; search_events pages instead.
  • Requests are limited to 120 a minute per client address, and repeated failed credentials back off on the escalating delay the sign-in routes use.
  • Nothing prevents a double booking; that is what check_conflicts is for.
  • find_free_slots counts only the account's own events, not calendars shared to it, matching what the app's own find-a-time overlay shows.
  • Read-only calendars (subscription feeds) reject writes.

What is deliberately not here ​

OAuth. The MCP specification describes an OAuth 2.1 authorization flow with dynamic client registration for remote servers. hypercal does not implement it. This is a self-hosted app whose credential model is already a revocable, high-entropy, per-client token with a scope, and a static bearer token is that model spelled the way MCP clients can already send it. Adding an authorization server would be a large amount of new code and a new thing to get wrong, for no capability this endpoint lacks.

stdio. There is no local bridge process to install. The endpoint is remote and every current client can reach it over HTTP.

Resources and prompts. Tools only, for now.

Tasks, attendees, sharing and attachments. Reachable over the REST API and over CalDAV, but not exposed as tools yet.

Android. The standalone app runs the same backend in a Web Worker with no HTTP listener, so nothing can connect to it. Point an agent at a server deployment instead.