Carebit MCP server
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., "@Carebit MCP serverWhat does Dr Ada Example have on tomorrow, and are there gaps?"
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.
Carebit MCP server
An MCP server that lets Claude, ChatGPT and other MCP clients work with a Carebit private-practice Organization: clinicians and their diaries, bookable slots, bookings, patients, invoices, payments, the staff task queue, services and locations, and (when enabled) creating staff tasks, creating bookings and cancelling bookings. It is built from Carebit's public developer documentation: the OpenAPI 3.1 document at carebit.dev/openapi.json and the guides on authentication, testing, pagination and filtering, idempotency and retries, validation errors and rate limits.
Once it's connected, someone at the practice can ask things like:
"What does Dr Ada Example have on tomorrow, and are there gaps?"
"When is the next free new-patient slot with Dr Example?"
"Which invoices are overdue, and how much is outstanding?"
"What was refunded this month?"
"What's still open in the task queue for Jo?"
With writes enabled: "Add a task for Kim to chase the insurer by Friday." / "Book Priya Shah a follow-up with Dr Example at 9 on the 12th." / "Cancel that booking, it was made in error."
Tools
Tool | What it does | API calls |
| The Organization the credential belongs to (name, type, contact details, address, currency, time zone) and, by default, the token's granted scopes and developer project. |
|
| Clinicians with display name, title and specialty. Practice email addresses only on request. |
|
| One clinician. |
|
| A clinician's diary for up to 45 days: bookings, availability and unavailability intervals in time order. Patient names are shown; contact details and the booking's free-text information only on request. |
|
| Bookable slots for a clinician and service variant: every slot in a date range (up to 45 days), or the next slot from a date (up to 8 months ahead). |
|
| Bookings in a time window (30 days, or 90 with |
|
| One booking: status, times, clinician, patient, service and variants, location, payor, recall and cancellation details. |
|
| Connected patients with the documented exact-match filters |
|
| One patient. |
|
| Invoices with status, totals, outstanding and paid amounts and line items; filters |
|
| One invoice with its line items. |
|
| Payments with status, amount, method type, payor type and refunds; filters |
|
| The staff task queue, newest first; filters |
|
| Services with duration, tax rate and their variants (the |
|
| The Organization's locations with addresses. |
|
| Adds a task to the queue with content, due date, urgency, optional patient, document and staff-member assignees. Writes only. |
|
| Creates a diary booking for a patient, service and variant at a UTC start time, optionally with clinician, location, end time, status, remote flag and information fields. |
|
| Cancels a booking with an optional documented reason code and note. Writes only, marked destructive. |
|
Not covered on purpose: staff members, payors, insurance companies and alternative payors, payment methods (stored cards), creating payments and refunds, creating invoices and quotes, patient creation and updates, patient connections, leads, letters, notes, photos, test results, digital forms, booking forms, lists, recall programmes, availability periods, reports, expirable files, transmissions, webhooks, PATCH /v1/bookings/{id} and PATCH /v1/human_tasks/{id}, and the organization search. The test suite asserts that the stored-card, payor, note, letter, test-result and report endpoints are never called.
Setup
Requires Node 18 or later.
npm install
npm run buildYou need an API credential for your Carebit Organization. As the authentication guide documents: sign in to Carebit as a staff member, go to Settings > Developer platform, create a project with the scopes you need, and create an API credential (a client_id and a client_secret) for it. The server exchanges them for a Bearer access token at POST /oauth/token (OAuth 2.0 client credentials, form-encoded), which the guide's example says lasts an hour (expires_in: 3600).
The read tools need these scopes on the project: organization.read, clinicians.read, clinician_agenda.read, availability_slots.read, bookings.read, patients.read, invoices.read, payments.read, human_tasks.read, services.read, locations.read. The write tools need human_tasks.create, bookings.create and bookings.cancel. A tool whose scope is missing reports which one.
Claude Desktop: add this to claude_desktop_config.json:
{
"mcpServers": {
"carebit": {
"command": "node",
"args": ["/absolute/path/to/carebit-mcp/dist/index.js"],
"env": { "CAREBIT_CLIENT_ID": "your-client-id", "CAREBIT_CLIENT_SECRET": "your-client-secret" }
}
}
}Claude Code:
claude mcp add carebit -e CAREBIT_CLIENT_ID=your-client-id -e CAREBIT_CLIENT_SECRET=your-client-secret -- node /absolute/path/to/carebit-mcp/dist/index.jsVariable | Required | Meaning |
| yes | The API credential's client ID, sent in the token request body. |
| yes | The API credential's client secret, sent in the token request body. Never logged or included in an error message. |
| no | Space-separated scopes to request on the token, e.g. |
| no |
|
| no | Defaults to |
Safety defaults
Read-only unless
CAREBIT_ALLOW_WRITES=true. Read tools carry the MCPreadOnlyHintannotation;cancel_bookingis marked destructive; the two create tools are not.Patient records are third-party health data. By default a patient's
email,phone_number,phone,mobile(and their country codes),date_of_birth,nhs_number,address_line_1,address_line_2,city,county,postcode,country_code,internal_patient_idandpatient_portal_add_payment_method_urlare not returned; a clinician'semailis not returned; a payor's name-and-address fields,insurance_policy_number,insurance_authorization_code, policy dates andnotesare not returned; an invoice'spayment_url(the Patient Portal page where that invoice can be paid) is not returned; and a booking'sinformation_for_patientandinformation_for_staff_members(free text that may hold clinical detail) are withheld, with two booleans saying whether they exist. Patient names, sex and the Organization's ownpatient_numberare returned, as are the Organization's own and its locations' contact details. In every other free-text field (names, specialties, task content, cancellation notes, invoice notes and line-item titles, payment and refund notes, service names and descriptions and variant descriptions, a booking's service name, and Carebit's own error messages) email addresses are replaced with[email redacted], phone-number-like sequences with[phone redacted], 10-digit NHS-number-shaped groups with[number redacted]and UK postcodes with[postcode redacted].include_contact_details=true(on every read tool exceptget_organization,find_availabilityandlist_locations, which return no personal data) returns all of it as stored, with one exception: a payment card number typed into free text (13 to 19 digits, optionally grouped by spaces or hyphens, that pass the Luhn check) is replaced with[card number redacted]whether or not contact details were requested, in every field this server returns, including the booking information and payor notes that are otherwise passed through as stored. That check catches full card numbers, not fragments:card ending 4242cannot be told from an ordinary number and is left alone, and one arbitrary 13-to-19-digit string in ten passes Luhn and is redacted too. The phone match is a heuristic: it covers international numbers written with+or00(including the+44 (0)7700 …form), UK numbers 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 with0are redacted too, and any 10-digit group writtennnn nnn nnnnor unbroken is redacted whether or not it is an NHS number, while UUIDs, timestamps and hyphenated references such asINV-1001orPOL-0001-000123are left alone. Dates inside free text are not redacted.Stored cards are never read:
GET /v1/patients/{id}/payment_methodsis not called, a payment'spayment_method_idis never returned, a full card number typed into any free text is redacted as above, and there are no payment, refund or invoice-creation tools. No file is ever downloaded.The access token is held in memory only, refreshed a minute before its documented expiry (or after half its lifetime when Carebit issues one shorter than two minutes, so it is still reused rather than fetched before every call), and fetched afresh once when a call answers 401; it never appears in logs or error messages, and neither does the client secret: should Carebit ever echo either in an error message, the value is replaced with
[redacted]before the message is passed on. The refresh token Carebit issues alongside is not used: refresh tokens rotate on every use and would have to be stored, while a client-credentials grant can simply be repeated (the token endpoint allows 10 requests per minute perclient_id). Only a JSON error message from the token endpoint is ever passed on, never its raw body; a non-JSON 200 from it is reported by its first 60 characters, redacted before they are cut.Every write carries an
Idempotency-Keyheader holding a fresh version-4 UUID, as the idempotency guide requires on every POST create; the same key is kept across retries of that operation. A409 idempotency_conflict(the same key still in flight) is retried afterRetry-After(1 second when absent, as documented) with the same key, at most twice. The same key is sent again on a409 idempotency_conflictretry, on a 429 retry and on the single retry after a token refresh (401), as the rate-limits guide requires for any write retry. The code reports a response that carriesIdempotency-Replayed: trueas "already created (replayed)", but the tests never reach the replay path, because the mock stores nothing for a request it answered 409, 429 or 401 (see Status).IDs are checked before any call is made: every identifier must look like a UUID (the spec types them all as RFC 4122 UUIDs). Dates take
YYYY-MM-DDand real calendar dates only; times takeYYYY-MM-DDTHH:MM:SSZ(UTC, the only form the API documents) or a bare date, which becomes the start of that UTC day, or its end for an upper bound. Offsets such as+01:00and forms such as05/10/2026are refused locally. The documented limits are enforced before a request: a diary or slot range of at most 45 days inclusive, a bookings window of at most 30 days (90 withpatient_id), no window with a recall status and a window with every other status includingdid_not_attend.create_bookingrefuses an end before the start;cancel_bookingonly accepts the documented reason codes.Rate limits, from the rate-limits guide: 120 requests and 30 writes per minute per access token, 240 and 60 per developer project, 300 and 90 per Organization, 10 patient creates per token, 10
/oauth/tokenrequests per minute per IP and perclient_id, all on a rolling 60-second window at the Cloudflare edge; every 429 carriesRetry-After: 60. Requests are spaced 500 ms apart, which keeps one server at the per-token ceiling; the project and Organization limits are shared with other integrations and are not modelled. A 429 is retried at most twice for any method, on the assumption that a request the edge rejected was not processed, waiting forRetry-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: with Carebit's documentedRetry-After: 60the call therefore gives up at once and the message says to wait 60 seconds. The cap is per request, not per tool call: a list can make up to 20 requests in one call.502, 503 and 504 are retried the same way for
GETand forPOST /oauth/tokenonly; 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 500 and a connection failure are reported at once, without a retry, although the idempotency guide says to retry every 5xx and network failure. APOSTis never retried after a gateway error, even though the idempotency guide says a retry with the same key is safe: this prototype does not repeat a write on the strength of documentation it could not test, and the error says to check with the matching list tool before repeating it (see Going to production).A 200 whose body is not JSON (a proxy or a login page in the way) is reported as an error naming
CAREBIT_BASE_URL, never as an empty list or an empty record. Every body excerpt in an error message (60 characters of a non-JSON 200, 300 of a non-JSON error body) is redacted before it is cut, so a contact detail that straddles the cut cannot leak in part. A list page that sayshas_more: truewithout a usablenext_cursor(the guide says the two always agree) is reported as incomplete with a note, not as the whole collection.Rejected client credentials produce a message that says which variables to fix and where credentials come from; a 401 that survives a token refresh says to check the credential and project; a 403 names the scope the endpoint needs; a 404 says the resource does not exist or is outside the Organization; a 422 passes on Carebit's message, code and, when several fields fail, every field-level error; and every error message, from the token endpoint included, carries the
Carebit-Developer-Platform-API-Request-Idfor support tickets whenever the response had the header (a gateway page from the edge may not carry it).
Tests
npm testThe test suite:
Validates every fixture record against the component schemas in Carebit's published OpenAPI 3.1 document (
Organization,Token,Clinician,Location,Service,Patient,Booking,ClinicianAgendaItem,AvailabilitySlot,Invoice,Payment,HumanTask) with Ajv 2020 and ajv-formats, including a negative control (a non-UUID id and an undeclared key are rejected, since every resource schema setsadditionalProperties: false). It also checks that every scope the mock's project holds is one the spec defines and that the mock's client ID and secret are obviously fake values that do not appear in the spec. The spec is downloaded fromcarebit.dev/openapi.jsontospec.jsonon the first run.Starts a local mock of the API:
POST /oauth/tokenwith the documented form-encoded client-credentials grant (answering theOAuthTokenResponseshape with a refresh token and scope,401 invalid_clientfor wrong credentials,400 invalid_scopefor a scope not on the project),GET /v1/token, a 401 for any API call without a token the mock issued, a 403 for a token without the endpoint's scope, the endpoints used here with the documented list envelope and cursor pagination (limit1 to 100 with a 422 above,cursorfromnext_cursor,has_morefalse andnext_cursornull on the last page; every page capped at four records so the suite pages), the documented filters (the bookings window rules, recall statuses without a window, exact patient filters includingids[], thepaid_atwindow omitting nullpaid_at,is_completed,is_bookable_online), theIdempotency-Keycontract on writes (400 idempotency_key_required, replay withIdempotency-Replayed: true,422 idempotency_key_reused, an injectable409 idempotency_conflictwithRetry-After: 1), the documented error envelope with field-levelerrors, 404s for unknown ids, a 422 for cancelling a cancelled booking, injected failures on any endpoint, and a one-off 429 on the firstGET /v1/services. The second check validates the mock's token, list, record, write and error responses against the documented response schemas.Starts the built server and drives it over stdio with the official MCP client: 30 checks (32 in the whole suite, 124 requests, about 70 seconds) covering every tool, tool annotations, the token fetched with the documented form body (no
scopeunlessCAREBIT_SCOPEis set, and that value when it is) before the first API call and reused as a Bearer header, a revoked token refreshed once with the call retried, a token near expiry refreshed before it expires, a 60-second token reused rather than fetched before every call, cursor pagination withlimit100 followingnext_cursortohas_more: falseand continuing from a returned cursor with the original filters on every page, every documented filter passed through exactly (clinician_ids[],start_date/end_date,clinician_id/service_variant_id/from_date,start_time_from/start_time_towith bare dates converted,clinician_id,patient_id,status,updated_since,first_name,last_name,date_of_birth,email,phone_number,ids[]repeated,booking_id,paid_at_from/paid_at_to,staff_member_id,document_id,is_completed,is_bookable_online), the documented window and range limits refused before any request (a 31-day calendar month given as bare dates refused with the two exact windows to use instead, 30 bare days and an exact 30-day span accepted), redaction of contact details, NHS number, date of birth, address, third-party identifier, payment-method link, policy number, payor address, invoice payment link, stored-card id and the booking's free text by default and their return on request, and of emails, phone numbers, an NHS-shaped number and a UK postcode typed into specialties, task content, cancellation notes, invoice notes and payment notes, service names and descriptions, variant descriptions, line-item titles and a booking's service name redacted by default and returned as stored on request, full card numbers (Visa and Amex shapes, unbroken, spaced and hyphenated) redacted in task content, booking information, payor notes and the record a create tool echoes whether or not contact details were requested while a non-Luhn digit string andending 4242stay, the three write bodies validated against the spec's request schemas (POST /v1/bookingsthrough itsallOf/oneOfdiary branch) with a version-4 UUIDIdempotency-Keythat changes per call and is kept across a 409 retry, a 429 retry and the retry after a token refresh, the 422 for an already-cancelled booking passed on with Carebit's message and code, the 429 retry waiting forRetry-Afterin the seconds, fractional-seconds and HTTP-date forms and falling back to 2 s without the header, giving up after three attempts on a persistent 429 and at once on the documentedRetry-After: 60with the request id in the message, a 502 retried forGETand never forPOST, aGETfailing three times with 503 reported with advice and without the gateway HTML (and with the request id and Carebit's message when the 503 carried them), a 200 with a non-JSON body reported as an error, body excerpts redacted before they are cut (an email address straddling the 60- and 300-character cuts), a list page withhas_more: trueand no cursor reported as incomplete, token-endpoint errors (400, 403, a non-JSON 200) carrying the request id and never the client secret even when the message echoes it, the write gate with the variable unset and set tofalse, the 401 for wrong client credentials naming the variables without echoing the secret, the 403 naming the missing scope, and that every request either carried the documented form body to/oauth/tokenwith noAuthorizationheader or carried a Bearer token the mock had actually minted (membership in the mock's issued set, not just the shape) to a documented method and path, that everyPOSTcarried anIdempotency-Key, that the secret never travelled in a query string, and that the stored-card, payor, note, letter, test-result, report and revoke endpoints were never called.
Status
This is a working prototype. It has not yet been run against the live API, because it was built without a Carebit Organization or API credential (Carebit has no sandbox; the testing guide says to test against production with an example patient, and a trial Organization is provided after a booked demo). Everything below is taken from the published spec and guides and should be confirmed on a real account:
The token endpoint: that client credentials sent as body fields (rather than HTTP Basic, which the spec also allows) are accepted, the exact status and
error.codefor wrong credentials (the spec documents 401; the mock'sinvalid_clientcode is the OAuth 2.0 convention), whether the API answers 401 for an expired token (the server refreshes a minute before the documented expiry and once on a 401 either way), and whether repeating the client-credentials grant every hour instead of using the refresh token has any side effect (the guide documents both grants; refresh tokens rotate on use).Error bodies: the spec documents the envelope and the idempotency, scope and rate-limit codes; the codes the mock uses for 401, 403 and 404 (
invalid_token,insufficient_scope,resource_missing) are placeholders. The server switches onidempotency_conflictonly and passes every other message through with contact details redacted.Pagination: the mock's cursor is opaque base64 and the server treats
next_cursoras opaque, as documented; that the real cursor survives being sent back with the original filters andlimitis assumed.starting_afteris not used. What alimitbetween the page's actual size and 100 returns on a real account is not tested beyond the mock's cap.GET /v1/clinician_agendaandGET /v1/availability_slotsanswer the list envelope but document nolimitorcursorparameter; the server makes one request and reportshas_more: trueas a note. Whether the real endpoints ever page, and how many items a 45-day diary holds, is unknown.The bookings window: whether
start_time_tois compared inclusively as documented, what the API answers for a window of exactly 30 or 90 days, whether 30 days and 23:59:59 counts as exceeding 30 days (the server assumes it does, so a 31-day calendar month given as bare dates is refused with the two exact windows to use instead), and whetherpatient_idalone (without a window) is accepted for non-recall statuses (the server requires the window, as the description says).GET /v1/next_availability_slot: the 404 for "no slot within 8 months" cannot be told apart from an unknown clinician or variant by status alone; the server's message says both.The exact-match patient filters: whether
first_nameandlast_namereally ignore case and whetherphone_numbermatching a national07700 900123against a stored E.164 number works as the parameter description says; the server sends the value as given.POST /v1/bookings: whichstatusvalues are accepted on creation (the schema listsarrived,confirmed,did_not_attend,unconfirmed,awaiting_recall; the tool offersunconfirmedandconfirmed), whether omittingend_timederives it from the service duration as the mock does, what the API's defaultstatusis, and whethernotify_patient: falsereally suppresses every patient email. The mock answers 201; the spec documents 201.POST /v1/bookings/{booking_id}/cancellations: whether the Organization requirescancellation_reason(the tool leaves it optional and passes on the 422), which statuses can be cancelled, and that an attendance penalty invoice is applied as the description says.POST /v1/human_tasks: the API default ofis_remindablewhen omitted (the mock usestrue), and whether an unknownassignee_idanswers 404 as the mock does.Retries: a 429 is retried on the assumption that a rate-limited request was not processed at the edge (with the same
Idempotency-Keyon a write), and a 409idempotency_conflictis retried with the same key as the guide says; neither has been observed on a live account. A write is never retried after a 5xx although the guide says a retry with the same key is safe, and a 500 or a network failure is never retried for any method.The
Idempotency-Replayedheader is read from the response and would be reported as "already created (replayed)"; no test reaches that path (the mock stores nothing for a write it answered 409, 429 or 401), and whether a replay of a create answers 201 (the guide says the original status) is untested.Rate limits: the 500 ms spacing is derived from the documented 120 requests per minute per token and has not been measured against the edge; the write limit of 30 per minute is not separately throttled. The token refresh margin (a minute, or half the lifetime of a token shorter than two minutes) assumes Carebit's
expires_inis accurate.The
Carebit-Developer-Platform-API-Request-Idheader is read case-insensitively and included in error messages, from the token endpoint and the retry paths included, whenever the response carried it; its presence on every response, and on token-endpoint and edge (429, 5xx) responses in particular, is as documented and untested.
Going to production
This version runs locally over stdio, with the practice's own API credential. For practices to connect from claude.ai or ChatGPT without handling credentials, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by Carebit, and then a listing in the Claude and ChatGPT connector directories. A production version, tested on a real Organization, would also retry writes after a 5xx with the same Idempotency-Key as the guide documents, use the refresh grant, cover the staff-member, payor, note and letter endpoints, and add the PATCH tools for completing tasks and confirming, arriving and rescheduling bookings.
Licence
MIT. Built by Alexandru Dragoș (alexandru.dragos96@gmail.com) with an AI agent (Claude) working under his direction.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted MCP server for Cliniko — patients, appointments, availability, and invoices for AI agents.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Let AI agents query data and act across all your business apps via MCP.
Hosted MCP server for the Healthie EHR & telehealth API: patients, appointments, charting, tasks.