159 lines
14 KiB
Markdown
159 lines
14 KiB
Markdown
# MCP 优化方案
|
||
|
||
> 基于当前 MCP(LLM Agent 工具)实现现状整理。日期:2026-09-16。
|
||
> 前提:完整测试 **132 个用例全部通过**(含数据权限守卫 `everyMcpMapperMethodShouldBeProtectedOrExplicitlyTenantSafe`、SQL 可重写守卫等),服务可正常编译运行。
|
||
|
||
---
|
||
|
||
## 一、现状总览
|
||
|
||
### 1.1 MCP 工具清单(24 个)
|
||
|
||
| # | 工具名 | 归属类 | 能力 |
|
||
|---|---|---|---|
|
||
| 1 | `crm_universal_search` | CrmUniversalSearchToolProvider | 跨实体关键词通用搜索(客户/商机/拓客/日报/签到/待办/活动/跟进) |
|
||
| 2 | `crm_user_profile` | UserProfileToolProvider | 当前用户画像(账号/姓名/角色/部门/租户) |
|
||
| 3 | `crm_entity_detail` | CrmEntityDetailToolProvider | 按实体类型+ID 查详情,可带相关明细(customer/opportunity/daily_report/checkin/sales_expansion/channel_expansion/crm_expansion/todo) |
|
||
| 4 | `crm_work_report_search` | WorkReportSearchToolProvider | 日报分页检索 |
|
||
| 5 | `crm_opportunity_search` | OpportunitySearchToolProvider | 商机分页检索(阶段/金额/排序/字段投影) |
|
||
| 6 | `crm_customer_search` | CrmDetailSearchToolProvider | 客户分页检索 |
|
||
| 7 | `crm_checkin_search` | CrmCheckinSearchToolProvider | 外勤打卡分页检索 |
|
||
| 8 | `crm_expansion_search` | CrmExpansionSearchToolProvider | 销售/渠道拓客分页检索 |
|
||
| 9 | `crm_crm_expansion_search` | CrmCrmExpansionSearchToolProvider | CRM 拓客(crm_crm_expansion)检索 |
|
||
| 10 | `crm_followup_search` | CrmFollowupSearchToolProvider | 商机/拓客跟进检索 |
|
||
| 11 | `crm_todo_search` | CrmTodoSearchToolProvider | 待办检索 |
|
||
| 12 | `crm_work_today_status` | CrmWorkTodayStatusToolProvider | 今日工作状态 |
|
||
| 13 | `crm_org_user_search` | CrmOrgUserSearchToolProvider | 组织用户检索 |
|
||
| 14 | `crm_report_query` | CrmReportQueryToolProvider | 报表/统计查询(见 1.2) |
|
||
| 15 | `crm_report_catalog` | CrmReportCatalogToolProvider | 报表与查询能力目录 |
|
||
| 16 | `crm_metadata_catalog` | CrmMetadataToolProvider | 模块/字段/工具能力目录 |
|
||
| 17 | `crm_dict_options` | CrmDictOptionsToolProvider | 系统字典类型和选项 |
|
||
| 18 | `crm_field_comment` | CrmFieldCommentToolProvider | MCP 返回字段的中文注释(读 DB COMMENT) |
|
||
|
||
> 注:上表已覆盖全部 ToolProvider 类。`crm_search`/`crm_detail` 等仅表示内部方法,非独立注册工具。
|
||
|
||
### 1.2 `crm_report_query` 支持的 reportType(10 种)
|
||
|
||
`dashboard_summary`、`sales_performance`、`opportunity_funnel`、`opportunity_trend`、`daily_report_completion`、`checkin_summary`、`expansion_summary`、`customer_summary`、`todo_summary`、`channel_analytics`(下含 `channelTierDistribution`、`channelIntentDistribution`、`channelCoverageSummary`、`channelRevenueTier`、`channelContactWecomDistribution` 5 个子维度)。
|
||
|
||
### 1.3 Mapper 规模(LlmMcpMapper.xml:83 个 select)
|
||
|
||
- **列表/搜索**:35 个(各实体 search + count + universalSearch)
|
||
- **Dashboard/汇总**:27 个(dashboard* 、*Summary、funnel/trend/completion、channel* 聚合)
|
||
- **详情**:15 个(select*Detail + 相关明细 select*Followups/Contacts/Coverages/Comments/Messages/Opportunities)
|
||
- **基础数据**:selectUserProfile/Roles/Orgs、searchOrgUsers、searchOrganizations、searchRoles、selectDictTypes/Options、selectColumnComments
|
||
|
||
### 1.4 数据权限模型
|
||
|
||
McpDataPermissionInterceptor 通过 SQL 重写注入 owner 过滤。策略见 `buildPolicies()`([McpDataPermissionInterceptor.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java#L402-L445)):
|
||
|
||
| RESOURCE 常量 | owner 列 | 关键受管方法 |
|
||
|---|---|---|
|
||
| RESOURCE_DAILY_REPORT | r.user_id | searchWorkReports、详情、comments、messages、日报完成率 |
|
||
| RESOURCE_CHECKIN | c.user_id | 签到系列 |
|
||
| RESOURCE_CUSTOMER | c.owner_user_id | 客户系列 |
|
||
| RESOURCE_EXPANSION | s.owner_user_id / c.owner_user_id / ce.owner_user_id | 销售/渠道/CRM 拓客系列 + channel* 聚合 |
|
||
| RESOURCE_WORK | t.user_id / l.operator_user_id / u.user_id | 待办、活动、今日状态 |
|
||
| RESOURCE_OPPORTUNITY | o.owner_user_id(+项目归属地投影,预售后台可见) | 商机系列、跟进、漏斗 |
|
||
| RESOURCE_ALL | u.user_id | searchOrgUsers |
|
||
|
||
受两类守卫测试保护:每个 mapper 方法要么受策略保护、要么显式 tenant-safe;受保护语句必须能被重写器正确注入过滤条件。
|
||
|
||
---
|
||
|
||
## 二、可优化点
|
||
|
||
按「优先级 P0(高) / P1(中) / P2(低)」与「收益」分组,每条含:现状、优化建议、影响。
|
||
|
||
### P0-1 `actual_signed_summary` 报表「已声明但不可用」(实际缺陷)
|
||
|
||
- **现状**:[CrmReportQueryToolProvider.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java)
|
||
- reportType 参数枚举里含 `"actual_signed_summary"`(L64),`crm_report_catalog` 也声明了该 reportType(L61);
|
||
- 但 `query()` 的分发 switch(L87-98)**没有 `case "actual_signed_summary"`**,会落到 `default` 抛 `BusinessException("不支持的 reportType")`;
|
||
- 而 L110 的 `if ("actual_signed_summary".equals(reportType)) { signedSummary(...) }` 分支位于 switch **之后**,因 switch 先抛异常而**永远不可达**。
|
||
- 底层 `signedSummaryRows / signedSummaryQuality` mapper 已就绪(XML 2508/2548)且已注册数据权限,只是没被 switch 接上。
|
||
- **建议(核心修复,最小改动)**:在 switch 增加 `case "actual_signed_summary" -> signedSummaryRows/...` 或在 `query()` 入口先处理该 reportType,使声明与实现一致。建议顺带新增一个「枚举声明的 reportType 与 switch 分支一一对应、且 catalog 同步」的守卫测试,防止再次漂移。
|
||
- **影响**:Agent 按 catalog 调用该报表当前会直接报错 → 属功能性缺陷,建议优先修复。
|
||
|
||
### P0-2 商机筛选/权限口径的「预售后台」与 SDK 差异(一致性风险中)
|
||
|
||
- **现状**:[McpDataPermissionInterceptor.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java#L436-L442) 中商机策略 `preSalesVisible=true` 并带 `o.project_ownership_location` 投影,而日报/签到/客户/拓客等 `preSalesVisible=false`。这个差异是**有意的**(对应商机模块的预售后台可见规则),但**没有注释说明为什么商机特殊**。
|
||
- **建议**:在策略处补注释说明「商机为何独享 preSalesVisible 与项目归属地投影」,避免后续维护误改。纯注释改动,零回归。
|
||
- **收益**:提升可维护性,防止未来误删权限差异导致商机数据越权或越收窄。
|
||
|
||
### P0-3 用户相关查询的敏感字段需审校(安全加固)
|
||
|
||
- **现状**:用户画像已暴露工号/职位/入职日期等。需核验 `selectUserProfile` / `searchOrgUsers` **是否为显式列**(而非 `*`),确认无 password/salt/token/密钥字段带出。
|
||
- **建议**:通读 `selectUserProfile`、`searchOrgUsers` 两个查询,确认全部为**显式列**,且不含密码/盐/token/密钥类字段。若有 `*`,改为显式列。同时补一个**黑名单断言测试**:任何 MCP 返回的字段名不得出现 `password`/`secret`/`token`/`key` 等(类似现有权限守卫)。
|
||
- **收益**:杜绝敏感信息经 MCP 泄漏的隐患,测试固化。
|
||
|
||
### P0-4 数据权限守卫测试存在,但缺乏「跨实体 join 越权」用例
|
||
|
||
- **现状**:现有测试验证每个受保护语句可被重写,但未覆盖**关联明细查询**(如 `selectChannelExpansionOpportunities`、`selectCustomerOpportunities`、`selectSalesExpansionOpportunities`)在注入 owner 条件后,相联合的 on 条件是否真正约束到目标行。
|
||
- **建议**:增加针对性测试:构造「主实体可见但关联实体不可见」的数据,断言关联明细不会越权返回超此 owner 范围的行。
|
||
- **收益**:堵住「通过详情联查旁路越权」的潜在漏洞。
|
||
|
||
### P1-1 报表 `channel_analytics` 的 groupBy 口径无文档校验
|
||
|
||
- **现状**:`channel_analytics` 有参数 `groupBy`([CrmReportQueryToolProvider.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java#L53)),但内部各聚合方法(tier/intent/coverage/revenue/wecom)对 `groupBy` 的支持程度看不到统一校验。
|
||
- **建议**:明确每个子维度「是否接受 groupBy、接受哪些取值」,对不支持的值在入口做显式校验并报错(而非静默忽略),避免返回误导性聚合。补充文档化说明到 `crm_report_catalog`。
|
||
- **收益**:提升统计可信度、避免 Agent 拿到「以为分组了其实没有」的数据。
|
||
|
||
### P1-2 `crm_report_catalog` 的工具清单可能未随新工具同步
|
||
|
||
- **现状**:[CrmReportCatalogToolProvider.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/tools/CrmReportCatalogToolProvider.java#L36-L50) 内联维护了一个工具清单(含 `crm_universal_search`、`crm_work_report_search` 等),但**未见 `crm_user_profile`/`crm_work_today_status`/`crm_crm_expansion_search`/`crm_report_query` 之外全部 24 个工具是否都在清单内**,且是手工维护,易遗漏。
|
||
- **建议**:改为**从注册表动态生成**工具清单(遍历已注册 ToolProvider 的名称与描述),消除手工同步漂移;或至少补齐缺失工具并加「清单与注册工具数量一致」的守卫测试。
|
||
- **收益**:Agent 发现工具的能力与真实能力一致,避免「目录找不到但实际能调」。
|
||
|
||
### P1-3 `selectColumnComments`(crm_field_comment)的表白名单是硬编码
|
||
|
||
- **现状**:[FieldCommentRegistry.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/tools/support/FieldCommentRegistry.java) 以硬编码 `tables()` / `TABLE_COLUMNS` 维护「别名→物理列」映射,新增表/字段需人工双处登记(列表+注释),易漏。
|
||
- **建议**:为映射的完整性加守卫测试:`TABLE_COLUMNS` 键集合与 `tables()` 返回的说明集合一致;并可核对每个映射的物理列是否在本批次已暴露的查询字段内。
|
||
- **收益**:固化「新增字段必登记注释」这一约定,避免字段有值但无注释。
|
||
|
||
### P1-4 `limit` 上限保护已较好,但部分列表可细化
|
||
|
||
- **现状**:商机等已统一 `pageSize/limit` 默认 10、最大 50([OpportunitySearchToolProvider.java](file:///Users/kangwenjing/Downloads/crm/unis_crm/backend/src/main/java/com/unis/crm/llm/tools/OpportunitySearchToolProvider.java#L56-L57),含 offset 计算)。这是好实践。
|
||
- **建议**:核验所有其余 search 工具(日报/客户/打卡/拓客/跟进/待办/CRM拓客)是否都用了同一套 `MAX_LIMIT` 与 `getOffset` 兜底,避免个别工具直接透传 limit 造成大结果集。可抽公共参数抽取器统一。
|
||
- **收益**:统一防爆量,避免个别查询成为性能/响应放大点。
|
||
|
||
### P2-1 列表 keyword 搜索未纳入 coverage/联系人等子表字段
|
||
|
||
- **现状**:销售/渠道列表已新增 `coverageSummary` 聚合列(本轮),但 **keyword 匹配仍只针对主表字段**,不会命中覆盖省/市或渠道联系人姓名。
|
||
- **建议**:如需「按覆盖地市、按联系人搜索」,在 keyword 的 LIKE 条件里补充对 coverage 子表(省/市)与 contact 表的关联匹配。
|
||
- **收益**:搜索召回更贴合业务;成本是 SQL 变复杂,需先确认是否为业务目标。
|
||
|
||
### P2-2 覆盖地市 / 日报消息只存在于「详情联查」,无独立搜索工具
|
||
|
||
- **现状**:`coverage`、`work_report_message` 已通过 `crm_entity_detail` 的相关明细暴露,但无独立搜索入口。
|
||
- **建议**:若 Agent 需要「按覆盖省/市全局统计」或「全局检索某条日报消息」,可新增 `crm_coverage_search` / `crm_report_message_search` 独立工具;否则保持现状(避免工具爆炸)。
|
||
- **收益**:按需增强;过度新增会导致工具面过大、Agent 选择成本上升。
|
||
|
||
### P2-3 未处理缺口(已知合理保留)
|
||
|
||
- `sys_org.parent_id/org_code/sort_order`:框架表无建表脚本,列名未核实,暂不补真实值。
|
||
- `business_calendar_day`、`report_reminder_*`、`speech_recognition_config`、`dashboard_analytics_card_config`:配置类表,未暴露属合理(报告鉴权用白名单、配置不属业务数据)。
|
||
- `crm_oms_dict_mapping`:OMS 后调用映射表,**按需求明确不暴露**。
|
||
|
||
---
|
||
|
||
## 三、推荐落地顺序(不含重构)
|
||
|
||
| 序号 | 项 | 工作量 | 回归风险 |
|
||
|---|---|---|---|
|
||
| 1 | **P0-1 修复 actual_signed_summary 不可用缺陷**(核心) | 小 | 低 |
|
||
| 2 | P0-3 敏感字段审校 + 黑名单测试 | 小 | 低 |
|
||
| 3 | **P0-4 关联明细越权测试** | 中 | 零(纯加测试) |
|
||
| 4 | P1-1 channel_analytics groupBy 校验 | 小 | 低 |
|
||
| 5 | P1-2 report_catalog 动态化/补全 + 守卫 | 中 | 低 |
|
||
| 6 | P1-3 field_comment 注册一致性守卫 | 小 | 零(纯加测试) |
|
||
| 7 | P1-4 limit 兜底统一 | 小-中 | 低 |
|
||
| 8 | P2-1/P2-2 按需 | 视需求 | 视改动 |
|
||
|
||
---
|
||
|
||
## 四、当前状态结论
|
||
|
||
- **功能完整性**:核心业务主表 + 详情 + 报表 + 数据权限均已覆盖,132 用例全绿,服务可启动。**不存在阻断性缺陷**。
|
||
- **最值得优先做**:P0-2(敏感字段黑名单)与 P0-3(关联明细越权测试)——属于「安全加固」性质,风险低、收益高,建议放到下一批次。
|
||
- 其余 P1/P2 多为可维护性与体验增强,可按迭代排期,不必一次性做完。 |