docs(mcp): CRM 接入手册 (REQ-20260911-0013)

This commit is contained in:
2026-09-14 12:21:26 +08:00
parent e408768fe5
commit f95de7eceb
@@ -0,0 +1,116 @@
# CRM 接入小趣 MCP 手册
> 适用版本:REQ-20260911-0013(增量同步框架)交付后
> 端点:`https://agent.ququclean.com/mcp`(生产)/ `http://192.168.4.236:8088/mcp`(内网)
> 协议:MCP Streamable HTTP(JSON-RPC),所有响应为 SSE,取 `data:` 行
## 0. 边界(先读这段)
小趣 MCP 只做三件事:**供数据、供身份、守权限**。
到期规则计算、AI 决策、推送、通知负责人、经营事项流转——全部是 CRM / 小趣 AI 系统的职责,MCP 不提供也不预留任何相关字段。
## 1. 一次性开通
| 步骤 | 操作方 | 说明 |
|---|---|---|
| 1 | 小趣管理员 | 为 CRM 开通服务密钥(X-Open-Key,一系统一密钥) |
| 2 | 小趣管理员 | 给 CRM 的服务主体绑定 `sync` 作用域(含 `integration.context.get` + 三个 changes 工具) |
| 3 | CRM 后端 | 调 `POST /api/open/token/ensure`(principal=服务主体)拿**长期服务 Token**(`xqak-...`),安全保管 |
完成后 CRM 持有两样东西:**服务 Token**(后台扫描用)、**服务密钥**(给销售换个人 Token 用)。
## 2. 每次调用前的身份自检
```
tools/call integration.context.get
```
返回 `principal_type` / `scopes` / `capability_levels` / `data_scope_version`。
- 后台扫描任务启动时必须确认 `principal_type=tenant_service` 且 capability 含 SYNC;拿错 Token(user_delegated)时**拒绝执行扫描**,防止串权。
- `data_scope_version` 变化说明管理员改了授权范围,建议触发一次全量重建。
## 3. 后台通道:每日定时扫描(增量同步)
三个工具:`users.changes.list` / `properties.changes.list` / `contracts.changes.list`。
入参只有三个(**禁止传 tenant_id/scope**,传了直接 PERMISSION_DENIED):
```json
{ "syncToken": null, "cursor": null, "limit": 200 }
```
### 协议语义
- **快照**:每轮同步签发 `snapshot_id` + `data_cutoff_at`,同一快照内翻页结果稳定,页间不会有数据漂移。
- **游标**:页内按 `(modify_time, id)` 复合游标推进;`has_more=true` 时用 `next_cursor` 翻下一页。
- **续传**:只有最后一页(`has_more=false`)才签发 `next_sync_token`。中断/宕机后从本地保存的 `sync_token + cursor` 继续,**不重不漏**。
- **tombstone**:`deleted_at` 非空的条目是被删除的数据,CRM 本地镜像应标记删除,不进 AI。
- **limit**:上限 200,传更大会被截断到 200 并带 notice。
### CRM 侧定时任务伪代码
```
token, cursor = load_local() # 首次均为 null → 全量
loop:
resp = call("contracts.changes.list", {syncToken: token, cursor: cursor, limit: 200})
for item in resp.items:
if item.deleted_at: 本地镜像标记删除(item.contract_id)
else: 本地镜像 upsert(item)
save_local(token, resp.next_cursor) # 每页都存,断点恢复
if resp.has_more: cursor = resp.next_cursor; continue
save_local(resp.next_sync_token, null) # 本轮结束,钉死新游标
break
```
### 错误分支(必须实现)
| 错误码 | 含义 | CRM 动作 |
|---|---|---|
| `SYNC_CURSOR_EXPIRED` | 快照/游标过期或被篡改 | 清空本地镜像,syncToken=null 全量重建 |
| `RATE_LIMITED` | 触发 SYNC 限流档 | 指数退避重试(建议 1s/2s/4s…上限 60s) |
| `UNAUTHENTICATED` | Token 被吊销/人员停用 | 重新 ensure 取 Token,仍失败则告警人工介入 |
### 两个错误用法
- 不要拿 `version` 字段做并发控制——它由 `modify_time` 派生,只用于调试核对。
- 不要把扫描任务和销售在线查询混用同一个 Token——服务 Token 调 QUERY 工具会被拒(scope 白名单),这是设计如此。
## 4. 前台通道:销售在线查询(实时 ACL)
```
1. 销售登录 CRM → CRM 后端调 /api/open/token/ensure {mobile: 销售手机号}
幂等,同一人永远拿到同一个个人 Token;缓存 "CRM用户 ↔ xqak ↔ userId" 映射。
2. 销售查合同时,带他的个人 Token 调:
- contract.list.query (列表,12 字段,自动按实时 ACL 行过滤)
- contract.detail.query (详情,含 version、files、processes)
3. 个人 Token 调 SYNC 工具直接被 scope 白名单拦截——销售永远扫不到全量数据。
```
个人 Token 缓存在 CRM 侧即可;收到 `UNAUTHENTICATED`(如人员被停用,60 秒内生效)时清缓存重取。
## 5. 两条通道速查
| | 前台(销售点查) | 后台(定时扫描) |
|---|---|---|
| Token 类型 | user_delegated | tenant_service |
| 可用工具 | contract.list/detail.query 等 QUERY | *.changes.list + context.get |
| 数据范围 | 实时 ACL 行过滤 | 管理员授权的 sync scope |
| 限流 | 常规档 | SYNC 独立档,互不占配额 |
| tenant_id 入参 | 不允许 | 不允许,从 Token 推导 |
## 6. 通用输出规范
所有工具返回统一信封 `structuredContent`:
```json
{ "schema_version": 2, "request_id": "...", "data": { ... } }
```
- 请求头 `X-Mcp-Schema-Version: 2` 启用新信封;不带则返回老结构(v1,兼容期内有效)。
- 所有请求把 `request_id` 记入 CRM 日志,排查时两边对得上。
- **Token 和手机号明文不要落日志**(MCP 侧已对手机号打码,CRM 侧请同样处理)。
## 7. 后续扩展
同步协议是通用框架:以后新增设备/订单/工单同步,CRM 侧代码不变,只是多调一个 `xxx.changes.list`、多存一对 `sync_token/cursor`。输出信封与错误码也不变。