clinica-mcp-server
Click on "Install 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., "@clinica-mcp-serverCheck available appointment slots for tomorrow."
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.
dental-clinic-mcp-server
A production-grade MCP server for a dental clinic: appointments, affiliation checks and accounts receivable, with the security controls that 92% of the MCP ecosystem does not have.
cp .env.example .env && make up && make smokeWhy this exists
There are 22,000+ MCP servers listed publicly. Audited samples show 40% ship with no authentication at all, 79% handle credentials in plaintext, and only 8.5% implement OAuth. In January 2026 even Anthropic's own reference server carried three CVEs: path traversal, arbitrary file deletion and RCE.
The domain here is not the differentiator; the engineering is. This repository is a deliberate demonstration of what the other 8.5% looks like.
The failure mode it defends against is concrete. In July 2025 an AI agent deleted a production database at SaaStr during a code freeze: it held write permissions it never needed, and nobody could revoke them granularly. Its token was valid, so scopes alone would not have stopped it. What was missing was a human between intent and effect. Both are implemented here.
Related MCP server: open-dental-mcp
Domain: why a dental clinic in Colombia
Nothing in this server is invented. The appointment state machine, the affiliation check against the Colombian health regimes, the copayment rules and the waiting list are the real process a clinic or IPS runs every day. Roughly 25% of scheduled appointments go unused each month, which is why 48-hour confirmation and slot release exist. The tools implementing them attack a quantified problem, not a demo script.
There is also a regulatory line that makes the security work necessary rather
than ornamental: booking an appointment is not a medical act, but recording a
reason for consultation is. The moment the system stores clinical data it
falls under Resolución 2654/2019, informed consent and RNBD registration with
the SIC. That boundary is exactly where the clinical scope and the mandatory
human approval live.
All data in this repository is synthetic, generated by Faker with a fixed seed. No real patient information is present anywhere, at any point, and a test asserts it.
Architecture
flowchart LR
C["MCP client<br/>Claude · Cursor · Inspector"]
subgraph mcp["MCP server · Streamable HTTP"]
direction TB
A1["1· OAuth 2.1 + PKCE"] --> A2["2· Scope check"] --> A3["3· Human approval"] --> T["14 tools · 3 resources · 1 prompt"]
T --> A4["4· Structured errors"]
T --> A5["5· Audit + transport guards"]
end
subgraph be["Domain backend · FastAPI"]
API["REST API"] --> DOM["state machine · cartera<br/>afiliación · lista de espera"] --> DB[("PostgreSQL 16")]
end
C --> A1
T --> API
style mcp fill:#f6f2ff,stroke:#7c5cff
style be fill:#f0f7ff,stroke:#3b82f6Layer | Stack | Responsibility |
Domain backend | FastAPI + PostgreSQL 16 + SQLAlchemy 2.x | Source of truth. Knows nothing about MCP. |
MCP server | MCP Python SDK v2, Streamable HTTP | Translates the domain into tools/resources/prompts. Every security control lives here. |
Authorization server | In-repo OAuth 2.1 (or Keycloak) | Issues tokens. Swappable without touching the resource server. |
Separating the backend from the MCP server is itself the point: in production an MCP server almost never is the system, it wraps one that already exists. The LLM never touches the database directly.
Full reasoning and diagrams: docs/architecture.md.
The tool catalogue
Fourteen tools, not thirty. Model accuracy degrades past roughly 25–30 tools, so a smaller, precisely described catalogue is the design, not a limitation.
Scope | Tools |
|
|
|
|
|
|
, |
|
Resources: clinica://info, politicas://cartera, agenda://hoy.
Prompt: recepcionista_odontologia.
Every write and clinical tool returns a proposal, not a result. Calling
cancelar_cita changes nothing; it hands back a plain-language summary of what
would happen plus a signed token. Only confirmar_operacion, with that token,
mutates anything:
{
"requiere_confirmacion": true,
"resumen": "Cancelar la cita 412 de Ana Gómez del 2026-09-03 09:00.",
"esto_va_a_pasar": [
"La cita pasará de 'confirmada' a 'cancelada'.",
"El cupo quedará libre en la agenda.",
"Si hay lista de espera para esa especialidad, se informará al siguiente."
],
"advertencias": ["El paciente registra $180.000 COP en mora. No impide agendar."],
"token_confirmacion": "eyJhY2Npb24iOi…",
"vigencia_segundos": 300,
"siguiente_paso": "Muestra este resumen a la persona responsable. …"
}Security
Five layers, each answering a documented failure of the ecosystem. Full
write-up and threat model: docs/security.md.
# | Layer | What it stops |
1 | OAuth 2.1 + PKCE, no API keys anywhere | Anonymous access; a stolen authorization code |
2 | Per-tool scopes | The confused deputy; the SaaStr shape of over-broad tokens |
3 | Human-in-the-loop: signed, single-use, TTL-bound proposals | An agent mutating data on its own judgement |
4 | Structured errors with an actionable next step | Blind retry loops; leaked stack traces |
5 | Audit trail + transport guards | Unattributable changes; DNS rebinding; runaway agents |
Three hardening measures beyond the brief, because concurrent agents find them in the first hour:
Double-booking is impossible at the database level, via a partial unique index over the slot plus optimistic locking. An application-level check always loses that race.
Idempotency keys on booking, so a retrying agent gets the same appointment back, not a duplicate.
Store UTC, present America/Bogota. Naive datetimes are rejected rather than guessed.
The scopes deliberately do not nest. A write token cannot read the reason
for consultation and a clinical token cannot cancel an appointment, because
"administrative" and "clinical" are different kinds of authority, not
different amounts of it.
Quickstart
cp .env.example .env # local placeholders only; no real secrets exist here
make up # postgres + backend + authorization server + mcp
make smoke # walks the whole client path and prints each stepmake up takes about ten seconds from cold and leaves you with:
MCP server |
|
Domain API docs |
|
Authorization server |
|
Connect the MCP Inspector
make inspector # opens the Inspector, already authenticated
make inspector-cli # or list the catalogue without a browsermake token prints an access token obtained through the real PKCE flow, for
curl or for pasting into any client. Try issuing a read-only token and calling
cancelar_cita, the refusal explains exactly what to do next.
Swap the authorization server for Keycloak
make keycloak # Keycloak on :9100 + a second MCP server on :8081
make keycloak-verify # proves the swap works, and that tokens don't cross over--profile keycloak starts a second MCP server, same image, same code: trusting a real Keycloak realm instead of the in-repo authorization server, and
runs it side by side with the original. Only OAUTH_ISSUER and OAUTH_JWKS_URL
differ.
make keycloak-verify obtains a token from Keycloak, uses it against that
server, and then shows the two are not interchangeable: each server returns
401 for the other's token. That refusal is the audience binding working, and
it is why the realm carries an explicit audience mapper. Keycloak omits aud
unless asked, and a resource server that accepts an audience-less token accepts
every token that IdP ever issued, to anyone.
Development
make install # uv sync
make lint # ruff + mypy --strict
make test-unit # fast tests, no docker required
make check # everything CI runsTesting
Every layer is tested against the real thing: real PostgreSQL (never SQLite, where partial unique indexes, native enums and timezone-aware timestamps do not exist), the real MCP server, the real authorization server.
Suite | What it proves |
| State machine (exhaustive 7×7 + property-based), affiliation, receivables, waiting-list ordering, time handling, error contracts |
| Schema constraints, migration reversibility, seed determinism, two live connections racing for one slot |
| The MCP surface: catalogue, tool schemas, descriptions, resources, prompt, and every tool executed end to end |
| The full 13 × 3 scope matrix, approval replay/expiry/tampering, PKCE enforcement, JWT audience and |
| The whole client path over real HTTP, run in CI |
813 tests: 346 unit, 231 integration, 86 contract, 150 security.
Want to check it yourself? docs/manual-testing.md
is a 25-minute walkthrough of thirteen checks, each saying what to run and what
you should see. docs/inspector.md covers the same ground
through the MCP Inspector.
CI gates on a 95% coverage floor (currently 99%), mypy --strict, ruff,
bandit, pip-audit, and a grep that fails the build if a secret-shaped
literal or a private key ever lands in the source.
Repository layout
backend/ domain source of truth, knows nothing about MCP
domain/ pure logic: estados, cartera, afiliacion, lista_espera, tiempo, errores
models.py SQLAlchemy 2.x schema · api.py internal REST API
seed.py deterministic synthetic data (Faker, fixed seed)
mcp_server/
tools/ read.py · write.py · clinical.py · confirmacion.py
auth.py OAuth verification and scopes (layers 1-2)
aprobacion.py signed human-approval tokens (layer 3)
errores.py structured, actionable failures (layer 4)
auditoria.py audit log · limites.py rate limit (layer 5)
oauth/ the in-repo authorization server
tests/ unit · integration · contract · security
docs/ architecture.md · security.md (bilingual)License
MIT. Portfolio project, Horizonte Labs.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Read-only MCP: US dental intel, all-category Medicaid fee schedules, dentist lookup. Free tier.
Agent-native dental planning MCP for plan drafts, presentations, and price estimates.
MCP server exposing structured data and tools for Taki Dent Patient Services (takident.com).
Remote MCP for MCP consent scope receipt, structured receipts, audit logs, and reviewer-ready eviden
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceExposes tools from the Ecuro Light API for managing clinical appointments, patient records, and clinic availability. It enables users to perform healthcare management tasks such as scheduling, patient search, and report generation through MCP-compatible clients.
- AlicenseAqualityBmaintenanceEnables interaction with Open Dental practice management software, allowing reading of patients, appointments, providers, procedures, and recalls, as well as writing communications back to patient charts via MCP clients.144MIT
- FlicenseNot gradedqualityBmaintenanceAn MCP server for clinical workflows with tools for patient lookup, appointment booking, prescriptions, drug interactions, symptom triage, lab results, insurance eligibility, and telehealth, enforcing role-based access control and audit logging.1
- FlicenseNot gradedqualityCmaintenanceExposes appointment scheduling and insurance claims data as MCP tools, allowing AI clients to query and update clinic records via a standard protocol.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/dylanbc1/dental-clinic-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server