MCP Gateway for Clinical AI Agent Oversight
ClinGate is an open-source MCP (Model Context Protocol) gateway that sits between any AI agent framework and the clinical tools it calls. It classifies every action by risk in real time, and forces a human decision before anything irreversible reaches a patient record. Clinical AI agents are moving from answering questions to taking actions, but most multi-agent frameworks lack the built-in capability to enforce who is allowed to approve what, and how it gets logged. This is an infrastructure and dev-tool project, not a medical device. It acts as a governance layer any agent framework can sit behind, with clinical policy rules as the first shipped example.
graph TD
Agent[Agent Orchestrator] -->|MCP Tool Calls| Gateway[ClinGate MCP Gateway Server]
Gateway --> RuleEngine{Deterministic Rule Engine}
RuleEngine -->|No match| LLM{LLM Risk Classifier}
RuleEngine -->|Match| DecisionBranch
LLM --> DecisionBranch
DecisionBranch{Decision State} -->|AUTO_CLEAR| RealTool[Real Downstream Tool API]
DecisionBranch -->|ESCALATE| Queue[Escalation Queue & Dashboard]
DecisionBranch -->|BLOCK| Blocked[Blocked Action]
Queue -->|Human Approves| RealTool
Queue -->|Human Rejects| Blocked
Queue -.->|Logs to| Ledger[(SQLite Audit Ledger)]
DecisionBranch -.->|Logs to| Ledger
sequenceDiagram
participant A as Agent
participant G as ClinGate Gateway
participant F as openFDA API
participant R as Rule Engine
participant L as Audit Log
A->>G: order_medication(drug="Adderall")
G->>F: GET api.fda.gov/drug/label.json
F-->>G: DEA Schedule II
G->>R: Evaluate Policies (clinical-default.yaml)
R-->>G: Match: block_controlled_substances -> BLOCK
G->>L: Append row (BLOCK, reason: "Controlled substance")
G-->>A: Call intercepted and held for review. Status: BLOCK.
This gateway directly addresses the human-oversight patterns expected by modern regulatory frameworks:
| Requirement | Article / Guidance | How ClinGate addresses it |
|---|---|---|
| Human oversight capability | EU AI Act Art. 14 | Escalation queue with hard stop before execution |
| Record-keeping / logging | EU AI Act Art. 12 | Structured, exportable audit ledger per action |
| Deployer oversight assignment | EU AI Act Art. 26 | Reviewer role explicitly modeled, not implicit |
| Risk management system | EU AI Act Art. 9 | Configurable risk taxonomy + policy rules as first-class artifact |
| AI as Medical Device oversight | UK MHRA guidance | Same interception pattern maps directly to SaMD change-control expectations |
| Vendor oversight evidence | NHS DTAC | Dashboard + audit export is the artifact a DTAC assessor asks for |
The single biggest thing that separates this from a toy demo is that the risk classification pipeline reasons over real, live, public data rather than a hard-coded drug list. Four public sources are wired in:
| Source | What it provides | Endpoint (public, no key required for demo volume) | Used for |
|---|---|---|---|
| openFDA Drug Label API | Structured drug labeling including DEA schedule where present | GET https://api.fda.gov/drug/label.json?search=openfda.generic_name:"{drug}"&limit=1 |
Real-time controlled-substance detection — replaces a static keyword list with an actual regulatory field lookup |
| RxNorm (NLM RxNav) | Normalized drug names, RxCUI identifiers, ingredient relationships | GET https://rxnav.nlm.nih.gov/REST/rxcui.json?name={drug} |
Normalizes whatever string the agent used ("tylenol" → acetaminophen RxCUI) before the rule engine matches it, so policy rules match on the real ingredient, not on string luck |
| ClinicalTrials.gov API v2 | Live registry of recruiting trials by condition | GET https://clinicaltrials.gov/api/v2/studies?query.term={condition}&pageSize=3 |
Grounds the create_referral action type with real, currently-recruiting trials instead of a fictional referral target |
| HAPI FHIR public R4 sandbox | Real FHIR R4 Patient resources (synthetic patients, real schema and server) |
GET https://hapi.fhir.org/baseR4/Patient?_count=3&_format=json |
Populates the demo's patient context with genuine FHIR-shaped records, not a hand-typed JSON blob |
- Clone the repository
git clone https://github.com/vilsee/clingate.git
cd clingate- Setup Environment Variables
cp .env.example .env(Note: CLASSIFIER_MODE=mock is the safe default requiring no Anthropic API key. If you have an Anthropic API key, set CLASSIFIER_MODE=live and supply your key).
- Run the application (While a Docker Compose configuration is provided, it has not yet been verified end-to-end. We recommend running locally with Uvicorn for development and testing.)
pip install -r requirements.txt
uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reloadThe FastAPI MCP server and the static Next.js/HTML dashboard will be available at http://localhost:8000.
- TODO:
(The Reviewer Queue showing an ESCALATE item pending with a Classifier Disagreement badge) - TODO:
(The raw JSON audit ledger showing the final_status and classifier_disagreement flags) - TODO:
(The demo.html page showing live API degradation)
As per the Product Requirements Document (PRD), the MVP milestone requires two differentiating features to be fully implemented, with the remaining four explicitly stubbed for the roadmap.
- Status: Implemented
- Description: Logs when the deterministic rule engine and the fallback LLM risk classifier would have made different decisions.
- Implementation Details: Evaluated inside the interception loop (
main.py). If the rule engine falls through, the LLM is queried. If the LLM returns an escalation or block (disagreeing with the implicit rule engine fall-through of AUTO_CLEAR), theclassifier_disagreementboolean flag in the SQLite audit table is set toTrue.
- Status: Implemented
- Description: A background daemon that automatically transitions unreviewed
ESCALATEitems intoBLOCK(or a specific SLA breach state) if human reviewers fail to act within a defined timeframe. - Implementation Details: Implemented as an asynchronous background task
sla_breach_monitorinmain.py. It loops every 60 seconds and updates anyPENDINGintercept call older than 5 minutes toSLA_BREACH_BLOCKin the database.
These features have been explicitly documented as stubs in the backend/main.py source code to facilitate future roadmap development.
- Status: Stubbed (
stub_feature_1_clinical_trials_matching) - Description: Would automatically query ClinicalTrials.gov and attach currently recruiting, localized trials to any blocked medication order to provide alternatives to the reviewer. (Note: ClinicalTrials.gov data is currently retrieved and attached for referrals as a baseline integration, but this advanced matching/suggestion algorithm is deferred).
- Status: Stubbed (
stub_feature_2_fhir_resource_validation) - Description: Would validate outgoing tool arguments against active FHIR R4 profiles to ensure payload schema compliance before the call ever reaches the real system.
- Status: Stubbed (
stub_feature_3_reviewer_delegation_rules) - Description: Would route specific
ESCALATEitems to specific specialist queues (e.g., Oncology-related referrals route only to boarded oncologists) rather than a global reviewer pool.
- Status: Stubbed (
stub_feature_4_agent_feedback_loop) - Description: Would send natural language explanations of a
BLOCKdecision back to the autonomous agent, giving it the opportunity to self-correct and try a different tool call without human intervention.
- Gateway server: Python, FastAPI, MCP Python SDK
- Rule engine: custom YAML DSL +
pydanticvalidation - LLM classification: Anthropic API (Claude) via structured JSON output
- Audit store: SQLite (migratable to PostgreSQL)
- Dashboard: HTML/CSS/JS (vanilla stack matching a Next.js aesthetic)
- External APIs: openFDA, RxNorm, ClinicalTrials.gov, HAPI FHIR
Licensed under the Apache 2.0 License. Want to add your own YAML policy pack? See CONTRIBUTING.md.