Skip to main content
Glama
README.md
# MCPDischarge — Cross-Department MCP Interoperability
### EHR × Pharmacy × Billing | RBAC | PHI Boundary | FastMCP
**CitiusTech Gen AI & Agentic AI Training —  Project 5**

---

## The Problem Traditional APIs Cannot Solve

A patient is ready for discharge. Data must flow across three departments that have never shared a common protocol:

```
Traditional workflow (45 minutes, 15 manual handoffs):
  Ward nurse    → prints discharge note
  Ward nurse    → phones pharmacy to check drug availability
  Pharmacy      → calls back 2 hours later (drug out of stock)
  Nurse         → calls doctor to re-prescribe
  Doctor        → updates chart
  Nurse         → re-contacts pharmacy
  Pharmacy      → dispenses (brand name ≠ generic name — wrong drug dispensed?)
  Nurse         → separately calls billing department
  Billing clerk → manually re-enters ICD-10 codes from printed note
  Billing clerk → can see full medication list including controlled substances (HIPAA risk)
  Patient       → waits, often 4–6 hours post-clinical-readiness
```

MCP (Model Context Protocol) solves this with a standardised, typed, RBAC-enforced tool call layer:

```
MCP workflow (< 1 second, automated):
  DischargeAgent.EHR.get_discharge_medications()           ← structured, not free text
  DischargeAgent.Pharmacy.check_stock()                    ← semantic name matching
  DischargeAgent.Pharmacy.get_alternative()                ← out-of-stock resolution
  DischargeAgent.EHR.get_billing_safe_summary()            ← PHI stripped at source
  DischargeAgent.Billing.generate_invoice()                ← billing never sees clinical notes
```

---

## Architecture

```
┌────────────────────────────────────────────────────────────────┐
│                 Discharge Coordination Agent                    │
│                   (MCP Client — role: discharge_coordinator)   │
└────────┬───────────────────┬───────────────────┬──────────────┘
         │ MCP calls         │ MCP calls          │ MCP calls
         ▼                   ▼                    ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  EHR MCP Server │ │ Pharmacy Server  │ │ Billing Server   │
│  (port 8001)    │ │ (port 8002)      │ │ (port 8003)      │
│                 │ │                  │ │                  │
│ Tools:          │ │ Tools:           │ │ Tools:           │
│ • discharge_meds│ │ • check_stock    │ │ • get_charges    │
│ • diagnosis_cod │ │ • get_alternative│ │ • get_insurance  │
│ • billing_safe  │ │ • get_price      │ │ • gen_invoice    │
│   _summary      │ │ • dispense_req   │ │                  │
│ [RBAC enforced] │ │ [RBAC enforced]  │ │ [RBAC enforced]  │
└─────────────────┘ └─────────────────┘ └─────────────────┘

PHI Boundary:
  EHR → Billing path uses get_billing_safe_summary()
  PHI fields blocked: name, DOB, MRN, discharge_note, attending_physician
  Billing receives: ICD-10 codes, LOS, ward — non-PHI operational data only
```

---

## RBAC Policy Matrix

| Role | EHR Clinical Notes | EHR Medications | EHR Diagnosis Codes | Pharmacy | Billing |
|------|-------------------|-----------------|--------------------|---------:|-------:|
| `discharge_coordinator` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `billing_agent` | ✗ BLOCKED | ✗ BLOCKED | ✓ | Price only | ✓ |
| `pharmacy_agent` | ✗ | ✓ | ✓ | ✓ | ✗ BLOCKED |
| `clinical_agent` | ✓ | ✓ | ✓ | Stock check | ✗ BLOCKED |

Every tool call validates the caller's role before returning data. Unauthorised calls raise `RBACError` and are logged to the telemetry feed.

---

## Quick Start

### Step 1: Install Dependencies

```bash
pip install fastmcp jsonschema numpy pandas matplotlib
# Optional for LLM integration:
pip install langchain-openai azure-ai-projects azure-identity
```

### Step 2: Generate Data

```bash
cd data/
python generate_dataset.py
```

### Step 3: Run the Servers

**Option A: FastMCP HTTP servers (production-style)**
```bash
pip install fastmcp

# Terminal 1:
python src/servers/mcp_servers.py --server ehr

# Terminal 2:
python src/servers/mcp_servers.py --server pharmacy

# Terminal 3:
python src/servers/mcp_servers.py --server billing
```

**Option B: Direct Python (no HTTP, for training)**
```python
from src.servers.mcp_servers import EHRServer, PharmacyServer, BillingServer

ehr = EHRServer()
meds = ehr.get_discharge_medications("PAT-001", caller_role="discharge_coordinator")
```

### Step 4: Run Discharge Agent

```bash
cd src/agents/
python discharge_agent.py PAT-001
python discharge_agent.py PAT-003    # biosimilar substitution
python discharge_agent.py PAT-006    # RBAC scope violation demo
```

### Step 5: Full Demo

```bash
cd demo/
python demo.py                    # All 4 scenarios + 2 limitations
python demo.py --scenario 3       # RBAC demo only
python demo.py --limitations      # Limitations only
```

### Step 6: Evaluation

```bash
cd evaluation/
python eval_dashboard.py
```

---

## Project Structure

```
mcpdischarge/
├── data/
│   ├── generate_dataset.py          ← Run this first
│   ├── ehr_patients.json            ← 6 patient records with discharge medications
│   ├── pharmacy_inventory.json      ← 17 drugs (4 out of stock, aliases table)
│   ├── billing_rate_cards.json      ← 15 charge codes
│   ├── insurance_contracts.json     ← 2 insurer contracts
│   ├── patient_insurance_map.json   ← Patient → insurer mappings
│   ├── icd10_billing_codes.json     ← ICD-10 → DRG billing mappings
│   └── rbac_policies.json           ← RBAC matrix (role → server → tools)
│
├── src/
│   ├── servers/
│   │   └── mcp_servers.py           ← EHRServer, PharmacyServer, BillingServer + FastMCP wrappers
│   └── agents/
│       └── discharge_agent.py       ← DischargeCoordinationAgent + WorkflowMetrics
│
├── evaluation/
│   ├── eval_dashboard.py
│   ├── 01_manual_vs_mcp.png
│   ├── 02_rbac_telemetry.png
│   └── 03_data_integrity.png
│
├── demo/
│   └── demo.py                      ← 4 scenarios + 2 limitations
│
├── configs/
│   ├── fastmcp_deployment.md        ← FastMCP HTTP server setup
│   ├── azure_foundry_mcp.md         ← Azure AI Foundry MCP integration
│   └── rbac_design.md               ← RBAC policy design guide
│
└── README.md
```

---

## Injected Challenge Patterns

| Pattern | Patient | Drug | Injected Issue |
|---------|---------|------|---------------|
| `[NAME_MISMATCH]` | PAT-001 | Dapagliflozin/Farxiga | EHR uses brand; Pharmacy stores generic |
| `[OUT_OF_STOCK]` | PAT-001 | Furosemide 40mg | Stock=0; MCP surfaces Torsemide as alternative |
| `[OUT_OF_STOCK]` | PAT-003 | Humira/Adalimumab | Brand out-of-stock; biosimilar Exemptia found |
| `[OUT_OF_STOCK]` | PAT-004 | Tafamidis/Vyndamax | Rare disease drug — no alternative; escalate |
| `[OUT_OF_STOCK]` | PAT-005 | Osimertinib/Tagrisso | Specialty drug — central pharmacy order |
| `[DATA_DRIFT]` | PAT-002 | Semaglutide 0.5mg | EHR maintenance dose vs formulary starter 0.25mg |
| `[SCOPE_VIOLATION]` | PAT-006 | Modafinil Schedule H | Billing must NOT see controlled substance details |
| `[PHI_BOUNDARY]` | All | — | 5 PHI fields blocked before billing invoice |

---

## The Three MCP Servers (Detailed)

### EHR Server

**PHI-sensitive tools (clinical roles only):**
```python
get_patient_discharge_summary(patient_id, caller_role)  # full clinical note
get_discharge_medications(patient_id, caller_role)       # medication list
```

**PHI-safe tools (all roles including billing):**
```python
get_diagnosis_codes(patient_id, caller_role)             # ICD-10 only
get_admission_info(patient_id, caller_role)              # LOS, ward, dates
get_billing_safe_summary(patient_id, caller_role)        # strips PHI fields
```

**PHI stripping (what gets blocked for billing):**
```python
PHI_FIELDS = {"name", "dob", "mrn", "discharge_note", "attending_physician"}
# Billing receives: patient_id, ward, admission_date, discharge_date, los_days, diagnosis_icd10
```

### Pharmacy Server

**Semantic name resolution:**
```python
# EHR says "Dapagliflozin" → Pharmacy stores as "Farxiga"
# MCP alias table: {"farxiga": "PH-001", "dapa": "PH-001", "sglt2 inhibitor": "PH-001"}
drug = _find_drug_by_name("Dapagliflozin")  # → PH-001 (Dapagliflozin)
drug = _find_drug_by_name("Humira")          # → PH-008 (Adalimumab, branded)
```

**Dose conflict detection:**
```python
# EHR prescribes Semaglutide 0.5mg, formulary standard is 0.25mg starter
if queried_dose not in formulary_dose:
    dose_conflict = True  # triggers clinical review alert
```

**Semantic match score:**
```python
# score = word overlap / max(len(ehr_words), len(pharm_words))
# score < 0.85 → NAME_MISMATCH alert even if drug found
semantic_drug_match_score("Humira", "Adalimumab")  # → 0.0 (no word overlap)
semantic_drug_match_score("Furosemide", "Furosemide")  # → 1.0 (exact)
```

### Billing Server

**Invoice generation (PHI guard):**
```python
def generate_invoice(patient_id, billing_safe_ehr, drug_costs, ...):
    # Verify PHI is stripped
    for phi_field in PHI_FIELDS:
        if phi_field in billing_safe_ehr:
            raise PermissionError(f"PHI field '{phi_field}' in billing payload")
    # Process invoice using only: ICD-10 + LOS + ward + drug prices
```

---

## MCP vs Traditional API Comparison

| Capability | Traditional REST APIs | MCP Protocol |
|-----------|----------------------|--------------|
| Schema discovery | Static Swagger docs | Dynamic tool manifests |
| Cross-department calls | Brittle point-to-point | Standardised tool calls |
| RBAC enforcement | App-layer (inconsistent) | Protocol-layer (guaranteed) |
| PHI boundary | Manual policy | Enforced per-tool |
| Drug name resolution | Hard-coded mapping | Semantic alias table |
| Out-of-stock handling | Manual pharmacy callback | Automatic alternative lookup |
| Telemetry | Custom logging | Built-in tool call trace |
| New department onboarding | New API integration | Register new MCP server |

---

## Evaluation Results (6 Patient Discharges)

| Patient | MCP Calls | Success | Alerts | PHI Blocked |
|---------|-----------|---------|--------|-------------|
| PAT-001 HFrEF | 16 | 100% | 1 | 5 fields |
| PAT-002 AKI | 11 | 100% | 1 | 5 fields |
| PAT-003 RA | 13 | 100% | 2 | 5 fields |
| PAT-004 ATTR | 14 | 100% | 2 | 5 fields |
| PAT-005 NSCLC | 9 | 100% | 1 | 5 fields |
| PAT-006 MS | 9 | 100% | 1 | 5 fields |

**Total: 72 MCP tool calls | 100% success | 15 manual handoffs replaced per discharge | ~45 minutes saved per case**

---

## FastMCP HTTP Deployment

See `configs/fastmcp_deployment.md`. Key pattern:

```python
from fastmcp import FastMCP

ehr_mcp = FastMCP("EHR-Server")

@ehr_mcp.tool()
def get_discharge_medications(patient_id: str, caller_role: str) -> dict:
    """Get discharge medication list from EHR."""
    return EHRServer().get_discharge_medications(patient_id, caller_role)

# Run as HTTP SSE server
ehr_mcp.run(transport="sse", host="0.0.0.0", port=8001)
```

Agent connects as MCP client:
```python
from mcp import ClientSession, StdioServerParameters
from mcp.client.sse import sse_client

async with sse_client("http://localhost:8001/sse") as (read, write):
    async with ClientSession(read, write) as session:
        result = await session.call_tool(
            "get_discharge_medications",
            {"patient_id": "PAT-001", "caller_role": "discharge_coordinator"}
        )
```

---

## Azure AI Foundry Integration

See `configs/azure_foundry_mcp.md`. MCP servers register as Foundry tools:

```python
from azure.ai.projects.models import McpToolDefinition

mcp_tools = [
    McpToolDefinition(server_url="http://ehr-server:8001/sse", name="ehr-server"),
    McpToolDefinition(server_url="http://pharmacy-server:8002/sse", name="pharmacy-server"),
    McpToolDefinition(server_url="http://billing-server:8003/sse", name="billing-server"),
]

agent = client.agents.create_agent(
    model="gpt-4o",
    name="DischargeCoordinationAgent",
    instructions=DISCHARGE_AGENT_SYSTEM_PROMPT,
    tools=[t.as_tool_definition() for t in mcp_tools],
)
```

*CitiusTech Gen AI & Agentic AI Training Program —  Project 5 of 5*


1. Product Vision (What your UI should feel like)

Think of it like:

👉 “Stripe Dashboard + AI Agent + Hospital System”

When a user opens your portal:

They don’t just click discharge
They see the system thinking, deciding, and acting in real-time
🧩 2. Core UI Experience (Step-by-Step)
🏁 Landing Page (Portal Entry)
What user sees:
Patient search / select
“Start Discharge” button
Recent discharges (history)
⚡ After Clicking “Start Discharge”

👉 Transition into a Live Execution Dashboard

🧠 3. THE MAIN SCREEN (This is your WOW factor)
🔥 A. Live Agent Trace Panel (MOST IMPORTANT)

Like a debug console + timeline

UI:
[10:21:01] 🔄 Calling EHR.get_patient()
[10:21:02] ✅ Patient data retrieved

[10:21:03] 🔄 Calling Pharmacy.check_stock(Metformin)
[10:21:03] ✅ In stock

[10:21:04] 🔄 Calling Pharmacy.check_stock(Atorvastatin)
[10:21:04] ❌ Out of stock

[10:21:05] 🤖 AI Suggestion:
"Atorvastatin is unavailable. Suggested alternative: Rosuvastatin (similar statin, higher potency)."

[10:21:06] 🔄 Calling Billing.generate_invoice()
[10:21:07] ✅ Invoice generated
🎨 How to implement:
Left panel → trace logs
Each step:
loading animation
success (green)
failure (red)
AI reasoning (purple)
💡 B. Smart Error + Suggestion Cards

When something fails:

Example UI Card:
⚠ Drug Out of Stock

Original: Atorvastatin  
Suggested: Rosuvastatin  

Reason:
- Same drug class (statin)
- Better LDL reduction
- Available in inventory

[ Accept Suggestion ]   [ Override ]

👉 This is your AI reasoning visibility → HUGE differentiator

📊 C. Right Panel → Structured Output
Sections:
🧾 Final Medications
💰 Invoice Breakdown
📋 Summary
🎥 D. Animated Flow (WOW Factor #2)

Show flow like:

EHR → Pharmacy → Billing

With animated arrows:

glowing when active
dim when idle

👉 Users SEE orchestration

🔍 E. Tool Call Inspector (WOW Factor #3)

Click any step → show:

Request:
{
  "drug": "Atorvastatin"
}

Response:
{
  "stock": 0
}

👉 This is developer-grade transparency

🧪 4. Failure Scenarios UI (CRITICAL)
Case 1: Drug Out of Stock
Show suggestion (already covered)
Case 2: No Alternative Found
❌ No substitute available

Suggested Actions:
- Notify doctor
- Delay discharge
Case 3: Billing Failure
⚠ Insurance validation failed

Reason:
Policy expired

[ Continue with self-pay ]
🎨 5. UI Stack (Recommended)
Frontend:
React
Tailwind CSS
Framer Motion (animations)
Backend:
FastAPI
WebSockets (for live trace updates)
⚙️ 6. How Data Flows (Important)
Use WebSockets:

Backend sends:

{
  "step": "pharmacy_check",
  "status": "failed",
  "message": "Out of stock",
  "suggestion": "Use Rosuvastatin"
}

Frontend:

updates UI in real-time
🧠 7. AI Layer (Upgrade your WOW factor)

Use:

LangChain

For:

generating explanations:
“why this drug?”
“why substitution?”
💎 8. Extra WOW Features (Highly Recommended)
🔥 1. “Explain This Decision” Button

User clicks → AI explains reasoning

🔥 2. Confidence Score
Substitution Confidence: 92%
🔥 3. Replay Mode
Re-run past discharge
Step-by-step playback