# MCP 实时数据 API 服务器模式（2026-07-26 实战）

## 架构

```
飞书项目 MCP ←── Python HTTP 后端(8765) ──→ Cloudflare Tunnel
                    │                            │
                    ├─ 内存缓存(TTL)              │
                    ├─ JSON 文件降级备份          │
                    └─ API 端点                   │
                                                 ↓
                    工作台前端(CloudBase) ←─── CORS API
                         ├─ 优先 API (Tunnel)
                         ├─ 降级 localhost
                         └─ 最终降级本地 JSON
```

## 后端 API 端点

| 端点 | 方法 | 说明 |
|------|------|------|
| `GET /api/mcp/{type}` | GET | 返回缓存数据，`?refresh=1` 强制刷新 |
| `POST /api/mcp/refresh` | POST | `{"type":"all|customers|orders|..."}` |

支持的类型：`customers`, `orders`, `complaints`, `activities`, `opportunities`

## 缓存策略

```python
MCP_CACHE_TTL = {
    'customers': 1800,   # 30分钟（变化不频繁）
    'orders': 300,       # 5分钟
    'complaints': 900,   # 15分钟
    'activities': 300,   # 5分钟
    'opportunities': 300 # 5分钟
}
```

缓存双写：内存 dict + JSON 文件（降级备份）。

## 前端三级降级 + 自动刷新

```javascript
// 三级数据源
var API_BASE_URLS = [
  'https://{tunnel}.trycloudflare.com',  // 优先
  'http://127.0.0.1:8765'                // 降级
];

// ENDPOINTS 配置：[key, [apiPath, localPath]]
var ENDPOINTS = [
  ['customers',    ['/api/mcp/customers',    '../数据/pricing/_key_customers.json']],
  ['orders',       ['/api/mcp/orders',       '../数据/orders_dashboard.json']],
  ['complaints',   ['/api/mcp/complaints',   '../数据/mcp_complaints.json']],
  ['myActivities', ['/api/mcp/activities',   '../数据/my_activities.json']],
  ['personnel',    ['',                       '../数据/pricing/_personnel.json']],  // 仍用本地
];

// 自动刷新（仅 visible tab）
var AUTO_REFRESH_MS = 5 * 60 * 1000;
refreshTimer = setInterval(function() {
  if (document.visibilityState === 'visible') refreshData();
}, AUTO_REFRESH_MS);
```

## CORS 配置

后端所有 JSON 响应必须加 `Access-Control-Allow-Origin: *`：

```python
def _send_json(self, obj, code=200):
    # ...
    self.send_header('Access-Control-Allow-Origin', '*')
    self.end_headers()

def do_OPTIONS(self):
    self.send_response(204)
    self.send_header('Access-Control-Allow-Origin', '*')
    self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
    self.send_header('Access-Control-Allow-Headers', 'Content-Type')
    self.end_headers()
```

## Cloudflare Tunnel 注意事项

Tunnel URL 每次重启会变，需同步更新前端 `API_BASE_URLS` 和后端 `AUTH_URLS`。

当前 Tunnel：`board-celebrate-nylon-address.trycloudflare.com`（2026-07-26）

## 飞书桌面端 OAuth 免登

工作台在飞书桌面端（非移动端）检测到飞书 UA 时，自动跳转 OAuth 授权：

```javascript
if (isInFeishu && !isFeishuMobile && !localStorage.getItem(ROLE_KEY)) {
  window.location.href = 'https://open.feishu.cn/open-apis/authen/v1/authorize'
    + '?app_id=cli_aaee6a2f90b89bc1'
    + '&redirect_uri=' + encodeURIComponent(WORKBENCH_URL)
    + '&state=login';
}
```

回调时通过 Tunnel 后端 `/api/feishu/auth` 交换 code → 用户名 → 匹配 user_roles → 免密登录。

## showLoginForm 必须调 loadUserList

当 OAuth 失败回退到密码登录时，`showLoginForm()` 需要确保用户下拉列表已加载：

```javascript
function showLoginForm() {
  // ...
  var sel = document.getElementById('loginRole');
  if (!sel || sel.options.length === 0) loadUserList();
}
```

否则 `feishuAutoLogin` 失败后用户看到的是空下拉框。
