
SpringAI技术记录-MCP
本文最后更新于 2025-07-21,文章内容可能已经过时。请谨慎检查,如果谬误,请评论联系
MCP Client
阅读
MCP Client Boot Starter :: Spring AI Reference
实践和原理分析
基本介绍
Spring AI
的 MCP Client
一共支持三种类型:
StdioClientTransport
标准输入输出WebFluxSseClientTransport
基于WebFlux
的SSE
模式HttpClientSseClientTransport
基于HttpClint
的SSE
模式
根据使用的依赖包或者Web类型可以支持不同的Transport。其中 StdioClientTransport
每种模式都支持。
实践发现一个Server加载两次的bug
SpringAI 1.0.0-M6版本WebFlux SSE的MCP Client 有自动注入缺陷,导致Server注入两次,报错
这个是描述的缺陷的 ISSUE: https://github.com/spring-projects/spring-ai/pull/2333
整体加载逻辑是先加载
WebFluxSSeClientTransport
,然后加载 HttpClientSseClientTransport
并且添加了条件判断。但是判断条件如图,写错了,可能是 copy
类名的时候,copy
多了。导致 SSE
模式加载 MCP Server
的时候,因为这个条件出错,会以 HttpClient
和 WebFlux
的方式各加载一遍。报错名称重复。
MCP Server 加载过程和使用原理
直接注册为工具的 bean
是 ToolCallbackProvider
从这里看注入的是 McpSyncClient
这里根据同步或者异步,文档以同步为例
@Bean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public ToolCallbackProvider toolCallbacks(ObjectProvider<List<McpSyncClient>> mcpClientsProvider) {
List<McpSyncClient> mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList();
return new SyncMcpToolCallbackProvider(mcpClients);
}
@Bean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer,
McpClientCommonProperties commonProperties,
ObjectProvider<List<NamedClientMcpTransport>> transportsProvider) {
List<McpSyncClient> mcpSyncClients = new ArrayList<>();
List<NamedClientMcpTransport> namedTransports = transportsProvider.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(namedTransports)) {
for (NamedClientMcpTransport namedTransport : namedTransports) {
McpSchema.Implementation clientInfo = new McpSchema.Implementation(commonProperties.getName(),
commonProperties.getVersion());
McpClient.SyncSpec syncSpec = McpClient.sync(namedTransport.transport())
.clientInfo(clientInfo)
.requestTimeout(commonProperties.getRequestTimeout());
syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec);
var syncClient = syncSpec.build();
if (commonProperties.isInitialized()) {
syncClient.initialize();
}
mcpSyncClients.add(syncClient);
}
}
return mcpSyncClients;
}
接下来是 McpSyncClient
的加载,需要的是 ObjectProvider<List<NamedClientMcpTransport>> transportsProvider
,这个即是由上面三个 Transport
提供的。
场景
- 使用STDIO的方式hack news
- Spring-ai example的 get weather server
配置文件:
mcp:
client:
sse:
connections:
my-weather-server: http://localhost:8081
# spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080
# spring.ai.mcp.client.sse.connections.server2.url=http://localhost:8081
stdio:
servers-configuration: classpath:/mcp-servers-config.json
pom: 这里排除了webflux,否则一些阻塞方法会报错
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</exclusion>
</exclusions>
</dependency>
使用:
private final ToolCallbackProvider toolCallbackProvider;
...
public Flux<String> chatStreamWithToolsJdbcMemory(String chatId, String prompt) {
Message systemMessage = new SystemMessage(demoAiDefaultProperties.getSystemPrompt());
log.info("toolCallBack tools {}", Arrays.stream(toolCallbackProvider.getToolCallbacks()).map(FunctionCallback::getName).collect(Collectors.joining("\n")));
return chatClient.prompt(new Prompt(List.of(systemMessage, new UserMessage(prompt))))
.tools(hupuHostListToolsService)
.tools(toolCallbackProvider)
.advisors(new MessageChatMemoryAdvisor(chatMemory))
.advisors(advisorSpec -> advisorSpec
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)
) .stream().content();
}