# ============================================================================= # Operation Ollama-First v5.0 / Phase 10 — MCP 自建 Stack # ============================================================================= # 部署位置:188 主機,與既有 momo-pro 容器共存 # 啟動方式:docker compose -f docker-compose.mcp.yml up -d # 啟用前置: # 1. .env 加 TAVILY_API_KEY(Tavily 1000 free credits/月) # 2. .env 加 EXA_API_KEY(Exa 備援) # 3. 確認 188 防火牆 allow 172.x docker bridge 對外(firecrawl 抓網頁) # # 4 個 MCP server 對應 ADR-031 (Phase 10) 規格: # - postgres-mcp: Claude 直連 momo_pro DB(read-only RBAC) # - mcp-omnisearch: 取代 Gemini Grounding,Tavily 主 + Exa 備 # - firecrawl-self: 自建爬蟲(含 Owen v5.0 護欄 #2 mem_limit:2g + chrome-reaper) # - filesystem-mcp: 跨主機檔案操作 # # 護欄一覽(ADR-033): # #2 Firecrawl mem_limit:2g + chrome-reaper sidecar + 1.8GB 告警 # # 部署後驗收(給統帥): # 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 # ============================================================================= services: # ───────────────────────────────────────────────────────────────────────── # postgres-mcp: Claude 直連 momo_pro DB(read-only) # ───────────────────────────────────────────────────────────────────────── postgres-mcp: image: mcp/postgres:latest container_name: momo-mcp-postgres restart: unless-stopped init: true ports: - "127.0.0.1:3001:3000" # 僅 localhost 暴露(避免外網直連 DB) environment: - POSTGRES_HOST=momo-db - POSTGRES_PORT=5432 - POSTGRES_USER=mcp_readonly # 須在 momo-db 預先建 read-only role - POSTGRES_PASSWORD=${MCP_POSTGRES_PASSWORD} - POSTGRES_DB=momo_pro # RBAC:限制 SELECT 到熱表 - ALLOWED_TABLES=ai_insights,ai_calls,mcp_calls,daily_sales_snapshot,competitor_prices,products networks: - momo-shared deploy: resources: limits: memory: 256m healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] interval: 30s timeout: 5s retries: 3 # ───────────────────────────────────────────────────────────────────────── # mcp-omnisearch: 統一搜尋(Tavily 主 + Exa 備) # ───────────────────────────────────────────────────────────────────────── mcp-omnisearch: image: ghcr.io/spences10/mcp-omnisearch:latest container_name: momo-mcp-omnisearch restart: unless-stopped init: true ports: - "127.0.0.1:3003:3000" environment: # 排除 Brave(2026-02 已取消免費 tier) - TAVILY_API_KEY=${TAVILY_API_KEY} # 1000 free credits/月(主) - EXA_API_KEY=${EXA_API_KEY} # 1000 free credits/月(備援) - SEARCH_PROVIDER_ORDER=tavily,exa # fallback 順序 deploy: resources: limits: memory: 512m healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] interval: 30s timeout: 5s retries: 3 # ───────────────────────────────────────────────────────────────────────── # firecrawl-self: 自建爬蟲(含 Owen v5.0 護欄 #2) # ───────────────────────────────────────────────────────────────────────── firecrawl-self: image: firecrawl/firecrawl:latest container_name: momo-mcp-firecrawl restart: unless-stopped init: true ports: - "127.0.0.1:3002:3002" environment: - REDIS_URL=redis://firecrawl-redis:6379 - PLAYWRIGHT_MICROSERVICE_URL=http://firecrawl-playwright:3000 - PLAYWRIGHT_BROWSER_POOL_MAX=3 # ⭐ 護欄 #2:瀏覽器池上限 - SCRAPE_TIMEOUT_MS=30000 - BULL_AUTH_KEY=${FIRECRAWL_AUTH_KEY:-momo-internal-only} depends_on: - firecrawl-redis - firecrawl-playwright deploy: resources: limits: memory: 2g # ⭐ Owen v5.0 護欄 #2 硬上限 cpus: '1.5' healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3002/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s firecrawl-redis: image: redis:7-alpine container_name: momo-mcp-firecrawl-redis restart: unless-stopped deploy: resources: limits: memory: 128m firecrawl-playwright: image: firecrawl/playwright:latest container_name: momo-mcp-firecrawl-playwright restart: unless-stopped deploy: resources: limits: memory: 1.5g cpus: '1.0' # ───────────────────────────────────────────────────────────────────────── # chrome-reaper: 護欄 #2 — 每小時清 Chrome 殘留 # ───────────────────────────────────────────────────────────────────────── chrome-reaper: image: alpine:3 container_name: momo-mcp-chrome-reaper restart: unless-stopped command: | sh -c ' apk add --no-cache docker-cli; while true; do echo "[reaper] $(date) cleaning Chrome zombies..."; docker exec momo-mcp-firecrawl-playwright \ sh -c "pkill -f \"chrome.*--type=zygote\" 2>/dev/null || true; pkill -f \"chrome.*--type=renderer\" 2>/dev/null || true" \ 2>/dev/null || true; sleep 3600; done ' volumes: - /var/run/docker.sock:/var/run/docker.sock:ro # ───────────────────────────────────────────────────────────────────────── # filesystem-mcp: 跨主機檔案操作(限本地) # ───────────────────────────────────────────────────────────────────────── filesystem-mcp: image: mcp/filesystem:latest container_name: momo-mcp-filesystem restart: unless-stopped init: true ports: - "127.0.0.1:3004:3000" environment: - ALLOWED_PATHS=/data,/logs # 限制存取範圍 volumes: - ./data:/data:ro # ⚠️ ro 唯讀,避免 LLM 改檔 - ./logs:/logs:ro deploy: resources: limits: memory: 128m # ───────────────────────────────────────────────────────────────────────────── # 與既有 momo-pro 共用 network(讓 postgres-mcp 連 momo-db) # ───────────────────────────────────────────────────────────────────────────── networks: momo-shared: external: true name: momo-pro_default # 既有 docker-compose.yml 的 default network