MCP reference / Appointments
Appointment tools
Six tools covering bookings, chair-blocking entries and computed availability. Overlapping bookings on the same chair are rejected rather than silently accepted.
How to read this
Each tool below lists its arguments exactly as the input schema declares them: the type, whether it is required, the permitted values where the schema fixes them, and what the schema says the argument means. None of it is written here. An amount in cents, a default that applies when a field is left out, which action needs which id: those sentences come from the tool itself.
practiceId is never an argument. It comes from your token, and the write paths strip it if a client sends one anyway.
A tool that writes shows one example per action, because what an action needs is not what the input schema marks required: the schema requires action and leaves the rest to the action. Every example is a complete tools/call request whose arguments are checked, when this page is built, against the same object the handler parses. Errors come back inside a 200 response with isError set: the MCP overview has that envelope, and the full catalogue lists every tool at a glance.
Appointments
Bookings, chair-blocking entries, and derived availability.
get_day_agenda
The diary for a day or up to 7 days, by name: live bookings with patient, service, chair and practitioner, blocked time apart, counts, and whether the practice is closed.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| date | string | Optional | YYYY-MM-DD on the practice's clock. Defaults to today. |
| to | string | Optional | Last day, for a range of up to 7 days. |
| practitioner | string | Optional | A name, as the practice said it. |
| chair | string | Optional | A name, as the practice said it. |
| patient | string | Optional | A name, as the practice said it. |
| service | string | Optional | A name, as the practice said it. |
| time | string | Optional | HH:mm on the practice's clock, only when one was named: the bookings that start then. |
| locationId | string | Optional | Only this location: an id from list_locations, only when the practice named a location. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_day_agenda",
"arguments": {}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the requested records" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
find_booking_slots
When a booking fits, by name: a verdict on a requested time, free times spread over the day with the chairs they are free on and a ready booking draft, and the patient's own bookings that day. An ambiguous name is answered with the candidates, never a guess.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| date | string | Required | YYYY-MM-DD on the practice's clock. |
| time | string | Optional | HH:mm on the practice's clock, only when one was named. |
| partOfDay | string: morning | afternoon | evening | Optional | Only when named. Morning ends 12:00, evening starts 17:00. |
| days | integer | Optional | Days from date to look at. Defaults to 1. |
| patient | string | Optional | A name, as the practice said it. |
| service | string | Optional | A name, as the practice said it. Its own length is used. |
| practitioner | string | Optional | A name, as the practice said it. |
| chair | string | Optional | A name, as the practice said it. |
| locationId | string | Optional | Only this location: an id from list_locations, only when the practice named a location. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "find_booking_slots",
"arguments": {
"date": "2026-11-02"
}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the requested records" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
list_appointments
List appointments in a time window. Pass chairId to restrict to one chair.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| from | string | Required | Start of the window. ISO-8601 datetime with an explicit offset or Z, e.g. "2026-07-08T09:00:00Z" or "2026-07-08T12:00:00+03:00". A datetime without one is rejected — do not send "2026-07-08T09:00:00". A time a person says is on the practice's clock (get_practice → settings.timezone): send it with that zone's offset for that date, not as Z. |
| to | string | Required | End of the window. ISO-8601 datetime with an explicit offset or Z, e.g. "2026-07-08T09:00:00Z" or "2026-07-08T12:00:00+03:00". A datetime without one is rejected — do not send "2026-07-08T09:00:00". A time a person says is on the practice's clock (get_practice → settings.timezone): send it with that zone's offset for that date, not as Z. |
| chairId | string | Optional | Restrict to one chair. Use list_chairs for ids. |
| practitionerId | string | Optional | Restrict to one practitioner's appointments. Use list_practice_members for ids. |
| patientId | string | Optional | Restrict to one patient's appointments. This is how a question about one person's bookings is answered — searching their name by text also matches names that contain it. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_appointments",
"arguments": {
"from": "2026-11-09T09:00:00.000Z",
"to": "2026-11-09T09:00:00.000Z"
}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the requested records" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
get_appointment
Read a single appointment by id.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| appointmentId | string | Required |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_appointment",
"arguments": {
"appointmentId": "d4398f97-d439-4c3a-9e21-7b84c5d10f33"
}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the requested records" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
get_appointment_availability
Free times for one calendar day on every chair, or on one chair or for one person, honouring the practice's opening hours, timezone and existing bookings. Times that have passed are left out, and a requested time gets a verdict.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| date | string | Required | The calendar day to check, YYYY-MM-DD. |
| time | string | Optional | A time the practice asked about, HH:mm on its clock. Send it only when the practice named a time. |
| partOfDay | string: morning | afternoon | evening | Optional | Only times starting in this part of the day, by the server's bounds: morning before 12:00, afternoon 12:00 to 17:00, evening from 17:00. Send it only when the practice named one. |
| chairId | string | Optional | Only this chair. Send it only when the practice named a chair. |
| practitionerId | string | Optional | Only times this person is free (a practice-member id, from list_practice_members), on any chair that is free too. Send it only when the practice named the person. |
| serviceId | string | Optional | The service being booked; its own length is used. Send this whenever the caller named a treatment. |
| durationMinutes | integer | Optional | Length of the booking when no service says it: moving a booking keeps the length it already has. Overrides serviceId. Defaults to 30 when neither is given. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_appointment_availability",
"arguments": {
"date": "2026-11-02"
}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the requested records" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
write_appointment
Create, update or cancel an appointment. action=create requires chairId, startAt and endAt; update and cancel require appointmentId. type is appointment, reminder or unavailable; blocked time with no chair and no practitioner may name one locationId to close only that location. Overlapping bookings on the same chair are rejected.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| action | string: create | update | confirm | decline | cancel | Required | |
| appointmentId | string | Optional | The appointment to update, confirm, decline or cancel. |
| expectedDate | string | Optional | Required for action=update, decline and cancel. The calendar day (YYYY-MM-DD, the practice's clock) of the booking named in appointmentId, as you just described it to the user — "her Thursday appointment" means the date of that Thursday. It is checked against the booking: if they disagree, nothing happens and the answer says which day the booking is really on. Send the day you told the user, not the day you infer from the id. |
| type | string: appointment | reminder | unavailable | Optional | Required for action=create. appointment = a patient booking; reminder = a note on the calendar; unavailable = blocked-out time. |
| date | string | Optional | The day of the booking, YYYY-MM-DD, on the practice's clock. Send this with startTime and endTime and you do not have to work out any offset — the server does it. This is the easy way and the one to use for a time somebody said out loud. |
| startTime | string | Optional | When it starts, HH:MM on the practice's clock — 10:00 means ten in the morning there. Needs date. This WINS over startAt if both are sent, because it is the time the practice was shown. |
| endTime | string | Optional | When it ends, HH:MM on the practice's clock. Needs date. This wins over endAt if both are sent. |
| startAt | string | Optional | When it starts, as an exact instant. Use this ONLY for a value a tool handed you — the startAt of a free slot — and then send NOTHING else about the time. For a time somebody said, send date and startTime and no startAt: converting it yourself is how a 10:00 booking got stored at 07:00. ISO-8601 datetime with an explicit offset or Z, e.g. "2026-07-08T09:00:00Z" or "2026-07-08T12:00:00+03:00". A datetime without one is rejected — do not send "2026-07-08T09:00:00". A time a person says is on the practice's clock (get_practice → settings.timezone): send it with that zone's offset for that date, not as Z. |
| endAt | string | Optional | When it ends, as an exact instant. For a time somebody said, send date and endTime instead. ISO-8601 datetime with an explicit offset or Z, e.g. "2026-07-08T09:00:00Z" or "2026-07-08T12:00:00+03:00". A datetime without one is rejected — do not send "2026-07-08T09:00:00". A time a person says is on the practice's clock (get_practice → settings.timezone): send it with that zone's offset for that date, not as Z. |
| chairId | string | Optional | The chair being booked. Use list_chairs for ids. Required on a booking when the practice has more than one chair. |
| patientId | string | Optional | The patient being seen, if there is one. |
| practitionerId | string | Optional | The practitioner the appointment is with — a practice-member id, from list_practice_members. Optional: unassigned booking requests have none. For type=unavailable it is the person who is away (a seminar, leave): send it only when the practice named that person, and that person's free times then leave the block out. Leave it out when nobody in particular is away. Assigning someone to a type=appointment marks them as one of the practice's practitioners; blocked time never does. |
| title | string | Optional | Calendar label, only when the user dictated one. Omitted, the calendar shows the patient and the service. A title that only repeats names already on the booking is not written (see ignoredFields in the answer). |
| color | string | Optional | Optional calendar colour override. Defaults to the chair's colour; sending the chair's own colour is not written. |
| serviceRef | string | Optional | Optional free-text reference to the service being performed. |
| notes | string | Optional | Optional free-text notes on the booking. |
| locationId | string | null | Optional | Blocked time with no chair and no practitioner only: the location it closes, an id from list_locations, only when one was named. Omitted, the whole practice closes. |
Example call
action=create
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_appointment",
"arguments": {
"action": "create",
"type": "appointment",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"startAt": "2026-11-09T09:00:00.000Z",
"endAt": "2026-11-09T09:30:00.000Z",
"chairId": "6f708192-2d3e-4f80-9b4c-5d6e7f8091a2"
}
}
}action=update
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_appointment",
"arguments": {
"action": "update",
"appointmentId": "2b3c4d5e-8f9a-4b6c-9d0e-1f2a3b4c5d6e",
"expectedDate": "2026-11-09",
"startAt": "2026-11-09T10:00:00.000Z",
"endAt": "2026-11-09T10:30:00.000Z"
}
}
}action=confirm
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_appointment",
"arguments": {
"action": "confirm",
"appointmentId": "2b3c4d5e-8f9a-4b6c-9d0e-1f2a3b4c5d6e",
"chairId": "6f708192-2d3e-4f80-9b4c-5d6e7f8091a2"
}
}
}action=decline
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_appointment",
"arguments": {
"action": "decline",
"appointmentId": "2b3c4d5e-8f9a-4b6c-9d0e-1f2a3b4c5d6e",
"expectedDate": "2026-11-09"
}
}
}action=cancel
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_appointment",
"arguments": {
"action": "cancel",
"appointmentId": "2b3c4d5e-8f9a-4b6c-9d0e-1f2a3b4c5d6e",
"expectedDate": "2026-11-09"
}
}
}Result
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structuredContent": { "...": "the written record" },
"content": [{ "type": "text", "text": "<the same JSON>" }]
}
}Errors
| Code | Status | When |
|---|---|---|
| validation_failed | 400 | An argument is missing, of the wrong type, or outside its allowed values. |
| not_found | 404 | The id does not exist in your practice. A malformed id looks identical. |
| conflict | 409 | The chair or the practitioner is already booked for part of that time, or the booking is not in a state this action can take it from (a cancelled booking cannot be confirmed). |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
| subscription_read_only | 403 | The subscription has lapsed, so the practice is read-only and nothing may be written. |
Common questions
Patient records are encrypted, both where they are stored and on their way to your screen. Files are private, and a link to one stops working after a short time. Changes to patient records are recorded, with who made them and when. There is no shared practice password: everyone has their own account. On the Complete plan, the practice owner can also require two-step sign-in for the whole team.
Yes. The practice owner can export the patient list, the reports and the activity log to a spreadsheet file (CSV) from the app, and anyone in the practice can download its files at any time. Your practice data belongs to you, and there is no export fee or waiting period. That holds on either plan, during the trial, and after you cancel.
HyperCRM is paid software. Every practice starts with a 30-day free trial of the Complete plan, with no card needed. After that, Essential starts at €19 a month and Complete at €34, VAT included. The price depends on how many practitioners you have. Every price is on the pricing page, so you never need a sales call.