Skip to main content
Glama
Ath404
by Ath404
README.md
# fhir-journey-mcp

A read-only [MCP](https://modelcontextprotocol.io) server that gives an AI agent a patient's context from any FHIR R4 server: who they are, when they were last seen, what is booked next, and which discharged patients never got a follow-up.

Hospitals already run HIS, EMR and scheduling systems that speak (some) FHIR. An agent that calls patients or answers the front desk needs that context before it says a word, and it should not need write access to get it.

## Tools

| Tool | What it answers |
|---|---|
| `fhir_search_patients` | Find a patient by name, phone, identifier (MRN, ABHA number) or birth date. Requires at least one filter. |
| `fhir_get_patient_journey` | Encounters + appointments as one dated timeline, last seen, next appointment, no-show and cancellation counts. |
| `fhir_list_appointments` | Appointments by patient, practitioner, status and date range, e.g. all `noshow` + `cancelled` last week to rebook. |
| `fhir_find_open_slots` | Free slots for a practitioner (chained through `Schedule.actor`) or schedule, earliest first. Does not book. |
| `fhir_find_followups_due` | Finished encounters in a window with **no live follow-up within N days**. Cancelled or no-show follow-ups don't count as booked. Each row says whether the window is already `overdue` or can still be met, and shows any follow-up booked too late. |

`fhir_find_followups_due` output, from the live smoke run below:

```json
[
  { "patient": "Meera Demo", "discharged": "2026-10-09T12:00:00.000Z", "dueBy": "2026-10-16T12:00:00.000Z", "lateBooking": "2026-10-28T10:00:00.000Z" },
  { "patient": "Rohan Demo", "discharged": "2026-10-13T12:00:00.000Z", "dueBy": "2026-10-20T12:00:00.000Z", "lateBooking": null }
]
```

## Run it

```bash
npm install
npm run build
FHIR_BASE_URL=https://your-server/fhir FHIR_TOKEN=... npx fhir-journey-mcp
```

Claude Desktop / any MCP client:

```json
{
  "mcpServers": {
    "fhir-journey": {
      "command": "node",
      "args": ["/path/to/fhir-journey-mcp/dist/mcp/server.js"],
      "env": { "FHIR_BASE_URL": "https://hapi.fhir.org/baseR4" }
    }
  }
}
```

With no `FHIR_BASE_URL` it uses the public HAPI test server, which holds synthetic data only.

## Verify it

```bash
npm test          # 22 offline tests against a fake FHIR server
npm run seed      # loads 4 synthetic patients with known answers, tagged per run
npm run smoke     # starts the server over stdio and checks every tool's answer
```

`scripts/seed.ts` documents the expected answers (who is booked, who is due, whose follow-up was cancelled and rebooked late). `npm run smoke` passes 10/10 against the public HAPI server.

## Design choices

- **Read-only.** Booking, cancelling and messaging patients belong behind a human or a separate, audited tool.
- **No full-table reads.** Search tools refuse to run without a filter.
- **Ids are validated** before they reach a URL path; a model can't turn `patientId` into `../Practitioner/1`.
- **Paging links are only followed on the configured origin**, so a bearer token is never sent to a host the server named in a `next` link.
- **Search URLs are redacted from errors**, since they carry names and phone numbers.
- **GET retries on 408/429/5xx with backoff; transaction POSTs are never replayed.**
- Compact summaries, not raw resources: less token spend, fewer fields for a model to misread.
- `meta.tag` filter on every search tool, for scoping to one tenant or one test dataset.

## Not done / honest limits

- Tested against HAPI FHIR only. Real hospital systems use FHIR profiles and partial implementations differently (chained search and `_sort` support vary). Not tested against any production EMR/HIS.
- No ABDM gateway integration, consent artefact handling, or SMART-on-FHIR auth flow; it takes a bearer token you already have.
- "Follow-up" means any live appointment for the patient after discharge, not one matched to the same department or condition.
- `fhir_find_followups_due` makes one appointment query per patient (5 in parallel). Fine for a daily worklist, not for a 10,000-discharge backfill.

MIT licensed.

TDQS

A3.8/5.0

Scored across 5 tools

Disambiguation4/5

Each tool targets a distinct resource or workflow: patient search, patient timeline, appointment listing, open slots, and follow-up detection. The only mild overlap is that get_patient_journey already embeds a patient's appointments while list_appointments offers a filtered appointment view, so an agent could occasionally hesitate on which to use for appointment data.

Naming Consistency5/5

All tools use a uniform fhir_ prefix followed by a consistent verb_noun pattern (search_patients, get_patient_journey, list_appointments, find_open_slots, find_followups_due). The convention is predictable and readable throughout.

Tool Count5/5

Five tools is well-scoped for a focused patient-journey server; each one earns its place covering search, context, appointments, availability, and follow-up detection without redundancy.

Completeness3/5

The read-only surface covers discovery and identification well, but the implied workflow dead-ends: find_open_slots explicitly cannot hold or book, and find_followups_due returns patients to contact with no tool to actually schedule the follow-up. Booking/rescheduling operations are notable gaps for the scheduling workflow the tools describe.

Maintenance

ActivityMaintained
ResponsivenessNo issues