Local-Safe-EMR-MCP
by ISLAB-rui
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 可能較慢,建議課前先預熱模型。
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues