{"record":{"id":"e7f68549d5e0e7a3","repo":"apache/shenyu","slug":"session-id-is-empty-it-should-have-been-set-earlier-by","errorCode":null,"errorMessage":"Session ID is empty – it should have been set earlier by handleMessageEndpoint","messagePattern":"Session ID is empty – it should have been set earlier by handleMessageEndpoint","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java","lineNumber":779,"sourceCode":"        return exchange;\n    }\n\n    /**\n     * Extracts the session ID from the MCP sync server exchange.\n     *\n     * @param mcpExchange the MCP sync server exchange\n     * @return the session ID\n     * @throws IllegalStateException if the session ID is blank or an SDK compatibility issue blocks extraction\n     * @throws IllegalArgumentException if the exchange is missing required session state\n     */\n    private String extractSessionId(final McpSyncServerExchange mcpExchange) {\n        try {\n            final String sessionId = McpSessionHelper.getSessionId(mcpExchange);\n            if (StringUtils.hasText(sessionId)) {\n                LOG.debug(\"Extracted session ID: {}\", sessionId);\n                return sessionId;\n            }\n            throw new IllegalStateException(\"Session ID is empty – it should have been set earlier by handleMessageEndpoint\");\n        } catch (RuntimeException e) {\n            if (!isSdkCompatibilityError(e)) {\n                throw e;\n            }\n\n            // Re-throw SDK compatibility errors with additional context.\n            throw new IllegalStateException(\n                    \"Failed to extract session ID from MCP exchange. \"\n                    + \"This may indicate an SDK compatibility issue. \"\n                    + \"Tested SDK version: \" + McpSessionHelper.getSupportedSdkVersion() + \". \"\n                    + \"Original error: \" + e.getMessage(), e);\n        }\n    }\n\n    private boolean isSdkCompatibilityError(final RuntimeException exception) {\n        return exception instanceof IllegalStateException\n                && StringUtils.hasText(exception.getMessage())\n                && exception.getMessage().startsWith(SDK_COMPATIBILITY_ERROR_PREFIX);","sourceCodeStart":761,"sourceCodeEnd":797,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java#L761-L797","documentation":"Thrown by ShenyuToolCallback.extractSessionId when McpSessionHelper.getSessionId() returns a null/blank session ID. The MCP tool invocation pipeline requires the MCP session id (established when the HTTP endpoint handleMessageEndpoint handled the client's request) to correlate the tool call back to the originating gateway ServerWebExchange; a blank id means the internal session state was not propagated. This is an internal invariant violation rather than a caller-input problem.","triggerScenarios":"McpSessionHelper.getSessionId(mcpExchange) returns null or empty string during a tool callback execution — i.e. the McpSyncServerExchange exists but its underlying McpServerSession has no id, or the session was not established by handleMessageEndpoint before the tool was invoked.","commonSituations":"Invoking a tool outside a normal MCP HTTP message flow (e.g. programmatic/direct tool invocation in tests); a partially-initialized McpSyncServerExchange built without a session; a gateway restart or session teardown between handleMessageEndpoint and the tool call.","solutions":["Ensure the tool is invoked only through the MCP message endpoint (handleMessageEndpoint) after a proper initialize handshake so the session id is set","Verify the MCP SDK (tested: 0.17.0) matches the version ShenYu was built against — a different version can produce exchanges with null session state","Log the McpSyncServerExchange state (via McpSessionHelper.getSession) to confirm the session object and its id before the call","Check ShenyuMcpExchangeHolder/handleMessageEndpoint wiring is intact and no custom filter stripped the session attribute"],"exampleFix":"// before: invoking tool directly without session\nnew ShenyuToolCallback(...).call(args);\n// after: route the call through the MCP message endpoint with an initialized session\nmcpClient.initialize(...); mcpClient.callTool(\"myTool\", args);","handlingStrategy":"validation","validationCode":"String sessionId = McpSessionHelper.getSessionId(mcpExchange);\nif (sessionId == null || sessionId.isBlank()) {\n    throw new McpError(\"MCP session not initialized; invoke tools via handleMessageEndpoint\");\n}","typeGuard":"boolean hasSession(McpSyncServerExchange ex) {\n    try { return McpSessionHelper.getSession(ex) != null && StringUtils.hasText(McpSessionHelper.getSession(ex).getId()); }\n    catch (RuntimeException e) { return false; }\n}","tryCatchPattern":"try {\n    callback.call(args, toolContext);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"Session ID is empty\")) {\n        LOG.error(\"MCP session state missing; re-handshake required\", e);\n        // return JSON-RPC error to client\n    } else throw e;\n}","preventionTips":["Only invoke tools through the MCP message endpoint after a successful initialize handshake","Keep the MCP SDK at the tested 0.17.0 version","Add a startup health check asserting McpSessionHelper.isReflectionAvailable()"],"tags":["mcp","session","state"],"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"}