Legacy-to-FHIR Mapping MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Legacy-to-FHIR Mapping MCP ServerFind patient Jane Smith and her recent vitals"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Legacy-to-FHIR Mapping MCP Server

Note: For this demo, a simplified local REPL (scripts/demo.py) that calls the
tool functions directly, with a keyword-based stand-in for tool selection —
the real natural-language routing is AI-driven via an MCP client, documented in Usage below.
A Model Context Protocol (MCP) server that sits on top of a mock legacy
healthcare database and exposes read-only tools that map fragmented,
inconsistently-formatted records into valid, schema-conformant FHIR
resources (Patient, Observation, Condition) — queryable by an AI agent
using natural language instead of hand-written SQL. Mock/synthetic data only — no real PHI. Read-only by design, no write path.
Overview
Legacy healthcare systems store patient data in fragmented, non-standardized formats that predate modern interoperability standards like FHIR. Hospitals and payers still run production systems on these decades-old schemas, and migrating them wholesale is slow, expensive, and high-risk — which is why healthcare interoperability/translation layers exist commercially as a practical middle ground: leave the legacy system in place, expose it through a standards-conformant interface instead of a full rewrite. Every AI agent or modern tool that wants to use this data safely needs exactly that kind of translation layer in front of it — natural-language querying without one means an LLM either can't reach the data at all, or reaches it by writing raw SQL against an undocumented, inconsistent schema, with no guarantee the result is even valid healthcare data.
This project builds a working (if intentionally small-scale) version of that translation layer end-to-end: a synthetic legacy database with realistic messiness (inconsistent date formats, demographic data split across unlinked tables, units embedded in free-text values, un-coded diagnosis notes), and an MCP server that maps it cleanly into FHIR — never silently guessing when a mapping is ambiguous, and never returning a resource that hasn't independently passed schema validation.
Related MCP server: FHIR MCP Server
Features
Natural-language patient/vitals lookup over a mock legacy database
Maps fragmented legacy records into valid FHIR
Patient,Observation, andConditionresourcesLOINC/SNOMED-CT code mapping, verified against a live FHIR terminology server rather than trusted at face value (
mapping/fhir-mapping-schema.md)Independent FHIR R4B schema validation gate on every returned resource — nothing reaches the caller without passing this check
Read-only by design — the SQLite connection is opened in
mode=ro, so no write path exists even at the driver levelDeliberate fallback-over-guessing behavior for every ambiguous mapping case (missing units, unmapped codes, ambiguous patient joins) — flagged and reported, never inferred
Architecture
flowchart TD
Q["Natural-language query"] --> S["MCP server\nmcp_server/server.py"]
S -->|"patient / condition query"| T1["query_legacy_patient_records\n(Patient + Condition)"]
S -->|"vitals / labs query"| T2["translate_vitals_log\n(Observation)"]
T1 --> R["legacy_repo.py\nresolves query against mock legacy\nSQLite database (data/legacy.db)"]
T2 --> R
R --> B["fhir_build.py\nmaps raw legacy fields to FHIR elements\nvia codes.py (LOINC/SNOMED-CT) + datetimes.py"]
B --> V{"validation.py\nindependent FHIR R4B\nschema check"}
V -->|"pass"| OK["Validated FHIR Bundle\nreturned to caller"]
V -->|"fail"| BAD["Excluded from bundle,\nreported in validation_failures"]The mock legacy data itself comes from Synthea
(MITRE's open-source synthetic patient generator), then deliberately degraded
by legacy_data/mangle.py into a fragmented, legacy-system-shaped schema —
see Legacy data design below.
Technical specification
Schema validation
Every FHIR resource this server returns is a pydantic model, not a plain
dict — fhir.resources (the FHIR model library used here) is built on
fhir_core, which is itself built on
pydantic v2. Concretely:
>>> from fhir.resources.R4B.patient import Patient
>>> import pydantic
>>> issubclass(Patient, pydantic.BaseModel)
Truemcp_server/validation.py instantiates the matching pydantic model
(Patient, Observation, or Condition) for every resource fhir_build.py
produces, as an independent step that doesn't trust the mapping code that
built the resource. Pydantic's own validation raises on missing required
fields, wrong types, or malformed structure — the exact same enforcement
mechanism a hand-written Zod schema would provide in a TypeScript service,
applied here to the FHIR R4B spec. A resource that fails is excluded from
the bundle and reported in validation_failures; there is no code path
that returns a resource without going through this check.
Security boundaries
Boundary | How it's enforced |
Read-only data access | The SQLite connection is opened in |
No secrets / API keys | Nothing in the codebase reads |
No real PHI | All patient data is synthetic, generated by Synthea — every SSN carries Synthea's |
Local-only execution | The MCP server communicates over stdio only; it does not open a network port or accept remote connections |
Untrusted-input handling | Every ambiguous or unparseable legacy value is flagged and excluded rather than guessed — see Validation & fallback philosophy |
Prerequisites
Python 3.10+
A JDK (11+) only if you intend to regenerate the mock database from scratch — not needed to run the server against the checked-in
data/legacy.db
Installation
Three steps — clone, install, verify. Dependency versions are pinned
(pyproject.toml) to a combination that's actually tested working; an
unpinned fhir.resources install can pull in an incompatible
annotated-types release and fail with an ImportError, so match these
versions rather than installing latest:
# 1. Clone
git clone https://github.com/gdanse/legacy-to-fhir-mcp-server.git
cd legacy-to-fhir-mcp-server
# 2. Install (pinned versions -- see pyproject.toml)
python3 -m venv .venv && source .venv/bin/activate
pip install "mcp==1.28.1" "fhir.resources==8.3.0" "annotated-types<0.8.0"
# 3. Verify
python3 scripts/test_query_legacy_patient_records.pyAll three steps together run in well under two minutes on a normal
connection — the mock database (data/legacy.db) is already checked in,
so there's no seeding step.
Usage
Start the server (stdio transport):
python3 mcp_server/server.pyClaude Code isn't required — the server speaks the standard Model Context Protocol over stdio, so anything that can act as an MCP client can use it. A few ways to actually make queries:
Via Claude Code (or any MCP client)
Copy .mcp.json.example to .mcp.json and replace <path-to-this-repo>
with your local checkout path (.mcp.json is gitignored since that path is
machine-specific):
cp .mcp.json.example .mcp.jsonThen ask natural-language questions — the model picks the right tool automatically:
Example query | Tool selected | Returns |
|
|
|
|
|
|
|
|
|
Both tools accept a name, a legacy MRN, or both, and return:
{
"bundle": "<FHIR searchset Bundle>",
"warnings": ["<flags for anything left deliberately unresolved>"],
"validation_failures": ["<resources excluded for failing schema validation>"]
}Any other MCP client works the same way — e.g. the standalone MCP Inspector, a browser-based dev tool for exercising an MCP server without wiring it into an AI assistant at all:
npx @modelcontextprotocol/inspector python3 mcp_server/server.pyWithout any MCP client — direct Python calls
Natural-language tool selection is what an MCP client provides. The data retrieval, FHIR mapping, and validation underneath don't depend on it — the tool functions are plain Python and can be called directly with a query string, no AI involved:
from mcp_server.server import query_legacy_patient_records, translate_vitals_log
query_legacy_patient_records("Ben Torp")
translate_vitals_log("Adelle Raynor's weight")This is exactly what the smoke tests in scripts/ do — see
Testing below.
Configuration
None needed. This project requires no API keys or secrets — all data is
local, synthetic, and self-contained (data/legacy.db), and no code path
calls any external service at runtime.
Testing
Smoke tests bypass the MCP transport, call the tool functions directly, and
validate every returned resource against fhir.resources' FHIR R4B models:
python3 scripts/test_query_legacy_patient_records.py
python3 scripts/test_translate_vitals_log.py
python3 scripts/test_validation_layer.pyscripts/verify_legacy_mess.py prints direct SQL evidence of each legacy
mess pattern described below, straight from data/legacy.db.
scripts/demo.py is an interactive REPL for trying queries yourself
without any MCP client (this is what the demo GIF above was recorded
from) — run it and type a natural-language query at the query> prompt.
Project structure
mcp_server/ MCP server + FHIR mapping/validation logic
server.py tool entry points
legacy_repo.py resolves natural-language queries against the DB
fhir_build.py maps legacy rows -> FHIR resource dicts
codes.py LOINC / SNOMED-CT lookup tables
datetimes.py legacy date-format parsing
validation.py independent FHIR R4B schema validation gate
legacy_data/ builds the mock legacy database from Synthea output
extract.py reads clean FHIR bundles
mangle.py deliberately degrades them into legacy-shaped rows
schema.py SQLite schema for the four legacy tables
mapping/
fhir-mapping-schema.md field-by-field mapping spec + verified code tables
scripts/ smoke tests + database build/verification scripts
demo.py interactive REPL for trying queries (used for the demo GIF)
docs/demo.gif the README demo recording
data/legacy.db the mock legacy database (checked in, regeneratable)Legacy data design
data/legacy.db is generated from Synthea's clean, FHIR-conformant output,
then deliberately degraded into four legacy-system-shaped SQLite tables:
Table | Legacy mess pattern |
| DOB as |
| Same patient's other half of the demographic record, in a separate table with no shared key — joins to |
| Date as bare |
| Date as |
To regenerate from scratch:
# 1. Requires a JDK (11+) on PATH.
git clone --depth 1 https://github.com/synthetichealth/synthea.git
cd synthea
./run_synthea -p 40 # generates ~40 synthetic patients into output/fhir/
# 2. Copy the bundles into this project
cp -R output/fhir "<this-repo>/synthea_output/fhir"
# 3. Build the mangled legacy SQLite DB
cd "<this-repo>"
python3 scripts/build_legacy_db.pyValidation & fallback philosophy
Every resource returned by either tool passes through mcp_server/validation.py
— an independent FHIR R4B schema check that doesn't trust the mapping logic
in fhir_build.py. Anything that fails is excluded from the bundle and
reported in a sibling validation_failures list instead of being returned
as if it were valid.
The same never-guess principle governs every ambiguous mapping case:
Flag | Resource | Trigger |
| Patient | Zero or multiple name+DOB matches across the split identity/contact tables |
| Observation |
|
| Observation |
|
| Condition |
|
See mapping/fhir-mapping-schema.md for the full field-by-field mapping
spec, including the verified LOINC/SNOMED-CT lookup tables and the
corrections made when initial code guesses turned out to be wrong.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gdanse/legacy-to-fhir-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server