This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 9d9ae38e5db Restrict MCP protocol support to 2025-11-25 (#39022)
9d9ae38e5db is described below
commit 9d9ae38e5db275360177b9d150ccfabe35c1c6c3
Author: Liang Zhang <[email protected]>
AuthorDate: Mon Jul 6 20:30:12 2026 +0800
Restrict MCP protocol support to 2025-11-25 (#39022)
* Restrict MCP protocol support to 2025-11-25
Limit MCP transport protocol negotiation to the 2025-11-25 protocol version
and treat 2025-06-18 as an unsupported legacy version for follow-up requests.
Simplify HTTP transport protocol header handling now that only one protocol can
be negotiated, and update MCP E2E/client tests to assert the new contract.
* Restrict MCP protocol support to 2025-11-25
Limit MCP transport protocol negotiation to the 2025-11-25 protocol version
and treat 2025-06-18 as an unsupported legacy version for follow-up requests.
Simplify HTTP transport protocol header handling now that only one protocol can
be negotiated, and update MCP E2E/client tests to assert the new contract.
---
.../bootstrap/transport/MCPTransportConstants.java | 2 +-
.../server/http/StreamableHttpMCPServlet.java | 12 +----
.../server/http/StreamableHttpMCPServletTest.java | 30 +++---------
.../ProtocolVersionHeaderConstraintTest.java | 7 ++-
.../ProductionMCPClientTransportFactory.java | 6 ++-
.../ProductionMCPClientTransportFactoryTest.java | 55 ++++++++++++++++++++++
.../HttpTransportProtocolContractE2ETest.java | 4 +-
7 files changed, 74 insertions(+), 42 deletions(-)
diff --git
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportConstants.java
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportConstants.java
index ccd9a443748..862e4b38e8a 100644
---
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportConstants.java
+++
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/MCPTransportConstants.java
@@ -31,7 +31,7 @@ public final class MCPTransportConstants {
public static final String PROTOCOL_VERSION =
ProtocolVersions.MCP_2025_11_25;
- public static final List<String> SUPPORTED_PROTOCOL_VERSIONS =
List.of(PROTOCOL_VERSION, ProtocolVersions.MCP_2025_06_18);
+ public static final List<String> SUPPORTED_PROTOCOL_VERSIONS =
List.of(PROTOCOL_VERSION);
public static final String SERVER_NAME = "apache-shardingsphere-mcp";
diff --git
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServlet.java
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServlet.java
index fc849f2c811..465baa0c673 100644
---
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServlet.java
+++
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServlet.java
@@ -48,7 +48,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
final class StreamableHttpMCPServlet extends HttpServlet implements
McpStreamableServerTransportProvider {
@@ -75,8 +74,6 @@ final class StreamableHttpMCPServlet extends HttpServlet
implements McpStreamabl
private final SessionAttributionResolver sessionAttributionResolver;
- private final Map<String, String> sessionProtocolVersions;
-
private final AtomicBoolean closed;
StreamableHttpMCPServlet(final MCPSessionManager sessionManager, final
McpJsonMapper jsonMapper, final HttpTransportConfiguration config) {
@@ -86,8 +83,6 @@ final class StreamableHttpMCPServlet extends HttpServlet
implements McpStreamabl
this.jsonMapper = jsonMapper;
this.sessionManager = sessionManager;
sessionExecutionCoordinator = new
MCPSessionExecutionCoordinator(sessionManager);
- sessionProtocolVersions = new ConcurrentHashMap<>();
-
sessionManager.addSessionCloseListener(sessionProtocolVersions::remove);
closed = new AtomicBoolean();
}
@@ -109,7 +104,6 @@ final class StreamableHttpMCPServlet extends HttpServlet
implements McpStreamabl
McpStreamableServerSession.McpStreamableServerSessionInit result =
sessionFactory.startSession(actualInitializeRequest);
String sessionId = result.session().getId();
sessionManager.createSession(sessionId);
- sessionProtocolVersions.put(sessionId,
actualInitializeRequest.protocolVersion());
return result;
});
}
@@ -281,14 +275,10 @@ final class StreamableHttpMCPServlet extends HttpServlet
implements McpStreamabl
private void addNegotiatedProtocolHeader(final
SessionAwareHttpServletResponse response, final String name, final String
sessionId) {
if (SESSION_HEADER.equalsIgnoreCase(name)) {
response.setSessionId(sessionId);
- response.setHeader(PROTOCOL_HEADER,
findNegotiatedProtocolVersion(sessionId));
+ response.setHeader(PROTOCOL_HEADER,
MCPTransportConstants.PROTOCOL_VERSION);
}
}
- private String findNegotiatedProtocolVersion(final String sessionId) {
- return
sessionProtocolVersions.getOrDefault(Objects.toString(sessionId, ""),
MCPTransportConstants.PROTOCOL_VERSION);
- }
-
private abstract static class SessionAwareHttpServletResponse extends
HttpServletResponseWrapper {
private String sessionId = "";
diff --git
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServletTest.java
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServletTest.java
index 1c8b82859a7..22fa133af8b 100644
---
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServletTest.java
+++
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/StreamableHttpMCPServletTest.java
@@ -69,9 +69,8 @@ class StreamableHttpMCPServletTest {
assertThat(actual.protocolVersions(),
is(MCPTransportConstants.SUPPORTED_PROTOCOL_VERSIONS));
}
- @ParameterizedTest(name = "{0}")
- @MethodSource("supportedProtocolVersions")
- void assertSetSessionFactoryWithSupportedProtocolVersion(final String
name, final String requestedProtocolVersion) throws
ReflectiveOperationException {
+ @Test
+ void assertSetSessionFactoryWithSupportedProtocolVersion() throws
ReflectiveOperationException {
HttpServletStreamableServerTransportProvider delegate =
mock(HttpServletStreamableServerTransportProvider.class);
MCPSessionManager sessionManager = mock(MCPSessionManager.class);
McpStreamableServerSession.Factory sessionFactory =
mock(McpStreamableServerSession.Factory.class);
@@ -85,19 +84,13 @@ class StreamableHttpMCPServletTest {
actual.setSessionFactory(sessionFactory);
ArgumentCaptor<McpStreamableServerSession.Factory> actualFactory =
ArgumentCaptor.forClass(McpStreamableServerSession.Factory.class);
verify(delegate).setSessionFactory(actualFactory.capture());
- McpSchema.InitializeRequest expectedInitializeRequest = new
McpSchema.InitializeRequest(requestedProtocolVersion,
+ McpSchema.InitializeRequest expectedInitializeRequest = new
McpSchema.InitializeRequest(MCPTransportConstants.PROTOCOL_VERSION,
new McpSchema.ClientCapabilities(Map.of(), null, null, null),
new McpSchema.Implementation("foo_client", "1.0.0"));
assertThat(actualFactory.getValue().startSession(expectedInitializeRequest),
is(expectedInit));
verify(sessionFactory).startSession(expectedInitializeRequest);
verify(sessionManager).createSession("session-id");
}
- private static Stream<Arguments> supportedProtocolVersions() {
- return Stream.of(
- Arguments.of("latest protocol version",
MCPTransportConstants.PROTOCOL_VERSION),
- Arguments.of("compatible protocol version",
ProtocolVersions.MCP_2025_06_18));
- }
-
@ParameterizedTest(name = "{0}")
@MethodSource("unsupportedProtocolVersions")
void assertSetSessionFactoryWithNegotiatedProtocolVersion(final String
name, final String requestedProtocolVersion) throws
ReflectiveOperationException {
@@ -130,6 +123,7 @@ class StreamableHttpMCPServletTest {
return Stream.of(
Arguments.of("null protocol version", null),
Arguments.of("blank protocol version", " "),
+ Arguments.of("legacy protocol version",
ProtocolVersions.MCP_2025_06_18),
Arguments.of("unsupported protocol version", "2024-11-05"));
}
@@ -278,19 +272,6 @@ class StreamableHttpMCPServletTest {
@Test
void assertServicePostWithNegotiatedProtocolHeader() throws
ServletException, IOException, ReflectiveOperationException {
HttpServletStreamableServerTransportProvider delegate =
mock(HttpServletStreamableServerTransportProvider.class);
- MCPSessionManager sessionManager = mock(MCPSessionManager.class);
- McpStreamableServerSession.Factory sessionFactory =
mock(McpStreamableServerSession.Factory.class);
- McpStreamableServerSession session =
mock(McpStreamableServerSession.class);
- when(session.getId()).thenReturn("session-id");
-
when(sessionFactory.startSession(any(McpSchema.InitializeRequest.class))).thenReturn(new
McpStreamableServerSession.McpStreamableServerSessionInit(session,
- Mono.just(new
InitializeResult(ProtocolVersions.MCP_2025_06_18,
McpSchema.ServerCapabilities.builder().tools(Boolean.FALSE).build(),
- new
McpSchema.Implementation(MCPTransportConstants.SERVER_NAME, "development"),
"runtime"))));
- StreamableHttpMCPServlet actual = createServlet(delegate,
sessionManager, mock(MCPSessionExecutionCoordinator.class));
- actual.setSessionFactory(sessionFactory);
- ArgumentCaptor<McpStreamableServerSession.Factory> actualFactory =
ArgumentCaptor.forClass(McpStreamableServerSession.Factory.class);
- verify(delegate).setSessionFactory(actualFactory.capture());
- actualFactory.getValue().startSession(new
McpSchema.InitializeRequest(ProtocolVersions.MCP_2025_06_18,
- new McpSchema.ClientCapabilities(Map.of(), null, null, null),
new McpSchema.Implementation("foo_client", "1.0.0")));
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getMethod()).thenReturn("POST");
when(request.getHeader(HttpHeaders.ACCEPT)).thenReturn(ACCEPT);
@@ -300,8 +281,9 @@ class StreamableHttpMCPServletTest {
((HttpServletResponse)
invocation.getArgument(1)).setHeader(HttpHeaders.MCP_SESSION_ID, "session-id");
return null;
}).when(delegate).service(any(HttpServletRequest.class),
any(HttpServletResponse.class));
+ StreamableHttpMCPServlet actual = createServlet(delegate,
mock(MCPSessionManager.class), mock(MCPSessionExecutionCoordinator.class));
actual.service(request, response);
- verify(response).setHeader(HttpHeaders.PROTOCOL_VERSION,
ProtocolVersions.MCP_2025_06_18);
+ verify(response).setHeader(HttpHeaders.PROTOCOL_VERSION,
MCPTransportConstants.PROTOCOL_VERSION);
}
@Test
diff --git
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/constraint/ProtocolVersionHeaderConstraintTest.java
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/constraint/ProtocolVersionHeaderConstraintTest.java
index 339d1cee0ea..bfa9f67febf 100644
---
a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/constraint/ProtocolVersionHeaderConstraintTest.java
+++
b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/server/http/validator/constraint/ProtocolVersionHeaderConstraintTest.java
@@ -58,7 +58,10 @@ class ProtocolVersionHeaderConstraintTest {
}
@Test
- void assertValidateWithCompatibleProtocolVersion() {
- assertDoesNotThrow(() -> new
ProtocolVersionHeaderConstraint().validate(ProtocolVersions.MCP_2025_06_18));
+ void assertValidateWithLegacyProtocolVersion() {
+ ServerTransportSecurityException ex =
assertThrows(ServerTransportSecurityException.class, () -> new
ProtocolVersionHeaderConstraint().validate(ProtocolVersions.MCP_2025_06_18));
+ assertThat(ex.getStatusCode(), is(400));
+ assertThat(ex.getMessage(), is(String.format("Unsupported MCP protocol
version `%s`. Supported versions are %s.", ProtocolVersions.MCP_2025_06_18,
+ MCPTransportConstants.SUPPORTED_PROTOCOL_VERSIONS)));
}
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactory.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactory.java
index 3a55210f428..3c5ae4ce315 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactory.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactory.java
@@ -51,6 +51,8 @@ final class ProductionMCPClientTransportFactory {
private static final String MASK_PLAN_TOOL_NAME =
"database_gateway_plan_mask_rule";
+ private static final List<String> SUPPORTED_PROTOCOL_VERSIONS =
List.of(ProtocolVersions.MCP_2025_11_25);
+
private ProductionMCPClientTransportFactory() {
}
@@ -67,7 +69,7 @@ final class ProductionMCPClientTransportFactory {
static McpClientTransport createHttpClientTransport(final URI endpointUri)
{
return
HttpClientStreamableHttpTransport.builder(String.format("%s://%s:%d",
endpointUri.getScheme(), endpointUri.getHost(), endpointUri.getPort()))
- .endpoint(endpointUri.getPath()).build();
+
.endpoint(endpointUri.getPath()).supportedProtocolVersions(SUPPORTED_PROTOCOL_VERSIONS).build();
}
static StdioClientTransport createStdioClientTransport(final Path
configFile) throws IOException {
@@ -111,7 +113,7 @@ final class ProductionMCPClientTransportFactory {
@Override
public List<String> protocolVersions() {
- return List.of(ProtocolVersions.MCP_2025_06_18,
ProtocolVersions.MCP_2025_11_25);
+ return SUPPORTED_PROTOCOL_VERSIONS;
}
}
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactoryTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactoryTest.java
new file mode 100644
index 00000000000..2560f39006a
--- /dev/null
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMCPClientTransportFactoryTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.test.e2e.mcp.runtime.production;
+
+import io.modelcontextprotocol.client.transport.StdioClientTransport;
+import io.modelcontextprotocol.spec.McpClientTransport;
+import io.modelcontextprotocol.spec.ProtocolVersions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ProductionMCPClientTransportFactoryTest {
+
+ @Test
+ void assertCreateHttpClientTransport() {
+ McpClientTransport actual =
ProductionMCPClientTransportFactory.createHttpClientTransport(URI.create("http://127.0.0.1:8080/mcp"));
+ try {
+ assertThat(actual.protocolVersions(),
is(List.of(ProtocolVersions.MCP_2025_11_25)));
+ } finally {
+ actual.closeGracefully().block();
+ }
+ }
+
+ @Test
+ void assertCreateStdioClientTransport(@TempDir final Path tempDir) throws
IOException {
+ StdioClientTransport actual =
ProductionMCPClientTransportFactory.createStdioClientTransport(tempDir.resolve("mcp.yaml"));
+ try {
+ assertThat(actual.protocolVersions(),
is(List.of(ProtocolVersions.MCP_2025_11_25)));
+ } finally {
+ actual.closeGracefully().block();
+ }
+ }
+}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
index e50fe15b5f7..576124974e0 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
@@ -150,7 +150,7 @@ class HttpTransportProtocolContractE2ETest extends
AbstractHttpProtocolOnlyE2ETe
launchHttpTransport();
HttpClient httpClient = HttpClient.newHttpClient();
Map<String, Object> initializeRequestParams = new
LinkedHashMap<>(MCPHttpTransportTestSupport.createInitializeRequestParams("mcp-e2e-programmatic"));
- initializeRequestParams.put("protocolVersion", "2024-11-05");
+ initializeRequestParams.put("protocolVersion", "2025-06-18");
HttpResponse<String> actual = sendInitializeRequest(httpClient,
initializeRequestParams);
assertThat(actual.statusCode(), is(200));
assertThat(actual.headers().firstValue("MCP-Protocol-Version").orElse(""),
is(getProtocolVersion()));
@@ -163,7 +163,7 @@ class HttpTransportProtocolContractE2ETest extends
AbstractHttpProtocolOnlyE2ETe
launchHttpTransport();
HttpClient httpClient = HttpClient.newHttpClient();
String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendCapabilitiesRequest(httpClient,
Map.of("MCP-Session-Id", sessionId, "MCP-Protocol-Version", "2024-11-05"));
+ HttpResponse<String> actual = sendCapabilitiesRequest(httpClient,
Map.of("MCP-Session-Id", sessionId, "MCP-Protocol-Version", "2025-06-18"));
assertThat(actual.statusCode(), is(400));
}