WhatsApp MCP
Eigener MCP-Server unter mcp-whatsapp fuer die WhatsApp Business Cloud API. Gebaut 2026-05-13 als zweite Branchen-Komponente fuer das Salon-Vertical (zusammen mit calcom).
Pattern: Outbound-Tools rufen Meta Graph API. Eingehende Nachrichten kommen ueber Webhook → werden im integrierten POST /webhook-Endpoint angenommen + in lokaler SQLite gespeichert. Read-Tools lesen aus SQLite. Das Brain (Claude Code Session oder spaeter Lambda) pollt list_recent_messages(only_unprocessed=True) und orchestriert.
Was kann der MCP
15 Tools — 13 dedizierte + 2 Raw-Escapes:
| Kategorie | Tools |
|---|---|
| Outbound | send_text, send_template, send_image, send_document, send_interactive_buttons, send_interactive_list |
| Status | mark_read (WhatsApp-Lesebestaetigung), mark_processed (lokal als verarbeitet) |
| Inbox | list_recent_messages, get_message |
| Phone + Templates | get_phone_info, list_phone_numbers, list_templates |
| Raw-Escapes | raw_get, raw_post |
Endpoints des Servers
| Endpoint | Zweck |
|---|---|
POST /mcp | MCP-Tool-Aufrufe (Standard streamable-http) |
GET /webhook | Meta-Verify-Handshake (vergleicht hub.verify_token mit WHATSAPP_WEBHOOK_VERIFY_TOKEN) |
POST /webhook | Eingehende Messages → SQLite-Inbox |
GET /health | Liveness |
Setup
1. Meta-App + WhatsApp Cloud API einrichten
- https://developers.facebook.com/ → My Apps → Create App
- Use case: Other → Type: Business → Name
agentic-friseur-bot, Emailhello@marvinkuehlmann.com - Im App-Dashboard → linke Sidebar → WhatsApp → Set up. Bei Frage nach Business-Account: „Create a new business account” → Name
Agentic Ventures - API Setup-Tab oeffnet — drei Werte kopieren:
- Phone number ID →
WHATSAPP_PHONE_NUMBER_ID - WhatsApp Business Account ID →
WHATSAPP_BUSINESS_ACCOUNT_ID - Temporary access token →
WHATSAPP_ACCESS_TOKEN(24h gueltig)
- Phone number ID →
- Eigene Handynummer im „To”-Dropdown verifizieren — Meta schickt 6-stelligen Code via WhatsApp.
Fuer Permanent-Token: Meta Business Settings → System Users → User anlegen → Permanent-Token mit Scopes whatsapp_business_messaging + whatsapp_business_management. Sonst muss der temporaere alle 24h ersetzt werden.
2. Env
cp ~/source/mcps/mcp-whatsapp/.env.local.example ~/source/mcps/mcp-whatsapp/.env.local
# Werte eintragen3. Install + Server starten
uv tool install --force --editable ~/source/mcps/mcp-whatsapp
mcp-whatsapp # laeuft auf 127.0.0.1:87714. In Claude Code registrieren
claude mcp add whatsapp --transport http http://127.0.0.1:8771/mcp5. Webhook bei Meta einrichten (fuer Inbound)
Server muss aussen erreichbar sein — ngrok-Tunnel:
ngrok http 8771
# kopiere die https-URL: https://<random>.ngrok-free.appIn Meta-App → WhatsApp → Configuration → Webhook:
- Callback URL:
https://<random>.ngrok-free.app/webhook - Verify token: identisch mit
WHATSAPP_WEBHOOK_VERIFY_TOKENin.env.local - Subscribe to fields:
messages,message_status
Meta sendet GET → unser Server antwortet mit hub.challenge wenn Token matcht → Webhook ist live. Ab jetzt kommen Inbound-Messages in die SQLite.
6. Smoke
# Nach Claude-Code-Reload:
mcp__whatsapp__get_phone_info()
# → {verified_name, display_phone_number, quality_rating, status}
mcp__whatsapp__send_text(to="491701234567", text="Hallo aus dem mcp-whatsapp!")
# Sollte auf dem verifizierten Handy ankommen.
# Wenn du auf die Test-Nummer zurueckschreibst (innerhalb 24h Service-Window):
mcp__whatsapp__list_recent_messages(only_unprocessed=True, limit=5)
# Sollte die Nachricht zeigen.Quirks
- Test-Phone-Number-Mode: maximal 5 verifizierte Empfaenger im Test-Modus. Empfaenger werden im API-Setup-Tab eingetragen.
- 24h-Service-Window: Ausserhalb darf nur ein approved Template gesendet werden, kein Freitext. Innerhalb: Freitext OK. Templates fuer Bestaetigung + Erinnerung muessen vorher bei Meta eingereicht werden (Approval 1-3 Tage).
- Webhook-Verify-Token: beim Meta-Setup EXAKT der Wert in
.env.local. Sonst „verification failed”. - Inbound-Webhook braucht 2xx innerhalb ~20s — sonst retried Meta. SQLite-Insert ist schnell genug, daher kein async Queue.
- Permanent-Token-Scopes:
whatsapp_business_messaging(Senden) +whatsapp_business_management(Templates, Phone-Info). Ohne zweites Scope schlagenlist_templatesundlist_phone_numbersfehl. - Interactive-Replies kommen als type=
interactivein Webhook-Payload zurueck —interactive.button_reply.idbzw.interactive.list_reply.identhaelt die geklickte Auswahl. Unser_store_incomingextrahiert die title alstext_bodyfuer einfache Bot-Logik. - Webhook-Payload-Parsing-Robustheit: Wenn Meta einen Status-Update-Event statt einer Message schickt (z.B. Delivery-Receipt), kommt
value.messageseinfach leer zurueck — wir ignorieren das stillschweigend. - FastMCP DNS-Rebinding-Schutz blockt Tunnel-Host (421 Invalid Host header). Default-Allowlist akzeptiert nur
127.0.0.1:*/localhost:*/[::1]:*. Cloudflared reicht den Public-Hostmcp-whatsapp.agenticventures.dedurch → 421. Fix: Env-VarMCP_ALLOWED_HOSTS=mcp-whatsapp.agenticventures.deam Container (ininfra/lib/mcp-whatsapp-hosted-stack.tsgesetzt). Server liest sie inmain()und erweitertmcp.settings.transport_security.allowed_hosts+allowed_origins. Erstmals 2026-05-15 nach Deploy aufgeschlagen, gefixt durch CDK-Redeploy. Voller Pattern in quirks—gotchas.
Bot-Workflow (Friseur)
1. User schickt WhatsApp "Mittwoch 14 Uhr Damen-Schnitt frei?"
→ Meta Webhook → POST /webhook → SQLite-Insert
2. Brain (Claude Code / Lambda) pollt list_recent_messages(only_unprocessed=True)
3. Brain ruft mcp__whatsapp__mark_read(wamid) // blauer Haken
4. Brain ruft mcp__calcom__list_slots(event_type_id=<damen-schnitt>, start, end)
5. Brain formuliert Antwort und ruft mcp__whatsapp__send_interactive_buttons(
to=from_phone,
body_text="Diese Slots sind frei: 13:30, 14:00, 14:30",
buttons=[{id:"book-1330", title:"13:30"}, {id:"book-1400", title:"14:00"}, ...]
)
6. User klickt Button → kommt als type=interactive zurueck mit button_reply.id="book-1400"
7. Brain ruft mcp__calcom__create_booking(...)
8. Brain ruft mcp__whatsapp__send_text("Termin gebucht. Bis Mittwoch 14:00!")
9. Brain ruft mcp__whatsapp__mark_processed(wamid)
Production-Hosting (live seit 2026-05-14)
Endpoint: https://mcp-whatsapp.agenticventures.de (MCP /mcp, Webhook /webhook, Health /health)
| Komponente | Wert |
|---|---|
| AWS-Account | av-production (425924867359) |
| Region | eu-central-1 |
| ECS-Cluster | default |
| ECS-Service | McpWhatsappHosted-ServiceD69D759B-HuB3n6EMF0qS |
| ECR-Repo | 425924867359.dkr.ecr.eu-central-1.amazonaws.com/mcp-whatsapp-hosted |
| TaskDef | 0.25 vCPU / 0.5 GB, 2 Container (whatsapp + cloudflared) |
| Cloudflare-Tunnel | a3af8b46-0236-4dd4-93da-e83f66c56f1b |
| DNS-Record | mcp-whatsapp.agenticventures.de → <tunnel-id>.cfargotunnel.com proxied=true |
| Secrets Manager | mcp-whatsapp-hosted/whatsapp-config (JSON) + mcp-whatsapp-hosted/cloudflared-token |
| Stack | CDK in ~/source/mcps/mcp-whatsapp/infra/ (McpWhatsappHosted) |
Pattern: mcp-hosting-fargate-tunnel. SQLite-Inbox ist ephemeral (geht bei Container-Restart verloren) — fuer Multi-Tenant-Live spaeter DynamoDB.
Production-Smoke (2026-05-14)
Voller End-to-End-Round-Trip mit Friseur-Account (889608426871476 / Phone 1170948102758111 / App 988192143605811):
Webhook msg from=4915128945607 type=text preview='Hi'→ SQLite-Insertsend_text→ Meta wamid →status=sentWebhook-Confirm- Service-Window-Regel bestaetigt: Outbound nur innerhalb 24h nachdem User zuerst geschrieben hat (sonst
131047 Re-engagement message)
Related
- mcp-whatsapp — Code im Source-Repo
- calcom — Companion-MCP fuer Cal.com-Buchungen
- _index — Use-Case (Phase 2 + 3)
- SKILL — Bau-Pattern
- WhatsApp Cloud API Docs
- Webhook Reference