Java 服务升级 MCP 服务实战

简介: MCP(Model Context Protocol)是由OpenAI牵头的标准化协议,基于JSON-RPC 2.0,支持stdio/HTTP/WebSocket三种通信模式,旨在安全、高效打通大模型与现有业务系统。其核心价值在于零侵入改造存量服务,一次封装即可跨平台复用,兼顾安全性、可观测性与生态兼容性。

一、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服务初始化成功的日志,说明服务正常启动。应用启动后,同时提供三种能力:

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

目录
相关文章
|
10天前
|
存储 监控 Cloud Native
吃透云原生可观测:Metrics、Logging、Tracing 架构底层逻辑与实战全指南
云原生可观测性是应对分布式系统复杂性的核心能力,以Metrics(指标)、Logging(日志)、Tracing(链路追踪)三大支柱为支撑,通过TraceId串联,实现故障快速定位、性能优化与根因分析。OpenTelemetry提供统一标准与自动埋点能力。
286 1
|
13天前
|
存储 自然语言处理 算法
Elasticsearch 核心命脉:倒排索引、分片机制与全链路高性能调优实战
本文深度解析Elasticsearch三大核心:倒排索引(Term Dict/Posting List/FST压缩)、分片机制(主/副本协同、路由算法)及全链路调优(写入/查询/分片/JVM),辅以ES 8.x实战代码,助开发者突破性能瓶颈,构建高可用、高性能搜索系统。
249 1
|
13天前
|
消息中间件 存储 调度
RocketMQ 两大核心特性深度拆解:事务消息与延时消息,从原理到实战全打通
RocketMQ作为阿里开源的金融级消息中间件,以高可靠、高吞吐、低延迟著称。其事务消息通过两阶段提交+回查机制,解决本地事务与消息发送的原子性问题;延时消息在5.x中升级为毫秒级任意时间定时消息,基于TimerStore与时间轮实现高性能调度,二者共同支撑分布式系统核心一致性与定时场景。
230 1
|
2月前
|
JSON Java 数据格式
Feign 复杂对象参数传递避坑指南:从报错到优雅落地
本文深入剖析了SpringCloud Feign在复杂对象参数传递中的常见问题及解决方案。文章首先分析了GET请求传递复杂对象失败的底层原因,包括HTTP规范约束和Feign参数解析逻辑。针对GET场景,提供了四种解决方案:@SpringQueryMap(首选)、手动拆分属性+@RequestParam、MultiValueMap封装和自定义FeignEncoder,详细比较了各方案的优缺点和适用场景。对于POST场景,推荐使用@RequestBody注解传递JSON请求体。
670 6
|
6天前
|
人工智能 安全 Java
告别 AI 代码乱炖!GitHub 爆火中文 Vibe Coding 指南,Java 开发者的 AI 编程终极工作流
《Vibe Coding中文指南》系统解析由Karpathy提出的“氛围编程”范式:以自然语言驱动AI生成代码,开发者聚焦意图对齐与结果校验。全文涵盖本质认知、Java工程化落地(Cursor+Claude 3.5实战)、标准化工作流及避坑指南,助力开发者从执行者升级为AI时代的决策者。
1509 8
|
11天前
|
存储 Java 中间件
分布式协调双雄深度拆解:ZooKeeper 与 Nacos 从底层原理到生产实战全指南
本文深度解析ZooKeeper与Nacos两大分布式协调中间件:ZooKeeper专注强一致协调,基于ZAB协议与ZNode模型,适用于大数据生态;Nacos则提供AP/CP双模、三层数据隔离及长轮询机制,是云原生下配置中心+服务发现的一站式选择。二者核心能力、架构差异与选型建议全面对比,附生产实践与避坑指南。
496 6
|
10天前
|
Java 关系型数据库 MySQL
DDD 领域驱动设计:从战略到战术,终结微服务拆分的所有混乱
本文深入剖析微服务拆分困境,指出问题根源在于混淆技术边界与业务边界。提出DDD(领域驱动设计)作为破局之道:以战略设计(领域划分、统一语言、事件风暴、上下文映射)确定微服务合理边界;以战术设计(四层架构、聚合根、值对象等)保障领域模型内聚。结合电商订单域完整落地示例,揭示DDD本质是“先懂业务,再写代码”的设计思想。
219 3
|
11天前
|
存储 NoSQL Java
扛住百万级 QPS:高并发架构核心三板斧全解
本文系统阐述高并发架构三大核心支柱:流量削峰(前端拦截、网关限流、应用缓冲、分布式限流)、异步化(本地CompletableFuture与RocketMQ分布式解耦)及水平扩展(无状态化、服务注册发现、读写分离与分库分表),并以秒杀系统为例实战整合,兼顾避坑指南与概念辨析。
159 3
|
11天前
|
缓存 NoSQL Java
高并发系统性能优化全链路实战:端到端榨干系统性能,百万 QPS 零卡顿
本文系统阐述高并发系统端到端全链路性能优化方法,涵盖接入层(HTTP/3、CDN、LVS)、网关层(Spring Cloud Gateway调优)、服务层(JDK21虚拟线程、线程池、Undertow、Protobuf)、缓存层(多级缓存、Caffeine、Redis)、数据库(索引/SQL/事务/连接池)及OS硬件层优化,并强调压测定位、避坑指南与闭环迭代。
337 3
|
10天前
|
存储 安全 Java
别让你的 Java 应用裸奔!OWASP Top10 全漏洞原理、复现与一站式防护方案
本文详解Java应用十大安全风险(OWASP Top10),涵盖失效访问控制、加密失效、注入攻击等核心漏洞的原理、复现代码及防护方案,结合Spring生态最佳实践,助力开发者构建高安全性企业级系统。
251 1