About mcp-fhir
mcp-fhir is an MCP server for reading, searching, and validating FHIR R4 resources against US Core and IPS profiles. It also supports SMART-on-FHIR backend authentication for EHR sandboxes such as Epic and Cerner.
Key features
- 01Read individual FHIR resources
- 02Search with FHIR query parameters
- 03Continue paginated searches
- 04Validate against US Core and IPS profiles
- 05SMART-on-FHIR OAuth 2.0 authentication
Use cases
- 01Retrieving records from a FHIR R4 server
- 02Searching clinical resources with pagination
- 03Validating resources against US Core or IPS profiles
- 04Connecting to Epic or Cerner EHR sandboxes
MCP support
It exposes five tools to MCP-compatible clients over stdio or SSE for FHIR capabilities, reads, searches, pagination, and profile validation. SMART-on-FHIR authentication can authorize requests to supported EHR sandboxes.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
uvx mcp-fhirFHIR_BASE_URLBase URL of the FHIR R4 server (default: https://hapi.fhir.org/baseR4)SMART_ENABLEDSet to 'true' to enable SMART-on-FHIR OAuth 2.0 (Epic/Cerner)Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.
Tools
Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.
Details
From the project READMEmcp-fhir
FHIR R4 MCP server — read, search, paginate, validate resources against US Core/IPS profiles, and authenticate against Epic/Cerner EHR sandboxes via SMART-on-FHIR OAuth 2.0.
Part of the fhir-mcp-suite monorepo.
What it does
mcp-fhir exposes five MCP tools that let any MCP-compatible AI (Claude, GPT-4o, etc.)
interact with any FHIR R4 server — and validate resources against US Core and IPS profiles —
in a single server process. No other public FHIR MCP server combines search + validation today.
| Tool | Description |
|---|---|
fhir_capabilities | CapabilityStatement summary — what the server supports |
fhir_read | Read a single resource by type + logical ID |
fhir_search | Search a resource type with FHIR query params; returns Bundle with _next_url when paginated |
fhir_search_next | Follow a _next_url pagination link (SSRF-guarded) |
validate_against_profile | Validate a resource via the HAPI validator; supports US Core & IPS aliases |
Architecture
Claude Desktop / AI client
│ MCP (stdio or SSE)
▼
┌───────────────────────────────┐
│ mcp-fhir v1.1 │
│ ┌───────────────────────────┐│
│ │ tools/ ││
│ │ ├─ fhir_capabilities.py ││
│ │ ├─ fhir_read.py ││
│ │ ├─ fhir_search.py ││ ──► FHIR R4 server
│ │ └─ validate_profile.py ││ ──► HAPI validator sidecar
│ └───────────────────────────┘│
│ fhir-mcp-shared │
│ ├─ LangFuse traces (session) │
│ ├─ Pydantic structured output │
│ └─ structlog JSON logging │
└───────────────────────────────┘
Quick start
# requires Python 3.12+ and uv
uvx mcp-fhir # stdio transport (Claude Desktop, default)
# SSE transport (HTTP, for API access)
MCP_TRANSPORT=sse uvx mcp-fhir
Claude Desktop config (claude_desktop_config.json)
{
"mcpServers": {
"mcp-fhir": {
"command": "uvx",
"args": ["mcp-fhir"],
"env": {
"FHIR_BASE_URL": "https://hapi.fhir.org/baseR4",
"HAPI_VALIDATOR_URL": "http://localhost:8082"
}
}
}
}
See full installation guide for HAPI validator setup, troubleshooting, and self-hosted FHIR server configuration.
Eval results
Golden query suite — run against the public HAPI demo server (hapi.fhir.org/baseR4):
| Category | Cases | Pass rate |
|---|---|---|
fhir_capabilities | 2 | 100 % |
fhir_read | 4 | 100 % |
fhir_search | 6 | 100 % |
fhir_search_next | 2 | 100 % |
validate_against_profile | 6 | 100 % |
| Total | 20 | 100 % |
CI gate:
uv run python evals/mcp-fhir/run_eval.py --ci --threshold 0.9
Validator cases require the HAPI sidecar (docker compose up hapi-validator).
Configuration
All settings via environment variables (see .env.example):
| Variable | Default | Description |
|---|---|---|
FHIR_BASE_URL | https://hapi.fhir.org/baseR4 | FHIR server base URL |
HAPI_VALIDATOR_URL | http://localhost:8082 | HAPI validator sidecar (runs on port 3500 internally; map 8082:3500) |
FHIR_TIMEOUT_S | 30 | HTTP timeout in seconds |
FHIR_MAX_RESULTS | 20 | Default _count for searches |
MCP_TRANSPORT | stdio | stdio or sse |
MCP_PORT | 8000 | Port for SSE transport |
LOG_FORMAT | json | json (prod) or console (dev) |
LANGFUSE_PUBLIC_KEY | (unset) | LangFuse observability (optional) |
LANGFUSE_SECRET_KEY | (unset) | LangFuse observability (optional) |
SMART-on-FHIR authentication
mcp-fhir v1.1.0 adds SMART-on-FHIR backend services (client_credentials grant, RFC 6749)
so the server can authenticate against real Epic / Cerner sandboxes — no browser redirect needed.
When SMART_ENABLED=true, every FHIR request gets an Authorization: Bearer <token> header.
Tokens are cached in-process and refreshed automatically 30 s before expiry.
SMART environment variables
| Variable | Default | Description |
|---|---|---|
SMART_ENABLED | false | Set true to activate SMART auth |
SMART_CLIENT_ID | "" | App client ID from EHR registration portal |
SMART_CLIENT_SECRET | "" | Client secret (stored as SecretStr, never logged) |
SMART_TOKEN_URL | "" | Token endpoint URL; auto-discovered if blank |
SMART_SCOPES | system/*.read | Space-separated OAuth 2.0 scopes |
SMART_GRANT_TYPE | client_credentials | OAuth grant type (only client_credentials supported) |
SMART_TOKEN_TIMEOUT_S | 15.0 | HTTP timeout for token requests |
When SMART_TOKEN_URL is blank the server performs SMART auto-discovery:
GET {FHIR_BASE_URL}/.well-known/smart-configuration → token_endpoint.
Falls back to {FHIR_BASE_URL}/oauth2/token if discovery returns a non-200.
Epic sandbox quick-start
- Register at https://fhir.epic.com/ → My Apps → create a backend-services app.
- Note your Client ID and Client Secret.
- Create
.envin the monorepo root (already in.gitignore):
FHIR_BASE_URL=https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4
SMART_ENABLED=true
SMART_CLIENT_ID=your-epic-client-id
SMART_CLIENT_SECRET=your-epic-client-secret
# SMART_TOKEN_URL is optional — auto-discovered from FHIR_BASE_URL
Cerner sandbox quick-start
- Register at https://code.cerner.com/ → create a backend-services app.
- Note your Client ID and the token endpoint URL.
- Update
.env:
FHIR_BASE_URL=https://fhir-ehr-code.cerner.com/r4/your-tenant-id
SMART_ENABLED=true
SMART_CLIENT_ID=your-cerner-client-id
SMART_CLIENT_SECRET=your-cerner-client-secret
SMART_TOKEN_URL=https://authorization.cerner.com/tenants/your-tenant-id/protocols/oauth2/profiles/smart-v1/token
Claude Desktop config with SMART auth
{
"mcpServers": {
"mcp-fhir": {
"command": "uvx",
"args": ["mcp-fhir"],
"env": {
"FHIR_BASE_URL": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4",
"SMART_ENABLED": "true",
"SMART_CLIENT_ID": "your-epic-client-id",
"SMART_CLIENT_SECRET": "your-epic-client-secret"
}
}
}
}
Programmatic client (LangGraph / any Python agent)
Use StdioServerParameters to point any Python MCP client at a FHIR server.
The same pattern works in LangGraph tool nodes, LangChain agents, or plain asyncio scripts.
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio, os
server = StdioServerParameters(
command="uv",
args=["run", "--package", "mcp-fhir", "mcp-fhir"],
env={
**os.environ,
# ── Public HAPI (no auth) ──────────────────────────────────
"FHIR_BASE_URL": "https://hapi.fhir.org/baseR4",
"SMART_ENABLED": "false",
# ── Epic sandbox (uncomment + fill in) ────────────────────
# "FHIR_BASE_URL": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4",
# "SMART_ENABLED": "true",
# "SMART_CLIENT_ID": os.environ["EPIC_CLIENT_ID"],
# "SMART_CLIENT_SECRET": os.environ["EPIC_CLIENT_SECRET"],
# ── Cerner sandbox (uncomment + fill in) ──────────────────
# "FHIR_BASE_URL": "https://fhir-myrecord.cerner.com/r4/<tenant-id>",
# "SMART_ENABLED": "true",
# "SMART_CLIENT_ID": os.environ["CERNER_CLIENT_ID"],
# "SMART_CLIENT_SECRET": os.environ["CERNER_CLIENT_SECRET"],
# "SMART_TOKEN_URL": "https://authorization.cerner.com/tenants/<tenant-id>/...",
},
)
async def main():
async with stdio_client(server) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"fhir_search",
arguments={"resource_type": "Patient", "params": {"_count": "3"}},
)
print(result.content[0].text)
asyncio.run(main())
See demo/fhir_server_configs.py for ready-to-run
configs for all supported servers (public HAPI, SMART Health IT, Epic, Cerner, local Docker).
Running SMART integration tests
# Requires real sandbox credentials in .env
uv run pytest packages/mcp-fhir/tests -m smart_integration -v
Profile validation
The validate_against_profile tool supports shorthand aliases:
| Alias | Profile URL |
|---|---|
us-core-patient | http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient |
us-core-observation | http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-clinical-result |
us-core-condition | http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition-problems-health-concerns |
us-core-medication-request | http://hl7.org/fhir/us/core/StructureDefinition/us-core-medicationrequest |
us-core-encounter | http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter |
ips-patient | http://hl7.org/fhir/uv/ips/StructureDefinition/Patient-uv-ips |
Profile validation requires the HAPI validator sidecar (included in
docker-compose.yml at repo root).
Development
# From monorepo root
uv sync
uv run pytest packages/mcp-fhir/tests -v
# Integration tests (requires HAPI public server access)
uv run pytest packages/mcp-fhir/tests -m integration
Architecture
Client (Claude / GPT-4o)
│ MCP protocol (stdio or SSE)
▼
mcp-fhir server (Python, anyio)
├── fhir_read ──────────► FHIR R4 server (configurable)
├── fhir_search ─────────► FHIR R4 server
└── validate_against_profile
│
▼
HAPI validator sidecar (markiantorno/validator-wrapper)
│
▼
LangFuse (optional observability)
License
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.