Files
xiaoqu_mcp_service/xiaoqu-agent-service/doc/agent-mcp-service-design.md
T

21 KiB
Raw Blame History

xiaoqu-agent-service 设计方案

面向智能体(Kimi Code / Claude 等 MCP 客户端)的业务数据服务:独立新建系统,远程部署,照搬现有 SaaS 的行级数据权限,以"作用域"控制工具可见性,带轻量管理后台。

状态:讨论定稿(2026-09-01),待进入实施。


1. 背景与目标

现有 SaaS(xiaoqu-franchisee)是面向 PC 后台人工操作的系统,接口鉴权(uuid + 短信登录态 + 签名)不适合机器调用。本方案新建一套独立系统,将仓库、客户等业务数据以 MCP 服务形式暴露给智能体,同时提供 CLI 兜底与 Web 管理后台。

设计约束(讨论中明确):

  • 与现有系统零代码耦合:不改老系统一行代码,耦合面只有数据库(只读)。
  • 照搬现有行级数据权限(getByIsAgent 那套规则),不移植字段级/页面级功能权限。
  • 远程部署,MCP over Streamable HTTP。
  • 用户熟悉 Java:技术栈定为 JDK 17 + Spring Boot 3 + Vue 3。

2. 总体架构

Kimi Code ──┐
            ├─ MCP (Streamable HTTP, Bearer ak) ─┐
Claude ─────┤                                    │
            │                                    ▼
xq CLI ─────┘  (Go 单二进制, 同为 MCP 客户端)   ┌─────────────────────────┐
                                    │  xiaoqu-agent-service   │
                                    │  (单 jar, Spring Boot 3) │
                                    │                         │
            ┌── Web 管理后台 ──────►│  /mcp    MCP 端点        │
            │   (Vue3 内嵌打包)     │  /api/** 后台 REST       │
            │                     │  static/  管理页面        │
            │                     ├─────────────────────────┤
            │                     │  鉴权层: ak→人, scope校验  │
            │                     │  权限引擎: 行级数据过滤     │
            │                     │  数据访问层: 3×MySQL/Redis/ES│
            │                     └───────┬─────────┬───────┘
            │                             │ 只读    │ 读写
            │                     ┌───────▼──┐  ┌───▼────────┐
            └────────────────────►│ 现有三库  │  │ 自有库表    │
                (user/agent等)   │ m0/m1/m2 │  │ token/scope│
                                 │ Redis/ES │  │ tool/audit │
                                 └──────────┘  └────────────┘

关键分层:业务工具只写一遍,MCP 端点(Kimi/Claude/CLI 同为 MCP 客户端)、后台页面共享同一套鉴权、权限引擎与数据访问层。

3. 权限模型(三层)

人员(user) 1──n Token(ak) n──n 作用域(Scope) n──n 工具(Tool)
                  │
                  └── 行级数据权限: 跟着"人"走,照搬现有系统

3.1 概念定义

  • Tool:MCP 暴露的工具(如 warehouse.stock.query)。代码中用注解声明,启动时同步进 tool 表。读写属性在注册时标注。
  • Scope(作用域):命名的工具集合(如"运营作用域""销售作用域")。纯手工配置:管理页面上创建作用域、勾选工具。新工具上线默认不属于任何作用域 = 任何 token 都调不到,必须管理员显式开通(安全优点)。
  • Token(ak):属于某个人,一人多把(Kimi 一把、Claude 一把)。每把 token 勾选任意组合的作用域。可长期有效(expire_time 可空),靠"禁用"回收;可选填过期时间供临时外发场景自毁。
  • 职责分离:token/scope 只决定"能用哪些工具";能看哪些行永远由 token 背后的人决定。同一人多把 token,数据视野一致,能力面不同。

3.2 调用判定链

  1. ak 有效?(SHA-256 哈希匹配、未禁用、未过期)
  2. 找得到人?(token → user,user 未删除;用户资料优先读 Redis entity:User:{id} 热缓存,变更即时生效,回源 m0 user 表)
  3. 工具在 token 作用域并集内?→ 否则拒绝(服务端强制,补齐老系统 PermissionInterceptor 空壳的短板)
  4. 执行查询,按"人"的数据权限规则加行过滤
  5. 审计落库(人、token、工具、入参摘要、结果、耗时)

注:tools/list 按作用域过滤(2026-09-04 决策,避免"看得到却调不了"的二义性):MCP SDK 工具清单在启动时静态注册,无法按请求过滤,故由 ToolsListScopeFilter 在 servlet 层重写 tools/list 响应。第 3 步的强制点仍是 tools/call——绕过 list 直接调未授权工具,得到的仍是可读的拒绝信息 + denied 审计。

3.3 行级数据权限移植规范(照搬现有系统)

来源:WrapperUtils.getByIsAgent 系列 + authority.isagent。规则:

条件 行过滤
权限组 isagent=0(总部/管理员) 不过滤,看全部
isagent=1(代理商) agent_id IN (自己 + 直属下级代理),下级只取一层(agent.supervisor_id)
代管扩展 并入 warehouse_agent_auth 中该用户被授权的 manager_agent_id;物业维度再并 agent_manage_district_auth
物业用户(enterprise_type=2) 再限 property_id = 自己
销售部门非负责人(dept 命中字典、post 不在白名单) 只看 create_id = 自己

实现方式:权限引擎输出中性的"可见 agent id 集合 + 过滤字段",由各数据源适配器翻译成 SQL IN 或 ES terms。不做跨库 JOIN——先算 id 集合,再拼到目标库查询(与老系统实际行为一致)。

4. 数据源

源 内容 接入方式
m0(权限库) user、agent、authority、agent_manage_district_auth 只读连接池
m1(商城库) 商城业务数据 只读连接池(按需启用)
m2(业务库) 仓库/客户/工单等、warehouse_agent_auth 只读连接池
Redis entity:User:{id} 用户热缓存、Dictionary 字典 只读
ES 部分列表查询 预留,一期可后置
  • 全部使用只读数据库账号(物理封死误写);将来写工具单独配有写权限的账号。
  • 配置按环境分文件(dev/test/prd);dev 环境密码明文写在 application-dev.yml(内网 dev 库,与现有 pom.xml dev profile 明文同一惯例),生产/测试环境不得在配置文件写明文密码。

4.1 Entity 生成

不依赖 xiaoqu-publicbeanandmap jar(MyBatis-Plus 3.4 旧依赖链与 Boot 3 冲突)。新工程内置 mybatis-plus-generator(run-once),表白名单制生成 Entity/Mapper,按来源库分包 entity/m0|m1|m2,生成物提交进仓库归新系统所有。Mapper XML 基本不用,LambdaQueryWrapper 为主。

5. 对外形态

5.1 MCP(主形态)

  • Streamable HTTP 传输,端点 https://mcp.<域名>/mcp。
  • 客户端配置(Kimi Code ~/.kimi-code/mcp.json 或项目级 .kimi-code/mcp.json,ak 明文直写;项目级文件已加入 .gitignore):
{
  "mcpServers": {
    "xiaoqu": {
      "url": "https://mcp.<域名>/mcp",
      "headers": {
        "Authorization": "Bearer <ak>"
      }
    }
  }
}
  • Claude Code:claude mcp add --transport http xiaoqu https://mcp.<域名>/mcp --header "Authorization: Bearer <ak>"。
  • ak 明文只出现在服务端签发响应和运行时;服务端保存 SHA-256 哈希及加密密文,含 ak 的客户端配置文件不得提交版本库。

5.2 Web 管理后台(四个页面)

  • 用户与 Token 管理:人员选自现有 user 表,只列企业账号(enterprise_type 0=企业内部 / 1=代理,历史 NULL 保留),物业账号(=2)不进列表——Token 是给能查代理商/客户数据的人用的;每人名下签发/禁用 token,展示 key 前缀、作用域、最后使用时间;选中人员展示其数据权限归属(权限组/isagent、所属代理、企业类型、可见范围说明,只读,便于排查"查不到数据")。一作用域一 Token:签发时选 N 个作用域生成 N 把 token(每把独立绑定单作用域,独立吊销/审计,明文逐把展示一次);编辑存量 token 时作用域收敛为单选。
  • 工具管理:工具列表来自服务端注册自动同步(工具名/分类/读写类型页面只读);展示每个工具被哪些作用域引用;「停用」为全局紧急开关。分类(category)在代码注册时由开发者声明(工具的业务域归属是客观属性,不允许页面上修改,避免与代码漂移)。
  • 作用域管理:作用域 CRUD + 勾选工具(工具按 category 分组展示)。
  • 审计日志:按人/token/工具/时间筛选。

原型见 doc/prototype/admin-prototype.html(浏览器直接打开)。

后台登录为独立的管理员账号密码(session/JWT),与 ak 体系分离。

5.3 CLI(兜底,P3 之后实施)

核心决策:CLI 不是第二套服务,而是 /mcp 端点的一个 MCP 客户端(Streamable HTTP + tools/call)。服务端零新增暴露面,ak/作用域/数据权限/审计自动生效;CLI 启动时通过 tools/list 自发现工具,服务端新增工具无需升级 CLI。

  • 语言:Go(与服务端 Java 栈解耦,语言自由)。单文件静态二进制分发,交叉编译覆盖 Mac/Linux,调用环境无需任何运行时。备选 Python(官方 MCP SDK 最全,但依赖本机 Python/uv)。
  • 命令规范:从工具名派生,不手工维护——warehouse.stock.query → xq warehouse stock query --warehouse-id 7;参数与 help 从 tools/list 的 schema 生成。
  • 输出约定:stdout 恒为 JSON(--pretty 给人看);错误统一 {"error":{code,message}} 且 exit code 非 0;--schema 吐入参 JSON Schema 供 agent 自发现。
  • 探测命令:xq whoami(ak 归属的人 + 作用域,兼作连通性自检)、xq tools(列出本 ak 可用工具)。
  • 配置:~/.xq/config.yaml 存 base-url、多环境 profile(--env dev|test|prd)与 ak 明文(本机文件,权限 600),与 mcp.json 明文直写同一风格。
  • 服务端配套改动(仅两点):审计表加 client_type(mcp-kimi / mcp-claude / cli,从请求头识别);错误消息面向"人和 agent 都可读"(如权限拒绝时提示应由管理员在哪个作用域开通)。

5.4 Open API(服务间接口)

供其他业务系统/智能体平台在服务端到服务端场景下获取用户 Token,路径 /api/open/**,与 ak 体系、后台 session 体系完全分离。

  • 鉴权:静态服务间密钥,header X-Open-Key,配置项 app.open.api-key(dev 明文写 application-dev.yml;配置为空 = 接口关闭)。常量时间比较防时序侧信道。
  • POST /api/open/token/ensure(get-or-create):
    • 入参:mobile、email(至少一个,手机号优先)、scopeCode(作用域编码,必填)。
    • 人员只认企业账号(enterprise_type 0/1,NULL 保留,与后台用户列表同口径);查不到 → 404。
  • 该人员在该作用域下已存在有效 Token(正常且未过期)→ created=false,只回 keyPrefix/expireTime 等元信息,明文不回传。
    • 不存在 → 自动创建永久 Token(一作用域一 Token,memo 标记 open-api 自动签发),created=true 返回明文,仅此一次。
    • 全程写 audit_log(tool_name=open.token.ensure,client_type=open-api,参数摘要脱敏)。

6. 技术栈与工程结构

  • JDK 17 + Spring Boot 3.x(Spring AI MCP starter 的硬性要求)
  • MCP:spring-ai-starter-mcp-server-webmvc,protocol: STREAMABLE;工具 @McpTool 注解声明
  • 数据:MyBatis-Plus 3.5(mybatis-plus-spring-boot3-starter)三数据源、Redis、ES 客户端
  • 前端:Vue 3 + Element Plus,yarn build 产物打入 jar static/(frontend-maven-plugin 联动构建),单 jar 部署,Nginx 终结 TLS
  • ⚠️ 开工先验证 Spring Boot / Spring AI 版本组合(Spring Boot 3.5.x 曾报 streamable NPE:spring-projects/spring-ai#6274),跑通 demo 再铺开
xiaoqu-agent-service/
├── src/main/java/me/iiv/agent/
│   ├── config/          # 三数据源、Redis、ES、MCP server
│   ├── mcp/             # @McpTool 工具(按业务域分包) + 注册同步
│   ├── security/        # ak Filter、身份上下文、scope 校验
│   ├── permission/      # 行级数据权限引擎(移植 getByIsAgent)
│   ├── admin/           # 后台 REST: 用户/token、作用域、审计
│   ├── entity/          # m0/m1/m2 生成实体
│   └── common/          # 审计 AOP、限流、结果包装
├── src/main/resources/  # application-{dev,test,prd}.yml
├── codegen/             # mybatis-plus-generator 配置(run-once)
└── front/               # Vue3 + Element Plus

7. 自有库表(新 schema,如 xiaoqu_agent_svc)

表 关键字段 说明
agent_api_key user_id、key_prefix、key_hash(SHA-256)、status、expire_time(可空=永久)、last_used_time token;user_id 逻辑关联 m0.user
scope name、code、memo、status 作用域
scope_tool scope_id、tool_id 作用域-工具
token_scope token_id、scope_id token-作用域
tool name、category、description、kind(read/write)、status 工具注册表,启动时同步;category 代码声明
audit_log user_id、token_id、tool_name、params_digest、result_status、cost_ms、client_ip、client_type 审计;client_type 区分 mcp-kimi/mcp-claude/cli
admin_user account、password_hash 后台管理员

均带标准审计五件套(create/modify id/time、soft_delete)。

8. 安全基线

  • dev/内网测试阶段允许裸跑 HTTP(客户端 URL 用 http://);对外/生产必须 HTTPS(Nginx 终结 TLS,ak 在 HTTP 下明文可被抓包)
  • ak 只存哈希;审计不记录 ak;限流按 token 维度(防 agent 死循环打爆库)
  • 数据访问全部只读账号;scope 最小化发放,新工具默认不可用
  • 可选:Nginx IP 白名单(客户端出口固定时)

9. 阶段规划

阶段 内容 验收
P1 骨架 工程初始化、版本组合验证、三数据源连通、codegen 跑通 demo 工具经 MCP 调通
P2 权限 ak 鉴权 Filter、scope 校验、数据权限引擎、审计 不同权限组的人查询结果隔离正确
P3 首批工具 代理商查询 + 客户(物业)查询(定义见 §9.1)+ tool 表同步 Kimi/Claude 双端实测
P4 管理后台 用户/token、工具、作用域、审计四页面(Vue3 内嵌) 管理员全流程自助配置
P5 加固 限流、ES 接入(按需)、写工具评审开放、CLI(Go 单二进制) 生产上线

9.1 首期工具定义(P3,均已确认)

对应现有接口 /agent/listByPage 与 /property/listByPage,数据均查 m0 库,行级权限照搬 §3.3。入参精选(不做 beanToWrapper 全字段放行),枚举值写入工具 description 供模型使用;名称富化(supervisorName/agentName/createName)批量查询,消除老实现的 N+1。

agent.list.query(分类 customer,读)

  • 语义照搬现有页面:isagent=1 时只查下级代理(supervisor_id = 自己agentId);isagent=0 看全部。额外允许入参传 id 精确查询(可查到自身)。
  • 入参:id(精确)、name(模糊)、level(0总部/1一级/2二级)、channelNo(Z/F/D)、province、city、serialnumber、page(默认1)、rows(默认20,上限100)。不含 type(已确认去掉)。
  • 出参:id、name、level、channelNo、serialnumber、supervisorName、province/city/countyseat、contact、phone、agencyarea、memo、createTime。
  • 已裁剪:开票信息、银行账户、营业执照图片 URL(OSS 依赖,二期再议)。

property.list.query(分类 customer,读)

  • 语义照搬:完整版 getByIsAgent(下级代理 + warehouse_agent_auth 代管 + agent_manage_district_auth 代管物业 + 销售非负责人规则)。销售非负责人规则 2026-09-03 决策:1:1 复刻老代码 saleFlag 分支(WrapperUtils.java:716-735)——可见范围 = 自己创建的物业(create_id=自己)∪ 代管授权非空时 agent_id IN (下级+代管,移除自己) 的物业,即 id IN (...) 结构,且与其余规则互斥(saleFlag 命中即 return);不再按 §3.3 表格收窄为 AND create_id=自己。不含 enterprise_type=2(物业用户限自己物业)限制——物业用户不查此列表,与老接口 /property/listByPage(PropertyController.java:95)语义一致(2026-09-03 决策;§3.3 通用规则表保留该条,仅此处不适用)。
  • 入参:name(模糊)、agentId、serialnumber、province、city、contact、phone、page、rows。
  • 出参:id、name、serialnumber、agentName、createName(销售)、province/city/countyseat、address、contact、phone、createTime。
  • 已裁剪:开票信息、银行账户。

10. 已明确的取舍记录

  • 不移植字段级(page_field 六表)功能权限 → 用 scope 替代,更简单且服务端强制
  • 作用域纯手工配置,不做代码内默认归属 → 新工具默认关闭
  • 工具分类(category)代码注册时声明,页面只读 → 分类与代码业务归属不漂移
  • token 可长期有效 → 靠禁用回收,过期时间可选
  • CLI 复用 /mcp 端点做 MCP 客户端,不开第二套服务 → 权限逻辑只有一份;Go 单二进制分发,与服务端 Java 栈解耦
  • 不依赖老 jar,Entity 自行生成 → 与老系统零代码依赖
  • 前后端一体打包单 jar → 可逆决定,将来可拆
  • 一作用域一 Token(2026-09-04)→ 收回某能力不需整把吊销;数据模型不变(token_scope 每把只写一行)
  • 长期 Token 仅按用户首次创建返回明文一次;后续复用元信息,不对外提供二次获取明文接口(服务端仅用于禁用/审计与治理)
  • Open API 鉴权用配置文件静态密钥(dev)→ 服务间调用方少,落库+轮换留给生产阶段
  • 列表类接口一律服务端分页,pageSize/rows 上限 100(超出截断为 100)→ 防止一次性拉全表,MCP 工具与后台 API 同一尺度

11. 开发环境(dev)

本机即开发环境并兼任部署机:程序直接跑在本机,dev 阶段裸跑 HTTP(§8),端口避开老系统的 8091。数据库/Redis/ES 连接信息沿用 xiaoqu-franchisee/pom.xml dev profile:

组件 地址 账号 库/DB
MySQL m0(权限库) 192.168.1.181:3306 root xiaoqu_comples_d(user/agent/authority/代管物业表在此)
MySQL m1(商城库) 192.168.1.181:3306 root xiaoqu_mall_d(按需启用)
MySQL m2(业务库) 192.168.1.181:3306 root xiaoqu_franchisee_d(warehouse_agent_auth 在此)
Redis 192.168.1.181:6379 密码见 pom dev db5(entity:User:{id}、Dictionary)、db13
ES 192.168.1.152:9200 无认证 预留,一期不用
  • 密码等敏感值不入库本文档;dev 环境 DB/Redis 连接信息明文写在新系统 application-dev.yml(内网 dev 库,与 pom.xml dev profile 明文同一惯例)。
  • 自有库表(§7)建在同实例新 schema:dev 用 xiaoqu_agent_svc_d(沿用 _d 后缀约定),首次部署前手工建库。
  • 新系统与老的 dev 后端(Jetty 8091)可同时运行,互不干扰——注意两边连的是同一套 dev 数据库和 Redis,老系统dev库的数据变动会同时影响两边的查询结果。

12. 开发流程约定

  • 一律用 git worktree 开发:每个需求从 main 拉分支到 ../oldproject-worktrees/<feature>/,在 worktree 里改代码、跑测试;验证通过后合并回 main 再推送远程。主检出(本目录)保持可随时部署的干净状态。
  • 合并回 main 后用 xiaoqu-agent-service/deploy/deploy.sh restart 完成构建+重启,接口实测通过再 git push。
  • 后台列表页统一服务端分页(page 从 1 开始,pageSize 默认 20、上限 100);MCP 工具 rows 同样上限 100,超出截断并在 notice 字段提示。
  • 新增/修改 MCP 工具必须同步更新 doc/agent-mcp-tools-reference.md(入参、出参、权限语义、示例),工具对外契约以该文档为准。