前言
上文[【MCP-04】Golang MCP StreamableHTTP方式调用Demo]和[【MCP-03】一次完整的MCP和LLM交互流程]已经使用PythonSDK和MCP-go实现了LLM使用MCP StreamableHTTP的交互,由于MCP-Java SDK版本对StreamableHTTP协议支持较慢,SpringAI到当前为止还只整合了StreamableHTTP mcp-client部分,这里参考官方Demo,使用MCP-Java SDK原生代码的方式编写一个Java版本的MCP StreamableHTTP和LLM交互Demo。
Demo
同Golang版本只实现走FunctionCall的方式,走系统提示词(system prompt)参考[Python版本]。java的MCPServer参考的[examples]。MCPClient参考[examples]。
JShMcpServer
# 依赖
<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.0-SNAPSHOT</spring-ai.version><!--1.0.0-M6-->
<guava.version>31.1-jre</guava.version>
<mcp.version>0.11.2</mcp.version>
</properties>
<dependencies>
# java mcp-sdk并不提供服务容器,因此要单独引入一个webflux
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
# 由于这里还是要用到@Tool注解等SpringAI的方法,这里还是要引入spring-ai-mcp
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<exclusions>
<exclusion>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</exclusion>
<exclusion>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
<version>${mcp.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
# 配置文件
spring.application.name=mcpv2
server.port=8210
# spring.main.web-application-type=none
# NOTE: You must disable the banner and the console logging
# to allow the STDIO transport to work !!!
#spring.main.banner-mode=off
#logging.pattern.console=
# transport.mode=stdio
#transport.mode=sse
#transport.mode=stateless
# 这里开启streamable http
transport.mode=streamable
logging.file.name=mcp.tooldemo.log
logging.level.root=info
# 配置类
/**
* @Author: wanghaoguang
* @CreateTime: 2025/8/14 10:01
*/
@Configuration
public class McpServerStreamableConfig {
/**********************streamable************************/
@Bean
@ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "streamable")
public WebFluxStreamableServerTransportProvider webFluxStreamableProvider() {
return WebFluxStreamableServerTransportProvider.builder()
.messageEndpoint("/mcp")
.objectMapper(new ObjectMapper())
.build();
}
@Bean
@ConditionalOnProperty(prefix = "transport", name = "mode", havingValue = "streamable")
public RouterFunction<?> webfluxMcpStreamableRouterFunction(
WebFluxStreamableServerTransportProvider webFluxStreamableProvider) {
return webFluxStreamableProvider.getRouterFunction();
}
@Bean
public CalculateService calculateService() {
return new CalculateService();
}
@Bean
public McpSyncServer mcpServer(WebFluxStreamableServerTransportProvider webFluxStreamableProvider, CalculateService calculateService) {
var capabilities = McpSchema.ServerCapabilities.builder()
.tools(true)
.logging()
.build();
McpSyncServer server = McpServer.sync(webFluxStreamableProvider)
.serverInfo("MCP Demo Calculate Server", "1.0.0")
.capabilities(capabilities)
.tools(McpToolUtils.toSyncToolSpecifications(ToolCallbacks.from(calculateService)))
.build();
return server;
}
}
# tool工具类
public class CalculateService {
@Tool(description = "对两个数字进行加法")
public float add(float number1, float number2) {
System.out.println("Add number1 = " + number1 + " number2 = " + number2);
return number1 + number2;
}
@Tool(description = "对两个数字进行减法")
public float subtract(float number1, float number2) {
System.out.println("Subtract number1 = " + number1 + " number2 = " + number2);
return number1 - number2;
}
@Tool(description = "对两个数字进行乘法")
public float multiply(float number1, float number2) {
System.out.println("Multiply number1 = " + number1 + " number2 = " + number2);
return number1 * number2;
}
@Tool(description = "对两个数字进行除法")
public float divide(float number1, float number2) {
System.out.println("Divide number1 = " + number1 + " number2 = " + number2);
return number1 / number2;
}
}
JShMCPClient
# 依赖
<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.0-SNAPSHOT</spring-ai.version><!--1.0.0-M6-->
<guava.version>31.1-jre</guava.version>
<mcp.version>0.11.2</mcp.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>${mcp.version}</version>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
<version>${mcp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
# 配置文件
spring.application.name=mcpv2client
server.port=8211
spring.main.web-application-type=reactive
spring.ai.openai.base-url=api.deepseek.com
spring.ai.openai.api-key=xxxxxxx
spring.ai.openai.chat.options.model=deepseek-chat
spring.ai.mcp.client.toolcallback.enabled=true
spring.ai.mcp.client.streamable-http.connections.server1.url=127.0.0.1:8210
# Controller
/**
* @Author: wanghaoguang
* @CreateTime: 2025/8/14 17:28
*/
@RestController
@RequestMapping("/test")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
this.chatClient = chatClientBuilder.defaultToolCallbacks(tools)
.build();
}
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatWithStream(@RequestParam String message) {
System.out.println("\n>>> QUESTION: " + message);
Flux<String> rt = chatClient.prompt()
.user(message)
.stream()
.content();
System.out.println("\n>>> ASSISTANT: " + rt);
return rt;
}
}
总结
0,同PythonSDK,所有语言的MCP-SDK均参考官方规划文档编写,从源码来看,大体思路和使用方式都差不多。
1,Java MCP-SDK相对PythonSDK和MCP-go更新迭代慢,相对于SpringAI MCP技术试用的SpringAI的方法,最好使用官方原生MCP-SDK+自定义运行容器的方式,这样也比较灵活,官方Java MCP-SDK代码更新也较快。
2,同样Java MCP-SDK有提供Last-Event-ID机制和stateless,不过stateless方式暂时测试还有一些问题。
3,为了保证使用到最新的版本和支持最新的协议,以及生态支持上,推荐还是使用PythonSDK。