apache/shenyu · error · IllegalArgumentException

Session is required in McpAsyncServerExchange

Error message

Session is required in McpAsyncServerExchange

What it means

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.

Solutions

  1. Verify the MCP session is still open when the tool runs; avoid invoking tools after client disconnect or server.close()
  2. Pin MCP SDK to 0.17.0 and align Spring AI 1.1.2 so field semantics match what the helper expects
  3. Reproduce by logging McpSessionHelper.isReflectionAvailable() and the session object before the tool call
  4. In tests, use a real session-backed McpAsyncServerExchange instead of a bare mock

Example fix

// before: calling tool after session close
server.closeSession(sessionId); client.callTool(...);
// after: call within the live session
client.callTool(...); server.closeSession(sessionId);
Defensive patterns

Strategy: try-catch

Validate before calling

try {
    McpServerSession s = McpSessionHelper.getSession(mcpSyncServerExchange);
    if (s == null || s.getId() == null) throw new McpError("MCP session closed");
} catch (RuntimeException ignored) { }

Type guard

boolean hasLiveSession(McpSyncServerExchange ex) {
    try { return McpSessionHelper.getSession(ex) != null; }
    catch (RuntimeException e) { return false; }
}

Try / catch

try {
    String id = McpSessionHelper.getSessionId(mcpSyncServerExchange);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Session is required in McpAsyncServerExchange")) {
        LOG.warn("Session already closed for exchange; reject tool call");
    } else throw e;
}

Prevention

When it happens

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

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

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/06389b4b3018c09d. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java:180

     * <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 McpServerSession instance
     * @throws IllegalStateException if SDK reflection fails (API incompatibility)
     */
    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);
        }
    }

    /**
     * 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)) {

View on GitHub (pinned to 567142e072)