oracle-fhir-mcp
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., "@oracle-fhir-mcpfind a patient named smart and probe their record coverage"
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.
oracle-fhir-mcp
A read-only Model Context Protocol server for the Oracle Health (Cerner) FHIR R4 public sandbox. It lets an MCP client find synthetic test patients, see which parts of their record hold data, and search or read individual FHIR resources. Responses are trimmed and size-capped so they fit in a model's context window.
It talks to one host and one tenant:
https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Every request is a GET that goes through one allowlisted client (src/fhir/client.ts). Nothing is written
back to the EHR, and the server cannot be pointed at another host.
Tools
Tool | What it does |
| Lists the 42 resource types the sandbox declares, one line each (group, read/search, patient-scoped, short purpose), plus a few verified sandbox patient ids. Optional |
| A short recipe for one resource type: search params, required-param rules, known sandbox quirks with examples, an example query and a plain-English description. No network call. |
| For one patient id, runs a small query against every patient-scoped resource and returns a table of status, count, sampled date range, top codes and a suggested next step. Optional |
| Searches one resource type with validated params. Returns trimmed rows, warnings and a |
| Reads one resource by id and returns a trimmed record. For |
All tools carry the MCP annotations readOnlyHint: true, destructiveHint: false, idempotentHint: true,
openWorldHint: true. The server also sends a short instructions string telling the client the usual order:
list_resources, then probe_patient_coverage, then describe_resource and search.
A typical flow: search Patient {"name":"smart"} to get an id, probe_patient_coverage {"patient":"12724066"}
to see what is there, then search Observation {"patient":"12724066","category":"vital-signs"}.
Related MCP server: FHIR MCP Server
Quick start
Requires Node.js 22.12 or newer.
npm ci
npm run buildstdio
node dist/stdio.js # or: npm run stdioExample client config (most desktop MCP clients use this shape):
{
"mcpServers": {
"oracle-fhir": {
"command": "node",
"args": ["/absolute/path/to/oracle-fhir-mcp/dist/stdio.js"]
}
}
}The server reads data/ and fixtures/r4-capabilitystatement.json at startup, relative to dist/, so run it
from a full checkout rather than copying dist/ somewhere else on its own.
HTTP (local)
MCP_PATH_TOKEN=some-long-random-string npm run dev:httpThis serves stateless Streamable HTTP (JSON responses, POST only) on http://127.0.0.1:8787/mcp/<token>.
It binds to loopback only. A wrong or missing token gets a plain 404.
HTTP (Vercel)
api/mcp/[token].ts is a thin shim over the same handler, with maxDuration set to 60 seconds in
vercel.json. Set MCP_PATH_TOKEN in the project's environment variables and deploy; the endpoint is
https://<your-deployment>/api/mcp/<token>.
Treat the token like a password. It is the only thing protecting the endpoint, and it ends up in the URL, so it can show up in proxy or access logs.
Configuration
Variable | Used by | Default | Notes |
| HTTP handler (dev server and Vercel) | unset | Required for HTTP. When unset, every request gets |
|
|
| The dev server always binds |
| test runner | unset | Set to |
The FHIR base URL, tenant, timeouts and caps are constants in the code, not configuration. See
src/fhir/allowlist.ts, src/fhir/client.ts, src/search/run.ts and src/probe/run.ts.
Project layout
api/mcp/[token].ts Vercel function shim
src/
server.ts createServer(): registers the five tools and the instructions
stdio.ts stdio entry point
deps.ts wires the client, CapabilityStatement, overlay and patient list
fhir/ allowlist, GET-only client with retries, CapabilityStatement loader
search/ param validation, URL building, cursors, the search/read services
overlay/ resource overlay schema, loader, required-param rules, preflight
probe/ probe_patient_coverage: per-resource probe rows, bounded concurrency
trim/ resource/bundle trimming, flattening, caps, date and code summaries
discovery/ list_resources and describe_resource rendering
http/ Streamable HTTP handler, path-token gate, rate limiter, dev server
tools/ MCP tool registrations and error-to-text mapping
data/
resource-overlay.json hand-written notes per resource: plain-English text, required params, quirks
sandbox-patients.json patient ids confirmed with a live read
fixtures/ recorded sandbox responses used by the offline tests
scripts/ maintenance scripts (re-record fixtures, refresh the CapabilityStatement, etc.)
test/ Vitest suite; test/live/ is opt-inTesting
npm run typecheck
npm test # offline: replays recorded responses, no network
npm run test:live # LIVE_SANDBOX=1, hits the real sandboxThe offline suite stubs fetch and replays the JSON under fixtures/. The live suite is excluded from
npm test and is slow; the sandbox sometimes times out, and those cases are recorded and skipped rather
than failed.
Maintenance scripts (all GET only, through the same allowlisted client):
Script | Purpose |
| Re-record the response fixtures in |
| Re-download the R4 CapabilityStatement |
| Rebuild |
| Re-record the overlay's quirk and probe cases in |
| Check that the tool list plus instructions stay within the token budget |
| Run the three-call "what vitals exist?" flow through the MCP tools |
Limitations
Read-only. Only
GETrequests exist in the code. There is no create, update, delete or$operationsupport.Public sandbox only. The host and tenant are hard-coded and enforced by an allowlist. It does not work against a production Oracle Health / Cerner environment, and was never meant to.
Synthetic data. Every patient in the sandbox is made up. Some records are odd: dates in 2099, duplicate or test-looking names, sparse charts. Rows dated in the future get a warning but are still returned.
No SMART on FHIR or user auth. The open sandbox endpoint needs no OAuth, so there is no login, no patient context and no scopes. The optional HTTP token only gates who can reach this server; it is not a user identity.
Trimmed responses. Results are cut down to fit a model's context:
text,meta,extension,containedandidentifierare dropped, strings are capped at 200 characters, arrays at 5 items, and each tool response at 6,000 characters.Binarycontent comes back as a 1,500-character text preview at most. If you need the full resource, this is the wrong tool.Paging and counts are capped.
searchreturns at most 50 rows and follows at most 10 pages per call._countis capped at 200, and Oracle does not honour it exactly. Counts are worked out on the client side from the pages actually fetched, and are marked inexact when more pages remain.Probe results are samples.
probe_patient_coveragelooks at the first page per resource only (up to 50 rows), with 4 requests in flight, 15 seconds per resource and 45 seconds overall. Slow resources come back astimeoutwith a suggested narrower search.Timeouts and retries. Each upstream request times out after 25 seconds. Network errors and 429, 502, 503 and 504 responses are retried at most twice, and a
Retry-Afterwait is capped at 30 seconds. The sandbox is often slow, and some queries (an unfilteredConditionsearch, for example) can take longer than any of these budgets.Rate limiting is basic. The HTTP handler allows 60 requests per minute per client IP, counted in memory per instance. Counters reset on a cold start and are not shared between serverless instances. The client IP is taken from
x-real-ip/x-forwarded-for, which can only be trusted behind a proxy that sets them.Stateless HTTP. No MCP sessions, no server-sent notifications; each
POSTbuilds a fresh server.Frozen sandbox knowledge. The CapabilityStatement, the resource overlay (required params, quirks) and the patient list are snapshots recorded in September 2026. If Oracle changes the sandbox, they can go stale until the scripts above are re-run.
Validation is strict. Params must be declared in the CapabilityStatement, paging and format params (
_count,_sort,_elements, and so on) are reserved for the server, and some resources need specific param combinations before a request is sent. A query that is valid FHIR can still be rejected here.Untrusted record text. Free text from records is tagged as
freeTextand the client is told it is data, not instructions. This reduces prompt-injection risk; it does not remove it.Not a medical device. This is a developer tool for exploring a test sandbox. Do not use it for diagnosis, treatment or any other clinical decision.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query Health Gorilla FHIR patients, conditions, medications and lab results.
Synthetic EHR: medications, labs, conditions, allergies, immunizations, FHIR R4. No API key.
- mcpOAuthcom.medplum
Securely access and manage FHIR healthcare data stored in Medplum.
Search and read a patient chart over the Canvas FHIR R4 API. Read-only.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables read-only FHIR access to Practice Fusion EHR to search patients, appointments, conditions, medications, and lab results.139 npm4MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI applications to query and retrieve healthcare data (patients, conditions, observations, medications) from a public FHIR R4 server via MCP tools.MIT
- AlicenseAqualityCmaintenanceProvides safe read-only access to synthetic healthcare data via FHIR R4, enabling AI assistants to search patients, retrieve vitals, and summarize conditions using a public sandbox.429 npmMIT
- AlicenseAqualityBmaintenanceEnables read-only SMART on FHIR R4 discovery, search, and read operations against public synthetic sandboxes, returning actual empty results and 404s rather than inventing patient data.41MIT