【MCP-05】Java MCP StreamableHTTP方式调用Demo

前言

上文【04】Golang MCP StreamableHTTP方式调用Demo【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=https://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=http://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;
    }
}
image.png

image.png

image.png

总结

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。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 8,550评论 0 5
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 6,001评论 0 3
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 5,238评论 0 2
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 3,582评论 0 1
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 3,561评论 0 0