打破版本枷锁!MCP SDK成功降级至JDK8,完美兼容老旧系统!支持StreamableHttpServerTransport!

打破版本枷锁!MCP SDK成功降级至JDK8,完美兼容老旧系统!支持StreamableHttpServerTransport!

📋 概述

官方SDK使用jdk17开发构建,为了能够使得更多使用老版本jdk的项目也能集成开发MCP功能,并且尽量避免重复造轮子,因此选择使用jdk8重构官方版本。

项目地址

重构分支项目地址:https://github.com/Lori888/mcp-java-sdk 分支0.10.0-jdk8(基于原项目分支0.10.0 revision 6307f069建立)

✨ 重构说明

重构原则

  • 保持原项目代码中类的注释等
  • 尽量和原项目代码的代码顺序保持一致,以便于修改对比

重构代码改动

主要进行了以下改动:

  • record 重构为 static class、添加 @Value注解(添加lombok包依赖)
  • sealed interface 重构为 interface 并相应修改其子类
  • List.of() 重构为 Collections.emptyList()
  • List.of(xxx) 重构为 Collections.singletonList(xxx)Arrays.stream(handlers).collect(Collectors.toList())
  • List.of(xxx, xxx) 重构为 Arrays.asList(xxx, xxx)
  • Map.of() 重构为 Collections.emptyMap()
  • Map.of(xxx) 重构为 Collections.singletonMap(xxx)
  • Map.of(xxx, xxx) 重构为 jdk8 map写法
  • stream().map(xxx).toList() 重构为 stream().map(xxx).collect(Collectors.toList())
  • stream().toList() 重构为 stream().collect(Collectors.toList())
  • Optional.isEmpty() 重构为 == null
  • var 重构为具体类型
  • instanceof Class xxx 重构为jdk8写法
  • switch 重构为jdk8写法
  • jakarta.servlet.* 重构为 javax.servlet.*(添加jakarta.servlet-api包依赖)
  • java.net.http.* 改为使用 OkHttp(添加okhttp包依赖)

功能变更

  • 在原项目代码的基础上,新增了StreamableHttpServerTransportProvider(复制于https://github.com/ZachGerman/mcp-java-sdk 项目分支StreamableHttpServerTransportProvider 并用jdk8重构~请给原作者1个小星星)
  • 修复了使用HttpServletSseServerTransportProvider时进行tools/call传参中文乱码问题

🛠️ 构建项目

环境要求

  • Java 8
  • Maven 3.3+

构建方法

1.下载项目源码:

1
git clone -b 0.10.0-jdk8 https://github.com/Lori888/mcp-java-sdk.git

2.构建安装到本地maven仓库中:

1
2
cd mcp-java-sdk
mvn clean install

🔥 使用方法

在项目中添加依赖:

1
2
3
4
5
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.10.0-jdk8</version>
</dependency>

🧪 应用示例

开发1个使用StreamableHttpTransport的MCP Server,关键步骤:

  • 构建具体的MCP Server tools类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @McpServerEndpoint(name = "示例MCP服务器", port = 9090)
    public class McpServerTool {

    @Tool(name = "getWeather", description = "获取天气信息")
    public String getWeather(@ToolParam(description = "城市名称") String city) {
    return String.format("%s: 晴天,温度25℃", city);
    }

    @Tool(description = "获取城市特产")
    public String getSpeciality(@ToolParam(description = "城市名称") String city, @ToolParam(description = "特产类型") String type) {
    return String.format("%s的%s特产是小笼包", type, city);
    }
    }
  • 构建MCP Server属性对象实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    private void processServerProperties() {
    mcpServerEndpoint = McpServerEndpointProcessor.resolveMcpServerEndpoint(targetBean.getClass().getName());
    assert mcpServerEndpoint != null;

    serverProperties.setName(mcpServerEndpoint.name());
    serverProperties.setVersion(mcpServerEndpoint.version());
    if (McpServerProperties.ServerType.ASYNC.name().equalsIgnoreCase(mcpServerEndpoint.type())) {
    serverProperties.setType(McpServerProperties.ServerType.ASYNC);
    }
    if (mcpServerEndpoint.transport().equalsIgnoreCase(TransportType.SSE.name())) {
    serverProperties.setTransport(TransportType.SSE);
    } else if (mcpServerEndpoint.transport().equalsIgnoreCase(TransportType.STDIO.name())) {
    serverProperties.setTransport(TransportType.STDIO);
    serverProperties.setStdio(true);
    }
    serverProperties.setPort(mcpServerEndpoint.port());
    serverProperties.setBaseUrl(mcpServerEndpoint.baseUrl());
    serverProperties.setSseEndpoint(mcpServerEndpoint.sseEndpoint());
    serverProperties.setSseMessageEndpoint(mcpServerEndpoint.sseMessageEndpoint());
    serverProperties.setMcpEndpoint(mcpServerEndpoint.mcpEndpoint());
    serverProperties.setToolChangeNotification(mcpServerEndpoint.toolChangeNotification());
    log.info("MCP Server properties: [{}]", serverProperties);

    serverInfo = new McpSchema.Implementation(serverProperties.getName(), serverProperties.getVersion());
    }
  • 根据MCP Server属性创建对应的McpServerTransportProvider实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    capabilitiesBuilder = McpSchema.ServerCapabilities.builder();
    if (serverProperties.getTransport() == TransportType.STREAMABLE_HTTP) {
    transportProvider = StreamableHttpServerTransportProvider.builder()
    .withMcpEndpoint(serverProperties.getMcpEndpoint())
    .build();
    } else if (serverProperties.getTransport() == TransportType.SSE) {
    transportProvider = HttpServletSseServerTransportProvider.builder()
    .baseUrl(serverProperties.getBaseUrl())
    .messageEndpoint(serverProperties.getSseMessageEndpoint())
    .sseEndpoint(serverProperties.getSseEndpoint())
    .build();
    } else if (serverProperties.isStdio()){
    transportProvider = new StdioServerTransportProvider();
    }
  • 根据同步/异步类型创建MCP Server实例

    1
    2
    3
    4
    5
    6
    7
    if (serverProperties.getTransport() == TransportType.STREAMABLE_HTTP) {
    buildAsyncStreamableHttpServer();
    } else if (serverProperties.getType() == McpServerProperties.ServerType.SYNC) {
    buildSyncServer();
    } else {
    buildAsyncServer();
    }
  • 将提供的能力(tools/resources/prompts)注册到MCP Server实例中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private void buildAsyncStreamableHttpServer() {
    log.info("building async StreamableHttpServer...");

    McpAsyncStreamableHttpServer.Builder serverBuilder = McpAsyncStreamableHttpServer.builder()
    .serverInfo(serverInfo.getName(), serverInfo.getVersion())
    .withMcpEndpoint(serverProperties.getMcpEndpoint());

    // tools
    List<McpServerFeatures.AsyncToolSpecification> toolSpecifications = buildAsyncToolSpecifications();
    if (!toolSpecifications.isEmpty()) {
    toolSpecifications.forEach(serverBuilder::withTool);
    capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
    }
    log.info(String.format(MSG_REGISTER_TOOLS, toolSpecifications.size(), serverProperties.isToolChangeNotification()));

    // TODO resources, prompts, rootsChangeConsumers

    serverBuilder.serverCapabilities(capabilitiesBuilder.build());
    McpAsyncStreamableHttpServer streamableHttpServer = serverBuilder.build();
    transportProvider = streamableHttpServer.getTransportProvider();
    }

  • 使用内嵌的Tomcat提供Web服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    private void startTomcat() throws LifecycleException {
    log.info("Starting Tomcat...");

    tomcat = new Tomcat();
    tomcat.setPort(serverProperties.getPort());

    // 设置临时目录
    File baseDir = new File(System.getProperty("java.io.tmpdir"));
    tomcat.setBaseDir(baseDir.getAbsolutePath());

    // 添加 Web 应用(无 webapp 目录,只注册 servlet)
    Context ctx = tomcat.addContext("", baseDir.getAbsolutePath());

    // 注册 MCP Servlet
    org.apache.catalina.Wrapper wrapper = ctx.createWrapper();
    wrapper.setName(MCP_SERVLET_NAME);
    wrapper.setServlet((Servlet) transportProvider);
    wrapper.setLoadOnStartup(1);
    wrapper.setAsyncSupported(true);
    ctx.addChild(wrapper);
    ctx.addServletMappingDecoded("/*", MCP_SERVLET_NAME);

    tomcat.getConnector(); // 默认创建一个 HTTP connector
    tomcat.start();

    String endpoint = serverProperties.getTransport() == TransportType.STREAMABLE_HTTP ?
    serverProperties.getMcpEndpoint() : serverProperties.getSseEndpoint();
    log.info("Tomcat running on {} and mcp server path is {}", serverProperties.getPort(), endpoint);
    }

完整代码详见: mcp-java-sdk-examples

📑 TODO LIST

  • mcp-spring-webfluxmcp-spring-webmvc子模块重构
  • 加入HttpClientStreamableHttpTransport
  • 适配Specification 2025-03-26 和 2025-06-18
  • jdk8重构官方主干分支

相关链接