当前位置: 首页 > news >正文

LLM MCP模型上下文协议快速入门(for Java)

什么是MCP

Model Control Protocol(MCP)是由AI研究机构Anthropic在2023年第二季度首次提出的新型协议规范,旨在解决大语言模型LLM应用中的上下文管理难题。作为LLM交互领域的创新标准,MCP协议在发布后短短一年内已进行了多次更新,最近一次更新是在2025-03-26(Key Changes - Model Context Protocol),包含 添加了一个基于 OAuth 2.1 的全面的授权框架 等内容变更:

协议定义与核心价值

MCP是一套开放式的通信协议,它通过标准化:

  1. 上下文结构化表示(JSON Schema)
  2. 多轮对话状态跟踪机制
  3. 模型控制指令集

使开发者能够精准控制LLM的上下文窗口,解决传统对话系统中存在的:

  • 上下文丢失(Context Bleeding)
  • 指令冲突
  • 长文本处理低效等痛点

成熟度

目前MCP已被Claude系列模型原生支持,并在Llama 3、Mistral等开源模型中实现兼容。行业分析显示,超过43%的企业级LLM应用已开始采用MCP作为首选上下文管理方案(数据来源:2024 ML Stack调查报告)。

MCP的目标对象

刚开始接触MCP的开发者可能会进入一个误区,以为有了MCP后,LLM是不是可以直接调用MCP Server的能力(API)了,其实并不是,LLM实际上是无法感知MCP的存在的。

MCP(Model Control Protocol)的核心目标对象是LLM应用开发者,而不是LLM(大语言模型)本身。要理解这一点,我们需要明确LLM和LLM应用的区别:  

1. LLM vs. LLM应用

  • LLM(大语言模型):指底层的大模型(如GPT-4、Claude、Llama等),它们负责接收输入并生成文本输出,但本身不具备复杂的状态管理或上下文控制能力。  
  • LLM应用:指基于LLM构建的完整系统,如聊天机器人、AI助手、代码生成工具等。这些应用需要管理多轮对话、维护上下文、处理用户指令,并可能集成外部数据或API。  

2. 为什么LLM不能直接使用MCP

LLM本身是“无状态”的——它们仅对当前输入做出响应,而不会自动记住之前的交互。MCP的作用是让LLM应用能更高效地管理上下文,例如:

  • 维护对话历史(避免超出模型的上下文窗口限制)  
  • 动态调整提示词(prompt),确保LLM获得正确的背景信息  
  • 控制模型行为(如切换模式、调整生成参数)  

MCP协议定义了一套标准化的接口,让LLM应用能以结构化的方式与LLM交互,而不是让LLM自己去解析或执行这些逻辑。  

MCP作为外部协议而非模型内置机制,还有两大更关键原因:

  1. 训练成本问题:若MCP逻辑固化到LLM中,协议每次更新都需重新训练模型,带来极高的计算和迭代成本;

  2. 安全与权限挑战:若LLM直接通过MCP调用三方应用,权限管理(如数据访问、API鉴权)将难以控制,增加滥用风险。

因此,MCP设计为应用层协议,由外部系统管理上下文和资源,既保持LLM的通用性,又避免强耦合带来的运维负担。

3. MCP的目标用户

MCP主要面向:

  • LLM应用开发者:需要构建复杂对话系统或AI代理的工程师  
  • AI平台架构师:设计LLM基础设施,优化上下文管理和推理效率  
  • 企业级AI解决方案:需要稳定、可扩展的LLM交互协议  

简而言之,MCP不是让LLM自己“学会”管理上下文,而是为LLM应用提供一套标准化的控制机制,使开发者能更高效地构建可靠的AI系统。

MCP的主要角色和架构

MCP最核心的两个角色就是MCP客户端MCP服务端。参见:Model Context Protocol (MCP) :: Spring AI Reference

MCP Client

MCP 客户端是模型上下文协议 (MCP) 架构中的关键组件,负责建立和管理与 MCP 服务器的连接。对,你没听错,MCP体系中,客户端才是最复杂的,而且通常这个客户端也是由服务端应用(也就是前面说的LLM应用)来“使用”

它实现了协议的客户端功能,处理以下操作:

  • 协议版本协商以确保与服务器的兼容性
  • 能力协商以确定可用功能
  • 消息传输和 JSON-RPC 通信
  • 工具发现和执行
  • 资源访问和管理
  • 提示系统交互
  • 可选功能:
    • 根部管理
    • 采样支持
  • 同步和异步操作
  • 通信选择:
    • 基于 Stdio 的传输,用于基于进程的通信
    • 基于  SSE 客户端传输

你可以看到,通常是MCP客户端在“调用” LLM 和 MCP服务端 

MCP Server

MCP 服务器是模型上下文协议 (MCP) 架构中的基础组件,为客户端提供:

  • 资源:上下文和数据,供用户或 AI 模型使用。它们通常是 只读的,AI 模型可以读取资源数据,但不会直接修改或执行它们,所以一般是指数据库查询。
  • 提示:为用户提供模板化的消息和工作流程
  • 工具:AI模型执行的功能。它们通常是 动态的、有副作用的(如修改数据、发送消息、触发操作),所以一般是指AI调用

它实现了协议的服务器端,负责:

  • 服务器端协议操作实现

    • 工具曝光和发现

    • 基于 URI 访问的资源管理

    • 及时提供和处理模板

    • 与客户进行能力谈判

    • 结构化日志记录和通知

  • 并发客户端连接管理

  • 同步和异步 API 支持

  • 通信实施:

    • 基于 Stdio 的传输,用于基于进程的通信

    • 基于 SSE 服务器传输

通常来说MCP服务器是不直接使用LLM的(当然,如果你这个MCP服务器也是另一些MCP服务器的MCP客户端时也有可能需要直接使用LLM),它通常是我们常见的一个个业务系统。

这里需要特殊说明的是如果MCP客户端和MCP服务器是部署在一起的,那么可以使用Stdio( Standard Input/Output(标准输入/输出) 的缩写,指计算机程序与外部环境(如终端、文件或其他程序)进行数据交互的标准方式。它是操作系统提供的基础通信机制,几乎所有编程语言都支持 stdio 操作)的方式来进行通信。

一言以蔽之

通过MCP的角色、架构分析,大家应该有所感知,MCP就是通过定义一套标准协议,同时标准化通过LLM来使用应用能力来解决我们之前直接使用LLM去编排Function Call难度大、复杂度高以及效率低的问题

实战:构建MCP客户端和服务器(for Java)

说的再多都是虚的,得眼见为实,这里我们通过构建两个Spring应用来演示如何在本地来使用MCP。Spring框架早已提供对调用LLM能力的封装:Spring AI API :: Spring AI Reference,这里我们就使用Spring框架来构建MCP客户端和MCP服务器。

构建MCP服务器

MCP服务器相对比较简单,它负责把业务功能封装成一个个原子能力,供MCP客户端来使用,新建一个Maven项目mcp-server,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>com.manzhizhen.mcp</groupId><artifactId>mcp-study</artifactId><version>0.0.1-SNAPSHOT</version></parent><artifactId>mcp-server</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>mcp-server</name><description>Demo project for mcp</description><properties><java.version>21</java.version><spring-ai.version>1.0.0-M7</spring-ai.version></properties><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>

这里和常规的Spring Boot项目不同的是加了spring-ai的依赖,而且我们计划使用SSE来进行通信(Stdio方式生产不常用):

        <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId></dependency>

接着,我们Copy官方的天气预报例子,构建一个通过经纬度查询天气预报(getWeatherForecastByLocation),另一个通过美国州代码来查询天气预报的接口(getAlerts),注意,这属于上面提到的MCP服务器提供的“工具Tool”类型。

我们先通过 org.springframework.ai.tool.annotation.Tool 注解完成工具的定义(参考 https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webmvc-server/src/main/java/org/springframework/ai/mcp/sample/server):

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Slf4j
@Service
public class WeatherService {private final RestClient restClient;public WeatherService() {this.restClient = RestClient.builder().baseUrl("https://api.weather.gov").defaultHeader("Accept", "application/geo+json").defaultHeader("User-Agent", "WeatherApiClient/1.0 (835576511@qq.com)").build();}/*** Get forecast for a specific latitude/longitude* @param latitude Latitude* @param longitude Longitude* @return The forecast for the given location* @throws RestClientException if the request fails*/@Tool(description = "Get weather forecast for a specific latitude/longitude")public String getWeatherForecastByLocation(double latitude,   // Latitude coordinatedouble longitude   // Longitude coordinate) {log.info("Fetching forecast for coordinates: {}, {}", latitude, longitude);var points = restClient.get().uri("/points/{latitude},{longitude}", latitude, longitude).retrieve().body(Points.class);var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);String forecastText = forecast.properties().periods().stream().map(p -> {return String.format("""%s:Temperature: %s %sWind: %s %sForecast: %s""", p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(),p.detailedForecast());}).collect(Collectors.joining());return forecastText;}/*** Get alerts for a specific area* @param state Area code. Two-letter US state code (e.g. CA, NY)* @return Human readable alert information* @throws RestClientException if the request fails*/@Tool(description = "Get weather alerts for a US state")public String getAlerts(@ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state) {log.info("Fetching alerts for state: {}", state);Alert alert = restClient.get().uri("/alerts/active/area/{state}", state).retrieve().body(Alert.class);return alert.features().stream().map(f -> String.format("""Event: %sArea: %sSeverity: %sDescription: %sInstructions: %s""", f.properties().event(), f.properties.areaDesc(), f.properties.severity(),f.properties.description(), f.properties.instruction())).collect(Collectors.joining("\n"));}@JsonIgnoreProperties(ignoreUnknown = true)public record Points(@JsonProperty("properties") Props properties) {@JsonIgnoreProperties(ignoreUnknown = true)public record Props(@JsonProperty("forecast") String forecast) {}}@JsonIgnoreProperties(ignoreUnknown = true)public record Forecast(@JsonProperty("properties") Props properties) {@JsonIgnoreProperties(ignoreUnknown = true)public record Props(@JsonProperty("periods") List<Period> periods) {}@JsonIgnoreProperties(ignoreUnknown = true)public record Period(@JsonProperty("number") Integer number, @JsonProperty("name") String name,@JsonProperty("startTime") String startTime, @JsonProperty("endTime") String endTime,@JsonProperty("isDaytime") Boolean isDayTime,@JsonProperty("temperature") Integer temperature,@JsonProperty("temperatureUnit") String temperatureUnit,@JsonProperty("temperatureTrend") String temperatureTrend,@JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,@JsonProperty("windSpeed") String windSpeed,@JsonProperty("windDirection") String windDirection,@JsonProperty("icon") String icon, @JsonProperty("shortForecast") String shortForecast,@JsonProperty("detailedForecast") String detailedForecast) {}}@JsonIgnoreProperties(ignoreUnknown = true)public record Alert(@JsonProperty("features") List<Feature> features) {@JsonIgnoreProperties(ignoreUnknown = true)public record Feature(@JsonProperty("properties") Properties properties) {}@JsonIgnoreProperties(ignoreUnknown = true)public record Properties(@JsonProperty("event") String event, @JsonProperty("areaDesc") String areaDesc,@JsonProperty("severity") String severity,@JsonProperty("description") String description,@JsonProperty("instruction") String instruction) {}}
}

@Tool是 Spring AI 项目中的一个注解,用于将 Java 方法标记为可由 AI 模型(如 OpenAI、Azure OpenAI 或其他支持的 AI 服务)调用的工具(Tool)。它的主要作用是将你的方法暴露给 MCP客户端,使它够动态调用这些方法来完成特定任务。其中 @Tool 完成方法整体的功能描述,而@ToolParam 可以进一步完成参数的说明,如果你参数名足够清晰,也可以不用加@ToolParam。

同时在我们的Spring Boot启动类中增加ToolCallbackProvider,这样才能真正把Tool暴露出去给MCP Client调用:

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@Slf4j
@SpringBootApplication
public class McpServerApplication {public static void main(String[] args) {log.info("McpServerApplication 启动啦");SpringApplication.run(McpServerApplication.class, args);}/*** Tools* Allows servers to expose tools that can be invoked by language models.* The auto-configuration will automatically register the tool callbacks as MCP tools.* You can have multiple beans producing ToolCallbacks. The auto-configuration will merge them.* @param weatherService* @return*/@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService) {return  MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}
}

细心的朋友会发现,前面不是说有Stdio和SSE两种方式来暴露吗?目前看代码上没有体现?其实前面在pom.xml中我们依赖的spring-ai-starter-mcp-server-webmvc里面就依赖了:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

这里面就支持了SSE的实现,我们只需要在application.yml配置文件中配置一下即可:

server:port: 8090spring:application:name: mcp-servermain:banner-mode: offai:mcp:server:name: mcp-serverversion: 1.0.0type: SYNCsse-message-endpoint: /mcp/messages

这里的 sse-message-endpoint 就是配置SSE的path。注意这里暴露的是8090端口。到这里,一个简单的MCP Server就构建完成了。

构建MCP客户端

前面说过通常MCP客户端是需要使用LLM的,这里我们首选DeepSeek,然后在DeepSeek开放平台官网创建Api Keys 再充10块钱,这样你就有了可用DeepSeek Api Key了:

同样,我们建一个Maven项目叫做mcp-client,pom.xml配置如下:

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.manzhizhen.mcp</groupId><artifactId>mcp-study</artifactId><version>0.0.1-SNAPSHOT</version></parent><artifactId>mcp-client</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>mcp-client</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>22</java.version></properties><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>33.3.1-jre</version><scope>compile</scope></dependency></dependencies>
</project>

其中和其他项目不同的是我们依赖了:

        <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency>

一个是为了支持mcp-client,一个是为了支持我们调用DeepSeek。由于只是做一个简单的演示,我们决定使用IDEA的控制台作为输入,来当做一个天气预报咨询平台。我们只需要修改启动类即可:

import io.modelcontextprotocol.client.McpSyncClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;import java.util.List;
import java.util.Scanner;@Slf4j
@SpringBootApplication
public class McpClientSseApplication {public static void main(String[] args) {SpringApplication.run(McpClientSseApplication.class, args);}@Beanpublic CommandLineRunner interactiveChatRunner(ChatClient.Builder chatClientBuilder,ToolCallbackProvider tools,ConfigurableApplicationContext context) {return args -> {var chatClient = chatClientBuilder.defaultTools(tools).build();Scanner scanner = new Scanner(System.in); // 用于读取控制台输入System.out.println("=== 聊天模式已启动(输入 'exit' 退出) ===");while (true) {System.out.print("\n>>> 你的问题: ");String userInput = scanner.nextLine().trim();// 输入 "exit" 退出聊天if ("exit".equalsIgnoreCase(userInput)) {System.out.println(">>> 聊天结束,程序退出。");break;}// 空输入则跳过if (userInput.isEmpty()) {continue;}// 调用 AI 并打印回复System.out.println("\n>>> AI 回复: " + chatClient.prompt(userInput).call().content());}context.close(); // 关闭 Spring 上下文};}
}

可以看到,为了能不断的在控制台提问,我们写了一个while循环,当你输入exit才退出。

这里有一个关键,就是不管我们mcp-client连接了多少个mcp-server以及使用了多少个mcp-server的能力(例如Tool),我们都不需要去太关心细节,一行代码“chatClient.prompt(userInput).call().content()”就搞定,mcp内置的库类会去调用AI来根据你的问题“合理”的使用mcp-server提供的一个或者多个能力来完成任务,之前用Function Call可没有这么丝滑

对,mcp-client修改这一个类即可,接下来我们配置下application.yml:

spring:application:name: mcp-clientai:mcp:client:toolcallback:enabled: trueenabled: truename: my-mcp-clientversion: 1.0.0request-timeout: 30stype: SYNC  # or ASYNC for reactive applicationssse:connections:server1:url: http://localhost:8090openai:api-key: 这里填写你的DeepSeek api keybase-url: https://api.deepseek.comchat:options:model: deepseek-chattemperature: 0.7embedding:enabled: false

可以看到,我们最重要的是要配置mcp-server的地址(这里由于mcp-server和mcp-client部署在一起,所以用的是localhost)以及我们的openai的类型和对于的api key。到这里,整个mcp-client示例我们也写完了。

看看效果

先运行mcp-server,再运行mcp-client,如果启动都没问题,那么我们在mcp-client的控制台可以进行天气预报咨询了:

>>> 你的问题: 帮我查下 纬度 (lat): 40.7128,经度 (lon): -74.0060 的天气>>> AI 回复: 以下是纬度 40.7128,经度 -74.0060 的天气预报:### 今天
- **温度**: 80°F
- **风速**: 10 到 23 mph,西南风
- **预报**: 上午8点前可能有零星小雨,随后可能有零星阵雨和雷暴。部分晴天,最高气温接近80°F。西南风10至23 mph,阵风高达39 mph。降水概率20%。### 今晚
- **温度**: 57°F
- **风速**: 15 到 22 mph,西风
- **预报**: 凌晨2点前可能有零星阵雨和雷暴。大部分多云,最低气温约57°F,夜间气温升至62°F左右。西风15至22 mph,阵风高达38 mph。降水概率20%。### 周日
- **温度**: 63°F
- **风速**: 15 到 20 mph,西北风
- **预报**: 大部分晴天,最高气温接近63°F。西北风15至20 mph。...

可以看到我的问题携带了经纬度,所以最终是mcp-server的WeatherService#getWeatherForecastByLocation 被调用了。

>>> 你的问题: 美国 WA 州 天气预报>>> AI 回复: 以下是美国华盛顿州(WA)的天气预报:### 今晚
- **温度**: 44°F  
- **风速**: 9 mph 西北风  
- **预报**: 多云,最低气温约44°F。西北风约9 mph,阵风高达22 mph。### 周六
- **温度**: 54°F  
- **风速**: 9至13 mph 西北风  
- **预报**: 大部分晴天,最高气温约54°F,下午气温降至约52°F。西北风9至13 mph,阵风高达29 mph。### 周六晚
- **温度**: 38°F  
- **风速**: 10 mph 西北风  
- **预报**: 部分多云,最低气温约38°F。西北风约10 mph,阵风高达28 mph。...

这个问题很明显想引导mcp-client去使用mcp-server的WeatherService#getAlerts,但由于getAlerts没有查到华盛顿州的数据,所以mcp-client最终通过DeepSeek拿到华盛顿州的经纬度47.7511, -120.7401 去调用了getWeatherForecastByLocation方法,真聪明!!!

总结

欢迎加作者WX sugarmq 一起来探讨技术话题。

相关文章:

  • B端管理系统:企业运营的智慧大脑,精准指挥
  • FPGA——DDS信号发生器设计
  • Qt UDP 通信的详细实现步骤和示例代码
  • 系统思考:危机中的转型机遇
  • JVM虚拟机--JVM的组成
  • PyTorch深度学习框架60天进阶学习计划 - 第46天:自动化模型设计(一)
  • 虚拟现实(VR)技术在教育领域的创新应用
  • 某局部三层休闲娱乐中心建筑设计与结构设计
  • 【GlobalMapper精品教程】094:GlobalMapper26简体中文版安装教程(附安装包下载)
  • Docker Overlay 网络的核心工作(以跨节点容器通信为例)
  • Java集合及面试题学习
  • C# 变量||C# 常量
  • 计算机网络 3-4 数据链路层(局域网)
  • QML Label 组件
  • BZOJ P1419 Red is good
  • 【leetcode100】分割等和子集
  • Linux 进程信号详解
  • ping: socket: Operation not permitted
  • AI调试工具有哪些?
  • 【漫话机器学习系列】211.驻点(Stationary Points)
  • 伊朗艺术中的中国风
  • 梅德韦杰夫:如果欧盟和美国 “撒手不管”,俄罗斯会更快解决俄乌冲突
  • 黄金投资热,成了“财富焦虑”的贩卖场
  • 变局中,上海浦东何以继续引领?
  • 9部门发文促进家政服务消费扩容升级
  • 山东海事局通报3起商渔船碰撞事故:将严打违反避碰规则等违法行为