Skip to main content
Glama
EltonC369

bookings-mcp

by EltonC369

bookings-mcp

CI

Replace EltonC369 in 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_booking and cancel_booking require an { token, approvedBy } payload. Without it, the call fails schema validation; with a forged or stale token, it is blocked by the ApprovalStore.

  • Args-bound tokens — each token is bound to SHA-256(tool name + canonicalized args) with sorted keys (the approval object 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, strict: true)

Runtime

Node.js >= 20

MCP SDK

@modelcontextprotocol/sdk ^1.32.1

Validation

zod ^4

HTTP transport

express ^5

Tests

vitest ^4 (MCP client over InMemoryTransport)

Build

tsc — dev loop with tsx

Prerequisites

  • Node.js 20 or newer

  • npm (the repo ships a package-lock.json, so npm ci works)

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

Useful scripts:

Command

What it does

npm run build

Compile src/ to dist/ via tsc

npm start

Run the compiled server over stdio

npm run dev

Run src/index.ts directly over stdio (tsx)

npm run dev:http

Run over Streamable HTTP on port 3270 (tsx)

npm test

Run the vitest suite once

npm run test:watch

Vitest in watch mode

npm run typecheck

tsc --noEmit

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

list_customers

read

—

List all customers

get_customer

read

—

Fetch a single customer by id

list_bookings

read

—

List all bookings with their status

check_availability

read

—

Check which of the given 30-minute slots are free on a date (YYYY-MM-DD, 1–16 times per call)

request_approval

approval

mints them

Queue a write action for approval; returns a one-time token bound to the exact tool + args. Never executes anything.

create_booking

write

✅ { token, approvedBy }

Create a booking for an existing customer in a free slot

cancel_booking

write

✅ { token, approvedBy }

Cancel an existing booking by bookingId

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_approval immediately 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

  1. Enforcement lives in host code, not in the prompt. Write tools take approval as a required, schema-validated argument and route through ApprovalStore.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.

  2. Approvals are bound to the exact action. The token records SHA-256(tool name + canonicalized JSON of the arguments) — object keys sorted, the approval object 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.

  3. 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.

  4. 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.

  5. 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.

  6. The transport is authenticated before the gate is even reached. Over HTTP, setting BOOKINGS_MCP_TOKEN requires Authorization: Bearer <token> on every request; without a token the server accepts localhost clients only and logs a loud warning (it binds to 127.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.json

Testing

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 carry readOnlyHint: true and the write tools do not; reads succeed with no approval; create_booking without an approval argument fails schema validation; a forged 48-char token is rejected with Unknown 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; a create_booking approval cannot be replayed against cancel_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 BookingStore for 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_approval with an actual approval surface: a dashboard, a Slack "Approve/Deny" button, or a mobile tap that calls ApprovalStore.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 into startHttpServer(port, deps) (or externalize them to Redis/Postgres) before scaling horizontally.

  • Auth & TLS — always set BOOKINGS_MCP_TOKEN when anything other than localhost needs access; terminate TLS at a reverse proxy in front of the server.

  • Audit sink — ship AuditLog entries to an append-only destination (file, DB table, SIEM) instead of stderr.

License

MIT — Copyright (c) 2026 Elton Castro

Available Tools

7 tools
cancel_bookingCancel a booking (approval required)A

Cancel an existing booking by id. REQUIRES a prior approval from request_approval bound to these exact arguments.

ParametersJSON Schema
NameRequiredDescriptionDefault
approvalYes
bookingIdYes

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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 availabilityB
Read-only

Check which of the given 30-minute timeslots are available on a date (YYYY-MM-DD). Read-only.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateYes
timesYes

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateYes
timeYes
serviceYes
approvalYes
customerIdYes

TDQS

A3.9/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 customerC
Read-only

Fetch a single customer by id. Read-only.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 bookingsB
Read-only

List all bookings with their status. Read-only.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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 customersB
Read-only

List all customers. Read-only.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

B3.2/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
argsYes
toolYes
requester_noteNo

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 7 tool updatesv1.0.0
    • First observedcancel_booking
    • First observedcheck_availability
    • First observedcreate_booking
    • First observedget_customer
    • First observedlist_bookings
    • First observedlist_customers
    • First observedrequest_approval

TDQS

A3.7/5.0

Scored across 7 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Local zero-trust permission gateway for AI agents. Enforces policy-based tool authorization, human approvals, scoped permissions, and cryptographically verifiable audit logs.
    4
    65 PyPI
    5
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
    -
  • -
    license
    Not graded
    quality
    B
    maintenance
    Enables 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.
    -