@@ -2,111 +2,180 @@
## 1. 目标与边界
本文是 [`xiaoqu 多产品 Agent 统一接入 xiaoqu-mcp 总体设计` ](./xiaoqu-mcp-multi-product-agent-design.md ) 的运营项目落地设计。
本文是 [`xiaoqu 多产品 Agent 统一接入 xiaoqu-mcp 总体设计` ](./xiaoqu-mcp-multi-product-agent-design.md ) 的运营项目落地设计,代码基线为 `/Users/tonylai/wangheng/xiaoqu-operations-ai` 。
`ops-assistant-service` 是运营项目唯一 MCP Client,同时服务运营 iOS 和 Android,并保留现有编排 :
运营 iOS、Android、Web 共用 `ops-assistant-service` 中的一套 MCP Client:
``` text
运营 iOS / Android
iOS / Android / Web
│ 产品登录 Token
▼
Router → Planner → Executor → Answer
│
▼
xiaoqu-mcp
ops-assistant-service REST
│
▼
Router → Resolver → Planner → Executor → Answer
│
▼
Capability Registry
│ 固定 adapter_id
▼
xiaoqu-mcp Adapter
│ 用户 MCP Token(仅服务端持有)
▼
xiaoqu-agent-service /mcp
```
客户端不新增 MCP 代码。项目目录: `/Users/tonylai/jiaxk/xiaoqu-operations-ai ` 。
WebSocket 只负责会话进度和完成通知,不承载 MCP 调用。客户端不新增 MCP 代码,不保存用户 MCP Token、MCP Session ID 或 `X-Open-Key ` 。
## 2. 当前代码依据
- `services /ops-assistant-service` 是 FastAPI + Python 3.12 服务 。
- 当前依赖中没有 MCP SDK,也没有 `tools/list` / `tools/call` 客户端 。
- `src /ops_ assistant_ service/capability_registry.py` 维护静态 `CAPABILITY_REGISTRY` 。
- `src/ops_assistant_service/executor.py` 当前执行 `customer_issues.*` 、 `smartclean.query` 等本地 Capability 。
- 用户身份来自 Gateway JWT,并通过 `ops_identity_mappings` 解析租户、用户和 `scope_tags` 。
- 最小接入点是 Executor 的工具适配层,不新增第二套编排器 。
- `web /ops-assistant/src/api.ts` 通过 `/api/v1/ops-assistant/*` REST 提交消息和读取业务数据 。
- `web/ops-assistant/src/ConversationPage.tsx` 使用 WebSocket 订阅会话进度,消息提交仍走 REST 。
- `services /ops- assistant- service` 是 FastAPI + Python 3.12 服务,现有 `httpx` 足够实现 MCP HTTP Client 。
- `worker.py` 同时运行旧流水线和 `contextual_fulfillment_v2` ;新流水线包含 Router、Resolver、Planner、Recovery、Executor、Answer 。
- `capability_registry.py` 维护静态 `CAPABILITY_REGISTRY` 、冻结的 Capability Contract 和 `adapter_id` 白名单 。
- 旧 `executor.py` 与新 `contextual_execution.py` 最终都调用 `execute_registered_tool()` 。
- 当前 Registry 包含 `customer_issues.*` 、`vision_analyze` 、`work_items.list` 、`read_only_todos.list` 、`knowledge_entries.search` ,已不包含 `smartclean.query` 。
- Gateway JWT 在入口解析为共享身份,再通过 `ops_identity_mappings` 映射为运营 `tenant_id` 、`user_id` 和 DataScope;后台 Worker 执行时使用运营 UUID。
## 3. 配置设计
因此不在 Web、路由层或某一条流水线中增加专用 MCP 分支。唯一接入点是两套流水线共用的 Capability Adapter 执行边界。
在 `src/ops_assistant_service/config.py` 的 `Settings` 中增加以下 `OPS_ASSISTANT_` 配置:
## 3. 设计决策
### 3.1 统一 Adapter
在 `CAPABILITY_REGISTRY` 中注册稳定的本地 Capability 和固定 `adapter_id` ,由 `execute_registered_tool()` 将 MCP Adapter ID 路由到 `XiaoquMcpAdapter` 。
``` text
Planner 选择本地 Capability
→ 校验冻结的 Capability Contract
→ 校验输入 schema、scope_tags、DataScope
→ execute_registered_tool(adapter_id)
→ XiaoquMcpAdapter
→ MCP tools/call
```
不得把远端 `tools/list` 原样注册给 Planner。`tools/list` 只用于启动或联调时验证远端工具是否与本地冻结契约一致。
### 3.2 显式依赖传递
`XiaoquMcpRuntime` 由 `worker.build_pipeline_runtime()` 创建,并通过显式参数传给旧 `run_executor_tick` 和新 `run_contextual_executor_tick` 。禁止用模块级全局变量或从请求上下文隐式读取用户。
`PipelineRuntime.close()` 负责关闭 MCP HTTP Client 和清理进程内 Session 缓存。未启用 MCP 时传入 `None` ,现有本地 Capability 保持原行为。
## 4. 配置设计
在 `src/ops_assistant_service/config.py` 的 `Settings` 增加以下 `OPS_ASSISTANT_` 配置:
| 变量 | 默认值 | 说明 |
|---|---|---|
| `XQ_MCP_ENABLED` | `false` | 是否启用 MCP Capability |
| `XQ_MCP_URL` | unset | xiaoqu-mcp 内网地址 |
| `XQ_MCP_OPEN_KEY` | unset | Token 初始化凭证,类型 使用 `SecretStr` |
| `XQ_MCP_SCOPE_CODE` | `operation` | 服务端固定 scope,禁止由 Planner 或 客户端选择 |
| `XQ_MCP_OPEN_KEY` | unset | Token 初始化凭证,使用 `SecretStr` |
| `XQ_MCP_SCOPE_CODE` | `operation` | 服务端固定 scope,禁止由客户端或模型 选择 |
| `XQ_MCP_TIMEOUT_SECONDS` | `10` | MCP 请求超时 |
启用时必须同时提供 URL、Open Key 和 scope。`operation` 已存在于 dev 种子,其他环境启用前仍需核对实际 scope 和工具绑定;生产配置不得写入仓库 。
启用时 URL、Open Key 和 scope 必填 。`operation` 已存在于 xiaoqu-mcp dev 种子;development、test、staging、production 均须分别核对真实 scope 和工具绑定。凭证只由部署 Secret 注入 。
## 4 . 最小模块
## 5 . 最小模块
只新增两个模块:
### 5.1 `mcp_token_provider.py`
- `src/ops_assistant_service/mcp_client.py`
- 使用现有 `httpx` 完成 JSON-RPC、Session ID、Bearer Header、超时和错误映射。
- 按租户、`user_id` 和 scope 隔离 MCP Session。
- `src/ops_assistant_service/mcp_token_provider.py`
- 接收已验证的 `SharedIdentity` 。
- 从受信任用户资料源取得 mobile/email 并调用 `ensure` 。
- 按租户、`user_id` 和 scope 缓存 Token;401 时只刷新当前用户一次。
- 不接受客户端或 Planner 传入的身份字段作为覆盖参数。
输入为 Worker 已持有的运营 `tenant_id` 、`user_id` 和固定 scope:
不引入通用 MCP 框架、不新增 Agent,也不直连 xiaoqu-mcp 数据库 。
1. 从 `ops_identity_mappings` 反查可信的 `shared_tenant_id` 、 `shared_user_id` 。
2. 通过现有只读共享用户 Port 获取权威 mobile;权威数据源存在 email 时可作为备用。
3. 使用固定 `scopeCode` 调用 `/api/open/token/ensure` 。
4. 按运营租户、运营用户、共享用户和 scope 缓存 Token。
## 5. Capability 映射
现有 `SharedUserPort` 尚未暴露 mobile/email,需要最小扩展其只读 DTO 和 PostgreSQL Adapter;不得复制用户表,也不得使用 Web、Planner、工具参数或模型输出中的身份字段。
在 `capability_registry.py` 注册固定的本地 Capability,不把远端 `tools/list` 原样暴露给 Planner:
### 5.2 `mcp_client.py`
| 本地 Capability | MCP 工具 | 首期权限 |
|---|---|---|
| `xiaoqu.agent_count` | `agent.count` | read |
| `xiaoqu.agent_list` | `agent.list.query` | read |
| `xiaoqu.property_list` | `property.list.query` | read |
使用现有 `httpx.Client` 管理:
Planner 只能选择注册表中的名称和 schema。Executor 在调用前执行本地 Capability、用户 `scope_tags` 、租户/DataScope 校验;xiaoqu-mcp 再执行 Token scope 和服务端数据权限校验 。
- `initialize` 、 `notifications/initialized` 、 `tools/list` 、 `tools/call` 。
- Bearer Header、`Mcp-Session-Id` 、JSON-RPC ID 和响应校验。
- 连接、读取、写入和总超时。
- 按运营租户、运营用户和 scope 隔离 Session。
- 关闭时释放 HTTP Client,不持久化 MCP Session。
远端结果经过字段白名单投影后再进入 Answer。Token、Authorization Header 和未授权字段不得进入 execution trace。
### 5.3 `mcp_adapter.py`
## 6. Executor 接入
`XiaoquMcpAdapter` 只接收已经验证的 Capability Contract、运营身份和参数:
在 `executor.py` 增加 MCP 只读分支:
- 根据固定映射选择远端 MCP 工具。
- 调用 Token Provider 和 MCP Client。
- 校验远端响应并执行字段白名单投影。
- 返回现有 Executor/Answer 可消费的字典,不返回 Token、Header 或原始未授权字段。
不引入通用 MCP 框架或第二套 Agent 编排器。
## 6. Capability 映射
首期只注册只读 Capability:
| 本地 Capability | `adapter_id` | MCP 工具 | 权限 |
|---|---|---|---|
| `xiaoqu.agent_count` | `mcp.xiaoqu.agent_count.v1` | `agent.count` | read |
| `xiaoqu.agent_list` | `mcp.xiaoqu.agent_list.v1` | `agent.list.query` | read |
| `xiaoqu.property_list` | `mcp.xiaoqu.property_list.v1` | `property.list.query` | read |
每项必须在 `CAPABILITY_REGISTRY` 中声明输入/输出 schema、风险、审批、超时重试、审计事件和结果模板,并将精确 `adapter_id` 加入 `EXECUTION_ADAPTER_IDS` 。已有本地 Capability 不改名、不改 Adapter、不改变执行语义。
## 7. 身份与权限
最终授权取交集:
``` text
validate_tool_inpu t
→ 校验本地 Capability 和参数白名单
→ 读取 SharedIdentity / DataS cope
→ mcp_token_provider 获取当前用户 Token
→ mcp_client 调用 tools/call
→ 结果字段投影
→ 现有 execution_trace / Answer
本地 Capability allowlis t
∩ 当前运营用户 scope_tags / DataScope
∩ MCP Token s cope
∩ xiaoqu-mcp 用户、租户和行级数据范围
```
现有 `customer_issues.*` 和 `smartclean.query` 保持不变。MCP 不可用时返回稳定的 `TOOL_UNAVAILABLE` ,不得回退公共 Token 或数据库直连 。
- REST 和 WebSocket 只负责验证产品登录态并建立运营身份 。
- Planner 只能选择 Registry 中的 Capability,不决定用户、租户或 scope。
- Worker 必须在每次执行前确认 `ops_identity_mappings` 仍有效,不能仅依赖消息入库时的旧快照。
- `tools/list` 与 `tools/call` 使用同一用户 Token 和 MCP Session;服务端在两个入口都强制鉴权。
## 7 . 开发顺序
## 8 . 异步执行与生命周期
1. 核对目标环境中的 `operation` scope、工具绑定和用户资料映射 。
2. 增加 Settings 配置及启用校验。
3. 实现 `mcp_token_provider.py` 与 `mcp_client.py` ,覆盖 JSON-RPC 和 Session 单元测试。
4. 注册 3 个只读 Capability 并接入 Executor。
5. 增加用户隔离、scope 拒绝、401 重连、Token 脱敏和 MCP 不可用测试。
6. 在 development/test 环境联调,通过后再进入 staging。
Web、iOS、Android 提交消息后获得 `202` ; Worker 在后台完成 Agent 和工具执行,再由消息查询或 WebSocket 返回进度与结果 。
## 8. 项目验收
- `build_pipeline_runtime()` 创建一个进程级 `XiaoquMcpRuntime` ,但 Token 和 Session 按用户隔离。
- Worker 重启后 Session 不恢复;首次调用重新 `ensure` 和 `initialize` 。
- 用户映射失效、scope 版本变化或 MCP 401 时,只清理该用户缓存。
- MCP 401:重新 `ensure` 和连接一次;只读调用最多重试一次。
- MCP 403:直接返回权限不足,不刷新 Token。
- 超时、协议错误或服务不可用:映射为稳定的 `TOOL_UNAVAILABLE` ,并让当前 Execution Attempt 进入可审计失败状态。
- 不自动重放写操作;首期不接写工具。
- 运营 iOS、Android 继续只调用 `ops-assistant-service` ,客户端无 MCP Token。
## 9. 开发顺序
1. 核对各环境 `operation` scope、三项工具绑定和用户映射数据源。
2. 扩展只读 SharedUser Port,完成运营 UUID 到 mobile/email 的可信解析测试。
3. 增加 Settings 配置、启用校验和 `XiaoquMcpRuntime` 生命周期。
4. 实现 Token Provider、MCP Client 和 Adapter 的协议单元测试。
5. 注册三项本地 Capability,并让旧 Executor 与 contextual v2 共用同一 Adapter。
6. 覆盖用户隔离、租户隔离、scope 拒绝、401、403、超时、脱敏和 Worker 重启测试。
7. 分别从 iOS、Android、Web 提交对话请求,验证异步进度和最终结果一致。
## 10. 项目验收
- iOS、Android、Web 均只调用 `ops-assistant-service` ,客户端没有 MCP Token、Session ID 或 Open Key。
- Web 消息走 REST, WebSocket 仅订阅进度/完成通知。
- 旧流水线和 `contextual_fulfillment_v2` 对同一 MCP Capability 使用同一个 Adapter。
- 用户 A/B、不同租户和 scope 的 Token 与 MCP Session 不互用。
- Planner 不能选择未注册的远程工具,Executor 不能绕过 `scope_tags` 和 DataScope。
- `tools/list` 、 `tools/call` 使用同一用户 Session 和 Bearer Token 。
- MCP 401 只刷新当前用户;只读请求最多重试一次 。
- 测试、日志、trace、配置和 Git 历史中均 无凭证明文。
- Planner 不能选择未注册工具;冻结 Contract、 `scope_tags` 、 DataScope 或 MCP scope 任一不满足即拒绝 。
- 现有七项本地 Capability 的契约和行为不受影响 。
- MCP 401 只刷新当前用户;MCP 不可用时不回退公共 Token、数据库直连或 xiaoqu-ai Agent 。
- 测试、日志、trace、配置和 Git 历史中无凭证明文。
## 9 . 当前不做
## 11 . 当前不做
- 不把 MCP Client 放进 运营 iOS 或 Android。
- 不在 运营 iOS、 Android、Web 中实现 MCP Client 。
- 不动态注册全部远程工具。
- 不在首期引入写工具;写工具需另行设计审批、幂等和补偿协议 。
- 不复制 xiaoqu-mcp 数据库,不调用 xiaoqu-ai Agent 代查 数据。
- 不恢复或假定已删除的 `smartclean.query` 。
- 不复制 xiaoqu-mcp 或共享用户 数据库 。
- 不在首期引入写工具;写工具需另行审批幂等、确认和补偿协议。