clinic-mcp-server
by leomengineer
README.md
# clinic-mcp-server
**MCP Server: Company Data as Claude Tools.**
Expose a clinic's knowledge base and structured records to Claude Desktop (or any MCP client) as callable tools — with the same grounding discipline as [docs-rag-chatbot](../docs-rag-chatbot): **read tools grounded with citations; write tools gated behind approval.**
That contrast is the pitch. Anyone can wrap a search endpoint. Showing you thought about what happens when an agent can *change* something is what senior looks like.
## Architecture
```
Claude Desktop (stdio MCP)
│
▼
clinic-mcp-server
├─ search_clinic_docs → hybrid RRF over shared `chunks` (pgvector + tsvector)
│ + SIMILARITY_FLOOR gate → citations or "no relevant sources"
├─ get_patient_record → Postgres `patients` (+ next confirmed appointment)
├─ list_appointments → parameterized date-range query on `appointments`
└─ create_appointment → INSERT into `appointment_requests` (pending only)
NEVER writes confirmed `appointments`
```
Shares `DATABASE_URL` with docs-rag-chatbot (same Postgres). Retrieval code is local; the RAG `chunks` table is reused as-is. Clinic tables (`patients`, `appointments`, `appointment_requests`) are additive.
## Prerequisites
1. Postgres/pgvector running for [docs-rag-chatbot](../docs-rag-chatbot) with docs ingested (`make up && make ingest` there).
2. Python 3.12 + [uv](https://github.com/astral-sh/uv).
## Quick start (two minutes)
```bash
cd clinic-mcp-server
uv sync
cp .env.example .env # DATABASE_URL should match docs-rag-chatbot
# Create clinic tables + seed Jordan Lee et al. (does not touch chunks)
make setup
# Smoke-test over stdio (or point Claude Desktop at it — see below)
make serve
```
### Claude Desktop config
Add this to your Claude Desktop `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`). Use **absolute** paths:
```json
{
"mcpServers": {
"clinic": {
"command": "/Users/leovet/.cargo/bin/uv",
"args": [
"run",
"--directory",
"/Users/leovet/freelance/clinic-mcp-server",
"python",
"-m",
"clinic_mcp"
]
}
}
}
```
Restart Claude Desktop. You should see four tools under the clinic server.
A ready-to-merge example lives at [`claude_desktop_config.example.json`](claude_desktop_config.example.json).
> Tip: find `uv` with `which uv` if your install path differs.
## Demo (~60s)
1. Open Claude Desktop with the MCP server connected — show the four tools.
2. Ask: **"Do you accept Delta Dental PPO?"**
→ Claude calls `search_clinic_docs`, answers with a source citation (e.g. `06_insurance_faq.md`).
3. Ask: **"when is Jordan Lee's next appointment?"**
→ Claude calls `get_patient_record` with `jordan-lee`, returns structured data (insurance, upcoming crown consult).
4. Ask: **"book Jordan a crown consult next Tuesday"**
→ Claude calls `create_appointment`; response is `pending_approval` — nothing committed to the confirmed calendar.
5. Close: **"Read tools answer. The write tool proposes and waits for a human."**
## Tools
| Tool | Mode | Behavior |
|------|------|----------|
| `search_clinic_docs(query, top_k=5)` | read | Hybrid retrieval; below `SIMILARITY_FLOOR` returns `status=no_relevant_sources` (empty chunks) — Claude must not invent. |
| `get_patient_record(patient_id)` | read | Patient row + next confirmed appointment. Missing patient / DB down → tool error (not empty success). |
| `list_appointments(date_from, date_to)` | read | Confirmed appointments in an inclusive date window (clinic TZ). Empty list is valid; DB failure is an error. |
| `create_appointment(patient_id, datetime, service)` | write (gated) | Inserts `appointment_requests.status='pending'` only. Returns `pending_approval`. |
Demo patient id: **`jordan-lee`**.
## Config (`.env`)
| Variable | Purpose | Default |
|----------|---------|---------|
| `DATABASE_URL` | Shared Postgres (same as RAG app) | `postgresql://rag:rag@localhost:5432/rag` |
| `SIMILARITY_FLOOR` | Dense cosine gate for doc search | `0.35` |
| `CLINIC_TZ` | Timezone for date windows / naive datetimes | `America/Los_Angeles` |
## Makefile
```bash
make sync # uv sync --extra dev
make setup # schema + seed
make schema # clinic tables only
make seed # demo patients / appointments
make serve # stdio MCP server
make test # pytest
```
## Safety contract
- **Grounded reads:** `search_clinic_docs` refuses weak matches instead of returning noise Claude could cite.
- **Loud failures:** DB outages raise `[db_unavailable] …` tool errors. Empty appointment lists are only returned when the query succeeded.
- **Guarded writes:** `create_appointment` never inserts into `appointments`. A human must approve the pending request before anything is confirmed.
- **Out of scope (intentionally):** auth, multi-tenancy, OAuth, remote hosting, MCP resources/prompts. Four tools done well beats twelve half-wired.
## Tests
```bash
make test
```
Covers tool discovery/schemas, Pydantic validation, retrieval gate + citations, structured errors, patient/appointment lookups, and the invariant that write calls create pending requests only.
TDQS
A4.3/5.0
Scored across 4 tools
Disambiguation5/5
Each tool addresses a distinct task: searching knowledge base, retrieving patient record, listing appointments, and creating appointment requests. No functional overlap exists.
Naming Consistency5/5
All tools follow a consistent verb_noun snake_case pattern (search_clinic_docs, get_patient_record, list_appointments, create_appointment).
Tool Count5/5
4 tools are well-scoped for a clinic management server, covering search, patient lookup, appointment listing, and appointment creation. Neither too few nor too many.
Completeness3/5
The set covers basic read and create operations, but lacks tools for updating patient records, confirming/cancelling appointments, or deleting requests, which are notable gaps for a full appointment management workflow.
Maintenance
ActivitySlowing
ResponsivenessNo issues