最近Anthropic主导发布了MCP(Model Context Protocol,模型上下文协议)后,着实真真火了一把。熟悉AI大模型的人对Anthropic应该不会陌生,Claude 3.5 Sonnet模型就是他们发布的,包括现在的最强编程AI模型 3.7 Sonnet。今天我们来刨析下什么是MCP,AI大模型下,需要MCP吗?
MCP(Model Context Protocol)模型上下文协议,是一种适用于AI大模型与数据源交互的标准协议。旨在实现跨模型、跨会话的上下文信息持久化与动态共享。它通过标准化接口实现模型间的上下文传递、版本控制和协同推理,解决复杂AI任务中的上下文碎片化问题。
那为什么需要重新定义AI的上下文管理? 在我们最开始使用AI大模型的时候,经常会遇到以下一些问题,如:上下文可能莫名的丢失,导致会话的语义出现断层。再如:不同模型之间,比如ChatGPT和DeepSeek之间记忆不互通,或者不同版本之间无法追踪上下文的变更历史等等,这些其实都是信息孤岛。
因此,我们需要有一个专门的协议或者说技术能跨模型、跨版本串联我们的上下文,解决记忆互通的问题。
如果我们观察整个AI自动化的发展过程,我们会发现AI自动化分三个阶段:AI Chat、AI Composer、AI Agent。
因此,进一步的演化路线自然是为了完善AI Agent的跨模型或跨系统而实现。他是一个中间层,可以作为AI Agent的智能路由中枢。
熟悉Java分布式的朋友应该会发现,这个其实很想分布式的发展进程。而MCP更多像是一个RPC的标准协议一样。不确定我这么比喻是否恰当。
模型上下文协议 (MCP) 遵循客户端-主机-服务器架构,其中每个主机可以运行多个客户端实例。这种架构使用户能够跨应用程序集成 AI 功能,同时保持明确的安全边界并隔离问题。MCP 基于 JSON-RPC 构建,提供有状态会话协议,专注于客户端和服务器之间的上下文交换和采样协调。
官方的MCP系统架构图:
可以发现 MCP 基于三层分层模式:
MCP提供了多种编程语言的支持,如Python,Java,Kotlin,TypeScript等。这里以Java SDK为例,官方也提供了相关文档:https://modelcontextprotocol.io/sdk/java/mcp-overview
Spring AI也已经支持了MCP了。
MCP提供了两种不同的传输实现。
MCP 客户端是模型上下文协议 (MCP) 架构中的关键组件,负责建立和管理与 MCP 服务器的连接。它实现协议的客户端。
MCP 服务器是模型上下文协议 (MCP) 架构中的基础组件,用于为客户提供工具、资源和功能。它实现协议的服务器端。
MCP 服务器可以提供三种主要类型的功能:
由于Spring AI已经同步支持了MCP,因此我们这里使用Spring AI MCP自动换配来快速入门。环境要求:Java 17+,Spring Boot 3.3.x+。
和常规的构建Spring Boot工程一样,只是这里选择MCP Server依赖。如果没有勾选,后续自己添加相应依赖即可。
MCP的maven依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
让我们实现一个使用 REST 客户端查询 National Weather Service API 数据的气象服务。创建WeatherService.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(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 服务器注册这些工具。
@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 服务器使用的可作回调。
接着我们运行mvn clean package,将上面的服务打成jar包。
我们可以使用一些桌面工具如Claude Desktop、Cursor等来引入我们的MCP Server。我自己试了下Claude,但是Claude需要国外的手机号进行注册才行,咋没这个条件啊。又试了下Cursor,发现这货只支持Http SSE方式。我这里只是为了做演示,所以就算了,不想封装了。 因此这里我直接编写一个MCP Client来做测试。
我们创建一个MCP Client工程:mcp-client-spring-ai,引入maven依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
创建MCP Client测试类:
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();
}
}
从打印的信息可以看出,成功获取到了我们通过@Tool注解的两个MCP Server的工具,同时也成功查询到了当地的气象信息。就是这么简单。
可见Java通过Spring AI进行MCP的集成,也是相当简单。想象一下,如果我们写好了能够满足需求的MCP Server,然后通过Client自己完成监管以及代码check,甚至可以集成视觉模型进行样式调整?那么岂不是一个活脱脱的智能程序员就出现了?当然可能想的比较简单。
其实由此可见,MCP也并不是什么新鲜玩意,他就只是个大家共识的标准协议而已。
有人肯定会说,MCP这样的方式和function calling有什么区别吗? function calling同样也具备调用外部接口的能力。为什么要需要MCP。
MCP正在重塑AI系统的构建范式,其核心价值体现在他的认知连续性,让AI真正具备"记忆传承"能力。以及系统协作性,构建模型间的认知协作网络。还有工程标准化,上下文管理从定制开发走向协议规范。
随着V1.2协议标准即将发布,MCP将成为智能系统的基础设施标配。我相信未来已来,唯智者先见;上下文革命,从MCP开始。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。