---
title: "MCP dental chart tools | HyperCRM"
description: "The six MCP tools for the dental chart and gum chart exams, for dental practices only: every argument, an example call, the result and the errors each returns."
canonical: "https://hypercrm.app/docs/mcp/dental"
lang: "en"
updated: "2026-11-02"
---

_MCP reference / Dental chart_

# Dental chart tools

Six tools for a patient's dental chart and gum chart exams. They exist only for a practice set up as a dental practice: any other practice is not offered them, and a call by name is refused with pack_feature_unavailable.

[Start the free trial](/dashboard) · [All 75 tools](/docs/mcp/tools)

## 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](/docs/mcp) has that envelope, and the [full catalogue](/docs/mcp/tools) lists every tool at a glance.

The dental chart and gum chart exams. These tools exist only for a practice set up as a dental practice: any other practice is not offered them, and a call by name is refused.

### get_dental_chart

_Read-only, Any member_

Read a patient's dental chart: which teeth are missing, implanted or treated, the entries charted so far (removed ones on request), the chart note and tooth notes.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `patientId` | `string` | Required | The patient whose chart this is. |
| `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**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_dental_chart",
    "arguments": {
      "patientId": "b065ac83-b065-4c3a-9e21-7b84c5d10f33"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `plan_feature_required` | 403 | The practice's plan does not include this. MCP needs Complete. |

### write_dental_chart_entry

_Writes, Can delete, Any member_

Chart a finding or treatment on one or more teeth, mark a planned one as done, or remove one. A removed entry stays in the tooth's history. An entry cannot be edited: to correct one, remove it and chart it again.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `action` | `string: create \| complete \| void` | Required |  |
| `patientId` | `string` | Required | The patient whose chart this is. |
| `entryId` | `string` | Optional | The entry to complete or remove, from get_dental_chart. |
| `teeth` | `array<integer>: 11 \| 12 \| 13 \| 14 \| 15 \| 16 \| 17 \| 18 \| 21 \| 22 \| 23 \| 24 \| 25 \| 26 \| 27 \| 28 \| 31 \| 32 \| 33 \| 34 \| 35 \| 36 \| 37 \| 38 \| 41 \| 42 \| 43 \| 44 \| 45 \| 46 \| 47 \| 48 \| 51 \| 52 \| 53 \| 54 \| 55 \| 61 \| 62 \| 63 \| 64 \| 65 \| 71 \| 72 \| 73 \| 74 \| 75 \| 81 \| 82 \| 83 \| 84 \| 85` | Optional | action=create: the teeth this applies to, each once. One entry per tooth; teeth charted together (a bridge) are grouped. |
| `kind` | `string: caries \| filling \| sealant \| crown \| bridge_abutment \| bridge_pontic \| root_canal \| implant \| missing \| extraction \| impacted \| unerupted \| watch` | Optional | action=create: what is charted. |
| `bridge` | `object` | Optional | action=create, instead of teeth and kind: a whole bridge. All teeth in one arch (it may cross the midline), side by side with none left out, each once. At least one abutment, and a pontic unless there are two or more abutments (one abutment with pontics is a cantilever). Pontics sit between abutments or off one end only: the teeth may not both start and end with a pontic. Unless status is planned, every pontic must already be charted missing or extracted. |
| `status` | `string: existing \| planned \| completed` | Optional | action=create: existing (found in the mouth), planned (to do) or completed (done at this practice). |
| `surfaces` | `array<string>` | Optional | action=create, caries, filling and sealant only: M mesial, O occlusal (incisal on front teeth), D distal, B buccal or labial, L lingual or palatal. Send each surface as the user wrote it, letter or name, Greek or English (μεσιαία, άπω, Π): the server reads it into these letters. |
| `material` | `string: composite \| amalgam \| glass_ionomer \| other` | Optional | action=create, a filling only, optional. |
| `recordedOn` | `string` | Optional | The clinical date, YYYY-MM-DD. Required for action=create; for action=complete the date it was done (omit to keep the planned date). |
| `expectedUpdatedAt` | `string` | Optional | The record's updatedAt exactly as you last read it. If it has changed since, nothing is written and the call fails with 409 (details[0].code: stale_version): 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. |
| `group` | `boolean` | Optional | action=void: true also removes every entry charted together with this one (the whole bridge). |

**Example call**

`action=create`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_entry",
    "arguments": {
      "action": "create",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "teeth": [
        16
      ],
      "kind": "caries",
      "status": "existing",
      "surfaces": [
        "O"
      ],
      "recordedOn": "2026-11-02"
    }
  }
}
```

`action=complete`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_entry",
    "arguments": {
      "action": "complete",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "entryId": "adbecfd0-0b1c-4d5e-992a-3b4c5d6e7f80",
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
      "recordedOn": "2026-11-02"
    }
  }
}
```

`action=void`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_entry",
    "arguments": {
      "action": "void",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "entryId": "adbecfd0-0b1c-4d5e-992a-3b4c5d6e7f80",
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
      "reason": "entered_in_error"
    }
  }
}
```

`action=create, a bridge, instead of teeth and kind`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_entry",
    "arguments": {
      "action": "create",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "bridge": {
        "abutments": [
          14,
          16
        ],
        "pontics": [
          15
        ]
      },
      "status": "planned",
      "recordedOn": "2026-11-02"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `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_dental_chart_note

_Writes, Can delete, Any member_

Set the chart's dentition and note, or write or delete one tooth's note.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `action` | `string: profile \| tooth_note \| delete_tooth_note` | Required |  |
| `patientId` | `string` | Required | The patient whose chart this is. |
| `dentition` | `string: permanent \| primary \| mixed` | Optional | action=profile: permanent (adult teeth), primary (baby teeth) or mixed (adult, with the baby teeth in primaryTeeth). |
| `primaryTeeth` | `array<integer>: 51 \| 52 \| 53 \| 54 \| 55 \| 61 \| 62 \| 63 \| 64 \| 65 \| 71 \| 72 \| 73 \| 74 \| 75 \| 81 \| 82 \| 83 \| 84 \| 85` | Optional | action=profile, mixed dentition only: the baby teeth charted in place of their adult successors. Omit for permanent and primary. |
| `chartNote` | `string \| null` | Optional | action=profile: a note on the whole chart. Omit it to keep the note as it is; send null only to remove a note the chart has. |
| `tooth` | `integer: 11 \| 12 \| 13 \| 14 \| 15 \| 16 \| 17 \| 18 \| 21 \| 22 \| 23 \| 24 \| 25 \| 26 \| 27 \| 28 \| 31 \| 32 \| 33 \| 34 \| 35 \| 36 \| 37 \| 38 \| 41 \| 42 \| 43 \| 44 \| 45 \| 46 \| 47 \| 48 \| 51 \| 52 \| 53 \| 54 \| 55 \| 61 \| 62 \| 63 \| 64 \| 65 \| 71 \| 72 \| 73 \| 74 \| 75 \| 81 \| 82 \| 83 \| 84 \| 85` | Optional | action=tooth_note or delete_tooth_note: the FDI tooth the note is on. |
| `body` | `string` | Optional | action=tooth_note: the note's text. |
| `expectedUpdatedAt` | `string \| null` | Optional | The profile's or the tooth note's updatedAt exactly as get_dental_chart gave it, or null when there was none yet. If it has changed since, nothing is written (409, stale_version): read again and retry. |

**Example call**

`action=profile`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_note",
    "arguments": {
      "action": "profile",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "dentition": "mixed",
      "primaryTeeth": [
        55,
        65
      ],
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z"
    }
  }
}
```

`action=tooth_note`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_note",
    "arguments": {
      "action": "tooth_note",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "tooth": 16,
      "body": "Sensitive to cold since the filling.",
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z"
    }
  }
}
```

`action=delete_tooth_note`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_dental_chart_note",
    "arguments": {
      "action": "delete_tooth_note",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "tooth": 16,
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `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_perio_exams

_Read-only, Any member_

List a patient's gum chart exams, newest first, without their measurements.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `patientId` | `string` | Required | The patient whose chart this is. |

**Example call**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_perio_exams",
    "arguments": {
      "patientId": "b065ac83-b065-4c3a-9e21-7b84c5d10f33"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `plan_feature_required` | 403 | The practice's plan does not include this. MCP needs Complete. |

### get_perio_exam

_Read-only, Any member_

Read one gum chart exam: six probing sites per tooth, bleeding, mobility and furcation.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `patientId` | `string` | Required | The patient whose chart this is. |
| `examId` | `string` | Required | The exam, from list_perio_exams. |

**Example call**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_perio_exam",
    "arguments": {
      "patientId": "b065ac83-b065-4c3a-9e21-7b84c5d10f33",
      "examId": "8959563f-8959-4c3a-9e21-7b84c5d10f33"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `plan_feature_required` | 403 | The practice's plan does not include this. MCP needs Complete. |

### write_perio_exam

_Writes, Can delete, Any member_

Start a gum chart exam, change its measurements (only what you send changes), or remove it.

**Arguments**

| Tool | Type | Required? | What it means |
| --- | --- | --- | --- |
| `action` | `string: create \| update \| void` | Required |  |
| `patientId` | `string` | Required | The patient whose chart this is. |
| `examId` | `string` | Optional | The exam to save or remove. |
| `examDate` | `string` | Optional | The exam date, YYYY-MM-DD. Required for action=create; for update, omit to keep it. |
| `copyFromExamId` | `string` | Optional | action=create: an earlier exam of the same patient to copy the missing teeth from. |
| `measurements` | `object` | Optional | action=update: only the changes, keyed by FDI tooth number as a string ("16"; 11-48, 51-85), merged into the stored exam. A value left out is kept; the tool description says how to remove one. Furcation only on a tooth with more than one root. |
| `expectedUpdatedAt` | `string` | Optional | The record's updatedAt exactly as you last read it. If it has changed since, nothing is written and the call fails with 409 (details[0].code: stale_version): 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`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_perio_exam",
    "arguments": {
      "action": "create",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "examDate": "2026-11-02"
    }
  }
}
```

`action=update`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_perio_exam",
    "arguments": {
      "action": "update",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "examId": "9cadbecf-fa0b-4c4d-8819-2a3b4c5d6e7f",
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
      "measurements": {
        "16": {
          "sites": {
            "MB": {
              "pd": 4,
              "bleeding": true
            }
          },
          "mobility": 1
        }
      }
    }
  }
}
```

`action=void`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_perio_exam",
    "arguments": {
      "action": "void",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "examId": "9cadbecf-fa0b-4c4d-8819-2a3b4c5d6e7f",
      "expectedUpdatedAt": "2026-11-02T08:30:00.000Z",
      "reason": "entered_in_error"
    }
  }
}
```

**Result**

```json
{
  "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. |
| `pack_feature_unavailable` | 403 | The practice's vertical pack does not have this. No upgrade unlocks it: a physiotherapy practice has no dental chart. |
| `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

### How is patient data protected?

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.

### Can I export my data?

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.

### How much does HyperCRM cost?

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.