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

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

📋 概述

官方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>

🧪 应用示例

构建StreamableHttpTransport MCP Server

开发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

构建StreamableHttpTransport MCP Client

前面已经构建了StreamableHttpTransport的MCP Server,接下来使用MCP Client代码连接服务并调用tool、resource、prompt:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.cafe.example.mcp;

import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class SyncMcpClient {

private final McpClientTransport transport;
private McpSyncClient syncClient;

public SyncMcpClient(McpClientTransport transport) {
this.transport = transport;

init();
}

private void init() {
syncClient = io.modelcontextprotocol.client.McpClient.sync(transport)
.capabilities(McpSchema.ClientCapabilities.builder().roots(true).build()).build();
}

public boolean connect() {
McpSchema.InitializeResult initializeResult = syncClient.initialize();
if (initializeResult != null) {
log.info("connected to Mcp Server: {}, {}", initializeResult.getServerInfo(), initializeResult.getCapabilities());
return true;
} else {
log.error("connect to Mcp Server fail");
return false;
}
}

public List<McpSchema.Tool> listTools() {
List<McpSchema.Tool> tools = syncClient.listTools().getTools();
if (tools != null && !tools.isEmpty()) {
tools.forEach(tool -> log.info("{}", tool));
} else {
log.error("No tools available!");
}
return tools;
}

public Object callTool(McpSchema.Tool tool, Map<String, Object> params) {
McpSchema.CallToolResult callToolResult = syncClient.callTool(new McpSchema.CallToolRequest(tool.getName(), params));
log.info("{}", callToolResult);
if (Boolean.TRUE.equals(callToolResult.getIsError())) {
log.error("callTool error: {}", callToolResult);
return null;
}
return callToolResult.getContent().get(0);
}

public List<McpSchema.Resource> listResources() {
List<McpSchema.Resource> resources = syncClient.listResources().getResources();
if (resources != null && !resources.isEmpty()) {
resources.forEach(resource -> log.info("{}", resource));
} else {
log.error("No resources available!");
}
return resources;
}

public McpSchema.ReadResourceResult readResource(String uri) {
return syncClient.readResource(new McpSchema.ReadResourceRequest(uri));
}

public McpSchema.ListPromptsResult listPrompts() {
McpSchema.ListPromptsResult listPromptsResult = syncClient.listPrompts();
if (listPromptsResult != null && listPromptsResult.getPrompts() != null && !listPromptsResult.getPrompts().isEmpty()) {
listPromptsResult.getPrompts().forEach(prompt -> log.info("{}", prompt));
} else {
log.error("No prompts available!");
}
return listPromptsResult;
}

public McpSchema.GetPromptResult getPrompt(String name, Map<String, Object> params) {
McpSchema.GetPromptRequest getPromptRequest = new McpSchema.GetPromptRequest(name, params);
McpSchema.GetPromptResult getPromptResult = syncClient.getPrompt(getPromptRequest);
log.info("{}", getPromptResult);
return getPromptResult;
}

public void disConnect() {
transport.closeGracefully();
syncClient.closeGracefully();
}

public static void main(String[] args) {
String serverUrl = "http://localhost:9090";
McpClientTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl).build();
// McpClientTransport transport = HttpClientSseClientTransport.builder(serverUrl).build();
SyncMcpClient client = new SyncMcpClient(transport);
if (!client.connect()) {
return;
}

// tool
List<McpSchema.Tool> tools = client.listTools();
if (tools != null && !tools.isEmpty()) {
McpSchema.Tool tool = tools.get(0);
Map<String, Object> paramMap = new HashMap<>();
// 不同的tool入参含义不同,通常都是由大模型调用tool,这里只是进行是否能调用成功测试(忽略入参实际作用与含义)
tool.getInputSchema().getRequired().forEach(param -> paramMap.put(param, "北京"));
Object result = client.callTool(tool, paramMap);
log.info("call tool '{}' result: {}", tool.getName(), result);
}

// resource
List<McpSchema.Resource> resources = client.listResources();
if (resources != null && !resources.isEmpty()) {
McpSchema.Resource resource = resources.get(0);
McpSchema.ReadResourceResult readResourceResult = client.readResource(resource.getUri());
log.info("readResourceResult: {}", readResourceResult);
}

// prompt
McpSchema.ListPromptsResult listPromptsResult = client.listPrompts();
if (listPromptsResult != null && listPromptsResult.getPrompts() != null && !listPromptsResult.getPrompts().isEmpty()) {
McpSchema.Prompt prompt = listPromptsResult.getPrompts().get(0);
Map<String, Object> paramMap = new HashMap<>();
McpSchema.GetPromptResult getPromptResult = client.getPrompt(prompt.getName(), paramMap);
log.info("getPromptResult: {}", getPromptResult);
}

client.disConnect();
}
}

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

📑 TODO LIST

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

相关链接