{"record":{"id":"06389b4b3018c09d","repo":"apache/shenyu","slug":"session-is-required-in-mcpasyncserverexchange","errorCode":null,"errorMessage":"Session is required in McpAsyncServerExchange","messagePattern":"Session is required in McpAsyncServerExchange","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":180,"sourceCode":"     * <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.\n     *\n     * @throws IllegalStateException if reflection fields are not available\n     */\n    private static void checkReflectionAvailability() {\n        if (!fieldsResolved || Objects.isNull(asyncExchangeFieldCache) || Objects.isNull(sessionFieldCache)) {","sourceCodeStart":162,"sourceCodeEnd":198,"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#L162-L198","documentation":"IllegalArgumentException from McpSessionHelper.getSession when the reflective read of McpAsyncServerExchange's internal 'session' field yields null. The helper walks exchange -> async exchange -> McpServerSession via reflection (SDK 0.17.0 layout) to get the session id; a null session object means the async exchange is not bound to a server session.","triggerScenarios":"sessionFieldCache.get(mcpAsyncServerExchange) returns null during getSession — the McpAsyncServerExchange exists but its session field was never set or has been cleared (e.g. session closed concurrently).","commonSituations":"Tool invoked after the MCP client disconnected / session was closed and nulled out; SDK or Spring AI version change in field lifecycle; mock McpAsyncServerExchange in tests without a session.","solutions":["Verify the MCP session is still open when the tool runs; avoid invoking tools after client disconnect or server.close()","Pin MCP SDK to 0.17.0 and align Spring AI 1.1.2 so field semantics match what the helper expects","Reproduce by logging McpSessionHelper.isReflectionAvailable() and the session object before the tool call","In tests, use a real session-backed McpAsyncServerExchange instead of a bare mock"],"exampleFix":"// before: calling tool after session close\nserver.closeSession(sessionId); client.callTool(...);\n// after: call within the live session\nclient.callTool(...); server.closeSession(sessionId);","handlingStrategy":"try-catch","validationCode":"try {\n    McpServerSession s = McpSessionHelper.getSession(mcpSyncServerExchange);\n    if (s == null || s.getId() == null) throw new McpError(\"MCP session closed\");\n} catch (RuntimeException ignored) { }","typeGuard":"boolean hasLiveSession(McpSyncServerExchange ex) {\n    try { return McpSessionHelper.getSession(ex) != null; }\n    catch (RuntimeException e) { return false; }\n}","tryCatchPattern":"try {\n    String id = McpSessionHelper.getSessionId(mcpSyncServerExchange);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Session is required in McpAsyncServerExchange\")) {\n        LOG.warn(\"Session already closed for exchange; reject tool call\");\n    } else throw e;\n}","preventionTips":["Do not invoke tools after server.closeSession() or client disconnect","Track session lifecycle and cancel in-flight tool work on close","Pin MCP SDK 0.17.0 / Spring AI 1.1.2"],"tags":["mcp","reflection","session"],"backgroundTag":"internal-invariant-violation","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"}