# KPI刷新：curl直连MCP（推荐替代方案）

当 `tool_call` 逐页拉取太慢，或 `delegate_task` 子Agent无法使用MCP工具时，
用 `terminal` + `curl` + JSON-RPC 直接调用飞书MCP端点，速度不受工具调用轮次限制。

## 核心端点

```
URL: https://project.feishu.cn/mcp_server/v1
Header: X-Mcp-Token: <from ~/.hermes/config.yaml>
Content-Type: application/json
Method: tools/call (JSON-RPC 2.0)
```

Token 持久位置：`~/.hermes/config.yaml` → `mcp_servers.feishu-project.headers.X-Mcp-Token`

## 步骤1：MQL翻页拉全量（✅ 用 OFFSET，不用 session_id）

> ⚠️ session_id 翻页在 cron/自动化中会无限循环（每页始终 50 条，`len(items) < 50` 永不触发）。
> **必须用 OFFSET 分页**，`empty_streak >= 2` 停止。

```python
import json, subprocess

TOKEN = "m-abfb29e8-..."
BASE_URL = "https://project.feishu.cn/mcp_server/v1"
MQL = "SELECT `name`, `work_item_id`, `field_51d592` FROM `销售管理`.`销售订单` WHERE `创建时间` >= '2026-08-01'"

all_rows = []
offset = 0
empty_streak = 0
max_pages = 20

for page in range(1, max_pages + 1):
    paginated_mql = f"{MQL} LIMIT 50 OFFSET {offset}"
    payload = {
        "jsonrpc": "2.0", "method": "tools/call",
        "params": {
            "name": "search_by_mql",
            "arguments": {
                "project_key": "6593cd71471290e3cc6be6e6",
                "mql": paginated_mql
            }
        },
        "id": page
    }
    cmd = ["curl", "-s", "--max-time", "30", "-X", "POST", BASE_URL,
           "-H", f"X-Mcp-Token: {TOKEN}",
           "-H", "Content-Type: application/json",
           "-d", json.dumps(payload)]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=35)
    resp = json.loads(result.stdout)
    text = resp["result"]["content"][0]["text"]
    data = json.loads(text)

    # 首页获取 total
    if page == 1 and data.get("list"):
        print(f"Total records: {data['list'][0]['count']}")

    items = data.get("data", {}).get("1", [])
    if len(items) == 0:
        empty_streak += 1
        if empty_streak >= 2:
            break
    else:
        empty_streak = 0

    # 解析字段
    for item in items:
        fields = {}
        for f in item.get("moql_field_list", []):
            v = f.get("value")
            if v is None: fields[f["key"]] = None
            elif "double_value" in v: fields[f["key"]] = v["double_value"]
            elif "string_value" in v: fields[f["key"]] = v["string_value"]
            elif "long_value" in v: fields[f["key"]] = v["long_value"]
        name = fields.get("name", "")
        wid = str(fields.get("work_item_id", ""))
        amount = float(fields.get("field_51d592") or 0)
        all_rows.append((name, wid, amount))

    offset += 50

print(f"Total rows: {len(all_rows)}")
```

## 步骤2：提取唯一单据编号 → 逐条查询销售员（role_7dd6e0）

```python
# 提取唯一单据编号（取第一个 work_item_id）
doc_to_wid = {}
for name, wid, amount in all_rows:
    if name and wid and name not in doc_to_wid:
        doc_to_wid[name] = wid
unique_docs = list(doc_to_wid.keys())
print(f"Unique docs: {len(unique_docs)}")
```

### 逐条查询销售员

### ⚠️ 关键：work_item_id 必须是字符串！

通过 JSON-RPC 调用 `get_workitem_brief` 时，`work_item_id` 必须传字符串，否则返回：
`"work_item_id is not single string value"`

```python
payload = {
    "jsonrpc": "2.0", "method": "tools/call",
    "params": {
        "name": "get_workitem_brief",
        "arguments": {
            "project_key": "6593cd71471290e3cc6be6e6",
            "fields": ["role_7dd6e0"],
            "work_item_id": str(work_item_id)  # ⚠️ 必须是字符串
        }
    },
    "id": 1
}
# 解析：
text = resp["result"]["content"][0]["text"]
detail = json.loads(text)
role_members = detail["work_item_attribute"]["role_members"]
for rm in role_members:
    if rm["key"] == "role_7dd6e0":
        salesperson = rm["members"][0]["name"]  # 取第一个成员名
```

## 步骤3：按销售员汇总

不按单据编号去重——同一编号多行对应不同货号，每行金额都累加。

```python
from collections import defaultdict
by_sp = defaultdict(float)
for name, wid, amount in all_rows:
    sp = doc_to_sp.get(name, "未知")
    by_sp[sp] += amount
total = sum(by_sp.values())
```

## 步骤4：写入 + 合并 + 部署

```bash
# 合并进 workbench_data.json（⚠️ 工作台KPI卡片读的是workbench_data.json内嵌字段）
python3 ~/Desktop/Hermes输出-工作类/脚本/merge_workbench_data.py

# 部署
tcb hosting deploy ~/Desktop/Hermes输出-工作类/数据/kpi_progress.json 数据/kpi_progress.json -e bier-sales-d0gatbvlx288724e9
tcb hosting deploy ~/Desktop/Hermes输出-工作类/数据/workbench_data.json 数据/workbench_data.json -e bier-sales-d0gatbvlx288724e9
```

## 性能对比

| 方式 | 拉取30页 | 查询717销售员 | 总计 |
|------|---------|-------------|------|
| tool_call逐页 | ~30轮对话 | 无法完成 | 不可行 |
| curl JSON-RPC | ~30秒 | ~10分钟 | ~11分钟 |

## 陷阱

1. **work_item_id 类型**：`tool_call` 自动转换 int→string，但 JSON-RPC 直连必须显式 `str()`。
2. **delegate_task 无法使用MCP**：子Agent调MCP工具返回404。KPI刷新这类大量MCP调用的任务不要用delegate_task。
3. **OFFSET 分页（非 session_id）**：session_id 在 cron 下无限循环，必须用 OFFSET + `empty_streak >= 2` 停止。
4. **merge_workbench_data.py 必须运行**：单独部署 kpi_progress.json 工作台看不到变化（读的是 workbench_data.json 内嵌字段）。
5. **⚠️ role_7dd6e0 缺失 → "未知"**：部分订单的 `role_members` 中无 `role_7dd6e0`（如仅含 `role_5ee38b` 技术支持、`role_00a9b7` 下单人），导致销售员为"未知"。这是 MCP 角色系统的固有局限——这些订单的销售员未在飞书项目中被配置。`get_workitem_brief` 返回的 `role_members` 中查找 `key == "role_7dd6e0"`，找不到则 fallback 到 `"未知"`。ERP 导出可补充此信息。
6. **cron 模式验证**（2026-08-10）：`terminal` + `python3` heredoc + curl JSON-RPC 在 cron 下完整通过（拉取 370 条 → 187 唯一文档 → 查询 187 销售员 → 汇总 → 部署）。全程约 3-4 分钟。
