Cal.com MCP

Eigener MCP-Server unter mcp-calcom fuer die Cal.com API v2. Gebaut 2026-05-13 als erste Branchen-Komponente fuer das Salon-Vertical (Use-Case 1: WhatsApp-Buchungs-Bot Friseur im Sueden → _index).

Wiederverwendbar fuer alle Termin-basierten Dienstleister — Friseur, Kosmetik, Massage, Coach.

Was kann der MCP

10 Tools (8 dedizierte + 2 Raw-Escapes):

ToolZweck
whoamiAuth-Check via GET /v2/me
list_event_typesServices eines Users/Teams (Schnitt 30min, Faerben 90min, etc.)
list_slotsVerfuegbare Slots fuer einen Event-Type in Datums-Range
create_bookingNeuer Termin mit Attendee (Email ODER Telefon Pflicht)
get_bookingEinzelner Termin per UID
list_bookingsTermine filtern (Status, Email, Telefon, Event-Type, Datum)
cancel_bookingStorno per POST /v2/bookings/{uid}/cancel
reschedule_bookingVerschieben per POST /v2/bookings/{uid}/reschedule
raw_get, raw_postEscape-Hatch fuer Teams/Orgs/Schedules/Webhooks-Endpoints

Setup

1. Cal.com-Account anlegen

Bei https://cal.com kostenlosen Account anlegen. Free-Tier reicht fuer API + Webhooks. Bei Multi-Staff-Salons spaeter ggf. Teams-Plan ($12/User/Monat) — die API funktioniert in jeder Tier-Variante.

Alternativ self-hosted Cal.diy (MIT-Lizenz) — dann CALCOM_BASE_URL setzen auf eigene Instanz.

2. API-Key generieren

Settings → Developer → API Keys → Create new. Token-Format: cal_live_<random>.

3. Env

CALCOM_API_KEY=cal_live_...
# Optional fuer self-hosted:
# CALCOM_BASE_URL=https://cal.example.com/v2

Token NACKT eintragen — Bearer-Prefix wird im Code automatisch ergaenzt.

4. Install

uv tool install --force --editable ~/source/mcps/mcp-calcom

Default-Transport: streamable-http auf 127.0.0.1:8770. Stdio: MCP_TRANSPORT=stdio.

5. Claude Code registrieren

claude mcp add calcom --transport http http://127.0.0.1:8770/mcp

Claude Code neu starten → Tools tauchen als mcp__calcom__* auf.

6. Smoke

# in Claude Code
mcp__calcom__whoami()
# → {"_status": 200, "data": {"username": "...", ...}}

Quirks

  • cal-api-version ist Pflicht und unterscheidet sich pro Endpoint — Cal.com versioniert pro Endpoint statt global. Der MCP setzt:

    • /me, /event-types2024-08-13
    • /slots2024-09-04
    • /bookings*2026-02-25

    Wenn Cal.com einen neuen Endpoint released: erwartete Version pruefen, ggf. _API_VERSIONS in server.py ergaenzen.

  • Booking-Cancel ist POST, nicht DELETE. v1 war DELETE, v2 ist POST /v2/bookings/{uid}/cancel mit optionalem cancellationReason-Body.

  • Reschedule hat zwei Varianten — wir nutzen POST /v2/bookings/{uid}/reschedule (Bot kennt neuen Slot bereits, direkte Umbuchung). Cal.com hat zusaetzlich /request-reschedule das einen Reschedule-Link an den Attendee schickt — fuer unseren Bot-Workflow nicht noetig.

  • Attendee braucht email ODER phoneNumber. Bei reinen WhatsApp-Bot-Buchungen reicht die Telefonnummer im internationalen Format (+49...). Wenn am Event-Type SMS-Reminder aktiv sind, ist phoneNumber zwingend.

  • Slots-Response-Shape ist ein Datums-Dict, kein flaches Array. {"slots": {"2026-05-20": ["09:00:00", "10:00:00"]}} — der Bot muss das zu lokaler Zeit ruecktransformieren bevor er antwortet.

  • Webhooks separat einrichten — keine API-Funktion zum Setzen, das geht im Cal.com-Dashboard unter Settings → Webhooks. HMAC-signiert, Events: BOOKING_CREATED / BOOKING_CANCELLED / BOOKING_RESCHEDULED. Pro Salon eine eigene Webhook-URL die zum Agent-Lambda zeigt.

Hosting fuer Kunden

Lokal-Stack ist Marvins Macbook. Fuer Produktiv-Salon-Setup spaeter:

  • AWS Fargate + cloudflared-Tunnel via mcp-hosting-fargate-tunnel
  • Auth pro Kunde via Scalekit-OAuth (Pattern: mcp-vf-hosted als Vorlage)
  • CALCOM_API_KEY als Tenant-spezifisches Secret in AWS Secrets Manager
  • Alternativ: Lambda + API Gateway wenn nur ein Bot-Agent ruft (Mono-Tenant)

Verwendung im Friseur-Bot

Workflow (whatsapp → Agent → calcom-MCP):

  1. User schreibt “Mittwoch 14 Uhr Schnitt frei?”
  2. Bot ruft list_event_types(username="friseur-im-sueden") (oder einmal beim Boot gecached)
  3. Bot ruft list_slots(event_type_id=<schnitt>, start="2026-05-20T00:00:00Z", end="2026-05-21T00:00:00Z", time_zone="Europe/Berlin")
  4. Bot antwortet mit den verfuegbaren Slots
  5. User: “Ja, 14:00 passt”
  6. Bot ruft create_booking(event_type_id=<schnitt>, start="2026-05-20T12:00:00Z", attendee_name="<aus Profil>", attendee_phone="+49170...")
  7. Bot schickt Bestaetigung via WhatsApp-Template