This is an automated email from the ASF dual-hosted git repository.
hengyuss 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 a89b6a2338 fix: make shenyu-examples-mcp work normally (#7033)
a89b6a2338 is described below
commit a89b6a2338c24e978428b731e8f1edce86022418
Author: hengyuss <[email protected]>
AuthorDate: Wed Oct 7 00:54:30 2026 +0800
fix: make shenyu-examples-mcp work normally (#7033)
* fix: make shenyu-examples-mcp work normally
* fix: make client register event publisher startup idempotent
* fix: update related build/documentation
---------
Co-authored-by: aias00 <[email protected]>
---
shenyu-examples/README.md | 63 ++++++++++++++++++----
.../{ => examples}/ShenyuTestMcpApplication.java | 2 +-
.../{ => examples}/controller/OrderController.java | 4 +-
.../apache/shenyu/{ => examples}/dto/OrderDTO.java | 2 +-
.../src/main/resources/application.yml | 2 -
.../mcp/server/callback/ShenyuToolCallback.java | 5 +-
.../server/callback/ShenyuToolCallbackTest.java | 23 ++++++--
.../src/main/resources/META-INF/spring.provides | 30 ++---------
...rk.boot.autoconfigure.AutoConfiguration.imports | 30 ++---------
9 files changed, 85 insertions(+), 76 deletions(-)
diff --git a/shenyu-examples/README.md b/shenyu-examples/README.md
index 56b8312a92..cc26866c92 100644
--- a/shenyu-examples/README.md
+++ b/shenyu-examples/README.md
@@ -4,7 +4,7 @@ This module contains example applications that demonstrate how
to integrate your
## Client Dependencies
-The example modules now consume **independent
[shenyu-client-java](https://github.com/apache/shenyu-client-java) artifacts**
from Maven Central, rather than depending on in-tree source modules from the
main `apache/shenyu` repository.
+The example modules consume **independent
[shenyu-client-java](https://github.com/apache/shenyu-client-java) artifacts**
from Maven Central, rather than depending on in-tree source modules from the
main `apache/shenyu` repository.
This reflects the recommended approach for real users: add the ShenYu client
library as an external Maven dependency.
@@ -18,6 +18,8 @@ The parent POM (`shenyu-examples/pom.xml`) defines a property:
All `shenyu-client-*` and `shenyu-spring-boot-starter-client-*` dependencies
in individual example POMs are managed by this property through the
`<dependencyManagement>` section. The version is inherited automatically —
individual examples do not specify a `<version>` for these artifacts.
+The released version remains the default so a clean standalone checkout can
resolve the client artifacts from Maven Central. To test unreleased MCP fixes,
explicitly override this property as described below.
+
### Which Artifacts Are Consumed Externally
| Client Artifact | Used By |
@@ -42,7 +44,44 @@ The following dependencies remain internal to this
repository (`${project.versio
- `shenyu-sdk-*` — ShenYu SDK modules (not extracted to shenyu-client-java)
- `shenyu-spring-boot-starter-sdk-*` — ShenYu SDK starters (not extracted to
shenyu-client-java)
-## Testing with Unreleased Client Artifacts
+## Testing the MCP Example with 2.7.2-SNAPSHOT
+
+Use JDK 17 and run the following commands from the root of this
`apache/shenyu` checkout. Use a checkout containing the MCP fixes: installing
an unrelated snapshot from `shenyu-client-java` does not provide the changes in
this repository.
+
+### Build and Run the MCP Example
+
+The MCP example uses both the MCP and Spring MVC client starters. Build both
starters and their required reactor modules into the local Maven repository:
+
+```bash
+./mvnw install -pl
shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp,shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-springmvc
-am -DskipTests
+```
+
+Build the MCP example and its shared example utilities using the separate
examples reactor:
+
+```bash
+./mvnw -f shenyu-examples/pom.xml install -pl shenyu-examples-mcp -am \
+ -Dshenyu.client.java.version=2.7.2-SNAPSHOT -DskipTests
+```
+
+Before starting the example, configure
`shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml` for
your ShenYu Admin address, credentials, namespace, and reachable service
address. Run ShenYu Admin and Bootstrap from a checkout containing the
corresponding MCP gateway fixes.
+
+```bash
+./mvnw -f shenyu-examples/pom.xml spring-boot:run -pl shenyu-examples-mcp \
+ -Dshenyu.client.java.version=2.7.2-SNAPSHOT
+```
+
+Keep the same version override for build and run commands. This selects
`2.7.2-SNAPSHOT` for both starters through the parent dependency management.
Check the resolved ShenYu dependencies with:
+
+```bash
+./mvnw -f shenyu-examples/pom.xml dependency:tree -pl shenyu-examples-mcp \
+ -Dshenyu.client.java.version=2.7.2-SNAPSHOT '-Dincludes=org.apache.shenyu:*'
+```
+
+Confirm that the selected MCP and Spring MVC starters and their shared client
dependencies use `2.7.2-SNAPSHOT`. Then verify that the example registers its
upstream and MCP tools in ShenYu Admin and that a tool invocation through the
gateway succeeds.
+
+Other examples continue to use the released `2.7.0.3` default and do not
require this override.
+
+## Testing with Other Unreleased Client Artifacts
When developing or testing changes to the `shenyu-client-java` library, you
may need to build the client artifacts locally and use them in these examples.
@@ -59,8 +98,9 @@ mvn clean install -DskipTests
This installs the artifacts with the project's snapshot version (e.g.,
`2.7.0.1-jdk8-SNAPSHOT`). Then, build the examples using that version:
```bash
-cd shenyu
-mvn clean install -pl shenyu-examples
-Dshenyu.client.java.version=2.7.0.1-jdk8-SNAPSHOT
+# Run from the apache/shenyu repository root; select the example to test.
+mvn -f shenyu-examples/pom.xml clean install -pl shenyu-examples-springmvc -am
\
+ -Dshenyu.client.java.version=2.7.0.1-jdk8-SNAPSHOT
```
### Option 2: Modify the Property in Parent POM
@@ -71,7 +111,7 @@ For a more permanent change during development, edit the
`<shenyu.client.java.ve
<shenyu.client.java.version>2.7.0.1-jdk8-SNAPSHOT</shenyu.client.java.version>
```
-> **Note**: When using `-SNAPSHOT` versions, you may need to configure the
Apache Snapshot Repository in your `settings.xml` or POM:
+> **Note**: Locally installed snapshots do not require a remote snapshot
repository. If the required snapshot has been published, you can configure the
Apache Snapshot Repository in your POM or an active profile in `settings.xml`
(inside `<repositories>`):
> ```xml
> <repository>
> <id>apache-snapshots</id>
@@ -87,10 +127,11 @@ For a more permanent change during development, edit the
`<shenyu.client.java.ve
If the snapshot has been published to the Apache snapshot repository:
```bash
-mvn clean install -pl shenyu-examples
-Dshenyu.client.java.version=2.7.0.1-jdk8-SNAPSHOT -U
+mvn -f shenyu-examples/pom.xml clean install -pl shenyu-examples-springmvc -am
\
+ -Dshenyu.client.java.version=2.7.0.1-jdk8-SNAPSHOT -U
```
-The `-U` flag forces Maven to check for updated snapshots.
+The `-U` flag forces Maven to check for updated snapshots. It does not publish
missing artifacts or replace the local build required for unpublished fixes.
## Verification
@@ -98,16 +139,16 @@ After updating the client version, verify that the
examples can register with a
### Prerequisites
-Before running any example, install the shared example utilities to your local
Maven repository:
+After preparing the client dependencies as described above, install the shared
example utilities and their parent POM to your local Maven repository:
```bash
cd shenyu-examples
-mvn install -pl shenyu-examples-common -DskipTests
+mvn install -pl shenyu-examples-common -am -DskipTests
```
### Run Examples
-Run the example from the `shenyu-examples` reactor root using `-pl` so
internal dependencies like `shenyu-examples-common` are resolved automatically:
+Run the example from the `shenyu-examples` reactor root using `-pl`. The run
command uses the internal dependencies installed in the previous step:
```bash
cd shenyu-examples
@@ -122,7 +163,7 @@ mvn spring-boot:run -pl shenyu-examples-springmvc
# mvn spring-boot:run -pl shenyu-examples-mcp
```
-> **Note**: Running `mvn spring-boot:run` directly from within an individual
example directory (e.g., `cd shenyu-examples-springmvc && mvn spring-boot:run`)
will fail because `shenyu-examples-common` and the parent POM cannot be
resolved outside the reactor. Always use the `-pl` approach from the
`shenyu-examples/` root.
+> **Note**: `-pl` selects an example but does not build its dependencies.
Install the required internal modules before running it. If you used a client
version override when building, pass the same override to `spring-boot:run`.
### Confirm Registration
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/ShenyuTestMcpApplication.java
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/ShenyuTestMcpApplication.java
similarity index 97%
rename from
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/ShenyuTestMcpApplication.java
rename to
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/ShenyuTestMcpApplication.java
index 9950241f11..e5a383c4f7 100644
---
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/ShenyuTestMcpApplication.java
+++
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/ShenyuTestMcpApplication.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.shenyu;
+package org.apache.shenyu.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/controller/OrderController.java
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/controller/OrderController.java
similarity index 97%
rename from
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/controller/OrderController.java
rename to
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/controller/OrderController.java
index e09f3c7d51..1c3f4af24b 100644
---
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/controller/OrderController.java
+++
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/controller/OrderController.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.shenyu.controller;
+package org.apache.shenyu.examples.controller;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
@@ -30,7 +30,7 @@ import
org.apache.shenyu.client.mcp.common.annotation.ShenyuMcpRequestConfig;
import org.apache.shenyu.client.mcp.common.annotation.ShenyuMcpTool;
import org.apache.shenyu.client.mcp.common.annotation.ShenyuMcpToolParam;
import org.apache.shenyu.client.springmvc.annotation.ShenyuSpringMvcClient;
-import org.apache.shenyu.dto.OrderDTO;
+import org.apache.shenyu.examples.dto.OrderDTO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/dto/OrderDTO.java
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/dto/OrderDTO.java
similarity index 97%
rename from
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/dto/OrderDTO.java
rename to
shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/dto/OrderDTO.java
index c97c963ed9..79aa0da5e4 100644
---
a/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/dto/OrderDTO.java
+++
b/shenyu-examples/shenyu-examples-mcp/src/main/java/org/apache/shenyu/examples/dto/OrderDTO.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.shenyu.dto;
+package org.apache.shenyu.examples.dto;
import java.io.Serializable;
import java.util.StringJoiner;
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
b/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
index accdbbb2ef..38dbf7fc03 100644
--- a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
+++ b/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
@@ -18,8 +18,6 @@ server:
address: 0.0.0.0
tomcat:
max-http-form-post-size: 100MB
- servlet:
- context-path: /mcp
shenyu:
namespace: 649330b6-c2d7-4edc-be8e-8a54df9eb385
register:
diff --git
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
index aa1bd65707..061bc7a564 100644
---
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
+++
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
@@ -24,6 +24,7 @@ import com.google.gson.JsonObject;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
import org.apache.shenyu.common.utils.GsonUtils;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.api.context.ShenyuContext;
@@ -681,9 +682,11 @@ public class ShenyuToolCallback implements ToolCallback {
MetaData metaData =
MetaDataCache.getInstance().obtain(configHelper.getUrlTemplate());
if (Objects.nonNull(metaData) &&
Boolean.TRUE.equals(metaData.getEnabled())) {
decoratedExchange.getAttributes().put(Constants.META_DATA,
metaData);
- shenyuContext.setRpcType(metaData.getRpcType());
}
+ // MCP tools are invoked through an internal HTTP request. The
response plugin
+ // must therefore select an HTTP MessageWriter rather than an MCP
writer.
+ shenyuContext.setRpcType(RpcTypeEnum.HTTP.getName());
shenyuContext.setPath(extractRawPath(decoratedPath));
final Map<String, Object> attributes =
decoratedExchange.getAttributes();
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 856caa704c..d2104e272c 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
@@ -17,12 +17,10 @@
package org.apache.shenyu.plugin.mcp.server.callback;
-import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.common.McpTransportContext;
-import org.springframework.test.util.ReflectionTestUtils;
-import java.util.Map;
-import static org.junit.jupiter.api.Assertions.assertSame;
+import io.modelcontextprotocol.server.McpSyncServerExchange;
import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.api.context.ShenyuContext;
import org.apache.shenyu.plugin.mcp.server.definition.ShenyuToolDefinition;
@@ -40,20 +38,24 @@ import org.springframework.ai.chat.model.ToolContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -261,6 +263,19 @@ class ShenyuToolCallbackTest {
});
}
+ @Test
+ void testToolCallUsesHttpRpcType() {
+ shenyuToolCallback = new ShenyuToolCallback(toolDefinition);
+
when(exchange.getAttribute(Constants.CONTEXT)).thenReturn(shenyuContext);
+ when(exchange.getAttributes()).thenReturn(new HashMap<>());
+
+ ReflectionTestUtils.invokeMethod(shenyuToolCallback,
"configureShenyuContext", exchange,
+ "session123", "/mcp/order/findAll",
+
"{\"requestTemplate\":{\"url\":\"/mcp/order/findAll\",\"method\":\"GET\"},\"argsPosition\":{}}");
+
+ verify(shenyuContext).setRpcType(RpcTypeEnum.HTTP.getName());
+ }
+
@Test
void testExecuteToolCallDisposesPluginChainOnTimeout() {
shenyuToolCallback = new ShenyuToolCallback(toolDefinition);
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring.provides
similarity index 60%
copy from shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
copy to
shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring.provides
index accdbbb2ef..43e1f868c1 100644
--- a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring.provides
@@ -1,3 +1,4 @@
+#
# 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.
@@ -12,31 +13,6 @@
# 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.
+#
-server:
- port: 8150
- address: 0.0.0.0
- tomcat:
- max-http-form-post-size: 100MB
- servlet:
- context-path: /mcp
-shenyu:
- namespace: 649330b6-c2d7-4edc-be8e-8a54df9eb385
- register:
- enable: true
- registerType: http
- serverLists: http://localhost:9095
- props:
- username: admin
- password: 123456
- client:
- mcp:
- props:
- contextPath: /mcp
- appName: mcp
- http:
- props:
- contextPath: /mcp
- appName: mcp
- host: 127.0.0.1
- port: 8150
+provides: shenyu-spring-boot-starter-client-mcp
diff --git
a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 60%
copy from shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
copy to
shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index accdbbb2ef..1e553ca260 100644
--- a/shenyu-examples/shenyu-examples-mcp/src/main/resources/application.yml
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-client/shenyu-spring-boot-starter-client-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,3 +1,4 @@
+#
# 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.
@@ -12,31 +13,6 @@
# 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.
+#
-server:
- port: 8150
- address: 0.0.0.0
- tomcat:
- max-http-form-post-size: 100MB
- servlet:
- context-path: /mcp
-shenyu:
- namespace: 649330b6-c2d7-4edc-be8e-8a54df9eb385
- register:
- enable: true
- registerType: http
- serverLists: http://localhost:9095
- props:
- username: admin
- password: 123456
- client:
- mcp:
- props:
- contextPath: /mcp
- appName: mcp
- http:
- props:
- contextPath: /mcp
- appName: mcp
- host: 127.0.0.1
- port: 8150
+org.apache.shenyu.springboot.starter.client.mcp.ShenyuMcpClientConfiguration