Set up HealthClaw Guardrails end-to-end and test every guardrail feature with real or sample FHIR health data. Walks through PHI redaction, Curatr data quality checks, step-up auth, MCP tools, and the Fasten Connect demo.
You are setting up HealthClaw Guardrails and walking the user through every major feature end-to-end. Work through each phase below in order. Show real output at each step; don't skip ahead.
Check if the user passed a FHIR JSON file path as an argument: $ARGUMENTS
$ARGUMENTS is a file path, read that file. It may be a single FHIR resource (Patient, Observation, Condition, MedicationRequest) or a Bundle. Confirm what resource types are present.$ARGUMENTS is blank, tell the user you'll use built-in sample data and proceed.Run these in order and confirm each succeeds before continuing:
cd "$(git rev-parse --show-toplevel)"
uv sync
Check that the venv is healthy:
uv run python -c "import flask, sqlalchemy; print('deps OK')"
Generate a STEP_UP_SECRET for this session:
python3 -c "import secrets; print(secrets.token_hex(32))"
Save the printed secret — you'll export it as STEP_UP_SECRET for all subsequent curl calls.
Check if port 5000 is already in use:
curl -s http://localhost:5000/r6/fhir/health 2>/dev/null | python3 -m json.tool 2>/dev/null || echo "NOT_RUNNING"
If Flask is not running, instruct the user:
Open a second terminal and run:
STEP_UP_SECRET=<secret-from-above> uv run python main.pyThen tell me when it's up (or I'll poll for it).
Poll until Flask is up (retry up to 20 times, 2-second sleep):
for i in $(seq 1 20); do
STATUS=$(curl -s http://localhost:5000/r6/fhir/health 2>/dev/null)
echo "$STATUS" | python3 -m json.tool 2>/dev/null && break
echo "Waiting... ($i/20)" && sleep 2
done
Show the health response. Confirm "status": "healthy" before continuing.
Generate a test tenant:
TENANT_ID="quickstart-$(date +%s)"
echo "Tenant: $TENANT_ID"
If using sample data, POST these four resources. For each, capture the returned id:
Patient:
curl -s -X POST http://localhost:5000/r6/fhir/Patient \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{
"resourceType": "Patient",
"name": [{"use": "official", "family": "Rivera", "given": ["Maria", "Elena"]}],
"birthDate": "1985-03-15",
"address": [{"line": ["123 Clinical Ave"], "city": "Boston", "state": "MA", "postalCode": "02101"}],
"telecom": [{"system": "phone", "value": "617-555-0198"}],
"identifier": [{"system": "http://example.org/mrn", "value": "MRN-2026-4471"}]
}'
Condition (with deprecated ICD-9 code — triggers Curatr warning):
curl -s -X POST http://localhost:5000/r6/fhir/Condition \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d "{
\"resourceType\": \"Condition\",
\"subject\": {\"reference\": \"Patient/$PATIENT_ID\"},
\"code\": {\"coding\": [{\"system\": \"http://hl7.org/fhir/sid/icd-9-cm\", \"code\": \"250.00\", \"display\": \"Diabetes mellitus without mention of complication\"}]},
\"clinicalStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-clinical\", \"code\": \"active\"}]},
\"verificationStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-ver-status\", \"code\": \"confirmed\"}]}
}"
Observation (blood glucose):
curl -s -X POST http://localhost:5000/r6/fhir/Observation \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d "{
\"resourceType\": \"Observation\",
\"status\": \"final\",
\"subject\": {\"reference\": \"Patient/$PATIENT_ID\"},
\"code\": {\"coding\": [{\"system\": \"http://loinc.org\", \"code\": \"2339-0\", \"display\": \"Glucose [Mass/volume] in Blood\"}]},
\"valueQuantity\": {\"value\": 180, \"unit\": \"mg/dL\", \"system\": \"http://unitsofmeasure.org\", \"code\": \"mg/dL\"},
\"effectiveDateTime\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
}"
MedicationRequest (for Curatr check):
curl -s -X POST http://localhost:5000/r6/fhir/MedicationRequest \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d "{
\"resourceType\": \"MedicationRequest\",
\"status\": \"active\",
\"intent\": \"order\",
\"subject\": {\"reference\": \"Patient/$PATIENT_ID\"},
\"medicationCodeableConcept\": {\"coding\": [{\"system\": \"http://www.nlm.nih.gov/research/umls/rxnorm\", \"code\": \"860975\", \"display\": \"Metformin 500 MG Oral Tablet\"}]}
}"
If using user-supplied FHIR JSON, POST whatever resources are in $ARGUMENTS to the appropriate endpoint (/r6/fhir/<ResourceType>). Extract resource IDs from the responses.
Capture the patient ID from the POST response and set PATIENT_ID=<id>.
Read back the patient and show what the AI agent sees vs. what was stored:
curl -s http://localhost:5000/r6/fhir/Patient/$PATIENT_ID \
-H "X-Tenant-ID: $TENANT_ID" | python3 -m json.tool
Point out exactly which fields were redacted:
name → initials only (e.g. M. E. Rivera)identifier → masked (e.g. ***4471)telecom → [Redacted]address → city/state only, street strippedbirthDate → year only (1985)Explain: This is what every AI agent connected via MCP receives. The full data is stored; agents never see it.
Run Curatr on the Condition with the ICD-9 code:
curl -s -X POST "http://localhost:5000/r6/fhir/Condition/$CONDITION_ID/\$curatr-evaluate" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{}' | python3 -m json.tool
Show the issues array and explain each finding (deprecated_code_system, invalid_icd10, missing_rxnorm, display_mismatch, etc.).
Then run Curatr on the MedicationRequest too:
curl -s -X POST "http://localhost:5000/r6/fhir/MedicationRequest/$MED_ID/\$curatr-evaluate" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{}' | python3 -m json.tool
curl -s -X PUT "http://localhost:5000/r6/fhir/Condition/$CONDITION_ID" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{"resourceType":"Condition","id":"'$CONDITION_ID'","status":"inactive"}' \
-w "\nHTTP %{http_code}"
Expected: 401 Unauthorized. Explain: All write operations require an HMAC step-up token.
curl -s -X POST http://localhost:5000/r6/fhir/auth/stepup \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{"reason": "quickstart demo — correcting deprecated ICD-9 code", "resource_type": "Condition"}' \
| python3 -m json.tool
Capture the token from the response. Set STEPUP_TOKEN=<token>.
curl -s -X POST http://localhost:5000/r6/fhir/Condition/$CONDITION_ID/propose \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-H "X-Step-Up-Token: $STEPUP_TOKEN" \
-d "{
\"resourceType\": \"Condition\",
\"id\": \"$CONDITION_ID\",
\"subject\": {\"reference\": \"Patient/$PATIENT_ID\"},
\"code\": {\"coding\": [{\"system\": \"http://hl7.org/fhir/sid/icd-10-cm\", \"code\": \"E11.9\", \"display\": \"Type 2 diabetes mellitus without complications\"}]},
\"clinicalStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-clinical\", \"code\": \"active\"}]},
\"verificationStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-ver-status\", \"code\": \"confirmed\"}]}
}" | python3 -m json.tool
If the response is 428 Precondition Required, explain: This is the human-in-the-loop gate. Clinical resource writes require explicit human confirmation.
curl -s -X POST http://localhost:5000/r6/fhir/Condition/$CONDITION_ID/propose \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-H "X-Step-Up-Token: $STEPUP_TOKEN" \
-H "X-Human-Confirmed: true" \
-d "{
\"resourceType\": \"Condition\",
\"id\": \"$CONDITION_ID\",
\"subject\": {\"reference\": \"Patient/$PATIENT_ID\"},
\"code\": {\"coding\": [{\"system\": \"http://hl7.org/fhir/sid/icd-10-cm\", \"code\": \"E11.9\", \"display\": \"Type 2 diabetes mellitus without complications\"}]},
\"clinicalStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-clinical\", \"code\": \"active\"}]},
\"verificationStatus\": {\"coding\": [{\"system\": \"http://terminology.hl7.org/CodeSystem/condition-ver-status\", \"code\": \"confirmed\"}]}
}" | python3 -m json.tool
Expected: 200 OK. The ICD-9 code is now replaced with ICD-10.
Show every action recorded for this tenant:
curl -s "http://localhost:5000/r6/fhir/AuditEvent?tenant=$TENANT_ID&_count=20" \
-H "X-Tenant-ID: $TENANT_ID" | python3 -m json.tool
Point out: each entry has action (C/R/U/D), recorded timestamp, agent (the requester), and entity (the resource touched). This log is append-only — no entry can be deleted or modified.
Trigger the 5-step animated demo (simulates: patient auth → webhook → NDJSON ingest → PHI redact → audit):
curl -s -X POST http://localhost:5000/fasten/demo \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{"tenant_id": "'$TENANT_ID'"}' | python3 -m json.tool
Walk through each of the 5 steps in the response and explain what happened.
Check if the MCP orchestrator dependencies are installed:
ls services/agent-orchestrator/node_modules/.bin/ts-node 2>/dev/null && echo "READY" || echo "RUN: cd services/agent-orchestrator && npm ci"
If ready, start the MCP server (instruct user to run in a third terminal):
cd services/agent-orchestrator && MCP_PORT=3001 FHIR_BASE_URL=http://localhost:5000/r6/fhir npm start
Then demonstrate the fhir.search tool via the HTTP bridge:
curl -s -X POST http://localhost:3001/mcp/rpc \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{"tool": "fhir.search", "params": {"resourceType": "Condition", "patient": "'$PATIENT_ID'"}}' \
| python3 -m json.tool
The response will include a _mcp_summary block with clinical context and limitations — that's what Claude Desktop sees when it calls this tool.
Run Curatr on the Condition again to show the ICD-9 warning is gone:
curl -s -X POST "http://localhost:5000/r6/fhir/Condition/$CONDITION_ID/\$curatr-evaluate" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT_ID" \
-d '{}' | python3 -m json.tool
Expected: "issues": [] or only non-critical findings. The fix worked.
Print a concise table of everything demonstrated:
| Feature | Endpoint | Result |
|---|---|---|
| PHI Redaction | GET /r6/fhir/Patient/:id | Name/DOB/address/phone stripped |
| Curatr ICD-9 detection | POST /Condition/:id/$curatr-evaluate | deprecated_code_system issue found |
| Step-up token gate | PUT /r6/fhir/... without token | 401 Unauthorized |
| Human-in-the-loop gate | propose without X-Human-Confirmed | 428 Precondition Required |
| Confirmed write | propose + X-Human-Confirmed: true | 200 OK, resource updated |
| Audit trail | GET /r6/fhir/AuditEvent | All actions recorded, append-only |
| Fasten Connect demo | POST /fasten/demo | 5-step EHR ingestion flow |
| Curatr after fix | POST /Condition/:id/$curatr-evaluate | Issues cleared |
Then ask: What would you like to explore next — try with a real FHIR server upstream, connect via Claude Desktop MCP, or test the OAuth 2.1 SMART flow?