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为例,官方也提供了相关文档:modelcontextprotocol.io/sdk/java/mc...
Spring AI也已经支持了MCP了。
4.1、多种传输方式
MCP提供了两种不同的传输实现。
- 默认传输方式:基于Stdio,HTTP SSE
- 基于Spring的传输:WebFlux SSE,WebMVC SSE
4.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依赖:
xml
<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 ([email protected])")
.build();
}
@Tool(name="getWeatherForecastByLocation", description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude,
double longitude
) {
try {
// Step 1: 获取点位信息
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();
// Step 2: 获取预报数据
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(name="getAlerts", description = "Get weather alerts for a US state")
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依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
创建MCP Client测试类:
java
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() {
// 这里直接指定我们刚mcp server打包的jar的位置
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.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开始。