Architecture, guardrail patterns, FHIR concepts, and how-tos for HealthClaw Guardrails.
HealthClaw Guardrails is a vendor-neutral guardrail proxy that sits between any AI agent and any FHIR health data server. It is a healthclaw.io open source project.
AI Agent ──▶ MCP Server ──▶ Guardrail Proxy ──▶ Any FHIR Server
(Claude, (Node.js) (Flask Python) (HAPI, Epic,
GPT-4, etc) ↓ Medplum, etc.)
PHI redaction
Audit trail
Step-up auth
Tenant isolation
Human-in-the-loop
fhir.read)| Component | Tech | Purpose |
|---|---|---|
| Flask App | Python / Flask | FHIR REST facade at /r6/fhir/*, guardrail enforcement |
| MCP Server | Node.js / TypeScript | 23 MCP tools (incl. SDC $populate/$extract), Streamable HTTP + SSE + stdio transports |
| Database | SQLite (dev) / PostgreSQL (prod) | Resource storage, audit trail, context envelopes |
| Redis | Optional | Per-tenant rate limiting, session storage |
| Upstream FHIR | Any FHIR R4/R5/R6 server | Real clinical data source (proxy mode) |
All guardrails are always active — they cannot be disabled per-request. They apply in local mode and upstream proxy mode identically.
Applied on every read path, including upstream FHIR server responses, before data reaches the AI agent.
| Field | What happens | Example |
|---|---|---|
| Name | Truncated to initials | Maria Elena Rivera → M. E. Rivera |
| Identifier | Masked to last 4 chars | MRN12345678 → ***5678 |
| Address | Stripped to city/state/country | 123 Main St, Boston, MA → Boston, MA |
| Birth date | Truncated to year | 1985-03-15 → 1985 |
| Telecom | Fully removed | 617-555-0198 → [removed] |
| Photo | Fully removed | — |
Reads require only X-Tenant-Id. All write operations additionally require an X-Step-Up-Token header.
STEP_UP_SECRETTokens are issued at GET /r6/fhir/internal/step-up-token (with tenant header).
Clinical resource writes return HTTP 428 Precondition Required unless the request includes X-Human-Confirmed: true.
Clinical types: Observation, Condition, MedicationRequest, DiagnosticReport, AllergyIntolerance, Procedure, CarePlan, Immunization, NutritionIntake, DeviceAlert.
Every resource access writes an AuditEvent record. Records are append-only — SQLAlchemy event listeners raise RuntimeError on any UPDATE or DELETE.
Each AuditEvent records: event type, resource type + ID, tenant ID, agent ID, outcome (success/failure), timestamp, and optional detail.
Export via GET /r6/fhir/AuditEvent/$export in NDJSON or Bundle format.
Every database query is scoped by tenant_id. Resources created by tenant A are invisible to tenant B. The X-Tenant-Id header is required on all non-public endpoints and validated against [a-zA-Z0-9_-]{1,64}.
Standard FHIR R4 resources conforming to the US Core Implementation Guide v9. Widely deployed in US healthcare.
| Resource | US Core required fields | Curatr evaluates |
|---|---|---|
| Condition | code, subject | Yes — ICD-10, SNOMED, deprecated code detection |
| AllergyIntolerance | clinicalStatus, verificationStatus, patient | Yes — RxNorm/SNOMED, status validation |
| Immunization | status, vaccineCode, patient, occurrence[x] | Yes — CVX/SNOMED, occurrence date |
| MedicationRequest | status, intent, medication[x], subject | Yes — RxNorm code quality |
| Procedure | status, code, subject | Yes — SNOMED/CPT code quality |
| DiagnosticReport | status, code, subject | Yes — LOINC code quality |
| CarePlan | status, intent, subject | No — CRUD only |
| Goal | lifecycleStatus, description, subject | No — CRUD only |
| Coverage | status, beneficiary, payor | No — CRUD only |
| ServiceRequest | status, intent, subject | No — CRUD only |
| DocumentReference | status, subject, content | No — CRUD only |
| Location, Organization, Practitioner, PractitionerRole, RelatedPerson, CareTeam, Specimen, FamilyMemberHistory | Varies | No — CRUD only |
FHIR R6 v6.0.0-ballot3 resources. These are experimental and may change before R6 final release.
| Resource | What's new in R6 |
|---|---|
| Permission | Access control separate from Consent. $evaluate with human-readable reasoning. |
| SubscriptionTopic | Restructured pub/sub. Storage + discovery — notifications not dispatched. |
| DeviceAlert | ISO/IEEE 11073 device alarms. |
| NutritionIntake | Dietary consumption tracking. |
| DeviceAssociation, NutritionProduct, Requirements, ActorDefinition | CRUD only. |
The MCP server exposes 12 tools in two tiers. All read tools include an _mcp_summary field with reasoning, clinical context, and limitations to help the AI agent understand what it received and what it should tell the user.
fhir.propose_write. Validates the resource and returns a preview. Nothing is written.requires_human_confirmation (true for clinical types) and proposal_status.fhir.commit_write with X-Step-Up-Token and X-Human-Confirmed: true for clinical resources.fhir.permission_evaluate evaluates stored R6 Permission resources against a subject + action + resource combination. Returns permit/deny with a human-readable reasoning string explaining which rule matched (or why default deny applied). This separates access control from consent records.
Curatr is the patient-facing skill within HealthClaw. It gives patients visibility into the quality of their own health records and the ability to correct them.
curatr.evaluate on a resourcecuratr.apply_fix — resource updated + linked Provenance resource created| Service | Validates | Auth needed |
|---|---|---|
| tx.fhir.org (HL7 public) | SNOMED CT, LOINC, ICD-10 | None |
| NLM Clinical Tables API | ICD-10-CM codes + descriptions | None |
| RXNAV API (NLM) | RxNorm drug codes | None |
| Local lookup | Deprecated systems (ICD-9-CM, etc.) | None |
Every approved fix creates a FHIR Provenance resource recording: target resource, recorded timestamp, patient intent as free text, agent attribution (curatr), and exact field changes (before/after values).
Fasten Connect is the ingestion layer for HealthClaw Guardrails. Patients authorize access to their EHR systems via the Fasten Stitch widget; records arrive as FHIR R4 NDJSON and flow through the full guardrail stack once ingested.
| Variable | Where to get it | Required |
|---|---|---|
FASTEN_PUBLIC_KEY | Fasten Developer Portal — starts with public_test_ or public_live_ | Yes |
FASTEN_PRIVATE_KEY | Same portal — never expose client-side | Yes |
FASTEN_WEBHOOK_SECRET | Portal → Delivery Logs → Signing secret (starts with whsec_) | Recommended |
FASTEN_CURATR_SCAN | Set true to auto-run Curatr after each import | No |
Step 1: Start the stack
export FASTEN_PUBLIC_KEY=public_test_XXX export FASTEN_PRIVATE_KEY=private_test_XXX export FASTEN_WEBHOOK_SECRET=whsec_XXX export STEP_UP_SECRET=$(openssl rand -hex 32) export FASTEN_CURATR_SCAN=true docker-compose up -d --build
Step 2: Enable the skill in OpenClaw
Place the skill in your workspace skills folder or install from the project:
# Copy the skill to your OpenClaw workspace skills folder cp -r skills/fasten-connect ~/.openclaw/skills/ # Or run from this project directory — OpenClaw picks up workspace skills automatically
OpenClaw reads FASTEN_PUBLIC_KEY from your environment (declared in metadata.openclaw.requires.env). The skill gates itself — it won't appear active until the key is set.
Step 3: Embed the Stitch widget in your web app
<!-- Add to any HTML page your patients use -->
<link rel="stylesheet" href="https://stitch.fastenhealth.com/v0.4/bundle.css">
<script src="https://stitch.fastenhealth.com/v0.4/bundle.js"></script>
<fasten-stitch-element public-id="public_test_XXX"></fasten-stitch-element>
<!-- TEFCA mode: single identity verification for all QHINs -->
<!-- <fasten-stitch-element public-id="public_test_XXX" tefca-mode="true"></fasten-stitch-element> -->
<script>
document.querySelector('fasten-stitch-element')
.addEventListener('widget.complete', async (event) => {
const { org_connection_id, endpoint_id, tefca_directory_id, platform_type } = event.detail;
// Register the connection with HealthClaw
await fetch('/fasten/connections', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Tenant-Id': 'YOUR_TENANT_ID' },
body: JSON.stringify({ org_connection_id, endpoint_id, tefca_directory_id, platform_type }),
});
});
</script>
Step 4: Configure your webhook
In the Fasten Developer Portal, set your webhook delivery URL to:
https://your-domain.com/fasten/webhook
For local development, use ngrok or the Fasten Webhook Simulator.
Step 5: Use OpenClaw to work with ingested data
Once an import completes, the fhir-r6-guardrails and curatr skills expose all ingested data through MCP. In OpenClaw Messenger:
You: "Show me my recent conditions" → fhir.search(patient, Condition) — returns redacted conditions You: "Check my diabetes diagnosis for quality issues" → curatr.evaluate(Condition, cond-001) — checks ICD-10, SNOMED, required fields You: "Update the code to ICD-10-CM E11.9" → Patient approves → curatr.apply_fix() — writes fix + Provenance record
Check import status:
curl /fasten/jobs -H "X-Tenant-Id: your-tenant-id"
# Returns: [{"task_id": "...", "status": "complete", "ingested_resources": 847, ...}]
railway.toml) or any persistent server for production use.
Step 1: Start the HealthClaw stack
export FASTEN_PUBLIC_KEY=public_test_XXX export FASTEN_PRIVATE_KEY=private_test_XXX export FASTEN_WEBHOOK_SECRET=whsec_XXX export STEP_UP_SECRET=$(openssl rand -hex 32) # Start Flask + MCP server docker-compose up -d --build # Or manually: uv sync && python main.py & cd services/agent-orchestrator && npm ci && npm start
Step 2: Point Claude at the MCP server
Add to ~/.claude/settings.json (Claude Code) or your Claude Desktop MCP config:
{
"mcpServers": {
"healthclaw-guardrails": {
"url": "http://localhost:3001/mcp",
"headers": {
"X-Tenant-Id": "my-tenant"
}
}
}
}
Step 3: Register a test connection
Use Fasten's test credentials to connect a synthetic patient, then register the org_connection_id:
# After patient authenticates via Stitch widget, register the connection:
curl -X POST http://localhost:5000/fasten/connections \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: my-tenant" \
-d '{"org_connection_id": "org-conn-test-001", "platform_type": "epic"}'
# Trigger the export via Fasten API:
curl -X POST https://api.connect.fastenhealth.com/v1/bridge/fhir/ehi-export \
-u "public_test_XXX:private_test_XXX" \
-H "Content-Type: application/json" \
-d '{"org_connection_id": "org-conn-test-001"}'
Step 4: Ask Claude to work with the data
Once the import webhook fires and ingestion completes, Claude has full access via MCP:
# In Claude Code (claude.ai/code) or Claude Desktop: "Check the import status" → (uses curl or direct) GET /fasten/jobs "Search for my conditions" → fhir.search with resourceType=Condition, patient filter "Evaluate this condition for data quality issues" → curatr.evaluate on the Condition resource "What guardrails apply to writing a new observation?" → fhir.propose_write to see validation + HITL requirements before committing
Step 5: Load the skill files for context
In Claude Code, the skill files provide authoritative reference without token overhead:
# The skills are in the workspace — Claude Code loads them automatically. # Or reference them directly: cat skills/fasten-connect/SKILL.md # Fasten integration reference cat skills/fhir-r6-guardrails/SKILL.md # MCP tool reference cat skills/curatr/SKILL.md # Curatr evaluation reference
tefca-mode="true" on the Stitch widget. For test mode, use synthetic patients from the Fasten test credentials page. Use tefca_directory_id (not endpoint_id) as the stable identifier when registering TEFCA connections.
HealthClaw Guardrails is the data + compliance layer. Conversational gateways are how a human talks to it. The MCP server is the same in every case — every gateway connects to it, sends tool calls, and renders the redacted responses back to the user in chat.
| Gateway | Where you chat | Auth model | Self-improving? |
|---|---|---|---|
| OpenClaw | Telegram bot | JSON-RPC HTTP bridge (/mcp/rpc) | No — fixed slash commands |
| Hermes | Telegram · Discord · Slack · WhatsApp · Signal · CLI · HTTP | Native MCP Streamable HTTP | Yes — learns skills from conversation |
| PromptOpinion | Marketplace agent in browser | Native MCP + SHARP-on-MCP headers | No (platform-level evaluations only) |
| Claude Desktop / Code | Anthropic clients | Native MCP stdio or HTTP | No |
The original integration — a Python Telegram bot in openclaw/ that translates slash commands into MCP tool calls via the HTTP bridge. Best when you want exactly one chat channel with predictable commands and zero ambiguity.
Setup is documented in the Fasten quick-start above — same install path applies for any data source.
Hermes is Nous Research's self-improving agent. It supports seven execution backends (local, Docker, SSH, Singularity, Modal, Daytona, Vercel Sandbox) and six messaging gateways (Telegram, Discord, Slack, WhatsApp, Signal, plus CLI / HTTP), and is a native MCP client over Streamable HTTP. No JSON-RPC bridge needed on the HealthClaw side.
~/.hermes/skills/healthclaw/ based on how you actually use them. The repo copy stays canonical.
Step 1 — Install Hermes
Follow the upstream instructions at github.com/nousresearch/hermes-agent. At the end you have a hermes CLI on PATH and a ~/.hermes/ directory.
Step 2 — Wire HealthClaw into Hermes
git clone https://github.com/aks129/HealthClawGuardrails cd HealthClawGuardrails ./hermes/install.sh
The installer is idempotent and does three things:
skills/ to ~/.hermes/skills/healthclaw/SOUL.md persona at ~/.hermes/personas/healthclaw.md~/.hermes/config.json via jqFlags: --dry-run previews actions without touching anything; --skills-only refreshes just the skills if Hermes has edited them and you want the repo copy back.
Step 3 — Start a session
hermes > /persona healthclaw > /mcp list # confirms healthclaw-hosted connected > show me my conditions > what's my last HbA1c? > propose adding peanut allergy, moderate severity
Step 4 — Pick a mode
| Mode | When to use | How to enable |
|---|---|---|
healthclaw-hosted | Try everything against synthetic Grover Keeling sample data — no install of HealthClaw stack needed | Default — installer enables |
healthclaw-local | Your own data on your own machine | docker-compose up -d --build, then rename _healthclaw-local → healthclaw-local in ~/.hermes/config.json |
healthclaw-sharp | Hermes is the SMART-launched agent and you want HealthClaw guarding a real EHR (Epic, Cerner, MEDITECH, athenahealth, eClinicalWorks, HAPI, SMART Health IT) | Rename _healthclaw-sharp → healthclaw-sharp, fill in X-FHIR-Server-URL, X-FHIR-Access-Token, X-Patient-ID |
Step 5 — Let it learn
Save working patterns: /skill save after a good answer. Refine SOUL on misfires: tell Hermes "that should have called fhir_search first" and it'll update the persona for next time. Your ~/.hermes/skills/healthclaw/ directory drifts toward your usage pattern; the repo copy stays as the canonical seed.
~/.hermes/skills/openclaw-imports/ — sitting alongside HealthClaw's skills at ~/.hermes/skills/healthclaw/. Hermes surfaces whichever skill matches the user's question.
HealthClaw is published on the PromptOpinion Marketplace as both a Superpower (MCP server) and an Agent. PromptOpinion is a healthcare AI assembly platform built on MCP, A2A, and FHIR; HealthClaw was the FHIR IQ team's entry for the Agents Assemble Challenge.
| Listing | What it is | URL |
|---|---|---|
| Agent | HealthClaw Clinical Reviewer — A2A-compatible agent that orchestrates the MCP tools and narrates the guardrails | Open in Marketplace |
| Superpower (MCP) | The raw HealthClaw MCP server — 16 tools, declared scopes, full guardrail stack | Open in Marketplace |
The PromptOpinion listing is "Open Access" — no credentials needed to add it to a workspace. The MCP server advertises both ecosystems' FHIR-context shapes in its initialize response:
| Spec | Where in capabilities | Purpose |
|---|---|---|
| SHARP-on-MCP | capabilities.experimental.{fhir_context_required, sharp} | Vendor-neutral header-forwarding contract — agent host obtains a SMART access token and forwards it on every call. No OAuth dance in HealthClaw itself. |
| PromptOpinion FHIR Extension | capabilities.extensions["ai.promptopinion/fhir-context"] | Declares the SMART-on-FHIR scope manifest PromptOpinion should request from the agent host (patient/*.read required, patient/*.write and offline_access optional). |
Both specs land on the same headers — X-FHIR-Server-URL, X-FHIR-Access-Token, X-Patient-ID, plus optional X-FHIR-Refresh-Token / X-FHIR-Refresh-Url when offline_access is granted — so a single HealthClaw deployment satisfies both marketplaces.
Set FHIR_UPSTREAM_URL to enable proxy mode. All guardrails remain active.
uv sync STEP_UP_SECRET=your-secret python main.py # With upstream FHIR server FHIR_UPSTREAM_URL=https://hapi.fhir.org/baseR4 STEP_UP_SECRET=secret python main.py # Docker Compose (Flask + MCP server + Redis) docker-compose up -d --build # MCP server only cd services/agent-orchestrator && npm ci && npm start