MCP Java SDK
MCP 提供了 Java SDK,同时还提供了 Spring WebFlux 及 MVC 的 SSE 实现。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>0.8.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
</dependency>
Spring AI MCP
Spring AI MCP 扩展了 MCP Java SDK,提供了 Spring Boot 的集成。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
其中 mcp-server 提供了 webmvc 及 webflux 两个版本
示例
pom.xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
Config
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
@Bean
public ToolCallback toUpperCase() {
return FunctionToolCallback.builder("toUpperCase", (TextInput input) -> input.input().toUpperCase())
.inputType(TextInput.class)
.description("Put the text to upper case")
.build();
}
WeatherService
@Service
public class WeatherService {
private static final String BASE_URL = "https://api.weather.gov";
private final RestClient restClient;
public WeatherService() {
this.restClient = RestClient.builder()
.baseUrl(BASE_URL)
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 ([email protected])")
.build();
}
@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,
String name,
String startTime,
String endTime,
Boolean isDayTime,
Integer temperature,
String temperatureUnit,
String temperatureTrend,
Map probabilityOfPrecipitation,
String windSpeed,
String windDirection,
String icon,
String shortForecast,
String detailedForecast) {}
}
List<Feature> features) {
Properties properties) {}
String event,
String areaDesc,
String severity,
String description,
String instruction) {}
}
String {
restClient.get()
.uri(, latitude, longitude)
.retrieve()
.body(Points.class);
restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);
forecast.properties().periods().stream().map(p -> {
String.format(,
p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(), p.detailedForecast());
}).collect(Collectors.joining());
forecastText;
}
String {
restClient.get().uri(, state).retrieve().body(Alert.class);
alert.features()
.stream()
.map(f -> String.format(,
f.properties().event(), f.properties.areaDesc(), f.properties.severity(), f.properties.description(), f.properties.instruction()))
.collect(Collectors.joining());
}
{
();
System.out.println(client.getWeatherForecastByLocation(, -));
System.out.println(client.getAlerts());
}
}
WeatherService 使用 RestClient 去请求 api.weather.gov,它使用@Tool 注解了 getWeatherForecastByLocation、getAlerts 方法
Client 调用
public class SampleClient {
public static void main(String[] args) {
var transport = new HttpClientSseClientTransport("http://localhost:8080");
var client = McpClient.sync(transport).build();
client.initialize();
client.ping();
ListToolsResult toolsList = client.listTools();
System.out.println("Available Tools = " + toolsList);
CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation", Map.of("latitude", "47.6062", "longitude", "-122.3321")));
System.out.println("Weather Forcast: " + weatherForcastResult);
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts", Map.of("state", "NY")));
System.out.println("Alert Response = " + alertResult);
client.closeGracefully();
}
}
这里构建了 HttpClientSseClientTransport,然后通过 McpClient.sync(transport).build() 创建了 McpSyncClient;示例先调用 listTools 查看有哪些 tool,之后构建 CallToolRequest 去请求 getWeatherForecastByLocation、getAlerts 方法。
输出示例:
16:18:34.707 [HttpClient-1-Worker-0] INFO io.modelcontextprotocol.client.McpAsyncClient -- Server response with Protocol: 2024-11-05, Capabilities: ServerCapabilities[experimental=null, logging=LoggingCapabilities[], prompts=null, resources=null, tools=ToolCapabilities[listChanged=true]], Info: Implementation[name=my-weather-server, version=0.0.1] and Instructions null
Available Tools = ListToolsResult[tools=[Tool[name=toUpperCase, description=Put the text to upper case, inputSchema=JsonSchema[type=object, properties={input={type=string}}, [input], additionalProperties]], [namegetAlerts, description weather alerts a state. letter state code (e.g. , ), inputSchema[typeobject, properties{state{typestring}}, [state], additionalProperties]], [namegetWeatherForecastByLocation, description weather forecast a specific latitudelongitude, inputSchema[typeobject, properties{latitude{typenumber, formatdouble}, longitude{typenumber, formatdouble}}, [latitude, longitude], additionalProperties]]], nextCursornull]
: [content[[audiencenull, prioritynull, text]], isError]
[content[[audiencenull, prioritynull,]], isError]
源码
McpSchema
io/modelcontextprotocol/spec/McpSchema.java
public final class McpSchema {
private static final Logger logger = LoggerFactory.getLogger(McpSchema.class);
private McpSchema() { }
public static final String LATEST_PROTOCOL_VERSION = "2024-11-05";
public static final String JSONRPC_VERSION = "2.0";
public static final String METHOD_INITIALIZE = "initialize";
public static final String METHOD_NOTIFICATION_INITIALIZED = "notifications/initialized";
public static final String METHOD_PING = "ping";
public ;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
();
}
McpSchema 定义了 Lifecycle 方法 (initialize,notifications/initialized,ping),Tool 方法 (tools/list,tools/call,notifications/tools/list_changed),Resources 方法 (resources/list,resources/read,notifications/resources/list_changed,resources/templates/list,resources/subscribe,resources/unsubscribe),Prompt 方法 (prompts/list,prompts/get,notifications/prompts/list_changed);Logging 方法 (logging/setLevel,notifications/message),Roots 方法 (roots/list,notifications/roots/list_changed),Sampling 方法 (sampling/createMessage)
McpSyncClient
io/modelcontextprotocol/client/McpSyncClient.java
public class McpSyncClient implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(McpSyncClient.class);
private static final long DEFAULT_CLOSE_TIMEOUT_MS = 10_000L;
private final McpAsyncClient delegate;
@Deprecated
public McpSyncClient(McpAsyncClient delegate) {
Assert.notNull(delegate, "The delegate can not be null");
this.delegate = delegate;
}
public McpSchema.ServerCapabilities getServerCapabilities() {
return this.delegate.getServerCapabilities();
}
McpSchema.Implementation {
.delegate.getServerInfo();
}
ClientCapabilities {
.delegate.getClientCapabilities();
}
McpSchema.Implementation {
.delegate.getClientInfo();
}
{
.delegate.close();
}
{
{
.delegate.closeGracefully().block(Duration.ofMillis(DEFAULT_CLOSE_TIMEOUT_MS));
} (RuntimeException e) {
logger.warn(, DEFAULT_CLOSE_TIMEOUT_MS, e);
;
}
;
}
McpSchema.InitializeResult {
.delegate.initialize().block();
}
{
.delegate.rootsListChangedNotification().block();
}
{
.delegate.addRoot(root).block();
}
{
.delegate.removeRoot(rootUri).block();
}
Object {
.delegate.ping().block();
}
McpSchema.CallToolResult {
.delegate.callTool(callToolRequest).block();
}
McpSchema.ListToolsResult {
.delegate.listTools().block();
}
McpSchema.ListToolsResult {
.delegate.listTools(cursor).block();
}
McpSchema.ListResourcesResult {
.delegate.listResources(cursor).block();
}
McpSchema.ListResourcesResult {
.delegate.listResources().block();
}
McpSchema.ReadResourceResult {
.delegate.readResource(resource).block();
}
McpSchema.ReadResourceResult {
.delegate.readResource(readResourceRequest).block();
}
McpSchema.ListResourceTemplatesResult {
.delegate.listResourceTemplates(cursor).block();
}
McpSchema.ListResourceTemplatesResult {
.delegate.listResourceTemplates().block();
}
{
.delegate.subscribeResource(subscribeRequest).block();
}
{
.delegate.unsubscribeResource(unsubscribeRequest).block();
}
ListPromptsResult {
.delegate.listPrompts(cursor).block();
}
ListPromptsResult {
.delegate.listPrompts().block();
}
GetPromptResult {
.delegate.getPrompt(getPromptRequest).block();
}
{
.delegate.setLoggingLevel(loggingLevel).block();
}
}
McpSyncClient 实现了 AutoCloseable 接口,它主要是使用 McpAsyncClient 进行了包装,包装为同步方法;它提供了 getServerCapabilities、getServerInfo、getClientCapabilities、getClientInfo、initialize、rootsListChangedNotification、addRoot、removeRoot、ping、callTool、listTools、listResources、readResource、listResourceTemplates、subscribeResource、unsubscribeResource、listPrompts、getPrompt、setLoggingLevel
McpServerAutoConfiguration
org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java
@AutoConfiguration(after = { McpWebMvcServerAutoConfiguration.class, McpWebFluxServerAutoConfiguration.class })
@ConditionalOnClass({ McpSchema.class, McpSyncServer.class })
@EnableConfigurationProperties(McpServerProperties.class)
@Import(McpBackwardCompatibility.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class McpServerAutoConfiguration {
private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean
public McpServerTransportProvider stdioServerTransport() {
return new StdioServerTransportProvider();
}
@Bean
@ConditionalOnMissingBean
public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() {
return McpSchema.ServerCapabilities.builder();
}
@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", matchIfMissing = true)
public List<McpServerFeatures.SyncToolSpecification> syncTools(ObjectProvider<List<ToolCallback>> toolCalls, List<ToolCallback> toolCallbacksList, McpServerProperties serverProperties) {
List<ToolCallback> tools = new ArrayList<>(toolCalls.stream().flatMap(List::stream).toList());
if (!CollectionUtils.isEmpty(toolCallbacksList)) {
tools.addAll(toolCallbacksList);
}
return this.toSyncToolSpecifications(tools, serverProperties);
}
List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(List<ToolCallback> tools, McpServerProperties serverProperties) {
tools.stream().map(tool -> {
tool.getToolDefinition().name();
(serverProperties.getToolResponseMimeType().containsKey(toolName)) ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : ;
McpToolUtils.toSyncToolSpecification(tool, mimeType);
}).toList();
}
McpSyncServer {
McpSchema. (serverProperties.getName(), serverProperties.getVersion());
McpServer.sync(transportProvider).serverInfo(serverInfo);
List<SyncToolSpecification> toolSpecifications = <>(tools.stream().flatMap(List::stream).toList());
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
.map(pr -> List.of(pr.getToolCallbacks()))
.flatMap(List::stream)
.filter(fc -> fc ToolCallback)
.map(fc -> (ToolCallback) fc)
.toList();
toolSpecifications.addAll(.toSyncToolSpecifications(providerToolCallbacks, serverProperties));
(!CollectionUtils.isEmpty(toolSpecifications)) {
serverBuilder.tools(toolSpecifications);
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info( + toolSpecifications.size() + + serverProperties.isToolChangeNotification());
}
List<SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
(!CollectionUtils.isEmpty(resourceSpecifications)) {
serverBuilder.resources(resourceSpecifications);
capabilitiesBuilder.resources(, serverProperties.isResourceChangeNotification());
logger.info( + resourceSpecifications.size() + + serverProperties.isResourceChangeNotification());
}
List<SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
(!CollectionUtils.isEmpty(promptSpecifications)) {
serverBuilder.prompts(promptSpecifications);
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info( + promptSpecifications.size() + + serverProperties.isPromptChangeNotification());
}
rootsChangeConsumers.ifAvailable(consumer -> {
serverBuilder.rootsChangeHandler((exchange, roots) -> {
consumer.accept(exchange, roots);
});
logger.info();
});
serverBuilder.capabilities(capabilitiesBuilder.build());
serverBuilder.build();
}
List<McpServerFeatures.AsyncToolSpecification> asyncTools(ObjectProvider<List<ToolCallback>> toolCalls, List<ToolCallback> toolCallbackList, McpServerProperties serverProperties) {
List<ToolCallback> tools = <>(toolCalls.stream().flatMap(List::stream).toList());
(!CollectionUtils.isEmpty(toolCallbackList)) {
tools.addAll(toolCallbackList);
}
.toAsyncToolSpecification(tools, serverProperties);
}
List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecification(List<ToolCallback> tools, McpServerProperties serverProperties) {
tools.stream().map(tool -> {
tool.getToolDefinition().name();
(serverProperties.getToolResponseMimeType().containsKey(toolName)) ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : ;
McpToolUtils.toAsyncToolSpecification(tool, mimeType);
}).toList();
}
McpAsyncServer {
McpSchema. (serverProperties.getName(), serverProperties.getVersion());
McpServer.async(transportProvider).serverInfo(serverInfo);
List<AsyncToolSpecification> toolSpecifications = <>(
tools.stream().flatMap(List::stream).toList());
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
.map(pr -> List.of(pr.getToolCallbacks()))
.flatMap(List::stream)
.filter(fc -> fc ToolCallback)
.map(fc -> (ToolCallback) fc)
.toList();
toolSpecifications.addAll(.toAsyncToolSpecification(providerToolCallbacks, serverProperties));
(!CollectionUtils.isEmpty(toolSpecifications)) {
serverBuilder.tools(toolSpecifications);
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info( + toolSpecifications.size() + + serverProperties.isToolChangeNotification());
}
List<AsyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
(!CollectionUtils.isEmpty(resourceSpecifications)) {
serverBuilder.resources(resourceSpecifications);
capabilitiesBuilder.resources(, serverProperties.isResourceChangeNotification());
logger.info( + resourceSpecifications.size() + + serverProperties.isResourceChangeNotification());
}
List<AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
(!CollectionUtils.isEmpty(promptSpecifications)) {
serverBuilder.prompts(promptSpecifications);
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info( + promptSpecifications.size() + + serverProperties.isPromptChangeNotification());
}
rootsChangeConsumer.ifAvailable(consumer -> {
BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>> asyncConsumer = (exchange, roots) -> {
consumer.accept(exchange, roots);
Mono.empty();
};
serverBuilder.rootsChangeHandler(asyncConsumer);
logger.info();
});
serverBuilder.capabilities(capabilitiesBuilder.build());
serverBuilder.build();
}
}
McpServerAutoConfiguration 主要是提供 McpSyncServer 亦或是 McpAsyncServer,它依赖于 McpServerTransportProvider、McpSchema.ServerCapabilities.Builder、McpServerProperties、List<SyncToolSpecification>、List<SyncResourceSpecification>、List<SyncPromptSpecification>、BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>、List<ToolCallbackProvider>;McpSyncServer 内部是包装 McpAsyncServer 来实现的。
McpWebMvcServerAutoConfiguration
org/springframework/ai/mcp/server/autoconfigure/McpWebMvcServerAutoConfiguration.java
@AutoConfiguration
@ConditionalOnClass({ WebMvcSseServerTransportProvider.class })
@ConditionalOnMissingBean(McpServerTransportProvider.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false", matchIfMissing = true)
public class McpWebMvcServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(ObjectMapper objectMapper, McpServerProperties serverProperties) {
return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getSseMessageEndpoint());
}
@Bean
public RouterFunction<ServerResponse> mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) {
return transportProvider.getRouterFunction();
}
}
McpWebMvcServerAutoConfiguration 自动配置了 WebMvcSseServerTransportProvider、mvcMcpRouterFunction
小结
MCP 提供了 Java SDK,同时还提供了 Spring WebFlux 及 MVC 的 SSE 实现;Spring AI MCP 扩展了 MCP Java SDK,提供了 Spring Boot 的集成,其中 mcp-server 提供了 webmvc 及 webflux 两个版本。
McpAsyncServer 内部实现了 MCP 协议定义的方法的 requestHandlers,之后创建 McpServerSession.Factory,设置给 McpServerTransportProvider,与 Spring 提供的 WebMvcSseServerTransportProvider 衔接起来。WebMvcSseServerTransportProvider 通过 McpServerSession.Factory 来集成 MCP,其构造器定义了 routerFunction(注册到 Spring 中),其 handleSseConnection 方法主要是构建 McpServerSession,其 handleMessage 方法获取 McpServerSession,执行其 handle 方法。
Doc