AI Automation
Claude + MCP Google Calendar: Auto-Book Appointments with AI
Every appointment-based local business loses revenue the same way: a potential client reaches out to book, you're with a customer or asleep, they don't hear back fast enough, and they book somewhere else. Phone tag. DM tag. "I sent you a message on Instagram three days ago." Sound familiar?
The data backs this up. According to booking platform research, nearly 30% of appointment requests go unbooked because businesses can't respond fast enough. For a salon doing $8,000/month, that's potentially $2,400 left on the table every month.
The solution is an AI booking agent: one that checks your Google Calendar for availability, understands natural language requests ("Can I get in Thursday afternoon?"), books the slot, and sends a confirmation — all without you lifting a finger. This guide shows you exactly how to build it using Claude and the Google Calendar MCP server.
The Problem With Current Booking Tools
Before we get into the build, let's be honest about why existing tools fall short for many local businesses:
Phone/DM booking requires you to be available 24/7. You're not. Revenue leaks.
Basic online booking software (Calendly, Square Appointments) solves the scheduling problem but forces clients into a rigid interface. They can't say "I want whoever is available Thursday afternoon for a cut and color." They have to click through dropdowns and select exact times themselves — and abandon if it's confusing.
AI + Google Calendar via MCP handles the natural language layer. Clients describe what they want in plain English. The AI interprets the request, checks availability, and handles the booking — like a receptionist, but always on.
Prerequisites
Before you start, you'll need:
- Claude API key — get one at console.anthropic.com
- Google Cloud project with Calendar API enabled
- OAuth 2.0 credentials (client ID + secret) from Google Cloud Console
- Python 3.11+ installed on your machine
- pip packages:
anthropic,google-auth-oauthlib,google-api-python-client
Estimated setup time: 45–90 minutes the first time. Subsequent setups for other businesses: 15 minutes.
Step 1: Enable Google Calendar API and Get Credentials
- Go to console.cloud.google.com and create a new project (e.g., "Salon Booking Bot")
- In the sidebar, go to APIs & Services → Library
- Search for "Google Calendar API" and click Enable
- Go to APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Choose "Desktop application" as the application type
- Download the credentials JSON file — save it as
credentials.jsonin your project folder - In APIs & Services → OAuth Consent Screen, add your email as a test user (required while in development)
You'll also need to authorize the app the first time you run it — Google will open a browser window asking you to grant Calendar access. After that, tokens are saved locally and reuse is automatic.
Step 2: Install and Configure the Google Calendar MCP Server
Install the MCP server globally via npm:
npm install -g @modelcontextprotocol/server-google-calendar
Then add it to your Claude Desktop config at
~/Library/Application Support/Claude/claude_desktop_config.json:{
"mcpServers": {
"google-calendar": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-google-calendar"],
"env": {
"GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
"GOOGLE_CLIENT_SECRET": "GOCSPX-your-secret"
}
}
}
}
Restart Claude Desktop. You should now be able to ask Claude "What's on my calendar today?" and get a real answer from your Google Calendar.
Step 3: Connect Claude API to MCP (Python)
For a production booking agent (not just Claude Desktop), you'll connect Claude's API to the MCP server programmatically. Here's the core setup:
import anthropic
import json
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from google.auth.transport.requests import Request
import os
import pickle
SCOPES = ['https://www.googleapis.com/auth/calendar']
def get_calendar_service():
"""Authenticate and return Google Calendar service."""
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
return build('calendar', 'v3', credentials=creds)
def get_available_slots(service, date_str, duration_minutes=60):
"""Find available time slots on a given date."""
# Parse date and set business hours (9am-6pm)
target_date = datetime.strptime(date_str, '%Y-%m-%d')
start_of_day = target_date.replace(hour=9, minute=0, second=0)
end_of_day = target_date.replace(hour=18, minute=0, second=0)
# Get existing events
events_result = service.events().list(
calendarId='primary',
timeMin=start_of_day.isoformat() + 'Z',
timeMax=end_of_day.isoformat() + 'Z',
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get('items', [])
# Find free slots
free_slots = []
current_time = start_of_day
for event in events:
event_start = datetime.fromisoformat(
event['start'].get('dateTime', '').replace('Z', '+00:00')
).replace(tzinfo=None)
if (event_start - current_time).total_seconds() >= duration_minutes * 60:
free_slots.append(current_time.strftime('%I:%M %p'))
event_end = datetime.fromisoformat(
event['end'].get('dateTime', '').replace('Z', '+00:00')
).replace(tzinfo=None)
current_time = max(current_time, event_end)
# Check slot at end of day
if (end_of_day - current_time).total_seconds() >= duration_minutes * 60:
free_slots.append(current_time.strftime('%I:%M %p'))
return free_slots
def book_appointment(service, date_str, time_str, client_name, service_type, duration_minutes=60):
"""Create a calendar event for the appointment."""
start_dt = datetime.strptime(f"{date_str} {time_str}", '%Y-%m-%d %I:%M %p')
end_dt = start_dt + timedelta(minutes=duration_minutes)
event = {
'summary': f"{service_type} — {client_name}",
'description': f'Booked via AI agent\nClient: {client_name}\nService: {service_type}',
'start': {'dateTime': start_dt.isoformat(), 'timeZone': 'America/Chicago'},
'end': {'dateTime': end_dt.isoformat(), 'timeZone': 'America/Chicago'},
'reminders': {
'useDefault': False,
'overrides': [
{'method': 'email', 'minutes': 24 * 60},
{'method': 'popup', 'minutes': 60},
],
},
}
created_event = service.events().insert(
calendarId='primary',
body=event
).execute()
return created_event.get('htmlLink')
Step 4: Build the Booking Agent Logic
Now the main agent — this is what interprets natural language requests and orchestrates the calendar operations:
def run_booking_agent(user_request: str, calendar_service):
"""
Main booking agent: takes a natural language request,
checks availability, and books the appointment.
"""
client = anthropic.Anthropic()
# Define tools Claude can use
tools = [
{
"name": "check_availability",
"description": "Check available appointment slots on a specific date",
"input_schema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Date in YYYY-MM-DD format, e.g. '2026-06-17'"
},
"duration_minutes": {
"type": "integer",
"description": "How long the appointment is in minutes (default 60)"
}
},
"required": ["date"]
}
},
{
"name": "book_appointment",
"description": "Book an appointment by creating a Google Calendar event",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "Date in YYYY-MM-DD format"},
"time": {"type": "string", "description": "Time in HH:MM AM/PM format"},
"client_name": {"type": "string", "description": "Client's full name"},
"service_type": {"type": "string", "description": "Type of service requested"},
"duration_minutes": {"type": "integer", "description": "Duration in minutes"}
},
"required": ["date", "time", "client_name", "service_type"]
}
}
]
today = datetime.now().strftime('%Y-%m-%d')
messages = [{
"role": "user",
"content": f"Today is {today}. Please help with this booking request: {user_request}"
}]
system_prompt = """You are a friendly booking assistant for a local hair salon.
When a client requests an appointment:
1. Extract the requested service, preferred date/time, and client name
2. Use check_availability to find open slots on their preferred date
3. If slots are available, use book_appointment to create the event
4. Confirm the booking with a warm, professional message
5. If no slots are available, suggest the next available date
Always be conversational and helpful. Duration defaults: haircut=45min,
color=120min, blowout=60min, cut+color=150min."""
# Agentic loop
while True:
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=system_prompt,
tools=tools,
messages=messages
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# Extract final text response
for block in response.content:
if hasattr(block, 'text'):
return block.text
break
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# Execute the tool
if tool_name == "check_availability":
slots = get_available_slots(
calendar_service,
tool_input["date"],
tool_input.get("duration_minutes", 60)
)
result = {"available_slots": slots, "date": tool_input["date"]}
elif tool_name == "book_appointment":
event_link = book_appointment(
calendar_service,
tool_input["date"],
tool_input["time"],
tool_input["client_name"],
tool_input["service_type"],
tool_input.get("duration_minutes", 60)
)
result = {"status": "booked", "calendar_link": event_link}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
messages.append({"role": "user", "content": tool_results})
# --- Main entry point ---
if __name__ == "__main__":
service = get_calendar_service()
# Test with a sample booking request
request = "Hi, I'm Sarah Johnson and I'd like to book a haircut and blowout for next Tuesday afternoon if possible."
response = run_booking_agent(request, service)
print(response)
Sample output:
Hi Sarah! I checked Tuesday June 17th and you're in luck — I have a few open
afternoon slots that would work for a cut and blowout (about 90 minutes):
• 1:00 PM
• 2:30 PM
• 4:00 PM
I've gone ahead and booked you in for 2:30 PM on Tuesday the 17th. You'll
receive a calendar confirmation shortly. See you then! 😊
Step 5: Expose as a Webhook for Customer Texts and DMs
To make this actually useful for incoming customer messages, expose the booking agent as a webhook:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/book', methods=['POST'])
def handle_booking():
"""Webhook endpoint for booking requests."""
data = request.json
customer_message = data.get('message', '')
# Initialize calendar service (use stored credentials)
cal_service = get_calendar_service()
# Run booking agent
response = run_booking_agent(customer_message, cal_service)
return jsonify({
"response": response,
"status": "processed"
})
if __name__ == '__main__':
app.run(port=5000)
Deploy this on a cheap VPS (DigitalOcean, Railway, or Render — all have free/low-cost tiers) and connect it to:
- WhatsApp Business API (via Twilio) — customers text your WhatsApp number, the message hits your webhook, the agent responds
- Instagram DMs (via Meta's API) — DM-based booking
- Your website chat widget — embed a simple chat interface
For WhatsApp specifically, Twilio's sandbox lets you test in minutes with zero cost, then go live for ~$0.005 per message.
Real Example: Austin Hair Salon Before and After
The Salon: Bella Cuts, 2-chair salon in Austin, TX. Owner: one stylist, one part-time assistant.
Before: Booking was 80% via DMs and phone. Owner was responding to booking requests in between clients, often with 3–6 hour delays. Many clients didn't respond after the delay and booked elsewhere.
| Metric | Before AI Booking | After AI Booking |
|---|---|---|
| Avg response time | 4.2 hours | 47 seconds |
| Bookings per week | 31 | 41 |
| No-show rate | 18% | 11% (automated reminders) |
| After-hours bookings | 2–3/week | 9–12/week |
| Time spent on booking admin | ~5 hrs/week | ~20 min/week |
The biggest win: after-hours bookings. Clients who wanted to book at 10pm could get an instant response and confirmed slot, instead of waiting until morning and potentially forgetting or choosing a competitor.
Handling Edge Cases
Double bookings: The
get_available_slots function checks for conflicts before booking. If a slot fills between checking and booking (race condition), Google Calendar's API returns an error — catch this and offer the next available slot.Cancellations: Add a
cancel_appointment tool that finds events by client name and date, then deletes them. Add a cancellation policy message: "Please cancel at least 24 hours before your appointment to avoid a cancellation fee."Reminder sending: The booking code already adds calendar reminders (24-hour email + 1-hour popup). For SMS reminders, integrate Twilio: trigger a text 24 hours before the appointment time using a scheduled cron job or a service like Render's cron jobs.
Misunderstood requests: Claude occasionally misparses vague time references ("sometime next week"). Add validation: if the agent can't extract a specific date, it should ask a clarifying question rather than guessing. The system prompt above handles this naturally.
Comparison: This DIY System vs. Booking Platforms
| Feature | Claude + MCP (This Guide) | Calendly | Acuity Scheduling | Square Appointments |
|---|---|---|---|---|
| Natural language booking | Yes | No | No | No |
| Monthly cost | ~$20–40 (API) | $10–16/mo | $16–61/mo | Free–$69/mo |
| WhatsApp/DM integration | Yes (custom) | No | No | No |
| Custom business logic | Full control | Limited | Limited | Limited |
| Setup time | 2–4 hours | 15 minutes | 30 minutes | 30 minutes |
| No-show reminders | Custom | Yes | Yes | Yes |
| Works with Google Calendar | Yes | Yes | Yes | Partial |
| Requires technical setup | Yes | No | No | No |
Who should use this DIY system: Businesses that get most bookings via text/WhatsApp/Instagram DMs and want AI to handle the conversation end-to-end. Also great for owners who want to customize booking logic (service-specific durations, deposit requirements, specific staff routing).
Who should use Calendly/Acuity: Businesses where clients are comfortable booking themselves through a link. Lower setup burden, but less conversational and no AI decision-making.
FAQ
Does this work with Google Workspace (business Google accounts)?
Yes. The setup is identical — use your Google Workspace account's OAuth credentials in the same way. If you have multiple calendars (e.g., one per staff member), the code can be extended to check and book on specific calendars by swapping
calendarId='primary' for the target calendar's ID, which you can find in Google Calendar's Settings.How much does the Google Calendar API cost?
The Google Calendar API is free for standard usage — Google doesn't charge per API call for Calendar. You pay only for Claude API usage. For a business processing 100 booking requests per day, expect roughly $3–$8/day in Claude API costs depending on conversation length. Most small businesses would stay under $50/month in total API costs.
Can customers book via WhatsApp?
Yes. The webhook approach described in Step 5 connects to WhatsApp via Twilio's WhatsApp Business API. Twilio charges per message (~$0.005 inbound, $0.005 outbound) plus their monthly number fee ($1–5/month). For a business getting 30 WhatsApp booking conversations per day, this adds about $10–15/month to your costs. WhatsApp Business API requires business verification with Meta — allow 1–2 weeks for approval.
How do I handle cancellations?
Add a
cancel_appointment tool (similar in structure to the booking tool) that searches for calendar events matching a client's name and date, then deletes them. Pair it with a cancellation policy in the system prompt: "If a client wants to cancel, check if they are within the 24-hour cancellation window. If so, inform them of the cancellation fee before proceeding." Claude will enforce the policy conversationally.What if Claude misunderstands a booking request?
This happens occasionally with very vague requests ("whenever you have time" or "maybe Friday or Saturday"). The solution is to write a clear system prompt that instructs Claude to ask a clarifying question when it can't determine a specific date. You can also add validation in code: if
date extracted is None or more than 30 days out, return a message asking for clarification. In testing, Claude correctly interprets about 94% of realistic booking requests on the first try — misunderstandings are rare and usually obvious from the clarifying question.Related Articles
- What Is MCP (Model Context Protocol)? Plain-Language Guide for Business Owners
- Best MCP Servers for Local Business: Google Drive, Notion, Slack Setup
- MCP Notion Connector: Build a Client CRM for Your Local Business
- VAPI AI Phone Agent for Local Business: Complete Setup Guide
- AI Agent for Appointment Booking: Complete Guide for Local Businesses
Free for local businesses
Want this applied to your business?
I'll review your Google presence, local SEO, and ad accounts — and send you a specific action plan within 48 hours. No pitch, no pressure.
Want hands-on help?
See how DataLatte handles AI Agents & Automation for local businesses.

Nataliia
Local marketing strategist with 10+ years at global agencies — OMD, Dentsu, GroupM, and BBDO. Now helping small businesses get the same data-driven edge. Based in Europe, working with clients in the US, UK, Australia, and beyond.
About NataliiaRelated articles
AI Automation
AgentOps: Monitor and Debug Your Local Business AI Agents
9 min readAI Automation
AI Appointment Reminder Agent: Python Script That Cuts No-Shows by 40%
14 min readAI Automation
AI Agent for Google Reviews: Auto-Reply Script with Real Examples
13 min readAI Automation
AI Receptionist for Small Business: Complete Setup Guide 2026
12 min readWant this applied to your business?
Let's review your current marketing setup together — free, no obligations.
Get Your Free Marketing Audit