Guardrailed FHIR access for AI agents: PHI redaction, audit trail, step-up auth, tenant isolation
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
FHIR standardized how health data is structured. MCP standardized how AI connects to tools. Nobody standardized the guardrails in between. This project does.
Quick Start Β· MCP Tools Β· Recipes Β· Roadmap Β· Claude Plugin Β· Architecture Β· healthclaw.io Β· Contributing Β· Dev Guide
What it is: an open reference implementation of the FHIR Γ MCP guardrail layer β PHI redaction, immutable audit, step-up auth, and tenant isolation β that sits between any AI agent and any FHIR server. Built in the open as a community project, MIT-licensed. Not a product, not a pitch: if the pattern is useful, take it; if it's wrong, tell us or fix it.
This is a community effort. It's most useful when implementers, clinicians, and standards folks poke holes in it. Issues, PRs, and "you got the SDC extraction wrong" critiques are all welcome β start with CONTRIBUTING.md and the Code of Conduct.
At a glance: v1.9.0 Β· 1,490+ Python + 170 Node tests Β· 29 MCP tools Β· CareAgents hosted consumer app (passkey sign-in, advisors, web/Telegram/iMessage) Β· real-world action rail (provably out-of-band gate) Β· forms rail end-to-end ($populate β human review β provenance PDF) Β· FHIR R4 US Core v9 + R6 v6.0.0-ballot3 Β· HL7 SDC forms Β· NQF 0018 quality measure Β· lab interpreter ($interpret) Β· care-gaps reminders ($care-gaps) + embedded MCP-App view Β· ChatGPT-connector search/fetch Β· Fasten TEFCA Β· HealthEx Β· HBO Β· Flexpa Β· Epic Β· MEDENT Β· Open Wearables Β· SMART Health Links Β· Claude Code plugin Β· OpenAI/Gemini adapters
The hosted demo runs synthetic data behind the full guardrail stack:
Point any MCP client at the public demo server β URL https://mcp-demo-production-ee2c.up.railway.app/mcp,
no key required β then ask: "Search my health records for lab results and explain them in plain
language." The demo server is unauthenticated but hard-pinned to a synthetic demo tenant, so it can
only ever serve fake data. A separate production endpoint (mcp-server-production-5112) requires a
deployment-scoped Authorization: Bearer <token> β real records stay behind auth, always. Hosted
connectors cannot attach that header, so the demo URL above is the one to paste.
One-command installs:
gemini extensions install https://github.com/aks129/HealthClawGuardrails Β·
claude plugin marketplace add aks129/HealthClawGuardrails Β·
skills on ClawHub
Non-developer? Step-by-step guides for Claude (web/desktop/phone), Perplexity, ChatGPT, and Telegram β plus a 10-minute demo script β in docs/quickstarts/.
Listed in: Official MCP Registry (io.github.aks129/healthclaw-guardrails) Β·
Glama (hosted connector) Β·
ClawHub (14 skills) Β·
Gemini CLI Extensions Β· agent-skills discovery at /.well-known/agent-skills/
Full notes live in Releases.
| Version | Highlights |
|---|---|
| v1.9.0 | CareAgents β the hosted consumer experience: sign up with a passkey, connect records through a pluggable connector marketplace (Fasten, Apple Health via Open Wearables, sample data), and spin up a guardrailed health agent reachable on web, Telegram, and iMessage Β· advisor registry β specialties ported from SmartHealthConnect (healthy-habits, care-completion, medication-refills, diet-exercise) as prompt-blocks over the guarded tool set, deferred ones honestly labeled Β· versioned informed consent enforced server-side (HTTP 428) before any real-record connection Β· forms rail ships end-to-end β $populate β per-item human review (NKA never inferred) β provenance-stamped PDF β signed expiring link Β· error fidelity is conformance property seven (Grade A = 7/7), hardened across both MCP transports with a PythonβTypeScript drift guard Β· MCP Apps β care-gaps results embed an engine-served UI (text/html; profile=mcp-app) whose only fetch target is the guarded operation Β· security pass: fail-closed prod config, authenticated tenant reads, MCP transport auth, Alembic Β· SmartHealthConnect archived (skills frozen at v1.2.0; advisors are the live successors) |
| v1.8.0 | Real-actions foundation β an agent can propose a real-world action (call, SMS, form) but commit only submits it (HTTP 202); execution happens through a separate approval that requires a single-use step-up credential and an expiry-guarded atomic claim, so the agent's own toolchain can never approve its own action (the spoofable X-Human-Confirmed header is gone) Β· ActionExecutor plugin registry β add a real-world capability behind the full guardrail rail in ~50 lines, no core changes (extend it) Β· mandatory red-flag emergency screen; fail-loud rails (no silent simulation) Β· durable execution β attempt ledger, provider reconciliation, external-tick reaper, append-only action-event log Β· reliability floor β config preflight (GET /r6/ops/preflight), Postgres CI lane, MCP fetch timeouts, poller 409-storm detection, source-aware resource identity (tenant, type, id), Fasten hardening + zombie-job reaper Β· public ROADMAP + contributor on-ramp Β· fixes: upstream FHIR error fidelity, quality measures default to current year |
| v1.7.0 | Preventive care-gaps engine (Patient/$care-gaps, USPSTF/ACIP/ADA + eCQM crosswalk) Β· patient connect flow: identity-verified Fasten onboarding mints a webhook-gated, read-scoped 30-day agent token Β· prescription transfer requests (rx_transfer_request, Schedule II refused) β 29 MCP tools Β· per-agent quickstarts (Claude/Perplexity/ChatGPT/Telegram) Β· HBO exportβFHIR converter + embedded-XML PHI scrubber Β· hardening: fail-closed webhook verify, scoped tokens, serverless write guard, live-path contract tests Β· clinical fixes: SNOMED diabetes detection, inclusive panic thresholds, one-sided-range honesty |
| v1.6.0 | Lab reference-range interpreter (Observation/$interpret) Β· NQF 0018 quality measure (Measure/$evaluate-measure) Β· any-agent-framework adapters (OpenAI/Gemini) Β· Medplum-in-front recipe Β· SMBP triage on 2025 AHA/ACC Β· ruff lint gate Β· all dependency advisories remediated |
| v1.5.0 | Read-auth hardening (tenant reads authenticated, not just scoped) Β· HL7 SDC forms β $populate / $extract |
| v1.4.0 | Six health-data connectors (Fasten TEFCA, HealthEx, Health Bank One, Flexpa, Epic, MEDENT) behind one guardrail stack |
| v1.3.0 | Wearables β FHIR Observations (8 providers, LOINC/UCUM mapping, device Provenance) |
| v1.2.0 | Compiled Truth β current state + append-only Provenance trail per resource |
This is a vendor-neutral guardrail proxy that sits between any AI agent and any FHIR server. Every request passes through:
commit only submits, and execution requires a provably out-of-band single-use approval the agent's own toolchain cannot satisfyThe guardrails are verifiable, not marketing. A runnable harness probes any deployment with synthetic data and emits a scorecard across all seven properties β run it against your own instance (or ours):
Or hit the one-URL self-test on any running deployment β no token needed, it self-tenants internally and returns 200 at Grade A (503 otherwise):
The local FHIR profile is Grade A: unsupported local-search inputs are rejected
or reported according to Prefer: handling, and every failure path is audited.
The same harness runs against the Flask test client as a CI baseline
(tests/test_guardrail_conformance.py). --json emits a machine-readable
report; --mcp-url additionally grades MCP tools/call error signaling as a
separate profile. For an authenticated MCP deployment, set MCP_AUTH_TOKEN or
pass --mcp-auth-token. Library API:
from r6.conformance import LiveProbeClient, ProbeContext, run_conformance.
The grade covers the HealthClaw guardrail layer only β a self-test of the seven properties against synthetic data it just created. It is not a HIPAA Security Rule assessment, a third-party audit, or a penetration test of your deployment: infrastructure, BAAs, encryption at rest/in transit, and access controls remain the deployer's responsibility (see Known Limitations). Because the harness is deployment-agnostic, a third party can run it against any instance as one input to a real assessment β it does not substitute for one. The report states this scope itself in every output format.
HealthClaw ships as a Claude Code plugin marketplace. Two plugins are available:
| Plugin | Skills | Source |
|---|---|---|
healthclaw-guardrails | curatr, fasten-connect, fhir-r6-guardrails, fhir-upstream-proxy, healthex-export, phi-redaction | aks129/HealthClawGuardrails |
smarthealthconnect | care-completion, diet-exercise, healthy-habits, kids-health, medication-refills, research-monitor | aks129/SmartHealthConnect (archived β skills frozen at v1.2.0; live successors are CareAgents advisors) |
Each skill is auto-discoverable β Claude loads it when your prompt matches the skill's trigger phrases (e.g. "check my care gaps", "redact this bundle", "run Curatr on my conditions").
Not on Claude/MCP? The same 28 guardrailed tools run on OpenAI, Gemini, LangChain, or plain HTTP via the framework-neutral bridge in adapters/ β see Recipe: run HealthClaw tools on any agent framework. Guardrails stay server-side, so no framework can bypass them.
Tool names use underscores (not dots) for Claude Desktop / MCP client compatibility.
Read tools (no step-up for public tenants):
| Tool | Description |
|---|---|
context_get | Retrieve pre-built context envelopes |
fhir_read | Read a FHIR resource (redacted) |
fhir_search | Search with patient, code, status, date filters |
fhir_validate | Structural validation |
fhir_stats | Observation statistics (count/min/max/mean) |
fhir_lastn | Most recent N observations per code |
fhir_interpret_labs | Lab reference-range interpretation ($interpret) β decision support, not diagnosis |
care_gaps | Preventive-care gaps ($care-gaps) β screenings/immunizations that may be due, from the patient's own records |
guardrail_conformance | Run the guardrail conformance self-test β graded AβF scorecard across all seven properties |
fhir_permission_evaluate | R6 Permission access control evaluation |
fhir_subscription_topics | List available SubscriptionTopics |
questionnaire_populate | SDC $populate β pre-fill a Questionnaire for a subject |
curatr_evaluate | Evaluate a FHIR resource for data quality issues |
action_status | Poll a real-world action (call/SMS) |
search | ChatGPT-connector-compatible search β thin wrapper over fhir_search, returns compact {id, title, url} results |
fetch | ChatGPT-connector-compatible fetch by ResourceType/id β thin wrapper over fhir_read, returns {id, title, text, url, metadata} |
Write tools (require step-up token):
| Tool | Description |
|---|---|
fhir_propose_write | Validate + preview without committing |
fhir_commit_write | Commit with step-up auth + human-in-the-loop |
questionnaire_extract | SDC $extract β extract resources from a completed QuestionnaireResponse |
curatr_apply_fix | Apply patient-approved fixes with Provenance tracking |
action_propose / action_commit | Propose / commit a real-world phone call or SMS |
rx_transfer_request | Draft a pharmacy-transfer request call from active meds (Schedule II refused); commit via action_commit |
shl_generate | Generate an encrypted SMART Health Link (QR) |
Utility tools:
| Tool | Description |
|---|---|
fhir_get_token | Issue a 5-minute step-up token (call before any write) |
fhir_seed | Seed a tenant with demo Patient + Observations + Condition |
fhir_compiled_truth | Current state + Provenance evidence timeline |
All tools add _mcp_summary with reasoning, clinical context, and limitations.
The 6-step demo at /r6/fhir/demo/agent-loop shows the full guardrail sequence:
| Feature | This Project | AWS HealthLake MCP | Medplum MCP | Raw FHIR API |
|---|---|---|---|---|
| Works with any FHIR server | Yes | HealthLake only | Medplum only | N/A |
| PHI redaction on reads | Yes | No | No | No |
| Immutable audit trail | Yes | CloudTrail (separate) | Partial | No |
| Step-up auth for writes | Yes | IAM (separate) | Medplum auth | No |
| Human-in-the-loop | Yes | No | No | No |
| Permission $evaluate (R6) | Yes | No | No | No |
| Setup time | 10 seconds | 30+ minutes | 15+ minutes | Varies |
| Version | Profile | Status | Resources |
|---|---|---|---|
| R4 | US Core v9 | Stable | Patient, Condition, AllergyIntolerance, Immunization, MedicationRequest, Procedure, DiagnosticReport, CarePlan, CareTeam, Goal, DocumentReference, Coverage, ServiceRequest, Location, Organization, Practitioner, PractitionerRole, RelatedPerson, Specimen, FamilyMemberHistory |
| R6 | v6.0.0-ballot3 | Experimental | Permission, SubscriptionTopic, DeviceAlert, NutritionIntake, DeviceAssociation, NutritionProduct, Requirements, ActorDefinition |
Both R4 and R6 resources flow through the same guardrail stack (PHI redaction, audit, step-up auth, tenant isolation). R6 ballot resources may change before final release.
| Endpoint | Method | Description |
|---|---|---|
/r6/fhir/metadata | GET | CapabilityStatement |
/r6/fhir/health | GET | Liveness probe (reports upstream status) |
/r6/fhir/{type} | POST | Create resource (requires step-up) |
/r6/fhir/{type} | GET | Search resources |
/r6/fhir/{type}/{id} | GET | Read resource (redacted) |
/r6/fhir/{type}/{id} | PUT | Update resource (requires step-up + ETag) |
/r6/fhir/{type}/$validate | POST | Validate resource |
/r6/fhir/Questionnaire[/{id}]/$populate | POST | SDC β pre-fill a QuestionnaireResponse from a subject |
/r6/fhir/QuestionnaireResponse/$extract | POST | SDC β extract a transaction Bundle (?dryRun=true to preview) |
/r6/fhir/{type}/{id}/$deidentify | GET | Conservative de-identification preview (expert review required) |
/r6/fhir/Observation/$stats | GET | Observation statistics |
/r6/fhir/Observation/$lastn | GET | Most recent observations |
/r6/fhir/Permission/$evaluate | POST | R6 access control evaluation |
/r6/fhir/SubscriptionTopic/$list | GET | Subscription topic discovery |
/r6/fhir/Bundle/$ingest-context | POST | Bundle ingestion + context envelope |
/r6/fhir/context/{id} | GET | Retrieve context envelope |
/r6/fhir/AuditEvent | GET | Search audit events |
/r6/fhir/AuditEvent/$export | GET | Export audit trail (NDJSON/Bundle) |
/r6/fhir/demo/agent-loop | POST | 6-step guardrail demo |
/r6/fhir/oauth/* | * | OAuth 2.1 + PKCE + SMART discovery |
/r6/fhir/{type}/{id}/$curatr-evaluate | GET | Evaluate resource data quality (Curatr) |
/r6/fhir/{type}/{id}/$curatr-apply-fix | POST | Apply patient-approved fixes with Provenance |
Local search accepts the parameters advertised by /r6/fhir/metadata.
Unknown parameters default to lenient handling (a bounded
search.mode="outcome" warning); Prefer: handling=strict returns a 400
OperationOutcome. Unsupported modifiers and malformed supported values always
return 400. _count=0 and _summary=count are count-only searches. Self links
contain exactly the applied, URL-encoded parameters, and audit output never
echoes submitted filter values or arbitrary parameter names.
Connect to real FHIR servers while keeping all guardrails active:
Tested with: HAPI FHIR R4/R5, SMART Health IT, Epic Sandbox.
Put the guardrails in front of your FHIR server β recipe for running the
redaction + audit + step-up + human-in-the-loop stack in front of Medplum
(the same pattern works for Aidbox, Google Cloud Healthcare, or any FHIR R4
server): docs/recipes/healthclaw-in-front-of-medplum.md.
A repeatable integration test (tests/test_medplum_in_front.py) proves a
Medplum-returned Patient comes back redacted + audited and writes are step-up
gated before reaching Medplum.
Curatr is a patient-facing data quality skill that evaluates FHIR health records for coding issues and lets the patient decide how to resolve them.
What Curatr checks on a Condition:
| Check | Service | Example |
|---|---|---|
| Deprecated code system | Local lookup (no network) | ICD-9-CM β critical |
| ICD-10-CM code validity | NLM Clinical Tables API | Invalid code β warning |
| SNOMED CT / LOINC validity | tx.fhir.org (HL7 public) | Unknown code β warning |
| RxNorm drug code | RXNAV API (NLM) | Missing RXCUI β warning |
| Display name accuracy | Cross-checked with canonical term | Mismatch β suggestion |
| Missing required fields | Structural | No clinicalStatus β warning |
Every fix creates a linked Provenance resource recording patient intent, field changes, and agent attribution. All changes are audited in the immutable trail.
OpenClaw skill: skills/curatr/SKILL.md
Patient-controlled encrypted record sharing via QR code, implemented on top of
jmandel/kill-the-clipboard-skill
(MIT, pinned fa0020d) β credit Josh Mandel. HealthClaw governs what enters the
bundle (step-up auth, profiles, guardrails, audit trail); KTC governs sharing
(zero-knowledge server-side storage, SHL STU 1 protocol, revocation, in-browser
viewer).
What it does: The shl_generate MCP tool (Write group, step-up required)
fetches the patient's guardrailed FHIR bundle, encrypts it client-side in the MCP
server (the SHL server never sees plaintext), uploads ciphertext, and returns:
shlink β the shlink:/ URI to encode in a QR (an encrypted pointer, not data)viewer_link β browser URL for clinic staffmanage_link β patient-only revocation + access-log URLSecurity: The QR encodes only the encrypted pointer. PHI never appears in the
QR image. The SHL server stores only ciphertext + sha256(auth_token). Persona
hard rule: see skills/share-health-qr/SKILL.md β never direct-encode PHI into
QR images (incident 2026-06-12).
Without SHL_SERVER_URL, shl_generate returns an explicit simulation stub
(simulated: true) β never a fake link.
Caveat 1 β deploy from the right directory: The repo-root
railway.tomltargets the Flask Dockerfile. If you runrailway up --service shl-serverfrom the repo root, Railway uses the wrong Dockerfile and the deploy fails. Alwayscd services/shl-serverfirst β that directory has its ownrailway.tomlthat points to the correct image.Caveat 2 β watchPatterns skip: A service that inherited
watchPatternsfrom the root config may silently skip Dockerfile-only deploys (no source file changes detected). The per-servicerailway.tomlinservices/shl-server/overrides this after the first successful build. If deploys are skipped, force one withrailway up --service shl-serverfrom the shl-server directory.Caveat 3 β simulation mode: Without
SHL_SERVER_URLon the MCP server,shl_generatereturns{ simulated: true, note: "SHL_SERVER_URL not configured β returned stub." }. Personas surface this note verbatim and never improvise an alternative.
OpenClaw skill: skills/share-health-qr/SKILL.md
These resources are part of the FHIR R6 ballot3 specification and may change before final release.
| Resource | What's New in R6 |
|---|---|
| Permission | Access control (separate from Consent), $evaluate operation |
| SubscriptionTopic | Restructured pub/sub (introduced R5, maturing R6) |
| DeviceAlert | ISO/IEEE 11073 device alarms |
| NutritionIntake | Dietary consumption tracking |
| DeviceAssociation | Device-patient relationships |
| NutritionProduct | Nutritional product definitions |
| Requirements | Functional requirements tracking |
| ActorDefinition | Actor role definitions |
Standard FHIR R4 resources conforming to US Core Implementation Guide v9. These are widely deployed in US healthcare and stable for production use.
AllergyIntolerance, Immunization, MedicationRequest, Medication, MedicationDispense, Procedure, DiagnosticReport, CarePlan, CareTeam, Goal, DocumentReference, Location, Organization, Practitioner, PractitionerRole, RelatedPerson, Coverage, ServiceRequest, Specimen, FamilyMemberHistory
| Variable | Required | Default | Description |
|---|---|---|---|
STEP_UP_SECRET | Production | β | HMAC-SHA256 signing secret |
FHIR_UPSTREAM_URL | No | β | Upstream FHIR server (enables proxy mode) |
SQLALCHEMY_DATABASE_URI | Production | sqlite:///mcp_server.db | Database connection |
SESSION_SECRET | No | (dev key) | Flask session secret |
READ_AUTH_ENABLED | Production | false | Require tenant-bound credentials on protected reads |
PUBLIC_TENANTS | Production | β | Explicit comma-separated synthetic/demo tenant allowlist |
REDIS_URL | Production | β | Shared nonce, OAuth, rate-limit, and worker state |
MCP_AUTH_TOKEN | HTTP MCP | β | Bearer credential required by MCP HTTP transports |
MCP_PUBLIC_DEMO | No | false | Run an unauthenticated MCP server hard-pinned to a synthetic demo tenant (the public keyless demo). Never set on a server that reaches real tenants |
MCP_DEMO_TENANT | No | desktop-demo | Synthetic tenant the demo server is pinned to when MCP_PUBLIC_DEMO is set |
FHIR_UPSTREAM_TIMEOUT | No | 15 | Upstream request timeout (seconds) |
FHIR_LOCAL_BASE_URL | No | β | Local URL for response URL rewriting |
Database DDL is never run during WSGI import. Run flask --app main init-db
before each release; it applies the locked Alembic revisions. Operators adopting
Alembic on an existing v1.8.0 Postgres deployment must follow the
database migration runbook to verify and
stamp the compatibility baseline before upgrading.
This walkthrough shows how to go from a raw HealthEx export to querying your own records through Claude Code's MCP tools.
.mcp.json in this repo auto-configures Claude Code when you open the project.
Update X-Tenant-ID to match your --tenant-id:
Then in Claude Code:
Records arrive via webhook at /r6/fasten/webhook and are stored under the
patient's canonical tenant ID.
Commands: /health, /conditions, /labs, /curatr, /curatr fix, /approve.
Or via Docker Compose:
Set in .env (leave FHIR_UPSTREAM_URL empty):
All guardrails apply to Medplum responses identically to local SQLite mode.
Access tokens are cached in Redis (key medplum:access_token; falls back to
in-process cache when Redis is unavailable).
X-Human-Confirmed), not cryptographic confirmation β a compensating control for the demo, not proof a human acted. Real-world actions (phone/SMS/etc.) no longer use that header: commit only submits the action for out-of-band approval (202 awaiting_confirmation), and the patient's Approve tap consumes a single-use ActionConfirmation credential server-side before anything executes.READ_AUTH_ENABLED=true (authenticate non-public reads), INTERNAL_TOKEN_MINT_SECRET (gate token mint/seed for non-public tenants; fail-closed in prod when unset), PUBLIC_TENANTS limited to synthetic demo tenants, a real SESSION_SECRET/STEP_UP_SECRET, and https-only upstreamsWHERE status='proposed' claim) rather than nonce consumptionHealthClaw Guardrails is developed in the open as a shared reference, not a commercial product. The guardrail layer between AI agents and clinical data only gets trustworthy if a lot of people with different vantage points pressure-test it. We especially want:
$populate/$extract.Start here: CONTRIBUTING.md Β· Roadmap Β· Dev Guide Β· Code of Conduct Β· CHANGELOG.md Β· Security policy
Good first contributions are labeled in the issue tracker. Contributions are DCO-signed (git commit -s) under the MIT license β see LICENSING.md for the project's licensing posture going forward.
MIT β free to use, fork, and build on. See LICENSE.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/healthclaw-guardrails)<a href="https://allmcps.com/mcp/healthclaw-guardrails"><img src="https://allmcps.com/api/badge/healthclaw-guardrails?style=directory" alt="Healthclaw Guardrails on AllMCPs" /></a>