# Skill 07: Tool Integration Expert
# MCP Tool 整合專家

> **管轄範圍**: MCP Bridge, 外部系統連接, RAG 向量化
> **觸發條件**: 修改 `plugins/mcp/`, `services/*_tool.py`, 向量資料庫

---

## 文件資訊

| 欄位 | 值 |
|------|-----|
| **版本** | v1.3 |
| **建立日期** | 2026-03-25 23:30 (台北) |
| **建立者** | Claude Code |
| **最後修改** | 2026-03-26 18:00 (台北) |
| **修改者** | Claude Code |

### 變更紀錄

| 版本 | 日期 | 執行者 | 變更內容 |
|------|------|--------|----------|
| v1.3 | 2026-03-26 18:00 | Claude Code | 新增 Grafana MCP (#83) + SignOz query_logs |
| v1.2 | 2026-03-26 23:30 | Claude Code | 新增 Filesystem MCP Tool (#82 已完成) |
| v1.1 | 2026-03-26 14:20 | Claude Code | 更新 MCP Tool 狀態 (#79/#80/#81 已完成) |
| v1.0 | 2026-03-25 23:30 | Claude Code | 初始建立 - Phase 13.2 Tool 整合專家 |

---

## 核心職責

Phase 13.2 Tool 實作 (P0 最優先):
- #79 SignOz MCP Tool (Trace/Logs/Metrics 查詢)
- #80 Kubernetes MCP Tool (真實 kubectl 執行)
- #81 PostgreSQL MCP Tool (歷史模式分析)
- #84 維運手冊 RAG Tool (Markdown 向量化)

---

## MCP Tool 狀態總覽

| Tool | 狀態 | 位置 | 工作項 |
|------|------|------|--------|
| **Kubernetes** | ✅ 真實 | `providers/k8s_provider.py` | #80 ✅ |
| **Database** | ✅ 真實 | `providers/database_provider.py` | #81 ✅ |
| **SignOz** | ✅ 真實 | `providers/signoz_provider.py` | #79 ✅ |
| **Filesystem** | ✅ 真實 | `providers/filesystem_provider.py` | #82 ✅ |
| **Grafana** | ✅ 真實 | `providers/grafana_provider.py` | #83 ✅ |
| 維運手冊 RAG | 📋 設計完成 | - | #84 (待實作) |

### 已完成 Tool 功能

**SignOz MCP (#79)**:
- `gold_metrics`: RPS, Error Rate, P99 Latency, AI Success Rate
- `trace_url`: 生成 Trace 查詢 URL
- `system_metrics`: 系統層級指標
- `query_logs`: 日誌查詢 (服務/級別/搜尋) - Phase 13.1 #77
- `error_logs_summary`: 錯誤摘要統計 - Phase 13.1 #77

**Grafana MCP (#83)**:
- `list_dashboards`: 列出儀表板 (支援過濾)
- `get_dashboard`: 取得儀表板詳情 (UID)
- `get_panel_data`: 查詢面板數據
- `generate_dashboard_url`: 生成可分享 URL

**PostgreSQL MCP (#81)**:
- `list_approvals`: 依狀態/incident 過濾
- `get_approval`: 取得單筆詳情
- `list_incidents`: 列出活躍事件
- `list_timeline`: 時間線事件

**Filesystem MCP (#82)**:
- `read_file`: 讀取文件內容 (支援 tail_lines)
- `list_directory`: 列出目錄 (支援 glob pattern)
- `search_in_file`: 搜尋文件內容 (正則表達式)

**Kubernetes MCP (#80)**:
- `kubectl_get`: 整合真實 ActionExecutor
- `kubectl_restart`: Pod/Deployment 重啟

---

## SignOz API 整合

### 端點

```
ClickHouse HTTP API: http://192.168.0.188:8123
SignOz Query API: http://192.168.0.188:3301/api/v3
```

### 查詢範例

```python
async def query_signoz_traces(
    service_name: str,
    start_ns: int,
    end_ns: int,
) -> list[dict]:
    """查詢 SignOz Traces

    Args:
        service_name: 服務名稱
        start_ns: 起始時間 (nanoseconds)
        end_ns: 結束時間 (nanoseconds)

    Warning:
        SignOz API 有速率限制，避免高頻查詢
    """
    query = f"""
    SELECT timestamp, traceID, serviceName, name, durationNano
    FROM signoz_traces.distributed_signoz_index_v3
    WHERE serviceName = '{service_name}'
      AND timestamp >= toDateTime64({start_ns}/1e9, 9)
      AND timestamp <= toDateTime64({end_ns}/1e9, 9)
    ORDER BY timestamp DESC
    LIMIT 100
    """
    # 調用 ClickHouse HTTP API...
```

### SignOz MCP Tool 介面

```python
class SignOzMCPTool:
    """SignOz 監控查詢 MCP Tool"""

    async def query_traces(
        self,
        service_name: str,
        start_time: datetime,
        end_time: datetime,
        limit: int = 100,
    ) -> MCPToolResult:
        """查詢服務 Traces"""

    async def query_logs(
        self,
        service_name: str,
        level: str | None = None,
        keyword: str | None = None,
    ) -> MCPToolResult:
        """查詢服務 Logs"""

    async def query_metrics(
        self,
        metric_name: str,
        aggregation: str = "avg",
    ) -> MCPToolResult:
        """查詢 Metrics"""
```

---

## Kubernetes MCP 實作

### 從 Mock 升級為真實執行

```python
# ✅ 正確: 真實 kubectl 執行
async def kubectl_execute(
    action: str,
    resource: str,
    namespace: str,
    name: str | None = None,
) -> MCPToolResult:
    """執行 kubectl 命令

    Warning:
        所有命令必須帶 -n namespace
        禁止操作 kube-system, default
    """
    # 驗證 namespace 白名單
    if namespace not in ALLOWED_NAMESPACES:
        return MCPToolResult(success=False, error="Namespace not allowed")

    cmd = ["kubectl", action, resource, "-n", namespace]
    if name:
        cmd.append(name)

    result = await asyncio.create_subprocess_exec(*cmd, ...)
```

### Namespace 白名單

```python
ALLOWED_NAMESPACES = ["awoooi-prod", "awoooi-dev"]

# ❌ 絕對禁止
FORBIDDEN_NAMESPACES = ["kube-system", "default", "kube-public"]
```

### 危險操作攔截

```python
FORBIDDEN_KUBECTL_COMMANDS = [
    "delete namespace",
    "delete -A",
    "delete --all",
    "drain",
    "cordon",
    "taint",
]
```

---

## PostgreSQL MCP Tool

### 查詢歷史模式分析

```python
class PostgresMCPTool:
    """PostgreSQL 歷史查詢 MCP Tool"""

    async def query_incident_patterns(
        self,
        alert_name: str,
        days: int = 30,
    ) -> MCPToolResult:
        """查詢告警歷史模式

        用途: 分析相同告警的歷史處理方式，提取 Playbook
        """
        query = """
        SELECT
            alert_name,
            COUNT(*) as occurrence_count,
            AVG(resolution_time_seconds) as avg_resolution_time,
            mode() WITHIN GROUP (ORDER BY resolution_action) as common_action
        FROM incident_records
        WHERE alert_name = :alert_name
          AND created_at > NOW() - INTERVAL ':days days'
        GROUP BY alert_name
        """

    async def query_approval_success_rate(
        self,
        action_type: str,
    ) -> MCPToolResult:
        """查詢特定操作的審批成功率

        用途: 為 Trust Engine 提供歷史數據
        """
```

---

## 維運手冊 RAG Tool

### 向量化流程

```
docs/*.md → 分段 (chunk) → 嵌入 (embedding) → 存入 Vector DB
```

### 配置

```python
RAG_CONFIG = {
    "chunk_size": 500,        # 每段字數
    "chunk_overlap": 50,      # 重疊字數
    "embedding_model": "bge-m3",  # 多語言嵌入
    "vector_db": "redis",     # Redis Stack Vector
    "index_name": "idx:runbooks",
}
```

### 查詢流程

```python
async def search_runbook(query: str, top_k: int = 5) -> list[str]:
    """搜尋維運手冊相關段落

    Args:
        query: 搜尋關鍵字 (自然語言)
        top_k: 返回最相關的 N 個段落

    Returns:
        相關段落列表
    """
    # 1. 嵌入查詢
    query_embedding = await embed(query)

    # 2. 向量搜尋
    results = await redis_client.ft_search(
        "idx:runbooks",
        f"*=>[KNN {top_k} @embedding $vec AS score]",
        query_params={"vec": query_embedding},
    )

    return [r.content for r in results]
```

### 文檔索引清單

```python
RUNBOOK_SOURCES = [
    "docs/operations/*.md",
    "docs/troubleshooting/*.md",
    "docs/adr/*.md",
    ".agents/skills/*.md",
]
```

---

## Tool 實作鐵律

### 1. Privacy Shield 必須套用

```python
# ✅ 正確: 所有 Tool 調用經過脫敏
async def call_tool(params: dict, redaction_mapping: dict):
    # Rehydration 還原敏感資料
    params = rehydrator.unredact(params, redaction_mapping)

    # 驗證無殘留標籤
    is_clean, remaining = rehydrator.validate_no_labels(params)
    if not is_clean:
        raise SecurityError(f"Unrehydrated labels: {remaining}")

# ❌ 禁止: 直接調用外部系統
await kubectl_execute(raw_params)  # 可能包含 [IP_1] 標籤
```

### 2. 錯誤隔離

```python
# ✅ 正確: Tool 失敗不影響主流程
try:
    result = await signoz_tool.query_traces(...)
except SignOzConnectionError:
    logger.warning("signoz_unavailable")
    return MCPToolResult(success=False, error="SignOz unavailable")

# ❌ 禁止: 讓 Tool 錯誤傳播
result = await signoz_tool.query_traces(...)  # 可能拋出異常
```

### 3. 超時設定

| Tool | 建議超時 |
|------|---------|
| SignOz Query | 30s |
| Kubernetes Get | 10s |
| Kubernetes Delete | 30s |
| PostgreSQL Query | 15s |
| RAG Search | 5s |

### 4. 審計日誌

```python
# 所有 Tool 調用必須記錄
await audit_log.record(
    tool_name="kubernetes",
    action="delete_pod",
    params={"namespace": "awoooi-prod", "name": "api-xxx"},
    result=result,
    user_id=user_id,
    timestamp=now_taipei(),
)
```

---

## Tool 封裝與模組化關係

> **統帥澄清 (2026-03-25)**: Tool 封裝 ≠ 模組化，兩者是不同層次

```
Tool 封裝 → 放在 ACTION 積木內 → 遵循模組化原則開發
```

| 維度 | leWOOOgo 模組化 | Tool 封裝 |
|------|-----------------|-----------|
| 層次 | 軟體架構層 | 系統整合層 |
| 範圍 | 六大積木 | 外部系統連接 |
| 關係 | 是 Tool 的**實作基礎** | 是模組化的**應用場景** |

---

## 參考文檔

- `apps/api/src/plugins/mcp/mcp_bridge.py`: MCP Bridge 核心
- `memory/feedback_tool_vs_modular.md`: Tool 與模組化關係
- `memory/project_phase13_enterprise_aiops.md`: Phase 13 規劃
- ADR-001: MCP Protocol 採用
- Phase 13.2: Tool 實作工作項目 (#79-84)
