bookings-mcp
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., "@bookings-mcpcheck availability for 2 guests this Friday and show existing bookings"
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.
bookings-mcp
Replace
EltonC369in the badge URL after forking/cloning this repository.
An MCP (Model Context Protocol) server that demonstrates production-grade safety patterns for giving AI agents write access to real systems: strict read/write tool separation and approval-gated writes. Every mutating tool requires a one-time approval token that is cryptographically bound to the exact arguments a human signed off on — change one field after approval and the token is dead.
The domain is a small booking system (customers, bookings, availability) with fictional seed data. The point is not the bookings — it is the pattern for letting an agent read freely while writes stay under human control, enforced in host code rather than in the prompt.
Table of contents
Related MCP server: mcp-booking-bridge
Key features
Read/write tool separation — read tools are annotated with
readOnlyHint: true; write tools make no such claim and cannot mutate without passing the gate.Approval-gated writes —
create_bookingandcancel_bookingrequire an{ token, approvedBy }payload. Without it, the call fails schema validation; with a forged or stale token, it is blocked by theApprovalStore.Args-bound tokens — each token is bound to
SHA-256(tool name + canonicalized args)with sorted keys (theapprovalobject itself excluded). Any post-approval change to the action invalidates the token.Single-use, 10-minute TTL — tokens are consumed on first use, even when the call fails, so they cannot be replayed or used to probe the gate.
Audit trail — every tool call (read, write, or approval) is recorded with timestamp, kind, outcome, argument preview, and the reason for any block.
Two transports — stdio for local MCP hosts (Claude Desktop, Claude Code) and stateless Streamable HTTP with optional bearer-token auth for service deployments.
Tested end to end — 19 tests drive a real MCP client against the server through in-memory transports, covering the gate from every angle.
Tech stack
Layer | Choice |
Language | TypeScript (ESM, |
Runtime | Node.js >= 20 |
MCP SDK |
|
Validation |
|
HTTP transport |
|
Tests |
|
Build |
|
Prerequisites
Node.js 20 or newer
npm (the repo ships a
package-lock.json, sonpm ciworks)
Getting started
# 1. install
npm ci # or: npm install
# 2. verify — 19 tests should pass
npm test
# 3. build
npm run build
# 4a. run over stdio (default transport)
npm start # node dist/index.js
# 4b. or run over Streamable HTTP during development
npm run dev:http # tsx src/index.ts --http --port 3270
# 4c. run HTTP with a bearer token (recommended before any exposure)
BOOKINGS_MCP_TOKEN="a-long-random-secret" npm run dev:httpUseful scripts:
Command | What it does |
| Compile |
| Run the compiled server over stdio |
| Run |
| Run over Streamable HTTP on port 3270 (tsx) |
| Run the vitest suite once |
| Vitest in watch mode |
|
|
The server prints its startup banner to stderr (stdout is reserved for the stdio protocol).
Connecting an MCP client
stdio (Claude Desktop / Claude Code)
Add to your MCP client configuration (e.g. claude_desktop_config.json):
{
"mcpServers": {
"bookings": {
"command": "node",
"args": ["/absolute/path/to/bookings-mcp/dist/index.js"]
}
}
}No flags are needed — stdio is the default transport. The 7 tools appear as list_customers, get_customer, list_bookings, check_availability, request_approval, create_booking, cancel_booking.
Streamable HTTP (curl)
Start the server with npm run dev:http (optionally with BOOKINGS_MCP_TOKEN), then speak JSON-RPC to POST /mcp. The transport is stateless — no session id is issued, and GET/DELETE /mcp return 405.
BASE=http://127.0.0.1:3270/mcp
# add when a token is configured: -H "Authorization: Bearer $BOOKINGS_MCP_TOKEN"
# initialize
curl -s "$BASE" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-03-26",
"capabilities":{},
"clientInfo":{"name":"curl","version":"0.0.0"}}}'
# list tools
curl -s "$BASE" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# call a read tool
curl -s "$BASE" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
"name":"check_availability",
"arguments":{"date":"2026-11-02","times":["09:00","11:00","16:30"]}}}'Tools
Tool | Kind | Requires approval | Description |
| read | — | List all customers |
| read | — | Fetch a single customer by |
| read | — | List all bookings with their status |
| read | — | Check which of the given 30-minute slots are free on a date ( |
| approval | mints them | Queue a write action for approval; returns a one-time token bound to the exact |
| write | ✅ | Create a booking for an existing customer in a free slot |
| write | ✅ | Cancel an existing booking by |
Business rules enforced by the store: slots run 09:00–16:30 in 30-minute steps (:00/:30 only), dates are YYYY-MM-DD, double-booking a taken slot is rejected, and cancelling twice fails. The store is seeded with 3 fictional customers and 3 confirmed bookings (dates relative to server start).
The approval flow
flowchart TD
A["Agent wants to write"] -->|"request_approval { tool, args }"| B["Token minted:<br/>SHA-256(tool + canonical args)"]
B --> C{"Human reviews the<br/>EXACT action"}
C -->|"rejected"| D["No valid token —<br/>the write has no execution path"]
C -->|"approved"| E["One-time token,<br/>10-minute TTL"]
E -->|"create_booking / cancel_booking<br/>with UNCHANGED args + { token, approvedBy }"| F{"ApprovalStore.consume()<br/>tool match · args hash · TTL · single-use"}
F -->|"any check fails"| G["BLOCKED —<br/>token consumed, reason returned"]
F -->|"all pass"| H["Store mutation executes"]
G --> I["Audit entry (ok: false + reason)"]
H --> J["Audit entry (ok: true)"]Demo note: in this repository
request_approvalimmediately returns the token, standing in for the human surface. The gate mechanics — binding, single-use, TTL, audit — are the production-grade part; see Production hardening notes for wiring a real approval surface.
A full cycle, end to end. The tool results below show the JSON payload delivered in the tool's text content block.
1. The agent requests approval for the exact action:
{
"jsonrpc": "2.0", "id": 10, "method": "tools/call",
"params": {
"name": "request_approval",
"arguments": {
"tool": "create_booking",
"args": {
"customerId": 1,
"date": "2026-11-02",
"time": "11:00",
"service": "consultation"
},
"requester_note": "Ada Miranda asked for Monday 11:00"
}
}
}2. A token comes back, bound to that action:
{
"message": "Approval requested. A human must review and release this token.",
"approvalId": "9f3c81a2",
"token": "9f3c81a2c47d1e6b0a5f8d2e4c6a9b1d3e7f5a0c8b2d4e6f...",
"boundTo": { "tool": "create_booking", "args": { "customerId": 1, "date": "2026-11-02", "time": "11:00", "service": "consultation" } },
"expiresAt": "2026-11-01T09:12:04.511Z",
"note": "Pass { token, approvedBy } in the write tool. Any change to the arguments invalidates the token."
}3. A human releases the token, and the agent calls the write tool with unchanged arguments:
{
"jsonrpc": "2.0", "id": 11, "method": "tools/call",
"params": {
"name": "create_booking",
"arguments": {
"customerId": 1,
"date": "2026-11-02",
"time": "11:00",
"service": "consultation",
"approval": { "token": "9f3c81a2c47d1e6b...", "approvedBy": "elton" }
}
}
}4. The gate validates and the booking is created (returning the new booking object).
If step 3 had sent "time": "12:00" instead, the response would be:
{ "isError": true, "content": [{ "type": "text", "text": "BLOCKED BY APPROVAL GATE: Arguments changed after approval — the token is bound to the original action" }] }…and the consumed token could not be retried, even with the original arguments.
Security model
Enforcement lives in host code, not in the prompt. Write tools take
approvalas a required, schema-validated argument and route throughApprovalStore.consume()before touching the store. Why it matters: the model cannot talk its way around a check it has no ability to bypass — there is no code path from a write tool to the store's mutators that skips the gate.Approvals are bound to the exact action. The token records
SHA-256(tool name + canonicalized JSON of the arguments)— object keys sorted, theapprovalobject itself excluded from the hash. Why it matters: an agent (or an attacker) cannot get a harmless action approved and then quietly swap in different arguments; changing a single field invalidates the token.Tokens are single-use — even on failure.
consume()deletes the token before validating tool match, expiry, and hash. Why it matters: a replayed approval can never double-apply, and a blocked call cannot be retried as a probing loop against the gate.Approvals expire in 10 minutes (
APPROVAL_TTL_MS). Why it matters: a leaked or lingering token has a short window of usefulness, forcing fresh human sign-off for anything delayed.Every call is audited. Each tool invocation records timestamp, tool, kind (
read/write/approval), success, an argument preview, and — for blocked writes — the exact gate reason. Why it matters: blocked attempts are first-class evidence, not silent failures, so you can answer "what did the agent try?" after the fact.The transport is authenticated before the gate is even reached. Over HTTP, setting
BOOKINGS_MCP_TOKENrequiresAuthorization: Bearer <token>on every request; without a token the server accepts localhost clients only and logs a loud warning (it binds to127.0.0.1). Why it matters: the approval gate protects the store from the model — auth protects the server from everyone else.
Project structure
bookings-mcp/
├── src/
│ ├── index.ts # entry: stdio by default; --http for Streamable HTTP; --port <n> (default 3270)
│ ├── server.ts # buildServer() factory: McpServer + tool registration (injectable store/approvals/audit)
│ ├── transport/
│ │ └── http.ts # stateless Streamable HTTP on POST /mcp; optional bearer auth via BOOKINGS_MCP_TOKEN
│ ├── store/
│ │ ├── types.ts # Customer, Booking, Availability domain types
│ │ └── memory-store.ts # in-memory BookingStore, fictional seed data, 09:00–16:30 / 30-min slot rules
│ ├── policy/
│ │ ├── approvals.ts # ApprovalStore: SHA-256(tool + canonicalized args), single-use, 10-min TTL
│ │ └── audit.ts # AuditLog: one structured entry per tool call
│ └── tools/
│ ├── read-tools.ts # list_customers, get_customer, list_bookings, check_availability (readOnlyHint: true)
│ └── write-tools.ts # request_approval, create_booking, cancel_booking (writes demand a valid approval)
├── tests/
│ ├── read-write-separation.test.ts # readOnly annotations honored; writes without/forged approval blocked
│ ├── approval-flow.test.ts # happy path, tampered args, cross-tool replay, single-use, double-booking
│ └── audit.test.ts # every call audited; blocks recorded with the gate reason
├── package.json
└── tsconfig.jsonTesting
npm test # 3 files, 19 tests (as reported by vitest)The suite does not mock the server internals — each test connects a real MCP Client to the built server through InMemoryTransport and drives it exactly like a host would:
read-write-separation.test.ts— the four read tools carryreadOnlyHint: trueand the write tools do not; reads succeed with no approval;create_bookingwithout anapprovalargument fails schema validation; a forged 48-char token is rejected withUnknown approval token.approval-flow.test.ts— the happy path books a slot with an exact-args approval; changing the time after approval invalidates the token; acreate_bookingapproval cannot be replayed againstcancel_booking; a second use of the same token fails; and the business rule (no double-booking) still applies after the gate passes.audit.test.ts— a read → approval → write sequence produces audit entries of all three kinds with correct outcomes, and blocked writes are recorded as failures carrying the gate's reason.
Together they pin the guarantee from both sides: writes cannot happen without a valid, exact-match, unconsumed approval — and every attempt, successful or blocked, leaves a trail.
Production hardening notes
This is a demonstration of the pattern, with deliberate simplifications. To take it to production:
Persistence — swap
BookingStorefor Postgres/SQLite behind the same small interface (buildServer()accepts injected deps, so no tool code changes).Real human surface — replace the demo minting in
request_approvalwith an actual approval surface: a dashboard, a Slack "Approve/Deny" button, or a mobile tap that callsApprovalStore.request(). The gate itself needs no changes.Shared state over HTTP — stateless mode builds a fresh server per request; pass shared
{ store, approvals, audit }instances intostartHttpServer(port, deps)(or externalize them to Redis/Postgres) before scaling horizontally.Auth & TLS — always set
BOOKINGS_MCP_TOKENwhen anything other than localhost needs access; terminate TLS at a reverse proxy in front of the server.Audit sink — ship
AuditLogentries to an append-only destination (file, DB table, SIEM) instead of stderr.
License
MIT — Copyright (c) 2026 Elton Castro
Available Tools
7 toolscancel_bookingCancel a booking (approval required)A
Cancel an existing booking by id. REQUIRES a prior approval from request_approval bound to these exact arguments.
| Name | Required | Description | Default |
|---|---|---|---|
| approval | Yes | ||
| bookingId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden, and it discloses the critical behavioral gate: cancellation is blocked without a pre-issued approval linked to the exact same arguments. It adds value beyond the schema by naming request_approval as the issuer and stressing argument binding, though it says nothing about reversibility, side effects, or where an agent obtains the approval token.
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 short sentences, with the core action first and the hard precondition emphasized. Every clause earns its place; there is no boilerplate or padding.
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?
For a destructive operation with zero annotations, no output schema, and a nested approval object, the description covers the essential precondition but omits return behavior, failure modes when the approval is missing/expired/mismatched, and whether the token is single-use. Adequate but with clear gaps given the tool's risk profile.
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 0%, so the description must compensate. It does identify both parameters at a high level (bookingId as the target id, approval as a prior grant from request_approval), but it does not explain the nested token/approvedBy structure, token constraints, or why binding to exact arguments matters, leaving the nested-object semantics largely unexplained.
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?
States a specific verb and resource ('Cancel an existing booking') and the keying mechanism ('by id'), so it is clearly distinct from create_booking and check_availability. It stops short of explicitly naming the sibling it is not, but the verb/resource pairing is unambiguous.
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?
Explicitly names the prerequisite tool ('REQUIRES a prior approval from request_approval') and the condition that the approval must be bound to these exact arguments, which is real when-to-use guidance an agent could not infer. It does not state when not to use the tool, so it falls short of the top band.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
check_availabilityCheck availabilityBRead-only
Check which of the given 30-minute timeslots are available on a date (YYYY-MM-DD). Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | ||
| times | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, and the description's 'Read-only' merely restates that, earning no extra credit. It does add genuine behavioral detail beyond the annotations: the 30-minute slot granularity and the required date format, though it says nothing about timezone handling, return format, or whether results are per-customer.
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?
A single front-loaded sentence plus a two-word safety tag; nothing is padded. The trailing 'Read-only' is redundant with the annotations, which is the only wasted token.
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?
For a simple two-parameter read tool with no output schema, the description covers the core operation but leaves real gaps: no timezone, no indication of what the response contains, and no note that this is a global rather than per-customer check. Adequate but not fully self-sufficient.
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 0%, so the description must compensate, and it partially does: it pins the date format (YYYY-MM-DD) and establishes that 'times' are 30-minute slots. It never explains the HH:MM format of each time entry or the 16-item cap already present in 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?
States a specific verb (check) and a precisely scoped resource (which of the given 30-minute timeslots are available on a date), which is enough to tell it apart from the booking-mutation siblings. It stops short of explicitly naming an alternative tool, so it is clear but not maximally differentiated.
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?
There is no when-to-use guidance, no prerequisite or ordering hint (e.g. call before create_booking), and no exclusions. The agent must infer that this is the pre-booking availability probe from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_bookingCreate a booking (approval required)A
Create a booking for an existing customer in a free 30-minute slot. REQUIRES a prior approval from request_approval bound to these exact arguments.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | ||
| time | Yes | ||
| service | Yes | ||
| approval | Yes | ||
| customerId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses the authorization model (approval token bound to these exact arguments) and a uniqueness/side-effect implication ('free slot'), but says nothing about what happens on conflict, whether the approval is consumed, or how to undo the booking (cancel_booking exists but is not referenced).
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, zero filler, with the action front-loaded and the hard prerequisite escalated via capitalization so it cannot be skimmed past.
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?
For a five-required-parameter mutation with a nested approval object, no annotations, and no output schema, the description covers the critical precondition but omits conflict/error behavior, whether booking creation is atomic with approval consumption, and any note on the returned booking identity.
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 0%, so the description must compensate. It does add real meaning for two parameters: customerId must reference an existing customer, and the approval.token is bound to these exact arguments — a semantic the nested schema cannot express. However, date, time, and service are left entirely to their patterns/schema, so coverage is partial.
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?
States a specific verb and resource ('Create a booking') and narrows scope with two hard constraints: the customer must already exist and the slot must be a free 30-minute one. This is unmistakably distinct from cancel_booking, check_availability, and request_approval, so the agent can select it without opening any schema.
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?
Explicitly names the gating prerequisite and its source ('REQUIRES a prior approval from request_approval bound to these exact arguments'), which is exactly the alternative tool an agent must call first. It stops short of stating failure conditions or exclusions (e.g. what to do if the slot is not free, or that the approval is single-use).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_customerGet customerCRead-only
Fetch a single customer by id. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
'Read-only' merely restates the readOnlyHint annotation, so it adds no value beyond the structured data. The description does not disclose what happens when the id doesn't exist (error vs empty), whether results are cached, or any auth requirements.
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 short sentences, front-loaded with the core action, nothing extraneous. Slight waste in repeating the read-only fact already covered by annotations.
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?
For a single-param read tool with no output schema, this is nearly adequate, but the missing not-found/error behavior leaves an agent unable to predict the failure mode when the id doesn't match a customer.
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 0%, so the description must carry the param burden, and it does so only minimally by indicating the customer is looked up 'by id'. It doesn't clarify that the id is the customer identifier (vs e.g. a booking id) or discuss valid ranges.
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?
States a specific verb ('Fetch') and resource ('a single customer'), and the word 'single' implicitly distinguishes it from the sibling list_customers. It's clear but never names or contrasts with the sibling explicitly.
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 on when to use this versus list_customers or any other sibling. The agent must infer that this is for retrieving one known customer by id rather than enumerating customers.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_bookingsList bookingsBRead-only
List all bookings with their status. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
"Read-only" merely restates readOnlyHint=true from the annotations, adding no new behavioral information. It omits things an agent would want for a list-everything tool such as pagination, result size limits, or ordering.
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 short, front-loaded sentences with no filler, appropriate for a simple list tool. The second sentence is redundant with the annotations, which slightly dilutes the otherwise tight structure.
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?
For a parameterless list tool with no output schema, the description conveys the payload shape (bookings with status). It is still incomplete on result boundaries – no mention of pagination, volume, or ordering, which matter for an unfiltered list-all endpoint.
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 tool takes zero parameters, so the schema-carried burden is nil and the baseline is 4. The description correctly implies no filtering input is needed, matching the empty 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?
States a specific verb and resource ("List all bookings") plus the notable scope "all", and the resource alone separates it from list_customers. However it makes no explicit differentiation from siblings like check_availability or get_customer, and no indication of ordering or paging scope.
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 only guidance is the implied read-only safety context; there is no statement of when to use this versus check_availability (availability lookup), get_customer, or cancel_booking. An agent must infer the use case entirely.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_customersList customersBRead-only
List all customers. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already declares this is a safe read, and the phrase 'Read-only' merely repeats that structured fact. The description adds nothing about return shape, pagination, ordering, or volume limits, so it contributes no behavioral value beyond the annotation.
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 short sentences, front-loaded with the core action. The second sentence is largely redundant with the readOnlyHint annotation, which costs it a point but it is not verbose.
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?
For a simple, parameterless list tool with annotations covering the safety profile and no output schema to explain, the description is adequate to invoke correctly. The only real gap is absence of any hint about result size, ordering, or pagination behavior.
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 tool takes zero parameters, so the schema is empty and there is no parameter meaning to document. Baseline for a parameterless tool is 4; nothing in the description is required to clarify inputs.
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 states a specific verb (list) and resource (customers) with a scope qualifier (all), which cleanly separates it from the singular sibling get_customer. It stops short of explicitly naming or contrasting with any sibling, so it is clear but not fully differentiated.
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?
There is no statement of when to use this tool versus get_customer for a single record, or versus list_bookings for other entities. The agent must infer the use case entirely from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
request_approvalRequest human approval for a write actionA
Queue a write action (create_booking or cancel_booking) for human approval. Returns a one-time approval token bound to the EXACT arguments. The token must be passed back, unchanged, in the write tool's approval argument. This tool only mints approvals — it never executes the action itself.
| Name | Required | Description | Default |
|---|---|---|---|
| args | Yes | ||
| tool | Yes | ||
| requester_note | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: one-time token, cryptographically bound to the EXACT arguments, must be returned unchanged, and the tool never executes the action. It leaves open token expiry/TTL, whether approval is synchronous or asynchronous, and whether anything is destroyed on reuse — real gaps for a security-gating tool.
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?
Four sentences, zero filler, front-loaded with the action and scope, then the return contract, then the usage constraint, then the non-execution disclaimer. Each sentence earns its place.
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?
No output schema exists, and the description does explain the returned artifact (the token) and its use, which is the key thing an agent needs. The remaining gap is the nested free-form `args` object and the unused `requester_note`, neither of which is documented anywhere.
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 0% and there are three parameters, so the description must compensate. It effectively covers `tool` (the two enum values are named) and `args` ('bound to the EXACT arguments'), but says nothing about `requester_note`, nor about what shape `args` must take given its free-form additionalProperties object.
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?
Specific verb+resource: 'Queue a write action ... for human approval' with the two eligible write tools named explicitly. It also draws the crucial boundary against its siblings create_booking and cancel_booking ('it never executes the action itself'), so an agent can tell it apart without opening any schema.
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?
It identifies the context (before a create_booking or cancel_booking write) and describes the follow-on step: the token must be passed back in the write tool's `approval` argument. It stops short of an explicit rule such as 'never call create_booking/cancel_booking without first obtaining a token', so the when-not is implied rather than stated.
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.
7 tool updates
v1.0.0- First observed
cancel_booking - First observed
check_availability - First observed
create_booking - First observed
get_customer - First observed
list_bookings - First observed
list_customers - First observed
request_approval
TDQS
Scored across 7 tools
Each tool has a clearly distinct purpose: customer reads (list/get), booking reads (list), availability checking, an approval-minting tool, and two write tools. The request_approval tool is explicitly scoped to only mint tokens and never execute, cleanly separating it from create_booking/cancel_booking. No meaningful overlap exists.
All seven tools follow a consistent snake_case verb_noun pattern (list_customers, get_customer, check_availability, request_approval, create_booking, cancel_booking). The convention is uniform throughout with no deviations.
Seven tools is well within the ideal range and each earns its place: four read tools, one approval tool, and two write tools. Nothing feels redundant or missing at the count level.
The core booking lifecycle (check availability, approve, create, cancel) plus customer lookup is covered. Minor gaps exist, such as no get_booking by id and no reschedule/update booking operation, but agents can work around these via list_bookings and cancel+recreate.
Maintenance
Related MCP Connectors
Hosted CRM and task desk for AI agents: per-agent keys, scopes, pause and audit log.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
Related MCP Servers
- AlicenseAqualityBmaintenanceLocal zero-trust permission gateway for AI agents. Enforces policy-based tool authorization, human approvals, scoped permissions, and cryptographically verifiable audit logs.465 PyPI5Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables managing restaurant table bookings through natural language conversations with an AI assistant, with built-in safety measures like no deletion and explicit write enable.MIT
- FlicenseNot gradedqualityCmaintenanceEnables controlled AI-agent access to enterprise-shaped tools with a deny-by-default gated write path, human approval, dry-run execution, and append-only audit logging.1-
- -licenseNot gradedqualityBmaintenanceEnables AI agents to discover and invoke expert booking tools via a Streamable HTTP MCP server, supporting slot search, holds, payments, and human-in-the-loop confirmations with strict validation and time-based contracts.-