OpenBooking MCP Server
OfficialEnables Cal.com event types to be bookable by AI agents: each event type becomes an offering, holds use Cal.com slot reservations, confirmations create Cal.com bookings, and cancellations sync with Cal.com.
Provides optional Google Calendar synchronization for hosted booking businesses, keeping bookings in sync with their Google Calendar.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OpenBooking MCP Serverbook me a haircut with Maria on Friday afternoon"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
OpenBooking
Make any booking system bookable by AI agents through one integration.
Restaurants, salons, clinics: implement one BookingProvider interface, and OpenBooking exposes
it to agents over MCP, UCP and A2A. It enforces the rules that make agent bookings
safe:
holds that expire;
idempotent retries that never double-book;
explicit user confirmation;
policy terms disclosed up front.
Protocol | Status | What you get |
MCP (Model Context Protocol) | ✅ Supported | 5 agent-friendly tools over Streamable HTTP (2025 and 2026-07-28 eras) and stdio |
UCP (Universal Commerce Protocol) | 🟡 Draft |
|
A2A (Agent2Agent) | ⚪ Stub | v1.0 Agent Card at |
5-minute quickstart
Requires Node 22.12+ and pnpm. The published packages run on Node 20+. Run corepack enable or npm i -g pnpm.
git clone <this repo> openbooking && cd openbooking
pnpm install
pnpm devThis starts Studio Nord, a fictional hair salon (in-memory) with three stylists, three services (haircut, beard trim, color & cut with a deposit), opening hours and a cancellation policy. Run DEMO=restaurant pnpm dev for the restaurant demo instead.
MCP (Streamable HTTP) http://localhost:3000/mcp
UCP profile http://localhost:3000/.well-known/ucp
UCP REST http://localhost:3000/ucp/booking-sessions
A2A Agent Card http://localhost:3000/.well-known/agent-card.json
Studio (dashboard) http://localhost:3000/studioTry it with an agent. Point any MCP client at http://localhost:3000/mcp. For example, run
npx @modelcontextprotocol/inspector. Then ask the agent to "book me a haircut with Maria on Friday
afternoon".
For stdio clients, use pnpm --dir examples/demo stdio (config snippet in
examples/demo/src/stdio.ts).
Try it with curl (UCP REST).
# 1. find a slot (EXTENSION: sh.openbooking.availability)
curl "localhost:3000/ucp/availability?date=2026-10-09&party_size=1&offering_id=haircut&time_from=15:00"
# 2. hold it (a UCP booking session); copy an offer id from step 1
curl -X POST localhost:3000/ucp/booking-sessions \
-H 'content-type: application/json' -H "Idempotency-Key: $(uuidgen)" \
-d '{"stays":[{"id":"<offer id>"}],"booker":{"first_name":"Ada","last_name":"Lovelace","email":"ada@example.com"}}'
# 3. confirm, only with explicit user consent
curl -X POST localhost:3000/ucp/booking-sessions/<id>/complete \
-H 'content-type: application/json' -H "Idempotency-Key: $(uuidgen)" \
-d '{"user_confirmed":true}'Related MCP server: G-Guest MCP server
Keep bookings in Postgres
The demos are in-memory by default. Set DATABASE_URL and bookings, holds, idempotency records and Studio activity are stored in Postgres (tables are created on start):
DATABASE_URL=postgres://user:pass@localhost:5432/openbooking pnpm devOn Vercel, add a Postgres database from the Marketplace (Neon, Supabase…) and use its pooled connection string. In code:
import { connectPostgres, migrate, postgresStores } from '@openbooking/postgres';
const db = connectPostgres(process.env.DATABASE_URL!);
await migrate(db);
const stores = postgresStores(db);
createOpenBookingApp({
provider: createDemoSalonProvider({ store: stores.bookings }), // or new CalcomBookingProvider({ store: stores.calcom, ... })
serviceOptions: { idempotencyStore: stores.idempotency },
studio: { token: process.env.STUDIO_TOKEN, activity: stores.activity },
baseUrl,
});Holds are reserved under a per-resource lock, so two servers can never hand out the same time.
OpenBooking Studio
The booking system for the people running the business, at /studio:
Calendar: a day view with a column per staff member or table, and the day's bookings and takings at a glance.
New booking: staff book phone calls and walk-ins. It finds free times, takes customer details, and records a deposit collected at the desk.
Bookings: filter by status. Open a booking for guest details, deposit, cancellation terms and history. Cancel or release a hold.
Customers: everyone who booked, with visits, spend, next visit and the channel they first came through.
Services & staff: what customers and AI assistants can book.
Insights and agent log: bookings per channel (Claude, ChatGPT, Studio…) and every agent call with its result.
In local dev (baseUrl on localhost) the Studio opens without a login. Anywhere else, set a token: createOpenBookingApp({ ..., studio: { token: process.env.STUDIO_TOKEN } }). Without a token, the Studio API stays locked. Agent names are self-reported by clients, so use them for analytics only, never for access control.
Hosted: many businesses, one deployment
pnpm dev:hostedBusinesses sign up at /signup and set up services, staff, hours and cancellation rules in Studio.
Each gets a booking page at /b/<id>. Every listed business is bookable through one MCP app at
/mcp (find_business → booking tools), with optional Google Calendar sync and confirmation
emails. See docs/HOSTED.md.
The MCP tools
Tool | Does |
| Slots for a date and party size, with price, deposit and cancellation policy. Nothing is reserved. |
| Reserves a slot until |
| Confirms a hold. Requires |
| Current status and details. |
| Releases a hold, or cancels a confirmed booking. A confirmed booking needs |
Design rules:
Every mutating tool takes an
idempotency_key.Every error is
{ code, message, suggested_next_action }.Every booking response includes
next_step.
Connect Cal.com (beta)
Already on Cal.com or self-hosted Cal.diy? Your event types become bookable by AI agents with no code:
CAL_API_KEY=cal_live_... VENUE_NAME="Studio Nord" VENUE_TIMEZONE=Europe/Oslo pnpm dev
# optional: CAL_EVENT_TYPE_IDS="123,456" CAL_BASE_URL=https://cal.example.com VENUE_CURRENCY=NOKOr in code:
import { CalcomBookingProvider } from '@openbooking/provider-calcom';
const provider = new CalcomBookingProvider({
apiKey: process.env.CAL_API_KEY!,
venue: { id: 'studio-nord', name: 'Studio Nord', timezone: 'Europe/Oslo', currency: 'NOK' },
});How it maps:
Each event type becomes an offering.
A hold is a Cal.com slot reservation for the hold time.
Confirm creates the Cal.com booking. Cal.com requires an email, so agents are asked for one.
Cancel cancels it in Cal.com.
Cancellations made in Cal.com show up in OpenBooking.
Limits in this beta:
One person per appointment.
Paid event types aren't charged through OpenBooking.
Hold and booking records are in memory until a durable store is added.
Tested against the documented API v2 shapes, not yet end to end on a live account.
Implementing BookingProvider
You own inventory and persistence. OpenBooking owns validation, idempotency, hold expiry, consent and cancellation rules.
import { BookingError, type BookingProvider } from '@openbooking/core';
import { createOpenBookingApp, listen } from '@openbooking/server';
class MySalonProvider implements BookingProvider {
info = { name: 'Studio Nord', description: 'Hair salon in Bergen' };
async listVenues() {
return [{ id: 'nord', name: 'Studio Nord', timezone: 'Europe/Oslo', currency: 'NOK' }];
}
async searchAvailability(query, ctx) {
// query: { venue_id, date, party_size, time_from?, time_to?, offering_id?, tags?, limit }
// Return Slot[]: each with an opaque slot_id, start/end (ISO with offset), price,
// deposit (or null) and a cancellation_policy computed for that slot.
}
async createHold({ slot_id, expires_at, customer, notes }, ctx) {
// ATOMICALLY reserve the slot until expires_at (transaction / unique constraint).
// If it's gone: throw new BookingError('slot_unavailable', 'Just taken.');
// Return the Booking with status 'held'.
}
async confirmHold({ booking_id, customer, payment_token }, ctx) {
// Re-check expiry atomically, charge the deposit if any, mark confirmed, return the Booking.
}
async getBooking(id, ctx) {
/* … */
}
async cancelBooking({ booking_id, reason, fee, refund }, ctx) {
// Release the hold, or cancel and record the fee/refund the engine computed.
}
// optional: updateBooking({ booking_id, customer?, notes? }, ctx)
}
const { app } = createOpenBookingApp({
provider: new MySalonProvider(),
baseUrl: 'https://book.studionord.example',
});
await listen(app, { port: 3000 });The contract is documented in
packages/core/src/provider.ts.
provider-memory is a complete reference
implementation. The four rules:
Mutations are atomic.
Overlapping holds never both succeed.
Expired holds stop blocking inventory.
Business failures throw
BookingErrorwith a specific code.
Repository layout
packages/
core/ Domain model, BookingProvider, BookingService (agent-safety rules)
provider-memory/ Configured provider (catalog + slot rules) + demo salon and restaurant
postgres/ Postgres storage: bookings, idempotency, Studio activity, Cal.com records
adapter-mcp/ MCP tools (Streamable HTTP + stdio)
adapter-ucp/ UCP discovery + booking sessions (draft)
adapter-a2a/ A2A Agent Card (stub)
studio/ OpenBooking Studio dashboard (/studio), incl. business settings
booking-page/ Public booking page: pre-filled links, JSON-LD, WebMCP, manage links
notifications/ Confirmation/cancellation emails with .ics invites
google-calendar/ Google Calendar sync (busy times, bookings as events)
server/ One Hono app mounting everything
hosted/ Many businesses on one deployment: sign-up, settings, the OpenBooking MCP app
examples/demo/
bench/ Agent-success benchmark (tasks + runner)
docs/ARCHITECTURE.md, docs/SPEC-NOTES.md, docs/HOSTED.mdDevelopment
pnpm dev # demo server with watch
pnpm test # vitest (unit + MCP end-to-end + bench smoke test)
pnpm typecheck
pnpm lint
pnpm build # tsup → dist/ per package
pnpm bench # run the agent benchmark (scripted baseline)
pnpm changeset # describe a change for releaseThe benchmark (bench/) runs booking tasks against isolated servers. It measures:
completion rate;
double bookings;
expired-hold errors.
LLM drivers plug into a stub interface.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Discover, read and book verified real-world businesses through one endpoint.
Verified local businesses, bookable by AI agents: services, prices, availability and appointments.
Find local services, read availability, and create short-lived booking holds.
Discover and book businesses via AI agents.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceScheduling and booking engine for AI agents. Check availability, hold slots, and confirm appointments with two-phase booking and conflict-free resource management.2-
- AlicenseAqualityAmaintenanceBook a table, an appointment or a place in a class at a real local business. Live availability, instant confirmation, no account and no API key. Eight tools: search, fetch, get_business, check_availability, create_booking, check_booking, cancel_booking and request_listing. Guest emails in eight languages. Hosted at https://g-guest.app/api/mcp814 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables AI agents and clients to check table availability and book restaurant reservations for a given date, time, and party size, returning either a confirmed booking or the nearest alternative slots. It also supports idempotent retries so replayed requests return the original reservation rather than double-booking a table.-
- FlicenseNot gradedqualityBmaintenanceEnables customers and AI agents to check restaurant table availability and create bookings, returning confirmations or nearby alternative times when a slot is full, with atomic capacity control and idempotent handling.-