fhir-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@fhir-mcp-serverfind patients with last name Smith and list their observations"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
fhir-mcp-server
A minimal MCP server over FHIR, built with the
official Python SDK v2 (mcp 2.1.1+).
It exposes all three MCP primitives — tool, resource and prompt — against a HAPI FHIR R4 server, and ships with an in-memory test suite.
The interesting problem with agent tooling isn't getting it to run once. It's proving it behaves. So the tests are the point of this repo as much as the server is.
Why v2 matters
Most MCP tutorials and Stack Overflow answers still show the v1 API:
from mcp.server.fastmcp import FastMCP # v1 — outdated
from mcp.server import MCPServer # v2 — currentSDK v2 was a major rework released alongside the 2026-07-28 spec revision. Two things bite if you arrive from v1:
Result models use
snake_case:tool.input_schema, nottool.inputSchema;result.resource_templates, notresult.resourceTemplates.FastMCPis gone from the official SDK. (Note:fastmcpon PyPI is a different, third-party package — don't confuse the two.)
Related MCP server: fhir-mcp-server
Requirements
Python 3.10+ and uv.
Install
uv venv .venv
source .venv/bin/activate
uv pip install "mcp[cli]" httpx
uv pip install pytest inline-snapshot # tests onlyRun
uv run mcp dev server.py # MCP Inspector — try the tools in a form
uv run mcp run server.py # stdio — for a real hostTo connect it to a host (Claude Code, Claude Desktop, an IDE):
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.pyThe host launches the server as a subprocess and speaks over stdin/stdout. No port, nothing listening on the network.
Test
pytest -vtest_tools_registrados PASSED
test_prompt_registrado PASSED
test_resource_template PASSED
test_search_patients_contra_servidor_real PASSEDClient(mcp) connects to the server object in memory — no subprocess, no
port, no wire. Same idea as FastAPI's TestClient. The last test is an
integration test and hits the public sandbox, so it needs network.
What it exposes
Primitive | Name | Who triggers it |
Tool |
| the model |
Tool |
| the model |
Resource |
| the application |
Prompt |
| the user |
The three primitives differ by who decides to use them, not by what they
do. A tool is a POST (acts, has side effects), a resource is a GET (loads
data, changes nothing), a prompt is a slash command.
You don't write JSON Schema — the SDK generates it from type hints. A parameter with a default stops being required. It's just Python.
Data
Points at https://hapi.fhir.org/baseR4, the public HAPI FHIR test
sandbox.
⚠️ Never put real clinical data in a public repo. Synthetic or sandbox data, always.
License
MIT
Available Tools
2 toolsget_observationsA
Trae las observaciones clínicas de un paciente por su ID FHIR.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| patient_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the transparency burden. 'Trae' clearly indicates a read/fetch operation rather than a mutation, but the description adds little beyond that—no mention of how limit affects results, ordering, pagination, or other behavioral details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that front-loads the action and key identifier with no filler. Every word contributes to understanding the tool's core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter read with an output schema, the essential invocation pattern is present. However, it does not relate the tool to its sibling search_patients, explain the limit parameter, or disclose any behavioral caveats, leaving minor gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It adds meaning to patient_id by calling it a FHIR ID, but it says nothing about the limit parameter, leaving its behavior to be inferred only from its name and default value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses the specific verb 'Trae' with a clear resource ('las observaciones clínicas') and scope ('por su ID FHIR'), making its purpose obvious. It does not explicitly contrast with search_patients, but the resource type and lookup-by-ID semantics distinguish it well.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'por su ID FHIR' clearly signals that this tool should be used when a FHIR patient identifier is already available. It provides clear context but does not explicitly mention when to prefer search_patients or state exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_patientsB
Busca pacientes por apellido en el servidor FHIR.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| family_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry the behavioral disclosure burden. It only states the basic search action and does not reveal behavior such as partial vs. exact matching, result ordering, the effect of the default limit, or FHIR search semantics. The agent is left guessing about pagination or truncation behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with zero filler. It front-loads the action, target, and filter criterion. It is appropriately minimal for a straightforward search tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple and an output schema exists, so return values do not need explaining. However, the description omits the default limit and does not clarify whether results are bounded or how search matching works. It is minimally viable but leaves an agent to infer basic behavioral expectations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It maps 'por apellido' to family_name, but it does not explain the required nature of family_name, the meaning of limit, or its default value of 5. The parameter documentation burden is effectively unmet beyond a one-word hint.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Busca' / 'search'), a clear resource ('pacientes' / 'patients'), a filtering criterion ('por apellido' / by last name), and a context ('servidor FHIR'). It unambiguously distinguishes the tool from the sibling get_observations, which targets a different FHIR resource.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is implied by the verb and resource: this is the tool for finding patients by family name. However, there is no explicit guidance on when to prefer this over get_observations, and no mention of limitations or filters such as the limit parameter affecting usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.1.0- First observed
get_observations - First observed
search_patients
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: one searches for patients by last name, the other retrieves clinical observations for a specific patient ID. There is no meaningful overlap or ambiguity between them.
Both tool names follow the same verb_noun snake_case pattern: search_patients and get_observations. The naming is consistent, predictable, and easy for an agent to navigate.
With only two tools, the server feels thin for a FHIR implementation, which typically supports a much broader range of resources and operations. However, the count is not egregiously low and could be acceptable for a narrowly scoped use case.
The server covers only patient search and observation retrieval, missing foundational FHIR operations like getting a patient by ID, creating or updating resources, and searching observations by clinical criteria. Agents would likely encounter dead ends when trying to perform common FHIR workflows.
Maintenance
Related MCP Connectors
Hosted MCP server for the Healthie EHR & telehealth API: patients, appointments, charting, tasks.
Hosted MCP server for Cliniko — patients, appointments, availability, and invoices for AI agents.
Hosted MCP server exposing US hospital procedure cost data to AI assistants
Synthetic EHR: medications, labs, conditions, allergies, immunizations, FHIR R4. No API key.
Related MCP Servers
AlicenseNot gradedqualityDmaintenanceEnterprise-grade MCP Server for FHIR-based EMRs. Enables AI agents to read, search, create, and update any FHIR R4 resource across major EHR systems like EPIC, Cerner, and OpenEMR.285 npm56MIT- AlicenseAqualityBmaintenanceConnects to FHIR R4 servers and enables natural language search, read, and optional write of FHIR data from MCP clients like Claude Desktop/Code.65 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI applications to query and retrieve healthcare data (patients, conditions, observations, medications) from a public FHIR R4 server via MCP tools.MIT
- AlicenseAqualityCmaintenanceMCP server for FHIR interoperability, enabling natural language querying and manipulation of clinical data with full CRUD operations, semantic search, and RAG capabilities.13MIT