MCP reference / Patients
Patient tools
Fourteen tools covering the patient record, its clinical notes, its courses of treatment, and the sub-resources hanging off it. Each one with its arguments, an example call, and what it returns.
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.
Patients
The patient record, its notes, its courses of treatment, and the sub-resources hanging off it.
list_patients
List patients in the practice, with optional search, status and tag filters.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| search | string | Optional | Matches against name, email and phone. This is the argument — the `search` tool calls the same thing `q`, and sending that name here used to return the first page of every patient instead of a search. Both work now, and the answer echoes what it searched for. |
| q | string | Optional | An alias for `search`. Prefer `search`. |
| status | string | Optional | One of this practice's pack-defined status ids — read them from get_me (pack.patientStatuses) rather than assuming. |
| tagId | string | Optional | Use list_tags to find tag ids. |
| includeInactive | boolean | Optional | Also list inactive records (archived, no longer seen), marked isActive=false. The answer always says how many it left out (inactiveCount). |
| limit | integer | Optional | How many rows to return. Defaults to 50; at most 200 (a larger value is lowered to it, not rejected). |
| offset | integer | Optional | How many rows to skip, for paging through a long list. At most 100000. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_patients",
"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. |
get_patient
Read a single patient by id.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_patient",
"arguments": {
"patientId": "b065ac83-b065-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. |
list_patient_notes
List every clinical note recorded against a patient.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_patient_notes",
"arguments": {
"patientId": "b065ac83-b065-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. |
list_patient_files
List files attached to a patient. Use get_file for a download URL.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_patient_files",
"arguments": {
"patientId": "b065ac83-b065-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. |
list_patient_billing_entries
List the billing entries recorded against a patient, with balance: what the patient owes now, the same figure as the ledger on the patient's page. Quote it rather than adding entries up. A receipt entry is a payment, not a document.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required | |
| includeVoided | boolean | Optional | Also list voided (cancelled) rows, marked voided=true. Only for a question about what was voided: they count for nothing. The answer always says how many it left out (voidedCount). |
| limit | integer | Optional | How many entries to return, the newest kept. The answer says when older ones were left out (truncated); totals always cover every entry matched. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_patient_billing_entries",
"arguments": {
"patientId": "b065ac83-b065-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. |
list_patient_invoices
List the invoices already issued for a patient.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required | |
| includeVoided | boolean | Optional | Also list voided (cancelled) rows, marked voided=true. Only for a question about what was voided: they count for nothing. The answer always says how many it left out (voidedCount). |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_patient_invoices",
"arguments": {
"patientId": "b065ac83-b065-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_patient_invoice_preview
Compute the draft invoice for a patient's unbilled entries without issuing it. Use write_invoice with action=issue to issue one.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_patient_invoice_preview",
"arguments": {
"patientId": "b065ac83-b065-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. |
write_patient
Create, update or delete a patient. action=create requires firstName and lastName; update and delete require patientId. Deleting is permanent. A create for a name the practice already has is refused with patient_exists; differentPerson: true adds the record anyway, once the practice says it is somebody else. The server cannot check that the practice said so, and the activity log records it.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| action | string: create | update | delete | Required | |
| patientId | string | Optional | The patient to update or delete. |
| firstName | string | Optional | Required for action=create. |
| lastName | string | Optional | Required for action=create. |
| dob | string | Optional | Date of birth, YYYY-MM-DD. Send it ONLY if the caller stated it — never a placeholder, never the earliest date this field accepts, never a guess from anything else on the record. A patient with no date of birth stays that way until somebody tells you theirs. Must be a real past date, and the practice's own records are the only source for it. |
| gender | string | Optional | Free text — the practice records whatever the patient told them. |
| string | Optional | The patient's email address, only if the caller gave it. A patient with no email stays that way: do not compose one from their name, and do not send an example address. | |
| phoneMobile | string | Optional | Mobile number, as the practice records it, and only if the caller gave it. Never a placeholder such as a run of zeros: a patient with no number stays that way. |
| address | string | Optional | Street address, free text. |
| postcode | string | Optional | Postal code. |
| country | string | Optional | Country, free text. |
| socialSecurityNumber | string | Optional | The patient's social security number (in Greece, the ΑΜΚΑ). Send an empty string to clear it. |
| taxId | string | Optional | The patient's tax id (in Greece, the ΑΦΜ). Only needed for a tax invoice (write_invoice kind=invoice). Send an empty string to clear it. |
| taxOffice | string | Optional | The tax office the patient's tax id is registered with (in Greece, the ΔΟΥ). |
| status | string | Optional | Must be one of the status ids defined by this practice's vertical pack — the values differ per pack, so read them from get_me (pack.patientStatuses) rather than assuming. Any other value is rejected. |
| notifyByEmail | boolean | Optional | Whether the patient consents to email reminders. Defaults to false when a patient is created without it. Booking emails (booked, confirmed, cancelled) are sent regardless. |
| notifyBySms | boolean | Optional | Whether the patient consents to SMS reminders. Defaults to false when a patient is created without it. |
| fileUseConsent | boolean | null | Optional | Whether the patient consents to their files (x-rays, scans, photos) being used for teaching or promotion. Null means nobody has asked, which is NOT a refusal, and is what a patient created without it holds. Send it only when the practice said what the patient answered: never infer it. |
| differentPerson | boolean | Optional | action=create only, and only after a patient_exists refusal and the practice saying this is somebody else with the same name. The server cannot check that the practice said so; the new record's activity log entry names the existing ones. |
Example call
action=create
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient",
"arguments": {
"action": "create",
"firstName": "Anna",
"lastName": "Papadopoulou",
"phoneMobile": "+30 694 000 0000"
}
}
}action=update
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient",
"arguments": {
"action": "update",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"email": "anna.papadopoulou@example.com",
"notifyBySms": true
}
}
}action=delete
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient",
"arguments": {
"action": "delete",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33"
}
}
}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. |
| patient_exists | 500 | action=create for a name a patient of the practice already has. The refusal names that patient. |
| 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. |
write_patient_note
Create, update or delete a note on a patient. action=create requires body; update and delete require noteId.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| action | string: create | update | delete | Required | |
| patientId | string | Required | The patient the note belongs to. |
| noteId | string | Optional | The note to update or delete. |
| title | string | Optional | Optional heading for the note. |
| body | string | Optional | The note's text. Required for action=create, and must be non-empty. |
Example call
action=create
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient_note",
"arguments": {
"action": "create",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"title": "Check-up",
"body": "Reviewed the upper right quadrant. No new findings."
}
}
}action=update
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient_note",
"arguments": {
"action": "update",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"noteId": "1a2b3c4d-7e8f-4a5b-8c9d-0e1f2a3b4c5d",
"body": "Reviewed the upper right quadrant. Recall in six months."
}
}
}action=delete
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_patient_note",
"arguments": {
"action": "delete",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"noteId": "1a2b3c4d-7e8f-4a5b-8c9d-0e1f2a3b4c5d"
}
}
}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. |
| 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. |
toggle_patient_tag
Add the tag to the patient if absent, remove it if already present.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required | |
| tagId | string | Required | Use list_tags to find tag ids. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "toggle_patient_tag",
"arguments": {
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"tagId": "70819203-3e4f-4091-8c5d-6e7f8091a2b3"
}
}
}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. |
| 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. |
list_treatment_courses
List a patient's courses of treatment, open ones first: status, dates and duration, diagnosis and plan, the diagnosis and treatment types they are tagged with, the next step, and the visit log.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| patientId | string | Required | The patient whose record this is. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_treatment_courses",
"arguments": {
"patientId": "b065ac83-b065-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. |
list_treatment_picklists
List the practice's diagnosis types and treatment types, the words a course is tagged with. Archived ones on request.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| kind | string: diagnosis | treatment | Optional | Only this list: diagnosis or treatment. Omit for both. |
| includeArchived | boolean | Optional | Also list archived types, marked archived=true. |
Example call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_treatment_picklists",
"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. |
| plan_feature_required | 403 | The practice's plan does not include this. MCP needs Complete. |
write_treatment_course
Start, change or remove a course of treatment. action=create requires title; update and void require courseId and expectedUpdatedAt. A removed course keeps its row and its visits, out of the list.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| action | string: create | update | void | Required | |
| patientId | string | Required | The patient whose record this is. |
| courseId | string | Optional | The course to change or remove, from list_treatment_courses. |
| title | string | Optional | What the course is, in a few words («Orthodontic treatment», «Physiotherapy, left knee»). Required for action=create. |
| status | string: planned | in_progress | completed | discontinued | Optional | Where the course stands. action=create defaults to planned. completed and discontinued end it. |
| diagnosis | string | null | Optional | What was found, as the user said it. Send null to clear it. |
| plan | string | null | Optional | The treatment plan, as the user said it. Send null to clear it. |
| diagnosisTypeIds | array<string> | Optional | Ids of the practice's diagnosis types (list_treatment_picklists kind=diagnosis), in the order shown. Replaces the whole list; [] clears it. An id that is not this practice's, is of the other list, or is archived (unless the course already has it) is refused (400, unknown_picklist_item). Only types the user named. |
| treatmentTypeIds | array<string> | Optional | Ids of the practice's treatment types (list_treatment_picklists kind=treatment), in the order shown. Replaces the whole list; [] clears it. An id that is not this practice's, is of the other list, or is archived (unless the course already has it) is refused (400, unknown_picklist_item). Only types the user named. |
| startDate | string | null | Optional | When treatment started. YYYY-MM-DD; send null to clear it. |
| plannedEndDate | string | null | Optional | When treatment is planned to end; not before startDate. YYYY-MM-DD; send null to clear it. |
| nextStepDate | string | null | Optional | When the next step is due. YYYY-MM-DD; send null to clear it. |
| nextStepNote | string | null | Optional | What to do next, as the user said it. Send null to clear it. |
| expectedUpdatedAt | string | Optional | The course's updatedAt exactly as list_treatment_courses gave it. If it has changed since, nothing is written and the call fails with 409 (details[0].code: stale_version, or course_voided when it was removed): read again and retry. Never make one up. |
| reason | string: entered_in_error | duplicate | other | Optional | Why it is removed. Optional; a closed list, no free text is stored. |
Example call
action=create
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_course",
"arguments": {
"action": "create",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"title": "Orthodontic treatment",
"startDate": "2026-11-02"
}
}
}action=update
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_course",
"arguments": {
"action": "update",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"courseId": "7a8b9cad-d8e9-4a2b-e6f7-08192a3b4c5d",
"expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
"nextStepDate": "2026-12-01",
"nextStepNote": "Adjust the archwire"
}
}
}action=void
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_course",
"arguments": {
"action": "void",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"courseId": "7a8b9cad-d8e9-4a2b-e6f7-08192a3b4c5d",
"expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
"reason": "entered_in_error"
}
}
}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 record changed since you read it (details[0].code: stale_version), or it has since been voided. Read it again and retry. Never invent an expectedUpdatedAt. |
| 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. |
write_treatment_visit
Log, change or remove a visit on a course. action=create requires body, and may link the patient's appointment it was logged from; update and void require visitId and expectedUpdatedAt.
Arguments
| Tool | Type | Required? | What it means |
|---|---|---|---|
| action | string: create | update | void | Required | |
| patientId | string | Required | The patient whose record this is. |
| courseId | string | Required | The course, from list_treatment_courses. |
| visitId | string | Optional | The visit to change or remove, from list_treatment_courses. |
| visitDate | string | Optional | The day of the visit, YYYY-MM-DD. action=create defaults to the practice's today. |
| body | string | Optional | What was done at the visit, as the user said it. Required for action=create. |
| appointmentId | string | Optional | action=create only: the patient's appointment this visit is logged from, from a read of their bookings. It must be one of this patient's (400 appointment_not_patients). |
| expectedUpdatedAt | string | Optional | The visit's updatedAt exactly as list_treatment_courses gave it. If it has changed since, nothing is written (409, stale_version or visit_voided): read again and retry. |
| reason | string: entered_in_error | duplicate | other | Optional | Why it is removed. Optional; a closed list, no free text is stored. |
Example call
action=create
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_visit",
"arguments": {
"action": "create",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"courseId": "7a8b9cad-d8e9-4a2b-e6f7-08192a3b4c5d",
"visitDate": "2026-11-02",
"body": "Fitted the brackets on the upper arch."
}
}
}action=update
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_visit",
"arguments": {
"action": "update",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"courseId": "7a8b9cad-d8e9-4a2b-e6f7-08192a3b4c5d",
"visitId": "8b9cadbe-e9fa-4b3c-f708-192a3b4c5d6e",
"expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
"body": "Fitted the brackets on the upper arch. Patient tolerated it well."
}
}
}action=void
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "write_treatment_visit",
"arguments": {
"action": "void",
"patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
"courseId": "7a8b9cad-d8e9-4a2b-e6f7-08192a3b4c5d",
"visitId": "8b9cadbe-e9fa-4b3c-f708-192a3b4c5d6e",
"expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
"reason": "duplicate"
}
}
}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 record changed since you read it (details[0].code: stale_version), or it has since been voided. Read it again and retry. Never invent an expectedUpdatedAt. |
| 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.