Skip to main content
Glama

booking_chest — Cal.com MCP Server

A Model Context Protocol server for Cal.com open-source scheduling. Exposes ~70 MCP tools across 10 resource modules — create schedules, manage event types, book meetings, integrate calendars, configure webhooks, and manage teams — all controllable directly from Claude or any MCP-compatible client.

Features

  • ~70 tools covering the Cal.com API v2

  • Clean two-layer architecture: pure calcom_client/ package + thin MCP wrapper

  • Static Bearer token auth (no refresh needed)

  • Typed exception hierarchy (APIError, NotFoundError, ValidationError, AuthenticationError)

  • API quirks baked in (correct parameter names, required fields, response envelope unwrapping)

Related MCP server: carly-cli

Resource Modules (10)

Module

Tools

Description

me

2

Profile get/update

timezones

1

List all 7,072 city/timezone entries

api_keys

1

Rotate API key (warning: invalidates immediately)

schedules

5

Availability schedule CRUD

event_types

9

Event type CRUD + per-type webhooks + private links (Enterprise)

slots

3

Available slots, reserve, delete reservation

bookings

6

List, get, create, cancel, reschedule, mark no-show

calendars

9

Connect, busy times, destination, selected, ICS feeds

webhooks

5

User-level webhook CRUD (21 trigger types)

teams

19

Team CRUD + memberships + event types + event-type webhooks + invite

Installation

git clone https://github.com/dsddet/booking_chest
cd booking_chest
pip install -e ".[mcp]"

Or with uv (recommended):

uv pip install -e ".[mcp]"

Environment Variables

Variable

Required

Description

CALCOM_API_KEY

Yes

Cal.com API key (starts with cal_)

CALCOM_BASE_URL

No

Cal.com instance URL (default: https://api.cal.com)

Create a .env file:

CALCOM_API_KEY=cal_your_api_key_here
CALCOM_BASE_URL=https://api.cal.com

For self-hosted Cal.com instances, set CALCOM_BASE_URL to your instance URL (e.g., http://192.168.0.214).

Getting an API Key

  1. Log into Cal.com Settings → Developer → API Keys

  2. Click "Generate API Key"

  3. Copy the key (starts with cal_) — it cannot be retrieved later

Claude Desktop Configuration

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "booking_chest": {
      "command": "uv",
      "args": [
        "--directory", "/path/to/booking_chest",
        "run", "--extra", "mcp", "mcp_server.py"
      ],
      "env": {
        "CALCOM_API_KEY": "cal_your_api_key_here",
        "CALCOM_BASE_URL": "https://api.cal.com"
      }
    }
  }
}

Replace /path/to/booking_chest with the absolute path to your cloned directory.

Usage Examples

Once connected, you can ask Claude:

  • "Create a 30-minute consultation event type"

  • "What slots are available this week for event type 1?"

  • "Book a meeting with john@example.com on March 5th at 2pm Eastern"

  • "List all my upcoming bookings"

  • "Cancel booking 42 — attendee has a conflict"

  • "Connect my Google Calendar and set it as the destination"

  • "Create a webhook to notify Zapier when bookings are created or cancelled"

  • "List all team members and their roles"

Architecture

calcom_client/          # Pure API client (no MCP dependency)
├── http.py             # httpx + Bearer token auth + response envelope unwrap
├── exceptions.py       # APIError, NotFoundError, ValidationError, AuthenticationError
├── me.py               # Profile get/update
├── timezones.py        # List all timezones
├── api_keys.py         # API key rotation
├── schedules.py        # Availability schedule CRUD
├── event_types.py      # Event type CRUD + webhooks + private links
├── slots.py            # Available slots, reserve, delete
├── bookings.py         # Booking CRUD (cancel, reschedule, no-show)
├── calendars.py        # Calendar integrations (Google, Office 365, Apple)
├── webhooks.py         # User-level webhook CRUD
├── teams.py            # Team CRUD + memberships + event types + invite
└── __init__.py         # CalcomClient facade composing all modules
mcp_server.py           # ~70 @mcp.tool() functions

Cal.com API Quirks (baked into client code)

  • API version: Use cal-api-version: 2024-06-14 (broadest support — 2024-08-13 misses some endpoints)

  • Response envelope: All responses wrapped in {"status": "success", "data": ...} — unwrapped automatically

  • Slots params: Use start/end (NOT startTime/endTime — those cause 400 errors)

  • Webhook triggers: Use triggers field (NOT eventTriggers — old doc error)

  • Calendar connect: Use google, office365, apple (NOT google-calendar, office365calendar)

  • calendarsToLoad is REQUIRED for busy-times endpoint (400 error without it)

  • isDefault is REQUIRED when creating schedules (400 error without it)

  • slotId is REQUIRED when reserving slots

  • API key refresh: Old key invalidated immediately — update env before next request

  • Team creation needs Stripe — use existing teams or configure Stripe billing

  • metadata PATCH: Returns null in response but IS saved (verify via team memberships)

  • No team-level webhooks — webhooks are always scoped to specific event types

License

MIT

Available Tools

68 tools
add_selected_calendarA

Mark a calendar for conflict detection (POST only, no GET).

To see selected status, use list_calendars() and check connectedCalendars[].calendars[].isSelected.

Inputs: external_id (required), integration (required), credential_id (required). Returns: result

cancel_bookingC

Cancel a booking.

Inputs: booking_id (required), cancellation_reason. Returns: result

check_calendar_connectionA

Check if a calendar type is connected.

Inputs: calendar_type (required). Returns: result

check_ics_feedC

Check ICS calendar feed subscription status.

Inputs: no named parameters. Returns: result

create_bookingC

Create a booking (schedule a meeting).

Inputs: start (required), attendee_name (required), event_type_id (required), attendee_time_zone (required), +5 more. Returns: result

create_event_typeC

Create an event type (meeting type / booking page).

Inputs: slug (required), title (required), length_in_minutes (required), price, +8 more. Returns: result

create_event_type_webhookC

Create a webhook for a specific event type.

Inputs: triggers (required), event_type_id (required), subscriber_url (required), active, +1 more. Returns: result

create_scheduleC

Create an availability schedule.

Inputs: name (required), time_zone (required), is_default (required), schedule. Returns: result

create_teamA

Create a new team.

WARNING: Requires Stripe billing configuration. Will fail with InternalServerError if Stripe is not set up. For most use cases, use existing teams.

Inputs: name (required), slug (required). Returns: result

create_team_event_typeB

Create a team event type.

scheduling_type is REQUIRED for team event types (API returns validation error without it).

Inputs: slug (required), title (required), team_id (required), scheduling_type (required), +2 more. Returns: result

create_team_event_type_webhookC

Create a webhook for a team event type.

Inputs: team_id (required), triggers (required), event_type_id (required), subscriber_url (required), +1 more. Returns: result

create_team_inviteC

Generate a team join invite link.

Returns a token and inviteLink. Share the inviteLink with users to let them join the team.

Inputs: team_id (required). Returns: result

create_team_membershipC

Add a user to a team.

Inputs: team_id (required), user_id (required), role, accepted. Returns: result

create_webhookA

Create a user-level webhook for real-time event notifications.

IMPORTANT: Use triggers (NOT eventTriggers — that was an old API doc error).

Inputs: triggers (required), subscriber_url (required), active, secret. Returns: result

delete_event_typeC

Delete an event type.

Inputs: event_type_id (required). Returns: result

delete_event_type_webhookC

Delete an event type webhook.

Inputs: webhook_id (required), event_type_id (required). Returns: result

delete_scheduleC

Delete a schedule.

Inputs: schedule_id (required). Returns: result

delete_slot_reservationC

Cancel a slot reservation.

Inputs: uid (required). Returns: result

delete_teamC

Delete a team.

Inputs: team_id (required). Returns: result

delete_team_event_typeC

Delete a team event type.

Inputs: team_id (required), event_type_id (required). Returns: result

delete_team_event_type_webhookC

Delete a team event type webhook.

Inputs: team_id (required), webhook_id (required), event_type_id (required). Returns: result

delete_team_membershipB

Remove a member from a team.

Inputs: team_id (required), membership_id (required). Returns: result

delete_webhookC

Delete a webhook.

Inputs: webhook_id (required). Returns: result

get_available_slotsA

Get available booking slots for an event type.

IMPORTANT: Use start/end (NOT startTime/endTime — those return a 400 error).

Inputs: end (required), start (required), duration, time_zone, +4 more. Returns: result

get_bookingB

Get a booking by ID.

Inputs: booking_id (required). Returns: result

get_calendar_busy_timesA

Get busy time ranges across connected calendars.

calendars_to_load is REQUIRED (API returns 400 without it).

Inputs: date_to (required), date_from (required), time_zone (required), calendars_to_load (required). Returns: result

get_calendar_connect_urlB

Get OAuth authorization URL to connect a calendar.

Inputs: calendar_type (required), redirect_url. Returns: result

get_event_typeB

Get an event type by ID.

Inputs: event_type_id (required). Returns: result

get_my_profileA

Get current user profile (name, email, timezone, bio, defaultScheduleId, etc.).

Inputs: no named parameters. Returns: result

get_scheduleB

Get a schedule by ID.

Inputs: schedule_id (required). Returns: result

get_teamB

Get team details by ID.

Inputs: team_id (required). Returns: result

get_team_event_typeA

Get a team event type by ID.

Inputs: team_id (required), event_type_id (required). Returns: result

get_team_event_type_webhookB

Get a webhook for a team event type.

Inputs: team_id (required), webhook_id (required), event_type_id (required). Returns: result

get_team_membershipB

Get a team membership by ID.

Inputs: team_id (required), membership_id (required). Returns: result

get_webhookB

Get a webhook by ID.

Inputs: webhook_id (required). Returns: result

list_bookingsC

List bookings with optional filters.

Inputs: skip, take, status, attendee_email. Returns: result

list_calendarsA

List connected calendars and destination calendar.

Returns connectedCalendars (list of connected accounts) and destinationCalendar (where new bookings are created). Check isSelected on each calendar entry to see which are used for conflict detection.

Inputs: no named parameters. Returns: result

list_event_typesA

List all event types (meeting types).

Inputs: no named parameters. Returns: result

list_event_type_webhooksA

List webhooks for a specific event type.

Inputs: event_type_id (required). Returns: result

list_schedulesB

List all availability schedules.

Inputs: no named parameters. Returns: result

list_team_bookingsC

List all bookings for a team's event types.

Inputs: team_id (required). Returns: result

list_team_event_typesB

List event types for a team.

Inputs: team_id (required). Returns: result

list_team_event_type_webhooksA

List webhooks for a team event type.

Note: There are NO team-level webhooks — webhooks are always scoped to event types.

Inputs: team_id (required), event_type_id (required). Returns: result

list_team_membersB

List all members of a team with their roles.

Inputs: team_id (required). Returns: result

list_teamsA

List all teams the current user belongs to.

Inputs: no named parameters. Returns: result

list_team_schedulesB

List availability schedules for team members.

Inputs: team_id (required). Returns: result

list_timezonesA

List all 7,072 available city/timezone entries (no pagination — returns all at once).

Inputs: no named parameters. Returns: result

list_webhooksA

List all user-level webhooks.

Inputs: no named parameters. Returns: result

mark_booking_no_showA

Mark one or more attendees as no-shows for a booking.

Inputs: attendees (required), booking_id (required). Returns: result

refresh_api_keyA

Rotate the current API key.

WARNING: The old key is invalidated IMMEDIATELY. The response contains the new key. You must update CALCOM_API_KEY in your environment before making any further requests.

Inputs: no named parameters. Returns: result

remove_selected_calendarA

Remove a calendar from conflict detection.

Inputs: external_id (required), integration (required), credential_id (required). Returns: result

reschedule_bookingB

Reschedule a booking to a new time slot.

Inputs: start (required), booking_id (required), rescheduling_reason. Returns: result

reserve_slotA

Temporarily reserve a slot before creating a booking.

slotId is REQUIRED (use the slot ID from get_available_slots response). Reservation expires after a short window — create the booking before it lapses.

Inputs: start (required), slot_id (required), event_type_id (required), attendee_time_zone (required). Returns: result

save_ics_feedC

Subscribe to external ICS calendar feeds.

Inputs: urls (required), read_only. Returns: result

set_destination_calendarA

Set the destination calendar where new bookings are created (PUT only, no GET).

To retrieve current destination, use list_calendars() and check destinationCalendar field.

Inputs: external_id (required), integration (required), credential_id (required). Returns: result

update_event_typeB

Update an event type (PATCH — partial update).

Inputs: fields (required), event_type_id (required). Returns: result

update_event_type_webhookC

Update an event type webhook.

Inputs: fields (required), webhook_id (required), event_type_id (required). Returns: result

update_my_profileA

Update current user profile (PATCH — send only fields to change).

Inputs: bio, name, email, locale, +6 more. Returns: result

update_scheduleB

Update a schedule (PATCH — send only fields to change).

Inputs: schedule_id (required), name, schedule, time_zone, +1 more. Returns: result

update_teamB

Update team details (PATCH — partial update).

Inputs: fields (required), team_id (required). Returns: result

update_team_event_typeA

Update a team event type (PATCH — partial update).

Inputs: fields (required), team_id (required), event_type_id (required). Returns: result

update_team_event_type_webhookC

Update a team event type webhook.

Inputs: fields (required), team_id (required), webhook_id (required), event_type_id (required). Returns: result

update_team_membershipA

Update a team membership (PATCH — role, accepted, disableImpersonation).

Inputs: fields (required), team_id (required), membership_id (required). Returns: result

update_webhookA

Update a webhook (PATCH — partial update).

Inputs: fields (required), webhook_id (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3/5.0

    Scored across 68 tools

    Disambiguation4/5

    Most tools target a distinct resource and action, and the descriptions clarify potential overlaps like reserve_slot vs create_booking or the various webhook scopes. However, with 68 tools, there are enough similar list/get/create/update/delete patterns (especially around webhooks and calendars) that an agent may occasionally need to read descriptions carefully.

    Naming Consistency4/5

    The toolset predominantly follows a get/list/create/update/delete + resource pattern, with slight deviations like refresh_api_key, set_destination_calendar, check_ics_feed, and add_selected_calendar. These deviations are still readable and mostly action-first, so the naming is fairly consistent overall.

    Tool Count1/5

    68 tools is an extremely large surface for a single MCP server, well beyond the 50+ threshold for a low score. While the domain is broad, the sheer number will likely overwhelm agents and increase the chance of selecting an irrelevant tool.

    Completeness4/5

    The toolset covers most core booking workflows: schedules, event types, availability, bookings, calendars, webhooks, and team management. Minor gaps exist, such as no get_event_type_webhook for non-team event types, no delete/removal for ICS feeds, and limited team booking detail operations, but agents can generally complete end-to-end tasks.

    Related MCP Connectors

    Related MCP Servers