前言
上文(wen)[【MCP-04】Golang MCP StreamableHTTP方式(shi)調用Demo]和(he)[【MCP-03】一(yi)次完整(zheng)的MCP和(he)LLM交(jiao)互(hu)流程]已經使用PythonSDK和(he)MCP-go實(shi)現了LLM使用MCP StreamableHTTP的交(jiao)互(hu),由于MCP-Java SDK版本對(dui)StreamableHTTP協議支持較慢(man),SpringAI到當(dang)前為止還只整(zheng)合(he)了StreamableHTTP mcp-client部(bu)分,這里(li)參(can)考官方Demo,使用MCP-Java SDK原生代碼的方式(shi)編寫一(yi)個Java版本的MCP StreamableHTTP和(he)LLM交(jiao)互(hu)Demo。
Demo
同Golang版本只實現走FunctionCall的(de)方式,走系統提示詞(system prompt)參考[Python版本]。java的(de)MCPServer參考的(de)[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,為了保(bao)證(zheng)使(shi)用到最(zui)新(xin)的(de)版本(ben)和支持(chi)最(zui)新(xin)的(de)協議,以及生態支持(chi)上,推薦還是使(shi)用PythonSDK。