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
- 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
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
- 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
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
- Failed to retrieve MCP sync server exchange from context
- Session ID is empty – it should have been set earlier by…
- Failed to extract session ID from MCP exchange. This may…
- No ServerWebExchange found for session
- McpAsyncServerExchange is required in McpSyncServerExchange
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)