Skip to main content
Glama
mbayucot

booking-mcp

by mbayucot

booking-mcp

A standalone MCP server (built on FastMCP) that exposes the booking datastore used by booking-agent to any MCP-compatible client. It is decoupled from booking-agent and connects to the shared DB with its own SQLAlchemy layer. booking-agent owns the schema and migrations; a schema-contract test guards against drift.

Features

Resources (read-only, URI-addressed)

URI

Returns

booking://staff

active cleaners (skills + location)

booking://staff/{staff_id}

one staff member

booking://schedule/{date}

appointments on a date

booking://clients/{email}

client + contacts + saved preferences

Read tools (readOnly, idempotent)

  • search_availability(service, date, time, latitude?, longitude?, radius_km?): staff who can do the job, are free at the slot, and are within range. This uses the same skill/free/geo filter as the booking engine.

  • find_next_available(service, date, time, days?, …): first day within the window with a free, qualified cleaner.

  • list_staff(skill?), daily_schedule(date), get_client(email).

Write tools (only when READ_ONLY=false; each asks for confirmation via MCP elicitation before writing)

  • create_booking(...): client + job + appointment, idempotent (deduped on a hash of all material fields).

  • cancel_booking(appointment_id): idempotent delete.

  • reschedule_booking(appointment_id, date, time): moves a slot and rejects staff conflicts.

  • add_customer_preference(email, note).

  • book_from_text(request): parses a free-text request using the client's LLM via MCP sampling, then confirms and books. Requires a sampling-capable client. Idempotent.

Writes go directly to the DB and bypass booking-agent's approval workflow. Use the workflow bridge below if you want human approval.

Workflow bridge tools (only when BOOKING_AGENT_URL is set; routes through booking-agent's human-approval workflow over HTTP)

  • book_via_workflow(message): start an approval run from a natural-language request and return {run_id, status}.

  • get_workflow_run(run_id): poll status and the final response.

  • decide_workflow_run(run_id, approve, by?, reason?): submit the approve/reject decision.

Prompts: book_cleaning(...), summarize_schedule(date).

All inputs are validated (real calendar dates/times, email format); all outputs are typed (structured content).

Related MCP server: Appointment Scheduler MCP Server

Quickstart

cp .env.example .env   # point DATABASE_URL at the shared Postgres
make install           # uv sync --dev
make dev-up            # start Postgres on :5433 (Docker)
make seed              # create_all + demo data (requires STANDALONE_MODE guard)
make server            # run in stdio mode

For the HTTP transport on the host: make server-http (binds :8000).

Fully standalone (own DB + data). No booking-agent needed. One-shot the whole stack:

make stack-up   # docker compose up (db + seed + mcp on :8000)

booking-mcp-seed bootstraps the schema with create_all and populates demo staff, clients, appointments, and preferences so the read tools return data immediately. STANDALONE_MODE=true is required. The guard prevents accidental schema mutation against a shared DB. When sharing a DB with booking-agent, skip the seed: booking-agent owns the canonical Alembic migrations.

API / Usage

Any MCP client takes the standard mcpServers config (the same JSON an mcp add accepts).

Local (stdio). The client launches the server as a subprocess. This is local and trusted, so no auth is required:

{
  "mcpServers": {
    "booking": {
      "command": "/ABS/PATH/booking-mcp/.venv/bin/booking-mcp",
      "env": {
        "DATABASE_URL": "postgresql+psycopg://booking:booking@localhost:5432/booking",
        "READ_ONLY": "true"
      }
    }
  }
}

(booking-mcp is the console script installed into the venv.)

Remote (HTTP). Connect over the streamable-HTTP transport with a Bearer key. Mint a key, then pass the hash in API_KEYS (the server refuses to start write-enabled over HTTP without credentials):

# 1. Mint a key (prints plaintext once + the JSON record to add to API_KEYS)
#    Available scopes: read, write, workflow, pii (grant only what the client needs)
booking-mcp-mintkey --client claude-desktop --scopes read,write,pii

# 2. Start the server
API_KEYS='[{"hash":"<paste-hash>","client_id":"claude-desktop","scopes":["read","write","pii"]}]' \
  READ_ONLY=false booking-mcp
{
  "mcpServers": {
    "booking": {
      "url": "http://your-host:8000/mcp",
      "headers": { "Authorization": "Bearer <plaintext-key>" }
    }
  }
}

A client with no or wrong key gets 401. Scope enforcement is strict: a key without read cannot see read tools; write/workflow/pii are additional gates on top. stdio needs no token because it is local/trusted, so all surfaces are open.

Legacy: AUTH_TOKEN=<token> still works as a single full-access fallback but is deprecated It grants read+write with no scope isolation. Migrate to API_KEYS.

Development

Common make targets:

Target

What it runs

make install

uv sync --dev

make dev-up / dev-stop / dev-down

Postgres container lifecycle

make seed

Schema + demo data (STANDALONE_MODE=true)

make server

stdio server on host

make server-http

HTTP server on host (:8000)

make stack-up / stack-down

Full containerised stack

make mintkey ARGS="--client X --scopes read,pii"

Mint an API key

make db

psql shell into the running container

make test

pytest --cov=booking_mcp

make lint

ruff check src tests

make fmt

ruff format src tests

make audit

pip-audit

make check

lint then test

Testing

make test   # pytest --cov=booking_mcp, requires 100% coverage to pass
  • In-memory client: tools/resources are exercised through fastmcp.Client against the server object, with no subprocess.

  • Testcontainer Postgres: the MCP's own create_all schema, truncated per test (real FK/types).

  • Schema-contract test (test_schema_contract.py): when ../booking-agent/backend is checked out, it applies booking-agent's real Alembic migrations to a fresh container and runs the MCP queries against them. This catches drift between this server's models and the owning service's schema. Skips when booking-agent isn't present.

Configuration

Copy .env.example to .env. All settings are read from the environment (or .env).

Variable

Default

Purpose

DATABASE_URL

postgresql+psycopg://booking:booking@localhost:5432/booking

The same Postgres booking-agent uses; booking-agent owns the schema, this is a client.

READ_ONLY

true

Set to false to enable the write tools. Writes bypass booking-agent's human-approval workflow, so enable deliberately.

STANDALONE_MODE

false

Must be true to run booking-mcp-seed / create_all(). Guards against accidental schema mutation on a shared DB. Not needed when connecting to a DB already managed by booking-agent.

API_KEYS

(empty)

Preferred HTTP auth. JSON array of {hash, client_id, scopes} records. Store hashes only, never plaintext. Mint records with booking-mcp-mintkey. All four scopes (read, write, workflow, pii) are enforced at the auth layer: a key sees only the surfaces its scopes explicitly cover.

AUTH_TOKEN

(empty)

Deprecated: single static token granting full access (all scopes). Superseded by API_KEYS. Kept for backward compatibility.

REDACT_PII

true

Mask phone numbers (last-4 digits) and addresses ([REDACTED]) in client resources and get_client. Resources are pulled into model context, where PII can spread to prompts/logs/transcripts. Set false only for internal tooling backed by a scoped key.

FORCE_WORKFLOW_FOR_SAMPLING

false

Redirect book_from_text to book_via_workflow instead of writing directly. Recommended in production: sampled LLM output carries implicit trust/quota risks.

BOOKING_AGENT_URL

(empty)

When set, the workflow-bridge tools are registered and POST to booking-agent so a booking goes through its full approval workflow. Decoupled: HTTP only, no import.

BOOKING_AGENT_TIMEOUT

10.0

HTTP timeout (seconds) for workflow-bridge calls to booking-agent.

SAMPLE_TIMEOUT

30.0

Cap on the client's LLM sampling call in book_from_text so a hung client can't pin a worker.

DB_POOL_SIZE

20

Connection pool size (sized for FastMCP's sync-tool threadpool).

DB_MAX_OVERFLOW

20

Pool overflow beyond DB_POOL_SIZE.

DB_POOL_RECYCLE

3600

Recycle connections after this many seconds.

DB_POOL_TIMEOUT

10

Seconds to wait for a pooled connection.

DB_STATEMENT_TIMEOUT_MS

30000

Per-query statement timeout (ms).

LOG_LEVEL

INFO

Logging level.

Notes

  • Schema ownership: in standalone mode (STANDALONE_MODE=true), booking-mcp-seed bootstraps the schema with create_all. When sharing a DB with booking-agent, booking-agent owns the canonical Alembic migrations. Skip the seed entirely; the schema-contract test guards against model drift.

  • No FastAPI/LangGraph. FastMCP brings its own (Starlette/uvicorn) HTTP stack for the HTTP transport.

  • MCP client features used: elicitation (write confirmation), sampling (book_from_text). Both degrade gracefully. A client that does not support them just cannot call those tools.

  • Per-resource content subscriptions and argument completions are not supported. Neither is first-class in this FastMCP version. Clients re-read booking://schedule/{date} for fresh data.

License

MIT. See LICENSE.

Available Tools

5 tools
daily_scheduleA
Read-onlyIdempotent

All appointments booked on a given date.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateYesISO date YYYY-MM-DD

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and idempotentHint=true, so the agent knows it's safe and idempotent. The description adds that it returns only booked appointments for a specific date, which clarifies the scope. However, it does not disclose potential limitations like pagination, timezone handling, or whether cancelled appointments are included. With strong annotations, a score of 3 is appropriate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence with no wasted words. It front-loads the core functionality and earns its place by being direct and efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one required parameter, read-only, with output schema available), the description is nearly complete. It could potentially mention the coverage of appointments (e.g., all staff, all clients) but the output schema likely fills that gap. The description is sufficient for an AI agent to understand the tool's purpose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, with a clear ISO date format. The description does not add any additional meaning beyond what the schema provides. Baseline 3 is correct since the schema already handles parameter semantics adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns all appointments booked on a given date. It implicitly indicates a read operation and distinguishes from siblings like find_next_available (which finds open slots) and search_availability (which checks availability). This is a specific verb+resource combination with clear differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. There is no mention of prerequisites, limitations, or when not to use it. The description simply states the tool's function without any contextual usage advice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_next_availableA
Read-onlyIdempotent

The first day from date (within days) with a free, qualified staff member at time — or null if none in the window.

ParametersJSON Schema
NameRequiredDescriptionDefault
serviceYesService/skill, e.g. 'cleaning'
dateYesISO date YYYY-MM-DD
timeYes24h time HH:MM
daysNoDays ahead to search
latitudeNo
longitudeNo
radius_kmNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate readOnlyHint and idempotentHint. The description adds the null return behavior but omits crucial information about location-based filtering (latitude, longitude, radius_km), which are part of the parameters and affect the search. This is a notable gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that frontloads the core behavior. Every word is informative; no fluff or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having an output schema, the description fails to mention the location parameters that significantly affect the tool's behavior. For a tool with 7 parameters and location filtering, this omission leaves the description incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 57%, with latitude, longitude, and radius_km lacking descriptions. The tool description does not compensate by explaining these parameters or their role (location proximity). It only implicitly covers date, days, and time. Thus, it adds little value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description precisely states the tool finds the first day within a window that has a free, qualified staff member at a given time, including the null case. It clearly distinguishes from sibling tools like search_availability by specifying 'first' and 'within days'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for finding the earliest available date matching criteria, but it does not explicitly state when to use this tool over alternatives (e.g., search_availability for broader searches). No exclusion criteria or context are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_clientA
Read-onlyIdempotent

Look up a client by email with their contacts and saved preferences. Phone and address are masked by default (REDACT_PII=true); set false only for internal/admin tooling backed by a scoped token.

ParametersJSON Schema
NameRequiredDescriptionDefault
emailYesCustomer email

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide readOnlyHint and idempotentHint. The description adds context about default PII masking and authorization requirements for unmasking, which goes beyond the structured annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: first states the purpose, second details behavioral constraints. No wasted words, front-loaded with key action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the presence of output schema, the description adequately covers the return data (contacts and saved preferences) and the masking behavior. For a single-parameter, well-annotated tool, this is complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema coverage is 100% with one parameter 'email' already described. The description does not add any semantic information about the parameter beyond what the schema provides, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Look up a client by email with their contacts and saved preferences.' This uses a specific verb ('look up') and resource ('client by email'), and is distinct from sibling tools like 'daily_schedule' or 'search_availability.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides guidance on the masking behavior ('set false only for internal/admin tooling') but does not explicitly tell when to use this tool versus alternatives or when not to use it. No sibling comparison is made.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_staffA
Read-onlyIdempotent

List active staff, optionally filtered by a skill.

ParametersJSON Schema
NameRequiredDescriptionDefault
skillNoFilter by skill

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly and idempotent hints. Description adds 'active' filter, which is useful but minimal additional behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence, no unnecessary words, front-loads the core action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Simple tool with one optional param and output schema; description is sufficient for basic understanding. Could optionally mention pagination or scope of 'active'.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only parameter 'skill' has schema description 'Filter by skill'. The description merely restates this, adding no new semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it lists active staff with optional skill filtering. Distinct from sibling tools like daily_schedule or find_next_available.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Mentions optional skill filtering but provides no guidance on when to use this tool vs. siblings or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_availabilityA
Read-onlyIdempotent

Find staff who can do the service, are free at the slot, and (if coords given) within range. Same skill/free/geo filter the booking engine uses.

ParametersJSON Schema
NameRequiredDescriptionDefault
serviceYesService/skill, e.g. 'cleaning'
dateYesISO date YYYY-MM-DD
timeYes24h time HH:MM
latitudeNoJob latitude
longitudeNoJob longitude
radius_kmNoSearch radius (km)

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint true, so the description carries a lower burden. The description adds value by explaining the three-part filter (skill, free time, geo range), which aligns with agent expectations. It does not contradict annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no fluff. The first sentence immediately states the core functionality, and the second efficiently provides context about the booking engine.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema, the description does not need to explain return values. It covers the filtering logic completely for a search tool. The only minor gap is omitting what happens with no results, but that is implied.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so parameters are well-documented. The description ties parameters together conceptually ('do the service'=service, 'free at the slot'=date/time, 'within range'=lat/lon/radius) but adds no detailed parameter-level guidance beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool finds staff who can perform the service, are free at the specified slot, and optionally within a geographic range. It distinguishes from siblings like 'list_staff' and 'find_next_available' by mentioning it uses the same filter as the booking engine.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context by stating it matches the booking engine's filter logic, suggesting it's appropriate when checking availability for a specific service/time/location. However, it does not explicitly contrast with alternatives like 'find_next_available' for next-slot searches.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observeddaily_schedule
    • First observedfind_next_available
    • First observedget_client
    • First observedlist_staff
    • First observedsearch_availability

TDQS

A3.8/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: daily_schedule shows appointments, find_next_available finds next slot, get_client looks up client info, list_staff lists staff, search_availability finds matching staff. No overlapping functionality.

Naming Consistency4/5

All tool names use snake_case, and most follow a verb_noun pattern (get_client, list_staff, search_availability). However, daily_schedule uses a noun_noun pattern, creating a minor inconsistency.

Tool Count5/5

Five tools is an appropriate number for a booking server, covering core read operations without being excessive or insufficient.

Completeness2/5

The tool set lacks any mutating operations such as create_booking or update_appointment, making it impossible to complete a booking workflow. This is a significant gap for a booking server.

Maintenance

ActivityStale
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A production-ready MCP server that enables safe, read-only SQL SELECT queries against PostgreSQL databases with built-in security validation. It features connection pooling, automatic row limits, and structured logging to ensure secure and reliable database interactions.
    17
    ISC
  • A
    license
    Not graded
    quality
    D
    maintenance
    An open-source MCP server for PostgreSQL schema introspection and guarded read-only queries. It enables MCP clients to discover schemas, tables, columns, indexes, relationships, and safe queryable data from a configured PostgreSQL database.
    8
    MIT