MCP 很火,来看看我们直接给后台管理系统上一个 MCP?

什么是 MCP

引用一些官方的介绍吧:

Model Context Protocol (MCP) 是一个开放协议,它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能。无论你是构建 AI 驱动的 IDE、改善 chat 交互,还是构建自定义的 AI 工作流,MCP 提供了一种标准化的方式,将 LLM 与它们所需的上下文连接起来。

大白话就是一个数据通信的应用协议,约定了应用和大模型之间如何传递数据进行无缝连接。

本文主要讲的是 MCPSSE+HTTP 方式的使用。

先举个荔枝吧:)

当下背景

  1. 服务器通过 Ollama 部署了一些乱七八糟的模型,用于提供给公司内部的朋友们使用。
  2. 另一台服务器上有一个公司内部的 ERP 系统,管理着公司大量的数据信息。
  3. 你从隔壁社区听到了 MCP 的概念。

那我们能在这个背景下玩一些什么事情呢?

先看截图:

我们使用的客户端是 CherryStudio ,左边是我们的 ERP 系统,右边是 Ollama 跑的一个小 7B 的通义千问开源模型。

我们直接通过 CherryStudioMCP 协议接入功能,直接和 ERP 系统进行通信,实现 ERP 系统的数据查询和操作。

如果我们把 CherryStudio 换成手机上的 Siri,身边的小爱同学呢?

Siri 可以通过快捷指令来完成,小爱同学可以通过小爱技能来完成,当然,体验肯定没有直接内置 MCP 来得快体验好。

着手分析

首先,我们先了解一下 MCP 的架构设计时序图:

sequenceDiagram participant User as User participant CherryStudio as CherryStudio participant Server as Server participant Ollama as Ollama User->>CherryStudio: 打开软件 CherryStudio-->>Server: **SSE** 兄弟,我们聊会 Server--)CherryStudio: **SSE** 好,你有事的话 POST 这个地址(endpoint) CherryStudio-->>Server: **POST** 兄弟,自我介绍一下(initalize) Server--)CherryStudio: **SSE** 好,这是我的基本信息(serverInfo) CherryStudio-->>Server: **POST** 兄弟,我收到了,我准备好了(initialized) CherryStudio-->>Server: POST: 兄弟,你有MCP的工具吗(tools/list) Server--)CherryStudio: **SSE** 我提供了几个工具(tools) User->>CherryStudio: 输入: 禁用张三的账号 CherryStudio->>Ollama: POST: 带工具调用 `禁用张三的账号` Ollama-)CherryStudio: 意图识别: {工具:禁用账号,参数:张三} CherryStudio-->>Server: **POST** 请求发送 {工具:禁用账号,参数:张三} Server-->>CherryStudio: 执行工具并 **SSE** 推送结果 CherryStudio->>Ollama: 整理下收到的结果 Ollama-)CherryStudio: 返回处理后的结果 CherryStudio-)User: 显示给用户看

开始开发

有了架构图了,那开发起来倒是没有什么难事了:

当然,你可以使用官网提供的一些 SDK 来做,不过吧,很多问题,你可以先试试了来评论区讨论~。。。

我们就不考虑上 SDK 啦,直接在项目里生撸!

项目技术栈

  • 运行时:Java17
  • 框架: SpringBoot
  • ORM: JPA

来吧,直接开始。

MCP 的基础数据结构

基础结构

json 复制代码
{
  "id": 0,
  "jsonrpc": "2.0"
}

请求结构 extends 基础结构

所有发送给 MCP 服务器的请求都是这个结构:

ts 复制代码
interface Request {
  // 请求的ID
  id: number

  // 请求的协议 固定2.0
  jsonrpc: "2.0";

  // 请求的方法
  method: string;

  // 请求的参数
  params?: { ... };
}

例如 方法 initalize 的请求结构:

json 复制代码
{
  "id": 0,
  "jsonrpc": "2.0",
  "method": "initalize",
  "params": {
    // 客户端的一些能力
    "capabilities": {},
    "clientInfo": {
      // 一些客户端信息,比如名称、版本等
    }
  }
}

又例如 函数调用的 请求结构

json 复制代码
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "disableUserByName",
    "arguments": {
      "name": "张三"
    }
  }
}

响应结构 extends 基础结构

所有通过 SSE 推送给客户端的响应都是这个结构:

ts 复制代码
interface Response {
  id: 0;
  jsonrpc: "2.0";
  result: {
    // 一些数据信息
  };
  error: {
    // 一些错误信息
  };
}

SSE 服务

SpringBoot 下开启一个 SSE 服务简单得不要不要的:

java 复制代码
public final static ConcurrentHashMap<String, SseEmitter> EMITTERS = new ConcurrentHashMap<>();

@GetMapping(value = "sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() throws IOException {
    String uuid = UUID.randomUUID().toString();
    SseEmitter emitter = new SseEmitter();
    sseEmitter.send(SseEmitter.event()
            .name("endpoint")
            .data("/mcp/messages?sessionId=" + uuid)
            .build()
    );
    EMITTERS.put(uuid, emitter);

    // 可以加点心跳

    emitter.onCompletion(() -> EMITTERS.remove(uuid));
    emitter.onTimeout(() -> EMITTERS.remove(uuid));
    return emitter;
    return sseEmitter;
}

这里需要注意的是,MCP 要求连接上后必须发送一次消息,内容是 MCP 服务用于接受 POST 请求的 URL。

好,这个服务有了,客户端就可以通过这个服务来收我们要下发的消息了。

Message POST API

接下来,我们来实现这个复杂一点的 POST 请求:

java 复制代码
@PostMapping("messages")
public Json messages(HttpServletRequest request, @RequestBody McpRequest mcpRequest) {
    String uuid = request.getParameter("sessionId");
    if (Objects.isNull(uuid)) {
        return Json.error("sessionId is required");
    }
    String method = mcpRequest.getMethod();

    switch(method){
      case "initalize":
        // 这个请求是初始化请求,需要返回一些服务器信息给客户端
        break;
      case "tools/call":
        // 这个请求是工具调用请求,需要返回执行结果给客户端
        break;
      case "tools/list":
        // 这个请求是工具列表请求,需要返回一些工具列表给客户端
        break;
      default:
    }
}

请注意,所有请求都不是 HTTP 直接响应,而是通过刚才的 SSE 通道推送回去。

initalize 初始化

初始化请求需要响应给客户端的是服务器的一些基本信息:

json 复制代码
{
  id: id,
  jsonrpc: "2.0",
  result: {
    // 一些服务能力
    capabilities: {},
    serverInfo: {
      name: "服务器名称",
      version: "1.0.0"
    }
  }
}

这时候,客户端已经可以显示服务器的基本信息了。

请求工具列表

SSE 服务器收到到请求后,需要响应给客户端的是工具列表:

json 复制代码
{
  "id": 0,
  "jsonrpc": "2.0",
  "result": {
    "tools": [
      {
        "name": "disableUserByName",
        "description": "禁用一个用户的账号",
        "inputSchema": {
          "type": "object",
          "properties": {
            "nickname": {
              "type": "string",
              "description": "名称"
            }
          },
          "required": ["nickname"]
        }
      }
    ]
  }
}
执行工具

SSE 服务器需要执行工具时,会得到这个结构体:

json 复制代码
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "disableUserByName",
    "arguments": {
      "name": "张三"
    }
  }
}

你可以在执行一些代码后,返回下面的结构体:

json 复制代码
{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "好,张三被我干掉了"
      }
    ]
  }
}

到这里,几乎完成了整个流程。

基于注解的封装

我们因为使用的 JavaSpringBoot , 所以我们使用了 @McpMethod 注解配合 Reflections 来实现自动注册工具。

java 复制代码
@McpMethod("modifyEmailByName")
@Description("modify user new email by name")
public String modifyEmailByName(
        @Description("the name of user, e.g. 凌小云")
        String name,
        @Description("the new email of user, e.g. [email protected]")
        String email
) {
    List<UserEntity> userList = filter(new UserEntity().setNickname(name));
    DATA_NOT_FOUND.when(userList.isEmpty(), "没有叫 " + name + " 的用户");
    userList.forEach(user -> {
        updateToDatabase(get(user.getId()).setEmail(email));
    });
    return "已经将 " + userList.size() + " 个叫 " + name + " 的用户邮箱修改为 " + email;
}

只要标记了 @McpMethod 注解, MCP 服务器就会自动注册这个方法。

然后你就可以通过 CherryStudio 等工具来调用这个方法了。

动动嘴的事情~

总结

我们通过上述的方式完成了一个的 MCP 服务, 并且也可以为我们的一些其他系统进行扩展,用大模型来改造这些系统的使用方式,美滋滋。

当然,这里还有很多问题需要我们解决,比如权限控制。

完整的代码我们放在了我们的 SPMS_Server 项目以及 AirPower4J 基础库里了:

展望

我倒是很悲观,现在满脑子都是 小爱同学,把张三的辞职报告审核通过一下

等各种终端设备都支持 MCP 协议了,我们再来玩更多的事情吧。

各位周末愉快,Bye.

相关推荐
李长渊哦6 分钟前
引入其他 YML 配置源 —— Spring Boot 中的 `import` 功能
数据库·spring boot·后端
高建伟-joe6 分钟前
Spring Boot Tomcat 漏洞修复
java·spring boot·后端·网络安全·tomcat
uhakadotcom36 分钟前
Python 缓存利器:`cachetools`
后端·面试·github
tan180°1 小时前
版本控制器Git(4)
linux·c++·git·后端·vim
龙雨LongYu121 小时前
Go执行当前package下的所有方法
开发语言·后端·golang
程序员小刚1 小时前
基于springboot + vue 的实验室(预约)管理系统
vue.js·spring boot·后端
程序员小刚1 小时前
基于SpringBoot + Vue 的校园论坛系统
vue.js·spring boot·后端
bobz9652 小时前
软件 ipsec 对接 h3c 防火墙 ipsec 对上了一半
后端
Asthenia04122 小时前
Java线程:如何防止虚假唤醒?从简单到复杂的探索之旅
后端