Spring AI MCP Server 基于 Model Context Protocol 提供 Spring Boot 集成方案。文章介绍 MCP Java SDK 依赖配置及 Spring AI Starter 扩展模块的使用方式。通过 WeatherService 示例展示了如何使用 @Tool 注解暴露工具方法,并演示了客户端如何通过 SSE 连接调用这些工具获取天气预报和警报信息。此外,还分析了 McpSchema、McpSyncClient 及 Spring AutoConfiguration 类的核心实现逻辑,涵盖了生命周期管理、工具注册及资源处理等关键机制。
<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><!-- Spring WebFlux-based SSE client and server transport --><dependency><groupId>io.modelcontextprotocol.sdk</groupId>
mcp-spring-webflux
io.modelcontextprotocol.sdk
mcp-spring-webmvc
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}}, required=[input], additionalProperties=false]], Tool[name=getAlerts, description=Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY), inputSchema=JsonSchema[type=object, properties={state={type=string}}, required=[state], additionalProperties=false]], Tool[name=getWeatherForecastByLocation, description=Get weather forecast for a specific latitude/longitude, inputSchema=JsonSchema[type=object, properties={latitude={type=number, format=double}, longitude={type=number, format=double}}, required=[latitude, longitude], additionalProperties=false]]], nextCursor=null]
Weather Forcast: CallToolResult[content=[TextContent[audience=null, priority=null, text="Overnight:\nTemperature: 50 F\nWind: 5 mph N\nForecast: Mostly cloudy, with a low around 50. North wind around 5 mph.\nWednesday:\nTemperature: 70 F\nWind: 2 to 7 mph NW\nForecast: A chance of rain showers between 9am and 5pm, then showers and thunderstorms. Some of the storms could be severe. Mostly cloudy. High near 70, with temperatures falling to around 68 in the afternoon. Northwest wind 2 to 7 mph. Chance of precipitation is 100%. New rainfall amounts between a quarter and half of an inch possible.\nWednesday Night:\nTemperature: 48 F\nWind: 7 to 10 mph SSW\nForecast: Showers and thunderstorms before 5am, then rain. Some of the storms could be severe. Cloudy. Low around 48, with temperatures rising to around 50 overnight. South southwest wind 7 to 10 mph, with gusts as high as 21 mph. Chance of precipitation is 100%. New rainfall amounts between a quarter and half of an inch possible.\nThursday:\nTemperature: 59 F\nWind: 9 mph S\nForecast: Rain. Cloudy, with a high near 59. South wind around 9 mph, with gusts as high as 21 mph. Chance of precipitation is 90%. New rainfall amounts between a tenth and quarter of an inch possible.\nThursday Night:\nTemperature: 47 F\nWind: 9 mph S\nForecast: Rain. Cloudy, with a low around 47. South wind around 9 mph, with gusts as high as 22 mph. Chance of precipitation is 90%. New rainfall amounts between a quarter and half of an inch possible.\nFriday:\nTemperature: 55 F\nWind: 9 to 13 mph S\nForecast: Rain. Cloudy, with a high near 55. Chance of precipitation is 100%. New rainfall amounts between a tenth and quarter of an inch possible.\nFriday Night:\nTemperature: 44 F\nWind: 6 to 10 mph S\nForecast: Rain. Mostly cloudy, with a low around 44. Chance of precipitation is 80%.\nSaturday:\nTemperature: 55 F\nWind: 7 mph S\nForecast: Rain likely. Partly sunny, with a high near 55.\nSaturday Night:\nTemperature: 41 F\nWind: 2 to 6 mph SE\nForecast: A chance of rain before 11pm. Mostly cloudy, with a low around 41.\nSunday:\nTemperature: 59 F\nWind: 2 to 6 mph NNE\nForecast: A chance of rain after 11am. Mostly cloudy, with a high near 59.\nSunday Night:\nTemperature: 45 F\nWind: 6 mph ESE\nForecast: Rain likely. Mostly cloudy, with a low around 45.\nMonday:\nTemperature: 54 F\nWind: 5 to 8 mph S\nForecast: Rain likely. Mostly cloudy, with a high near 54.\nMonday Night:\nTemperature: 42 F\nWind: 5 to 8 mph S\nForecast: Rain likely. Mostly cloudy, with a low around 42.\nTuesday:\nTemperature: 54 F\nWind: 7 mph SSW\nForecast: Rain likely. Mostly cloudy, with a high near 54.\n"]], isError=false]
Alert Response = CallToolResult[content=[TextContent[audience=null, priority=null,]], isError=false]
publicclassMcpSyncClientimplementsAutoCloseable {
privatestaticfinalLoggerlogger= LoggerFactory.getLogger(McpSyncClient.class);
privatestaticfinallongDEFAULT_CLOSE_TIMEOUT_MS=10_000L;
privatefinal McpAsyncClient delegate;
/**
* Create a new McpSyncClient with the given delegate.
* @param delegate the asynchronous kernel on top of which this synchronous client
* provides a blocking API.
* @deprecated This method will be removed in 0.9.0. Use
* {@link McpClient#sync(McpClientTransport)} to obtain an instance.
*/@DeprecatedpublicMcpSyncClient(McpAsyncClient delegate) {
Assert.notNull(delegate, "The delegate can not be null");
this.delegate = delegate;
}
/**
* Get the server capabilities that define the supported features and functionality.
* @return The server capabilities
*/public McpSchema.ServerCapabilities getServerCapabilities() {
returnthis.delegate.getServerCapabilities();
}
/**
* Get the server implementation information.
* @return The server implementation details
*/public McpSchema.Implementation getServerInfo() {
returnthis.delegate.getServerInfo();
}
/**
* Get the client capabilities that define the supported features and functionality.
* @return The client capabilities
*/public ClientCapabilities getClientCapabilities() {
returnthis.delegate.getClientCapabilities();
}
/**
* Get the client implementation information.
* @return The client implementation details
*/public McpSchema.Implementation getClientInfo() {
returnthis.delegate.getClientInfo();
}
@Overridepublicvoidclose() {
this.delegate.close();
}
publicbooleancloseGracefully() {
try {
this.delegate.closeGracefully().block(Duration.ofMillis(DEFAULT_CLOSE_TIMEOUT_MS));
} catch (RuntimeException e) {
logger.warn("Client didn't close within timeout of {} ms.", DEFAULT_CLOSE_TIMEOUT_MS, e);
returnfalse;
}
returntrue;
}
/**
* The initialization phase MUST be the first interaction between client and server.
* During this phase, the client and server:
* <ul>
* <li>Establish protocol version compatibility</li>
* <li>Exchange and negotiate capabilities</li>
* <li>Share implementation details</li>
* </ul>
* <br/>
* The client MUST initiate this phase by sending an initialize request containing:
* <ul>
* <li>The protocol version the client supports</li>
* <li>The client's capabilities</li>
* <li>Client implementation information</li>
* </ul>
*
* The server MUST respond with its own capabilities and information: {@link McpSchema.ServerCapabilities}. <br/>
* After successful initialization, the client MUST send an initialized notification
* to indicate it is ready to begin normal operations.
*
* <br/>
*
* <a href="https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization Spec</a>
* @return the initialize result.
*/public McpSchema.InitializeResult initialize() {
returnthis.delegate.initialize().block();
}
/**
* Send a roots/list_changed notification.
*/publicvoidrootsListChangedNotification() {
this.delegate.rootsListChangedNotification().block();
}
/**
* Add a roots dynamically.
*/publicvoidaddRoot(McpSchema.Root root) {
this.delegate.addRoot(root).block();
}
/**
* Remove a root dynamically.
*/publicvoidremoveRoot(String rootUri) {
this.delegate.removeRoot(rootUri).block();
}
/**
* Send a synchronous ping request.
* @return
*/public Object ping() {
returnthis.delegate.ping().block();
}
// --------------------------// Tools// --------------------------/**
* Calls a tool provided by the server. Tools enable servers to expose executable
* functionality that can interact with external systems, perform computations, and
* take actions in the real world.
* @param callToolRequest The request containing: - name: The name of the tool to call
* (must match a tool name from tools/list) - arguments: Arguments that conform to the
* tool's input schema
* @return The tool execution result containing: - content: List of content items
* (text, images, or embedded resources) representing the tool's output - isError:
* Boolean indicating if the execution failed (true) or succeeded (false/absent)
*/public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolRequest) {
returnthis.delegate.callTool(callToolRequest).block();
}
/**
* Retrieves the list of all tools provided by the server.
* @return The list of tools result containing: - tools: List of available tools, each
* with a name, description, and input schema - nextCursor: Optional cursor for
* pagination if more tools are available
*/public McpSchema.ListToolsResult listTools() {
returnthis.delegate.listTools().block();
}
/**
* Retrieves a paginated list of tools provided by the server.
* @param cursor Optional pagination cursor from a previous list request
* @return The list of tools result containing: - tools: List of available tools, each
* with a name, description, and input schema - nextCursor: Optional cursor for
* pagination if more tools are available
*/public McpSchema.ListToolsResult listTools(String cursor) {
returnthis.delegate.listTools(cursor).block();
}
// --------------------------// Resources// --------------------------/**
* Send a resources/list request.
* @param cursor the cursor
* @return the list of resources result.
*/public McpSchema.ListResourcesResult listResources(String cursor) {
returnthis.delegate.listResources(cursor).block();
}
/**
* Send a resources/list request.
* @return the list of resources result.
*/public McpSchema.ListResourcesResult listResources() {
returnthis.delegate.listResources().block();
}
/**
* Send a resources/read request.
* @param resource the resource to read
* @return the resource content.
*/public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) {
returnthis.delegate.readResource(resource).block();
}
/**
* Send a resources/read request.
* @param readResourceRequest the read resource request.
* @return the resource content.
*/public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest readResourceRequest) {
returnthis.delegate.readResource(readResourceRequest).block();
}
/**
* Resource templates allow servers to expose parameterized resources using URI
* templates. Arguments may be auto-completed through the completion API.
*
* Request a list of resource templates the server has.
* @param cursor the cursor
* @return the list of resource templates result.
*/public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) {
returnthis.delegate.listResourceTemplates(cursor).block();
}
/**
* Request a list of resource templates the server has.
* @return the list of resource templates result.
*/public McpSchema.ListResourceTemplatesResult listResourceTemplates() {
returnthis.delegate.listResourceTemplates().block();
}
/**
* Subscriptions. The protocol supports optional subscriptions to resource changes.
* Clients can subscribe to specific resources and receive notifications when they
* change.
*
* Send a resources/subscribe request.
* @param subscribeRequest the subscribe request contains the uri of the resource to
* subscribe to.
*/publicvoidsubscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
this.delegate.subscribeResource(subscribeRequest).block();
}
/**
* Send a resources/unsubscribe request.
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
* to unsubscribe from.
*/publicvoidunsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
this.delegate.unsubscribeResource(unsubscribeRequest).block();
}
// --------------------------// Prompts// --------------------------public ListPromptsResult listPrompts(String cursor) {
returnthis.delegate.listPrompts(cursor).block();
}
public ListPromptsResult listPrompts() {
returnthis.delegate.listPrompts().block();
}
public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) {
returnthis.delegate.getPrompt(getPromptRequest).block();
}
/**
* Client can set the minimum logging level it wants to receive from the server.
* @param loggingLevel the min logging level
*/publicvoidsetLoggingLevel(McpSchema.LoggingLevel loggingLevel) {
this.delegate.setLoggingLevel(loggingLevel).block();
}
}