{"record":{"id":"705f128fd0923595","repo":"apache/shenyu","slug":"mcpasyncserverexchange-is-required-in-mcpsyncserverexchange","errorCode":null,"errorMessage":"McpAsyncServerExchange is required in McpSyncServerExchange","messagePattern":"McpAsyncServerExchange is required in McpSyncServerExchange","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java","lineNumber":175,"sourceCode":"    }\n\n    /**\n     * Get McpServerSession from McpSyncServerExchange.\n     *\n     * <p>Uses reflection to access internal SDK fields. If reflection fails,\n     * an IllegalStateException is thrown with SDK compatibility information.\n     *\n     * @param mcpSyncServerExchange the McpSyncServerExchange instance\n     * @return the McpServerSession instance\n     * @throws IllegalStateException if SDK reflection fails (API incompatibility)\n     */\n    public static McpServerSession getSession(final McpSyncServerExchange mcpSyncServerExchange) {\n        checkReflectionAvailability();\n\n        try {\n            Object asyncExchange = asyncExchangeFieldCache.get(mcpSyncServerExchange);\n            if (Objects.isNull(asyncExchange)) {\n                throw new IllegalArgumentException(\"McpAsyncServerExchange is required in McpSyncServerExchange\");\n            }\n            McpAsyncServerExchange mcpAsyncServerExchange = (McpAsyncServerExchange) asyncExchange;\n            Object session = sessionFieldCache.get(mcpAsyncServerExchange);\n            if (Objects.isNull(session)) {\n                throw new IllegalArgumentException(\"Session is required in McpAsyncServerExchange\");\n            }\n            return (McpServerSession) session;\n        } catch (IllegalAccessException e) {\n            throw new IllegalStateException(\n                    \"SDK COMPATIBILITY ERROR: Failed to access SDK internal fields via reflection. \"\n                    + \"This indicates the MCP SDK API has changed. \"\n                    + \"Tested SDK version: \" + SUPPORTED_SDK_VERSION + \". \"\n                    + \"Error: \" + e.getMessage(), e);\n        }\n    }\n\n    /**\n     * Checks if reflection fields are available and throws an informative exception if not.","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java#L157-L193","documentation":"IllegalArgumentException from McpSessionHelper.getSession when the reflective read of McpSyncServerExchange's internal 'exchange' field yields null. The helper uses reflection (tested against MCP SDK 0.17.0) to reach the nested McpAsyncServerExchange and then the McpServerSession to obtain the session id; a null nested exchange means the McpSyncServerExchange was constructed without its async delegate.","triggerScenarios":"asyncExchangeFieldCache.get(mcpSyncServerExchange) returns null during getSession — the McpSyncServerExchange instance was created with a null internal exchange field (e.g. by test code or an SDK change in how the wrapper is built).","commonSituations":"Test code instantiating McpSyncServerExchange without an McpAsyncServerExchange; a proxy/mock framework replacing the internal field; an MCP SDK version where the wrapper no longer eagerly sets the delegate.","solutions":["Always obtain McpSyncServerExchange from the live MCP request (the ToolContext 'exchange' entry), never construct it manually","Pin the MCP SDK to the tested version 0.17.0 and check startup logs for reflection resolution errors","Inspect the instance with a debugger/toString to confirm the internal exchange field is populated before calling getSessionId","If you must build one in tests, construct it with a real McpAsyncServerExchange and session"],"exampleFix":"// before\nMcpSyncServerExchange exchange = new McpSyncServerExchange(null);\n// after\nMcpSyncServerExchange exchange = new McpSyncServerExchange(mcpAsyncServerExchange);","handlingStrategy":"type-guard","validationCode":"if (McpSessionHelper.getSession(mcpSyncServerExchange) == null) {\n    throw new IllegalStateException(\"exchange has no async delegate; obtain it from the live MCP request\");\n}","typeGuard":"boolean hasAsyncExchange(McpSyncServerExchange ex) {\n    try { return McpSessionHelper.getSession(ex) != null; }\n    catch (RuntimeException e) { return false; }\n}","tryCatchPattern":"try {\n    McpSessionHelper.getSessionId(mcpSyncServerExchange);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"McpAsyncServerExchange is required\")) {\n        LOG.error(\"McpSyncServerExchange built without async delegate; use the framework-supplied instance\", e);\n    } else throw e;\n}","preventionTips":["Never construct McpSyncServerExchange manually; take it from the ToolContext","Use real session-backed objects instead of partial mocks in tests","Pin the MCP SDK to 0.17.0"],"tags":["mcp","reflection","null"],"backgroundTag":"null-argument","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}