Skip to main content
Glama

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

list_resources

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 group filter. No network call.

describe_resource

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.

probe_patient_coverage

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 resources list to narrow or re-probe.

search

Searches one resource type with validated params. Returns trimmed rows, warnings and a next_cursor, or a count when count_only is set. Options: max_results (1-50, default 10), max_pages (1-10, default 3), fields, cursor.

read

Reads one resource by id and returns a trimmed record. For Binary it returns metadata plus a short text preview.

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 build

stdio

node dist/stdio.js      # or: npm run stdio

Example 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:http

This 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

MCP_PATH_TOKEN

HTTP handler (dev server and Vercel)

unset

Required for HTTP. When unset, every request gets 404. Compared in constant time.

PORT

npm run dev:http

8787

The dev server always binds 127.0.0.1.

LIVE_SANDBOX

test runner

unset

Set to 1 to run the live tests in test/live/ (see Testing).

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-in

Testing

npm run typecheck
npm test              # offline: replays recorded responses, no network
npm run test:live     # LIVE_SANDBOX=1, hits the real sandbox

The 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

npm run record-fixtures

Re-record the response fixtures in fixtures/responses/

npm run refresh-capability

Re-download the R4 CapabilityStatement

npm run discover-patients

Rebuild data/sandbox-patients.json

npm run probe-quirks

Re-record the overlay's quirk and probe cases in fixtures/probes/

npm run measure-context

Check that the tool list plus instructions stay within the token budget

npm run discover-vitals

Run the three-call "what vitals exist?" flow through the MCP tools

Limitations

  • Read-only. Only GET requests exist in the code. There is no create, update, delete or $operation support.

  • 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, contained and identifier are dropped, strings are capped at 200 characters, arrays at 5 items, and each tool response at 6,000 characters. Binary content 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. search returns at most 50 rows and follows at most 10 pages per call. _count is 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_coverage looks 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 as timeout with 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-After wait is capped at 30 seconds. The sandbox is often slow, and some queries (an unfiltered Condition search, 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 POST builds 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 freeText and 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.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    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
    Provides 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.
    4
    29 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    4
    1
    MIT