apache/shenyu · error · IllegalStateException

Session ID is empty – it should have been set earlier by…

Error message

Session ID is empty – it should have been set earlier by handleMessageEndpoint

What it means

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.

Solutions

  1. Ensure the tool is invoked only through the MCP message endpoint (handleMessageEndpoint) after a proper initialize handshake so the session id is set
  2. 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
  3. Log the McpSyncServerExchange state (via McpSessionHelper.getSession) to confirm the session object and its id before the call
  4. Check ShenyuMcpExchangeHolder/handleMessageEndpoint wiring is intact and no custom filter stripped the session attribute

Example fix

// before: invoking tool directly without session
new ShenyuToolCallback(...).call(args);
// after: route the call through the MCP message endpoint with an initialized session
mcpClient.initialize(...); mcpClient.callTool("myTool", args);
Defensive patterns

Strategy: validation

Validate before calling

String sessionId = McpSessionHelper.getSessionId(mcpExchange);
if (sessionId == null || sessionId.isBlank()) {
    throw new McpError("MCP session not initialized; invoke tools via handleMessageEndpoint");
}

Type guard

boolean hasSession(McpSyncServerExchange ex) {
    try { return McpSessionHelper.getSession(ex) != null && StringUtils.hasText(McpSessionHelper.getSession(ex).getId()); }
    catch (RuntimeException e) { return false; }
}

Try / catch

try {
    callback.call(args, toolContext);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("Session ID is empty")) {
        LOG.error("MCP session state missing; re-handshake required", e);
        // return JSON-RPC error to client
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/e7f68549d5e0e7a3. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java:779

        return exchange;
    }

    /**
     * Extracts the session ID from the MCP sync server exchange.
     *
     * @param mcpExchange the MCP sync server exchange
     * @return the session ID
     * @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) {
        try {
            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);
        }
    }

    private boolean isSdkCompatibilityError(final RuntimeException exception) {
        return exception instanceof IllegalStateException
                && StringUtils.hasText(exception.getMessage())
                && exception.getMessage().startsWith(SDK_COMPATIBILITY_ERROR_PREFIX);

View on GitHub (pinned to 567142e072)