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)

🟡 Beta

v1.0 Agent Card plus a JSON-RPC SendMessage endpoint (/a2a) for search, hold, confirm and cancel; no streaming or tasks

Start in one command

npx create-openbooking my-salon
cd my-salon && npm run dev

You get a booking backend with a booking page, a website snippet, Studio and an MCP endpoint. Edit business.ts for staff, services and opening hours.

Related MCP server: G-Guest MCP server

Install into an existing app

npm install @openbooking-sh/server @openbooking-sh/provider-memory
import { createOpenBookingApp, listen } from '@openbooking-sh/server';
import { createDemoSalonProvider } from '@openbooking-sh/provider-memory';

const { app } = createOpenBookingApp({
  provider: createDemoSalonProvider(), // or your own BookingProvider
  baseUrl: 'http://localhost:3000',
});
await listen(app, { port: 3000 });
// MCP at /mcp · booking page at /book · Studio at /studio · UCP at /.well-known/ucp

Package

What it is

@openbooking-sh/core

The engine and the BookingProvider interface

@openbooking-sh/server

One app for MCP, UCP, A2A, booking page and Studio

@openbooking-sh/provider-memory

Configured provider (services, staff, hours, rules) on a pluggable store

@openbooking-sh/postgres

Postgres storage for everything

@openbooking-sh/booking-page

Booking page, embed snippet and WebMCP

@openbooking-sh/client

Typed fetch client for the booking API (and its OpenAPI document)

@openbooking-sh/studio

Dashboard for the business

@openbooking-sh/adapter-mcp, adapter-ucp, adapter-usp, adapter-a2a

The protocol adapters

@openbooking-sh/provider-calcom

Cal.com connector (beta)

@openbooking-sh/conformance

Test suite that proves your BookingProvider never double-books

@openbooking-sh/notifications, google-calendar

Emails with .ics, Google Calendar sync

@openbooking-sh/hosted

Multi-business hosting (what runs app.openbooking.sh)

More: recipes (Postgres, Vercel, website snippet, your own booking system) · docs for AI coding tools · contributing

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}'

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-sh/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

get_business_info

Services (duration, price), staff customers can ask for by name, opening hours and how far ahead bookings are accepted.

search_availability

Free times on a date, with who is booked, who else is free, 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.

reschedule_booking

Moves a confirmed booking to a new time for the same service, keeping its id and code. Needs user_confirmed; allowed while cancelling is still free.

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, retryable }.

  • Every response includes next_step, the instruction for the agent.

  • New holds are limited per caller IP (default 20 per 10 minutes), so nobody can tie up the calendar. Set serviceOptions.holdLimit to tune or disable it.

Field reference

search_availability

Field

Type

Notes

date

YYYY-MM-DD

In the venue's time zone

party_size

integer

Default 1 (an appointment); guests for a table

offering_id

string

Service id from get_business_info, e.g. haircut

staff

string

A staff member by name or id, any capitalisation ("Kari")

preferences

string[]

Other required features, e.g. ["outdoor"]

time_from, time_to

HH:MM

Venue local time, inclusive

limit

integer

Default 20, max 50

customer (on hold_slot or confirm_booking): first_name, last_name, and at least one of email or phone_number (international format, e.g. +4791234567).

Slots and bookings:

  • Times (start, end) are ISO 8601 with the venue's offset, e.g. 2026-10-13T10:00:00+02:00.

  • expires_at is in UTC.

  • Prices are in minor units: { amount: 45000, currency: "NOK" } is 450.00 NOK.

  • resource says who or what is booked; also_available lists other staff free at that time.

  • Booking status is held, confirmed, cancelled or expired.

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-sh/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-sh/core';
import { createOpenBookingApp, listen } from '@openbooking-sh/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.

Prove it with the conformance suite (vitest). It books slots until the provider says no, fires parallel holds, lets holds expire and cancels, and fails if any rule is broken:

import { describeProviderConformance } from '@openbooking-sh/conformance/vitest';

describeProviderConformance('MySystemProvider', {
  create: () => new MySystemProvider(testConfig), // a fresh, empty provider per test
  now: new Date('2030-06-03T08:00:00Z'), // before the date you search
  query: { date: '2030-06-04', party_size: { total: 1 } }, // must return open slots
});

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
  create-openbooking/  npx create-openbooking: a ready-to-run booking backend
  adapter-mcp/      MCP tools (Streamable HTTP + stdio)
  adapter-ucp/      UCP discovery + booking sessions (draft)
  adapter-usp/      USP profile, catalog, availability, holds and bookings (draft)
  adapter-a2a/      A2A Agent Card + SendMessage endpoint
  studio/           OpenBooking Studio dashboard (/studio), incl. business settings
  booking-page/     Public booking page: pre-filled links, JSON-LD, WebMCP, manage links, OpenAPI
  client/           Typed fetch client for the booking page API
  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
    6 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.
    -