HEOR MCP Server
by lin-penglin
README.md
# HEOR MCP Server (synthetic data)
<!-- After your first push, replace USER and uncomment for a green CI badge:
 -->
An [MCP](https://modelcontextprotocol.io) server that lets Claude run **validated,
auditable HEOR analyses** — cohort construction, outcome computation, and
overlap-weighted comparison — as deterministic tools, on **synthetic** real-world
healthcare data.
> **What this demonstrates (for reviewers).** Putting an LLM agent to work on messy
> real-world healthcare data safely: the model *orchestrates*, but every number is
> computed by a deterministic, tested Python tool — never by the model. Codes are
> validated against a reference and **ambiguous codes are flagged, not guessed**,
> because a subtly wrong answer in a clinical/economic setting is worse than no
> answer. This is a public, synthetic reconstruction of the pattern behind a
> reusable HEOR dashboard framework I standardized for my team.
## Demo (example session)
A session against the synthetic data. **Every value below is real output from the tools
in this repo — nothing is invented.** Ask Claude, in plain English:
> *"Build a cohort for procedure P100, summarize the HEOR outcomes by approach, and run
> an overlap-weighted comparison of 30-day readmission (Robotic vs Open)."*
Claude calls the tools in sequence:
**1. `get_cohort(procedure_codes=["P100"])`**
```json
{ "cohort_id": "coh_9ddf25146e", "n": 2436,
"by_approach": { "Robotic": 1703, "Laparoscopic": 387, "Open": 346 } }
```
**2. `compute_heor_summary("coh_9ddf25146e")`** _(excerpt)_
```json
{ "clinical": { "any_complication_rate_pct": { "Robotic": 6.11, "Laparoscopic": 7.75, "Open": 14.16 } },
"utilization": { "readmission_30d_rate_pct": { "Robotic": 4.76, "Open": 6.94 },
"mean_length_of_stay": { "Robotic": 5.4, "Open": 7.68 } },
"economic": { "total_cost_median": { "Robotic": 19107, "Open": 19971 } } }
```
**3. `run_overlap_weighted_comparison("coh_9ddf25146e", outcome="readmit_30d")`**
```json
{ "unadjusted_difference": -2.18, "overlap_weighted_difference": -0.74,
"difference_unit": "percentage points", "n_treatment": 1703, "n_control": 346 }
```
Claude's answer back to the user:
> In the P100 cohort (n = 2,436), unadjusted 30-day readmission is 4.76% for Robotic vs
> 6.94% for Open — a 2.18-point gap. **After overlap weighting on age, CCI, and sex, the
> difference shrinks to 0.74 points** — most of the apparent advantage reflects that
> robotic patients were younger and healthier, not the approach itself. Every figure was
> computed by a tool; none was estimated by the model.
### The guardrail
**`validate_codes(["P100", "AMB1", "ZZZ9"])`**
```json
{ "results": [
{ "code": "P100", "status": "valid", "category": "Colorectal Resection" },
{ "code": "AMB1", "status": "ambiguous", "detail": "maps to multiple categories: ['Bleeding', 'Colorectal Resection']" },
{ "code": "ZZZ9", "status": "invalid", "detail": "not found in code reference" } ],
"requires_review": true }
```
`AMB1` is **flagged, not silently resolved** — the server refuses to guess which category a
code belongs to, because a subtly wrong code assignment can change the conclusion.
## Why it's built this way
- **The model must call a tool for any number.** No free-text estimates. Tools live in
`heor/` as plain, unit-tested Python; `server.py` only exposes them over MCP.
- **Correctness is checked.** `evals/run_evals.py` recomputes expected values straight
from the raw synthetic data and asserts the tools match — plus a case proving the
server *refuses* an ambiguous code instead of guessing.
- **Synthetic data only.** `data/generate_synthetic.py` creates the dataset from a fixed
seed. No real, licensed, or proprietary data is present anywhere in this repo.
## Tools exposed
| Tool | What it does |
| --- | --- |
| `get_cohort` | Build a cohort from procedure codes / approach / age; returns a `cohort_id` + counts |
| `compute_heor_summary` | Volume, clinical, utilization, and economic outcomes for a cohort |
| `run_overlap_weighted_comparison` | Overlap-weighted (ATO) Robotic-vs-Open comparison for one outcome |
| `validate_codes` | Classify codes as valid / invalid / **ambiguous** (the guardrail) |
Plus a `schema://heor` resource describing the columns.
## Quickstart
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python data/generate_synthetic.py # writes data/*.csv (deterministic)
python evals/run_evals.py # correctness + guardrail checks — no API key needed
```
Run the MCP server (stdio) and connect a client (Claude Desktop / Claude Code):
```bash
python server.py
# Claude Code: claude mcp add heor -- python /abs/path/to/server.py
# Claude Desktop: add to claude_desktop_config.json (see MCP docs)
```
Then ask, in plain English: *"Build a cohort for procedure P100, give me the HEOR summary,
and run an overlap-weighted comparison of 30-day readmission (Robotic vs Open)."* Watch it
call the tools.
> SDK note: `server.py` uses `mcp.server.fastmcp`. The MCP SDK evolves — confirm import
> names against the current docs at modelcontextprotocol.io if anything has moved.
## Mapping to the real framework
In the private R/Shiny version, these tools correspond to the reusable statistics layer
(`run_overlap_weighting`, `compare_before_after_overlap_weighting`, the code-list-driven
cohort logic). This repo reconstructs the *pattern* on synthetic data so it can be shared.
## Repo layout
```
heor-mcp/
data/generate_synthetic.py # deterministic synthetic dataset + code reference
heor/ # plain, tested Python tools (no MCP dependency)
cohort.py outcomes.py weighting.py validate.py dataset.py
server.py # MCP server exposing the heor/ tools
evals/run_evals.py # recompute-from-raw correctness + guardrail eval
tests/test_heor.py # pytest unit tests (synthetic data)
```
All results are illustrative and computed on synthetic data.
## Continuous integration
`.github/workflows/ci.yml` regenerates the synthetic data, runs the correctness +
guardrail evals, and runs the unit tests on every push — so the "evals pass" claim
is verifiable, not just asserted.
## License
MIT — see [LICENSE](LICENSE). Synthetic data only; no real, licensed, or proprietary
data is included in this repository.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing