# MCP FHIR Server
A generic MCP server providing read/write access to any FHIR-compliant API with built-in validation.
This server works with **any FHIR server**, not just Zus Health. For Zus-specific features (like getting UPIDs), see the Zus Extensions section below.
## Features
### Core FHIR Features
- **FHIR resource validation** using consolidated FHIR schemas
- **Create and update** resources (POST/PUT)
- **Read** resources by type and ID
- **Search** resources with query parameters
- **Granular permissions** via environment configuration
- **Bearer token authentication**
- **Custom HTTP headers** for multi-tenant or vendor-specific requirements
- **Detailed error messages** for debugging and LLM-based correction
### Zus Health Extensions (Optional)
- **Zus UPID lookup** - Get Universal Patient IDs from Zus FHIR servers
- **Intelligent name matching** - Find best patient match when multiple results exist
- **Builder ID support** - Multi-tenant access via `Zus-Account` header
## Installation
### Prerequisites
- Python 3.13+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
### Setup
```bash
# Clone the repository
git clone <repository-url>
cd mcp-fhir
# Install dependencies (production only)
uv sync
# For development (includes test tools, linter, etc.)
uv sync --extra dev
# Or with pip
pip install -e .
```
**Note:** The `make` commands will automatically install development dependencies when needed, so you can also just run `make test` directly after cloning.
## Configuration
### Environment File
The server can load environment variables from a file using the `--env-file` command line flag:
```bash
# Load environment variables from a specific file
uv run fastmcp run server.py --env-file /path/to/your/.env
# Or for development
uv run fastmcp dev server.py --env-file /path/to/your/.env
```
If no `--env-file` flag is provided, the server will use system environment variables only.
Create a `.env` file:
```bash
cp .env.example .env
```
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `FHIR_BASE_URL` | `http://localhost:8080/fhir` | FHIR server base URL |
| `FHIR_ALLOW_READ` | `true` | Enable GET operations |
| `FHIR_ALLOW_WRITE` | `true` | Enable POST/PUT/PATCH/DELETE operations |
| `FHIR_AUTH_TOKEN` | _(empty)_ | Bearer token for authentication |
| `FHIR_ALLOWED_METHODS` | _(empty)_ | Comma-separated HTTP methods (overrides READ/WRITE) |
### Permission Model
**Option 1: Simple Read/Write** (default)
```bash
FHIR_ALLOW_READ=true # Enables GET
FHIR_ALLOW_WRITE=true # Enables POST, PUT, PATCH, DELETE
```
**Option 2: Granular Methods** (takes precedence)
```bash
FHIR_ALLOWED_METHODS=GET,POST # Only read and create
```
Examples:
- `GET` - Read-only
- `POST,PUT` - Create and update only (no reads)
- `GET,POST` - Read and create (no updates)
- `GET,POST,PUT` - Full access
## Running
### Development
```bash
# Using make (recommended)
make dev
# Or directly
uv run fastmcp dev server.py
```
### Production
```bash
# Using make (recommended)
make run
# Or directly
uv run fastmcp run server.py
```
## Claude Desktop Integration
Edit your Claude Desktop config file:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
**Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"fhir": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/mcp-fhir",
"run",
"fastmcp",
"run",
"server.py",
"--env-file",
"/absolute/path/to/mcp-fhir/.env"
]
}
}
}
```
**Alternative:** You can also set environment variables directly in the config:
```json
{
"mcpServers": {
"fhir": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/mcp-fhir",
"run",
"fastmcp",
"run",
"server.py"
],
"env": {
"FHIR_BASE_URL": "https://your-fhir-server.com/fhir",
"FHIR_ALLOW_READ": "true",
"FHIR_ALLOW_WRITE": "true",
"FHIR_AUTH_TOKEN": "your-token-here"
}
}
}
}
```
Restart Claude Desktop after editing.
## Tools
### Core FHIR Tools
These tools work with any FHIR-compliant server:
#### `write_fhir_resource`
Create or update a FHIR resource.
**Parameters:**
- `resource` (object): FHIR resource JSON
- `custom_headers` (object, optional): Custom HTTP headers for the request
- For Zus servers: `{"Zus-Account": "builder-id"}` for multi-tenant access
- For other servers: Any vendor-specific headers your FHIR server requires
**Behavior:**
1. Validates resource against FHIR schema
2. Uses POST if no `id` field (create), PUT if `id` exists (update)
3. Returns validation errors for correction if invalid
4. Returns server response on success (if `FHIR_ALLOW_READ=true`)
**Example:**
```json
{
"resourceType": "Patient",
"name": [{"family": "Smith", "given": ["John"]}],
"gender": "male"
}
```
**Note:** If validation schema fails to load, validation is skipped (server-side validation still applies).
---
#### `read_fhir_resource`
Read a resource by type and ID.
**Parameters:**
- `resource_type` (string): e.g., "Patient", "Observation"
- `resource_id` (string): Resource ID
- `custom_headers` (object, optional): Custom HTTP headers for the request
**Returns:** JSON resource or error message
---
#### `search_fhir_resources`
Search resources with query parameters.
**Parameters:**
- `resource_type` (string): Resource type to search
- `search_params` (object, optional): Query parameters
- Example: `{"name": "Smith", "gender": "female"}`
- `custom_headers` (object, optional): Custom HTTP headers for the request
**Returns:** FHIR Bundle with matching resources
---
#### `get_fhir_config`
View current configuration.
**Returns:** Configuration summary including base URL, permissions, and allowed methods.
---
### Zus Health Extensions
**These tools are specific to Zus Health FHIR servers and will not work with other FHIR implementations.**
#### `get_patient_zus_upid`
Get the Zus UPID (Universal Patient ID) for a Patient resource from Zus FHIR server.
**Parameters:**
- `first_name` (string): Patient's first name
- `last_name` (string): Patient's last name
- `builder_id` (string, optional): Zus builder ID to filter the search
**Behavior:**
1. Searches for Patient resources using `name` parameter (concatenated first and last name)
2. Optionally filters by Zus `builderID` parameter if provided
3. Extracts Zus UPID from Patient's identifiers with system `https://zusapi.com/fhir/identifier/universal-id`
4. When multiple patients are found, uses intelligent name matching to find the best match
5. Returns the Zus UPID value or appropriate error message
**Example usage:**
```
get_patient_zus_upid("John", "Smith")
get_patient_zus_upid("John", "Smith", "builder-123")
```
**Response formats:**
- Single patient found: `Zus UPID: zus-upid-12345`
- Multiple patients with good name match: `Zus UPID: zus-upid-12345 (Best match: John Smith)` + other matches if any
- Multiple patients with no clear match: Lists all found patients with their Zus UPIDs
- No patients found: `Error: No Patient found with name 'John Smith'`
- No Zus UPID: `Error: No Zus UPID found for Patient(s) with name 'John Smith'`
**Name Matching Logic:**
- Exact name matches get highest priority (score 1.0)
- Partial matches (e.g., "John" matching "Johnny") get medium priority (score 0.7 for given name)
- Family name matches are weighted more heavily than given name matches
- Partial matches are permissive: shorter names can match longer ones (e.g., "John" matches "Johnny")
- If the best match has a score ≥ 0.5, it's returned as the primary result
- Other decent matches (score ≥ 0.3) are listed as alternatives
## Technical Details
### HTTP Headers
All requests include:
```
Content-Type: application/fhir+json
Accept: application/fhir+json
Authorization: Bearer {token} (if FHIR_AUTH_TOKEN set)
```
**Custom Headers:**
You can provide additional custom headers via the `custom_headers` parameter in any tool. This is useful for:
- Multi-tenant systems (e.g., Zus's `Zus-Account` header)
- Vendor-specific authentication or routing headers
- Any other FHIR server-specific requirements
**Example (Zus):**
```json
{"Zus-Account": "builder-123"}
```
### Timeouts
All requests timeout after 30 seconds.
### Error Handling
The server returns detailed errors for:
| Code | Description |
|------|-------------|
| 400 | Invalid request/validation error |
| 401 | Authentication failed |
| 403 | Insufficient permissions |
| 404 | Resource or endpoint not found |
| 422 | Business rule violation |
| Timeout | Connection timeout (30s) |
Errors include full server response when available for debugging.
### Validation
Resources are validated using the `fhir-validator` library before submission:
- Checks FHIR spec compliance
- Validates required fields and data types
- Verifies resource structure
If validation schema loading fails at startup, a warning is logged and validation is bypassed (server-side validation still occurs).
## Development
### Testing
```bash
# Run tests (automatically installs dev dependencies if needed)
make test
# With coverage
make test-cov
# Watch mode
make test-watch
```
Or directly:
```bash
uv run pytest
uv run pytest --cov=. --cov-report=term-missing
```
**Note:** All `make` commands automatically install development dependencies when needed, so new developers can simply run `make test` after cloning the repository.
### Code Quality
```bash
make lint # Run linter (automatically installs dev dependencies)
make format # Format code (automatically installs dev dependencies)
make check # Lint + format check (automatically installs dev dependencies)
```
### Project Structure
```
mcp-fhir/
├── server.py # Generic MCP FHIR server implementation
├── zus_extensions.py # Zus Health-specific tools (optional)
├── fhir_validator.py # FHIR validation logic
├── pyproject.toml # Dependencies
├── .env.example # Example configuration
└── tests/ # Test suite
```
### Architecture
The server is designed with modularity in mind:
- **server.py**: Contains generic FHIR operations that work with any FHIR server
- **zus_extensions.py**: Contains Zus Health-specific functionality (UPID lookup, etc.)
- **Generic tools** accept `custom_headers` for flexibility with different FHIR vendors
- **Zus tools** use `builder_id` for Zus-specific multi-tenancy
This separation allows you to:
1. Use the generic tools with any FHIR server
2. Add your own vendor-specific extensions by following the zus_extensions.py pattern
3. Keep the core FHIR functionality clean and standards-compliant
## License
[Add license information]
## Contributing
[Add contribution guidelines]