Skip to main content

FHIR R4 MCP server — read, search, validate resources + SMART-on-FHIR auth (Epic/Cerner sandbox)

Project description

mcp-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.

PyPI PyPI - Python Version CI License

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

  1. Register at https://fhir.epic.com/My Apps → create a backend-services app.
  2. Note your Client ID and Client Secret.
  3. Create .env in 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

  1. Register at https://code.cerner.com/ → create a backend-services app.
  2. Note your Client ID and the token endpoint URL.
  3. 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

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_fhir-1.1.1.tar.gz (21.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_fhir-1.1.1-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file mcp_fhir-1.1.1.tar.gz.

File metadata

  • Download URL: mcp_fhir-1.1.1.tar.gz
  • Upload date:
  • Size: 21.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcp_fhir-1.1.1.tar.gz
Algorithm Hash digest
SHA256 b29fe58083ad553d6c35b03714bfe7190fe4278de3d1fc8da0749436ddc31ea9
MD5 8b72daa0626be6a36a53e542ee929e58
BLAKE2b-256 291aa7feb2aa7121ef2cb55bdef39faee008964c70aa6f4d02e8c60023770c37

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_fhir-1.1.1.tar.gz:

Publisher: release.yml on pcmedsinge/fhir-mcp-suite

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcp_fhir-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: mcp_fhir-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 22.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcp_fhir-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4e66b264186a95e37d0394fd66ade1635221fb13ebc704055779874f0488f5ac
MD5 ea5accae90eaf15d6ba18d45b75ce713
BLAKE2b-256 a6a9ef2abcc329b3c814bfdbc4a36d05388ffcfe46481e8694927a30cafc4d87

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_fhir-1.1.1-py3-none-any.whl:

Publisher: release.yml on pcmedsinge/fhir-mcp-suite

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page