Skip to main content
Glama

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

Related MCP server: smart-mcp-server

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.

History in Bundles: vread (Resource/id/_history/vid), instance history (Resource/id/_history), and type history (Resource/_history) entries are allowed in Bundles when the server advertises the corresponding interaction and scopes permit it. These count as read entries.

Metadata And Scope Gating

Unless FHIR_METADATA_MODE=off, fhirHydrant fetches the FHIR server's CapabilityStatement at startup. In strict mode:

  • Resource tools are registered only when the resource type is present in /metadata

  • Server-side search controls such as _count, _sort, _summary, _elements, _include, and _revinclude are exposed only when advertised

  • Search params are blocked when the server does not advertise them

  • Write actions require both FHIR_WRITE_CAPABILITIES and matching CapabilityStatement interactions

  • Named operations require the target resource type to exist, the granted SMART scope to allow the resource, and the operation itself to be advertised in the resource's CapabilityStatement entry

In warn mode, unadvertised params are allowed with a warning, but absent resource types are still skipped. SMART scopes are also checked at runtime, so a tool can exist in the schema and still be blocked by the granted token scope.

Token Economy And Response Shaping

FHIR responses are often much larger than an MCP client needs. fhirHydrant shapes responses for token economy after retrieval, using server-side controls when the FHIR server advertises them.

Feature

Behavior

_count default/cap

No _count injected by default (server decides page size). Set FHIR_DEFAULT_COUNT to inject one; FHIR_MAX_COUNT caps explicit caller values (0 = no cap)

Page coalescing

When compact mode is active, the server fetches multiple upstream pages sequentially, compacts each immediately, and returns one consolidated Bundle. Controlled by maxResults, prefetch, and FHIR_PREFETCH_* env vars

Byte limit

FHIR_MAX_RESPONSE_BYTES limits every model-facing JSON response; oversized Bundles are chunked transparently

Auto-retry

Oversized search Bundles attempt local chunking first, then retry with smaller _count as a fallback

FHIRPath

fhirpath filters the returned FHIR JSON locally and returns matching nodes as an array

Compact mode

responseMode=compact strips common FHIR envelope noise and simplifies datatypes

Full mode

responseMode=full returns raw FHIR JSON

Locked compact

FHIR_RESPONSE_MODE=compact-locked hides responseMode from the tool schema

Native artifacts

Non-JSON responses (documents, images, DICOM, RTF, HTML, XML, CSV, NDJSON, ZIP, octet-stream) and JSON FHIR Binary are normalized into a metadata envelope plus one MCP embedded text/blob resource. Capped by FHIR_MAX_ARTIFACT_MB (not the JSON limit), never chunked, and never passed through FHIRPath/compaction/coalescing. JSON-only shaping arguments are ignored with a note

Compact output is AI-oriented JSON, not canonical FHIR. It drops or simplifies FHIR noise and common datatypes such as meta, narrative, extensions, CodeableConcept, Reference, Quantity, and newer datatypes such as CodeableReference. FHIRPath runs locally; the FHIR server never sees the expression. If evaluation fails, the raw response is withheld and an error is returned.

Structured Response Envelope

Every FHIR-data tool (resource tools, paginate, operate, bundle, system_history) returns a single structured envelope, advertised via each tool's outputSchema and returned as structuredContent (the text content is the same envelope serialized). It carries the FHIR payload (data) plus metadata: response mode, a hasMore/continuation pagination signal, Bundle and coalescing stats, and human-readable notes. The full field list is the tool's outputSchema.

Oversized responses are chunked when possible (data preserved, retrievable via continuation); if unchunkable, the envelope is marked status: "truncated" with data omitted. Truncation is a successful-but-partial result, not an error. The capabilities and terminology tools return their own structured shapes rather than this FHIR envelope.

Page Coalescing

When compact mode is active for a search (resource tools or paginate), the server fetches multiple upstream FHIR pages sequentially, compacts each page immediately, and returns one consolidated compact Bundle. This reduces MCP round-trips from many "next page" calls down to one.

  • maxResults sets a target — the server stops fetching once this threshold is crossed (may slightly exceed since whole pages are appended)

  • prefetch=false disables coalescing for one call

  • _count still controls the upstream FHIR page size

  • Coalescing stops at configurable page, entry, byte, and time limits

  • continuation.url points to where the server stopped; call paginate with responseMode=compact to continue (hasMore indicates more remain)

  • FHIRPath-filtered requests stay single-page (no coalescing)

  • responseMode=full always returns a single upstream page

Audit Events

Set FHIR_AUDIT_SINK to any combination of console, file, and http.

The http sink POSTs each audit event to an external collector, SIEM, or FHIR audit repository (not the FHIR server itself). Set FHIR_AUDIT_HTTP_URL to the destination and FHIR_AUDIT_HTTP_FORMAT to either raw (the internal PHI-light audit JSON, for generic collectors such as Splunk HEC or Datadog) or fhir-auditevent (a minimal FHIR R4 AuditEvent resource, suitable for ATNA-style and FHIR-native audit repositories). The fhir-auditevent mapping is intentionally lightweight — it is not a full ATNA/BALP compliance profile. An optional FHIR_AUDIT_HTTP_AUTH value is sent verbatim as the Authorization header. Delivery is fire-and-forget with a 5s timeout; transport failures are logged and never affect tool responses.

Audit events include timestamp, tool, resource type when applicable, operation, status, duration, response size, pagination summary, request ID, and optional proxy-authenticated user. They do not include FHIR resource content by default.

When running behind an authenticating proxy, set FHIR_AUDIT_USER_HEADER to the trusted identity header injected by that proxy:

Common headers: Azure EasyAuth X-MS-CLIENT-PRINCIPAL-NAME, OAuth2 Proxy X-Auth-Request-Email, Cloudflare Access Cf-Access-Authenticated-User-Email.

Only use this when the proxy strips or overwrites inbound copies of that header. Otherwise clients can spoof arbitrary audit users.

SMART Backend Auth And Keys

fhirHydrant uses SMART Backend Services: client credentials plus a signed JWT assertion. This is backend FHIR access, not browser-based SMART standalone launch; there is no interactive redirect/login flow in the MCP path.

FHIR_ACTIVE_KEY holds the raw PKCS#8 signing key (RSA, signed RS384, or EC P-384, signed ES384). In HTTP mode, the built-in /jwks endpoint exposes public keys for the active key plus any retired keys when FHIR_JWKS_URL is unset. The kid for each key is derived automatically via a truncated RFC 7638 JWK Thumbprint (first 12 base64url chars of SHA-256 over the canonical public JWK members) and logged at startup.

Key rotation workflow:

  1. Generate a new key (RSA-2048 or EC P-384).

  2. Add the new PEM to FHIR_RETIRED_KEYS and redeploy so JWKS includes both.

  3. Register the new kid (logged at startup) with your auth server.

  4. Move the new PEM to FHIR_ACTIVE_KEY and move the old PEM to FHIR_RETIRED_KEYS. Redeploy.

  5. After auth-server caches expire, remove the old key from FHIR_RETIRED_KEYS.

If using external JWKS, publish the new public key before switching FHIR_ACTIVE_KEY.

Environment Variables

See .env.example for a complete sample.

Required

Variable

Description

FHIR_BASE_URL

Base URL used to derive the FHIR server URL and token URL. Optional when FHIR_SERVER_URL is set (and, for smart auth, FHIR_TOKEN_URL)

FHIR_CLIENT_ID

SMART Backend Services client ID (not needed when FHIR_AUTH=none)

FHIR_ACTIVE_KEY

Base64-encoded PKCS#8 PEM signing key, RSA or EC P-384 (not needed when FHIR_AUTH=none)

Optional

Variable

Default

Description

FHIR_AUTH

smart

smart (SMART Backend Services) or none (unauthenticated, for public test endpoints)

FHIR_RETIRED_KEYS

unset

Comma-separated base64-encoded PEMs for JWKS rotation

FHIR_VERSION

R4

Active R4+ FHIR release; controls derived URL, FHIRPath model, and compact model metadata

FHIR_SERVER_URL

<base>/api/FHIR/<FHIR_VERSION>

Explicit FHIR API URL override

FHIR_TOKEN_URL

<base>/oauth2/token

Explicit token endpoint override

FHIR_JWKS_URL

unset

External JWKS URL. Omit in HTTP mode to enable built-in /jwks

MCP_TRANSPORT

http

http or stdio

PORT

5000

HTTP listener port

BIND_HOST

0.0.0.0 (or 127.0.0.1 with --dev flag)

HTTP bind address

ALLOWED_HOSTS

unset

Comma-separated hostnames for DNS rebinding protection

FHIR_METADATA_MODE

strict

strict, warn, or off for /metadata validation

FHIR_DEFAULT_COUNT

0

Default _count injected into searches when allowed; 0 = server decides

FHIR_MAX_COUNT

0

Cap on explicit caller _count values; 0 = no cap

FHIR_MAX_RESPONSE_BYTES

262144

Byte limit for model-facing JSON responses; oversized Bundles are chunked

FHIR_MAX_ARTIFACT_MB

16

Separate byte ceiling (MiB) for native/binary artifact bodies; independent of the JSON limit (base64 transport ≈ +33%)

FHIR_REQUEST_TIMEOUT_MS

30000

Per-attempt timeout for outgoing FHIR requests

MCP_JSON_LIMIT

4mb

Max accepted MCP request body size (Express json limit string); raise if large write/bundle payloads are rejected

MCP_AUTHZ

none

Authorization provider: none or entra. Gates tools per caller (HTTP + Authorization: Bearer only)

MCP_ROLE_PREFIX

FhirHydrant

Prefix on granted role values (e.g. FhirHydrant.Patient.Read)

MCP_ENTRA_TENANT_ID

unset

Entra tenant GUID (not a domain alias); required when MCP_AUTHZ=entra

MCP_ENTRA_AUDIENCE

unset

API application (client) ID expected in the v2 access token aud; required when MCP_AUTHZ=entra

FHIR_RESPONSE_MODE

unset

compact, full, or compact-locked; unset means search defaults compact and direct reads default full

FHIR_WRITE_CAPABILITIES

unset

Comma-separated write actions: create, update, patch, delete

FHIR_VALIDATE_WRITES

local

off, local (client-side structural checks), or server (local + server $validate preflight for create/update)

FHIR_WRITE_DRY_RUN

false

Set to true to validate and log writes without executing them against the FHIR server

FHIR_BUNDLE_CAPABILITIES

unset

Comma-separated Bundle types: batch, transaction; enables bundle tool

FHIR_BUNDLE_WRITES_ENABLED

false

Set to true to allow write entries inside Bundles (also requires FHIR_WRITE_CAPABILITIES)

FHIR_OPERATIONS

unset

Comma-separated operation keys; none disables all catalog operations. Default catalog: everything, lastn, validate, docref, expand, lookup, translate, summary, match

FHIR_TERMINOLOGY_BASE_URL

unset

Enables terminology tools, e.g. https://tx.fhir.org/r4

FHIR_PAGINATION_PATHS

unset

Extra allowed path prefixes for pagination links, e.g. FHIRProxy

FHIR_PREFETCH_MAX_PAGES

5

Max upstream pages fetched per coalesced compact search

FHIR_PREFETCH_MAX_ENTRIES

5000

Max upstream entries accumulated before stopping

FHIR_PREFETCH_MAX_BYTES

2097152

Max raw bytes fetched before stopping

FHIR_PREFETCH_TIMEOUT_MS

25000

Wall-clock budget for the coalescing loop

FHIR_AUDIT_SINK

unset

Any combination of console, file, http

FHIR_AUDIT_FILE

./audit.jsonl

JSONL file used when the file audit sink is enabled

FHIR_AUDIT_HTTP_URL

unset

Destination URL for the http audit sink; required when http is enabled

FHIR_AUDIT_HTTP_FORMAT

raw

raw (internal AuditEvent JSON) or fhir-auditevent (FHIR R4 AuditEvent)

FHIR_AUDIT_HTTP_AUTH

unset

Authorization header value sent verbatim by the http sink

FHIR_AUDIT_USER_HEADER

unset

Proxy-authenticated user header copied into audit events

LOG_LEVEL

info

Log verbosity: error, warn, info, or debug

Explicit FHIR_SERVER_URL and FHIR_TOKEN_URL values always win over derived URLs.

FHIR Version Support

Set FHIR_VERSION to select the active R4+ FHIR release. It controls the derived FHIR API URL, FHIRPath model context, and compact response model metadata. Some releases may use the nearest compatible FHIRPath model. For terminology, use an endpoint that matches the selected FHIR release. Startup logs hint when explicit FHIR or terminology URLs appear to reference a different version.

Customizing Tools And Messages

Everything under config/ is customizable without source changes.

Config is resolved as a partial overlay: for each file, a ./config/<file> in the current working directory (if present) overrides the packaged default, and anything you omit falls back to the built-in default. So npm installs work out of the box, and to customize you drop a ./config folder next to where you launch the server containing only the files you want to change.

There are two overlay granularities:

  • Whole-file (resources/*.json, operations.json, search-controls.json, core-tools.json, instructions/*): a file you provide replaces the packaged file entirely. A new resource file (e.g. ./config/resources/myresource.json) adds a tool. The overlay can override and add, but cannot remove a packaged resource — to ship a strictly minimal catalog, remove the packaged config/resources/ files (see the compose example).

  • Per-key (messages/*.json): a local file overrides only the individual keys it contains; every other key falls back to the packaged default. So you can retune a single description or message without copying the whole file. Unknown keys, empty values, and malformed JSON fail fast at startup to catch typos.

messages/*.json files are read once at process startup. Changing them requires a server restart (and, for tool schemas or instructions, a client reconnect) to take effect. Development hot reload for resources, search controls, and operations is described below.

File

Purpose

resources/*.json

FHIR resource tools (one file per resource): search params, direct-read behavior, and requireOneOf rules

operations.json

Named operation catalog for operate (per-operation descriptions and notes)

search-controls.json

Descriptions for _count, _sort, _summary, _elements, _include, _revinclude, _lastUpdated, fhirpath, responseMode, maxResults, and prefetch

messages/output-schema.json

Descriptions for every tool outputSchema field (per-key overlay)

messages/input-schema.json

Descriptions for generated resource input params (_id, _vid, _since, _at, action, body) and the operate tool's title and params (per-key overlay)

instructions/manifest.json

Ordered list of instruction fragments to compose, each with an optional when gate (terminology, writes, operations, bundle). Custom builds reorder, add, or remove sections by editing this file.

instructions/*.md

Instruction fragments referenced by the manifest. Gated sections are included only when their feature is enabled; the {{OPERATIONS_LIST}} token is replaced with the live operation catalog.

messages/*.json

User-facing messages, errors, and response notes (per-key overlay, split by domain: core, write, operations, terminology, bundle, artifact)

core-tools.json

Built-in tool descriptions and param hints

Resource Definition Schema

Each file in config/resources/ is a single resource definition object. Files are scanned in filename order; the filename is conventionally the lowercase resource name (e.g. patient.json). Each object has these fields:

Field

Type

Description

resource

string

FHIR resource type

toolName

string

MCP tool name; must be unique

description

string

Tool description

supportsDirectRead

boolean

Enables GET /ResourceType/{id} via _id

searchParams

Record<string,string>

FHIR search params and descriptions

requireOneOf

(string | string[])[]

Search requires at least one option. A string is a single required param; a nested array is a param set where every param is required. ["patient"] accepts patient; [["given","family"],["identifier"]] accepts given+family together, or identifier

searchParams values are descriptions, not a full FHIR capability model. Server-specific search behavior can still apply.

Hot Reload

In development (NODE_ENV is not production), the config/resources/ folder, search-controls.json, and operations.json are watched. Invalid JSON keeps the last valid snapshot. A materially changed reload is applied transactionally: when the derived SMART scopes change, a replacement token is acquired before the new definitions and tool registrations are committed, so a failed acquisition leaves the running catalog untouched. Adding/removing tools, operation and param-name schema changes are re-registered live — no restart needed. Semantically unchanged saves cause no refresh. Production reads config once at startup, but a runtime /metadata change (via capabilities(refresh=true)) or a backend SMART-scope change on token refresh re-evaluates the available tools in every mode.

One boundary is unavoidable: the tool list and schemas hot-refresh, but the server instructions are sent once during MCP initialize and cannot be replaced on an existing connection. A client must reconnect/reinitialize to receive changed instruction text.

Transports

Stdio

Set MCP_TRANSPORT=stdio. stdout is reserved for the MCP protocol; logs are redirected to stderr. Use an external FHIR_JWKS_URL for stdio deployments.

Streamable HTTP

HTTP transport is stateless and exposes MCP at:

POST http://localhost:5000/mcp
Accept: application/json, text/event-stream
Content-Type: application/json

MCP client config:

{
   "mcpServers": {
      "fhirhydrant": {
         "url": "http://localhost:5000/mcp"
      }
   }
}

GET /health returns a no-PHI readiness snapshot:

{
   "status": "ok",
   "mcp": true,
   "metadata": true,
   "tools": 23,
   "auth": true,
   "tokenExpiresIn": 287
}

When authorization is enabled, authz reports the active provider and tools is omitted because the registered tool count is caller-specific.

Use a reverse proxy for TLS and user authentication when exposing HTTP beyond localhost. Set ALLOWED_HOSTS when binding to a public interface.

Per-caller authorization (Entra, optional)

By default (MCP_AUTHZ=none) every caller sees the full tool set gated only by /metadata and the backend SMART scopes. Setting MCP_AUTHZ=entra adds an optional per-caller layer: each /mcp request must carry an Authorization: Bearer <token> issued by Microsoft Entra, and the caller's App Roles determine which tools are built for that request. This is MCP-layer authorization only — it never replaces the FHIR server's own authorization, and it can only subtract from what the backend SMART token and config already allow.

The API app registration must set requestedAccessTokenVersion to 2 in its manifest. The provider validates tenant-specific v2 issuers and expects MCP_ENTRA_AUDIENCE to be the API application's client ID.

Tools a caller lacks a role for are not registered at all — they are absent from tools/list, not merely blocked. Helper tools (capabilities, paginate, terminology_lookup, code_search) are never gated.

App Role values (with the default FhirHydrant prefix):

Role

Grants

FhirHydrant.<Resource>.Read

search, read, vread, history for that resource

FhirHydrant.<Resource>.Write

read actions plus create, update, patch, delete (subject to FHIR_WRITE_CAPABILITIES)

FhirHydrant.Operation.<key>

the named operation via the operate tool (e.g. FhirHydrant.Operation.everything)

FhirHydrant.Bundle

the bundle tool

FhirHydrant.SystemHistory.Read

the system-wide system_history tool

FhirHydrant.Admin

all of the above, still bounded by backend SMART scopes, /metadata, and write/bundle/operation config

Requires HTTP transport; MCP_AUTHZ=entra with MCP_TRANSPORT=stdio fails at startup. Missing or invalid bearer tokens receive 401.

Adding an authorization provider

Entra is the only shipped provider, but the authorization layer is provider-neutral. This is a source extension, not a runtime plugin: the npm package ships only bin/server.js (providers are bundled in), so adding one means forking or cloning the repo and rebuilding.

The shared pipeline is provider-agnostic — a provider only maps an Authorization header to { subject, roles }. The role vocabulary (.Read/.Write/Operation.<key>/Bundle/SystemHistory.Read/Admin) and MCP_ROLE_PREFIX handling are applied by decideAuthz for every provider.

To add one (e.g. auth0) takes just two edits:

  1. Create ts/mcp/authz/auth0.ts exporting an AuthzProvider — implement validate(authorization) to return { subject, roles } (throw to reject), and optionally validateConfig() to fail fast on missing provider env. Keep all provider-specific env inside this module; do not add fields to Config.

  2. Add one entry to ts/mcp/authz/registry.ts: auth0: () => import("./auth0.ts").then((m) => m.auth0Provider).

That's it. The AuthzMode type, the MCP_AUTHZ parser, and its error message all derive from the registry keys automatically, so MCP_AUTHZ=auth0 just works with full type safety — no other file needs to change.

Deployment Examples

The examples/ directory has standalone deployment examples for Docker Compose, reverse proxy (Caddy), Azure Container Apps, Azure App Service, and Kubernetes. Each includes a Dockerfile that installs from npm and a config/ overlay demonstrating how to override different config files.

Development

# dev server
npm run dev

# type-check
npm run check

# build and run
npm run build
npm start

Build output goes to bin/server.js.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Provides seamless integration with FHIR APIs, enabling AI/LLM tools to search, retrieve, and analyze clinical healthcare data with support for SMART-on-FHIR authentication and multiple transport protocols.
    7
    134
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to securely interact with FHIR R4 servers for clinical decision support workflows, including PlanDefinition execution, FHIR resource management, terminology services, and Questionnaire/StructureMap transformation via Matchbox.
    1

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/faulkj/fhirHydrant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server