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 3085bc7ea5 fix: handle the default annotations of the mcp openApi 
generator (#6666) (#7177)
3085bc7ea5 is described below

commit 3085bc7ea5e77382e08698b0d8eb248c28f1614f
Author: HY-love-sleep <[email protected]>
AuthorDate: Thu Sep 24 11:28:09 2026 +0800

    fix: handle the default annotations of the mcp openApi generator (#6666) 
(#7177)
---
 .../client/mcp/generator/McpOpenApiGenerator.java  |  18 +++-
 .../mcp/generator/McpOpenApiGeneratorTest.java     | 119 +++++++++++++++++++++
 2 files changed, 132 insertions(+), 5 deletions(-)

diff --git 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGenerator.java
 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGenerator.java
index 1446fa5e0b..eba9ed0be2 100644
--- 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGenerator.java
+++ 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGenerator.java
@@ -20,6 +20,7 @@ package org.apache.shenyu.client.mcp.generator;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.servers.Server;
 import io.swagger.v3.oas.models.parameters.Parameter;
 import org.apache.shenyu.client.mcp.common.annotation.ShenyuMcpTool;
 import org.apache.shenyu.client.mcp.common.constants.OpenApiConstants;
@@ -46,9 +47,14 @@ public class McpOpenApiGenerator {
         root.add(OpenApiConstants.OPEN_API_INFO_KEY, info);
 
         // Servers
-        JsonObject server = new JsonObject();
-        root.add(OpenApiConstants.OPEN_API_SERVER_KEY, server);
-        server.addProperty(OpenApiConstants.OPEN_API_SERVER_URL_KEY, 
definition.servers()[0].url());
+        // The servers array of @OpenAPIDefinition defaults to empty, which is 
the common case for a bare
+        // @ShenyuMcpTool; the server block is optional in OpenAPI 3.0, so it 
is only emitted when configured.
+        Server[] servers = definition.servers();
+        if (servers.length > 0) {
+            JsonObject server = new JsonObject();
+            root.add(OpenApiConstants.OPEN_API_SERVER_KEY, server);
+            server.addProperty(OpenApiConstants.OPEN_API_SERVER_URL_KEY, 
servers[0].url());
+        }
 
         // Paths
         JsonObject paths = new JsonObject();
@@ -70,9 +76,11 @@ public class McpOpenApiGenerator {
         JsonArray parameters = new JsonArray();
         
methodMap.add(OpenApiConstants.OPEN_API_PATH_OPERATION_METHOD_PARAMETERS_KEY, 
parameters);
 
-        List<io.swagger.v3.oas.models.parameters.Parameter> parameterList = 
shenyuMcpTool.getOperation().getParameters();
+        List<Parameter> parameterList = 
shenyuMcpTool.getOperation().getParameters();
 
-        if (!parameterList.isEmpty()) {
+        // The parameters of @Operation also default to unset, so the list can 
be null for a tool whose method
+        // declares no parameters.
+        if (Objects.nonNull(parameterList) && !parameterList.isEmpty()) {
 
             for (Parameter parameter : parameterList) {
                 JsonObject parameterObj = new JsonObject();
diff --git 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/test/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGeneratorTest.java
 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/test/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGeneratorTest.java
new file mode 100644
index 0000000000..7ef81711b5
--- /dev/null
+++ 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/test/java/org/apache/shenyu/client/mcp/generator/McpOpenApiGeneratorTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.shenyu.client.mcp.generator;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.servers.Server;
+import org.apache.shenyu.client.mcp.common.annotation.ShenyuMcpTool;
+import org.apache.shenyu.client.mcp.common.constants.OpenApiConstants;
+import org.apache.shenyu.client.mcp.utils.OpenApiConvertorUtil;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test cases for {@link McpOpenApiGenerator}.
+ */
+public class McpOpenApiGeneratorTest {
+
+    private static final String URL = "/mcp/test/tool";
+
+    private static final String SERVER_URL = "http://localhost:8080";;
+
+    /**
+     * A bare tool uses the default annotations, whose servers() and 
parameters are empty: the generated document
+     * must still be produced instead of failing with an 
ArrayIndexOutOfBoundsException or a NullPointerException.
+     */
+    @Test
+    void testGenerateOpenApiJsonWithDefaultAnnotations() {
+        JsonObject json = 
McpOpenApiGenerator.generateOpenApiJson(getAnnotation(DefaultTool.class), 
buildTool(DefaultTool.class), URL);
+
+        assertFalse(json.has(OpenApiConstants.OPEN_API_SERVER_KEY), "the 
optional server block is omitted when servers() is empty");
+        assertEquals("3.0.0", 
json.get(OpenApiConstants.OPEN_API_VERSION_KEY).getAsString());
+        assertTrue(json.has(OpenApiConstants.OPEN_API_PATH_KEY));
+        
assertTrue(json.getAsJsonObject(OpenApiConstants.OPEN_API_PATH_KEY).has(URL));
+        JsonArray parameters = 
json.getAsJsonObject(OpenApiConstants.OPEN_API_PATH_KEY)
+                .getAsJsonObject(URL).getAsJsonObject("get")
+                
.getAsJsonArray(OpenApiConstants.OPEN_API_PATH_OPERATION_METHOD_PARAMETERS_KEY);
+        assertTrue(parameters.isEmpty(), "a tool without parameters produces 
an empty parameter list");
+    }
+
+    /**
+     * The guard must not change the behaviour for a tool that configures a 
server.
+     */
+    @Test
+    void testGenerateOpenApiJsonWithConfiguredServer() {
+        JsonObject json = 
McpOpenApiGenerator.generateOpenApiJson(getAnnotation(ConfiguredServerTool.class),
+                buildTool(ConfiguredServerTool.class), URL);
+
+        assertTrue(json.has(OpenApiConstants.OPEN_API_SERVER_KEY));
+        assertEquals(SERVER_URL, 
json.getAsJsonObject(OpenApiConstants.OPEN_API_SERVER_KEY)
+                .get(OpenApiConstants.OPEN_API_SERVER_URL_KEY).getAsString());
+    }
+
+    /**
+     * Parameters are still reported when the tool declares them.
+     */
+    @Test
+    void testGenerateOpenApiJsonWithParameters() {
+        JsonObject json = 
McpOpenApiGenerator.generateOpenApiJson(getAnnotation(ToolWithParameters.class),
+                buildTool(ToolWithParameters.class), URL);
+
+        JsonArray parameters = 
json.getAsJsonObject(OpenApiConstants.OPEN_API_PATH_KEY)
+                .getAsJsonObject(URL).getAsJsonObject("get")
+                
.getAsJsonArray(OpenApiConstants.OPEN_API_PATH_OPERATION_METHOD_PARAMETERS_KEY);
+        assertEquals(1, parameters.size());
+        assertEquals("id", parameters.get(0).getAsJsonObject()
+                
.get(OpenApiConstants.OPEN_API_PATH_OPERATION_METHOD_PARAMETERS_NAME_KEY).getAsString());
+    }
+
+    private static ShenyuMcpTool getAnnotation(final Class<?> clazz) {
+        return clazz.getAnnotation(ShenyuMcpTool.class);
+    }
+
+    /**
+     * Build the tool the same way the client registration does: the operation 
is converted from the annotation.
+     */
+    private static org.apache.shenyu.client.mcp.common.dto.ShenyuMcpTool 
buildTool(final Class<?> clazz) {
+        org.apache.shenyu.client.mcp.common.dto.ShenyuMcpTool tool = new 
org.apache.shenyu.client.mcp.common.dto.ShenyuMcpTool();
+        
tool.setOperation(OpenApiConvertorUtil.convertOperation(getAnnotation(clazz).operation()));
+        tool.setMethod("get");
+        tool.setToolName(getAnnotation(clazz).toolName());
+        return tool;
+    }
+
+    @ShenyuMcpTool(toolName = "defaultTool", desc = "bare tool: servers() and 
parameters are unset")
+    private static class DefaultTool {
+    }
+
+    @ShenyuMcpTool(definition = @OpenAPIDefinition(servers = @Server(url = 
SERVER_URL)), toolName = "configuredServerTool")
+    private static class ConfiguredServerTool {
+    }
+
+    @ShenyuMcpTool(toolName = "toolWithParameters",
+            operation = @Operation(parameters = @Parameter(name = "id", in = 
ParameterIn.QUERY, description = "the id")))
+    private static class ToolWithParameters {
+    }
+}

Reply via email to