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

hefengen 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 2c44e7ef7e feat(mcp-server): enhance SDK compatibility with reflection 
caching and error handling (#6312)
2c44e7ef7e is described below

commit 2c44e7ef7e7bf1b5cb0a3adee68106547c810ee2
Author: aias00 <[email protected]>
AuthorDate: Thu Apr 2 10:03:33 2026 +0800

    feat(mcp-server): enhance SDK compatibility with reflection caching and 
error handling (#6312)
    
    * feat(mcp-server): enhance SDK compatibility with reflection caching and 
error handling
    
    - Add reflection field caching in McpSessionHelper for better performance 
and reliability
    - Add SUPPORTED_SDK_VERSION constant for SDK compatibility tracking
    - Enhance error messages with SDK version information in ShenyuToolCallback
    - Add SDK compatibility notes in pom.xml documenting reflection usage
    - Update MCP_TOOL_EXAMPLES.md and MCP_TOOL_EXAMPLES_EN.md with SDK version 
compatibility table
    
    This change improves compatibility with MCP SDK 0.17.0 by:
    - Caching reflection fields at class load time
    - Adding graceful degradation when reflection fails
    - Documenting known limitations and supported SDK versions
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * fix mcp sdk compatibility error handling
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES.md  |  21 +++
 .../MCP_TOOL_EXAMPLES_EN.md                        |  22 ++-
 shenyu-plugin/shenyu-plugin-mcp-server/pom.xml     |  42 ++---
 .../mcp/server/callback/ShenyuToolCallback.java    |  40 +++-
 .../mcp/server/session/McpSessionHelper.java       | 202 +++++++++++++++++----
 .../server/callback/ShenyuToolCallbackTest.java    |  55 +++++-
 6 files changed, 312 insertions(+), 70 deletions(-)

diff --git a/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES.md 
b/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES.md
index d1457ffd5a..38d1eadafc 100644
--- a/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES.md
+++ b/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES.md
@@ -2,6 +2,27 @@
 
 本文档基于 **shenyu-examples-http** 项目中的真实接口,提供了 Shenyu MCP Server Plugin 
的各种工具配置示例,涵盖不同的 HTTP 请求方法、参数类型和配置方式。
 
+## SDK 版本兼容性
+
+本插件基于以下 SDK 版本开发和测试:
+
+| 依赖 | 版本 |
+|------|------|
+| MCP SDK (io.modelcontextprotocol.sdk:mcp-bom) | 0.17.0 |
+| Spring AI (org.springframework.ai:spring-ai-bom) | 1.1.2 |
+| Spring Boot | 3.3.1 |
+
+### 支持的协议
+
+- **SSE (Server-Sent Events)**: `/sse` 端点,支持长连接会话
+- **Streamable HTTP**: 统一端点,支持 GET (SSE 流) 和 POST (消息) 请求
+
+### 已知限制
+
+1. **Session 管理**: 使用反射机制访问 SDK 内部字段获取 Session ID,SDK 版本升级可能影响兼容性
+2. **工具调用超时**: 默认 60 秒,可通过 `requestTemplate.timeout` 配置
+3. **CORS 支持**: 通过 `shenyu.cross.allowedHeaders` 配置允许的请求头
+
 ## 1. 简单 GET 请求示例
 
 ### 1.1 无参数 GET 请求
diff --git a/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES_EN.md 
b/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES_EN.md
index 5cbcfc74f4..933e8c833f 100644
--- a/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES_EN.md
+++ b/shenyu-plugin/shenyu-plugin-mcp-server/MCP_TOOL_EXAMPLES_EN.md
@@ -2,6 +2,27 @@
 
 This document provides comprehensive examples of tool configurations for the 
Shenyu MCP Server Plugin based on **real interfaces from the 
shenyu-examples-http project**, covering different HTTP request methods, 
parameter types, and configuration patterns.
 
+## SDK Version Compatibility
+
+This plugin is developed and tested with the following SDK versions:
+
+| Dependency | Version |
+|------------|---------|
+| MCP SDK (io.modelcontextprotocol.sdk:mcp-bom) | 0.17.0 |
+| Spring AI (org.springframework.ai:spring-ai-bom) | 1.1.2 |
+| Spring Boot | 3.3.1 |
+
+### Supported Protocols
+
+- **SSE (Server-Sent Events)**: `/sse` endpoint with a long-lived streaming 
connection and session ID correlation
+- **Streamable HTTP**: Unified endpoint handling POST message requests for MCP 
operations
+
+### Known Limitations
+
+1. **Session Management**: Uses reflection to access SDK internal fields for 
Session ID retrieval; SDK version upgrades may affect compatibility
+2. **Tool Call Timeout**: Default 60 seconds, configurable via 
`requestTemplate.timeout`
+3. **CORS Support**: Configure allowed headers via 
`shenyu.cross.allowedHeaders`
+
 ## 1. Simple GET Request Examples
 
 ### 1.1 GET Request with No Parameters
@@ -496,4 +517,3 @@ Implement retry mechanisms and proper error responses at 
the business layer.
 - Monitor and log API performance
 
 These examples are based on **real interfaces from the shenyu-examples-http 
project** and can be used and tested directly in a Shenyu environment.
-
diff --git a/shenyu-plugin/shenyu-plugin-mcp-server/pom.xml 
b/shenyu-plugin/shenyu-plugin-mcp-server/pom.xml
index a29abdc65b..4fbf1d518e 100644
--- a/shenyu-plugin/shenyu-plugin-mcp-server/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-mcp-server/pom.xml
@@ -41,10 +41,6 @@
             <artifactId>shenyu-web</artifactId>
             <version>${project.version}</version>
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>org.springframework.ai</groupId>-->
-<!--            
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>-->
-<!--        </dependency>-->
         <dependency>
             <groupId>org.apache.shenyu</groupId>
             <artifactId>shenyu-plugin-base</artifactId>
@@ -55,43 +51,47 @@
             <artifactId>shenyu-loadbalancer</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <!--
+            Spring AI and MCP SDK Dependencies
+
+            SDK Compatibility Notes:
+            - Current tested version: MCP SDK 0.17.0, Spring AI 1.1.2
+            - McpSessionHelper uses reflection to access 
McpSyncServerExchange.exchange and
+              McpAsyncServerExchange.session fields for session ID extraction
+            - ShenyuStreamableHttpServerTransportProvider uses reflection to 
access
+              McpServerSession.initialized and McpServerSession.state fields 
for session verification
+            - When upgrading SDK versions, verify that these internal fields 
still exist and
+              update McpSessionHelper.SUPPORTED_SDK_VERSION and any 
corresponding
+              SDK compatibility documentation in 
ShenyuStreamableHttpServerTransportProvider
+        -->
         <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-model</artifactId>
         </dependency>
-        
+
         <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-mcp</artifactId>
         </dependency>
-        
+
+        <!-- MCP SDK - Explicit version from parent BOM -->
         <dependency>
             <groupId>io.modelcontextprotocol.sdk</groupId>
             <artifactId>mcp-spring-webflux</artifactId>
+            <!-- Version managed by mcp-bom in parent pom.xml (${mcp.version}) 
-->
         </dependency>
         <dependency>
             <groupId>io.modelcontextprotocol.sdk</groupId>
             <artifactId>mcp-json-jackson2</artifactId>
+            <!-- Version managed by mcp-bom in parent pom.xml (${mcp.version}) 
-->
         </dependency>
-<!--        <dependency>-->
-<!--            <groupId>org.springframework.ai</groupId>-->
-<!--            <artifactId>spring-ai-starter-mcp-server</artifactId>-->
-<!--        </dependency>-->
-        
-        
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-webflux</artifactId>
         </dependency>
-        
-<!--        <dependency>-->
-<!--            <groupId>org.springframework.boot</groupId>-->
-<!--            <artifactId>spring-boot-starter-test</artifactId>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>org.springframework</groupId>-->
-<!--            <artifactId>spring-test</artifactId>-->
-<!--        </dependency>-->
+
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-web</artifactId>
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 9cf25383e8..dad414af28 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
@@ -95,6 +95,11 @@ public class ShenyuToolCallback implements ToolCallback {
      */
     private static final String STREAMABLE_HTTP_PATH = "/streamablehttp";
 
+    /**
+     * Prefix used by McpSessionHelper for SDK compatibility failures.
+     */
+    private static final String SDK_COMPATIBILITY_ERROR_PREFIX = "SDK 
COMPATIBILITY ERROR";
+
     /**
      * Regex pattern for template variable interpolation in tool inputs.
      **/
@@ -761,20 +766,35 @@ public class ShenyuToolCallback implements ToolCallback {
      *
      * @param mcpExchange the MCP sync server exchange
      * @return the session ID
-     * @throws IllegalStateException if session ID cannot be extracted
+     * @throws IllegalStateException if the session ID is blank or an SDK 
compatibility issue blocks extraction
+     * @throws IllegalArgumentException if the exchange is missing required 
session state
      */
     private String extractSessionId(final McpSyncServerExchange mcpExchange) {
-        final String sessionId;
         try {
-            sessionId = McpSessionHelper.getSessionId(mcpExchange);
-        } catch (NoSuchFieldException | IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-        if (StringUtils.hasText(sessionId)) {
-            LOG.debug("Extracted session ID: {}", sessionId);
-            return sessionId;
+            final String sessionId = 
McpSessionHelper.getSessionId(mcpExchange);
+            if (StringUtils.hasText(sessionId)) {
+                LOG.debug("Extracted session ID: {}", sessionId);
+                return sessionId;
+            }
+            throw new IllegalStateException("Session ID is empty – it should 
have been set earlier by handleMessageEndpoint");
+        } catch (RuntimeException e) {
+            if (!isSdkCompatibilityError(e)) {
+                throw e;
+            }
+
+            // Re-throw SDK compatibility errors with additional context.
+            throw new IllegalStateException(
+                    "Failed to extract session ID from MCP exchange. "
+                    + "This may indicate an SDK compatibility issue. "
+                    + "Tested SDK version: " + 
McpSessionHelper.getSupportedSdkVersion() + ". "
+                    + "Original error: " + e.getMessage(), e);
         }
-        throw new IllegalStateException("Session ID is empty – it should have 
been set earlier by handleMessageEndpoint");
+    }
+
+    private boolean isSdkCompatibilityError(final RuntimeException exception) {
+        return exception instanceof IllegalStateException
+                && StringUtils.hasText(exception.getMessage())
+                && 
exception.getMessage().startsWith(SDK_COMPATIBILITY_ERROR_PREFIX);
     }
 
     /**
diff --git 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java
 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java
index 923cdea479..fe3c114f4c 100644
--- 
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java
+++ 
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java
@@ -20,6 +20,8 @@ package org.apache.shenyu.plugin.mcp.server.session;
 import io.modelcontextprotocol.server.McpAsyncServerExchange;
 import io.modelcontextprotocol.server.McpSyncServerExchange;
 import io.modelcontextprotocol.spec.McpServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.ai.chat.model.ToolContext;
 
 import java.lang.reflect.Field;
@@ -28,9 +30,97 @@ import java.util.Objects;
 
 /**
  * Helper class for handling McpSession related operations.
+ *
+ * <p>SDK Compatibility Note: This class uses reflection to access internal 
SDK fields
+ * that may change between versions. The following SDK versions are tested and 
supported:
+ * <ul>
+ *   <li>MCP SDK 0.17.0 (current)</li>
+ *   <li>Spring AI 1.1.2 (current)</li>
+ * </ul>
+ *
+ * <p>If reflection fails (e.g., due to SDK API changes), this class will throw
+ * IllegalStateException with clear error messages indicating SDK 
compatibility issues.
+ *
+ * @since 2.7.0.2
  */
 public class McpSessionHelper {
-    
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(McpSessionHelper.class);
+
+    /**
+     * SDK version for compatibility checking.
+     * This should be updated when testing with new SDK versions.
+     */
+    private static final String SUPPORTED_SDK_VERSION = "0.17.0";
+
+    /**
+     * Cached field reference for McpSyncServerExchange.exchange field.
+     * Null if not yet resolved or resolution failed.
+     */
+    private static volatile Field asyncExchangeFieldCache;
+
+    /**
+     * Cached field reference for McpAsyncServerExchange.session field.
+     * Null if not yet resolved or resolution failed.
+     */
+    private static volatile Field sessionFieldCache;
+
+    /**
+     * Flag indicating whether reflection fields have been resolved.
+     */
+    private static volatile boolean fieldsResolved;
+
+    /**
+     * Lock for resolving reflection fields.
+     */
+    private static final Object FIELD_RESOLVE_LOCK = new Object();
+
+    static {
+        resolveReflectionFields();
+    }
+
+    /**
+     * Resolves reflection fields for accessing internal SDK state.
+     * This method is called during class initialization and logs any 
compatibility issues.
+     */
+    private static void resolveReflectionFields() {
+        try {
+            // Resolve asyncExchange field from McpSyncServerExchange
+            asyncExchangeFieldCache = 
McpSyncServerExchange.class.getDeclaredField("exchange");
+            asyncExchangeFieldCache.setAccessible(true);
+            LOG.info("Successfully resolved McpSyncServerExchange.exchange 
field for SDK compatibility");
+
+            // Resolve session field from McpAsyncServerExchange
+            sessionFieldCache = 
McpAsyncServerExchange.class.getDeclaredField("session");
+            sessionFieldCache.setAccessible(true);
+            LOG.info("Successfully resolved McpAsyncServerExchange.session 
field for SDK compatibility");
+
+            fieldsResolved = true;
+            LOG.info("MCP SDK reflection fields resolved successfully. Tested 
with SDK version: {}", SUPPORTED_SDK_VERSION);
+        } catch (NoSuchFieldException e) {
+            clearReflectionFieldCache();
+            LOG.error("SDK COMPATIBILITY ERROR: Failed to resolve reflection 
fields. "
+                    + "This indicates the MCP SDK API has changed. "
+                    + "Tested version: {}, Current SDK may be incompatible. "
+                    + "Missing field: {}", SUPPORTED_SDK_VERSION, 
e.getMessage());
+        } catch (SecurityException e) {
+            clearReflectionFieldCache();
+            LOG.error("SDK COMPATIBILITY ERROR: Security manager blocked 
reflection access. "
+                    + "Field resolution failed: {}", e.getMessage());
+        } catch (RuntimeException e) {
+            clearReflectionFieldCache();
+            LOG.error("SDK COMPATIBILITY ERROR: Unexpected runtime exception 
during reflection field resolution. "
+                    + "This may indicate module access restrictions or an 
incompatible SDK version "
+                    + "(tested: {}). Error: {}", SUPPORTED_SDK_VERSION, 
e.getMessage(), e);
+        }
+    }
+
+    private static void clearReflectionFieldCache() {
+        asyncExchangeFieldCache = null;
+        sessionFieldCache = null;
+        fieldsResolved = false;
+    }
+
     /**
      * Get McpSyncServerExchange from ToolContext.
      *
@@ -51,54 +141,94 @@ public class McpSessionHelper {
         }
         return mcpSyncServerExchange;
     }
-    
+
     /**
      * Get sessionId from McpSyncServerExchange.
      *
+     * <p>Uses reflection to access internal SDK fields. If reflection fails,
+     * an IllegalStateException is thrown with SDK compatibility information.
+     *
      * @param mcpSyncServerExchange the McpSyncServerExchange instance
      * @return the session id string
-     * @throws NoSuchFieldException if field not found
-     * @throws IllegalAccessException if field not accessible
+     * @throws IllegalStateException if SDK reflection fails (API 
incompatibility)
      */
-    public static String getSessionId(final McpSyncServerExchange 
mcpSyncServerExchange)
-            throws NoSuchFieldException, IllegalAccessException {
-        Field asyncExchangeField = 
mcpSyncServerExchange.getClass().getDeclaredField("exchange");
-        asyncExchangeField.setAccessible(true);
-        Object session = getSession(mcpSyncServerExchange, asyncExchangeField);
-        if (Objects.isNull(session)) {
-            throw new IllegalArgumentException("Session is required in 
McpAsyncServerExchange");
-        }
-        McpServerSession mcpServerSession = (McpServerSession) session;
-        return mcpServerSession.getId();
+    public static String getSessionId(final McpSyncServerExchange 
mcpSyncServerExchange) {
+        return getSession(mcpSyncServerExchange).getId();
     }
-    
+
     /**
-     * Get sessionId from McpSyncServerExchange.
+     * Get McpServerSession from McpSyncServerExchange.
+     *
+     * <p>Uses reflection to access internal SDK fields. If reflection fails,
+     * an IllegalStateException is thrown with SDK compatibility information.
      *
      * @param mcpSyncServerExchange the McpSyncServerExchange instance
-     * @return the session id string
-     * @throws NoSuchFieldException if field not found
-     * @throws IllegalAccessException if field not accessible
+     * @return the McpServerSession instance
+     * @throws IllegalStateException if SDK reflection fails (API 
incompatibility)
      */
-    public static McpServerSession getSession(final McpSyncServerExchange 
mcpSyncServerExchange)
-            throws NoSuchFieldException, IllegalAccessException {
-        Field asyncExchangeField = 
mcpSyncServerExchange.getClass().getDeclaredField("exchange");
-        asyncExchangeField.setAccessible(true);
-        Object session = getSession(mcpSyncServerExchange, asyncExchangeField);
-        if (Objects.isNull(session)) {
-            throw new IllegalArgumentException("Session is required in 
McpAsyncServerExchange");
+    public static McpServerSession getSession(final McpSyncServerExchange 
mcpSyncServerExchange) {
+        checkReflectionAvailability();
+
+        try {
+            Object asyncExchange = 
asyncExchangeFieldCache.get(mcpSyncServerExchange);
+            if (Objects.isNull(asyncExchange)) {
+                throw new IllegalArgumentException("McpAsyncServerExchange is 
required in McpSyncServerExchange");
+            }
+            McpAsyncServerExchange mcpAsyncServerExchange = 
(McpAsyncServerExchange) asyncExchange;
+            Object session = sessionFieldCache.get(mcpAsyncServerExchange);
+            if (Objects.isNull(session)) {
+                throw new IllegalArgumentException("Session is required in 
McpAsyncServerExchange");
+            }
+            return (McpServerSession) session;
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException(
+                    "SDK COMPATIBILITY ERROR: Failed to access SDK internal 
fields via reflection. "
+                    + "This indicates the MCP SDK API has changed. "
+                    + "Tested SDK version: " + SUPPORTED_SDK_VERSION + ". "
+                    + "Error: " + e.getMessage(), e);
         }
-        return (McpServerSession) session;
     }
-    
-    private static Object getSession(final McpSyncServerExchange 
mcpSyncServerExchange, final Field asyncExchangeField) throws 
IllegalAccessException, NoSuchFieldException {
-        Object asyncExchange = asyncExchangeField.get(mcpSyncServerExchange);
-        if (Objects.isNull(asyncExchange)) {
-            throw new IllegalArgumentException("McpAsyncServerExchange is 
required in McpSyncServerExchange");
+
+    /**
+     * Checks if reflection fields are available and throws an informative 
exception if not.
+     *
+     * @throws IllegalStateException if reflection fields are not available
+     */
+    private static void checkReflectionAvailability() {
+        if (!fieldsResolved || Objects.isNull(asyncExchangeFieldCache) || 
Objects.isNull(sessionFieldCache)) {
+            // Attempt to re-resolve fields in case of delayed class loading
+            synchronized (FIELD_RESOLVE_LOCK) {
+                if (!fieldsResolved) {
+                    resolveReflectionFields();
+                }
+            }
+
+            if (!fieldsResolved || Objects.isNull(asyncExchangeFieldCache) || 
Objects.isNull(sessionFieldCache)) {
+                throw new IllegalStateException(
+                        "SDK COMPATIBILITY ERROR: MCP SDK reflection fields 
are not available. "
+                        + "The MCP SDK version may be incompatible with this 
implementation. "
+                        + "Tested SDK version: " + SUPPORTED_SDK_VERSION + ". "
+                        + "Please verify SDK version compatibility or check 
logs for field resolution errors.");
+            }
         }
-        McpAsyncServerExchange mcpAsyncServerExchange = 
(McpAsyncServerExchange) asyncExchange;
-        Field sessionField = 
mcpAsyncServerExchange.getClass().getDeclaredField("session");
-        sessionField.setAccessible(true);
-        return sessionField.get(mcpAsyncServerExchange);
+    }
+
+    /**
+     * Checks if the SDK reflection fields are available for use.
+     * This can be used for proactive compatibility checking.
+     *
+     * @return true if reflection fields are resolved and available
+     */
+    public static boolean isReflectionAvailable() {
+        return fieldsResolved && Objects.nonNull(asyncExchangeFieldCache) && 
Objects.nonNull(sessionFieldCache);
+    }
+
+    /**
+     * Returns the SDK version that this implementation has been tested with.
+     *
+     * @return the supported SDK version string
+     */
+    public static String getSupportedSdkVersion() {
+        return SUPPORTED_SDK_VERSION;
     }
 }
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 39035071b9..69acb2848f 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
@@ -39,8 +39,11 @@ import org.springframework.web.server.ServerWebExchange;
 import java.util.HashMap;
 
 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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
@@ -148,10 +151,15 @@ class ShenyuToolCallbackTest {
                 .thenReturn(mcpSyncServerExchange);
         mcpSessionHelperMock.when(() -> McpSessionHelper.getSessionId(any()))
                 .thenReturn("");
-        
-        assertThrows(RuntimeException.class, () -> {
+
+        final RuntimeException exception = 
assertThrows(RuntimeException.class, () -> {
             shenyuToolCallback.call("{}", toolContext);
         });
+
+        assertEquals("Tool execution failed: Session ID is empty – it should 
have been set earlier by handleMessageEndpoint",
+                exception.getMessage());
+        assertInstanceOf(IllegalStateException.class, exception.getCause());
+        assertFalse(exception.getMessage().contains("SDK compatibility"));
     }
 
     @Test
@@ -175,6 +183,49 @@ class ShenyuToolCallbackTest {
         });
     }
 
+    @Test
+    void testCallWithSdkCompatibilitySessionErrorAddsVersionContext() {
+        when(toolDefinition.name()).thenReturn("testTool");
+        shenyuToolCallback = new ShenyuToolCallback(toolDefinition);
+
+        final ToolContext toolContext = new ToolContext(new HashMap<>());
+        mcpSessionHelperMock.when(() -> 
McpSessionHelper.getMcpSyncServerExchange(any()))
+                .thenReturn(mcpSyncServerExchange);
+        mcpSessionHelperMock.when(() -> McpSessionHelper.getSessionId(any()))
+                .thenThrow(new IllegalStateException("SDK COMPATIBILITY ERROR: 
reflection access failed"));
+        mcpSessionHelperMock.when(McpSessionHelper::getSupportedSdkVersion)
+                .thenReturn("0.17.0");
+
+        final RuntimeException exception = 
assertThrows(RuntimeException.class, () -> {
+            shenyuToolCallback.call("{}", toolContext);
+        });
+
+        assertTrue(exception.getMessage().contains("Tool execution failed: 
Failed to extract session ID from MCP exchange."));
+        assertInstanceOf(IllegalStateException.class, exception.getCause());
+        assertTrue(exception.getCause().getMessage().contains("Tested SDK 
version: 0.17.0."));
+        assertTrue(exception.getCause().getMessage().contains("Original error: 
SDK COMPATIBILITY ERROR: reflection access failed"));
+    }
+
+    @Test
+    void testCallWithMissingSessionStateDoesNotWrapAsCompatibilityError() {
+        when(toolDefinition.name()).thenReturn("testTool");
+        shenyuToolCallback = new ShenyuToolCallback(toolDefinition);
+
+        final ToolContext toolContext = new ToolContext(new HashMap<>());
+        mcpSessionHelperMock.when(() -> 
McpSessionHelper.getMcpSyncServerExchange(any()))
+                .thenReturn(mcpSyncServerExchange);
+        mcpSessionHelperMock.when(() -> McpSessionHelper.getSessionId(any()))
+                .thenThrow(new IllegalArgumentException("Session is required 
in McpAsyncServerExchange"));
+
+        final RuntimeException exception = 
assertThrows(RuntimeException.class, () -> {
+            shenyuToolCallback.call("{}", toolContext);
+        });
+
+        assertEquals("Tool execution failed: Session is required in 
McpAsyncServerExchange", exception.getMessage());
+        assertInstanceOf(IllegalArgumentException.class, exception.getCause());
+        assertFalse(exception.getMessage().contains("SDK compatibility"));
+    }
+
     @Test
     void testCallWithValidSetup() throws Exception {
         when(toolDefinition.name()).thenReturn("testTool");

Reply via email to