MCP server and REST API

Updated November 10, 2026

Documentation for the Keeper.sh MCP server and REST API. Connect an MCP client over OAuth 2.1 to read and write every calendar you have synced, or call the same endpoints with an API token.

What the server does

Keeper.sh syncs events between Google Calendar, Outlook, iCloud, Fastmail, CalDAV servers, and iCal links, as the calendar sync features page describes. The MCP server puts that same merged view in front of an assistant: one connection reads and writes every calendar you have connected, rather than one integration per provider.

It speaks the Model Context Protocol over streamable HTTP at https://www.keeper.sh/mcp, and every tool is a thin wrapper over the public REST API described further down. Writes go through to the provider that owns the calendar, so an event an assistant creates appears in Google Calendar or Outlook exactly like one you made by hand, and Keeper.sh keeps syncing it from there.

Self-hosted instances serve the same endpoint at /mcp on your own domain. Everything on this page applies to them, with your instance URL in place of www.keeper.sh.

Quickstart

You need a Keeper.sh account (create one free) with at least one calendar connected. Client-by-client steps for Claude are in how to give Claude access to your calendar. There is no key to copy: the client registers itself and you approve it in the browser.

  1. Point an MCP client at https://www.keeper.sh/mcp.
  2. The first request comes back 401 with a WWW-Authenticate header, and the client follows it into the OAuth flow.
  3. Sign in and approve the consent screen. The client stores the tokens it gets back.
  4. Ask it something like "what is on my calendar tomorrow?". It should call get_events.

Configuration for a client that takes a JSON server list:

{
  "mcpServers": {
    "keeper": {
      "type": "url",
      "url": "https://www.keeper.sh/mcp"
    }
  }
}

Authentication with OAuth 2.1

The MCP server is an OAuth 2.1 protected resource. It never sees your password, and there is nothing to paste into a client.

An unauthenticated request is answered 401 with WWW-Authenticate: Bearer resource_metadata=…, which points at the protected resource metadata document. That document names the authorization server, and the client discovers the rest from there.

curl https://www.keeper.sh/.well-known/oauth-protected-resource

{
  "resource": "https://www.keeper.sh/mcp",
  "authorization_servers": ["https://www.keeper.sh/api/auth"],
  "scopes_supported": ["keeper.read", "keeper.events.read", "..."]
}

Clients register themselves through dynamic client registration, so you do not create an OAuth app first. Authorization uses the authorization code flow with PKCE, and the consent screen is hosted by Keeper.sh at /oauth/consent. Access tokens last 30 days and refresh tokens 90 days; a client that asks for offline_access can refresh without sending you back through consent.

Every tool call requires the keeper.read scope. A token without it is answered 403 with error="insufficient_scope". The scopes a client may request are:

ScopeCovers
keeper.readRequired by every tool call. Without it the server answers 403.
keeper.writeCreating, editing, deleting and responding to events, triggering or pausing a sync, and changing syncs, calendar settings, calendar links and shareable calendar links.
keeper.events.readReading events and invitations.
keeper.sources.readReading the calendars events are copied from.
keeper.destinations.readReading the calendars events are copied into.
keeper.mappings.readReading which calendar is copied into which.
keeper.sync-status.readReading sync state.

Write tools are covered by the same grant: the token is passed straight through to the REST API, which authorizes it as your account. Removing the Keeper.sh connector in the client cuts it off from both.

Tools

The server exposes 27 tools. Date ranges are ISO 8601 datetimes, and read tools that take a timezone return times already localized to it, so an assistant does not have to convert anything itself.

Calendars and accounts

What the user has connected. Most sessions start here, because every other tool takes a calendar ID.

ToolWhat it doesArguments
list_calendarsLists every calendar connected to Keeper.sh with its provider and the account it belongs to.No arguments
list_accountsLists connected calendar accounts and the provider each one signs in with.No arguments
refresh_calendarsRe-discovers the calendars on one account, or on every account: imports calendars added at the provider and flags ones that are gone. Refreshing every account is throttled to one request per minute.Optional accountId
rename_calendarRenames a calendar in Keeper.sh. The name at the provider stays as it is.calendarId, name

Reading events

Reads across every synced calendar at once. Times come back localized to the timezone you pass in.

ToolWhat it doesArguments
get_eventsReturns the events in a date range, with their calendar, title, description, location, and times.from, to, timezone
get_eventReturns one event by the ID that get_events reported.eventId
get_event_countReturns only how many events fall in a range, so a busy week can be measured without listing it.from and to, both optional
get_pending_invitesReturns invitations on a calendar that have not been responded to yet.calendarId, from, to, timezone

Writing events

Writes land on the provider the calendar belongs to, so a created event appears in Google Calendar, Outlook, or a CalDAV server the same as one made by hand.

ToolWhat it doesArguments
create_eventCreates an event on a connected calendar. Pass a timezone so CalDAV providers show it in local time.calendarId, title, startTime, endTime, and optional description, location, isAllDay, availability, timezone
update_eventUpdates an existing event. Only the fields you send are changed.eventId, plus any fields to change
delete_eventDeletes an event by ID.eventId
rsvp_eventResponds to an invitation with accepted, declined, or tentative.eventId, rsvpStatus

Scheduling and sync

Finding an open slot, and controlling when syncing happens.

ToolWhat it doesArguments
find_free_timeFinds open slots of at least a given length across every synced calendar. Events marked free or working-elsewhere never block; busy, out-of-office, and all-day events do.from, to, timezone, durationMinutes, and optional workingHoursStart, workingHoursEnd, workingDays, calendarId, ignoreAllDayEvents, limit
trigger_syncForces a sync now instead of waiting for the next pass. Throttled to one request per minute per user.No arguments
pause_syncPauses or resumes one calendar without disconnecting it. Nothing is read from it or written to it while paused, and its stored events are kept.calendarId, paused

Syncs and calendar settings

Which calendar copies into which, and what a copy shows. Changing a sync replaces the whole destination list, so an assistant reads it first.

ToolWhat it doesArguments
list_syncsLists every sync as a source calendar ID and a destination calendar ID.No arguments
set_sync_destinationsSets the calendars a calendar copies its events to, replacing its current list. Copies already made on a removed destination are removed on the next sync. The Free plan allows 3 syncs.calendarId, destinationCalendarIds
get_calendar_settingsReturns a calendar's settings, and the calendars it copies to and from.calendarId
update_calendar_settingsChanges what a calendar's copies show: the title, which events and details are left out, privacy, reminders and how far back and ahead events are copied. Requires Pro.calendarId, plus any of customEventName, excludeAllDayEvents, excludeAvailabilities, excludeEventDescription, excludeEventLocation, excludeEventName, excludeFocusTime, excludeOutOfOffice, excludeTitleKeywords, markEventsAsPrivate, disableEventReminders, syncHistoricRange, syncFutureRange, treatFullDayTimedEventsAsAllDay

Read-only calendars subscribed from a link. Each link counts as one linked account on the plan.

ToolWhat it doesArguments
add_calendar_linkSubscribes to a calendar from its iCal link.url, and optional name
remove_calendar_linkRemoves a calendar added from an iCal link. Calendars from a signed-in account cannot be removed this way.calendarId

The iCal feeds Keeper.sh publishes from your calendars, for subscribing from any calendar app. Every account has a default feed, which cannot be deleted. The Free plan allows 1 feed, and feed settings other than the name require Pro.

ToolWhat it doesArguments
list_ical_feedsLists your feeds with their subscription URLs, settings and calendar IDs.No arguments
create_ical_feedCreates a feed with its own URL. It starts with no calendars.name
update_ical_feedRenames a feed or changes what its events show. Only the fields you send are changed.feedId, plus any of name, includeEventName, includeEventDescription, includeEventLocation, excludeAllDayEvents, excludeFocusTime, excludeOutOfOffice, customEventName
set_ical_feed_calendarsSets the calendars a feed publishes, replacing its current list.feedId, calendarIds
delete_ical_feedDeletes a feed. Its URL stops working for every subscriber.feedId

Feedback

For the assistant itself, not your calendar. When a tool errors unexpectedly, returns something confusing, lacks a capability the assistant needed, or forces a workaround, the assistant can tell the Keeper.sh team directly. The server's instructions point it here, and unexpected errors end with a reminder.

ToolWhat it doesArguments
send_feedbackSends feedback about Keeper.sh's tools and API to the Keeper.sh team and answers with a one-line confirmation.message (the goal, what was tried and where it got stuck, up to 2,000 characters), category (tool_error, missing_capability, confusing_output, documentation or other), and optional toolName and severity (high, medium or low, default medium)

Feedback goes to Keeper.sh from every server, self-hosted ones included. It carries the message, category, severity, tool name, the intent given on that call, the server's Keeper.sh version, and whether it is hosted or self-hosted. It never carries your token, user ID, email address, calendar data or your server's hostname.

To opt out, turn off Agent feedback in Settings. Your assistant is then not offered send_feedback, nothing is sent if it calls it anyway, and the server stops mentioning feedback in its instructions and error messages. On a self-hosted server, setting AGENT_FEEDBACK_DISABLED=true on the MCP service turns it off for everyone.

Intent

Every tool also takes an optional intent of up to 300 characters, where the assistant says what it is trying to do. Keeper.sh records it with the tool name in its request logs to learn what assistants are using the tools for, unless you have turned off agent feedback, in which case it is accepted and dropped. It is never passed to the API or echoed back, and it does not change what the tool does.

Worked examples

What a session actually looks like. The prompts are yours; the calls are what the assistant makes. More requests an assistant can carry out are in what an AI agent can do with your calendar.

"What does my Thursday look like across all my calendars?"

One read covers every connected calendar, so the assistant does not need to know which provider an event came from. Each event carries its calendar name and provider back with it.

get_events(from: "2026-09-10T00:00:00Z", to: "2026-09-11T00:00:00Z", timezone: "America/New_York")

"Find 45 minutes next week that works for me and book a design review."

find_free_time reads busy time from every calendar at once, then create_event writes the booking back to the provider that owns the calendar you name.

find_free_time(from: …, to: …, timezone: "America/New_York", durationMinutes: 45, workingHoursStart: "09:00", workingHoursEnd: "17:00", workingDays: "1,2,3,4,5")
list_calendars()
create_event(calendarId: …, title: "Design review", startTime: …, endTime: …, timezone: "America/New_York")

"Decline anything on my work calendar I have not answered yet."

Invitations are listed per calendar, and the RSVP is written back to the provider, so the organizer sees the response.

get_pending_invites(calendarId: …, from: …, to: …, timezone: "America/New_York")
rsvp_event(eventId: …, rsvpStatus: "declined")

"Copy my work calendar into my personal one."

The assistant looks up both calendar IDs, reads the work calendar's current destinations so none are dropped, then writes the new list.

list_calendars()
get_calendar_settings(calendarId: …)
set_sync_destinations(calendarId: …, destinationCalendarIds: [… , …])

REST API

The MCP tools call the same public API you can call yourself, under /api/v1. Anything an assistant can do through MCP you can do with curl.

Create a token from Settings → API Tokens in the dashboard. Tokens are prefixed with kpr_ and shown once, at creation. Pass one as a bearer token. The same routes also accept a logged-in browser session or an MCP OAuth access token, so all three callers reach the same handlers.

curl https://www.keeper.sh/api/v1/calendars \
  -H "Authorization: Bearer kpr_..."
EndpointDescription
GET /api/v1/calendarsList connected calendars. Accepts an optional comma-delimited provider filter.
PATCH /api/v1/calendars/{calendarId}Pause or resume syncing for a calendar without disconnecting it.
GET /api/v1/calendars/{calendarId}/invitesList invitations on a calendar that have not been responded to, within a date range.
GET /api/v1/accountsList connected accounts and how many calendars each one has.
GET /api/v1/eventsList events in a date range. Accepts calendarId, availability, and isAllDay filters, and count=true to return only a count.
POST /api/v1/eventsCreate an event. Requires calendarId, title, startTime, and endTime.
GET /api/v1/events/{id}Get a single event.
PATCH /api/v1/events/{id}Update an event's fields, or send rsvpStatus to respond to an invitation.
DELETE /api/v1/events/{id}Delete an event.
GET /api/v1/events/free-timeFind free slots of at least durationMinutes in a date range. Requires timezone, and accepts working-hours options.
POST /api/v1/syncTrigger a sync immediately. Throttled to one request per minute per user.
GET /api/v1/icalGet the URL of your iCal feed.
GET /api/v1/mappingsList which calendars sync into which, as source and destination calendar ID pairs.
PUT /api/v1/calendars/{calendarId}/destinationsReplace the calendars a calendar syncs to. Send calendarIds.
GET /api/v1/calendars/{calendarId}/settingsGet a calendar's settings and the calendars it syncs to and from.
PATCH /api/v1/calendars/{calendarId}/settingsRename a calendar with name, or change its settings. Settings other than the name require Pro.
POST /api/v1/accounts/{id}/refreshRe-discover the calendars on one account.
POST /api/v1/accounts/refresh-calendarsRe-discover the calendars on every account. Throttled to one request per minute per user.
POST /api/v1/icsSubscribe to a calendar from its iCal link. Send url, and optionally name.
DELETE /api/v1/ics/{calendarId}Remove a calendar added from an iCal link.
GET /api/v1/ical/feedsList your shareable calendar links with their URLs, settings and calendar IDs.
POST /api/v1/ical/feedsCreate a shareable calendar link. Send name.
PATCH /api/v1/ical/feeds/{id}Rename a shareable calendar link or change its settings. Settings other than the name require Pro.
PUT /api/v1/ical/feeds/{id}/calendarsReplace the calendars a shareable calendar link publishes. Send calendarIds.
DELETE /api/v1/ical/feeds/{id}Delete a shareable calendar link. The default one cannot be deleted.

Range parameters from and to are ISO 8601 datetimes. If omitted, from defaults to now and to to a week later. A range may not exceed 732 days.

Finding free time

Events marked free or working-elsewhere are treated as non-blocking; everything else, including all-day events, is busy. Pass ignoreAllDayEvents=true to stop all-day events blocking. Working hours are 24-hour local times and workingDays counts 0 as Sunday, both read against timezone, so the hours hold across daylight saving transitions.

curl -G https://www.keeper.sh/api/v1/events/free-time \
  -H "Authorization: Bearer kpr_..." \
  --data-urlencode "from=2026-09-07T00:00:00Z" \
  --data-urlencode "to=2026-09-11T00:00:00Z" \
  --data-urlencode "timezone=America/New_York" \
  --data-urlencode "durationMinutes=45" \
  --data-urlencode "workingHoursStart=09:00" \
  --data-urlencode "workingHoursEnd=17:00" \
  --data-urlencode "workingDays=1,2,3,4,5"

Creating an event

The calendar ID comes from GET /api/v1/calendars. Sending a timezone keeps CalDAV providers such as iCloud and Fastmail from rendering the event in GMT.

curl -X POST https://www.keeper.sh/api/v1/events \
  -H "Authorization: Bearer kpr_..." \
  -H "Content-Type: application/json" \
  -d '{
    "calendarId": "6f1c0c2e-1f5f-4a2f-9a1e-6a1b7f0c9d3e",
    "title": "Design review",
    "startTime": "2026-09-08T15:00:00Z",
    "endTime": "2026-09-08T15:45:00Z",
    "timezone": "America/New_York"
  }'

Limits

On the Free plan the API and MCP server together are capped at 25 requests per day (plans and limits, pricing), after which requests return 429. Pro is uncapped, and self-hosted instances running without commercial mode are treated as Pro. POST /api/v1/sync and trigger_sync are separately throttled to one request per minute per user, and so is refreshing every account at once. Both answer 429 with a Retry-After header rather than queueing a second run.

Can Keeper.sh use cookies for analytics?