Java 服务升级 MCP 服务实战

0 阅读19分钟

一、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(64NOT NULL COMMENT '用户姓名',
  `phone` VARCHAR(11NOT NULL COMMENT '手机号码',
  `email` VARCHAR(128DEFAULT 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, Userimplements 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<UsergetUserByPage(
            @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<UsergetUserByIds(@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<StringObjectresult = 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<StringObjectgetUserPageList(
            @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<StringObjectresult = Maps.newHashMap();
        try {
            Page<UserpageData = 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<StringObjectaddUser(
            @McpParam(
                    name = "userJson",
                    description = "用户信息JSON字符串,必须包含userName(用户姓名)、phone(手机号)字段,可选包含email(邮箱)、status(状态)字段",
                    required = true
            ) String userJson
    ) {
        Map<StringObjectresult = 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<StringObjectupdateUser(
            @McpParam(
                    name = "userJson",
                    description = "用户信息JSON字符串,必须包含id(用户ID)字段,可选包含userName、phone、email、status字段",
                    required = true
            ) String userJson
    ) {
        Map<StringObjectresult = 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<StringObjectupdateUserStatus(
            @McpParam(
                    name = "id",
                    description = "用户主键ID,必填参数",
                    required = true
            ) Long id,
            @McpParam(
                    name = "status",
                    description = "用户状态,必填参数,0代表禁用,1代表正常",
                    required = true
            ) Integer status
    ) {
        Map<StringObjectresult = 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<StringObjectgetUserByPhone(
            @McpParam(
                    name = "phone",
                    description = "用户的手机号码,必填参数",
                    required = true
            ) String phone
    ) {
        Map<StringObjectresult = 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服务初始化成功的日志,说明服务正常启动。应用启动后,同时提供三种能力:

  1. 原有REST接口能力,可通过Swagger访问http://127.0.0.1:8080/swagger-ui.html 验证
  2. MCP HTTP服务,访问路径为http://127.0.0.1:8080/mcp/jsonrpc
  3. 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 常见问题与解决方案

  1. 大模型参数格式不匹配问题 问题表现:大模型传入的参数格式不符合业务要求,导致业务逻辑报错 解决方案:MCP适配层实现严格的参数校验与格式转换,设置合理的参数默认值,异常场景返回清晰的错误描述,引导大模型自动修正参数
  2. 工具描述不清晰导致调用错误 问题表现:MCP工具与参数的描述不精准,大模型无法正确理解工具用途与参数要求,导致调用错误 解决方案:每个工具与参数都必须添加详细、精准的描述,明确说明工具的使用场景、参数含义、取值范围、格式要求与示例,确保大模型能准确理解
  3. WebSocket长连接断连问题 问题表现:WebSocket长连接超时断连,导致大模型无法正常调用服务 解决方案:配置合理的心跳机制,实现客户端与服务端双向心跳检测,设置自动重连机制,保证长连接的稳定性
  4. 事务一致性问题 问题表现:MCP工具调用涉及多步数据操作,出现异常时无法保证事务一致性 解决方案:完全复用原有业务层的编程式事务逻辑,MCP适配层不处理事务,所有事务控制都在原有业务层实现,保证数据一致性
  5. JSON序列化异常问题 问题表现:大模型与服务端之间的参数传递出现JSON序列化与反序列化异常 解决方案:统一使用fastjson2作为JSON序列化工具,配置全局序列化规则,处理日期、枚举、空值等特殊场景,保证序列化与反序列化的一致性

六、扩展能力与进阶玩法

6.1 多服务聚合MCP网关

当企业存在多个存量业务服务时,可搭建统一的MCP网关,聚合所有存量服务的MCP能力,统一对外暴露服务端点。大模型仅需连接一个MCP网关,即可调用所有存量服务的业务能力,同时实现统一的鉴权、限流、审计、监控与生命周期管理。

6.2 双向上下文推送

利用MCP协议的双向通信能力,存量业务服务可主动向大模型客户端推送业务数据变更、事件通知、实时告警等上下文信息,大模型可实时获取最新的业务数据,提升响应的准确性与时效性,实现真正的智能化业务交互。

6.3 场景化提示词模板封装

MCP协议不仅支持工具能力封装,还支持标准化的提示词模板定义。可将企业业务场景的标准化提示词封装到MCP服务中,大模型可直接调用,实现业务场景的标准化、规范化落地,降低大模型的使用门槛,保证输出结果的一致性。

结尾

存量业务服务是企业多年沉淀的核心资产,在大模型智能化升级的浪潮中,MCP协议为这些核心资产的复用提供了标准化、低成本、安全可控的解决方案。通过零侵入的适配层改造,企业无需重构原有业务系统,即可快速将沉淀多年的业务能力接入全球主流大模型生态,实现业务的智能化升级与价值重构。