Bookwhen MCP server
Read-only MCP server for exploring a Bookwhen account's public booking data: events, tickets, locations, class passes, leaders, and attachments.
List or filter events by date (
from/to), tags, title, detail, location, calendar, and compact mode; optionally side-load locations and page through results.Get a single event with its location, tickets, leaders, and attachments in one call.
List all ticket types for an event, or get one ticket with availability, cost, and the event(s) it books onto.
List or get venues with address, directions, coordinates, and map image URL.
List class passes with usage allowance, type (
personal/any), availability, and day restriction; filter by title, detail, usage type, and numeric cost/allowance/day comparisons.List leaders with name, job title, location, bio, website, and social links; contact email/phone only when explicitly requested.
Get one event attachment with title, file name, type, size, and download URL.
All tools are read-only, send only
GET, make no writes, and expose public data only; no attendee or booking records, only counts such asattendee_countandnumber_taken.Redacts emails and phone numbers from free text by default, with
include_contact_detailsto opt out.Safely handles pagination, throttling, 429/5xx retries, ID/filter validation, foreign-host next links, and redaction in error text.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Bookwhen MCP server What's on next week, and where?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
| Events with date, attendee limit and count, spaces left, tags and location ID, from today onwards unless |
|
| 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. |
|
| Every ticket type for one event with availability and cost, following pages. |
|
| One ticket type with its availability and cost, and the event(s) it books onto (all events in the course for a course ticket). |
|
| Venues with address, additional info, coordinates and static map image URL. Filters: |
|
| One venue. |
|
| Class passes with usage allowance, type ( |
|
| Leaders (published admin profiles) with name, job title, location, bio, website and social links. Contact email and phone only with |
|
| One file attached to an event: title, file name, type, size and download URL. |
|
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: Carebit MCP server
Setup
Requires Node 18 or later.
npm install
npm run buildYou 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.jsVariable | Required | Meaning |
| yes | Your API key, sent as the HTTP Basic username with a blank password. |
| no | Defaults to |
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
readOnlyHintannotation; the server only ever sendsGET.Leaders'
contact_emailandcontact_phoneare 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+or00(including the+44 (0)7700 …form), UK numbers written with a bracketed area code such as(020) 7946 0958, and UK-style0…numbers of 9 to 11 digits with spaces, dots or hyphens between groups. Other digit strings that happen to start with0(an order number, say) are redacted too, while Bookwhen IDs such asev-sboe-20200320100000, ISO timestamps and hyphenated references are left alone; the raw text is available withinclude_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 theAuthorizationheader 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'sbasket_pathare 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/tomust beYYYYMMDDorYYYYMMDDHHMMSS; 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.nextURL the API returns (the spec'sIndexLinks, e.g.?page[offset]=20) and stops when there is none. The spec documents no page-size parameter, so none is sent. Anextlink 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 repeatednextlink 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 one request waits at most 20 seconds in all; a tool call that makes several requests (pages, lookups) can still run past the MCP client's default 60-second request timeout. Past the cap: 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 testThe test suite:
Validates every fixture record against the component schemas in Bookwhen's published OpenAPI spec (
Event,Ticket,Location,Attachment,Leader,ClassPass). The spec is downloaded fromapi.bookwhen.com/v2/openapi.yamltospec.yamlon the first run.Starts a local mock of the API under
/v2that serves those fixtures as JSON:API documents withIndexLinkspagination (self,first,prev,nextwithpage[offset], 20 per page, links only when relevant), side-loads related resources for the documentedincludeoptions, applies the documented event, location and class pass filters, answers an empty-bodied 401 with aWWW-Authenticateheader for a wrong key and an empty-bodied 404 for unknown IDs, answers the firstGET /leaderswith 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 documenteddata/linkskeys are asserted explicitly (see the note below).Starts the built server and drives it over stdio with the official MCP client: 27 checks covering every tool and its annotations, following
links.nextacross three pages to the documented end (nonext) with the page requests about 250 ms apart, stopping atmax_resultswith a note, every implementedfilter[...]option on events (all documented ones exceptentry; including comma-joined multiple values sent asopen%20day,retreaton the wire,compacttrue and false, and combined filters),include=location, the exactincludesent byget_eventandget_ticket,includedread from both the top level (JSON:API) and inside the resource (the spec's schema), theeventparameter and the next link that keeps it onGET /tickets, ticket availability arithmetic (spaces_left,nullwhen no limit is set), the location, class pass (text,usage_type, exact and operator comparisons as in the spec'sfilter[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 forRetry-Afterin 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 aRetry-Afterabove 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-hostnextlink not followed and a repeatednextlink stopping the loop, invalid IDs and filters refused before any request, the 401 and 404 messages (including a 404 whose JSON:APIerrorstext is appended after redaction), thatBOOKWHEN_ALLOW_WRITEShas no effect, and that every request usedBasic 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
EventandTicketschemas carry anincludedarray inside each resource; JSON:API (which the spec says the API follows) putsincludedat 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 steppage[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 asfilter%5Btag%5D, which decodes to the same thing. Neither form has been seen accepted by the live API.The values
filter[calendar]andfilter[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 writesYYYYMMDDHHMISS; the server accepts 8 or 14 digits.Null handling. The spec describes
number_issued,available_from,available_to,number_availableanduse_restricted_for_daysas null when unset but types them as non-nullable; the fixtures omit those fields in that case and the server treats missing andnullalike (number_issued: nullandspaces_left: nullmean "no limit set").Whether
ClassPassrecords carry a cost. The schema has no cost attribute althoughGET /class_passesis filterable bycost; 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, theirtitle/detailare appended to the message (after contact-detail redaction).The
built_basket_urlvalue is a relative path in the spec's example (/pagecode/basket_items/apply?…); the server passes it through asbasket_pathwithout 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 toolsget_attachmentGet an attachmentBRead-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).
Inputs: attachment_id (required), include_contact_details.
get_eventGet event with tickets, location, leaders and attachmentsARead-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.
Inputs: event_id (required), include_contact_details.
get_locationGet a locationBRead-onlyIdempotent
One venue: address, additional info, coordinates and map image URL.
Inputs: location_id (required), include_contact_details.
get_ticketGet a ticketCRead-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).
Inputs: ticket_id (required), include_contact_details.
list_class_passesList class passesARead-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.
Inputs: cost, title, detail, usage_type, +4 more.
list_eventsList eventsARead-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.
Inputs: to, from, tags, title, +7 more.
list_event_ticketsList tickets for an eventBRead-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).
Inputs: event_id (required), max_results, include_contact_details.
list_leadersList leadersARead-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.
Inputs: max_results, include_contact_details.
list_locationsList locationsARead-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.
Inputs: max_results, address_text, additional_info, include_contact_details.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
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.
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.
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.
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
Related MCP Connectors
Read Corsizio events, attendees and registrations, including payment status. Read-only.
WhatsApp® reminders and rescheduling for Calendly. Public read-only MCP endpoint.
Read your events, sales, orders and attendees from your AI assistant. OAuth sign-in, read only.
Read Appointedd bookings, customers and availability; create, change and cancel bookings.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables MCP clients like Claude and ChatGPT to query and manage TicketSource box office data, including events, performances, bookings, customers, and seats, through natural language.7MIT
- AlicenseBqualityCmaintenanceEnables Claude, ChatGPT and other MCP clients to read practice-management data including organization, clinicians, diaries, availability, bookings, patients, invoices, payments, staff tasks, services, and locations, and optionally create staff tasks, create bookings, and cancel bookings.15MIT
- AlicenseNot gradedqualityCmaintenanceLets MCP clients such as Claude and ChatGPT read a rota and time-and-attendance account, exposing venues, groups, shifts, absences and absence types, time entries, venue events and staff names through read-only tools that never return pay data.MIT
- AlicenseNot gradedqualityCmaintenanceEnables Claude, ChatGPT and other MCP clients to search a membership database's people and organisations, read a contact's summary and membership history, list events with ticket types and attendance lists, read invoices, and find and count segments. When writes are enabled, it also lets clients record who attended an event.MIT