外观
78.SpringAI轻松构建MCP Client-Server架构
来源:语雀原文 | 整理日期:2026-09-22
示例代码
注:将ppt后缀名改为zip后解压
jdk版本:17.0.8
1、前言
最近 Anthropic 主导发布了 MCP(Model Context Protocol,模型上下文协议)后,着实真真火了一把。熟悉 AI 大模型的人对 Anthropic 应该不会陌生,Claude 3.5 Sonnet 模型就是他们发布的,包括现在的最强编程 AI 模型 3.7 Sonnet。今天我们来刨析下什么是 MCP,AI 大模型下,需要 MCP 吗?
2、什么是 MCP?
MCP(Model Context Protocol)模型上下文协议,是一种适用于 AI 大模型与数据源交互的标准协议。旨在实现跨模型、跨会话的上下文信息持久化与动态共享。它通过标准化接口实现模型间的上下文传递、版本控制和协同推理,解决复杂 AI 任务中的上下文碎片化问题。
那为什么需要重新定义 AI 的上下文管理? 在我们最开始使用 AI 大模型的时候,经常会遇到以下一些问题,如:上下文可能莫名的丢失,导致会话的语义出现断层。再如:不同模型之间,比如 ChatGPT 和 DeepSeek 之间记忆不互通,或者不同版本之间无法追踪上下文的变更历史等等,这些其实都是信息孤岛。
因此,我们需要有一个专门的协议或者说技术能跨模型、跨版本串联我们的上下文,解决记忆互通的问题。
如果我们观察整个 AI 自动化的发展过程,我们会发现 AI 自动化分三个阶段:AI Chat、AI Composer、AI Agent。
- AI Chat 通常只是提供建议和代码片段,人机协作的模式通常需要用户手动进行复制,调试,集成。
- AI Composer 相比 AI Chat 支持了局部的代码自动修改,我们只需要确认接受或拒绝接受即可。但是也局限于代码的上下文操作。
- AI Agent 可以完成端到端的任务闭环。他是一个完全的智能体,自主决策,修改,调整我们所需的代码块,或者自主提取需要的信息。我们只需要负责监控动作就行。但是跨系统的协作能力有所欠缺。
因此,进一步的演化路线自然是为了完善 AI Agent 的跨模型或跨系统而实现。他是一个中间层,可以作为 AI Agent 的智能路由中枢。
熟悉 Java 分布式的朋友应该会发现,这个其实很想分布式的发展进程。而 MCP 更多像是一个 RPC 的标准协议一样。不确定我这么比喻是否恰当。
3、MCP 架构
模型上下文协议 (MCP) 遵循客户端 - 主机 - 服务器架构,其中每个主机可以运行多个客户端实例。这种架构使用户能够跨应用程序集成 AI 功能,同时保持明确的安全边界并隔离问题。MCP 基于 JSON-RPC 构建,提供有状态会话协议,专注于客户端和服务器之间的上下文交换和采样协调。
官方的 MCP 系统架构图:

可以发现 MCP 基于三层分层模式:
- **MCP Host(主机应用):**运行 AI 模型或代理的宿主程序,如 Claude 桌面版、某 IDE 中的 AI 助手等。主机应用通过内置的 MCP 客户端与外部建立连接,是连接的发起方。
- **MCP Client(客户端):**嵌入在主机应用中的协议客户端组件。每个 MCP 客户端与一个特定的 MCP 服务器保持一对一的连接,用于向服务器发送请求或接收响应。一个主机应用中可以运行多个 MCP 客户端,从而同时连接多个不同的服务器。
- **MCP Server(服务器):**独立运行的轻量程序,封装了某一数据源或服务的具体能力,通过标准化的 MCP 接口对外提供。每个 MCP 服务器相当于一个 “适配器”,将底层的数据源 / 工具(本地文件、数据库、第三方 API 等)的功能以统一格式暴露出来,供客户端调用。
- **本地数据源:**部署在用户本地环境中的数据资源,例如本机文件系统、数据库、应用服务等。MCP 服务器可以受控地访问这些资源,并将内容提供给 AI 模型使用。例如,一个本地 MCP 服务器可以读取电脑文件或查询本地 SQLite 数据库,然后将结果发给模型作为参考。
- **远程服务:**通过网络提供的外部系统或在线服务(通常通过 HTTP API 访问)。MCP 服务器也可以连接到这些远程服务获取数据。比如,可以有一个 MCP 服务器连接 Slack 的 Web API,代表 AI 助手执行发送消息、读取频道记录等操作。
4、MCP & JAVA 支持
MCP 提供了多种编程语言的支持,如 Python,Java,Kotlin,TypeScript 等。这里以 Java SDK 为例,官方也提供了相关文档:https://modelcontextprotocol.io/sdk/java/mcp-overview
Spring AI 也已经支持了 MCP 了。
4.1、多种传输方式
MCP 提供了两种不同的传输实现。
- 默认传输方式:基于 Stdio,HTTP SSE
- 基于 Spring 的传输:WebFlux SSE,WebMVC SSE4.2、JAVA 应用的基础架构 JAVA SDK 也是遵循分层结构:

- 客户端 / 服务器层 (McpClient/McpServer): 两者都使用 McpSession 进行同步 / 异步操作,其中 McpClient 处理客户端协议操作,McpServer 管理服务器端协议操作。
- 会话层 (McpSession): 使用 DefaultMcpSession 实现管理通信模式和状态。
- 传输层 (McpTransport): 通过以下方式处理 JSON-RPC 消息序列化 / 反序列化:
- 核心模块中的 StdioTransport (stdin/stdout)
- 专用传输模块(Java HttpClient、Spring WebFlux、Spring WebMVC)中的 HTTP SSE 传输
MCP 客户端是模型上下文协议 (MCP) 架构中的关键组件,负责建立和管理与 MCP 服务器的连接。它实现协议的客户端。

MCP 服务器是模型上下文协议 (MCP) 架构中的基础组件,用于为客户提供工具、资源和功能。它实现协议的服务器端。

4.3、简单构建自己的 MCP Server
MCP 服务器可以提供三种主要类型的功能:
- **Resources: 资源。**客户端可以读取的类似文件的数据(如 API 响应或文件内容)。
- **Tools: 工具。**可由 LLM(经用户批准)调用的函数。
- **Prompts: 提示。**帮助用户完成特定任务的预先编写的模板。
由于 Spring AI 已经同步支持了 MCP,因此我们这里使用 Spring AI MCP 自动换配来快速入门。环境要求:Java 17+,Spring Boot 3.3.x+。
4.3.1、构建 Spring Boot 初始化工程
和常规的构建 Spring Boot 工程一样,只是这里选择 MCP Server 依赖。如果没有勾选,后续自己添加相应依赖即可。

MCP 的 maven 依赖:
plain
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>4.3.2、实现 MCP Server
让我们实现一个使用 REST 客户端查询 National Weather Service API 数据的气象服务。创建 WeatherService.java:
java
package org.example.mcpserverspringai;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RestClientResponseException;
@Service
public class WeatherService {
private final RestClient restClient;
private final ObjectMapper objectMapper = new ObjectMapper();
public WeatherService() {
this.restClient = RestClient.builder()
.baseUrl("https://api.weather.gov")
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
.build();
}
@Tool()
public String getWeatherForecastByLocation(
double latitude,
double longitude
) {
try {
String pointsResponse = restClient.get()
.uri("/points/{lat},{lon}", latitude, longitude)
.retrieve()
.onStatus(s -> s.is4xxClientError() || s.is5xxServerError(), (req, res) -> {
throw new WeatherApiException("Failed to get location data: " + res.getStatusText());
})
.body(String.class);
JsonNode pointsRoot = objectMapper.readTree(pointsResponse);
String forecastUrl = pointsRoot.path("properties")
.path("forecast")
.asText();
String forecastResponse = restClient.get()
.uri(forecastUrl)
.retrieve()
.body(String.class);
return parseForecastData(forecastResponse);
} catch (RestClientResponseException e) {
throw new WeatherApiException("Weather API error: " + e.getResponseBodyAsString());
} catch (Exception e) {
throw new WeatherApiException("Failed to retrieve forecast", e);
}
}
private String parseForecastData(String json) throws Exception {
StringBuilder result = new StringBuilder();
JsonNode root = objectMapper.readTree(json);
JsonNode periods = root.path("properties").path("periods");
result.append("## Weather Forecast\n");
for (JsonNode period : periods) {
result.append(String.format(
"### %s\n- Temperature: %.1f°F / %.1f°C\n- Wind: %s %s\n- Details: %s\n\n",
period.path("name").asText(),
period.path("temperature").asDouble(),
fahrenheitToCelsius(period.path("temperature").asDouble()),
period.path("windDirection").asText(),
period.path("windSpeed").asText(),
period.path("detailedForecast").asText()
));
}
return result.toString();
}
@Tool()
public String getAlerts(@ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state) {
if (state == null || state.length() != 2) {
throw new IllegalArgumentException("Invalid state code format");
}
try {
String alertResponse = restClient.get()
.uri("/alerts/active?area={state}", state.toUpperCase())
.retrieve()
.body(String.class);
return parseAlertData(alertResponse);
} catch (RestClientResponseException e) {
throw new WeatherApiException("Weather API error: " + e.getResponseBodyAsString());
} catch (Exception e) {
throw new WeatherApiException("Failed to retrieve alerts", e);
}
}
private String parseAlertData(String json) throws Exception {
StringBuilder result = new StringBuilder();
JsonNode root = objectMapper.readTree(json);
JsonNode features = root.path("features");
result.append("## Weather Alerts\n");
for (JsonNode feature : features) {
JsonNode properties = feature.path("properties");
result.append(String.format(
"### %s\n- Severity: %s\n- Areas: %s\n- Effective: %s\n- Instructions: %s\n\n",
properties.path("event").asText(),
properties.path("severity").asText(),
properties.path("areaDesc").asText(),
properties.path("effective").asText(),
properties.path("instruction").asText()
));
}
if (result.isEmpty()) {
return "No active alerts for this area";
}
return result.toString();
}
private double fahrenheitToCelsius(double f) {
return (f - 32) * 5 / 9;
}
public static class WeatherApiException extends RuntimeException {
public WeatherApiException(String message) {
super(message);
}
public WeatherApiException(String message, Throwable cause) {
super(message, cause);
}
}
}@Service 注解会在您的应用程序上下文中自动注册服务。Spring AI @Tool 注释,使创建和维护 MCP 工具变得容易。使用 Spring Boot 自动配置将自动向 MCP 服务器注册这些工具。
4.3.3、创建 Server 启动器
java
@SpringBootApplication
public class McpServerSpringAiApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerSpringAiApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}这里使用 MethodToolCallbackProvider 实用程序将 @Tools 转换为 MCP 服务器使用的可作回调。
4.3.4、打包 MCP Server
接着我们运行 mvn clean package,将上面的服务打成 jar 包。

4.3.5、MCP Client 调用
我们可以使用一些桌面工具如 Claude Desktop、Cursor 等来引入我们的 MCP Server。我自己试了下 Claude,但是 Claude 需要国外的手机号进行注册才行,咋没这个条件啊。又试了下 Cursor,发现这货只支持 Http SSE 方式。我这里只是为了做演示,所以就算了,不想封装了。 因此这里我直接编写一个 MCP Client 来做测试。
我们创建一个 MCP Client 工程:mcp-client-spring-ai,引入 maven 依赖:
plain
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>创建 MCP Client 测试类:
plain
package org.example.mcpclientspringai;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Map;
@SpringBootTest
class McpClientSpringAiApplicationTests {
@Test
void contextLoads() {
var stdioParams = ServerParameters.builder("java")
.args("-jar", "E:\\idea_projects\\mcp-server-spring-ai\\target\\mcp-server-spring-ai-0.0.1-SNAPSHOT.jar")
.build();
var stdioTransport = new StdioClientTransport(stdioParams);
var mcpClient = McpClient.sync(stdioTransport).build();
mcpClient.initialize();
McpSchema.ListToolsResult toolsList = mcpClient.listTools();
System.out.println("MCP tools集合:" + toolsList.tools());
McpSchema.CallToolResult weather = mcpClient.callTool(
new McpSchema.CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", "47.6062", "longitude", "-122.3321")));
System.out.println("根据经纬度查询天气信息:" + weather.content());
McpSchema.CallToolResult alert = mcpClient.callTool(
new McpSchema.CallToolRequest("getAlerts", Map.of("state", "NY")));
System.out.println("获取天气状态信息:" + alert.content());
mcpClient.closeGracefully();
}
}4.3.6、运行结果

从打印的信息可以看出,成功获取到了我们通过 @Tool 注解的两个 MCP Server 的工具,同时也成功查询到了当地的气象信息。就是这么简单。
4.3.7、Cherry Studio 测试MCP服务调用
https://cherry-ai.com/download



调用mcp_weather 服务的 getWeatherForecastByLocation 工具,经纬度参数如下,结果请用中文输出
"latitude", "47.6062", "longitude", "-122.3321"

4.4、既然如此?
可见 Java 通过 Spring AI 进行 MCP 的集成,也是相当简单。想象一下,如果我们写好了能够满足需求的 MCP Server,然后通过 Client 自己完成监管以及代码 check,甚至可以集成视觉模型进行样式调整?那么岂不是一个活脱脱的智能程序员就出现了?当然可能想的比较简单。
其实由此可见,MCP 也并不是什么新鲜玩意,他就只是个大家共识的标准协议而已。
5、MCP & function calling
有人肯定会说,MCP 这样的方式和 function calling 有什么区别吗? function calling 同样也具备调用外部接口的能力。为什么要需要 MCP。
- MAP:
- MCP 是一个更底层,通用的标准协议。
- MCP 更倾向于抽象和通用。
- MCP 通常支持多数据源。
- function calling:
- function calling 是大模型专用的特性,或者说实现方式。
- function calling 更倾向于具体的实现调用。
- function calling 适用于特定场景,单一数据源。
6、小结
MCP 正在重塑 AI 系统的构建范式,其核心价值体现在他的认知连续性,让 AI 真正具备 "记忆传承" 能力。以及系统协作性,构建模型间的认知协作网络。还有工程标准化,上下文管理从定制开发走向协议规范。
随着 V1.2 协议标准即将发布,MCP 将成为智能系统的基础设施标配。我相信未来已来,唯智者先见;上下文革命,从 MCP 开始。