apache/shenyu · error · IllegalArgumentException
McpAsyncServerExchange is required in McpSyncServerExchange
Error message
McpAsyncServerExchange is required in McpSyncServerExchange
What it means
IllegalArgumentException from McpSessionHelper.getSession when the reflective read of McpSyncServerExchange's internal 'exchange' field yields null. The helper uses reflection (tested against MCP SDK 0.17.0) to reach the nested McpAsyncServerExchange and then the McpServerSession to obtain the session id; a null nested exchange means the McpSyncServerExchange was constructed without its async delegate.
Solutions
- Always obtain McpSyncServerExchange from the live MCP request (the ToolContext 'exchange' entry), never construct it manually
- Pin the MCP SDK to the tested version 0.17.0 and check startup logs for reflection resolution errors
- Inspect the instance with a debugger/toString to confirm the internal exchange field is populated before calling getSessionId
- If you must build one in tests, construct it with a real McpAsyncServerExchange and session
Example fix
// before McpSyncServerExchange exchange = new McpSyncServerExchange(null); // after McpSyncServerExchange exchange = new McpSyncServerExchange(mcpAsyncServerExchange);
Defensive patterns
Strategy: type-guard
Validate before calling
if (McpSessionHelper.getSession(mcpSyncServerExchange) == null) {
throw new IllegalStateException("exchange has no async delegate; obtain it from the live MCP request");
} Type guard
boolean hasAsyncExchange(McpSyncServerExchange ex) {
try { return McpSessionHelper.getSession(ex) != null; }
catch (RuntimeException e) { return false; }
} Try / catch
try {
McpSessionHelper.getSessionId(mcpSyncServerExchange);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("McpAsyncServerExchange is required")) {
LOG.error("McpSyncServerExchange built without async delegate; use the framework-supplied instance", e);
} else throw e;
} Prevention
- Never construct McpSyncServerExchange manually; take it from the ToolContext
- Use real session-backed objects instead of partial mocks in tests
- Pin the MCP SDK to 0.17.0
When it happens
Trigger: asyncExchangeFieldCache.get(mcpSyncServerExchange) returns null during getSession — the McpSyncServerExchange instance was created with a null internal exchange field (e.g. by test code or an SDK change in how the wrapper is built).
Common situations: Test code instantiating McpSyncServerExchange without an McpAsyncServerExchange; a proxy/mock framework replacing the internal field; an MCP SDK version where the wrapper no longer eagerly sets the delegate.
Related errors
- Failed to extract session ID from MCP exchange. This may…
- ToolContext is required
- Session is required in McpAsyncServerExchange
- SDK COMPATIBILITY ERROR: Failed to access SDK internal…
- SDK COMPATIBILITY ERROR: MCP SDK reflection fields are not…
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/705f128fd0923595.
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:175
}
/**
* Get McpServerSession from McpSyncServerExchange.
*
* <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.View on GitHub (pinned to 567142e072)