A production-ready Model Context Protocol (MCP) server that connects LLMs to Epic's FHIR API. Supports both stdio (for Claude Desktop) and HTTP/SSE transports with OAuth2 backend systems authentication.
- OAuth2 Backend Systems Flow - Secure server-to-server authentication with JWT
- Epic FHIR R4 API - Access patient data, medications, observations, and more
- Dual Transport - Stdio for Claude Desktop, Streamable HTTP for web clients
Stdio Transport (Claude Desktop):
βββββββββββββββ stdin/stdout βββββββββββββββ OAuth2/FHIR βββββββββββββββ
β Claude β βββββββββββββββββΊ β MCP Server β ββββββββββββββββΊ β Epic FHIR β
β Desktop β β (Python) β β API β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
Streamable HTTP Transport (Web Clients):
βββββββββββββββ Streamable HTTP βββββββββββββββ OAuth2/FHIR βββββββββββββββ
β Web/API β ββββββββββββββββββΊ β MCP Server β ββββββββββββββββΊ β Epic FHIR β
β Client β β (Python) β β API β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
- Python 3.12+
- Epic FHIR Sandbox account (Sign up)
- OpenSSL (for key generation)
- uv (recommended) or pip
git clone <your-repo-url>
cd fhir-epic-mcpserver
# Install dependencies
uv sync
# or: pip install -e .# Run setup script to generate RSA keys and JWKS
uv run python create_keys.pyThis will create:
private_key.pem- Your private key (keep secret!)public_key.pem- Public keycertificate.pem- X.509 certificatejwks.json- JSON Web Key Set for Epic.env- Environment configuration
Epic requires your JWKS to be accessible via public HTTPS URL.
Option A: GitHub Gist (Quick)
- Go to https://gist.github.com
- Create new gist with filename
jwks.json - Paste contents from your
jwks.jsonfile - Create public gist
- Click "Raw" and copy the URL
Option B: Production Hosting
- Your domain:
https://yourdomain.com/.well-known/jwks.json - AWS S3, Azure Blob, or Google Cloud Storage (public)
- Go to https://fhir.epic.com/Developer/Apps
- Create Backend Systems app
- Select FHIR R4 APIs you need:
- Patient.Read
- Observation.Read
- Condition.Read
- MedicationRequest.Read
- AllergyIntolerance.Read
- Immunization.Read
- In Non-Production section:
- Add your JWKS URL
- Click Test to validate
- Click Save & Ready for Sandbox
- Wait 30 minutes for Epic to sync
Update .env with your Epic credentials:
EPIC_CLIENT_ID=your-client-id-from-epic
EPIC_PRIVATE_KEY_PATH=./private_key.pem
EPIC_TOKEN_URL=https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token
EPIC_FHIR_BASE_URL=https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4
# Server config
HOST=0.0.0.0
PORT=8000
LOG_LEVEL=INFO# For Claude Desktop (stdio mode - default)
uv run python src/server.py
# For HTTP/SSE mode (web/API usage)
uv run python src/server.py --transport sse --port 8000
# Production with uvicorn (SSE mode)
uv run uvicorn src.server:app --host 0.0.0.0 --port 8000Stdio mode runs on stdin/stdout for Claude Desktop
SSE mode runs HTTP server on http://localhost:8000
The MCP server exposes these tools to LLMs:
| Tool | Description | Required Args |
|---|---|---|
get_patient |
Get patient demographics | patient_id |
search_patients |
Search patients by name/DOB | family, given, birthdate, gender |
get_patient_conditions |
Get medical conditions | patient_id |
get_patient_medications |
Get medications/prescriptions | patient_id |
get_patient_observations |
Get labs/vitals | patient_id, category (optional) |
get_patient_allergies |
Get allergies | patient_id |
get_patient_immunizations |
Get vaccination history | patient_id |
get_patient_procedures |
Get procedures | patient_id |
Epic provides test patients with realistic data:
| Patient Name | FHIR ID | Available Data |
|---|---|---|
| Camila Lopez | erXuFYUfucBZaryVksYEcMg3 |
Medications, Labs, Procedures |
| Derrick Lin | eq081-VQEgP8drUUqCWzHfw3 |
Conditions, CarePlans |
| Desiree Powell | eAB3mDIBBcyUKviyzrxsnAw3 |
Immunizations, Vitals |
See full list: https://fhir.epic.com/Documentation?docId=testpatients
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"epic-fhir": {
"command": "uv",
"args": [
"run",
"python",
"/path/to/fhir-epic-mcpserver/src/server.py"
],
"env": {
"EPIC_CLIENT_ID": "your-client-id-here",
"EPIC_PRIVATE_KEY_PATH": "/path/to/fhir-epic-mcpserver/private_key.pem",
"EPIC_TOKEN_URL": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
"EPIC_FHIR_BASE_URL": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4"
}
}
}
}Important:
- Use absolute paths for the script and private key
- Replace
your-client-id-herewith your actual Epic Client ID - Replace
/path/to/fhir-epic-mcpserverwith your actual project path - Restart Claude Desktop after updating the config
Quick setup:
- Copy
claude_desktop_config.example.jsoncontent - Update the paths to your actual project location
- Add your Epic Client ID
- Paste into Claude Desktop config at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
For web-based or HTTP clients, run in SSE mode:
uv run python src/server.py --transport sse --port 8000Connect to SSE endpoint: http://localhost:8000/sse
epic-fhir-mcp-server/
βββ src/
β βββ __init__.py
β βββ server.py # HTTP/SSE MCP server
β βββ epic_client.py # Epic OAuth2 + FHIR client
β βββ tools.py # MCP tool definitions
β βββ config.py # Configuration management
βββ create_keys.py # Key generation script
βββ pyproject.toml # Dependencies
βββ .env.example # Environment template
βββ README.md
# Install dev dependencies
uv sync --dev
# Run tests
uv run pytest
# With coverage
uv run pytest --cov=src# Format code
uv run ruff format src/
# Lint
uv run ruff check src/Causes:
- App not "Ready for Sandbox"
- Epic configuration not synced (wait 30 min)
- Wrong client ID
- JWKS URL not accessible
Solutions:
- Verify app status in Epic portal
- Wait 30 minutes after configuration changes
- Test JWKS URL:
curl https://your-jwks-url - Check
kidmatches between JWT and JWKS
Causes:
- Token expired (1-hour lifetime)
- Missing Authorization header
- Insufficient scopes
Solutions:
- Token is cached automatically
- Check logs for authentication errors
- Verify required scopes in Epic app config
# Check server is running
curl http://localhost:8000/sse
# View logs
LOG_LEVEL=DEBUG uv run python src/server.pyThis MCP server can be used with the OpenAI Agents SDK to create AI agents that can interact with Epic FHIR data.
-
Install the OpenAI Agents SDK:
pip install openai-agents # or uv add openai-agents -
Set your OpenAI API key:
export OPENAI_API_KEY="your-api-key-here"
-
Run the simple test agent:
python test_agent_simple.py
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main():
async with MCPServerStdio(
name="Epic FHIR",
params={"command": "uv", "args": ["run", "python", "-m", "src.server"]},
) as mcp_server:
agent = Agent(
name="FHIR Assistant",
instructions="You are a helpful medical assistant.",
mcp_servers=[mcp_server],
)
result = await Runner.run(
starting_agent=agent,
input="Search for patient Derrick Borer"
)
print(result.final_output)
asyncio.run(main())