1. MCP协议在企业系统集成中的实战应用
最近在技术社区看到不少关于MCP协议的讨论,作为在企业内部系统集成领域摸爬滚打多年的开发者,我意识到这可能是一个改变游戏规则的协议。经过两周的实践验证,我们成功将MCP协议应用于公司ERP系统与本地部署的大模型集成,实现了通过自然语言操作后台系统的功能。下面分享这个项目的完整实现过程。
1.1 为什么选择MCP协议
在传统企业系统中,我们通常需要开发专门的API接口来实现系统间的数据交互。但随着大模型技术的普及,这种固定接口的模式越来越难以满足灵活交互的需求。MCP(Model Context Protocol)协议的出现正好解决了这个问题,它提供了一套标准化的方式,让大模型能够理解并操作系统提供的各种功能。
我们选择MCP协议主要基于以下考虑:
- 协议标准化:MCP定义了清晰的通信规范,避免了每次集成都要重新设计协议
- 双向通信:支持SSE(Server-Sent Events)和HTTP POST组合,实现实时交互
- 工具自动发现:客户端可以动态获取系统提供的功能列表
- 自然语言适配:协议设计考虑了大模型的输入输出特点
1.2 项目背景与目标
我们公司的技术架构包含以下关键组件:
- 模型服务器:基于Ollama部署的通义千问7B开源模型,提供本地化的大模型服务
- ERP系统:Java开发的内部管理系统,包含用户管理、审批流程等核心业务功能
- 客户端:使用CherryStudio作为测试客户端,后期计划扩展到移动设备
项目目标是让用户能够通过自然语言指令直接操作系统功能,比如"禁用张三的账号"或"查询上季度销售数据"。这需要建立ERP系统与大模型之间的可靠通信渠道。
2. MCP协议架构深度解析
2.1 核心通信流程
MCP协议采用SSE+HTTP POST的组合通信方式,这是经过实践验证的高效方案。整个交互过程可以分为以下几个阶段:
- 连接建立:客户端通过SSE连接到服务器,服务器返回POST接口地址
- 初始化握手:客户端发送初始化请求,交换双方的能力信息
- 工具发现:客户端获取系统提供的功能列表
- 工具执行:客户端发起功能调用请求,服务器执行并返回结果
mermaid复制sequenceDiagram
participant Client
participant Server
Client->>Server: SSE连接请求
Server->>Client: 返回POST端点(endpoint)
Client->>Server: POST初始化请求(initialize)
Server->>Client: SSE返回服务信息(serverInfo)
Client->>Server: POST工具列表请求(tools/list)
Server->>Client: SSE返回可用工具列表(tools)
Client->>Server: POST工具调用请求(tools/call)
Server->>Client: SSE返回执行结果(result)
2.2 关键数据结构设计
MCP协议基于JSON-RPC 2.0规范,所有消息都遵循特定的格式要求。理解这些数据结构对实现协议至关重要。
2.2.1 基础消息结构
所有MCP消息都包含以下基础字段:
json复制{
"id": 123,
"jsonrpc": "2.0"
}
id:请求/响应的唯一标识,用于匹配请求和响应jsonrpc:固定为"2.0",表明协议版本
2.2.2 请求消息扩展
客户端发起的请求消息额外包含:
typescript复制interface Request {
method: string;
params?: object;
}
method:请求的方法名,如"initialize"、"tools/list"等params:可选的参数对象,内容取决于具体方法
2.2.3 响应消息扩展
服务器返回的响应消息额外包含:
typescript复制interface Response {
result?: object;
error?: {
code: number;
message: string;
data?: any;
};
}
result:成功时的返回结果error:失败时的错误信息
2.3 协议状态机
理解MCP协议的状态流转对正确实现协议非常重要。以下是简化的状态转换图:
- 未连接 → 已连接:SSE连接建立成功
- 已连接 → 已初始化:完成initialize握手
- 已初始化 → 工具就绪:获取tools/list成功
- 工具就绪 → 执行中:发起tools/call请求
- 执行中 → 工具就绪:收到执行结果
重要提示:服务器必须严格维护会话状态,拒绝非法状态转换。例如,未初始化的会话直接调用工具应该返回错误。
3. Java实现MCP服务端
3.1 技术选型与项目搭建
我们选择以下技术栈实现MCP服务端:
- Java 17:使用最新的LTS版本,利用Record等新特性
- Spring Boot 3.x:提供完善的Web和SSE支持
- JPA/Hibernate:与现有ERP系统数据层集成
项目基础依赖:
xml复制<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 其他必要依赖 -->
</dependencies>
3.2 SSE服务实现
SSE(Server-Sent Events)是MCP协议的基础通信通道。Spring Boot提供了开箱即用的SSE支持:
java复制@RestController
@RequestMapping("/mcp")
public class McpSseController {
private static final ConcurrentHashMap<String, SseEmitter> SESSIONS = new ConcurrentHashMap<>();
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
String sessionId = UUID.randomUUID().toString();
SseEmitter emitter = new SseEmitter(30 * 60 * 1000L); // 30分钟超时
// 发送初始消息,包含POST端点信息
try {
emitter.send(SseEmitter.event()
.name("endpoint")
.data("/mcp/messages?sessionId=" + sessionId)
);
} catch (IOException e) {
throw new RuntimeException("SSE连接建立失败", e);
}
// 维护会话状态
SESSIONS.put(sessionId, emitter);
emitter.onCompletion(() -> SESSIONS.remove(sessionId));
emitter.onTimeout(() -> SESSIONS.remove(sessionId));
return emitter;
}
}
关键实现细节:
- 每个SSE连接分配唯一sessionId,用于关联后续POST请求
- 设置合理的超时时间(如30分钟),平衡资源占用和用户体验
- 使用线程安全的ConcurrentHashMap管理活跃会话
- 在连接建立时立即发送POST端点信息,这是MCP协议的要求
3.3 消息处理端点实现
MCP协议要求所有客户端请求都通过HTTP POST发送到指定端点。我们需要实现一个统一的消息处理器:
java复制@PostMapping("/messages")
public ResponseEntity<Void> handleMessage(
@RequestParam String sessionId,
@RequestBody McpRequest request) {
// 验证会话有效性
if (!SESSIONS.containsKey(sessionId)) {
return ResponseEntity.notFound().build();
}
SseEmitter emitter = SESSIONS.get(sessionId);
switch (request.method()) {
case "initialize":
handleInitialize(emitter, request);
break;
case "tools/list":
handleToolsList(emitter, request);
break;
case "tools/call":
handleToolsCall(emitter, request);
break;
default:
sendError(emitter, request.id(), "Method not supported");
}
return ResponseEntity.accepted().build();
}
注意:所有处理都是异步的,HTTP接口只接受请求,实际响应通过SSE通道返回。这是MCP协议的设计特点。
3.4 初始化处理实现
初始化是MCP协议的第一个关键交互。客户端通过initialize方法声明自己的能力,服务器返回自身信息:
java复制private void handleInitialize(SseEmitter emitter, McpRequest request) {
McpResponse response = new McpResponse(
request.id(),
Map.of(
"capabilities", Map.of(
// 服务器支持的能力
"toolUse", true
),
"serverInfo", Map.of(
"name", "ERP Integration Server",
"version", "1.0.0"
)
)
);
sendResponse(emitter, response);
}
初始化阶段需要关注:
- 交换双方的能力信息,为后续交互奠定基础
- 服务器应诚实地声明自己支持的功能,避免后续出现兼容性问题
- 可以在此阶段进行权限验证等安全检查
3.5 工具列表管理
MCP协议的核心价值在于动态工具发现机制。我们需要实现tools/list方法来返回系统提供的功能:
java复制private void handleToolsList(SseEmitter emitter, McpRequest request) {
List<Map<String, Object>> tools = toolRegistry.getAllTools().stream()
.map(tool -> Map.of(
"name", tool.name(),
"description", tool.description(),
"inputSchema", tool.inputSchema()
))
.toList();
McpResponse response = new McpResponse(
request.id(),
Map.of("tools", tools)
);
sendResponse(emitter, response);
}
工具定义示例:
json复制{
"name": "disableUserByName",
"description": "禁用一个用户的账号",
"inputSchema": {
"type": "object",
"properties": {
"nickname": {
"type": "string",
"description": "用户姓名"
}
},
"required": ["nickname"]
}
}
工具列表管理的关键点:
- 每个工具需要明确定义输入参数和类型
- 描述信息应该清晰准确,帮助大模型正确使用工具
- 可以根据会话上下文动态过滤工具(如基于权限)
3.6 工具调用执行
工具调用是MCP协议最复杂的部分。客户端通过tools/call方法请求执行特定功能:
java复制private void handleToolsCall(SseEmitter emitter, McpRequest request) {
String toolName = ((Map<String, String>)request.params().get("name"));
Map<String, Object> arguments = (Map<String, Object>)request.params().get("arguments");
try {
Object result = toolRegistry.executeTool(toolName, arguments);
McpResponse response = new McpResponse(
request.id(),
Map.of("content", List.of(
Map.of("type", "text", "text", result.toString())
))
);
sendResponse(emitter, response);
} catch (Exception e) {
sendError(emitter, request.id(), "Tool execution failed: " + e.getMessage());
}
}
工具执行结果格式:
json复制{
"content": [
{
"type": "text",
"text": "已禁用用户张三"
}
]
}
工具调用实现要点:
- 参数验证是必须的,防止非法输入
- 执行过程应该有完善的异常处理
- 结果格式应该符合协议规范
- 考虑添加执行超时机制
4. 高级功能与优化实践
4.1 基于注解的工具注册
为了简化工具开发,我们实现了一套基于注解的自动注册机制。开发者只需使用特定注解标记工具方法:
java复制@McpTool(name = "userManager")
public class UserManagementTools {
@McpMethod("disableUserByName")
@Description("根据姓名禁用用户账号")
public String disableUser(
@Description("用户姓名") String nickname) {
// 实际业务逻辑
return "已禁用用户: " + nickname;
}
}
注解处理器核心逻辑:
java复制public class McpToolScanner {
public void scanAndRegister(String... basePackages) {
Reflections reflections = new Reflections(basePackages);
// 查找所有带有@McpTool的类
Set<Class<?>> toolClasses = reflections.getTypesAnnotatedWith(McpTool.class);
for (Class<?> toolClass : toolClasses) {
Object toolInstance = createInstance(toolClass);
McpTool toolAnnotation = toolClass.getAnnotation(McpTool.class);
String toolPrefix = toolAnnotation.name();
// 注册类中的所有@McpMethod方法
for (Method method : toolClass.getMethods()) {
if (method.isAnnotationPresent(McpMethod.class)) {
registerMethod(toolInstance, toolPrefix, method);
}
}
}
}
}
这种设计带来的好处:
- 开发者只需关注业务逻辑,不用手动注册工具
- 工具方法可以自动生成文档和schema
- 代码组织结构更清晰,维护更方便
4.2 权限控制集成
企业系统必须考虑权限控制。我们在MCP协议基础上添加了权限验证层:
- 会话级别权限:在initialize阶段验证用户身份
- 工具级别过滤:根据用户角色过滤tools/list返回的结果
- 参数级别校验:在执行具体工具时验证数据访问权限
权限验证示例:
java复制@McpMethod("disableUserByName")
public String disableUser(
@AuthCheck(requiredRole = "ADMIN") String sessionId,
String nickname) {
// 验证通过后执行实际逻辑
}
权限控制注意事项:
- 默认拒绝所有请求,显式声明所需权限
- 权限错误应该返回明确的错误信息
- 考虑添加操作审计日志
4.3 性能优化策略
MCP协议作为实时交互通道,性能优化尤为重要:
- SSE连接复用:保持长连接,避免频繁重建
- 消息压缩:对大型消息启用gzip压缩
- 批量处理:对高频小消息进行批量发送
- 资源清理:及时释放闲置连接和资源
性能监控指标示例:
- 活跃连接数
- 平均响应时间
- 消息吞吐量
- 错误率
5. 客户端集成与测试
5.1 使用CherryStudio测试
CherryStudio是支持MCP协议的测试客户端,集成过程非常简单:
- 配置MCP服务器地址
- 建立连接后,客户端会自动发现可用工具
- 通过自然语言指令测试工具调用
测试要点:
- 验证各种边界条件
- 测试错误处理流程
- 检查性能表现
5.2 开发自定义客户端
如果需要开发自己的客户端,核心流程如下:
javascript复制async function connectToMcpServer(url) {
// 1. 建立SSE连接
const eventSource = new EventSource(url);
eventSource.addEventListener('endpoint', (e) => {
const postUrl = e.data;
// 2. 发送初始化请求
fetch(postUrl, {
method: 'POST',
body: JSON.stringify({
id: 1,
jsonrpc: "2.0",
method: "initialize",
params: {
capabilities: {},
clientInfo: {
name: "My Custom Client"
}
}
})
});
});
// 3. 处理服务器响应
eventSource.addEventListener('message', (e) => {
const response = JSON.parse(e.data);
// 根据响应类型处理...
});
}
客户端开发注意事项:
- 正确处理连接中断和重试
- 实现请求-响应匹配机制
- 提供友好的用户界面展示工具和结果
6. 项目总结与经验分享
经过一个月的开发和测试,我们的MCP集成项目已经稳定运行在生产环境。系统目前支持30+个工具方法,日均处理500+次自然语言指令。以下是从中获得的关键经验:
- 协议理解要透彻:初期因对MCP状态机理解不深,导致了一些边界条件问题
- 工具设计要原子化:复合工具难以被大模型正确使用,拆分为小工具后效果显著提升
- 错误处理要友好:清晰的错误信息能大幅提高调试效率
- 性能监控不可少:SSE连接数监控帮助我们及时发现并解决了资源泄漏问题
遇到的典型问题及解决方案:
- 问题1:大模型有时会错误理解工具参数
- 解决:优化工具描述和参数schema,添加更详细的示例
- 问题2:长耗时操作阻塞SSE通道
- 解决:实现异步执行机制,立即返回接收确认,通过独立事件推送结果
- 问题3:工具版本兼容性问题
- 解决:在initialize阶段加入协议版本协商机制
项目代码已开源:
对于考虑实施类似项目的团队,我的建议是:从小范围试点开始,先集成几个核心工具验证技术路线,再逐步扩大范围。同时要特别注意权限控制和操作审计,确保系统安全性。