Skip to main content
Glama
README.md
# Local-Safe-EMR-MCP

Local-Safe-EMR-MCP 是一個教學用 demo,展示如何用 MCP server 在本地端讀取含個資的醫療病例,並在資料回傳給外部 agent 前,先呼叫本地或私有 LLM API 完成去識別化。

這個專案刻意保持簡單:資料庫只有病例 ID 與一段病例文字;MCP server 只提供查詢工具;去識別化邏輯獨立放在 `deidentify.py`;另外提供一個 CLI debug proxy,用來觀察 MCP HTTP/JSON-RPC 通訊。

## 架構

```text
Antigravity / MCP Client
        |
        | MCP over HTTP
        v
server.py  (Local MCP server)
        |
        | read case_id/raw_note
        v
SQLite demo database
        |
        | raw_note stays local
        v
deidentify.py
        |
        | OpenAI-compatible API
        v
Local/private LLM server
        |
        | deidentified_text
        v
MCP response to client
```

核心安全概念:

- 原始病例只存在本機 SQLite。
- MCP client 只能透過 tool 查詢病例。
- `get_deidentified_case` 會先呼叫 `deidentify.py` 去識別化。
- MCP response 只回傳脫敏文字。
- demo 不保存 mapping,避免流程變複雜。

## 專案檔案

```text
Local-Safe-EMR-MCP/
├─ server.py            # FastMCP server,提供 MCP tools
├─ deidentify.py        # 呼叫 OpenAI-compatible LLM 做去識別化
├─ seed_demo_data.py    # 建立 SQLite demo 病例資料
├─ mcp_debug_proxy.py   # CLI reverse proxy,用來觀察 MCP 通訊
├─ .env.example         # 環境變數範例
├─ pyproject.toml       # Python 依賴
└─ data/
   └─ hospital_demo.db  # SQLite demo database
```

## Demo 資料庫

資料庫只有一張表:

```sql
CREATE TABLE cases (
    case_id TEXT PRIMARY KEY,
    raw_note TEXT NOT NULL
);
```

目前 seed script 會建立三筆病例:

```text
CASE-001: 慢性腎臟病與用藥風險
CASE-002: 糖尿病、高血壓與血脂控制
CASE-003: 胸悶與急性冠心症風險
```

## 設定

複製環境變數範例:

```powershell
Copy-Item .env.example .env
```

範例設定:

```env
DATABASE_PATH=data/hospital_demo.db
MCP_TRANSPORT=http
MCP_HOST=127.0.0.1
MCP_PORT=9000

MCP_PROXY_HOST=127.0.0.1
MCP_PROXY_PORT=9001
MCP_PROXY_TARGET=http://127.0.0.1:9000
MCP_PROXY_MAX_PRINT_CHARS=4000

LLM_BASE_URL=http://192.168.1.107:8000/v1
LLM_API_KEY=EMPTY
LLM_MODEL=google/gemma-4-31B-it
```

`LLM_BASE_URL` 必須是 OpenAI-compatible API,例如 vLLM:

```powershell
vllm serve google/gemma-4-31B-it --host 0.0.0.0 --port 8000 --dtype bfloat16 --gpu-memory-utilization 0.85
```

## 建立 Demo 資料

```powershell
cd C:\Users\rui\Desktop\mcp課程\Local-Safe-EMR-MCP
.\.venv\Scripts\python.exe .\seed_demo_data.py
```

## 啟動 MCP Server

```powershell
cd C:\Users\rui\Desktop\mcp課程\Local-Safe-EMR-MCP
.\.venv\Scripts\python.exe .\server.py
```

預設 MCP endpoint:

```text
http://127.0.0.1:9000/mcp
```

## MCP Tools

### `list_cases`

列出可查詢的病例 ID。

回傳範例:

```json
{
  "case_ids": ["CASE-001", "CASE-002", "CASE-003"]
}
```

### `get_deidentified_case`

用病例 ID 查詢病例。MCP server 會讀取本地原始病例,呼叫 `deidentify.py` 去識別化,再回傳脫敏後文字。

參數:

```json
{
  "case_id": "CASE-001"
}
```

回傳範例:

```json
{
  "ok": true,
  "case_id": "CASE-001",
  "deidentified_text": "病患PATIENT_001,身分證 NATIONAL_ID_001,電話 PHONE_001...",
  "note": "Raw EMR text stayed local. Only de-identified text is returned."
}
```

## Antigravity MCP 設定

一般使用時,直接連 MCP server:

```json
{
  "mcpServers": {
    "local-safe-emr": {
      "serverUrl": "http://127.0.0.1:9000/mcp"
    }
  }
}
```

如果要觀察 MCP 通訊,改連 debug proxy:

```json
{
  "mcpServers": {
    "local-safe-emr": {
      "serverUrl": "http://127.0.0.1:9001/mcp"
    }
  }
}
```

## MCP 通訊教學 Proxy

`mcp_debug_proxy.py` 是一個 CLI reverse proxy,專門用來觀察 MCP over HTTP 的 JSON-RPC 通訊。

流向:

```text
Antigravity -> http://127.0.0.1:9001/mcp
mcp_debug_proxy.py -> http://127.0.0.1:9000/mcp
server.py
```

啟動:

```powershell
.\.venv\Scripts\python.exe .\mcp_debug_proxy.py --listen 9001 --target http://127.0.0.1:9000
```

預設輸出是精簡模式:

- 不印 HTTP headers
- 不印 SSE ping
- JSON 單行顯示
- 只顯示 MCP request/response body

範例輸出:

```text
>>> POST /mcp
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}}

<<< 200 /mcp
{"jsonrpc":"2.0","id":3,"result":{"tools":[...]}}
```

常用參數:

```powershell
# 多行 JSON
.\.venv\Scripts\python.exe .\mcp_debug_proxy.py --pretty

# 顯示 headers
.\.venv\Scripts\python.exe .\mcp_debug_proxy.py --verbose

# 顯示 SSE ping
.\.venv\Scripts\python.exe .\mcp_debug_proxy.py --show-ping
```

## MCP 通訊重點

MCP message 本身是 JSON-RPC 2.0:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_deidentified_case",
    "arguments": {
      "case_id": "CASE-001"
    }
  }
}
```

HTTP transport 則會使用:

```text
POST /mcp
GET /mcp
Mcp-Session-Id
text/event-stream
```

常見流程:

```text
initialize
notifications/initialized
tools/list
tools/call
```

因為 MCP HTTP transport 會使用 streaming/SSE,連線可能維持一段時間,不像一般 REST API 每次都立即結束。

## 教學建議

一般 demo:

```text
Antigravity -> 127.0.0.1:9000/mcp -> MCP server
```

通訊觀察 demo:

```text
Antigravity -> 127.0.0.1:9001/mcp -> debug proxy -> 127.0.0.1:9000/mcp
```

建議上課流程:

1. 啟動 vLLM。
2. 啟動 MCP server。
3. 讓 Antigravity 直接連 `9000`,確認 tools 可用。
4. 啟動 `mcp_debug_proxy.py`。
5. 將 Antigravity 改連 `9001`。
6. 呼叫 `list_cases` 與 `get_deidentified_case`,觀察 JSON-RPC。

## 注意事項

- 這是教學 demo,不是正式醫療系統。
- SQLite 內含模擬個資,請勿放真實病人資料。
- 本專案不保存去識別化 mapping。
- 若 LLM 回應慢,`deidentify.py` 的 HTTP timeout 目前設定為 180 秒。
- 第一次呼叫 vLLM 可能較慢,建議課前先預熱模型。