# AddCal MCP Server

Connect an AI assistant to AddCal over the Model Context Protocol to manage
calendars, events, RSVPs and invites in the user's own account.

- Endpoint: `https://addcal.co/mcp`
- Transport: streamable HTTP
- Authentication: OAuth 2.1 with dynamic client registration and PKCE
- Tools: 19
- Interactive version: https://addcal.co/docs/mcp

## Connecting

Claude Code:

```
claude mcp add --transport http addcal https://addcal.co/mcp
```

Any client that takes a JSON config:

```json
{
    "mcpServers": {
        "addcal": {
            "type": "http",
            "url": "https://addcal.co/mcp"
        }
    }
}
```

Windsurf names the same field `serverUrl`:

```json
{
    "mcpServers": {
        "addcal": {
            "serverUrl": "https://addcal.co/mcp"
        }
    }
}
```

Claude (Desktop or claude.ai) and ChatGPT take the endpoint directly:
add it under Connectors as a custom connector. ChatGPT needs developer
mode enabled first. Cursor and VS Code can install it in one click from
https://addcal.co/docs/mcp.

The first call opens a browser to authorise the client against your AddCal
account. No API key is copied or stored: the client registers itself and
receives a token scoped to your team. Revoke it any time from Settings > API.

## Tools

### Search

#### `search` (read only)

Search across calendars, collections, events and RSVP responses in one call, returning the ids each other tool needs. Use this first whenever the user refers to something by name rather than by id. Matches RSVPs on attendee name and email, so results may contain personal data.

- `query` (string, required) - What to search for. Matched against calendar and collection names, event titles, and RSVP attendee names and emails.
- `limit` (integer) - Maximum results per category (1-25).
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

### Calendars

#### `list-calendars` (read only)

List the calendars belonging to the authenticated user's team, with a count of upcoming events on each. Use this first to find the calendar_id needed by the event tools.

- `search` (string) - Optional name filter.
- `limit` (integer) - Maximum calendars to return.
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

#### `create-calendar`

Create a new calendar for the authenticated user's team.

- `name` (string, required) - The calendar name.
- `description` (string) - Optional calendar description.
- `timezone` (string) - IANA timezone, e.g. "Australia/Sydney". Defaults to the team timezone.
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

### Collections

#### `list-calendar-collections` (read only)

List the team's calendar collections. A collection bundles several calendars behind a single public page and subscribe link, so someone can follow them all at once. Returns each collection's member calendars and subscriber count.

- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

#### `create-calendar-collection`

Bundle several calendars into a collection with one public page and subscribe link. Pass the calendar ids in the order they should appear. Collections require a paid plan.

- `name` (string, required) - The collection name, shown on its public page.
- `calendar_ids` (array, required) - Calendar public ids (cal_xxx) to include, in display order.
- `description` (string) - Optional description shown on the collection page.
- `allow_subscriber_selection` (boolean) - Let subscribers pick which of the member calendars they follow, rather than following all of them.
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

### Subscribers

#### `list-subscribers` (read only)

List the people subscribed to a calendar, with a breakdown by status. "active" means their calendar app is still fetching the feed, "inactive" means it has not synced in over a week, and "blocked" means the organiser cut them off. Names and emails are only captured when the calendar asks subscribers for their details, so many rows are anonymous. Returns personal data.

- `calendar_id` (string, required) - The calendar public id, e.g. "cal_ab12cd34ef56".
- `status` (string) - Only return subscribers with this status.
- `search` (string) - Filter by subscriber name or email. Only works on calendars that collect subscriber details.
- `limit` (integer) - Maximum subscribers to return (1-100).

### Events

#### `list-events` (read only)

List or search events. Returns upcoming events across the team by default; pass calendar_id to scope to one calendar, or range="past"/"all" to look backwards. Dates are returned in ISO 8601 in the event's own timezone.

- `calendar_id` (string) - Calendar public id (cal_xxx) to scope results to. Omit to search the whole team.
- `search` (string) - Free text search across event titles.
- `range` (string) - Which events to return relative to now.
- `start_date` (string) - Only return events starting on or after this date (YYYY-MM-DD).
- `end_date` (string) - Only return events starting on or before this date (YYYY-MM-DD).
- `limit` (integer) - Maximum events to return (1-100).
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

#### `get-event` (read only)

Retrieve the full details of a single event by its id, including its shareable add-to-calendar links.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".

#### `create-event`

Create an event on a calendar. Supply date_start and date_end as "YYYY-MM-DD HH:MM:SS" (or a full ISO 8601 string) plus an IANA timezone. For an all day event set is_all_day and pass dates only. To make it repeat, pass an RFC 5545 recurrence_rule such as "FREQ=WEEKLY;BYDAY=MO".

- `calendar_id` (string, required) - Calendar public id (cal_xxx) to create the event on.
- `title` (string, required) - The event title.
- `date_start` (string, required) - Start date/time, e.g. "2026-09-01 10:00:00". For all day events pass just "2026-09-01".
- `date_end` (string, required) - End date/time. Must be on or after date_start.
- `timezone` (string) - IANA timezone for the supplied dates. Defaults to the calendar timezone. Ignored for all day events.
- `description` (string) - Event description. Basic HTML is permitted and will be sanitised.
- `location` (string) - Physical address or meeting URL.
- `is_all_day` (boolean) - Whether this is an all day event.
- `is_draft` (boolean) - Create as a draft, hidden from the public calendar.
- `has_rsvp` (boolean) - Enable RSVP registration for the event.
- `recurrence_rule` (string) - RFC 5545 RRULE without the "RRULE:" prefix, e.g. "FREQ=WEEKLY;BYDAY=MO;COUNT=10".
- `series_title` (string) - Optional title for the recurring series. Only used with recurrence_rule.
- `reminder_before` (integer) - Send attendees a reminder this many minutes before the event starts, e.g. 60 for one hour or 1440 for one day. 0 means at the time of the event. Email reminders depend on the team's plan.

#### `update-event`

Update an existing event. Only the fields you supply are changed. Note that events belonging to a recurring series have restrictions: the start date and recurrence rule of a series cannot be changed here.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `title` (string) - New event title.
- `description` (string) - New description. Basic HTML is permitted and will be sanitised.
- `location` (string) - New physical address or meeting URL.
- `date_start` (string) - New start date/time. If you change either date, supply both to avoid an inconsistent range.
- `date_end` (string) - New end date/time.
- `timezone` (string) - IANA timezone for the supplied dates. Defaults to the event's existing timezone.
- `is_all_day` (boolean) - Whether this is an all day event.
- `is_draft` (boolean) - Whether the event is a hidden draft.
- `has_rsvp` (boolean) - Whether RSVP registration is enabled.
- `recurrence_rule` (string) - RFC 5545 RRULE. Cannot be changed on an event that is already part of a series.
- `reminder_before` (integer) - Minutes before the event to send attendees a reminder, e.g. 60 for one hour. 0 means at the time of the event.

#### `duplicate-event`

Copy an existing event, optionally onto a different calendar and with a new title or start date. Far cheaper than re-specifying every field when the user wants "the same event again next month". The copy is independent: RSVPs, invites and analytics are not carried over, and a copy of a recurring event does not keep its series.

- `event_id` (string, required) - The event to copy, e.g. "evt_ab12cd34ef56".
- `target_calendar_id` (string) - Calendar to place the copy on (cal_xxx). Defaults to the source event's own calendar.
- `title` (string) - Title for the copy. Defaults to the original title.
- `date_start` (string) - New start date/time for the copy. Supply both dates when moving it.
- `date_end` (string) - New end date/time for the copy.

#### `delete-event` (destructive)

Permanently delete an event. Deleting the parent of a recurring series deletes its instances too. Optionally notifies anyone holding a calendar invite that the event is cancelled.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `confirm` (boolean, required) - Must be true. Deletion is permanent, and deleting the parent of a recurring series deletes every instance. Confirm which event the user means before setting this.
- `notify_invitees` (boolean) - Send a cancellation to anyone who was sent a calendar invite for this event.

#### `preview-recurring-dates` (read only)

Work out which dates a recurrence rule actually produces, without creating anything. Use this before create-event whenever you have translated a phrase like "every third Tuesday" into an RRULE, so you can show the user the real dates and have them confirm before any events are written.

- `recurrence_rule` (string, required) - RFC 5545 RRULE without the "RRULE:" prefix, e.g. "FREQ=MONTHLY;BYDAY=3TU;COUNT=6".
- `date_start` (string, required) - Start date/time of the first occurrence, e.g. "2026-09-01 10:00:00".
- `date_end` (string, required) - End date/time of the first occurrence.
- `timezone` (string) - IANA timezone the dates are expressed in. Defaults to UTC.
- `is_all_day` (boolean) - Whether these are all day occurrences.
- `count` (integer) - How many occurrences to return (1-50).

### RSVPs

#### `list-rsvps` (read only)

List the RSVP responses for an event, with a count of yes/maybe/no. Filter by response_type to get just the attendees. Returns attendee names, email addresses and any custom form answers, so treat the output as personal data.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `response_type` (string) - Only return responses of this type. Omit for all.
- `search` (string) - Filter by attendee name or email.
- `limit` (integer) - Maximum responses to return (1-100).

#### `create-rsvp` (destructive)

Record an RSVP on someone's behalf, as if they had responded on the event's public page.

USUALLY SENDS A REAL EMAIL. Unless the event has confirmation emails turned off, this
emails a confirmation to the address you provide, and on events with calendar invites
enabled it may also send them an invitation. Set send_confirmation=false to record the
response silently instead.

Only call this when the user has asked you to add a specific named person. Confirm the
name and address with them first, then pass confirm=true. If the same address has already
responded and the event prevents duplicates, the existing response is updated rather than
a second one created.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `name` (string, required) - The attendee's full name.
- `email` (string, required) - The attendee's email address. Confirm this with the user before calling.
- `response_type` (string, required) - Their response: attending, maybe, or not attending.
- `confirm` (boolean, required) - Must be true. Set this only after the user has explicitly confirmed the attendee's name and email address.
- `custom_fields` (object) - Answers to the event's custom RSVP form, keyed by field name. Omit if the event has no custom form.
- `send_confirmation` (boolean) - Set false to record the response without emailing the attendee. Defaults to the event's own confirmation setting.

### Invites

#### `list-invites` (read only)

List the calendar invites sent for an event and their current status (pending, sent, accepted, tentative, declined, bounced, failed, cancelled). Calendar invites are a Business plan feature.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `status` (string) - Only return invites with this status.
- `search` (string) - Filter by invitee email address.
- `limit` (integer) - Maximum invites to return (1-100).

#### `send-event-invite` (destructive)

SENDS A REAL EMAIL. This immediately emails a calendar invitation to the address you
provide, from the team's sending domain. It is not a draft or a preview, and it cannot
be unsent.

Only call this when the user has explicitly asked to invite that specific person. Never
call it to "test", to check whether an address is valid, or on your own initiative while
exploring. Confirm the recipient address with the user first, then pass confirm=true.

Calendar invites are a Business plan feature and must also be enabled on the event
itself. Each send counts against the team's monthly email allowance.

- `event_id` (string, required) - The event public id, e.g. "evt_ab12cd34ef56".
- `email` (string, required) - The email address to send the calendar invitation to. Confirm this with the user before calling.
- `confirm` (boolean, required) - Must be true. Set this only after the user has explicitly confirmed that a real invitation email should be sent to this address.

### Account

#### `get-analytics` (read only)

Get engagement analytics: how many people viewed an event or calendar page and how many added it to their own calendar, broken down over time and by referrer, country, device, browser, calendar provider and UTM parameter, plus top events, best time to post, lead time and the equivalent figures for the preceding period. Scope with calendar_id or event_id, or omit both for the whole team. Defaults to the last 30 days. Teams without a paid subscription receive headline totals only.

- `calendar_id` (string) - Scope to a single calendar (cal_xxx).
- `event_id` (string) - Scope to a single event (evt_xxx).
- `start_date` (string) - Start of the reporting window (YYYY-MM-DD). Defaults to 30 days ago.
- `end_date` (string) - End of the reporting window (YYYY-MM-DD). Defaults to today.
- `timezone` (string) - IANA timezone used to bucket the results. Defaults to the team timezone.
- `unique_only` (boolean) - Count each visitor once instead of every interaction. Changes what the headline click and add figures mean, which is reported back as is_unique.
- `include_series` (boolean) - Roll every instance of a recurring series up into its parent event. Off by default, matching the dashboard.
- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.

#### `get-team-usage` (read only)

Get the team's plan, current usage and limits: events, calendars, subscribers, email sends, invites and more. Use this to explain why an action was blocked, to check headroom before creating things in bulk, or to answer "what plan am I on" and "how close am I to my limit".

- `team_id` (string) - Team public id (team_xxx). Defaults to the user's current team.
