This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 5fda45cc45 [type: bug] Fix session key mismatch in streamable HTTP 
transport (#6833) (#6997)
5fda45cc45 is described below

commit 5fda45cc45fb5386309db23d9f329639caf1e2ea
Author: Arvin <[email protected]>
AuthorDate: Sun Oct 11 23:20:53 2026 +0800

    [type: bug] Fix session key mismatch in streamable HTTP transport (#6833) 
(#6997)
---
 ...henyuStreamableHttpServerTransportProvider.java | 16 +++++++-
 ...uStreamableHttpServerTransportProviderTest.java | 45 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 1 deletion(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProvider.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProvider.java
index 344257f912..ba356c2a14 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProvider.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProvider.java
@@ -332,6 +332,7 @@ public class ShenyuStreamableHttpServerTransportProvider 
implements McpServerTra
             final StreamableHttpSessionTransport transport = new 
StreamableHttpSessionTransport();
             final McpServerSession session = sessionFactory.create(transport);
             final String newSessionId = session.getId();
+            transport.setSessionId(newSessionId);
             LOGGER.debug("Created new MCP session: {}", newSessionId);
             // Store session and transport for reuse
             sessions.put(newSessionId, session);
@@ -1054,7 +1055,7 @@ public class ShenyuStreamableHttpServerTransportProvider 
implements McpServerTra
      */
     private class StreamableHttpSessionTransport implements McpServerTransport 
{
 
-        private final String sessionId;
+        private String sessionId;
 
         private volatile boolean closed;
 
@@ -1082,6 +1083,19 @@ public class ShenyuStreamableHttpServerTransportProvider 
implements McpServerTra
             LOGGER.debug("Created StreamableHttpSessionTransport with 
sessionId: {}", this.sessionId);
         }
 
+        /**
+         * Sets the session ID to match the MCP server session's ID.
+         * This ensures the transport's sessionId is consistent with the key 
used
+         * to store the session in {@code sessions} and {@code 
sessionTransports},
+         * so that {@link #close()} and {@link #closeGracefully()} correctly
+         * remove the session.
+         *
+         * @param sessionId the session identifier to use
+         */
+        public void setSessionId(final String sessionId) {
+            this.sessionId = sessionId;
+        }
+
         /**
          * Gets the last message sent through this transport.
          * This is used for Streamable HTTP response correlation, allowing the
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProviderTest.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProviderTest.java
index f2b160e5ac..e73ccae0dc 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProviderTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuStreamableHttpServerTransportProviderTest.java
@@ -20,6 +20,7 @@ package org.apache.shenyu.plugin.mcp.server.transport;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.modelcontextprotocol.spec.McpSchema;
 import io.modelcontextprotocol.spec.McpServerSession;
+import io.modelcontextprotocol.spec.McpServerTransport;
 import io.modelcontextprotocol.server.McpRequestHandler;
 import org.apache.shenyu.plugin.mcp.server.session.McpSessionHelper;
 import org.springframework.web.server.ServerWebExchange;
@@ -229,6 +230,50 @@ class ShenyuStreamableHttpServerTransportProviderTest {
         provider.removeSession(sessionId);
     }
 
+    /**
+     * Regression test for the reported initialize-path session leak (#6833).
+     *
+     * <p>Before the fix the provider stored the session under the MCP server 
session ID while the
+     * transport kept its own independently auto-generated {@code sessionId}, 
so {@code close()}
+     * looked up a key that never existed in {@code sessions} / {@code 
sessionTransports} and left
+     * both registries plus {@link ShenyuMcpExchangeHolder} populated forever.
+     *
+     * <p>Assertion: after a real initialize handshake the ID returned to the 
client
+     * ({@code Mcp-Session-Id}) is exactly the key used in both registries, so 
a later
+     * {@code close()} is able to remove the entry.
+     */
+    @Test
+    void testInitializeRegistersSessionUnderReturnedSessionId() throws 
Exception {
+        ShenyuStreamableHttpServerTransportProvider provider = 
providerWithRealSessions();
+
+        MockServerHttpResponse response = performRequest(provider,
+                postRequest(INITIALIZE_REQUEST_BODY, null));
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+
+        final String returnedSessionId = 
response.getHeaders().getFirst(SESSION_ID_HEADER);
+        assertNotNull(returnedSessionId);
+
+        final Map<String, ?> sessions = readMap(provider, "sessions");
+        final Map<String, ?> transports = readMap(provider, 
"sessionTransports");
+        assertTrue(sessions.containsKey(returnedSessionId),
+                "sessions must be keyed by the session ID returned to the 
client, otherwise close() cannot clean it up");
+        assertTrue(transports.containsKey(returnedSessionId),
+                "sessionTransports must be keyed by the session ID returned to 
the client, otherwise close() cannot clean it up");
+
+        // Drive the close path: it is the only thing that exercises the 
transport's own sessionId,
+        // so without the setSessionId() fix nothing would be removed here. 
Asserting only the map
+        // keys above would pass on master too (they were always keyed by the 
returned id); this part
+        // fails when line transport.setSessionId(newSessionId) is reverted.
+        final McpServerTransport transport = (McpServerTransport) 
transports.get(returnedSessionId);
+        assertNotNull(transport, "the transport must be reachable via the 
returned session id");
+        transport.closeGracefully().block();
+
+        assertTrue(sessions.isEmpty(), "close() must remove the session from 
the sessions map");
+        assertTrue(transports.isEmpty(), "close() must remove the transport 
from the sessionTransports map");
+        assertNull(ShenyuMcpExchangeHolder.get(returnedSessionId),
+                "close() must remove the exchange binding for the returned 
session id");
+    }
+
     @SuppressWarnings("unchecked")
     private Map<String, ?> readMap(final 
ShenyuStreamableHttpServerTransportProvider provider, final String fieldName)
             throws Exception {

Reply via email to