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

sihengyu 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 c2efae14e7 fix: improve MCP server plugin path handling and backward 
compatibility for argsPosition (#6293)
c2efae14e7 is described below

commit c2efae14e795655f3ec37a9e9e78d33bfeedf5dc
Author: aias00 <[email protected]>
AuthorDate: Wed Feb 25 19:37:25 2026 +0800

    fix: improve MCP server plugin path handling and backward compatibility for 
argsPosition (#6293)
    
    * fix: improve MCP server plugin path handling and backward compatibility 
for argsPosition
    
    * fix: enhance MCP server plugin path validation and improve handling of 
blank paths
    
    * Update 
shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandler.java
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update 
shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManager.java
    
    Co-authored-by: Copilot <[email protected]>
    
    * fix: streamline MCP server plugin path handling by removing redundant 
checks
    
    * fix: remove unused imports in ShenyuMcpServerManager
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
    Co-authored-by: Yu Siheng <[email protected]>
---
 .../mcp/generator/McpRequestConfigGenerator.java   |   8 +-
 .../shenyu/client/mcp/McpServiceEventListener.java |   5 +-
 .../server/handler/McpServerPluginDataHandler.java |  72 ++++++----
 .../mcp/server/manager/ShenyuMcpServerManager.java | 157 ++++++++++++++++-----
 .../mcp/server/request/RequestConfigHelper.java    |  12 +-
 .../ShenyuSseServerTransportProvider.java          |  11 +-
 ...henyuStreamableHttpServerTransportProvider.java |   1 +
 .../mcp/server/McpServerPluginIntegrationTest.java |   6 +-
 .../handler/McpServerPluginDataHandlerTest.java    |  38 ++++-
 .../server/manager/ShenyuMcpServerManagerTest.java |  13 +-
 .../server/request/RequestConfigHelperTest.java    |  11 ++
 11 files changed, 257 insertions(+), 77 deletions(-)

diff --git 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpRequestConfigGenerator.java
 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpRequestConfigGenerator.java
index b5c8ddf608..feea24fd41 100644
--- 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpRequestConfigGenerator.java
+++ 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-common/src/main/java/org/apache/shenyu/client/mcp/generator/McpRequestConfigGenerator.java
@@ -60,11 +60,10 @@ public class McpRequestConfigGenerator {
         requestTemplate.addProperty(RequestTemplateConstants.METHOD_KEY, 
methodType);
 
         // argsPosition
+        JsonObject argsPosition = new JsonObject();
         JsonObject methodTypeJson = method.getAsJsonObject(methodType);
         JsonArray parameters = 
methodTypeJson.getAsJsonArray(OpenApiConstants.OPEN_API_PATH_OPERATION_METHOD_PARAMETERS_KEY);
         if (Objects.nonNull(parameters)) {
-            JsonObject argsPosition = new JsonObject();
-
             for (JsonElement parameter : parameters) {
                 JsonObject paramObj = parameter.getAsJsonObject();
 
@@ -77,8 +76,11 @@ public class McpRequestConfigGenerator {
                     argsPosition.addProperty(name, inValue);
                 }
             }
-            requestTemplate.add(RequestTemplateConstants.ARGS_POSITION_KEY, 
argsPosition);
         }
+        // Keep root-level argsPosition as canonical format used by gateway 
parser.
+        root.add(RequestTemplateConstants.ARGS_POSITION_KEY, 
argsPosition.deepCopy());
+        // Keep requestTemplate-level argsPosition for backward compatibility.
+        requestTemplate.add(RequestTemplateConstants.ARGS_POSITION_KEY, 
argsPosition);
 
         // argsToJsonBody
         requestTemplate.addProperty(RequestTemplateConstants.BODY_JSON_KEY, 
shenyuMcpRequestConfig.getBodyToJson());
diff --git 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-register/src/main/java/org/apache/shenyu/client/mcp/McpServiceEventListener.java
 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-register/src/main/java/org/apache/shenyu/client/mcp/McpServiceEventListener.java
index 4b333b3a69..e9a15032ad 100644
--- 
a/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-register/src/main/java/org/apache/shenyu/client/mcp/McpServiceEventListener.java
+++ 
b/shenyu-client/shenyu-client-mcp/shenyu-client-mcp-register/src/main/java/org/apache/shenyu/client/mcp/McpServiceEventListener.java
@@ -304,6 +304,9 @@ public class McpServiceEventListener extends 
AbstractContextRefreshedEventListen
     @Override
     protected String buildApiSuperPath(final Class<?> clazz, final 
ShenyuMcpTool beanShenyuClient) {
         Server[] servers = beanShenyuClient.definition().servers();
+        if (servers.length == 0) {
+            return "";
+        }
         if (servers.length != 1) {
             log.warn("The shenyuMcp service supports only a single server 
entry. Please ensure that only one server is configured");
         }
@@ -363,7 +366,7 @@ public class McpServiceEventListener extends 
AbstractContextRefreshedEventListen
         validateClientConfig(shenyuMcpTool, url);
         JsonObject openApiJson = 
McpOpenApiGenerator.generateOpenApiJson(classShenyuClient, shenyuMcpTool, url);
         McpToolsRegisterDTO mcpToolsRegisterDTO = 
McpToolsRegisterDTOGenerator.generateRegisterDTO(shenyuMcpTool, openApiJson, 
url, namespaceId);
-        MetaDataRegisterDTO metaDataRegisterDTO = buildMetaDataDTO(bean, 
classShenyuClient, superPath, clazz, method, namespaceId);
+        MetaDataRegisterDTO metaDataRegisterDTO = buildMetaDataDTO(bean, 
classShenyuClient, url, clazz, method, namespaceId);
         metaDataRegisterDTO.setEnabled(shenyuMcpTool.getEnable());
         mcpToolsRegisterDTO.setMetaDataRegisterDTO(metaDataRegisterDTO);
         return mcpToolsRegisterDTO;
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandler.java
index b660e9df84..be8a7af7f9 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandler.java
@@ -78,15 +78,14 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
             return;
         }
 
-        // Get the URI from selector data
-        String uri = selectorData.getConditionList().stream()
-                .filter(condition -> 
Constants.URI.equals(condition.getParamType()))
-                .map(ConditionData::getParamValue)
-                .findFirst()
-                .orElse(null);
-
-        String path = StringUtils.removeEnd(uri, SLASH);
-        path = StringUtils.removeEnd(path, STAR);
+        String uri = extractSelectorUri(selectorData);
+        if (StringUtils.isBlank(uri)) {
+            return;
+        }
+        String path = normalizeSelectorPath(uri);
+        if (StringUtils.isBlank(path)) {
+            return;
+        }
         ShenyuMcpServer shenyuMcpServer = 
GsonUtils.getInstance().fromJson(StringUtils.isBlank(selectorData.getHandle()) 
? DEFAULT_MESSAGE_ENDPOINT : selectorData.getHandle(), ShenyuMcpServer.class);
         shenyuMcpServer.setPath(path);
         CACHED_SERVER.get().cachedHandle(
@@ -94,8 +93,8 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
                 shenyuMcpServer);
         String messageEndpoint = shenyuMcpServer.getMessageEndpoint();
         // Get or create McpServer for this URI
-        if (StringUtils.isNotBlank(uri) && 
!shenyuMcpServerManager.hasMcpServer(uri)) {
-            shenyuMcpServerManager.getOrCreateMcpServerTransport(uri, 
messageEndpoint);
+        if (StringUtils.isNotBlank(path) && 
!shenyuMcpServerManager.hasMcpServer(path)) {
+            shenyuMcpServerManager.getOrCreateMcpServerTransport(path, 
messageEndpoint);
         }
         if (StringUtils.isNotBlank(path)) {
             shenyuMcpServerManager.getOrCreateStreamableHttpTransport(path + 
STREAMABLE_HTTP_PATH);
@@ -108,31 +107,27 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
 
     @Override
     public void removeSelector(final SelectorData selectorData) {
+        if (Objects.isNull(selectorData) || 
Objects.isNull(selectorData.getId())) {
+            return;
+        }
         UpstreamCacheManager.getInstance().removeByKey(selectorData.getId());
         MetaDataCache.getInstance().clean();
         
CACHED_TOOL.get().removeHandle(CacheKeyUtils.INST.getKey(selectorData.getId(), 
Constants.DEFAULT_RULE));
 
-        // Remove the McpServer for this URI
-        // First try to get URI from handle, then from condition list
-        String uri = selectorData.getHandle();
-        if (StringUtils.isBlank(uri)) {
-            // Try to get URI from condition list
-            uri = selectorData.getConditionList().stream()
-                    .filter(condition -> 
Constants.URI.equals(condition.getParamType()))
-                    .map(ConditionData::getParamValue)
-                    .findFirst()
-                    .orElse(null);
-        }
+        String path = normalizeSelectorPath(extractSelectorUri(selectorData));
 
         CACHED_SERVER.get().removeHandle(selectorData.getId());
 
-        if (StringUtils.isNotBlank(uri) && 
shenyuMcpServerManager.hasMcpServer(uri)) {
-            shenyuMcpServerManager.removeMcpServer(uri);
+        if (StringUtils.isNotBlank(path) && 
shenyuMcpServerManager.hasMcpServer(path)) {
+            shenyuMcpServerManager.removeMcpServer(path);
         }
     }
 
     @Override
     public void handlerRule(final RuleData ruleData) {
+        if (Objects.isNull(ruleData)) {
+            return;
+        }
         Optional.ofNullable(ruleData.getHandle()).ifPresent(s -> {
             ShenyuMcpServerTool mcpServerTool = 
GsonUtils.getInstance().fromJson(s, ShenyuMcpServerTool.class);
             
CACHED_TOOL.get().cachedHandle(CacheKeyUtils.INST.getKey(ruleData), 
mcpServerTool);
@@ -145,7 +140,7 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
             // Create JSON schema from parameters
             String inputSchema = 
JsonSchemaUtil.createParameterSchema(parameters);
             ShenyuMcpServer server = 
CACHED_SERVER.get().obtainHandle(ruleData.getSelectorId());
-            if (Objects.nonNull(server)) {
+            if (Objects.nonNull(server) && 
StringUtils.isNotBlank(server.getPath())) {
                 shenyuMcpServerManager.addTool(server.getPath(),
                         StringUtils.isBlank(mcpServerTool.getName()) ? 
ruleData.getName()
                                 : mcpServerTool.getName(),
@@ -158,10 +153,15 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
 
     @Override
     public void removeRule(final RuleData ruleData) {
+        if (Objects.isNull(ruleData)) {
+            return;
+        }
         Optional.ofNullable(ruleData.getHandle()).ifPresent(s -> {
             
CACHED_TOOL.get().removeHandle(CacheKeyUtils.INST.getKey(ruleData));
             ShenyuMcpServer server = 
CACHED_SERVER.get().obtainHandle(ruleData.getSelectorId());
-            shenyuMcpServerManager.removeTool(server.getPath(), 
ruleData.getName());
+            if (Objects.nonNull(server) && 
StringUtils.isNotBlank(server.getPath())) {
+                shenyuMcpServerManager.removeTool(server.getPath(), 
ruleData.getName());
+            }
         });
         MetaDataCache.getInstance().clean();
     }
@@ -171,4 +171,24 @@ public class McpServerPluginDataHandler implements 
PluginDataHandler {
         return PluginEnum.MCP_SERVER.getName();
     }
 
+    private String extractSelectorUri(final SelectorData selectorData) {
+        if (Objects.isNull(selectorData) || 
CollectionUtils.isEmpty(selectorData.getConditionList())) {
+            return null;
+        }
+        return selectorData.getConditionList().stream()
+                .filter(condition -> 
Constants.URI.equals(condition.getParamType()))
+                .map(ConditionData::getParamValue)
+                .findFirst()
+                .orElse(null);
+    }
+
+    private String normalizeSelectorPath(final String selectorUri) {
+        if (StringUtils.isBlank(selectorUri)) {
+            return selectorUri;
+        }
+        String path = StringUtils.removeEnd(selectorUri, STAR);
+        path = StringUtils.removeEnd(path, SLASH);
+        return StringUtils.defaultIfBlank(path, SLASH);
+    }
+
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManager.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManager.java
index f60589a91a..432fe295a7 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManager.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManager.java
@@ -42,6 +42,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.net.URI;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -172,7 +173,7 @@ public class ShenyuMcpServerManager {
      * @return normalized path
      */
     private String processPath(final String uri) {
-        return normalizeServerPath(extractBasePath(uri));
+        return normalizeServerPath(uri);
     }
 
     /**
@@ -244,7 +245,7 @@ public class ShenyuMcpServerManager {
      * Creates SSE transport provider.
      */
     private ShenyuSseServerTransportProvider createSseTransport(final String 
normalizedPath, final String messageEndPoint) {
-        String messageEndpoint = normalizedPath + messageEndPoint;
+        String messageEndpoint = joinPath(normalizedPath, messageEndPoint);
         ShenyuSseServerTransportProvider transportProvider = 
ShenyuSseServerTransportProvider.builder()
                 .objectMapper(objectMapper)
                 .sseEndpoint(normalizedPath)
@@ -285,39 +286,17 @@ public class ShenyuMcpServerManager {
      */
     private void registerRoutes(final String primaryPath, final String 
secondaryPath, 
                                final HandlerFunction<?> primaryHandler, final 
HandlerFunction<?> secondaryHandler) {
-        routeMap.put(primaryPath, primaryHandler);
-        routeMap.put(primaryPath + "/**", primaryHandler);
+        String normalizedPrimaryPath = normalizeRoutePath(primaryPath);
+        routeMap.put(normalizedPrimaryPath, primaryHandler);
+        routeMap.put(normalizedPrimaryPath + "/**", primaryHandler);
         
         if (Objects.nonNull(secondaryPath) && 
Objects.nonNull(secondaryHandler)) {
-            routeMap.put(secondaryPath, secondaryHandler);
-            routeMap.put(secondaryPath + "/**", secondaryHandler);
+            String normalizedSecondaryPath = normalizeRoutePath(secondaryPath);
+            routeMap.put(normalizedSecondaryPath, secondaryHandler);
+            routeMap.put(normalizedSecondaryPath + "/**", secondaryHandler);
         }
     }
 
-    /**
-     * Extract the base path from a URI by removing the /message suffix and 
any sub-paths.
-     *
-     * @param uri The URI to extract base path from
-     * @return The base path
-     */
-    private String extractBasePath(final String uri) {
-        String basePath = uri;
-
-        // Remove /message suffix if present
-        if (basePath.endsWith("/message")) {
-            basePath = basePath.substring(0, basePath.length() - 
"/message".length());
-        }
-
-        // For sub-paths, extract the main MCP server path
-        String[] pathSegments = basePath.split("/");
-        if (pathSegments.length >= 2) {
-            // Keep only the first two segments (empty + server-name)
-            basePath = "/" + pathSegments[1];
-        }
-
-        return basePath;
-    }
-
     /**
      * Check if a McpServer exists for the given URI.
      *
@@ -390,7 +369,7 @@ public class ShenyuMcpServerManager {
      */
     public synchronized void addTool(final String serverPath, final String 
name, final String description,
                         final String requestTemplate, final String 
inputSchema) {
-        String normalizedPath = 
normalizeServerPath(extractBasePath(serverPath));
+        String normalizedPath = processPath(serverPath);
 
         // Remove existing tool first
         try {
@@ -442,7 +421,7 @@ public class ShenyuMcpServerManager {
      * @param name       the tool name
      */
     public void removeTool(final String serverPath, final String name) {
-        String normalizedPath = normalizeServerPath(serverPath);
+        String normalizedPath = processPath(serverPath);
         LOG.debug("Removing tool from shared server - name: {}, path: {}", 
name, normalizedPath);
 
         McpAsyncServer sharedServer = sharedServerMap.get(normalizedPath);
@@ -485,7 +464,7 @@ public class ShenyuMcpServerManager {
      * @return Set of supported protocols
      */
     public Set<String> getSupportedProtocols(final String serverPath) {
-        String normalizedPath = normalizeServerPath(serverPath);
+        String normalizedPath = processPath(serverPath);
         CompositeTransportProvider compositeTransport = 
compositeTransportMap.get(normalizedPath);
         return Objects.nonNull(compositeTransport) ? 
compositeTransport.getSupportedProtocols() : new HashSet<>();
     }
@@ -501,17 +480,119 @@ public class ShenyuMcpServerManager {
             return null;
         }
 
-        String normalizedPath = path;
+        String normalizedPath = path.trim();
+        if (normalizedPath.isEmpty()) {
+            return "/";
+        }
 
-        // Remove /streamablehttp suffix
-        if (normalizedPath.endsWith("/streamablehttp")) {
-            normalizedPath = normalizedPath.substring(0, 
normalizedPath.length() - "/streamablehttp".length());
-            LOG.debug("Normalized Streamable HTTP path from '{}' to '{}' for 
shared server", path, normalizedPath);
+        try {
+            URI uri = URI.create(normalizedPath);
+            if (Objects.nonNull(uri.getScheme())) {
+                normalizedPath = uri.getRawPath();
+            }
+        } catch (IllegalArgumentException ignored) {
+            // Keep original input when it's not a full URI.
+        }
+
+        if (Objects.isNull(normalizedPath) || normalizedPath.isEmpty()) {
+            normalizedPath = "/";
+        }
+        if (!normalizedPath.startsWith("/")) {
+            normalizedPath = "/" + normalizedPath;
+        }
+        int queryStart = normalizedPath.indexOf('?');
+        if (queryStart >= 0) {
+            normalizedPath = normalizedPath.substring(0, queryStart);
+        }
+        int fragmentStart = normalizedPath.indexOf('#');
+        if (fragmentStart >= 0) {
+            normalizedPath = normalizedPath.substring(0, fragmentStart);
         }
 
+        normalizedPath = normalizedPath.replaceAll("/{2,}", "/");
+        if (normalizedPath.endsWith("/**")) {
+            normalizedPath = normalizedPath.substring(0, 
normalizedPath.length() - "/**".length());
+        }
+        normalizedPath = removeSuffix(normalizedPath, "/message");
+        normalizedPath = removeSuffix(normalizedPath, "/sse");
+        normalizedPath = removeSuffix(normalizedPath, "/streamablehttp");
+
+        if (normalizedPath.length() > 1 && normalizedPath.endsWith("/")) {
+            normalizedPath = normalizedPath.substring(0, 
normalizedPath.length() - 1);
+        }
+        if (normalizedPath.isEmpty()) {
+            return "/";
+        }
         return normalizedPath;
     }
 
+    private String normalizeRoutePath(final String path) {
+        String routePath = Objects.isNull(path) ? "/" : path;
+        routePath = routePath.trim();
+        if (routePath.isEmpty()) {
+            return "/";
+        }
+
+        try {
+            URI uri = URI.create(routePath);
+            if (Objects.nonNull(uri.getScheme())) {
+                routePath = uri.getRawPath();
+            }
+        } catch (IllegalArgumentException ignored) {
+            // Keep original input when it's not a full URI.
+        }
+
+        if (Objects.isNull(routePath) || routePath.isEmpty()) {
+            routePath = "/";
+        }
+        if (!routePath.startsWith("/")) {
+            routePath = "/" + routePath;
+        }
+
+        int queryStart = routePath.indexOf('?');
+        if (queryStart >= 0) {
+            routePath = routePath.substring(0, queryStart);
+        }
+        int fragmentStart = routePath.indexOf('#');
+        if (fragmentStart >= 0) {
+            routePath = routePath.substring(0, fragmentStart);
+        }
+        routePath = routePath.replaceAll("/{2,}", "/");
+        if (routePath.length() > 1 && routePath.endsWith("/")) {
+            routePath = routePath.substring(0, routePath.length() - 1);
+        }
+        if (routePath.isEmpty()) {
+            return "/";
+        }
+        return routePath;
+    }
+
+    private String joinPath(final String basePath, final String subPath) {
+        String safeBase = normalizeRoutePath(basePath);
+        if (Objects.isNull(subPath) || subPath.trim().isEmpty()) {
+            return safeBase;
+        }
+        String safeSub = subPath.trim();
+        if (safeBase.endsWith("/") && safeSub.startsWith("/")) {
+            return safeBase + safeSub.substring(1);
+        }
+        if (!safeBase.endsWith("/") && !safeSub.startsWith("/")) {
+            return safeBase + "/" + safeSub;
+        }
+        return safeBase + safeSub;
+    }
+
+    private String removeSuffix(final String value, final String suffix) {
+        if (Objects.isNull(value) || Objects.isNull(suffix) || 
suffix.isEmpty()) {
+            return value;
+        }
+        if (value.endsWith(suffix)) {
+            String result = value.substring(0, value.length() - 
suffix.length());
+            return result.isEmpty() ? "/" : result;
+        }
+        return value;
+    }
+
     /**
      * Composite transport provider that delegates to multiple transport 
implementations.
      * Enhanced with protocol-aware session management and improved error 
handling.
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelper.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelper.java
index 556b0cc490..6f60d661c8 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelper.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelper.java
@@ -20,6 +20,8 @@ package org.apache.shenyu.plugin.mcp.server.request;
 import com.google.gson.JsonObject;
 import org.apache.shenyu.common.utils.GsonUtils;
 
+import java.util.Objects;
+
 /**
  * Helper class for parsing and handling requestConfig.
  */
@@ -51,7 +53,15 @@ public class RequestConfigHelper {
      * @return the argument position json object
      */
     public JsonObject getArgsPosition() {
-        return configJson.has("argsPosition") ? 
configJson.getAsJsonObject("argsPosition") : new JsonObject();
+        if (configJson.has("argsPosition")) {
+            return configJson.getAsJsonObject("argsPosition");
+        }
+        // Backward compatibility for configs generated with nested 
argsPosition.
+        JsonObject requestTemplate = getRequestTemplate();
+        if (Objects.nonNull(requestTemplate) && 
requestTemplate.has("argsPosition")) {
+            return requestTemplate.getAsJsonObject("argsPosition");
+        }
+        return new JsonObject();
     }
 
     /**
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuSseServerTransportProvider.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuSseServerTransportProvider.java
index b0bccdf0ef..d1e608d14c 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuSseServerTransportProvider.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/transport/ShenyuSseServerTransportProvider.java
@@ -27,6 +27,7 @@ import io.modelcontextprotocol.spec.McpServerSession;
 import io.modelcontextprotocol.spec.McpServerTransport;
 import io.modelcontextprotocol.spec.McpServerTransportProvider;
 import io.modelcontextprotocol.util.Assert;
+import org.apache.shenyu.plugin.mcp.server.holder.ShenyuMcpExchangeHolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
@@ -210,7 +211,12 @@ public class ShenyuSseServerTransportProvider implements 
McpServerTransportProvi
         return Flux.fromIterable(sessions
                 .values())
                 .doFirst(() -> LOGGER.debug("Initiating graceful shutdown with 
{} active sessions", sessions.size()))
-                .flatMap(McpServerSession::closeGracefully).then();
+                .flatMap(McpServerSession::closeGracefully)
+                .then()
+                .doFinally(signalType -> {
+                    sessions.keySet().forEach(ShenyuMcpExchangeHolder::remove);
+                    sessions.clear();
+                });
     }
 
     /**
@@ -259,6 +265,7 @@ public class ShenyuSseServerTransportProvider implements 
McpServerTransportProvi
                         sink.onCancel(() -> {
                             LOGGER.debug("Session {} cancelled", sessionId);
                             sessions.remove(sessionId);
+                            ShenyuMcpExchangeHolder.remove(sessionId);
                         });
                     } catch (Exception e) {
                         LOGGER.error("Error creating SSE session", e);
@@ -312,11 +319,13 @@ public class ShenyuSseServerTransportProvider implements 
McpServerTransportProvi
                 sink.onCancel(() -> {
                     LOGGER.info("Session {} cancelled by client", sessionId);
                     sessions.remove(sessionId);
+                    ShenyuMcpExchangeHolder.remove(sessionId);
                 });
 
                 sink.onDispose(() -> {
                     LOGGER.info("Session {} disposed", sessionId);
                     sessions.remove(sessionId);
+                    ShenyuMcpExchangeHolder.remove(sessionId);
                 });
 
             } catch (Exception e) {
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 f67c9c8d51..540d834853 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
@@ -763,6 +763,7 @@ public class ShenyuStreamableHttpServerTransportProvider 
implements McpServerTra
     public void removeSession(final String sessionId) {
         final McpServerSession removedSession = sessions.remove(sessionId);
         final StreamableHttpSessionTransport removedTransport = 
sessionTransports.remove(sessionId);
+        ShenyuMcpExchangeHolder.remove(sessionId);
         if (Objects.nonNull(removedSession) || 
Objects.nonNull(removedTransport)) {
             LOGGER.debug("Removed session and transport: {}", sessionId);
         }
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/McpServerPluginIntegrationTest.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/McpServerPluginIntegrationTest.java
index 1839f88d3d..11a157c2ae 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/McpServerPluginIntegrationTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/McpServerPluginIntegrationTest.java
@@ -93,7 +93,7 @@ class McpServerPluginIntegrationTest {
         dataHandler.handlerSelector(selectorData);
         
         // Verify that the server can now route to this path
-        assertTrue(mcpServerManager.hasMcpServer("/mcp"));
+        assertTrue(mcpServerManager.hasMcpServer("/mcp/test"));
         assertTrue(mcpServerManager.canRoute("/mcp/test/sse"));
         assertTrue(mcpServerManager.canRoute("/mcp/test/message"));
         assertTrue(mcpServerManager.canRoute("/mcp/test/anything"));
@@ -160,7 +160,7 @@ class McpServerPluginIntegrationTest {
         
         // Verify all tools are handled (this tests the fix for the multiple 
tools issue)
         assertTrue(mcpServerManager.canRoute("/mcp/api/sse"));
-        assertTrue(mcpServerManager.hasMcpServer("/mcp"));
+        assertTrue(mcpServerManager.hasMcpServer("/mcp/api"));
         
         // Test that the plugin can handle requests (setup verification only)
         // Mock setup removed since we're not executing the plugin
@@ -188,7 +188,7 @@ class McpServerPluginIntegrationTest {
         
mcpServerManager.getOrCreateStreamableHttpTransport("/mcp/stream/streamablehttp");
         
         assertTrue(mcpServerManager.canRoute("/mcp/stream/streamablehttp"));
-        Set<String> protocols = mcpServerManager.getSupportedProtocols("/mcp");
+        Set<String> protocols = 
mcpServerManager.getSupportedProtocols("/mcp/stream");
         assertTrue(protocols.contains("Streamable HTTP"));
     }
     
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandlerTest.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandlerTest.java
index 569a96e41a..ad7f35e4ea 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandlerTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/handler/McpServerPluginDataHandlerTest.java
@@ -86,6 +86,22 @@ class McpServerPluginDataHandlerTest {
         verify(shenyuMcpServerManager, 
never()).getOrCreateMcpServerTransport(anyString(), anyString());
     }
 
+    @Test
+    void testHandlerSelectorWithoutUriCondition() {
+        ConditionData condition = new ConditionData();
+        condition.setParamType(ParamTypeEnum.HEADER.getName());
+        condition.setParamValue("x-session-id");
+
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId("selector-no-uri");
+        selectorData.setConditionList(Arrays.asList(condition));
+
+        dataHandler.handlerSelector(selectorData);
+
+        verify(shenyuMcpServerManager, 
never()).getOrCreateMcpServerTransport(anyString(), anyString());
+        verify(shenyuMcpServerManager, 
never()).getOrCreateStreamableHttpTransport(anyString());
+    }
+
     @Test
     void testHandlerSelectorWithValidData() {
         ConditionData condition = new ConditionData();
@@ -102,7 +118,7 @@ class McpServerPluginDataHandlerTest {
         
         dataHandler.handlerSelector(selectorData);
         
-        
verify(shenyuMcpServerManager).getOrCreateMcpServerTransport(eq("/mcp/test/**"),
 eq("/message"));
+        
verify(shenyuMcpServerManager).getOrCreateMcpServerTransport(eq("/mcp/test"), 
eq("/message"));
     }
 
     @Test
@@ -138,7 +154,7 @@ class McpServerPluginDataHandlerTest {
         
         dataHandler.removeSelector(selectorData);
         
-        verify(shenyuMcpServerManager).removeMcpServer(eq("/mcp/test/**"));
+        verify(shenyuMcpServerManager).removeMcpServer(eq("/mcp/test"));
     }
 
     @Test
@@ -170,6 +186,24 @@ class McpServerPluginDataHandlerTest {
         verify(shenyuMcpServerManager, never()).addTool(anyString(), 
anyString(), anyString(), anyString(), anyString());
     }
 
+    @Test
+    void testHandlerRuleWithBlankServerPath() {
+        RuleData ruleData = new RuleData();
+        ruleData.setId("rule-blank-path");
+        ruleData.setSelectorId("selector-blank-path");
+        ruleData.setName("testTool");
+        ruleData.setHandle("{\"name\":\"testTool\",\"description\":\"A test 
tool\",\"requestConfig\":\"{\\\"url\\\":\\\"/test\\\",\\\"method\\\":\\\"GET\\\"}\",\"parameters\":[]}");
+
+        org.apache.shenyu.plugin.mcp.server.model.ShenyuMcpServer server =
+                new 
org.apache.shenyu.plugin.mcp.server.model.ShenyuMcpServer();
+        server.setPath("");
+        
McpServerPluginDataHandler.CACHED_SERVER.get().cachedHandle("selector-blank-path",
 server);
+
+        dataHandler.handlerRule(ruleData);
+
+        verify(shenyuMcpServerManager, never()).addTool(anyString(), 
anyString(), anyString(), anyString(), anyString());
+    }
+
     @Test
     void testRemoveRule() {
         RuleData ruleData = new RuleData();
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManagerTest.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManagerTest.java
index 7668501355..62554bc18a 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManagerTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/manager/ShenyuMcpServerManagerTest.java
@@ -154,8 +154,7 @@ class ShenyuMcpServerManagerTest {
         
         shenyuMcpServerManager.getOrCreateStreamableHttpTransport(uri);
         
-        // Use base path since that's what the manager uses internally
-        Set<String> protocols = 
shenyuMcpServerManager.getSupportedProtocols("/mcp");
+        Set<String> protocols = 
shenyuMcpServerManager.getSupportedProtocols("/mcp/test");
         assertNotNull(protocols);
         assertTrue(protocols.contains("Streamable HTTP"));
     }
@@ -170,4 +169,14 @@ class ShenyuMcpServerManagerTest {
         assertTrue(shenyuMcpServerManager.hasMcpServer(uri));
         assertTrue(shenyuMcpServerManager.hasMcpServer("/mcp/test"));
     }
+
+    @Test
+    void testRegisterRouteWithFullUriAndQuery() {
+        String uri = 
"http://localhost:9195/mcp/test/streamablehttp?debug=true#anchor";;
+
+        shenyuMcpServerManager.getOrCreateStreamableHttpTransport(uri);
+
+        
assertTrue(shenyuMcpServerManager.canRoute("/mcp/test/streamablehttp"));
+        assertTrue(shenyuMcpServerManager.hasMcpServer("/mcp/test"));
+    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelperTest.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelperTest.java
index 520efbdbb2..aa12ad11f3 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelperTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/request/RequestConfigHelperTest.java
@@ -62,6 +62,17 @@ class RequestConfigHelperTest {
         assertEquals("body", argsPosition.get("email").getAsString());
     }
 
+    @Test
+    void testArgsPositionCompatibilityWithNestedFormat() {
+        String configStr = "{\"requestTemplate\":{\"url\":\"/api/users\","
+                + 
"\"method\":\"POST\",\"argsPosition\":{\"name\":\"body\",\"email\":\"body\"}}}";
+        RequestConfigHelper helper = new RequestConfigHelper(configStr);
+
+        JsonObject argsPosition = helper.getArgsPosition();
+        assertEquals("body", argsPosition.get("name").getAsString());
+        assertEquals("body", argsPosition.get("email").getAsString());
+    }
+
     @Test
     void testPathParameterBuilding() {
         JsonObject argsPosition = new JsonObject();


Reply via email to