跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaAIjava

Spring AI MCP Server 集成与示例

综述由AI生成Spring AI MCP Server 基于 Model Context Protocol 提供 Java SDK 及 Spring Boot 集成方案。文章展示了 Maven 依赖配置,通过 ToolCallbackProvider 注册工具方法(如天气查询),利用 RestClient 调用外部 API。客户端使用 HttpClientSseClientTransport 连接服务器,支持同步/异步模式。源码解析了 McpSchema 协议定义、McpSyncClient 封装及 Spring AutoConfiguration 自动装配逻辑,涵盖 WebMvc/WebFlux SSE 传输实现。

观心发布于 2026/3/16更新于 2026/6/435 浏览
Spring AI MCP Server 集成与示例

概述

本文主要研究一下 Spring AI 的 MCP Server。

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>
<!-- Spring WebFlux-based SSE client and server transport -->
<dependency>
  <groupId>io.modelcontextprotocol.sdk</groupId>
  mcp-spring-webflux



  io.modelcontextprotocol.sdk
  mcp-spring-webmvc

目录

  1. 概述
  2. MCP Java SDK
  3. Spring AI MCP
  4. 示例
  5. Maven 依赖配置
  6. 工具回调配置
  7. WeatherService 实现
  8. 客户端调用示例
  9. 核心源码解析
  10. McpSchema 协议定义
  11. McpSyncClient 同步客户端
  12. McpServerAutoConfiguration 自动配置
  13. McpWebMvcServerAutoConfiguration WebMvc 传输
  14. 总结
  15. 参考资料
  • 💰 8折买阿里云服务器限时8折了解详情
<artifactId>
</artifactId>
</dependency>
<!-- Spring WebMVC-based SSE server transport -->
<dependency>
<groupId>
</groupId>
<artifactId>
</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>
</dependency>

其中 mcp-server 提供了 webmvc 及 webflux 两个版本

示例

Maven 依赖配置

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

工具回调配置

@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,
                         @JsonProperty("name") String name,
                         @JsonProperty("startTime") String startTime,
                         @JsonProperty("endTime") String endTime,
                         @JsonProperty("isDaytime") Boolean isDayTime,
                         @JsonProperty("temperature") Integer temperature,
                         @JsonProperty("temperatureUnit") String temperatureUnit,
                         @JsonProperty("temperatureTrend") String temperatureTrend,
                         @JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
                         @JsonProperty("windSpeed") String windSpeed,
                         @JsonProperty("windDirection") String windDirection,
                         @JsonProperty("icon") String icon,
                         @JsonProperty("shortForecast") String shortForecast,
                         @JsonProperty("detailedForecast") String detailedForecast) {}
  }

  @JsonIgnoreProperties(ignoreUnknown = true)
  public record Alert(@JsonProperty("features") List<Feature> features) {
    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Feature(@JsonProperty("properties") Properties properties) {}
    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Properties(@JsonProperty("event") String event,
                             @JsonProperty("areaDesc") String areaDesc,
                             @JsonProperty("severity") String severity,
                             @JsonProperty("description") String description,
                             @JsonProperty("instruction") String instruction) {}
  }

  /**
   * Get forecast for a specific latitude/longitude
   * @param latitude Latitude
   * @param longitude Longitude
   * @return The forecast for the given location
   * @throws RestClientException if the request fails
   */
  @Tool(description = "Get weather forecast for a specific latitude/longitude")
  public String getWeatherForecastByLocation(double latitude, double longitude) {
    var points = restClient.get()
        .uri("/points/{latitude},{longitude}", latitude, longitude)
        .retrieve()
        .body(Points.class);
    var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);
    String forecastText = forecast.properties().periods().stream().map(p -> {
      return String.format("""
%s: Temperature: %s %s Wind: %s %s Forecast: %s """,
          p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(), p.detailedForecast());
    }).collect(Collectors.joining());
    return forecastText;
  }

  /**
   * Get alerts for a specific area
   * @param state Area code. Two-letter US state code (e.g. CA, NY)
   * @return Human readable alert information
   * @throws RestClientException if the request fails
   */
  @Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
  public String getAlerts(String state) {
    Alert alert = restClient.get().uri("/alerts/active/area/{state}", state).retrieve().body(Alert.class);
    return alert.features()
        .stream()
        .map(f -> String.format("""
Event: %s Area: %s Severity: %s Description: %s Instructions: %s """,
            f.properties().event(), f.properties.areaDesc(), f.properties.severity(), f.properties.description(), f.properties.instruction()))
        .collect(Collectors.joining("\n"));
  }

  public static void main(String[] args) {
    WeatherService client = new WeatherService();
    System.out.println(client.getWeatherForecastByLocation(47.6062, -122.3321));
    System.out.println(client.getAlerts("NY"));
  }
}

WeatherService 使用 RestClient 去请求 api.weather.gov,它使用@Tool 注解了 getWeatherForecastByLocation、getAlerts 方法

客户端调用示例

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();
    // List and demonstrate tools
    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}}, 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.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 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]
Alert Response = CallToolResult[content=[TextContent[audience=null, priority=null,]], isError=false]

核心源码解析

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";

  // ---------------------------
  // Method Names
  // ---------------------------
  // Lifecycle Methods
  public static final String METHOD_INITIALIZE = "initialize";
  public static final String METHOD_NOTIFICATION_INITIALIZED = "notifications/initialized";
  public static final String METHOD_PING = "ping";

  // Tool Methods
  public static final String METHOD_TOOLS_LIST = "tools/list";
  public static final String METHOD_TOOLS_CALL = "tools/call";
  public static final String METHOD_NOTIFICATION_TOOLS_LIST_CHANGED = "notifications/tools/list_changed";

  // Resources Methods
  public static final String METHOD_RESOURCES_LIST = "resources/list";
  public static final String METHOD_RESOURCES_READ = "resources/read";
  public static final String METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed";
  public static final String METHOD_RESOURCES_TEMPLATES_LIST = "resources/templates/list";
  public static final String METHOD_RESOURCES_SUBSCRIBE = "resources/subscribe";
  public static final String METHOD_RESOURCES_UNSUBSCRIBE = "resources/unsubscribe";

  // Prompt Methods
  public static final String METHOD_PROMPT_LIST = "prompts/list";
  public static final String METHOD_PROMPT_GET = "prompts/get";
  public static final String METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED = "notifications/prompts/list_changed";

  // Logging Methods
  public static final String METHOD_LOGGING_SET_LEVEL = "logging/setLevel";
  public static final String METHOD_NOTIFICATION_MESSAGE = "notifications/message";

  // Roots Methods
  public static final String METHOD_ROOTS_LIST = "roots/list";
  public static final String METHOD_NOTIFICATION_ROOTS_LIST_CHANGED = "notifications/roots/list_changed";

  // Sampling Methods
  public static final String METHOD_SAMPLING_CREATE_MESSAGE = "sampling/createMessage";

  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
  //......
}

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);
  // 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?
  private static final long DEFAULT_CLOSE_TIMEOUT_MS = 10_000L;
  private final 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-deprecation
  public McpSyncClient(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() {
    return this.delegate.getServerCapabilities();
  }

  /**
   * Get the server implementation information.
   * @return The server implementation details
   */
  public McpSchema.Implementation getServerInfo() {
    return this.delegate.getServerInfo();
  }

  /**
   * Get the client capabilities that define the supported features and functionality.
   * @return The client capabilities
   */
  public ClientCapabilities getClientCapabilities() {
    return this.delegate.getClientCapabilities();
  }

  /**
   * Get the client implementation information.
   * @return The client implementation details
   */
  public McpSchema.Implementation getClientInfo() {
    return this.delegate.getClientInfo();
  }

  @Override
  public void close() {
    this.delegate.close();
  }

  public boolean closeGracefully() {
    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);
      return false;
    }
    return true;
  }

  /**
   * 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 times
    return this.delegate.initialize().block();
  }

  /**
   * Send a roots/list_changed notification.
   */
  public void rootsListChangedNotification() {
    this.delegate.rootsListChangedNotification().block();
  }

  /**
   * Add a roots dynamically.
   */
  public void addRoot(McpSchema.Root root) {
    this.delegate.addRoot(root).block();
  }

  /**
   * Remove a root dynamically.
   */
  public void removeRoot(String rootUri) {
    this.delegate.removeRoot(rootUri).block();
  }

  /**
   * Send a synchronous ping request.
   * @return
   */
  public Object ping() {
    return this.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) {
    return this.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() {
    return this.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) {
    return this.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) {
    return this.delegate.listResources(cursor).block();
  }

  /**
   * Send a resources/list request.
   * @return the list of resources result.
   */
  public McpSchema.ListResourcesResult listResources() {
    return this.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) {
    return this.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) {
    return this.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) {
    return this.delegate.listResourceTemplates(cursor).block();
  }

  /**
   * Request a list of resource templates the server has.
   * @return the list of resource templates result.
   */
  public McpSchema.ListResourceTemplatesResult listResourceTemplates() {
    return this.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.
   */
  public void subscribeResource(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.
   */
  public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
    this.delegate.unsubscribeResource(unsubscribeRequest).block();
  }

  // --------------------------
  // Prompts
  // --------------------------
  public ListPromptsResult listPrompts(String cursor) {
    return this.delegate.listPrompts(cursor).block();
  }

  public ListPromptsResult listPrompts() {
    return this.delegate.listPrompts().block();
  }

  public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) {
    return this.delegate.getPrompt(getPromptRequest).block();
  }

  /**
   * Client can set the minimum logging level it wants to receive from the server.
   * @param loggingLevel the min logging level
   */
  public void setLoggingLevel(McpSchema.LoggingLevel loggingLevel) {
    this.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);
  }

  private List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(List<ToolCallback> tools, McpServerProperties serverProperties) {
    return tools.stream().map(tool -> {
      String toolName = tool.getToolDefinition().name();
      MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
      return McpToolUtils.toSyncToolSpecification(tool, mimeType);
    }).toList();
  }

  @Bean
  @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", matchIfMissing = true)
  public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, ObjectProvider<List<SyncToolSpecification>> tools, ObjectProvider<List<SyncResourceSpecification>> resources, ObjectProvider<List<SyncPromptSpecification>> prompts, ObjectProvider<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumers, List<ToolCallbackProvider> toolCallbackProvider) {
    McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(), serverProperties.getVersion());
    // Create the server with both tool and resource capabilities
    SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo);
    List<SyncToolSpecification> toolSpecifications = new ArrayList<>(tools.stream().flatMap(List::stream).toList());
    List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
        .map(pr -> List.of(pr.getToolCallbacks()))
        .flatMap(List::stream)
        .filter(fc -> fc instanceof ToolCallback)
        .map(fc -> (ToolCallback) fc)
        .toList();
    toolSpecifications.addAll(this.toSyncToolSpecifications(providerToolCallbacks, serverProperties));
    if (!CollectionUtils.isEmpty(toolSpecifications)) {
      serverBuilder.tools(toolSpecifications);
      capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
      logger.info("Registered tools: " + toolSpecifications.size() + ", notification: " + serverProperties.isToolChangeNotification());
    }
    List<SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
    if (!CollectionUtils.isEmpty(resourceSpecifications)) {
      serverBuilder.resources(resourceSpecifications);
      capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
      logger.info("Registered resources: " + resourceSpecifications.size() + ", notification: " + serverProperties.isResourceChangeNotification());
    }
    List<SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
    if (!CollectionUtils.isEmpty(promptSpecifications)) {
      serverBuilder.prompts(promptSpecifications);
      capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
      logger.info("Registered prompts: " + promptSpecifications.size() + ", notification: " + serverProperties.isPromptChangeNotification());
    }
    rootsChangeConsumers.ifAvailable(consumer -> {
      serverBuilder.rootsChangeHandler((exchange, roots) -> {
        consumer.accept(exchange, roots);
      });
      logger.info("Registered roots change consumer");
    });
    serverBuilder.capabilities(capabilitiesBuilder.build());
    return serverBuilder.build();
  }

  @Bean
  @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
  public List<McpServerFeatures.AsyncToolSpecification> asyncTools(ObjectProvider<List<ToolCallback>> toolCalls, List<ToolCallback> toolCallbackList, McpServerProperties serverProperties) {
    List<ToolCallback> tools = new ArrayList<>(toolCalls.stream().flatMap(List::stream).toList());
    if (!CollectionUtils.isEmpty(toolCallbackList)) {
      tools.addAll(toolCallbackList);
    }
    return this.toAsyncToolSpecification(tools, serverProperties);
  }

  private List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecification(List<ToolCallback> tools, McpServerProperties serverProperties) {
    return tools.stream().map(tool -> {
      String toolName = tool.getToolDefinition().name();
      MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
      return McpToolUtils.toAsyncToolSpecification(tool, mimeType);
    }).toList();
  }

  @Bean
  @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
  public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, ObjectProvider<List<AsyncToolSpecification>> tools, ObjectProvider<List<AsyncResourceSpecification>> resources, ObjectProvider<List<AsyncPromptSpecification>> prompts, ObjectProvider<BiConsumer<McpAsyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumer, List<ToolCallbackProvider> toolCallbackProvider) {
    McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(), serverProperties.getVersion());
    // Create the server with both tool and resource capabilities
    AsyncSpecification serverBuilder = McpServer.async(transportProvider).serverInfo(serverInfo);
    List<AsyncToolSpecification> toolSpecifications = new ArrayList<>(
        tools.stream().flatMap(List::stream).toList());
    List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
        .map(pr -> List.of(pr.getToolCallbacks()))
        .flatMap(List::stream)
        .filter(fc -> fc instanceof ToolCallback)
        .map(fc -> (ToolCallback) fc)
        .toList();
    toolSpecifications.addAll(this.toAsyncToolSpecification(providerToolCallbacks, serverProperties));
    if (!CollectionUtils.isEmpty(toolSpecifications)) {
      serverBuilder.tools(toolSpecifications);
      capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
      logger.info("Registered tools: " + toolSpecifications.size() + ", notification: " + serverProperties.isToolChangeNotification());
    }
    List<AsyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
    if (!CollectionUtils.isEmpty(resourceSpecifications)) {
      serverBuilder.resources(resourceSpecifications);
      capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
      logger.info("Registered resources: " + resourceSpecifications.size() + ", notification: " + serverProperties.isResourceChangeNotification());
    }
    List<AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
    if (!CollectionUtils.isEmpty(promptSpecifications)) {
      serverBuilder.prompts(promptSpecifications);
      capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
      logger.info("Registered prompts: " + promptSpecifications.size() + ", notification: " + serverProperties.isPromptChangeNotification());
    }
    rootsChangeConsumer.ifAvailable(consumer -> {
      BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>> asyncConsumer = (exchange, roots) -> {
        consumer.accept(exchange, roots);
        return Mono.empty();
      };
      serverBuilder.rootsChangeHandler(asyncConsumer);
      logger.info("Registered roots change consumer");
    });
    serverBuilder.capabilities(capabilitiesBuilder.build());
    return 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 WebMvc 传输

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 方法。

参考资料

  • modelcontextprotocol
  • modelcontextprotocol java sdk
  • mcp-server-boot-starter-docs
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Docker Desktop 多系统中文界面配置指南
  • 6 年自研纯 C# UI 引擎:轻量跨平台与高性能渲染实践
  • 企业投融资模式与法律风险分析
  • 2018 年编程语言趋势与开源生态报告解读
  • Jenkins 自动化部署与流水线实战指南
  • Stable Diffusion ComfyUI 整合包安装与部署指南
  • Clawdbot 源码部署实战:从环境搭建到 WebChat 验证
  • 网络安全基础与黑客技能入门指南
  • 2026年3月18日人工智能早间新闻
  • Flutter 三方库 arcade 的鸿蒙化适配指南
  • OpenClaw 本地部署教程:环境配置、插件开发与常见问题排查
  • Java 面向对象编程:封装机制详解与代码实现
  • 低空无人机 AI 算法详解:覆盖公安、消防、水利等 11 大行业
  • 多模态模型开发与应用:文本、图像与语音融合实践
  • MySQL 环境配置教程:CentOS 7 与 Ubuntu 双系统安装指南
  • Windows 10 本地部署 DataX 与 DataX-Web
  • FOC 电机控制深度解析:有感与无感原理及无人机应用
  • 绿联 NAS 配置 WebDAV 公网访问及 RaiDrive 挂载指南
  • 2026 年值得关注的开源低代码与零代码平台推荐
  • Python PyQt6/PySide6 现代化界面设计与美化实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online