fhirHydrant: FHIR MCP Server
A modern, fully configurable, open-source Node.js Model Context Protocol (MCP)
server for R4+ FHIR APIs. It connects MCP-compatible LLM AI clients to
clinical data over SMART on FHIR v2 Backend Services using signed JWT client
credentials.
fhirHydrant turns FHIR resources, named operations, terminology lookups, and
pagination into MCP tools. The default resources and operations are starting
points: resources, operations, search controls, instructions, and messages can
be expanded, trimmed, or replaced through config files without source changes.
- SMART Backend Services auth with JWKS hosting, key rotation, token refresh,
and dynamic scopes
- Configurable resource tools for search, direct read, vread, history, and
optional metadata-gated CRUD
- Config-driven named operations for clinical data, terminology, IPS, patient
matching, validation, and custom workflows
- CapabilityStatement-aware tools, search controls, operation gating, and
runtime scope checks
- Token economy features: compact responses, FHIRPath filtering, byte limits,
_count shaping, and oversized Bundle retry
- Optional terminology tools, PHI-light audit events (no resource content by
default), and stdio or Streamable HTTP transport
Note: FHIR data returned through MCP tool calls may contain PHI.
Make sure your MCP client's transcript storage and logging behavior match
your compliance requirements.
Contents
Quick Start
Requirements
- Node.js >= 24
- A supported FHIR server
- For SMART auth (default): a SMART Backend Services client registration and an
RSA-2048 or EC P-384 private key whose public key is available through JWKS
To run against a public, unauthenticated FHIR test server, set FHIR_AUTH=none
and skip the client and key entirely (see Unauthenticated Access).
The stdio transport usually needs an externally hosted JWKS URL. The built-in
/jwks endpoint is available only when fhirHydrant runs over HTTP with SMART auth.
Install
# install globally
npm install -g fhirhydrant
# or run without installing
npx fhirhydrant
Run from source:
git clone https://github.com/faulkj/fhirhydrant.git
cd fhirhydrant
npm install
npm run build
MCP Client Config
For desktop MCP clients, stdio is usually the simplest transport:
{
"mcpServers": {
"fhirhydrant": {
"command": "npx",
"args": ["-y", "fhirhydrant"],
"env": {
"MCP_TRANSPORT": "stdio",
"FHIR_BASE_URL": "https://fhir.example.org",
"FHIR_CLIENT_ID": "your-client-id",
"FHIR_ACTIVE_KEY": "LS0tLS1CRUdJTi...base64-of-your-pem...",
"FHIR_JWKS_URL": "https://example.org/.well-known/jwks.json"
}
}
}
}
FHIR_ACTIVE_KEY is your PKCS#8 private key (RSA or EC P-384), base64-encoded.
The kid is derived automatically at startup via a truncated JWK Thumbprint and
logged to the console.
Unauthenticated Access
To point fhirHydrant at a public, unauthenticated FHIR endpoint (handy for
testing against open sandboxes), set FHIR_AUTH=none. No client ID or signing
key is required, no token is requested, and requests are sent without an
Authorization header:
{
"mcpServers": {
"fhirhydrant": {
"command": "npx",
"args": ["-y", "fhirhydrant"],
"env": {
"MCP_TRANSPORT": "stdio",
"FHIR_AUTH": "none",
"FHIR_SERVER_URL": "https://hapi.fhir.org/baseR4"
}
}
}
}
Tools
fhirHydrant registers tools from configuration and runtime capability checks.
The exact list depends on the config/resources/ folder, granted SMART scopes,
/metadata, write settings, operation settings, and terminology settings.
| Tool or family | Available when | Purpose |
|---|
| Resource tools | Resource is configured and allowed by metadata/scopes | Search, direct-read, vread, history, and optionally CRUD FHIR resources |
system_history | Server advertises system history interaction and scopes allow it | Retrieve system-level change history across all resource types |
capabilities | Always registered | Inspect CapabilityStatement summary, registered tools, skipped tools, search params, operations, and metadata notes |
paginate | Always registered | Fetch the next page of a FHIR Bundle using a server-returned next URL |
operate | At least one named operation passes gating | Invoke configured FHIR named operations for clinical data, terminology, IPS, matching, validation, or custom workflows |
bundle | FHIR_BUNDLE_CAPABILITIES is set | Submit a FHIR batch or transaction Bundle; writes require additional opt-in |
terminology_lookup | FHIR_TERMINOLOGY_BASE_URL is set | Look up one LOINC or SNOMED CT code |
code_search | FHIR_TERMINOLOGY_BASE_URL is set | Search LOINC or SNOMED CT codes by text |
Resource Tools
Resource tools are generated from the config/resources/
folder — one JSON file per resource (e.g. patient.json), scanned at startup.
The shipped config covers common clinical, administrative, medication,
practitioner, organization, and document resources. Add a file to add a
resource, or delete one to drop it — no source changes required.
Each resource tool supports configured search params, optional direct reads
with _id, fhirpath, and, unless compact-locked, responseMode. Direct read
only happens when _id is the only non-empty argument; _id plus other params
stays a search so caller intent is not silently discarded.
Resource tools are search/read by default. Set FHIR_WRITE_CAPABILITIES to
enable metadata-gated CRUD actions:
FHIR_WRITE_CAPABILITIES=create,update,patch,delete
| Action | Required params | FHIR call |
|---|
vread | _id, _vid | GET /ResourceType/{id}/_history/{vid} |
history | _id (instance) or none (type) | GET /ResourceType/{id}/_history or GET /ResourceType/_history |
create | body | POST /ResourceType |
update | _id, body | PUT /ResourceType/{id} |
patch | _id, body | PATCH /ResourceType/{id} with JSON Patch |
delete | _id | DELETE /ResourceType/{id} |
vread is available when the resource has supportsDirectRead and the server
advertises the vread interaction. history is available when the server
advertises history-instance or history-type. Both require the SMART r
permission. Optional _since and _at parameters filter history results.
History responses are Bundles and support compact mode, FHIRPath, and
coalescing.
Write bodies are validated before the FHIR call: body.resourceType must match
the tool resource, body.id must match _id for update when present, and patch
requires a JSON Patch array. Scopes are derived from enabled capabilities:
read/search uses system/Patient.rs, create/read/search uses
system/Patient.crs, and full write support uses system/Patient.cruds.
SMART v2 has no separate patch letter, so patch maps to u.
Core Tools
capabilities returns the cached CapabilityStatement summary, registered and
skipped tools, search params, operations, and metadata notes.
paginate fetches one Bundle page using a server-returned next URL validated
against the FHIR origin and allowed path prefixes. When compact mode is active
and the fetched page has more results, paginate automatically coalesces
multiple upstream pages into one compact response (same behavior as resource
search tools). Pass prefetch=false to disable coalescing and get a single
page.
Named Operations
The operate tool invokes FHIR named operations from config/operations.json.
The shipped operation catalog covers clinical aggregation, validation, document
lookup, terminology operations, IPS generation, and patient matching. You can
expand, trim, replace, or disable the operation catalog without source changes.
Terminology Tools
Set FHIR_TERMINOLOGY_BASE_URL to enable:
| Tool | Description |
|---|
terminology_lookup | Looks up one LOINC or SNOMED CT code |
code_search | Searches codes by text filter with paging support |
These tools call the configured terminology server directly. They do not use
the clinical FHIR server credentials. Use a terminology endpoint that matches
your selected FHIR release, such as https://tx.fhir.org/r4.
Bundle Execution
Set FHIR_BUNDLE_CAPABILITIES=batch (or batch,transaction) to enable
bundle. This tool submits a FHIR batch or transaction Bundle and
returns the server's response through the standard response pipeline.
Safety model:
- Read-only batch Bundles (all GET entries) are allowed with just
FHIR_BUNDLE_CAPABILITIES=batch.
- Write entries (POST, PUT, PATCH, DELETE) additionally require
FHIR_BUNDLE_WRITES_ENABLED=true and the corresponding action in
FHIR_WRITE_CAPABILITIES.
- Transaction Bundles require explicit
FHIR_BUNDLE_CAPABILITIES=transaction.
- Every entry is preflighted against configured resources, SMART scopes, and
metadata interactions. If any single entry fails, the entire Bundle is
rejected before submission.
V1 exclusions: Conditional requests, system-level _history, absolute URLs,
and $operation URLs inside Bundle entries are not supported.