mcp-removal-and-datasource-fix-plan.md 8.8 KB

MCP 移除与多数据源 / 案件隔离修复计划

状态:Phase 0-3 已实施,Phase 4 自动验证已完成(人工场景见 §6.2) 背景:生产不允许同账号多端登录(联调可用环境变量开启);MCP 相关能力不再使用,全部移除。 关联审计:CaseRoutingDataSource / CaseDataSourceRegistry / CaseContextHolder / 跨案件接口.


1. MCP 盘点与移除

1.1 盘点结果

位置 内容
后端主代码 module/agent/mcp/:AgentScopeMcpToolProvider、AgentScopeToolCallback、McpCaseSession;vo/McpServerInfoVO
后端接口 GET /capability/mcp 及 AgentCapabilityService.mcpServerInfo()
配置 application.yaml 的 spring.ai.mcp.server 段;SaTokenWebConfig 的 4 条 MCP 白名单
依赖 spring-ai-starter-mcp-server-webmvc、spring-ai-starter-mcp-server
测试 AgentScopeMcpToolProviderTest、McpCaseSessionSlotsTest
前端 无功能性 MCP;仅 4 个 MCP 专用工具展示名和注释
数据库 无 MCP 表/列/种子数据

1.2 已执行

  • 删除 4 个后端 MCP 主类 + 2 个 MCP 测试类
  • 删除 /capability/mcp 接口、service 方法、VO 及 Impl 中的 @Value/字段/joinPath
  • 删除 spring.ai 配置段、2 个 Maven starter、sa-token MCP 白名单
  • 保留 spring-ai-bom 与 spring-ai-tika-document-reader(Tika 仍被文件解析使用)
  • 重跑 python ai-server/scripts/gen_field_labels.py,清除 field-labels.json 中的 MCP 类
  • 前端删除 4 个 MCP 工具展示名与 MCP 注释;不删除「插件与技能」页面(它是技能/工具,不是 MCP)
  • 删除本机运行时 ai-electron/data/codex-home/config.toml 的 [mcp_servers.mcpsse]
  • 同步 QUICK_START.md、AI_AGENT.md、docs/design/ai-gateway-user-sticky-routing.md
  • 全仓 grep -ri mcp ai-server/src ai-electron/frontend/src ai-electron/electron ai-server/pom.xml 无业务命中

1.3 说明

agentscope-harness 会传递引入 io.modelcontextprotocol.sdk:mcp。本期已在 ai-server/pom.xml 对 agentscope-harness 增加 exclusion,产物 jar 中不再包含任何 mcp-*.jar;AiServerApplicationTests.contextLoads() 与全量单测已回归通过。


2. 生产单端登录

  • 整个 sa-token 配置从 application-dev.yaml 移到基础 application.yaml,避免新增 prod/test profile 时漏配而退回 Sa-Token 默认值
  • is-concurrent: ${SA_TOKEN_CONCURRENT:false}:默认生产单端;联调启动加 SA_TOKEN_CONCURRENT=true 可多端
  • is-share: false、max-login-count: -1 保留并补注释
  • 同步 SysUserService、VerifyPwdDTO、QUICK_START.md 的说明
  • 案件数据源归属增加 ownerToken:联调多端时,一个 token 退出/换案不会关闭另一个 token 正在使用的案件
    • CaseDataSourceRegistry.OpenedCase 增加 ownerToken
    • CaseInfoService.open/closeOwnedCases 按 token 过滤
    • SysUserService.releaseOpenedCases(userId, token):登出按 token,管理员踢人按用户
    • CaseSessionListener.doLogout 按 token;doKickout/doReplaced 按用户

3. 案件上下文一致性

3.1 CaseAccessGuard

新增 common/context/CaseAccessGuard:

  • requireOpenCaseId():无案件 400
  • requireCurrentCase(requestedCaseId):请求 caseId 与当前案件不一致 409
  • requireOwnedCurrentCase(caseId):再校验 case_info.owner_id
  • 历史 owner_id == null 的案件记 warn 并放行,待数据迁移后收紧

3.2 已接入的入口

  • AgentChatServiceImpl:createSession / listSessions / getSession / listMessages / requireSessionInOpenCase
  • InsightServiceImpl:createSession / listSessions / listClues / getMessageTrace / requireSession
  • AiCleanService:run / judge / revert / dataPage
  • ChatAttachmentService.upload:请求 caseId 必须等于当前案件
  • AgentPythonFileController:workspaceId 必须等于当前案件
  • requireSessionInOpenCase / InsightServiceImpl.requireSession 去掉“无案件放行”

3.3 前端换案同步

  • appWindow.ts 新增 reloadSmartWindowIfOpen():只在工作台已打开时 reloadIfOpen
  • case.ts 的 afteropenCaseAction 换案后调用,避免工作台继续用旧 workspaceId
  • 退出/登出仍通过 APP_RESET 关闭工作台

4. 写错库与文件路径

4.1 SpecialDate(确定的写错库)

  • SpecialDateService.create/update 增加 @DS("slave") + @Transactional
    • 原因:saveBatch 走 SqlSession.insert,不经过 mapper 代理,mapper 上的 @DS 不生效
    • 不能只加在 private 的 saveSpecialDate(self-invocation 不生效)
  • EntryRecMapper 补 @DS("slave")

4.2 文件路径

  • 新增 common/utils/WorkspacePathGuard:归一化 + 符号链接 + toRealPath 三层校验
  • /dm/preFile:目录/文件都必须在 workspace/<当前caseId> 内
  • /dm/doClean:优先用 DB 中 fileId 对应路径,并限制在当前案件;不再信任客户端 filePath
  • /sys/downloadFile:下载路径必须在当前案件 workspace 内
  • /py/files/{workspaceId}/...:workspaceId 必须等于当前案件

4.3 案件库路径

  • CaseInfoService.open:dbPath 必须存在、非空;不限制所在目录(允许历史案件/外部挂载目录)
  • CaseDataSourceRegistry.open:文件不存在/为空直接拒绝,避免 DuckDB 静默建空库

5. 生命周期与缓存

  • CaseInfoService.open:先校验容量与 dbPath → 先打开新数据源 → 再关闭旧数据源;失败不丢旧案件
  • 换案/退案/删案前调用 CaseActivityProbe,有在途清洗/治理任务返回 409
  • CaseDataCache.initCache 失败只记日志,不阻断开案
  • delete 删除当前案件时清 token、SSE、GOVERN_CONFIG
  • 新增 CaseDataCleanupService:按 caseId 清理聊天/研判/AI 清洗/附件(行、文件、向量)
  • GovernCache.getSql() 不再写静态 TABLE_MAPPINGS,消除并发串表名
  • CaseDataCache.cleanGovernConfig 在 exit/delete/closeOwnedCases/登出/回收路径调用
  • SseDTO 增加 caseId,发送时按当前上下文补齐;前端进度页丢弃非当前案件的帧
  • strict: true:@DS 拼错直接报错,不再静默回退主库

6. 验证

6.1 已通过

  • mvn -pl ai-server -am -DskipTests compile / package
  • 全量单测:467 个,0 失败、0 错误、17 skipped(BUILD SUCCESS)
  • SqlAnalysisToolTest:9/9(含 read_csv/glob/read_parquet 拒绝)
  • AgentCapabilityToolkitTest:1/1
  • SpecialDateServiceRoutingTest:1/1(@DS + @Transactional)
  • CaseAccessGuardTest:4/4(无案件/上下文不一致/他人案件/本人案件)
  • AiServerApplicationTests.contextLoads():1/1(含 strict=true、MCP SDK exclusion)
  • 前端 pnpm build 通过
  • 前端 pnpm type:check:改动文件无新增错误(仓库存在既有 src/trans 等类型错误)
  • 全仓源码 MCP 引用清零;产物 jar 中无 mcp-*.jar、无 MCP class

6.2 待人工验收

  1. MCP:/mcpstreamable、/mcpsse、/capability/mcp 均不可用;Agent 对话/工具正常
  2. 单端登录:第二次登录旧 token 401;旧案件数据源释放
  3. 多窗口:主窗口 A→B,工作台自动 reload,后续请求全部作用于 B
  4. 跨案件:A 传 B 的 workspaceId/sessionId/jobId 返回 403/404;无案件访问聊天/研判 400
  5. SpecialDate:新增/修改写案件 DuckDB;主库无 special_date;失败回滚
  6. 文件:../../other-case/x.xlsx、绝对路径、符号链接均被拒绝
  7. 生命周期:有清洗/治理任务时换案/退案/删案返回 409
  8. SSE:旧案件进度不显示在新案件页面

6.3 数据审计 SQL(发布前执行)

-- 无归属案件
select id, name, db_path from case_info where owner_id is null;

-- db_path 为空(无法开案;目录位置不再限制,只要文件存在即可)
select id, name, db_path from case_info
where db_path is null or trim(db_path) = '';

-- 主库孤儿 AI 数据(示例:不存在的 case)
select distinct case_id from agent_chat_session
where case_id not in (select id from case_info);
select distinct case_id from agent_insight_session
where case_id not in (select id from case_info);
select distinct case_id from ai_clean_job
where case_id not in (select id from case_info);

7. 剩余风险

  1. owner_id == null 的历史案件仍按 warn 放行,需迁移后收紧。
  2. Python 工具是兜底黑名单,不是真沙箱;生产建议关闭或容器化。
  3. 删除案件的主库数据清理是尽力而为,失败会记 error 日志,需按 caseId 补删。
  4. 工作台刷新依赖 Electron IPC;浏览器多标签页仍共享同一 token,换案后需手动刷新另一个标签页。