Skip to main content
Glama

MCPDischarge — Abteilungsübergreifende MCP-Interoperabilität

EHR × Apotheke × Abrechnung | RBAC | PHI-Grenze | FastMCP

CitiusTech Gen AI & Agentic AI Training — Projekt 5


Das Problem, das herkömmliche APIs nicht lösen können

Ein Patient ist bereit für die Entlassung. Daten müssen zwischen drei Abteilungen fließen, die noch nie ein gemeinsames Protokoll geteilt haben:

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) löst dies mit einer standardisierten, typisierten, RBAC-erzwungenen Tool-Call-Ebene:

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

Related MCP server: FHIR MCP Server

Architektur

┌────────────────────────────────────────────────────────────────┐
│                 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-Richtlinienmatrix

Rolle

EHR Klinische Notizen

EHR Medikamente

EHR Diagnosecodes

Apotheke

Abrechnung

discharge_coordinator

billing_agent

✗ BLOCKIERT

✗ BLOCKIERT

Nur Preis

pharmacy_agent

✗ BLOCKIERT

clinical_agent

Bestandsprüfung

✗ BLOCKIERT

Jeder Tool-Call validiert die Rolle des Aufrufers, bevor Daten zurückgegeben werden. Unbefugte Aufrufe lösen einen RBACError aus und werden im Telemetrie-Feed protokolliert.


Schnellstart

Schritt 1: Abhängigkeiten installieren

pip install -r requirements.txt

Schritt 2: Daten generieren

cd data/
python generate_dataset.py

Schritt 3: Die Server ausführen

FastMCP HTTP-Server (Produktionsstil, erforderlich für den asynchronen MCP-Agenten):

# 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

Oder alle drei in einem Prozess ausführen (startet 3 Hintergrund-Threads):

python src/servers/mcp_servers.py --all

Direktes Python (kein HTTP, nur für Schulungszwecke):

from src.servers.ehr_server import EHRServer

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

Schritt 4: Entlassungs-Agent ausführen

python src/agents/discharge_agent.py PAT-001
python src/agents/discharge_agent.py PAT-003

Schritt 5: Vollständige Demo

python demo/demo.py               # Runs 4 scenarios
python demo/demo.py --scenario 3  # RBAC violation only

Chat-UI (React)

Dieses Repo enthält ein einfaches React-Chat-Frontend, das ein leichtgewichtiges FastAPI-Gateway aufruft, welches wiederum die MCP-Server aufruft.

1) MCP-Server starten (SSE)

python src/servers/mcp_servers.py --all

2) Chat-Gateway-API starten (Port 8000)

copy .env.example .env   # then fill in Azure OpenAI settings (optional)
python -m uvicorn src.gateway.chat_gateway:app --reload --port 8000

3) React-Dev-Server starten (Port 5173)

cd frontend
npm install
npm run dev

Schritt 6: Evaluierung

cd evaluation/
python eval_dashboard.py

Hinweis: Die Evaluierung erfordert laufende MCP-Server (Schritt 3), da sie den asynchronen MCP-Agenten über SSE aufruft.


Projektstruktur

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

Injizierte Herausforderungsmuster

Muster

Patient

Medikament

Injiziertes Problem

[NAME_MISMATCH]

PAT-001

Dapagliflozin/Farxiga

EHR verwendet Marke; Apotheke speichert Generikum

[OUT_OF_STOCK]

PAT-001

Furosemide 40mg

Bestand=0; MCP zeigt Torsemide als Alternative an

[OUT_OF_STOCK]

PAT-003

Humira/Adalimumab

Marke nicht vorrätig; Biosimilar Exemptia gefunden

[OUT_OF_STOCK]

PAT-004

Tafamidis/Vyndamax

Medikament für seltene Krankheiten — keine Alternative; eskalieren

[OUT_OF_STOCK]

PAT-005

Osimertinib/Tagrisso

Spezialmedikament — Bestellung bei Zentralapotheke

[DATA_DRIFT]

PAT-002

Semaglutide 0.5mg

EHR-Erhaltungsdosis vs. Formular-Startdosis 0.25mg

[SCOPE_VIOLATION]

PAT-006

Modafinil Schedule H

Abrechnung darf KEINE Details zu kontrollierten Substanzen sehen

[PHI_BOUNDARY]

Alle

5 PHI-Felder vor der Abrechnungsrechnung blockiert


Die drei MCP-Server (detailliert)

EHR-Server

PHI-sensible Tools (nur klinische Rollen):

get_patient_discharge_summary(patient_id, caller_role)  # full clinical note
get_discharge_medications(patient_id, caller_role)       # medication list

PHI-sichere Tools (alle Rollen einschließlich Abrechnung):

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-Bereinigung (was für die Abrechnung blockiert wird):

PHI_FIELDS = {"name", "dob", "mrn", "discharge_note", "attending_physician"}
# Billing receives: patient_id, ward, admission_date, discharge_date, los_days, diagnosis_icd10

Apotheken-Server

Semantische Namensauflösung:

# 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)

Erkennung von Dosierungskonflikten:

# 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

Semantischer Übereinstimmungs-Score:

# 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)

Abrechnungs-Server

Rechnungserstellung (PHI-Schutz):

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

Vergleich: MCP vs. herkömmliche API

Fähigkeit

Herkömmliche REST-APIs

MCP-Protokoll

Schema-Erkennung

Statische Swagger-Docs

Dynamische Tool-Manifeste

Abteilungsübergreifende Aufrufe

Spröde Punkt-zu-Punkt

Standardisierte Tool-Aufrufe

RBAC-Durchsetzung

App-Ebene (inkonsistent)

Protokoll-Ebene (garantiert)

PHI-Grenze

Manuelle Richtlinie

Pro Tool erzwungen

Medikamentennamensauflösung

Hartkodierte Zuordnung

Semantische Alias-Tabelle

Umgang mit Nicht-Verfügbarkeit

Manueller Apotheken-Rückruf

Automatische Alternativsuche

Telemetrie

Benutzerdefinierte Protokollierung

Integrierte Tool-Call-Verfolgung

Onboarding neuer Abteilungen

Neue API-Integration

Neuen MCP-Server registrieren


Evaluierungsergebnisse (6 Patientenentlassungen)

Patient

MCP-Aufrufe

Erfolg

Warnungen

PHI blockiert

PAT-001 HFrEF

16

100%

1

5 Felder

PAT-002 AKI

11

100%

1

5 Felder

PAT-003 RA

13

100%

2

5 Felder

PAT-004 ATTR

14

100%

2

5 Felder

PAT-005 NSCLC

9

100%

1

5 Felder

PAT-006 MS

9

100%

1

5 Felder

Gesamt: 72 MCP-Tool-Aufrufe | 100% Erfolg | 15 manuelle Übergaben pro Entlassung ersetzt | ~45 Minuten Ersparnis pro Fall


FastMCP HTTP-Bereitstellung

Siehe configs/fastmcp_deployment.md. Schlüsselmuster:

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 verbindet sich als MCP-Client:

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

Siehe configs/azure_foundry_mcp.md. MCP-Server registrieren sich als Foundry-Tools:

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 — Projekt 5 von 5

Related MCP Connectors

Related MCP Servers