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 9bd13166e5 fix(agent-gateway): validate initialized notification 
acknowledgements (#7805)
9bd13166e5 is described below

commit 9bd13166e567da4dc54dc706bfface4bc4a95479
Author: BobSong <[email protected]>
AuthorDate: Mon Oct 19 14:01:19 2026 +0800

    fix(agent-gateway): validate initialized notification acknowledgements 
(#7805)
    
    Co-authored-by: BobSong-dev <[email protected]>
---
 .../gateway/remote/RequestScopedMcpClient.java     |  3 ++
 .../gateway/remote/RequestScopedMcpClientTest.java | 62 +++++++++++++++++++++-
 2 files changed, 64 insertions(+), 1 deletion(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClient.java
 
b/shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClient.java
index 1d5a75001d..a635c22ea6 100644
--- 
a/shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClient.java
+++ 
b/shenyu-plugin/shenyu-plugin-agent-gateway/src/main/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClient.java
@@ -247,6 +247,9 @@ public final class RequestScopedMcpClient implements 
RemoteMcpEndpoint {
             session = 
RemoteTransportPolicy.session(response.headers().allValues("MCP-Session-Id"));
         }
         if (notification) {
+            if (response.statusCode() != 202 || response.body().length != 0) {
+                throw new IllegalArgumentException("Invalid notification 
acknowledgement");
+            }
             return JSON.createObjectNode();
         }
         String contentType = 
response.headers().firstValue("Content-Type").orElse("").split(";")[0].trim();
diff --git 
a/shenyu-plugin/shenyu-plugin-agent-gateway/src/test/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClientTest.java
 
b/shenyu-plugin/shenyu-plugin-agent-gateway/src/test/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClientTest.java
index e76052a363..f9b47abf52 100644
--- 
a/shenyu-plugin/shenyu-plugin-agent-gateway/src/test/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClientTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-agent-gateway/src/test/java/org/apache/shenyu/plugin/agent/gateway/remote/RequestScopedMcpClientTest.java
@@ -82,6 +82,59 @@ class RequestScopedMcpClientTest {
 
     private static final Duration WAIT = Duration.ofSeconds(5);
 
+    @ParameterizedTest
+    @ValueSource(ints = {200, 201, 204, 206})
+    void rejectsInvalidInitializedStatus(final int status) throws Exception {
+        try (Fixture fixture = new Fixture(false)) {
+            fixture.initializedStatus = status;
+            assertRejectedAcknowledgement(fixture);
+        }
+    }
+
+    @Test
+    void rejectsNonEmptyInitializedAcknowledgement() throws Exception {
+        try (Fixture fixture = new Fixture(false)) {
+            fixture.initializedBody = "{}".getBytes(StandardCharsets.UTF_8);
+            assertRejectedAcknowledgement(fixture);
+        }
+    }
+
+    @Test
+    void acceptsEmptyInitializedAcknowledgement() throws Exception {
+        try (Fixture fixture = new Fixture(false)) {
+            RequestScopedMcpClient client = fixture.client();
+            try {
+                assertEquals("2025-06-18", 
client.initialize().block(WAIT).protocolVersion());
+                assertEquals(1, 
client.listTools(null).block(WAIT).tools().size());
+                awaitIdle(client);
+                assertEquals(1, fixture.requests.stream().filter(request -> 
"initialize".equals(request.get("method"))).count());
+            } finally {
+                client.closeGracefully().block(WAIT);
+            }
+            assertEquals(1, fixture.deletes);
+        }
+    }
+
+    private void assertRejectedAcknowledgement(final Fixture fixture) throws 
InterruptedException {
+        RequestScopedMcpClient client = fixture.client();
+        try {
+            for (int attempt = 0; attempt < 2; attempt++) {
+                
StepVerifier.create(client.initialize()).expectErrorMatches(error -> error 
instanceof IllegalArgumentException
+                        && "Invalid notification 
acknowledgement".equals(error.getMessage())).verify(WAIT);
+            }
+            
StepVerifier.create(client.listTools(null)).expectError(IllegalStateException.class).verify(WAIT);
+            StepVerifier.create(client.callRaw(new 
McpSchema.CallToolRequest("lookup", Map.of())))
+                    .expectError(IllegalStateException.class).verify(WAIT);
+            awaitIdle(client);
+            assertEquals(2, fixture.requests.size());
+            assertEquals(1, fixture.requests.stream().filter(request -> 
"initialize".equals(request.get("method"))).count());
+            assertEquals(1, fixture.requests.stream().filter(request -> 
"notifications/initialized".equals(request.get("method"))).count());
+        } finally {
+            client.closeGracefully().block(WAIT);
+        }
+        assertEquals(1, fixture.deletes);
+    }
+
     @ParameterizedTest
     @ValueSource(booleans = {false, true})
     void expiredDeadlineIsATimeoutWithoutAnUpstreamRequest(final boolean sse) 
throws Exception {
@@ -381,6 +434,10 @@ class RequestScopedMcpClientTest {
 
         private volatile int deleteStatus = 204;
 
+        private volatile int initializedStatus = 202;
+
+        private volatile byte[] initializedBody = new byte[0];
+
         private volatile int deletes;
 
         private Fixture(final boolean sse) throws Exception {
@@ -405,7 +462,10 @@ class RequestScopedMcpClientTest {
                             "cookie", 
Objects.toString(exchange.getRequestHeaders().getFirst("Cookie"), "")));
                     if ("notifications/initialized".equals(method)) {
                         responseBytes.put(request.path("id").asText(), 0);
-                        exchange.sendResponseHeaders(202, -1);
+                        exchange.sendResponseHeaders(initializedStatus, 
initializedBody.length == 0 ? -1 : initializedBody.length);
+                        if (initializedBody.length > 0) {
+                            exchange.getResponseBody().write(initializedBody);
+                        }
                         return;
                     }
                     ObjectNode result = json.createObjectNode();

Reply via email to