AI + MCP
Mesh Platform 将 AI 能力集中在 services/mesh-ai,并通过 MCP(Model Context Protocol)把 CRM 等业务服务的能力转换为模型可调用的工具。当前后端平台版本为 4.1.0,AI 模块使用 Spring AI 2.0.0。
模块结构
services/mesh-ai
├── mesh-ai-api // MCP 公共契约、工具定义与远程服务接口
└── mesh-ai-biz // 智能会话、模型接入、MCP Server、工具与 Access Key
services/mesh-crm/mesh-crm-biz
└── mcp // CRM 工具发现、注册、校验与业务执行mesh-ai-api 中的 McpToolDefinitionRO 定义工具元数据,RemoteMcpToolProviderService 定义 AI 服务调用业务工具的统一契约。业务服务只需实现这个契约,不需要依赖 AI Biz 的内部实现。
项目与 AI 的结合逻辑
业务前端通过平台现有认证和网关进入 AI 会话接口。AiChatServiceImpl 根据会话选择模型配置,并交给 Spring AI ChatClient 处理同步或流式对话。
业务页面
-> Gateway 与平台认证
-> mesh-ai-biz / AiChatServiceImpl
-> 读取模型、会话和工具配置
-> Spring AI ChatClient
-> 模型响应
-> SSE 或普通响应返回业务页面项目接入 AI 时应复用平台的账号、用户和租户上下文。模型 API Key、外部服务地址与超时策略由 AI 模块配置管理,业务模块不直接保存模型密钥。
AI 与 MCP 的结合逻辑
AiChatServiceImpl 在创建 ChatOptions 时调用 McpServerToolService.withExternalTools(...),把启用的外部 MCP 工具装配到本次模型会话。模型选择调用工具后,工具回调进入 AI 工具调度器,再按 mcpModule 找到业务服务提供方。
用户消息
-> AI 会话装配模型参数
-> McpServerToolService 装配可用工具
-> 大模型选择 MCP Tool
-> AiMcpToolDispatcher
-> McpToolProviderRegistry
-> RemoteMcpToolProviderService(Feign)
-> CRM 等业务服务执行工具
-> 工具结果返回模型
-> 模型生成最终回答这条链路将“模型推理”与“业务执行”分离:AI 服务负责会话、模型和工具编排,CRM 等服务负责业务规则、数据权限和真实数据操作。
项目 MCP 与 CRM 的结合
CRM 的 CrmMcpToolFactory 发现并注册声明为 CRM MCP 的工具方法;CrmMcpToolProviderController 实现 RemoteMcpToolProviderService,接收 AI 服务的内部工具调用;CrmMcpToolProviderService 校验请求并转交具体 CRM 工具执行。
当前 CRM 场景工具覆盖客户、跟进、商机等业务查询与处理场景。新增工具时:
- 在所属业务服务中实现工具方法和输入校验。
- 使用项目现有 MCP 工具声明方式注册名称与描述。
- 在 AI 工具配置中启用工具并绑定正确的
mcpModule。 - 验证租户、账号、接口权限和数据权限均生效。
- 用完整 AI 会话验证“模型选择工具—工具执行—模型总结”的闭环。
MCP Server 与访问控制
AI 服务以 Streamable HTTP 方式暴露 MCP Server,默认端点为 /mcp,主要开放 Tool 能力。外部客户端通过平台生成的 MCP Access Key 接入。
Access Key 验证会恢复账号、用户和租户身份,并执行有效性、绑定状态与频率限制检查。密钥只在创建时安全展示,数据库保存摘要;日志中不得记录完整密钥。
外部 MCP Client
-> /mcp
-> AiMcpAccessKeyVerifier
-> 恢复 McpKeyPrincipal(账号 / 用户 / 租户)
-> 可用工具列表
-> 业务工具调用联调检查
mesh-ai-biz与目标业务服务均已注册到 Nacos。- AI 服务已加载模型、MCP Server 和工具配置。
- 工具的模块编码、名称和提供服务一致,工具名称没有重复。
- Access Key 对应的账号与租户绑定有效。
- CRM 工具调用携带正确租户上下文,并继续执行业务数据权限。
- 失败时分别查看 AI 会话、工具调度、Feign 调用和业务服务日志,不要只依据模型返回文本判断原因。
