一、MCP核心底层逻辑与核心价值
1.1 MCP的本质与底层架构
MCP全称Model Context Protocol,是由OpenAI牵头推出的标准化开放协议,核心目标是打通大模型与外部系统、工具、数据之间的壁垒,为大模型提供安全、标准化、可扩展的上下文获取与工具调用能力。
从底层来看,MCP是基于JSON-RPC 2.0规范的应用层协议,定义了大模型客户端(MCP Client)与外部服务(MCP Server)之间的通信标准、消息格式、能力发现与调用规范。其核心采用客户端-服务端架构,支持请求-响应与双向流式推送两种通信模式,传输层原生支持三种标准化方案:
- stdio:基于标准输入输出的通信模式,无需网络配置,适配本地桌面端大模型应用
- HTTP(S) :基于HTTP的请求-响应模式,兼容现有Web生态,易于实现负载均衡与安全防护
- WebSocket:全双工长连接模式,适配低延迟、实时双向通信与流式响应场景
1.2 核心方案边界区分
针对开发者普遍存在的“OpenAPI已能实现大模型调用,为何需要MCP”的疑问,这里做明确的能力边界与适用场景区分:
| 对比维度 | MCP协议 | 传统OpenAPI | 自定义SDK封装 |
| 大模型适配性 | 原生支持工具描述规范,自动生成大模型可识别的能力元数据,零prompt适配 | 需手动编写prompt描述接口规范,适配成本高,不同大模型兼容性差 | 需针对大模型做定制化封装,无统一行业标准 |
| 通信模式 | 支持请求-响应、双向流式推送,全双工通信 | 仅支持单向请求-响应模式 | 需自行实现通信逻辑,无标准化约束 |
| 能力发现 | 标准化服务能力发现机制,客户端自动获取服务端所有可用工具、资源与模板 | 需手动提供OpenAPI文档,大模型无法自动感知能力变更 | 无标准化能力发现机制,需手动同步能力变更 |
| 安全体系 | 内置标准化权限控制、调用审计、上下文隔离机制 | 需自行实现鉴权、限流、审计全链路逻辑 | 需自行封装安全能力,无统一规范 |
| 生态兼容性 | 全球主流大模型与AI应用原生支持,一次改造跨平台复用 | 需针对不同大模型做接口适配,兼容性差 | 仅能在特定场景使用,无法跨平台复用 |
1.3 存量服务改造的核心价值
- 业务能力无损复用:企业沉淀多年的存量业务逻辑无需重构,直接封装为标准化MCP工具,避免重复造轮子
- 零侵入改造:仅需新增适配层代码,原有业务代码、数据库、配置完全无需修改,不影响线上业务稳定性
- 全生态标准化接入:一次改造即可支持所有兼容MCP协议的大模型与AI应用,无需针对不同平台做重复适配
- 安全风险可控:MCP适配层独立实现权限控制、参数校验、限流熔断,与原有业务安全体系解耦
- 极低改造成本:最小化改造仅需新增3个核心文件,即可完成存量服务的MCP能力升级
二、存量服务改造的核心设计原则与全流程
2.1 核心设计原则
- 业务零侵入:原有业务代码完全不修改,所有MCP相关逻辑均在新增的适配层实现,避免对线上业务造成影响
- 最小化改造:完全复用原有Spring生态、业务组件、数据访问层,仅新增MCP依赖与适配代码
- 安全优先:MCP层必须实现全链路参数校验、鉴权授权、限流熔断、调用审计,所有外部请求必须经过安全校验才能进入业务层
- 兼容性保障:完全兼容原有业务的事务机制、异常处理、日志体系,MCP层异常不会扩散到原有业务链路
- 可观测性:MCP调用的日志、指标、链路追踪完全复用原有服务的可观测体系,实现全链路可监控、可排查
2.2 改造全流程
三、全链路改造实战落地
3.1 环境与依赖配置
项目基于JDK 17开发,核心pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>mcp-service-demo</artifactId>
<version>1.0.0</version>
<name>mcp-service-demo</name>
<description>存量服务改造MCP服务示例项目</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<springdoc.version>2.6.0</springdoc.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.2.1-jre</guava.version>
<mcp.version>0.6.0</mcp.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol</groupId>
<artifactId>mcp-spring-boot-starter</artifactId>
<version>${mcp.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2 存量业务服务现状
本次实战以企业最常见的用户管理存量服务为例,该服务已稳定运行,实现了用户全生命周期的业务管理能力,采用MySQL 8.0作为数据存储,MyBatis-Plus作为持久层框架。
3.2.1 数据库表结构
CREATE TABLE IF NOT EXISTS `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户主键ID',
`user_name` VARCHAR(64) NOT NULL COMMENT '用户姓名',
`phone` VARCHAR(11) NOT NULL COMMENT '手机号码',
`email` VARCHAR(128) DEFAULT NULL COMMENT '邮箱地址',
`status` TINYINT NOT NULL DEFAULT '1' COMMENT '用户状态: 0-禁用 1-正常',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone`),
KEY `idx_user_name` (`user_name`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户表';
3.2.2 存量业务核心代码
实体类
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
* @author ken
*/
@Data
@TableName("sys_user")
@Schema(description = "用户信息实体")
public class User {
@TableId(type = IdType.AUTO)
@Schema(description = "用户主键ID", example = "1")
private Long id;
@Schema(description = "用户姓名", example = "张三")
private String userName;
@Schema(description = "手机号码", example = "13800138000")
private String phone;
@Schema(description = "邮箱地址", example = "zhangsan@example.com")
private String email;
@Schema(description = "用户状态: 0-禁用 1-正常", example = "1")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
Mapper接口
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
* @author ken
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Service层接口
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jam.demo.entity.User;
import java.util.List;
/**
* 用户服务接口
* @author ken
*/
public interface UserService {
User getUserById(Long id);
Page<User> getUserByPage(Integer pageNum, Integer pageSize, String keyword, Integer status);
Boolean addUser(User user);
Boolean updateUser(User user);
Boolean updateUserStatus(Long id, Integer status);
User getUserByPhone(String phone);
User getUserByEmail(String email);
List<User> getUserByIds(List<Long> ids);
}
Service层实现类
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 用户服务实现类
* @author ken
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private TransactionTemplate transactionTemplate;
@Override
public User getUserById(Long id) {
if (ObjectUtils.isEmpty(id)) {
log.warn("查询用户信息失败,用户ID为空");
return null;
}
return this.getById(id);
}
@Override
public Page<User> getUserByPage(Integer pageNum, Integer pageSize, String keyword, Integer status) {
int currentPage = ObjectUtils.isEmpty(pageNum) || pageNum < 1 ? 1 : pageNum;
int size = ObjectUtils.isEmpty(pageSize) || pageSize < 1 || pageSize > 100 ? 10 : pageSize;
Page<User> page = new Page<>(currentPage, size);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
queryWrapper.and(wrapper -> wrapper
.like(User::getUserName, keyword)
.or()
.like(User::getPhone, keyword)
.or()
.like(User::getEmail, keyword)
);
}
if (!ObjectUtils.isEmpty(status)) {
queryWrapper.eq(User::getStatus, status);
}
queryWrapper.orderByDesc(User::getCreateTime);
return this.page(page, queryWrapper);
}
@Override
public Boolean addUser(User user) {
if (ObjectUtils.isEmpty(user)) {
log.warn("新增用户失败,用户信息为空");
return Boolean.FALSE;
}
if (!StringUtils.hasText(user.getUserName())) {
log.warn("新增用户失败,用户姓名为空");
return Boolean.FALSE;
}
if (!StringUtils.hasText(user.getPhone())) {
log.warn("新增用户失败,手机号码为空");
return Boolean.FALSE;
}
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
boolean result = save(user);
if (result) {
log.info("新增用户成功,用户ID:{}", user.getId());
}
return result;
} catch (Exception e) {
status.setRollbackOnly();
log.error("新增用户异常,用户信息:{}", user, e);
return Boolean.FALSE;
}
}
});
}
@Override
public Boolean updateUser(User user) {
if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
log.warn("更新用户失败,用户信息或ID为空");
return Boolean.FALSE;
}
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
boolean result = updateById(user);
if (result) {
log.info("更新用户成功,用户ID:{}", user.getId());
}
return result;
} catch (Exception e) {
status.setRollbackOnly();
log.error("更新用户异常,用户ID:{}", user.getId(), e);
return Boolean.FALSE;
}
}
});
}
@Override
public Boolean updateUserStatus(Long id, Integer status) {
if (ObjectUtils.isEmpty(id)) {
log.warn("更新用户状态失败,用户ID为空");
return Boolean.FALSE;
}
if (ObjectUtils.isEmpty(status) || (status != 0 && status != 1)) {
log.warn("更新用户状态失败,状态值非法,用户ID:{}, status:{}", id, status);
return Boolean.FALSE;
}
User user = new User();
user.setId(id);
user.setStatus(status);
return updateUser(user);
}
@Override
public User getUserByPhone(String phone) {
if (!StringUtils.hasText(phone)) {
log.warn("根据手机号查询用户失败,手机号为空");
return null;
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone, phone);
return this.getOne(queryWrapper);
}
@Override
public User getUserByEmail(String email) {
if (!StringUtils.hasText(email)) {
log.warn("根据邮箱查询用户失败,邮箱为空");
return null;
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getEmail, email);
return this.getOne(queryWrapper);
}
@Override
public List<User> getUserByIds(List<Long> ids) {
if (ObjectUtils.isEmpty(ids)) {
log.warn("根据ID列表查询用户失败,ID列表为空");
return Lists.newArrayList();
}
return this.listByIds(ids);
}
}
Controller层(原有REST接口)
package com.jam.demo.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jam.demo.entity.User;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 用户管理控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户信息增删改查接口")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户信息", description = "通过用户主键ID查询用户详情")
public User getUserById(@PathVariable @Parameter(description = "用户ID", required = true) Long id) {
return userService.getUserById(id);
}
@GetMapping("/page")
@Operation(summary = "分页查询用户列表", description = "根据关键词和状态分页查询用户信息")
public Page<User> getUserByPage(
@RequestParam(required = false) @Parameter(description = "页码") Integer pageNum,
@RequestParam(required = false) @Parameter(description = "每页条数") Integer pageSize,
@RequestParam(required = false) @Parameter(description = "搜索关键词") String keyword,
@RequestParam(required = false) @Parameter(description = "用户状态") Integer status
) {
return userService.getUserByPage(pageNum, pageSize, keyword, status);
}
@PostMapping
@Operation(summary = "新增用户", description = "创建新的用户信息")
public Boolean addUser(@RequestBody @Parameter(description = "用户信息", required = true) User user) {
return userService.addUser(user);
}
@PutMapping
@Operation(summary = "更新用户信息", description = "根据用户ID更新用户信息")
public Boolean updateUser(@RequestBody @Parameter(description = "用户信息", required = true) User user) {
return userService.updateUser(user);
}
@PatchMapping("/{id}/status")
@Operation(summary = "更新用户状态", description = "启用或禁用用户")
public Boolean updateUserStatus(
@PathVariable @Parameter(description = "用户ID", required = true) Long id,
@RequestParam @Parameter(description = "用户状态 0-禁用 1-正常", required = true) Integer status
) {
return userService.updateUserStatus(id, status);
}
@GetMapping("/phone/{phone}")
@Operation(summary = "根据手机号查询用户", description = "通过手机号码精确查询用户信息")
public User getUserByPhone(@PathVariable @Parameter(description = "手机号码", required = true) String phone) {
return userService.getUserByPhone(phone);
}
@GetMapping("/email/{email}")
@Operation(summary = "根据邮箱查询用户", description = "通过邮箱地址精确查询用户信息")
public User getUserByEmail(@PathVariable @Parameter(description = "邮箱地址", required = true) String email) {
return userService.getUserByEmail(email);
}
@PostMapping("/batch")
@Operation(summary = "根据ID列表批量查询用户", description = "通过用户ID列表批量查询用户信息")
public List<User> getUserByIds(@RequestBody @Parameter(description = "用户ID列表", required = true) List<Long> ids) {
return userService.getUserByIds(ids);
}
}
3.3 MCP适配层核心开发
存量业务代码完全无需修改,仅需新增MCP适配层代码,即可完成服务能力的MCP协议封装,实现零侵入改造。
3.3.1 MCP服务配置类
package com.jam.demo.config;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.config.McpServerConfig;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* MCP服务配置类
* @author ken
*/
@Configuration
public class McpServerConfiguration {
/**
* 配置MCP服务基础信息
*/
@Bean
public McpSchema.ServerInfo serverInfo() {
return new McpSchema.ServerInfo("user-manage-mcp-server", "1.0.0");
}
/**
* 配置MCP服务能力
*/
@Bean
public McpServerFeatures serverFeatures() {
return new McpServerFeatures(
new McpSchema.ServerCapabilities(
null,
new McpSchema.ToolCapabilities(),
null,
null,
null
),
List.of()
);
}
}
3.3.2 MCP工具适配类
package com.jam.demo.mcp;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.jam.demo.entity.User;
import com.jam.demo.service.UserService;
import io.modelcontextprotocol.server.McpTool;
import io.modelcontextprotocol.server.McpToolParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import java.util.Map;
/**
* 用户管理MCP工具类
* 封装存量用户服务能力,暴露为MCP标准工具
* @author ken
*/
@Slf4j
@RestController
public class UserMcpTool {
@Resource
private UserService userService;
/**
* 根据用户ID查询用户详情
* @param id 用户主键ID
* @return 用户详情信息
*/
@McpTool(
name = "get_user_by_id",
description = "根据用户主键ID查询用户的详细信息,包括姓名、手机号、邮箱、状态等"
)
public Map<String, Object> getUserById(
@McpParam(
name = "id",
description = "用户的主键ID,必填参数",
required = true
) Long id
) {
Map<String, Object> result = Maps.newHashMap();
try {
if (ObjectUtils.isEmpty(id)) {
result.put("success", false);
result.put("message", "用户ID不能为空");
return result;
}
User user = userService.getUserById(id);
if (ObjectUtils.isEmpty(user)) {
result.put("success", false);
result.put("message", "未查询到对应用户信息");
return result;
}
result.put("success", true);
result.put("data", user);
result.put("message", "查询成功");
} catch (Exception e) {
log.error("MCP工具get_user_by_id执行异常,用户ID:{}", id, e);
result.put("success", false);
result.put("message", "查询用户信息异常:" + e.getMessage());
}
return result;
}
/**
* 分页查询用户列表
* @param pageNum 页码
* @param pageSize 每页条数
* @param keyword 搜索关键词
* @param status 用户状态
* @return 分页用户列表数据
*/
@McpTool(
name = "get_user_page_list",
description = "分页查询用户列表,支持通过关键词模糊搜索姓名、手机号、邮箱,也可通过用户状态筛选"
)
public Map<String, Object> getUserPageList(
@McpParam(
name = "pageNum",
description = "页码,默认值为1,最小值为1"
) Integer pageNum,
@McpParam(
name = "pageSize",
description = "每页数据条数,默认值为10,取值范围1-100"
) Integer pageSize,
@McpParam(
name = "keyword",
description = "搜索关键词,支持模糊匹配用户姓名、手机号、邮箱"
) String keyword,
@McpParam(
name = "status",
description = "用户状态筛选,0代表禁用,1代表正常,不传则查询所有状态"
) Integer status
) {
Map<String, Object> result = Maps.newHashMap();
try {
Page<User> pageData = userService.getUserByPage(pageNum, pageSize, keyword, status);
result.put("success", true);
result.put("data", pageData.getRecords());
result.put("total", pageData.getTotal());
result.put("pages", pageData.getPages());
result.put("current", pageData.getCurrent());
result.put("size", pageData.getSize());
result.put("message", "查询成功");
} catch (Exception e) {
log.error("MCP工具get_user_page_list执行异常", e);
result.put("success", false);
result.put("message", "查询用户列表异常:" + e.getMessage());
}
return result;
}
/**
* 新增用户信息
* @param userJson 用户信息JSON字符串
* @return 新增结果
*/
@McpTool(
name = "add_user",
description = "新增用户信息,必填参数为userName和phone,email为选填,status默认值为1(正常)"
)
public Map<String, Object> addUser(
@McpParam(
name = "userJson",
description = "用户信息JSON字符串,必须包含userName(用户姓名)、phone(手机号)字段,可选包含email(邮箱)、status(状态)字段",
required = true
) String userJson
) {
Map<String, Object> result = Maps.newHashMap();
try {
if (!StringUtils.hasText(userJson)) {
result.put("success", false);
result.put("message", "用户信息不能为空");
return result;
}
User user = JSON.parseObject(userJson, User.class);
Boolean addResult = userService.addUser(user);
if (addResult) {
result.put("success", true);
result.put("userId", user.getId());
result.put("message", "新增用户成功");
} else {
result.put("success", false);
result.put("message", "新增用户失败,请检查参数是否正确");
}
} catch (Exception e) {
log.error("MCP工具add_user执行异常,用户信息:{}", userJson, e);
result.put("success", false);
result.put("message", "新增用户异常:" + e.getMessage());
}
return result;
}
/**
* 更新用户信息
* @param userJson 用户信息JSON字符串
* @return 更新结果
*/
@McpTool(
name = "update_user",
description = "根据用户ID更新用户信息,必须包含id字段,可更新字段包括userName、phone、email、status"
)
public Map<String, Object> updateUser(
@McpParam(
name = "userJson",
description = "用户信息JSON字符串,必须包含id(用户ID)字段,可选包含userName、phone、email、status字段",
required = true
) String userJson
) {
Map<String, Object> result = Maps.newHashMap();
try {
if (!StringUtils.hasText(userJson)) {
result.put("success", false);
result.put("message", "用户信息不能为空");
return result;
}
User user = JSON.parseObject(userJson, User.class);
if (ObjectUtils.isEmpty(user.getId())) {
result.put("success", false);
result.put("message", "用户ID不能为空");
return result;
}
Boolean updateResult = userService.updateUser(user);
if (updateResult) {
result.put("success", true);
result.put("message", "更新用户成功");
} else {
result.put("success", false);
result.put("message", "更新用户失败,请检查参数是否正确");
}
} catch (Exception e) {
log.error("MCP工具update_user执行异常,用户信息:{}", userJson, e);
result.put("success", false);
result.put("message", "更新用户异常:" + e.getMessage());
}
return result;
}
/**
* 更新用户状态
* @param id 用户ID
* @param status 用户状态
* @return 更新结果
*/
@McpTool(
name = "update_user_status",
description = "更新用户的启用/禁用状态,0为禁用,1为正常"
)
public Map<String, Object> updateUserStatus(
@McpParam(
name = "id",
description = "用户主键ID,必填参数",
required = true
) Long id,
@McpParam(
name = "status",
description = "用户状态,必填参数,0代表禁用,1代表正常",
required = true
) Integer status
) {
Map<String, Object> result = Maps.newHashMap();
try {
if (ObjectUtils.isEmpty(id)) {
result.put("success", false);
result.put("message", "用户ID不能为空");
return result;
}
if (ObjectUtils.isEmpty(status) || (status != 0 && status != 1)) {
result.put("success", false);
result.put("message", "用户状态非法,只能为0或1");
return result;
}
Boolean updateResult = userService.updateUserStatus(id, status);
if (updateResult) {
result.put("success", true);
result.put("message", status == 1 ? "启用用户成功" : "禁用用户成功");
} else {
result.put("success", false);
result.put("message", "更新用户状态失败,请检查用户ID是否正确");
}
} catch (Exception e) {
log.error("MCP工具update_user_status执行异常,用户ID:{}, status:{}", id, status, e);
result.put("success", false);
result.put("message", "更新用户状态异常:" + e.getMessage());
}
return result;
}
/**
* 根据手机号查询用户信息
* @param phone 手机号码
* @return 用户详情信息
*/
@McpTool(
name = "get_user_by_phone",
description = "根据手机号码精确查询用户的详细信息"
)
public Map<String, Object> getUserByPhone(
@McpParam(
name = "phone",
description = "用户的手机号码,必填参数",
required = true
) String phone
) {
Map<String, Object> result = Maps.newHashMap();
try {
if (!StringUtils.hasText(phone)) {
result.put("success", false);
result.put("message", "手机号码不能为空");
return result;
}
User user = userService.getUserByPhone(phone);
if (ObjectUtils.isEmpty(user)) {
result.put("success", false);
result.put("message", "未查询到对应用户信息");
return result;
}
result.put("success", true);
result.put("data", user);
result.put("message", "查询成功");
} catch (Exception e) {
log.error("MCP工具get_user_by_phone执行异常,手机号:{}", phone, e);
result.put("success", false);
result.put("message", "查询用户信息异常:" + e.getMessage());
}
return result;
}
}
3.3.3 应用配置文件
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
mcp:
server:
http:
enabled: true
path: /mcp/jsonrpc
websocket:
enabled: true
path: /mcp/ws
name: user-manage-mcp-server
version: 1.0.0
3.3.4 项目启动类
package com.jam.demo;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
@OpenAPIDefinition(
info = @Info(
title = "用户管理MCP服务API文档",
version = "1.0.0",
description = "存量服务改造MCP服务示例项目API文档"
)
)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.3.5 MyBatis-Plus分页插件配置
package com.jam.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus配置类
* @author ken
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
四、改造后服务验证与联调
4.1 本地服务启动验证
完成上述配置与代码开发后,启动Spring Boot应用,观察启动日志,若出现MCP服务初始化成功的日志,说明服务正常启动。应用启动后,同时提供三种能力:
- 原有REST接口能力,可通过Swagger访问http://127.0.0.1:8080/swagger-ui.html 验证
- MCP HTTP服务,访问路径为http://127.0.0.1:8080/mcp/jsonrpc
- MCP WebSocket服务,访问路径为ws://127.0.0.1:8080/mcp/ws
4.2 标准化能力发现验证
MCP协议定义了标准化的能力发现机制,客户端可通过initialize方法获取服务端所有可用工具。通过HTTP客户端发送POST请求到http://127.0.0.1:8080/mcp/jsonrpc,请求体如下:
{
"jsonrpc": "2.0",
"id": "1",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "test-client",
"version": "1.0.0"
}
}
}
服务端会返回包含所有MCP工具定义的响应数据,大模型客户端通过该响应自动识别服务能力。
4.3 工具调用验证
通过tools/call方法调用具体的业务工具,例如调用根据ID查询用户的工具,请求体如下:
{
"jsonrpc": "2.0",
"id": "2",
"method": "tools/call",
"params": {
"name": "get_user_by_id",
"arguments": {
"id": 1
}
}
}
服务端会执行对应的业务逻辑,返回标准化的响应结果,完成一次完整的MCP工具调用。
五、生产环境最佳实践与避坑指南
5.1 安全加固最佳实践
- 全链路鉴权:MCP服务必须添加API Key鉴权机制,所有请求必须携带有效的API Key,在MCP拦截器中完成校验,拒绝未授权请求
- 严格参数校验:MCP适配层必须实现全量参数校验,包括必填项校验、格式校验、长度校验、非法字符过滤,防止SQL注入、XSS等安全风险
- 限流熔断保护:使用Resilience4j或Sentinel对MCP工具调用实现限流、熔断、降级,防止大模型高频调用打垮存量业务服务
- 全量调用审计:所有MCP调用必须记录审计日志,包括调用方、调用时间、调用工具、参数、结果、耗时,满足合规审计与问题排查需求
- 传输加密:生产环境必须使用HTTPS/WSS协议,实现传输内容全加密,防止数据泄露
- 最小权限控制:针对不同调用方配置差异化的工具访问权限,只读调用方仅开放查询类工具,严格控制修改类工具的访问权限
5.2 性能优化最佳实践
- 线程池隔离:MCP调用使用独立的线程池,与原有业务线程池完全隔离,防止MCP调用影响原有业务的性能与稳定性
- 多级缓存优化:针对高频查询的MCP工具,添加本地缓存与分布式缓存,减少数据库访问压力,提升响应速度
- 批量处理优化:针对批量操作场景,复用原有业务的批量处理逻辑,减少数据库IO次数,提升处理效率
- 异步非阻塞处理:针对非实时性的操作,采用异步处理模式,快速响应客户端请求,提升调用体验
5.3 常见问题与解决方案
- 大模型参数格式不匹配问题问题表现:大模型传入的参数格式不符合业务要求,导致业务逻辑报错 解决方案:MCP适配层实现严格的参数校验与格式转换,设置合理的参数默认值,异常场景返回清晰的错误描述,引导大模型自动修正参数
- 工具描述不清晰导致调用错误问题表现:MCP工具与参数的描述不精准,大模型无法正确理解工具用途与参数要求,导致调用错误 解决方案:每个工具与参数都必须添加详细、精准的描述,明确说明工具的使用场景、参数含义、取值范围、格式要求与示例,确保大模型能准确理解
- WebSocket长连接断连问题问题表现:WebSocket长连接超时断连,导致大模型无法正常调用服务 解决方案:配置合理的心跳机制,实现客户端与服务端双向心跳检测,设置自动重连机制,保证长连接的稳定性
- 事务一致性问题问题表现:MCP工具调用涉及多步数据操作,出现异常时无法保证事务一致性 解决方案:完全复用原有业务层的编程式事务逻辑,MCP适配层不处理事务,所有事务控制都在原有业务层实现,保证数据一致性
- JSON序列化异常问题问题表现:大模型与服务端之间的参数传递出现JSON序列化与反序列化异常 解决方案:统一使用fastjson2作为JSON序列化工具,配置全局序列化规则,处理日期、枚举、空值等特殊场景,保证序列化与反序列化的一致性
六、扩展能力与进阶玩法
6.1 多服务聚合MCP网关
当企业存在多个存量业务服务时,可搭建统一的MCP网关,聚合所有存量服务的MCP能力,统一对外暴露服务端点。大模型仅需连接一个MCP网关,即可调用所有存量服务的业务能力,同时实现统一的鉴权、限流、审计、监控与生命周期管理。
6.2 双向上下文推送
利用MCP协议的双向通信能力,存量业务服务可主动向大模型客户端推送业务数据变更、事件通知、实时告警等上下文信息,大模型可实时获取最新的业务数据,提升响应的准确性与时效性,实现真正的智能化业务交互。
6.3 场景化提示词模板封装
MCP协议不仅支持工具能力封装,还支持标准化的提示词模板定义。可将企业业务场景的标准化提示词封装到MCP服务中,大模型可直接调用,实现业务场景的标准化、规范化落地,降低大模型的使用门槛,保证输出结果的一致性。
结尾
存量业务服务是企业多年沉淀的核心资产,在大模型智能化升级的浪潮中,MCP协议为这些核心资产的复用提供了标准化、低成本、安全可控的解决方案。通过零侵入的适配层改造,企业无需重构原有业务系统,即可快速将沉淀多年的业务能力接入全球主流大模型生态,实现业务的智能化升级与价值重构。