booking-mcp
The booking-mcp server provides an interface to a cleaning service booking system, supporting read-only access to resources, conditional write operations, workflow integration, and high-level prompts.
Resources (read-only via URI)
Active staff list:
booking://staffIndividual staff details:
booking://staff/{staff_id}Daily schedule:
booking://schedule/{date}Client info (contacts & preferences):
booking://clients/{email}
Read Tools
search_availability: Find staff with a required skill, free at a given date/time, optionally within a geographic radius.find_next_available: Search forward up to 30 days to find the first day with a free, qualified staff member at a specified time.list_staff: List active staff, optionally filtered by skill.daily_schedule: Get all appointments booked on a specific date.get_client: Look up a client by email (PII-sensitive; phone/address redacted by default).
Write Tools (require READ_ONLY=false; all require confirmation via MCP elicitation)
create_booking: Add a new client, job, and appointment (idempotent).cancel_booking: Cancel an existing appointment.reschedule_booking: Move an existing appointment to a new time.add_customer_preference: Store notes or preferences for a client.book_from_text: Parse a natural-language booking request via LLM sampling and attempt to book.
Workflow Tools (require BOOKING_AGENT_URL to be set)
book_via_workflow: Route a booking request through a human-approval workflow.get_workflow_run: Poll the status of an approval run.decide_workflow_run: Submit an approval or rejection decision.
Prompts
book_cleaning(...)andsummarize_schedule(date)for high-level interactions.
Access Control: When using HTTP, API_KEYS with granular scopes (read, write, workflow, pii) are enforced across all tools.
Integrates with a PostgreSQL database to manage booking data, providing tools for staff management, appointment scheduling, client records, and availability search.
Click on "Install 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., "@booking-mcpbook a cleaning for next Tuesday morning"
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.
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 |
| active cleaners (skills + location) |
| one staff member |
| appointments on a date |
| 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 modeFor 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 toAPI_KEYS.
Development
Common make targets:
Target | What it runs |
|
|
| Postgres container lifecycle |
| Schema + demo data ( |
| stdio server on host |
| HTTP server on host ( |
| Full containerised stack |
| Mint an API key |
| psql shell into the running container |
|
|
|
|
|
|
|
|
|
|
Testing
make test # pytest --cov=booking_mcp, requires 100% coverage to passIn-memory client: tools/resources are exercised through
fastmcp.Clientagainst the server object, with no subprocess.Testcontainer Postgres: the MCP's own
create_allschema, truncated per test (real FK/types).Schema-contract test (
test_schema_contract.py): when../booking-agent/backendis 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 |
|
| The same Postgres booking-agent uses; booking-agent owns the schema, this is a client. |
|
| Set to |
|
| Must be |
| (empty) | Preferred HTTP auth. JSON array of |
| (empty) | Deprecated: single static token granting full access (all scopes). Superseded by |
|
| Mask phone numbers (last-4 digits) and addresses ( |
|
| Redirect |
| (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. |
|
| HTTP timeout (seconds) for workflow-bridge calls to booking-agent. |
|
| Cap on the client's LLM sampling call in |
|
| Connection pool size (sized for FastMCP's sync-tool threadpool). |
|
| Pool overflow beyond |
|
| Recycle connections after this many seconds. |
|
| Seconds to wait for a pooled connection. |
|
| Per-query statement timeout (ms). |
|
| Logging level. |
Notes
Schema ownership: in standalone mode (
STANDALONE_MODE=true),booking-mcp-seedbootstraps the schema withcreate_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 toolsdaily_scheduleARead-onlyIdempotent
All appointments booked on a given date.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | ISO date YYYY-MM-DD |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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_availableARead-onlyIdempotent
The first day from date (within days) with a free, qualified
staff member at time — or null if none in the window.
| Name | Required | Description | Default |
|---|---|---|---|
| service | Yes | Service/skill, e.g. 'cleaning' | |
| date | Yes | ISO date YYYY-MM-DD | |
| time | Yes | 24h time HH:MM | |
| days | No | Days ahead to search | |
| latitude | No | ||
| longitude | No | ||
| radius_km | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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_clientARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| Yes | Customer email |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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_staffARead-onlyIdempotent
List active staff, optionally filtered by a skill.
| Name | Required | Description | Default |
|---|---|---|---|
| skill | No | Filter by skill |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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_availabilityARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| service | Yes | Service/skill, e.g. 'cleaning' | |
| date | Yes | ISO date YYYY-MM-DD | |
| time | Yes | 24h time HH:MM | |
| latitude | No | Job latitude | |
| longitude | No | Job longitude | |
| radius_km | No | Search radius (km) |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
5 tool updates
v0.1.0- First observed
daily_schedule - First observed
find_next_available - First observed
get_client - First observed
list_staff - First observed
search_availability
TDQS
Scored across 5 tools
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.
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.
Five tools is an appropriate number for a booking server, covering core read operations without being excessive or insufficient.
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
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
Hosted MCP server for Cliniko — patients, appointments, availability, and invoices for AI agents.
MCP server for lacita - appointment management software
- mcp-serverOAuthio.klokin
MCP server exposing klokin time-tracking operations (employees, time entries, stores) to AI clients.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA 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.17ISC
- FlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server that connects to a PostgreSQL database to manage appointment scheduling.-
- AlicenseNot gradedqualityDmaintenanceAn 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.8MIT
- AlicenseAqualityDmaintenanceA production-grade MCP server that gives AI agents safe, authenticated access to a PostgreSQL database.3MIT