Skip to main content
Glama
openbooking-sh

OpenBooking MCP Server

Official

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

/.well-known/ucp profile plus dev.ucp.lodging.booking REST sessions, extended for time slots (spec notes)

A2A (Agent2Agent)

⚪ Stub

v1.0 Agent Card at /.well-known/agent-card.json; task endpoint not implemented yet

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 dev

This 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/studio

Try 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 dev

On 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:hosted

Businesses 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

search_availability

Slots for a date and party size, with price, deposit and cancellation policy. Nothing is reserved.

hold_slot

Reserves a slot until expires_at. Returns booking_id and the terms to show the user.

confirm_booking

Confirms a hold. Requires user_confirmed: true, customer details, and a payment_token if a deposit is due.

get_booking

Current status and details.

cancel_booking

Releases a hold, or cancels a confirmed booking. A confirmed booking needs user_confirmed; the fee follows the policy.

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=NOK

Or 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:

  1. Mutations are atomic.

  2. Overlapping holds never both succeed.

  3. Expired holds stop blocking inventory.

  4. Business failures throw BookingError with 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.md

Development

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 release

The 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

Apache-2.0

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Book 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/mcp
    8
    14 npm
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables 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.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables 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.
    -