Skip to content

Quickstart

Connect to the WhatsDo MCP server and make your first call.

Server address

WhatsDo is a hosted MCP server. Clients that speak MCP over Streamable HTTP can use it. The server is stateless: each request stands on its own.

https://app.whatsdo.com/mcp/

Keep the trailing slash. /mcp without it answers with a redirect, and not every client follows a redirect on POST.

Sign in

  • Claude, Claude Code and ChatGPT: add the server address. The client opens a WhatsDo sign-in. No key to copy.
  • Cursor, Gemini CLI and Codex: sign-in or an API key. See Connect your agent.
  • Scripts, servers and VS Code: get an API key, copy it once and send it as Authorization: Bearer <key>. Tick bookings:write if your agent books. Or use the setup page: it creates a key that can book.

Bookings are made as the signed-in WhatsDo account. Name and phone come from that account’s profile.

First call

A booking request takes four calls, in this order.

  1. search with the person’s words, a San Francisco location, bookable_only: true and a limit of 3 to 5. Keep the id and booking_source of the place they pick.
  2. get_booking_requirements with that id. Ask the person only for fields that are missing.
  3. book after the person said yes to the place, date, time and party size. Booking is in Beta.
  4. my_bookings to read the status. Nothing is pushed to you.

Try the first call with cURL:

bash
curl https://app.whatsdo.com/mcp/ \
  -H "Authorization: Bearer $WHATSDO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"search",
                 "arguments":{"query":"asian dinner highly rated",
                              "location":"San Francisco, CA",
                              "bookable_only":true,"limit":3}}}'

The reply can come as plain JSON or as an event stream. Read result.structuredContent when it is there, otherwise parse the JSON text in result.content[0].text.

Full example in Python

One file that runs the whole sequence: initialize, search, then, if you pass a time, get_booking_requirements, book and polling my_bookings until the status settles. It handles both reply formats and prints only safe booking fields. Set WHATSDO_API_KEY first.

python
# Python 3.11+, pip install httpx
# Search:          python whatsdo_flow.py
# Search and book: python whatsdo_flow.py 2026-10-09T19:00:00-07:00   (writes a real booking)
import json, os, sys, time
import httpx

MCP_URL = "https://app.whatsdo.com/mcp/"
HEADERS = {"Authorization": f"Bearer {os.environ['WHATSDO_API_KEY']}",
           "Content-Type": "application/json",
           "Accept": "application/json, text/event-stream"}
SAFE = ("place_name", "status", "date_time", "party_size", "confirmation_number", "message")
http = httpx.Client(timeout=120.0)
next_id = 0


def rpc(method, params=None):
    global next_id
    next_id += 1
    resp = http.post(MCP_URL, headers=HEADERS,
                     json={"jsonrpc": "2.0", "id": next_id, "method": method, "params": params or {}})
    resp.raise_for_status()
    if resp.headers.get("content-type", "").startswith("text/event-stream"):
        # The reply is the data: event whose id matches the request.
        events = [json.loads(l[5:]) for l in resp.text.splitlines() if l.startswith("data:")]
        msg = next((e for e in events if e.get("id") == next_id), events[-1])
    else:
        msg = resp.json()
    if "error" in msg:
        raise RuntimeError(msg["error"])
    return msg["result"]


def tool(name, arguments):
    result = rpc("tools/call", {"name": name, "arguments": arguments})
    texts = [c.get("text", "") for c in result.get("content", []) if c.get("type") == "text"]
    if result.get("isError"):
        raise RuntimeError(" ".join(texts))
    if isinstance(result.get("structuredContent"), dict):
        return result["structuredContent"]
    return json.loads(texts[0])


init = rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                          "clientInfo": {"name": "my-agent", "version": "0.1"}})
HEADERS["MCP-Protocol-Version"] = init["protocolVersion"]
http.post(MCP_URL, headers=HEADERS, json={"jsonrpc": "2.0", "method": "notifications/initialized"})

found = tool("search", {"query": "asian dinner highly rated", "location": "San Francisco, CA",
                        "bookable_only": True, "limit": 3})
results = found.get("results", [])
if not results:
    sys.exit("No matches. WhatsDo search covers San Francisco; new cities are available on request.")
for r in results:
    print(r["name"], r.get("rating"), r.get("price"), r["id"])
if len(sys.argv) < 2:
    sys.exit(0)

# Only after the person agreed to the place, date, time and party size.
place = results[0]
needs = tool("get_booking_requirements", {"item_id": place["id"]})
if not needs.get("available"):
    sys.exit(f"{place['name']} can't be booked here. Phone: {place.get('phone')}")
booking = tool("book", {"item_name": place["name"], "item_id": place["id"], "date_time": sys.argv[1],
                        "party_size": 2, "source": place["booking_source"]})
rid, status = booking["reservation_id"], booking["status"]

deadline = time.monotonic() + 300
while status == "pending" and time.monotonic() < deadline:
    time.sleep(30)
    rows = tool("my_bookings", {"limit": 10}).get("bookings", [])
    row = next((b for b in rows if b.get("reservation_id") == rid), None)
    if row:
        booking, status = row, row["status"]

# Never print guest email, phone, user id or session links.
print({k: booking.get(k) for k in SAFE})

book writes a real booking as the key’s account. Run the booking path only for a place, date and time the person agreed to.

Next