Spring AI MCP Server 基于 Model Context Protocol 提供 Java SDK 及 Spring Boot 集成。文章涵盖依赖配置、工具回调实现(以天气服务为例)、客户端调用流程及核心源码分析(McpSchema、McpSyncClient、自动配置类)。示例展示了如何注册工具并响应 LLM 请求,支持 WebFlux 和 WebMvc 传输方式。
<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
AvailableTools=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. InputisTwo-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]
WeatherForcast: 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.Saturday Night:Temperature: 41 FWind: 2 to 6 mph SEForecast: A chance of rain before 11pm. Mostly cloudy, with a low around 41.Sunday:Temperature: 59 FWind: 2 to 6 mph NNEForecast: A chance of rain after 11am. Mostly cloudy, with a high near 59.Sunday Night:Temperature: 45 FWind: 6 mph ESEForecast: Rain likely. Mostly cloudy, with a low around 45.Monday:Temperature: 54 FWind: 5 to 8 mph SForecast: Rain. Mostly cloudy, with a high near 54.Monday Night:Temperature: 42 FWind: 5 to 8 mph SForecast: Rain likely. Mostly cloudy, with a low around 42.Tuesday:Temperature: 54 FWind: 7 mph SSWForecast: Rain likely. Mostly cloudy, with a high near 54."]], isError=false]
AlertResponse=CallToolResult[content=[TextContent[audience=null, priority=null,]], isError=false]
publicclassMcpSyncClientimplementsAutoCloseable {
privatestaticfinalLoggerlogger= LoggerFactory.getLogger(McpSyncClient.class);
// TODO: Consider providing a client config to set this properly// this is currently a concern only because AutoCloseable is used - perhaps it// is not a requirement?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.
*/@Deprecated// TODO make the constructor package private post-deprecationpublicMcpSyncClient(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() {
// TODO: block takes no argument here as we assume the async client is// configured with a requestTimeout at all timesreturnthis.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();
}
}