Inspiration

One of us works as a healthcare assistant on an acute care ward. Every shift, we watched the same scene play out: a patient ready to go home, a clinician under time pressure, and a stack of paperwork that had to be assembled from scratch — medication lists cross-referenced by hand, follow-up appointments guessed at, patient instructions copy-pasted and edited for readability, readmission risk calculated mentally. On a busy day, that process takes 30–40 minutes per patient. On a short-staffed day, corners get cut. Missed medication changes, vague patient instructions, and skipped follow-ups are among the most consistent drivers of preventable 30-day readmissions.

We wanted to eliminate the assembly work entirely — not replace clinical judgment, but take the paperwork off the clinician's plate so they can spend that time with the patient instead of a keyboard.

What it does

Discharge+ is an MCP server that gives any AI agent six composable tools to automate the clinical discharge workflow against any FHIR R4 EHR:

  • GenerateDischargeInstructions — produces a dual discharge packet: a full clinical summary for the clinician, and plain-language take-home instructions for the patient. Available at three reading levels (simple, standard, detailed) with medical abbreviations automatically expanded throughout the patient-facing copy (e.g. "DVT" → "deep vein thrombosis", "INR" → "Blood Clotting Level (INR)").
  • ReconcileMedications — compares pre-admission medications against discharge medications and classifies every drug as new, stopped, changed, or continued. Runs drug interaction checks via RxNav and surfaces allergy conflicts.
  • AssessReadmissionRisk — computes the LACE index, a validated 0–19 readmission risk score, where L is length of stay (0–7), A is acuity of admission (0 or 3), C is Charlson Comorbidity Index (0–5), and E is prior emergency visits in the last 6 months (0–4). The tool returns the numeric score, a risk tier (low / moderate / high), and a plain-English recommendation.

  • PlanFollowUp — generates a structured follow-up plan (visits, labs, imaging) driven by the patient's active conditions and recent procedures.

  • AuditMedCosts — identifies brand-to-generic substitution opportunities for active medications, filtered against the patient's allergy list, with estimated monthly savings.

  • BuildDischargePacket — the master orchestrator. Invokes all five tools in parallel and assembles a single structured JSON discharge packet with a clinician section and a patient section.

The server reads SHARP context headers (x-fhir-server-url, x-fhir-access-token, x-patient-id) that Prompt Opinion injects automatically, so there is zero per-patient configuration required.

How we built it

The stack is TypeScript + Express + the @modelcontextprotocol/sdk. Each tool follows a clean IMcpTool interface — a Zod input schema, an execute function, and a registration call. FHIR data is fetched through a typed Axios client that injects the Bearer token from the SHARP context on every request.

Clinical logic is entirely deterministic and rule-based: a Charlson comorbidity mapping table, a drug interaction pair list, condition-to-follow-up rules, RxNorm-to-generic cost tiers, a warning-signs lookup, and a medical abbreviations dictionary. Nothing is inferred by an LLM at runtime — the agent calls the tool, the tool runs the rules, the tool returns structured JSON. This makes the outputs auditable and reproducible.

The BuildDischargePacket orchestrator runs all five sub-tools via Promise.all and wraps each call in a runSafe() helper that converts any thrown error into a { error: string } envelope rather than crashing the whole packet. A failing RxNav lookup will never prevent the patient instructions from being generated.

We deployed via Docker and published to the Prompt Opinion Marketplace, where SHARP context bridging meant the integration required no additional authentication code on our side.

Challenges we ran into

FHIR data variability. FHIR R4 is a standard, but real servers use it inconsistently. Medication coding arrives as RxNorm, NDC, or a plain text name depending on the system. We had to build multi-path extraction logic that tries RxNorm first, falls back to display text, and flags anything unresolvable as requiring manual review rather than silently dropping it.

The dual-packet rendering problem. The clinician needs clinical precision — abbreviations intact, change types explicit, LACE breakdown visible. The patient needs the opposite — no jargon, no scores, no specialty labels. Building a single data model that renders cleanly into both without duplicating the business logic required careful separation of the expansion and simplification passes.

LACE edge cases. The Charlson Comorbidity Index only scores specific SNOMED-coded conditions. Patients with conditions coded outside the standard set would return a deflated score. We handled this by making unrecognized codes contribute 0 (conservative, clinically appropriate) and documenting the behavior explicitly rather than guessing.

Fault tolerance under parallel execution. Running five FHIR-dependent tools simultaneously means five independent failure surfaces. Designing runSafe() to isolate failures without hiding them — surfacing { error: "…" } per section while leaving other sections intact — required deliberately testing partial-failure scenarios, not just happy paths.

Accomplishments that we're proud of

The end-to-end test against a synthetic HAPI FHIR patient (Sarah Kim, 45F, DVT + hip fracture, 4-day admission) passed all six tool scenarios and all eight edge cases on the first clean run — including the RxNav timeout fallback, the unknown SNOMED code path, and the missing-allergy-list path. No 500s, no silent data loss, no spurious interaction warnings.

The abbreviation expansion system — built entirely from a static lookup table — correctly rendered every medical term in the patient packet without a single LLM call. That felt important: the output a patient takes home should not depend on a non-deterministic inference step.

We're also proud of the reading level system. The gap between simple and detailed is significant: "You were in the hospital for 4 days for deep vein thrombosis and hip fracture." versus a fully structured clinical narrative with admission/discharge dates, procedure names, and expanded lab references. Same data, deliberately different voice.

What we learned

Deterministic, rule-based clinical tools and LLM agents are a natural complement. The agent is good at understanding what the clinician needs and translating that into a tool call. The tool is good at applying consistent rules to structured data and returning auditable output. Neither should do the other's job.

We also learned that SHARP context propagation is genuinely the hard problem in healthcare AI integration — and having the platform handle it meant we could focus entirely on clinical logic rather than OAuth flows and session management.

What's next for Discharge+

  • Multilingual patient packets. The abbreviation expansion and reading-level system is the right foundation; the next step is rendering the patient section in the patient's preferred language, pulled from the FHIR Patient.communication field.
  • ICD-10 support. The current rule tables are SNOMED-coded. Many real EHRs code conditions in ICD-10. Adding a SNOMED↔ICD-10 crosswalk would dramatically expand compatibility.
  • Nurse handoff tool. A GenerateHandoffSummary tool for shift-change communication — same FHIR data source, different rendering target.
  • Feedback loop. Capturing which follow-up recommendations clinicians accept or modify would let us refine the rule tables over time without touching the deterministic core.

Built With

Share this project:

Updates