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 d3f5ee9bbb fix(mcp): correctly merge fixed and dynamic query
parameters (#7448)
d3f5ee9bbb is described below
commit d3f5ee9bbb1d1f4a7518260fccd36d422f605a6d
Author: lymerin <[email protected]>
AuthorDate: Thu Oct 8 14:28:15 2026 +0800
fix(mcp): correctly merge fixed and dynamic query parameters (#7448)
---
.../mcp/server/request/RequestConfigHelper.java | 13 ++++-----
.../server/callback/ShenyuToolCallbackTest.java | 21 +++++++++++++++
.../server/request/RequestConfigHelperTest.java | 31 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 8 deletions(-)
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 07eb4e020e..2db1573dcd 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
@@ -140,7 +140,7 @@ public class RequestConfigHelper {
String existingQuery = hasExistingQuery ?
urlTemplate.substring(urlTemplate.indexOf("?") + 1) : "";
// Handle new query parameters
- basePath = processArguments(argsPosition, inputJson, basePath,
queryBuilder);
+ basePath = processArguments(argsPosition, inputJson, basePath,
queryBuilder, existingQuery);
// Clear the template variables that have not been replaced
basePath = basePath.replaceAll("\\{\\{\\.[^}]+}}", "");
@@ -202,10 +202,11 @@ public class RequestConfigHelper {
* @param inputJson the input JSON object
* @param basePath the base path to modify
* @param queryBuilder the query builder to append to
+ * @param existingQuery the existing query string
* @return the modified base path
*/
private static String processArguments(final JsonObject argsPosition,
final JsonObject inputJson,
- final String basePath, final
StringBuilder queryBuilder) {
+ final String basePath, final
StringBuilder queryBuilder, final String existingQuery) {
String modifiedBasePath = basePath;
for (String key : argsPosition.keySet()) {
String position = argsPosition.get(key).getAsString();
@@ -216,7 +217,7 @@ public class RequestConfigHelper {
modifiedBasePath = modifiedBasePath.replace("{{." + key +
"}}", UriUtils.encodePathSegment(value, StandardCharsets.UTF_8));
} else if ("query".equals(position) && inputJson.has(key)) {
// Handle query parameters
- if (!modifiedBasePath.contains(key + "=")) {
+ if (!existingQuery.startsWith(key + "=") &&
!existingQuery.contains("&" + key + "=")) {
if (!queryBuilder.isEmpty()) {
queryBuilder.append("&");
}
@@ -244,11 +245,7 @@ public class RequestConfigHelper {
// Add query parameters
if (!queryBuilder.isEmpty()) {
- if (hasExistingQuery) {
- finalPath.append("&").append(queryBuilder);
- } else {
- finalPath.append("?").append(queryBuilder);
- }
+ finalPath.append("?").append(queryBuilder);
}
// Add existing query parameters
diff --git
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallbackTest.java
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallbackTest.java
index d2104e272c..60fa36bc84 100644
---
a/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallbackTest.java
+++
b/shenyu-plugin/shenyu-plugin-mcp-server/src/test/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallbackTest.java
@@ -45,6 +45,7 @@ import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -342,6 +343,26 @@ class ShenyuToolCallbackTest {
assertEquals(URI.create("http://gateway.example:9195/api/order"),
builder.build().getURI());
}
+ @Test
+ void testDecoratedExchangePreservesFixedAndDynamicQueryParameters() {
+ shenyuToolCallback = new ShenyuToolCallback(toolDefinition);
+ MockServerWebExchange originExchange = MockServerWebExchange.from(
+
MockServerHttpRequest.get("http://gateway.example:9195/mcp").build());
+ ShenyuContext context = new ShenyuContext();
+ originExchange.getAttributes().put(Constants.CONTEXT, context);
+ String config =
"{\"requestTemplate\":{\"url\":\"/users?fixed=true\",\"method\":\"GET\"},"
+ + "\"argsPosition\":{\"page\":\"query\"}}";
+
+ ServerWebExchange decoratedExchange =
ReflectionTestUtils.invokeMethod(shenyuToolCallback, "buildDecoratedExchange",
+ originExchange, new CompletableFuture<String>(), "session123",
config, "{\"page\":\"1\"}");
+
+
assertEquals(URI.create("http://gateway.example:9195/users?page=1&fixed=true"),
decoratedExchange.getRequest().getURI());
+ assertEquals("1",
decoratedExchange.getRequest().getQueryParams().getFirst("page"));
+ assertEquals("true",
decoratedExchange.getRequest().getQueryParams().getFirst("fixed"));
+ assertEquals("/users", context.getPath());
+ assertEquals("/users", context.getRealUrl());
+ }
+
@Test
void testConstructorWithValidToolDefinition() {
ShenyuToolCallback callback = new ShenyuToolCallback(toolDefinition);
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 36fd938770..f1ae261150 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
@@ -19,6 +19,8 @@ package org.apache.shenyu.plugin.mcp.server.request;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -102,6 +104,35 @@ class RequestConfigHelperTest {
assertTrue(result.contains("&"));
}
+ @ParameterizedTest
+ @CsvSource({
+ "/users?fixed=true, /users?page=1&fixed=true",
+ "/users?page=10, /users?page=10",
+ "/users?homepage=10¬e=page=10,
/users?page=1&homepage=10¬e=page=10"
+ })
+ void testQueryParameterMerging(final String template, final String
expected) {
+ JsonObject argsPosition = new JsonObject();
+ argsPosition.addProperty("page", "query");
+ JsonObject inputJson = new JsonObject();
+ inputJson.addProperty("page", "1");
+
+ String result = RequestConfigHelper.buildPath(template, argsPosition,
inputJson);
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void testQueryMergingPreservesEncoding() {
+ JsonObject argsPosition = new JsonObject();
+ argsPosition.addProperty("query", "query");
+ JsonObject inputJson = new JsonObject();
+ inputJson.addProperty("query", "hello world & 50%");
+
+ String result =
RequestConfigHelper.buildPath("/users?fixed=a%26b%3Dc%20d", argsPosition,
inputJson);
+
+
assertEquals("/users?query=hello%20world%20%26%2050%25&fixed=a%26b%3Dc%20d",
result);
+ }
+
@Test
void testMixedPathAndQueryParameters() {
JsonObject argsPosition = new JsonObject();