clinic-mcp

clinic-mcp

A reference MCP server for clinic scheduling and intake, demonstrating production patterns like tenant isolation, idempotent writes, and structured errors using synthetic data.

Category
Visit Server

README

clinic-mcp

A reference Model Context Protocol server for clinic scheduling and intake. Built in TypeScript with strict typing, structured errors, and tenant isolation enforced at the data layer. The data is synthetic. This is not clinical software.

The goal is to show what a production-shaped MCP server looks like for a vertical that demands data isolation and grounded outputs: the same shape of code I write at Rentive, with mock data and a different domain so the patterns are reviewable without leaking anything proprietary.

Why MCP

LLM applications keep reinventing the same wiring: ad-hoc function definitions per provider, bespoke argument parsing, no shared transport, no consistent error model. MCP is a small open protocol that fixes the wiring layer. A server exposes a list of typed tools over stdio (or HTTP), and any MCP-aware client (Claude Desktop, IDE integrations, custom agents) can discover and call them with the same machinery.

For domain backends, that means you write tools once and they work everywhere. For agent builders, it means you stop hand-rolling tool schemas and start composing servers.

Architecture

flowchart LR
    Client["MCP client<br/>(Claude Desktop, custom agent)"]
    Server["clinic-mcp server"]
    Tools["Tools<br/>find_available_slot<br/>book_appointment<br/>record_intake<br/>search_protocols<br/>escalate_to_oncall"]
    Store["ClinicStore<br/>tenant-scoped accessors"]
    Seed[("seed.json<br/>synthetic clinics, providers,<br/>patients, protocols")]

    Client -->|stdio JSON-RPC| Server
    Server --> Tools
    Tools --> Store
    Store --> Seed

Every tool takes a clinic_id and the store enforces that all reads and writes are scoped to that clinic. Cross-tenant access throws TenantMismatchError rather than silently returning the wrong row. This mirrors the row-level-security pattern a production deployment would enforce in Postgres, surfaced here in application code so the guarantee is reviewable in one file (src/store/index.ts).

Run it locally

Requires Node 20+ and pnpm.

git clone https://github.com/dominikstefanski/clinic-mcp.git
cd clinic-mcp
pnpm install
pnpm test          # 29 tests
pnpm typecheck
pnpm dev           # boots the server on stdio

The server reads src/store/seed.json at startup and serves two synthetic clinics: clinic_north (general practice, cardiology, dermatology) and clinic_west (pediatrics, general practice).

Wire into Claude Desktop

Add this to your Claude Desktop config (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). Replace the path with your local clone.

{
  "mcpServers": {
    "clinic-mcp": {
      "command": "npx",
      "args": ["-y", "tsx", "/absolute/path/to/clinic-mcp/src/server.ts"]
    }
  }
}

Restart Claude Desktop. The five tools will appear under the connections menu. Try a prompt like "Find a general practice opening at clinic_north next Monday morning."

Tool reference

All tools return { ok: true, ...result } on success or { ok: false, error: { code, message } } on failure. Inputs are validated with zod; MCP-level argument errors are returned as validation errors with field details.

find_available_slot

Find open appointment slots for a specialty in a date range, skipping conflicts.

Field Type Notes
clinic_id string Required
specialty enum general_practice | pediatrics | cardiology | dermatology
from_iso string Inclusive ISO 8601 start
to_iso string Exclusive ISO 8601 end
duration_minutes int 15 to 120, default 30
limit int 1 to 50, default 10

book_appointment

Create an appointment. Requires a caller-supplied idempotency_key; replays return the original appointment instead of double-booking. Voice agents will retry, so this is non-optional.

Field Type Notes
clinic_id string Required
provider_id string Must belong to clinic_id
patient_id string Must belong to clinic_id
start_iso string ISO 8601
duration_minutes int 15 to 120, default 30
reason string 1 to 500 chars
idempotency_key string 8 to 128 chars, caller-supplied

Returns { appointment, idempotent_replay }.

record_intake

Persist a structured intake note and assign a triage level.

Field Type Notes
clinic_id string Required
patient_id string Must belong to clinic_id
symptoms string[] 1 to 20 entries
severity int 1 to 10, patient-reported
onset_iso string ISO 8601
notes string Optional, max 2000 chars

Triage rule: severity >= 8 is urgent, >= 5 is elevated, otherwise routine.

search_protocols

Keyword search over the clinic's protocol library. Returns ranked snippets the model can cite when answering.

Field Type Notes
clinic_id string Required
query string 1 to 500 chars
limit int 1 to 20, default 5

The current implementation is a naive TF score with title weighting (3x). It exists to demonstrate the interface of a retrieval tool; production deployments would swap the backend for vector search (see Design notes).

escalate_to_oncall

Mark an existing appointment as urgent and reassign it to the clinic's on-call provider.

Field Type Notes
clinic_id string Required
appointment_id string Must belong to clinic_id
reason string 1 to 500 chars, appended to the appointment's reason

Returns { appointment, on_call_provider, reassigned }.

Design notes

Tenant isolation is enforced at the store, not the tool. Tools accept a clinic_id and pass it down. The store validates ownership on every accessor and throws TenantMismatchError on mismatch. If you add a new tool tomorrow, you cannot accidentally leak across clinics; the store will not let you.

Idempotency on writes. book_appointment requires an idempotency_key. Real callers (voice agents, retry loops, network blips) will repeat requests, and a healthcare system that responds to retries by creating duplicate appointments is a healthcare system that loses trust on day one.

Structured errors over thrown strings. Every domain failure is a typed DomainError subclass with a stable code. The MCP wrapper turns them into { ok: false, error: { code, message } }. Clients can branch on code instead of regexing message.

The retrieval tool is a stand-in. search_protocols uses an in-memory TF score so the repo runs without external services. In production this is the seam where you wire in Pinecone, pgvector, or your retrieval backend of choice. The tool's input/output contract stays the same.

Time handling is simplified. Provider working hours are interpreted in UTC for clarity. A real deployment would respect each clinic's timezone (already in the schema). Calling this out explicitly so reviewers know it's intentional, not an oversight.

What this isn't

  • Not clinical software. The triage rule is a toy and the protocol corpus is hand-written prose. Do not use it for anything that touches real patients.
  • Not HIPAA-compliant. The data is fake, the storage is in-memory, there is no audit log. Production would need all of that and then some.
  • Not a complete EMR or scheduling backend. The point is to show the MCP-server shape, not to ship a clinic system.

License

MIT. See LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured