MCP:LLM与知识库之间的通信协议—(1)初认知

为什么需要MCP

众所周知,当代的大模型依然存在着或多或少的幻觉问题,大模型只保证能回答你的问题,但不保证回答的正确性,在越是专业的问题上,幻觉问题越严重

为了解决这个问题,现在的技术方案主要有两种:大模型微调、RAG 。其中,最常用的技术方案就是RAG。RAG通过为大模型外挂知识库,旨在通过从外部知识库中检索相关信息来增强模型生成的内容准确性。 这些知识库来源十分广泛,可以是结构化的数据库、非结构化的文本集合、维基百科、专业文献,甚至是外部API。

但多种多样的知识库来源也引入了新的问题,即不同知识库的访问方式不同 。假设存在M个大模型,N个外部知识库,在这种情况下,我们为每个大模型接入每个外部知识库时都需要编写一套代码。复杂度达到了 M x N

为了解决上述问题,MCP协议应运而生。在该协议下,大模型不再与外部知识库直接相连,而是借由MCP客户端与MCP服务器与外部知识库通信。每个大模型连接MCP客户端,MCP服务器连接每个外部知识库,从而将复杂度降低到了 M + N

该协议本身并不复杂,甚至可能许多公司在此之前已经研发过自己的"MCP"协议

MCP官方定义

MCP(模型上下文协议)是一个使大型语言模型(LLMs,如 Claude)能够与外部工具和数据源交互的协议。使用 MCP,您可以:

  • 构建为 LLMs 提供工具和数据的服务器
  • 将这些服务器连接到兼容 MCP 的客户端
  • 通过自定义功能扩展 LLM 的能力

MCP系统结构

从图中可以看到,MCP的系统协议架构主要包括MCP HostsMCP ClientMCP Server

  • MCP Host(主机应用) :Hosts 是指 LLM 启动连接的应用程序,像Cursor、Claude、Cline 这样的应用程序。
  • MCP Client(客户端) :客户端是用来在 Hosts 应用程序内维护与 Server 之间 1:1 连接。一个主机应用中可以运行多个MCP客户端,从而同时连接多个不同的服务器。
  • MCP Server(服务器) :独立运行的轻量程序,通过标准化的协议,为客户端提供上下文、工具和提示,是MCP服务的核心。

我们可以通过HTTP来理解MCP

scss 复制代码
MCP Host(MCP Client) <--MCP--> MCP Server(Services)

Chrome(Http Client) <--HTTP--> Http Server(Resources)

MCP Hello World

根据上面的理解,实际上利用MCP开发,就和利用Http协议开发一样的逻辑。下面将使用示例来展示MCP的Hello World

MCP Server

为了更直观的体验MCP,本文暂时先使用已经支持了MCP Client的程序来充当Host进行测试,就像我们开发HTTP Server时直接使用Chrome进行测试,而不另开发HTTP Client一样。

虽然官方使用Claude客户端来充当Host,但由于Claude并不支持国区,故本文使用已经支持的DeepChat代替Claude客户端。

导入 SDK

官方中文教程给的Java版的SDK似乎有点问题,这里改用了最新的英文原版提供的Spring版的SDK。

xml 复制代码
<dependencies>
    <dependency>
       <groupId>org.springframework.ai</groupId>
       <artifactId>spring-ai-starter-mcp-server</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
</dependencies>

编写工具

使用@Serevice编写服务层,这里面用于定义有哪些工具

java 复制代码
@Service  // 服务层
public class WeatherService {

    // 响应模板,自然语句格式
    private static final String RESPONSE_TEMPLATE = """
            %s的天气为%s, 气温为%s
            """;

    private final RestClient restClient;

    public WeatherService() {
        String apiUrl = "http://apis.juhe.cn/simpleWeather/query";
        this.restClient = RestClient.builder()
                .baseUrl(apiUrl)
                .defaultHeader("Content-Type", "application/x-www-form-urlencoded")
                .defaultHeader("Accept", "application/json")
                .defaultHeader("User-Agent", "WeatherApiClient/1.0")
                .build();
    }
    
    // LLM可用的工具,要准确描述该工具的作用,用于LLM的查找
    @Tool(description = "获得中国的天气信息")
    public String getWeather(
            @ToolParam(description = "城市") String city
    ) throws UnsupportedEncodingException {

        byte[] bytes = city.getBytes("GBK");
        String u8City = new String(bytes, StandardCharsets.UTF_8);

        String apiKey = "your api key";
        RestClient.ResponseSpec retrieve = this.restClient.get()
                .uri("?key=" + apiKey + "&city=" + u8City)
                .retrieve();
        WeatherPojo body = retrieve.body(WeatherPojo.class);
        try {
            return String.format(RESPONSE_TEMPLATE,
                    u8City,
                    body.getResult().getRealtime().getInfo(),
                    body.getResult().getRealtime().getTemperature()
            );
        } catch (NullPointerException e) {
            return "抱歉,我无法获取到" + u8City + "的天气信息";
        }
    }
}

byte[] bytes = city.getBytes("GBK");

String u8City = new String(bytes, StandardCharsets.UTF_8);

由于默认使用stdio的方式传输,而客户端往IO管道里放数据时使用UTF-8模式encode,而OS(windows)再decode时使用GBK。导致产生乱码,所以这里我们需要把GBK再转回UTF-8,

创建Bean

主要是为了将API的JSON返回值转成Bean

java 复制代码
@Data
public class WeatherPojo {
    private String reason;
    private Result result;
    private int errorCode;

    @Data
    public static class Result {
        private String city;
        private Realtime realtime;

        @Data
        public static class Realtime {
            private String temperature;
            private String humidity;
            private String info;
            private String wid;
            private String direct;
            private String power;
        }
    }
}

Tool注册进入Spring容器

java 复制代码
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
    return  MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}

请求访问

首先,需要在客户端这里配置服务器信息。

json 复制代码
{
  "mcpServers": {
    "mcp-server": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.transport=STDIO",
        "-jar",
        "D:/Documents/IdeaProjects/mcp-server/target/mcpserver-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

然后我们直接对话就行,deepChat软件会自动根据对话内容选择性调用Tool

结语

当前还只是最简单的一种模式,基于标准IO流进行通信,一般用于用户本地使用。不过MCP还支持Http+SSE,相比于标准IO流,这种方式可以更好的支持网络服务。说不定以后会有专门的MCP服务提供商

相关推荐
Persistence___42 分钟前
SpringBoot中的拦截器
java·spring boot·后端
嘵奇1 小时前
Spring Boot 跨域问题全解:原理、解决方案与最佳实践
java·spring boot·后端
景天科技苑3 小时前
【Rust泛型】Rust泛型使用详解与应用场景
开发语言·后端·rust·泛型·rust泛型
lgily-12255 小时前
常用的设计模式详解
java·后端·python·设计模式
意倾城5 小时前
Spring Boot 配置文件敏感信息加密:Jasypt 实战
java·spring boot·后端
火皇4055 小时前
Spring Boot 使用 OSHI 实现系统运行状态监控接口
java·spring boot·后端
薯条不要番茄酱6 小时前
【SpringBoot】从零开始全面解析Spring MVC (一)
java·spring boot·后端
懵逼的小黑子14 小时前
Django 项目的 models 目录中,__init__.py 文件的作用
后端·python·django
小林学习编程15 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
java1234_小锋17 小时前
Spring Bean有哪几种配置方式?
java·后端·spring