RFC: What should a great FHIR MCP server look like? #231
angela-helios
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
This is an open design discussion — an RFC, not a decision. We want to build an HL7 FHIR MCP server that we'd be proud to put in front of the FHIR community, and we'd rather argue about the shape before writing it. Tell us where we're wrong.
The question we're really asking: when an AI agent needs to work with FHIR data, what should the tool between them look like?
What MCP is (one paragraph)
The Model Context Protocol is an open JSON-RPC 2.0 protocol that standardizes how applications expose context and actions to LLMs. A server offers three primitives: tools (model-invokable functions with JSON-Schema inputs), resources (readable, URI-addressed context), and prompts (reusable templated workflows). Standard transports are stdio (local subprocess — how desktop/IDE agents launch a server) and streamable HTTP (remote/multi-user, with SSE for server→client streaming).
The goal
Make a FHIR server directly usable by LLM agents without those agents hand-rolling FHIR REST calls, FHIRPath, or SQL-on-FHIR. The MCP server should be a thin, safe bridge from MCP's primitives to real FHIR capabilities — not a reimplementation of FHIR, and not a chatbot.
Our opinion: what a great FHIR MCP server needs
The category is young, and most of what exists today is a thin CRUD wrapper. That's table stakes, not the bar. Here's where we think the bar actually is:
A small, well-named tool set — but wider than CRUD.
search/read/create/update/deleteare the price of entry. The value is in the analytical surface most stop short of: evaluate a FHIRPath expression, run a SQL-on-FHIR ViewDefinition, resolve terminology ($lookup/$validate-code/$expand). An agent that can analyze FHIR, not just fetch it, is the difference.Token efficiency is a correctness concern, not a nicety. FHIR resources are large; a naive
searchthat inlines a full Bundle blows the context window and degrades the agent. Summaries (_summary/_elements), sane paging defaults, and returning resource URIs the agent can fetch on demand instead of always inlining are first-class design requirements.Safety is what separates a product from a demo. Reads default-on, writes default-off behind explicit config and an authorized scope. Every tool call is audited with the resolved principal + tenant. Destructive operations require explicit confirmation. Tool availability should reflect the caller's scopes — a read-only token never even sees the write tools.
Errors should teach the model. When a write is malformed, hand back the FHIR
OperationOutcomedetail so the agent can self-correct on the next turn, rather than a bare failure.Multi-tenancy and FHIR-version selection must be first-class, resolved once per session and threaded to every tool — the same isolation guarantees the REST API gives.
We'd love the community's take on this bar — especially where it's too low or too high.
Proposed technical decisions (arguable)
Build on the official Rust MCP SDK (
rmcp), don't hand-roll the protocol. It's mature, edition-2024, permissively licensed, and ships a typed#[tool]macro plus both transports and OAuth. Hand-rolling JSON-RPC + session/capability handshakes is undifferentiated work.Ship stdio first, then streamable HTTP. stdio is how local agents launch a server (single-user, the process boundary is the trust boundary). Streamable HTTP is the modern remote/multi-user transport; the deprecated HTTP+SSE transport isn't worth building.
Share one service layer with the REST API — don't fork FHIR logic. If MCP tools and REST handlers each orchestrate the storage traits independently, they will drift (search-param parsing,
_include, OperationOutcome shaping, tenant checks). Extract a version-agnostic FHIR service both call. Pragmatically this can be phased so it doesn't block an MVP.Auth maps to real principals + scopes. Local stdio is single-user (principal/tenant from config). Remote HTTP validates a bearer token to a principal with SMART scopes; write-gating and tool availability derive from those scopes.
Result shaping is built in (summary projections, paging, byte/token budgets, URIs-over-inlining) — see principle #2.
Sketch of the surface
fhir_search,fhir_read,fhir_history,fhir_capabilities,fhirpath_evaluate,sof_run_view(SQL-on-FHIR),terminology_lookup/validate_code/expand_valueset, and (gated)fhir_create/update/patch/delete.fhir://metadata(CapabilityStatement),fhir://{type}/{id}(fetch-on-demand target),fhir://search-parameters/{type}(so the model forms valid queries).summarize-patient,build-cohort.A phased path
search/read/capabilities/fhirpath_evaluate.Each phase is independently shippable.
Open questions for the community
OperationOutcomedo we hand back?References
rmcp) — https://github.com/modelcontextprotocol/rust-sdkPlease comment with disagreements, missing requirements, or "you're overthinking this." We'll fold the discussion into a ratified design before any code lands.
All reactions