Skip to main content
Glama
dragosh29

Bookwhen MCP server

by dragosh29

Bookwhen MCP server

An MCP server that lets Claude, ChatGPT and other MCP clients read a Bookwhen account's public booking data: events (classes, courses, workshops), their tickets and availability, locations, class passes, leaders and attachments. It is built from Bookwhen's public API documentation and its published OpenAPI spec (https://api.bookwhen.com/v2/openapi.yaml).

Bookwhen's public API is read-only and exposes public data only. It has no write endpoints, so this server has no write tools and no "allow writes" setting, and it returns no attendee or booking records: attendee_count on an event and number_taken on a ticket are counts, and nothing here can say who booked.

Once it's connected, someone on the account can ask things like:

  • "What's on next week, and where?"

  • "Is there space on Tuesday's beginners class, and what does a ticket cost?"

  • "Which tickets does the pottery course sell, and how many are left?"

  • "Which class passes cover ten or more classes?"

  • "Who leads the retreat weekend, and what does their profile say?"

Tools

Tool

What it does

API calls

list_events

Events with date, attendee limit and count, spaces left, tags and location ID, from today onwards unless from is given (the API's default). Filters: from/to (YYYYMMDD or YYYYMMDDHHMMSS), tags, title, detail, location, calendar, compact; multiple values within one filter are sent comma-separated, and filters combine with AND. include_location side-loads each event's location. Pages are followed via the API's links.next until it reports no next page or max_results is reached.

GET /events

get_event

One event with its location, every ticket type with availability (number issued, taken, spaces left, cost, availability window, group and course flags), its leaders and its attachments, side-loaded in one call.

GET /events/{event_id}?include=location,tickets,attachments,leaders

list_event_tickets

Every ticket type for one event with availability and cost, following pages.

GET /tickets?event={event_id}

get_ticket

One ticket type with its availability and cost, and the event(s) it books onto (all events in the course for a course ticket).

GET /tickets/{ticket_id}?include=events

list_locations

Venues with address, additional info, coordinates and static map image URL. Filters: address_text, additional_info.

GET /locations

get_location

One venue.

GET /locations/{location_id}

list_class_passes

Class passes with usage allowance, type (personal or any), number available and day restriction. Filters: title, detail, usage_type, and cost, usage_allowance and use_restricted_for_days as an exact value or with gt/gte/lt/lte/eq (an empty operator object or an unknown operator name is refused rather than sent as no filter).

GET /class_passes

list_leaders

Leaders (published admin profiles) with name, job title, location, bio, website and social links. Contact email and phone only with include_contact_details.

GET /leaders

get_attachment

One file attached to an event: title, file name, type, size and download URL.

GET /attachments/{attachment_id}

Not covered on purpose: GET /attachments (list), GET /leaders/{leader_id}, GET /class_passes/{class_pass_id}, the entry filter on events, and the tickets.events, tickets.class_passes, events.location, events.tickets and events.attachments include paths. Ticket costs are passed through as the API states them, in the currency's smallest unit (the spec's example: 1000 is $10); the server does not convert them.

Related MCP server: booking_chest

Setup

Requires Node 18 or later.

npm install
npm run build

You need an API key for your Bookwhen account, generated in Bookwhen under API tokens setup (admin.bookwhen.com/settings/api_access_permission_sets). The API authenticates with HTTP Basic: the key is the username and the password is blank, which is what this server sends.

Claude Desktop: add this to claude_desktop_config.json:

{
  "mcpServers": {
    "bookwhen": {
      "command": "node",
      "args": ["/absolute/path/to/bookwhen-mcp/dist/index.js"],
      "env": { "BOOKWHEN_API_KEY": "your-key" }
    }
  }
}

Claude Code:

claude mcp add bookwhen -e BOOKWHEN_API_KEY=your-key -- node /absolute/path/to/bookwhen-mcp/dist/index.js

Variable

Required

Meaning

BOOKWHEN_API_KEY

yes

Your API key, sent as the HTTP Basic username with a blank password.

BOOKWHEN_BASE_URL

no

Defaults to https://api.bookwhen.com/v2. Used by the tests.

There is no BOOKWHEN_ALLOW_WRITES: the API documents no write endpoint, so there is nothing to enable. Setting it has no effect (the tests check this).

Safety defaults

  • Every tool is read-only and carries the MCP readOnlyHint annotation; the server only ever sends GET.

  • Leaders' contact_email and contact_phone are only returned when the assistant explicitly asks (include_contact_details), even though the spec labels them public. In free text (event titles, details and tags, ticket titles and details, location address and additional info, leader names, job titles, locations, bios, websites and social feed types, attachment titles and file names, class pass titles and details) email addresses are replaced with [email redacted] and phone-number-like sequences with [phone redacted] by default. The phone match is a heuristic: it covers international numbers written with + or 00 (including the +44 (0)7700 … form), UK numbers written with a bracketed area code such as (020) 7946 0958, and UK-style 0… numbers of 9 to 11 digits with spaces, dots or hyphens between groups. Other digit strings that happen to start with 0 (an order number, say) are redacted too, while Bookwhen IDs such as ev-sboe-20200320100000, ISO timestamps and hyphenated references are left alone; the raw text is available with include_contact_details. The same redaction is applied to any error text the API returns before it is passed on, including the quoted start of a non-JSON body; in that error text the API key and the Authorization header value are also replaced with [key redacted], in case a live response or a proxy ever echoes them (the spec documents 401 and 404 bodies as empty). Redaction runs on the whole body before it is cut short for the message.

  • Image, map, avatar, file and social feed URLs (image_url, map_url, avatar_url, file_url, social_feeds[].url) and the ticket's basket_path are returned as stored; the redaction is not applied to them.

  • IDs are checked before any call is made: they must be short strings of letters, digits, _ and - (up to 80 characters, no slashes, spaces or query characters), because the spec types every ID as a plain string and documents no format (its examples: ev-sboe-20200320100000, ti-sboe-20200320100000-tk1m, sjm7pskr31t3, 9v06h1cbv0en, g189gdkucw7r, cp-vk3x1brhpsbf). from/to must be YYYYMMDD or YYYYMMDDHHMMSS; a tag, title, detail, location or calendar value may not contain a comma, because the API separates multiple values with commas. Filter values are sent as in the spec's example (filter[tag]=tag%20one,tag%20two): spaces as %20, the separating comma literal, everything else percent-encoded.

  • Pagination follows the links.next URL the API returns (the spec's IndexLinks, e.g. ?page[offset]=20) and stops when there is none. The spec documents no page-size parameter, so none is sent. A next link that is not under the configured API base URL (host and path, e.g. /v2) is never followed, so the API key cannot be sent anywhere else; the result then names the link's host and path and is marked incomplete. A repeated next link stops the loop.

  • Bookwhen does not document a rate limit. Requests are spaced 250 ms apart (about four per second). A 429 is retried at most twice, waiting for Retry-After (whole or fractional seconds, or an HTTP-date; 2 s then 4 s when the header is absent or unreadable). Each wait is capped at 10 seconds so a tool call stays under the MCP client's default 60-second request timeout: if Bookwhen asks for a longer wait the call gives up at once and the message says how long to wait.

  • 502, 503 and 504 are retried the same way for GET (which is every request this server makes); when all three attempts fail the error says the service may be unavailable and to try again in a few minutes, without the gateway's HTML.

  • A 200 whose body is not JSON (a proxy or a login page in the way) is reported as an error naming BOOKWHEN_BASE_URL, never as an empty list.

  • A rejected API key produces a message that says which variable to fix and where keys come from. The spec documents 401 and 404 with empty bodies; a 404 is reported with the path and "Check the ID", and a 404 from GET /tickets?event=… names the event ID.

Tests

npm test

The test suite:

  1. Validates every fixture record against the component schemas in Bookwhen's published OpenAPI spec (Event, Ticket, Location, Attachment, Leader, ClassPass). The spec is downloaded from api.bookwhen.com/v2/openapi.yaml to spec.yaml on the first run.

  2. Starts a local mock of the API under /v2 that serves those fixtures as JSON:API documents with IndexLinks pagination (self, first, prev, next with page[offset], 20 per page, links only when relevant), side-loads related resources for the documented include options, applies the documented event, location and class pass filters, answers an empty-bodied 401 with a WWW-Authenticate header for a wrong key and an empty-bodied 404 for unknown IDs, answers the first GET /leaders with a 429, and records each request line as sent so the wire encoding of filter values can be checked. Each of the mock's list and detail responses is validated against the operation's documented 200 response schema, and the documented data/links keys are asserted explicitly (see the note below).

  3. Starts the built server and drives it over stdio with the official MCP client: 27 checks covering every tool and its annotations, following links.next across three pages to the documented end (no next) with the page requests about 250 ms apart, stopping at max_results with a note, every implemented filter[...] option on events (all documented ones except entry; including comma-joined multiple values sent as open%20day,retreat on the wire, compact true and false, and combined filters), include=location, the exact include sent by get_event and get_ticket, included read from both the top level (JSON:API) and inside the resource (the spec's schema), the event parameter and the next link that keeps it on GET /tickets, ticket availability arithmetic (spaces_left, null when no limit is set), the location, class pass (text, usage_type, exact and operator comparisons as in the spec's filter[cost][gte] example; {} and unknown operator names refused before any request; the cost note kept when the list is capped) and leader tools, redaction of emails and phone numbers by default in event details and tags, ticket details, location info, leader bios and attachment titles, leader contact fields withheld by default and all of these returned on request, the 429 retry waiting for Retry-After in the seconds, fractional-seconds and HTTP-date forms and falling back to 2 s when the header is absent, giving up after three attempts on a persistent 429 and at once on a Retry-After above the cap, a 502 and a 504 retried and a 503 failing three times reported with advice, a 200 with a non-JSON body reported as an error with the quoted start of the body redacted, the API key and Basic header value scrubbed from 401, 404, 503 and non-JSON error text that echoes them, a foreign-host next link not followed and a repeated next link stopping the loop, invalid IDs and filters refused before any request, the 401 and 404 messages (including a 404 whose JSON:API errors text is appended after redaction), that BOOKWHEN_ALLOW_WRITES has no effect, and that every request used Basic base64(key:), a documented method and path, and only documented query parameters.

Note that the spec marks no field as required on any schema, so schema validation only proves the types of fields that are present. Step 2 therefore also asserts that the documented keys are present in the mock's responses; the fixture records themselves are only type-checked.

Status

This is a working prototype. It has not yet been run against the live API, because it was built without a Bookwhen account. Everything below is taken from the published spec and should be confirmed on a real account:

  • Where the API puts side-loaded resources. The spec's Event and Ticket schemas carry an included array inside each resource; JSON:API (which the spec says the API follows) puts included at the top level of the document. The server reads both and the tests exercise both; only one will be real.

  • The page size and the exact form of links.next. The spec's examples step page[offset] by 20; the mock uses 20. The server sends no page-size parameter because none is documented.

  • What GET /tickets?event=… returns for an unknown event. The spec lists only 200 and 401 for that operation; the server reads a 404 as "no such event" and would pass an empty list through as "no tickets".

  • The wire encoding of filter values. The server sends them as the spec's example shows (filter[tag]=tag%20one,tag%20two: spaces as %20, the separating comma literal); the parameter names go out as filter%5Btag%5D, which decodes to the same thing. Neither form has been seen accepted by the live API.

  • The values filter[calendar] and filter[location] expect. The spec says "calendars (schedule pages)" and "location slugs"; the server sends what it is given, and the mock treats a location slug as the location's ID.

  • Whether multiple values in one filter (filter[tag]=a,b) match any or all of them. The mock matches any; the server just passes the comma-joined list through.

  • The exact format of filter[from]/filter[to] with a time part. The spec writes YYYYMMDDHHMISS; the server accepts 8 or 14 digits.

  • Null handling. The spec describes number_issued, available_from, available_to, number_available and use_restricted_for_days as null when unset but types them as non-nullable; the fixtures omit those fields in that case and the server treats missing and null alike (number_issued: null and spaces_left: null mean "no limit set").

  • Whether ClassPass records carry a cost. The schema has no cost attribute although GET /class_passes is filterable by cost; the server applies the filter and says in its output that no cost is shown.

  • The bodies of 401 and 404 responses. The spec documents them as empty; if the live API returns JSON:API errors, their title/detail are appended to the message (after contact-detail redaction).

  • The built_basket_url value is a relative path in the spec's example (/pagecode/basket_items/apply?…); the server passes it through as basket_path without guessing the host.

  • How many requests per second the API tolerates and whether it ever returns 429 or Retry-After; the spec says nothing, so the throttle here is a guess on the polite side.

  • Whether an API key with a restricted permission set answers 401 or 403 for an endpoint it may not read; the server treats both as a key problem.

Going to production

This version runs locally over stdio, with the account holder's own API key. For customers to connect from claude.ai or ChatGPT without handling keys, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by Bookwhen, and then a listing in the Claude and ChatGPT connector directories.

Licence

MIT. Built by Alexandru Dragoș (alexandru.dragos96@gmail.com) with an AI agent (Claude) working under his direction.

Available Tools

9 tools
get_attachmentGet an attachmentB
Read-onlyIdempotent

One file attached to an event: title, file name, type, size and the download URL (the spec says to use this URL rather than the one it forwards to).

ParametersJSON Schema
NameRequiredDescriptionDefault
attachment_idYesAttachment ID
include_contact_detailsNoStop redacting email addresses and phone numbers from the title and file name

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, openWorldHint and non-destructive behavior, so the safety profile is covered. The description adds one genuinely useful piece of context – to use the returned download URL rather than the one it forwards to – but says nothing about error or permission behavior.

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, tightly packed sentence with no filler; the resource and its fields are front-loaded. The trailing parenthetical about the download URL is a slight digression but 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?

With no output schema, the description usefully enumerates the returned fields (title, file name, type, size, URL), which compensates for the absence of a return contract. Only the when-to-use guidance is missing for what is otherwise a simple, well-annotated read tool.

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

Parameters3/5

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

Schema description coverage is 100%, with both attachment_id and include_contact_details documented in the schema (including the redaction semantics). The description adds no parameter-level meaning beyond the schema, so the baseline 3 applies.

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 names the resource (one file attached to an event) and enumerates the fields it returns, so the agent knows exactly what it fetches. It does not explicitly differentiate from siblings like get_event or get_ticket, but the resource is specific enough to be unambiguous.

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 alternatives, no prerequisites, and no exclusions. The agent must infer from the bare purpose that this is a single-attachment fetch by ID.

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

get_eventGet event with tickets, location, leaders and attachmentsA
Read-onlyIdempotent

One event with its location, every ticket type with availability (number issued, taken, spaces left, cost), its leaders and its attachments, side-loaded in a single call.

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idYesEvent ID
include_contact_detailsNoInclude leaders' contact email and phone, and stop redacting email addresses and phone numbers from free text

TDQS

A3.8/5.0
Behavior4/5

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

Annotations already carry the safety profile (readOnly, idempotent, non-destructive, openWorld), so the description's job is to add payload context — and it does, spelling out that ticket types include issued/taken/spaces-left/cost and that related entities are side-loaded rather than requiring extra calls. It does not mention the redaction behavior of include_contact_details, a minor gap.

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

Conciseness5/5

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

A single tight sentence that front-loads the resource and then enumerates the payload. Every clause earns its place and there is no filler.

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?

With no output schema, the description must convey the return shape, and it does so adequately: location, per-ticket-type availability counters and cost, leaders, attachments, all side-loaded. It stops short of noting the contact-detail unredaction toggle, which is the one behavior an agent might need to know before calling.

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 coverage is 100% with only 2 parameters, so the schema already documents both event_id and include_contact_details with its redaction semantics. The description adds no parameter-level detail beyond implying leaders/tickets are returned, making the baseline 3 appropriate.

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 names a specific resource (one event) and enumerates the side-loaded payload (location, ticket types with availability, leaders, attachments), which clearly separates it from the list_* siblings. It lacks an explicit verb but the scope ('one event') makes the single-record intent unambiguous.

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

Usage Guidelines3/5

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

Usage is only implied: 'side-loaded in a single call' hints that this is preferable to fanning out across get_location/get_ticket/list_leaders, but the description never states when to use it versus those siblings or any exclusions. An agent can infer retrieval-by-id usage but gets no explicit routing guidance.

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

get_locationGet a locationB
Read-onlyIdempotent

One venue: address, additional info, coordinates and map image URL.

ParametersJSON Schema
NameRequiredDescriptionDefault
location_idYesLocation ID (slug)
include_contact_detailsNoStop redacting email addresses and phone numbers from address and additional info text

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, idempotentHint=true, destructiveHint=false, and openWorldHint=true, so the safety profile is fully covered by structured data. The description adds only that a map image URL and coordinates come back, which is modest output context rather than behavioral disclosure. It does not mention the contact-detail redaction default, though that is documented in the schema.

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 compact sentence-fragment with no filler and the return payload front-loaded. It is terse to the point of omitting a verb, which slightly weakens readability, but nothing is wasted.

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?

With rich annotations, 100% schema coverage, and no output schema, the description's main remaining job is to convey what comes back, which it does by listing the four payload components. Nothing an agent needs to invoke it correctly is missing, though it could say more about single-vs-list selection.

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

Parameters3/5

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

Schema description coverage is 100% – both location_id (slug pattern) and include_contact_details (redaction toggle) are fully documented in the schema. The description adds nothing about parameters, so the baseline 3 applies.

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 names the resource ('One venue') and enumerates exactly what the call returns (address, additional info, coordinates, map image URL). The verb is only implied by the tool name, and it never explicitly contrasts itself with the sibling list_locations, so it stops short of a 5.

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 stated when-to-use, no prerequisites, and no named alternative. The phrase 'One venue' hints that this is the single-item counterpart to list_locations, but that routing decision is left entirely to inference.

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

get_ticketGet a ticketC
Read-onlyIdempotent

One ticket type with its availability and cost, and the event(s) it books onto (all events in the course for a course ticket).

ParametersJSON Schema
NameRequiredDescriptionDefault
ticket_idYesTicket ID
include_contact_detailsNoStop redacting email addresses and phone numbers from free text

TDQS

C2.9/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false, so the safety profile is covered. The description adds useful context about the payload (availability, cost, and the event(s) booked, with course-wide events for course tickets), but says nothing about redaction behavior, errors for unknown IDs, or pagination.

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 compact sentence with no filler, though the missing subject/verb makes it read as a fragment rather than a properly front-loaded statement of what the tool does.

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?

With no output schema, the description carries the return-value burden and does a partial job by naming the returned fields. However, for a read tool whose annotations are already informative, the absence of usage context and any mention of the contact-detail toggle leaves it only minimally adequate.

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

Parameters3/5

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

Schema description coverage is 100%, with both ticket_id and include_contact_details documented in the schema, so the baseline is 3. The description adds no parameter-level detail (e.g., ID format constraints or the effect of toggling contact details) beyond what the schema already states.

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

Purpose3/5

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

The description conveys the resource (a ticket type) and enumerates what it returns (availability, cost, booked events), but it is a verbless fragment that never states the action ('retrieve/fetch by ID'). It also does not differentiate from siblings like get_event, get_location, or list_event_tickets.

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 alternatives such as list_event_tickets or get_event. The only hint about usage is the implicit assumption that a ticket_id is already known, which the agent must infer.

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

list_class_passesList class passesA
Read-onlyIdempotent

Class passes (pre-purchased bundles of bookings) with usage allowance, type (personal or any attendee), number available and day restriction. Filters: title or details text, usage type, and cost, usage_allowance or use_restricted_for_days as an exact value or comparison (gt, gte, lt, lte, eq). Cost values are in the currency's smallest unit, as in the spec's example filter[cost][gte]=2000.

ParametersJSON Schema
NameRequiredDescriptionDefault
costNoCost in the currency's smallest unit, exact or with operators (filter[cost], filter[cost][gte] ...)
titleNoText in the pass title (filter[title])
detailNoText in the pass details (filter[detail])
usage_typeNopersonal: booker only; any: additional attendees too (filter[usage_type])
max_resultsNo
usage_allowanceNoNumber of classes the pass covers, exact or with operators (filter[usage_allowance])
include_contact_detailsNoStop redacting email addresses and phone numbers from titles and details
use_restricted_for_daysNoDays the pass stays valid from first use, exact or with operators (filter[use_restricted_for_days])

TDQS

A3.5/5.0
Behavior3/5

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

Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is fully covered. The description adds data-model context (what a class pass is, cost unit convention) but says nothing about the redaction behavior of include_contact_details or any auth/rate-limit traits beyond what annotations give.

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 dense sentences that front-load the data-model definition before the filter list. There is some redundancy with the schema on filter names and the cost unit, but no filler.

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?

With no output schema, the description usefully compensates by naming the returned fields (usage allowance, type, number available, day restriction). Combined with the filter coverage and the safety annotations, an agent has enough to invoke it correctly; only the redaction semantics of include_contact_details are left to the schema.

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 88%, so the schema already documents every filter including the comparison-operator shape and the cost unit. The description largely restates these (usage type, cost, usage_allowance, use_restricted_for_days) and adds only one concrete example (filter[cost][gte]=2000), so baseline 3 is appropriate.

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 resource (class passes) and defines it parenthetically as 'pre-purchased bundles of bookings', then enumerates the fields it returns (usage allowance, type, number available, day restriction). The verb is implied by the name rather than stated, and no sibling is named, but the resource is unambiguous and clearly distinct from get_event/list_events siblings.

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

Usage Guidelines3/5

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

The description lists the available filters, which implies how to narrow a search, but never states when to use this tool, when not to, or what alternative exists. Usage is inferred from the filter catalogue rather than spelled out.

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

list_eventsList eventsA
Read-onlyIdempotent

List events (classes, courses, workshops) with date, attendee limit and count, tags and location ID. The API returns events from today onwards unless from is given; every filter is combined with AND, and multiple values within one filter are sent comma-separated. Pages are followed until the API reports no next page or max_results is reached.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoNon-inclusive end, YYYYMMDD or YYYYMMDDHHMMSS (filter[to])
fromNoInclusive start, YYYYMMDD or YYYYMMDDHHMMSS (filter[from]; the API defaults to today)
tagsNoTag words to include (filter[tag])
titleNoEntry titles to search for (filter[title])
detailNoEntry details text to search for (filter[detail])
compactNoCombine the events of a course into a single virtual event (filter[compact])
calendarNoCalendars (schedule pages) to restrict to (filter[calendar])
locationNoLocation slugs to include (filter[location])
max_resultsNoMaximum number of events to return
include_locationNoSide-load each event's location (include=location). Slower; the API recommends includes for single-event requests
include_contact_detailsNoStop redacting email addresses and phone numbers from titles, details and location text

TDQS

A4.1/5.0
Behavior5/5

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

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, openWorld), and the description adds substantial extra behavior: the default date window when `from` is omitted, AND-combination of filters, comma-separated multi-values within one filter, and automatic pagination until no next page or max_results. These are traits an agent could not infer from the structured fields.

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?

Three tight sentences: purpose and scope first, then defaults and filter-combination rules, then pagination. No filler, and the most decision-relevant information (default date window) is front-loaded.

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?

With 11 optional parameters, no output schema, and a fully documented schema, the description is nearly sufficient: it explains filtering semantics, defaults, and pagination. It could say more about ordering or what pagination metadata accompanies the events, but nothing an agent needs in order to invoke it correctly is missing.

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?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine cross-parameter semantics the schema does not convey: every filter is AND-ed together and multiple values inside a single filter are sent comma-separated. That is real meaning beyond per-parameter docs, though individual parameter formats are left to 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?

Names a specific verb and resource and disambiguates the resource with synonyms ('classes, courses, workshops'), plus lists the returned fields (date, attendee limit and count, tags, location ID). It does not explicitly distinguish itself from siblings like list_class_passes or get_event, so it stops short of the top band.

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

Usage Guidelines3/5

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

The description gives operating context (default range is today onwards, filters combined with AND) but never states when to choose this tool over get_event or the other list_* siblings, nor any when-not conditions. Usage is implied rather than prescribed.

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

list_event_ticketsList tickets for an eventB
Read-onlyIdempotent

Every ticket type for one event with availability: number issued (null = no limit), number taken (booked or reserved in checkout), spaces left, availability window, cost, and whether it is a group ticket (min/max people) or a course ticket (books all events in the course).

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idYesEvent ID (required by GET /tickets)
max_resultsNo
include_contact_detailsNoStop redacting email addresses and phone numbers from ticket titles and details

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already declare readOnlyHint/idempotentHint/destructiveHint=false, so the safety profile is covered. The description adds data semantics ('null = no limit', 'taken = booked or reserved in checkout'), which is useful, but says nothing about auth needs, rate limits, pagination, or redaction behavior. Moderate added value against an already-covered safety profile.

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 dense sentence front-loaded with the resource and scope, then the field inventory. No filler, though the run-on list of fields is slightly harder to parse than separate clauses.

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?

There is no output schema, so the description carries the return-value burden and does so thoroughly, enumerating the fields the caller receives and clarifying ambiguous ones (null=unlimited, reserved-in-checkout, group/course ticket). The remaining gaps are behavioral (max_results/pagination) rather than structural.

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 67%, the mid-band baseline. The description adds no parameter guidance at all: event_id is only implied by 'for one event', max_results is undocumented anywhere, and include_contact_details (a surprising redaction toggle) is explained only by the schema. It does not compensate for the coverage gap.

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?

Specific verb+resource-scope: 'Every ticket type for one event with availability.' An agent can tell this lists ticket types rather than fetching a single ticket (get_ticket) or event-level data (get_event). It does not explicitly name a sibling, so it lands at 4 rather than 5.

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 when-to-use, when-not-to-use, or alternative guidance. It never mentions events as a precondition in a routing sense (only implies it via the field list) and never contrasts itself with get_ticket or list_class_passes. The agent must infer fit from the purpose alone.

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

list_leadersList leadersA
Read-onlyIdempotent

Leaders (published admin profiles: the people running or teaching sessions) with name, job title, location, bio, website and social links. Their contact email and phone are only returned with include_contact_details.

ParametersJSON Schema
NameRequiredDescriptionDefault
max_resultsNo
include_contact_detailsNoInclude each leader's contact email and phone, and stop redacting email addresses and phone numbers from bios

TDQS

A3.5/5.0
Behavior4/5

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

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the description's job is to add context — and it does: it discloses that only 'published' profiles are returned and that email/phone are redacted unless include_contact_details is set. It omits any mention of pagination or result ordering, which the max_results parameter makes relevant.

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 sentences, no filler, and the resource definition plus returned fields are front-loaded before the redaction caveat. Efficient, though the parenthetical definition is slightly dense.

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?

With no output schema, the field enumeration is necessary and present, and the redaction rule is disclosed. The remaining gap is max_results semantics (maximum 500, default 100, whether results are paginated), which an agent would want before calling.

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 coverage is 50%: include_contact_details is already described in the schema, and the description's statement about it repeats that effect (with the added detail that bios are un-redacted). max_results is undocumented in both the schema and the description, so the description fails to compensate for the coverage gap.

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 the resource ('Leaders') and defines it parenthetically as published admin profiles, then enumerates the returned fields (name, job title, location, bio, website, social links). That is specific but stops short of an explicit verb such as 'list all'; it is clear largely because the title and no competing sibling tool exist for leaders.

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 description never says when to call this tool, what context it applies to, or what alternatives exist. The only conditional information ('contact email and phone are only returned with include_contact_details') is a parameter effect, not usage guidance.

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

list_locationsList locationsA
Read-onlyIdempotent

Venues with address, extra directions, coordinates and a static map image URL. Optionally filter by text in the address or in the additional info.

ParametersJSON Schema
NameRequiredDescriptionDefault
max_resultsNo
address_textNoOnly locations whose address contains this text (filter[address_text])
additional_infoNoOnly locations whose additional info contains this text (filter[additional_info])
include_contact_detailsNoStop redacting email addresses and phone numbers from address and additional info text

TDQS

A3.5/5.0
Behavior3/5

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

Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds the notable return contents (static map image URL, coordinates), but it omits the significant behavioral fact that contact details are redacted by default, which is only surfaced in the schema.

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 compact sentences with the resource and its returned fields front-loaded, and the filtering note appended. No wasted text, though it is terse enough that some needed information is absent.

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?

There is no output schema, so the description carries return-value burden and does describe the principal fields a caller receives. It is largely sufficient, though it never notes the default redaction of email/phone, which an agent should know before relying on contact text.

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 75%, so most parameters are already documented. The description connects address_text and additional_info to their filtering role but adds no syntax or semantics beyond the schema, and says nothing about max_results or the contact-detail redaction toggle.

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 names the resource (venues/locations) and enumerates the fields returned (address, directions, coordinates, static map image URL), so the agent knows what domain object it deals with. It never explicitly states the verb 'list all' and does not name the sibling get_location, leaving some differentiation work to the tool name.

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

Usage Guidelines3/5

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

It states that filtering by address or additional-info text is optional, which is useful usage context. However, it never says when to prefer this over get_location for a single venue, nor gives any prerequisite or exclusion condition.

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. 9 tool updatesv0.1.0
    • First observedget_attachment
    • First observedget_event
    • First observedget_location
    • First observedget_ticket
    • First observedlist_class_passes
    • First observedlist_event_tickets
    • First observedlist_events
    • First observedlist_leaders
    • First observedlist_locations

TDQS

A3.5/5.0

Scored across 9 tools

Disambiguation4/5

Tools are largely distinct, with clear get/list pairs for events, tickets, and locations. get_event side-loads tickets, leaders, and attachments that other specialized tools also expose, creating mild overlap, but descriptions help an agent choose correctly.

Naming Consistency5/5

All tool names use consistent snake_case with get_ or list_ prefixes and follow a predictable verb_noun pattern. There are no mixed conventions or vague verbs.

Tool Count5/5

With 9 tools, the server is well-scoped for a read-oriented event-booking query surface. Each tool covers a distinct resource or action without obvious redundancy.

Completeness3/5

The surface covers read/query operations across events, tickets, locations, leaders, attachments, and class passes. However, it lacks any create, update, delete, or booking/checkout operations, which is a notable lifecycle gap for a booking platform.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to search, filter, and analyze Microsoft events (conferences, workshops, webinars) using the Microsoft Events API.
    4
    2
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    MCP server for Cal.com scheduling, providing ~70 tools to manage schedules, event types, bookings, calendars, webhooks, and teams. Enables natural language control of Cal.com from Claude or any MCP-compatible client.
    68
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides structured, read-mostly access to small-business back-office data including customers, invoices, and account notes, allowing Claude to query overdue invoices, revenue summaries, and more.
    MIT