# ADR-031: MCP 自建 Stack — postgres + omnisearch + firecrawl + filesystem

- **Status**: Accepted (待 188 deploy 後 Active)
- **Date**: 2026-05-04
- **Decision Maker**: 統帥
- **Author**: Operation Ollama-First v5.0 / Phase 10
- **Related**: ADR-028（LLM 路由統一準則）、ADR-032（RAG 自主學習）、ADR-033（三護欄）

---

## Context

戰役 v4.0 階段就提案 MCP 自建（取代 Gemini Grounding 唯一聯網能力），但 Phase 10 因 hook 阻擋 SSH 188 deploy，先完成本地 docker-compose.mcp.yml + ADR 設計，待統帥手動 `ssh wooo@110 → ssh ollama@188 → docker compose -f docker-compose.mcp.yml up -d` 啟用。

**為何要自建 MCP？**
- mcp_collector_service.py 目前 100% 走 Gemini 2.0 Flash Grounding（鎖定 7 場景之一）
- 若 Gemini API 配額爆 / 政策變更 → 即時情報唯一通路斷
- 多供應商策略（ADR-030）需要 Tavily / Exa 作為 grounding 備援
- Claude Code 直連 momo_pro DB（read-only RBAC）能加速統帥日常 SQL 查詢

---

## Decision — 4 + 3 容器架構

### 4 個 MCP server（核心）

| Server | port | 用途 | 取代誰 |
|---|---|---|---|
| `postgres-mcp` | 127.0.0.1:3001 | Claude 直連 momo_pro DB（read-only） | 統帥手動 SQL |
| `mcp-omnisearch` | 127.0.0.1:3003 | Tavily + Exa 統一搜尋 | Gemini Grounding |
| `firecrawl-self` | 127.0.0.1:3002 | 自建爬蟲（含護欄 #2） | 部分自寫 BeautifulSoup |
| `filesystem-mcp` | 127.0.0.1:3004 | 跨主機檔案操作（read-only） | SSH 跳板手動 cat |

### 3 個輔助容器

| Container | 用途 |
|---|---|
| `firecrawl-redis` | Firecrawl job queue |
| `firecrawl-playwright` | Browser pool（mem 1.5g）|
| `chrome-reaper` | 每小時清 Chrome 殘留（護欄 #2）|

### 護欄 #2 落地（Owen v5.0 鐵律）

```yaml
firecrawl-self:
  deploy:
    resources:
      limits:
        memory: 2g          # ⭐ 硬上限
        cpus: '1.5'
  environment:
    - PLAYWRIGHT_BROWSER_POOL_MAX=3
    - SCRAPE_TIMEOUT_MS=30000
  healthcheck:
    interval: 30s
    start_period: 60s

chrome-reaper:
  command: 每小時 pkill chrome zombie processes
```

### 安全設計

- **僅 127.0.0.1 暴露**：避免外網直連 DB / 爬蟲服務
- **read-only volume mount**：filesystem-mcp 只能讀
- **postgres-mcp RBAC**：mcp_readonly role 限 SELECT 到熱表（ai_insights / ai_calls / mcp_calls / daily_sales_snapshot / competitor_prices / products）
- **Tavily/Exa API key 走 env**：不寫死 docker-compose

### 取代 Gemini Grounding 的 fallback 鏈（mcp_collector_service.py 改造）

```
舊：
  Gemini 2.0 Grounding → Gemini 1.5 → Ollama → 靜態

新：
  mcp-omnisearch (Tavily) → omnisearch (Exa) → 
    全失敗 → Gemini 2.0 Grounding (保留為 L4)
    → Gemini 1.5 → Ollama → 靜態
```

預期 70%+ 流量走免費 Tavily，省 ~70% Gemini Grounding 成本。

---

## Alternatives Considered

| 方案 | 否決理由 |
|---|---|
| **A. 維持 Gemini Grounding 唯一** | 單供應商風險（已是 ADR-030 否決理由）|
| **B. 用 Brave Search API** | 2026-02 取消免費 tier（A2 web research 紅燈）|
| **C. 純 Tavily 不要 Firecrawl** | Firecrawl 對 SPA 動態頁更強（蝦皮等 JS-heavy 站）|
| **D. Firecrawl 不限資源** | 188 主機跑 5+ project，OOM 連鎖（reference_188_multi_project）|
| **E. 用 SaaS Firecrawl Cloud** | 成本（自建免費）+ 資料外流風險 |

---

## Consequences

### 正面（5）
1. **Gemini Grounding 多供應商**：Tavily 主 + Exa 備援，月省 ~70% grounding 成本
2. **Claude 直連 DB**：統帥日常 SQL 查詢可走 MCP 介面（read-only 安全）
3. **爬蟲自主性**：Firecrawl 取代部分自寫爬蟲，SPA 反爬蟲更強
4. **零外部 SaaS 依賴**：全部自建在 188（Tavily/Exa 是 API 不是 SaaS）
5. **Owen 護欄 #2 落地**：mem_limit + chrome-reaper 防 OOM

### 負面（3）
1. **188 主機資源占用 +4-5GB RAM**（Firecrawl 2g + Playwright 1.5g + 其他）
2. **Tavily/Exa API key 維護**：申請 + 月配額追蹤
3. **mcp_collector_service.py 重構工作量**：~200 行改動

### 風險（3）
1. **Firecrawl OOM 連鎖**：mem_limit 2g 觸發 OOM kill → mitigate by healthcheck + restart
2. **Tavily 免費額度（1000/月）爆**：mitigate by Exa 備援 + Gemini Grounding L4
3. **postgres-mcp RBAC 設置失誤**：mitigate by mcp_readonly role 預先建立 + only SELECT

---

## Verification

### V1：健康檢查
```bash
curl http://localhost:3001/health    # postgres-mcp
curl http://localhost:3002/health    # firecrawl
curl http://localhost:3003/health    # omnisearch
curl http://localhost:3004/health    # filesystem
# 全部期待 200 OK
```

### V2：Firecrawl 資源
```bash
ssh ollama@192.168.0.188 'docker stats momo-mcp-firecrawl --no-stream'
# 期望 < 1.8GB（mem_limit 2GB 90%）
```

### V3：Tavily 配額
```sql
SELECT COUNT(*) FROM mcp_calls
WHERE server = 'omnisearch' AND tool = 'tavily_search'
  AND called_at > date_trunc('month', NOW());
-- 期望 < 1000（免費額度上限）
```

---

## Migration Plan

| 步驟 | 工作 | 狀態 |
|---|---|---|
| 10.1 | docker-compose.mcp.yml 寫完 | ✅ 本 commit |
| 10.2 | ADR-031 撰寫 | ✅ 本 commit |
| 10.3 | 統帥申請 Tavily + Exa API key | ⏳ 待 |
| 10.4 | momo-db 建 mcp_readonly role + GRANT SELECT | ⏳ 待 |
| 10.5 | 188 deploy: docker compose -f docker-compose.mcp.yml up -d | ⏳ 待 |
| 10.6 | mcp_collector_service.py 改用 mcp-omnisearch（取代 Gemini Grounding 主路徑）| ⏳ Phase 10.5 |
| 10.7 | 健康檢查 + Firecrawl mem 監控告警 | ⏳ Phase 10.5 |

---

## References

- `docker-compose.mcp.yml`（本 commit）
- ADR-028（LLM 路由）/ ADR-030（多供應商）/ ADR-033（護欄）
- `services/mcp_collector_service.py`（將改造）
- A2 web research 報告：`docs/phase0_research_report_20260503.md`
- mcp-omnisearch GitHub：https://github.com/spences10/mcp-omnisearch
