Skip to main content
Glama
krlz-dev
by krlz-dev

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 — current

SDK 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, not tool.inputSchema; result.resource_templates, not result.resourceTemplates.

  • FastMCP is gone from the official SDK. (Note: fastmcp on 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 only

Run

uv run mcp dev server.py     # MCP Inspector — try the tools in a form
uv run mcp run server.py     # stdio — for a real host

To connect it to a host (Claude Code, Claude Desktop, an IDE):

uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py

The host launches the server as a subprocess and speaks over stdin/stdout. No port, nothing listening on the network.

Test

pytest -v
test_tools_registrados PASSED
test_prompt_registrado PASSED
test_resource_template PASSED
test_search_patients_contra_servidor_real PASSED

Client(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

search_patients(family_name, limit=5)

the model

Tool

get_observations(patient_id, limit=10)

the model

Resource

fhir://patient/{patient_id}

the application

Prompt

resumen_clinico(patient_id)

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 tools
get_observationsA

Trae las observaciones clínicas de un paciente por su ID FHIR.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
patient_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
family_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.4/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

  1. 2 tool updatesv0.1.0
    • First observedget_observations
    • First observedsearch_patients

TDQS

A3.6/5.0

Scored across 2 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count3/5

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.

Completeness2/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Connects to FHIR R4 servers and enables natural language search, read, and optional write of FHIR data from MCP clients like Claude Desktop/Code.
    6
    5 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI applications to query and retrieve healthcare data (patients, conditions, observations, medications) from a public FHIR R4 server via MCP tools.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for FHIR interoperability, enabling natural language querying and manipulation of clinical data with full CRUD operations, semantic search, and RAG capabilities.
    13
    MIT