Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public class ShenyuClientRegisterEventPublisher {

private static final ShenyuClientRegisterEventPublisher INSTANCE = new ShenyuClientRegisterEventPublisher();

private DisruptorProviderManage<DataTypeParent> providerManage;
private volatile DisruptorProviderManage<DataTypeParent> providerManage;

private boolean initialized;

/**
* Get instance.
Expand All @@ -50,14 +52,19 @@ public static ShenyuClientRegisterEventPublisher getInstance() {
*
* @param shenyuClientRegisterRepository shenyuClientRegisterRepository
*/
public void start(final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
public synchronized void start(final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
if (initialized) {
return;
}
RegisterClientExecutorFactory factory = new RegisterClientExecutorFactory();
factory.addSubscribers(new ShenyuClientMetadataExecutorSubscriber(shenyuClientRegisterRepository));
factory.addSubscribers(new ShenyuClientURIExecutorSubscriber(shenyuClientRegisterRepository));
factory.addSubscribers(new ShenyuClientApiDocExecutorSubscriber(shenyuClientRegisterRepository));
factory.addSubscribers(new ShenyuClientMcpExecutorSubscriber(shenyuClientRegisterRepository));
providerManage = new DisruptorProviderManage<>(factory);
providerManage.startup();
DisruptorProviderManage<DataTypeParent> newProviderManage = new DisruptorProviderManage<>(factory);
newProviderManage.startup();
providerManage = newProviderManage;
initialized = true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.shenyu.client.core.disruptor.subcriber;

import org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
import org.apache.shenyu.disruptor.DisruptorProviderManage;
import org.apache.shenyu.register.client.api.ShenyuClientRegisterRepository;
import org.apache.shenyu.register.common.type.DataTypeParent;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -45,7 +46,10 @@ public void testGetInstanceReturnsSameInstance() {
public void testStart() {
ShenyuClientRegisterEventPublisher publisher = ShenyuClientRegisterEventPublisher.getInstance();
publisher.start(shenyuClientRegisterRepository);
Assertions.assertNotNull(publisher.getProviderManage());
DisruptorProviderManage<DataTypeParent> providerManage = publisher.getProviderManage();
Assertions.assertNotNull(providerManage);
publisher.start(shenyuClientRegisterRepository);
assertSame(providerManage, publisher.getProviderManage());
assertDoesNotThrow(() -> publisher.getProviderManage().startup());
}

Expand Down
82 changes: 70 additions & 12 deletions shenyu-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 resolve ShenYu client artifacts through Maven dependencies. Released client artifacts are published by [shenyu-client-java](https://github.com/apache/shenyu-client-java). The current development branch defaults to `2.7.2-SNAPSHOT` to use the MCP registration fixes in this repository; build and install these artifacts locally before building the examples.

This reflects the recommended approach for real users: add the ShenYu client library as an external Maven dependency.

Expand All @@ -13,11 +13,13 @@ This reflects the recommended approach for real users: add the ShenYu client lib
The parent POM (`shenyu-examples/pom.xml`) defines a property:

```xml
<shenyu.client.java.version>2.7.0.3</shenyu.client.java.version>
<shenyu.client.java.version>2.7.2-SNAPSHOT</shenyu.client.java.version>
```

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.

This default applies to all examples. A clean standalone build cannot assume that these unreleased artifacts are available from Maven Central. Follow the local build steps below, or override the version to `2.7.0.3` for examples that do not require the MCP fixes.

### Which Artifacts Are Consumed Externally

| Client Artifact | Used By |
Expand All @@ -42,7 +44,61 @@ 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
## Building 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
mvn 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
mvn -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
mvn -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
mvn -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.

### Build Other Examples

To prepare client dependencies for other examples using the default snapshot version, install the client and client starter modules and their dependencies from the repository root:

```bash
mvn install -pl shenyu-client,shenyu-spring-boot-starter/shenyu-spring-boot-starter-client -am -DskipTests
```

Examples with internal SDK dependencies also require the corresponding SDK modules to be installed.

For an example that does not require the MCP fixes, you can use the released client artifacts without changing any POM. For example, from the repository root:

```bash
mvn -f shenyu-examples/pom.xml install -pl shenyu-examples-springmvc -am \
-Dshenyu.client.java.version=2.7.0.3 -DskipTests
mvn -f shenyu-examples/pom.xml spring-boot:run -pl shenyu-examples-springmvc \
-Dshenyu.client.java.version=2.7.0.3
```

## 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.

Expand All @@ -59,8 +115,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
Expand All @@ -71,7 +128,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>
Expand All @@ -87,27 +144,28 @@ 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

After updating the client version, verify that the examples can register with a local ShenYu Admin and Bootstrap.

### 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
Expand All @@ -122,7 +180,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

Expand Down
2 changes: 1 addition & 1 deletion shenyu-examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<docker-maven-plugin.version>0.40.1</docker-maven-plugin.version>
<maven-checkstyle-plugin.version>3.4.0</maven-checkstyle-plugin.version>
<swagger.version>2.6.0</swagger.version>
<shenyu.client.java.version>2.7.0.3</shenyu.client.java.version>
<shenyu.client.java.version>2.7.2-SNAPSHOT</shenyu.client.java.version>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocking — this must stay a released version.

shenyu-examples is a standalone reactor (mvn -f shenyu-examples/pom.xml) and these client artifacts come from apache/shenyu-client-java. 2.7.2-SNAPSHOT is not on Maven Central and is not produced by this repo's reactor, so making it the default means a clean checkout can no longer build any example.

Please revert to 2.7.0.3 here and pass 2.7.2-SNAPSHOT as an explicit -Dshenyu.client.java.version=... override in the MCP build docs. Apache examples have to build out of the box.

</properties>

<modules>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +30,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -660,9 +661,11 @@ private void configureShenyuContext(final ServerWebExchange decoratedExchange,
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

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;
Expand All @@ -34,6 +35,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ServerWebExchange;

import java.util.HashMap;
Expand All @@ -45,6 +47,7 @@
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;

/**
Expand Down Expand Up @@ -252,6 +255,19 @@ void testCallWithValidSetup() throws Exception {
});
}

@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 testConstructorWithNullToolDefinition() {
assertThrows(NullPointerException.class, () -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# 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.
#

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.shenyu.springboot.starter.client.mcp.ShenyuMcpClientConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# 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.
#

provides: shenyu-spring-boot-starter-client-mcp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# 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.
#

org.apache.shenyu.springboot.starter.client.mcp.ShenyuMcpClientConfiguration
Loading