apache/shenyu · error · IllegalStateException
No ServerWebExchange found for session
Error message
No ServerWebExchange found for session '${sessionId}'. It should have been stored by handleMessageEndpoint before the tool was invoked. What it means
Thrown by ShenyuToolCallback.getOriginExchange when ShenyuMcpExchangeHolder.get(sessionId) returns null. The MCP HTTP message endpoint (handleMessageEndpoint) must store the originating gateway ServerWebExchange in ShenyuMcpExchangeHolder before a tool is invoked; without it the tool cannot access the original request context (headers, params) to proxy the backend call. This means the session-to-exchange mapping is missing from the holder.
Solutions
- Ensure requests flow through handleMessageEndpoint so the ServerWebExchange is registered in ShenyuMcpExchangeHolder before the tool executes
- Verify the sessionId used is the one obtained via McpSessionHelper.getSessionId from the current McpSyncServerExchange, not a stale/foreign id
- Check ShenyuMcpExchangeHolder cleanup/expiry logic and gateway restarts — single-instance sticky routing is required since the holder is in-memory
- Call ShenyuMcpExchangeHolder.get(sessionId) yourself before invoking the tool and fail fast with a clear client error
Example fix
// before
callback.call(request);
// after: guard before invocation
if (ShenyuMcpExchangeHolder.get(sessionId) == null) {
throw new McpError("no exchange for session " + sessionId);
}
callback.call(request); Defensive patterns
Strategy: validation
Validate before calling
ServerWebExchange origin = ShenyuMcpExchangeHolder.get(sessionId);
if (origin == null) {
throw new McpError("no origin exchange registered for session " + sessionId);
} Type guard
boolean exchangeStored(String sessionId) {
return sessionId != null && ShenyuMcpExchangeHolder.get(sessionId) != null;
} Try / catch
try {
ServerWebExchange ex = ShenyuMcpExchangeHolder.get(sessionId);
// use ex
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("No ServerWebExchange found")) {
LOG.warn("Exchange missing for session {}; client must reconnect", sessionId);
} else throw e;
} Prevention
- Always route tool executions through handleMessageEndpoint
- Use sticky/single-instance routing since ShenyuMcpExchangeHolder is in-memory
- Remove stale sessions promptly so lookups fail early, not mid-tool-call
When it happens
Trigger: ShenyuMcpExchangeHolder.get(sessionId) returns null for the session id extracted from the MCP exchange — the exchange was never stored, was removed on session close, or a different session id is being looked up.
Common situations: Calling tools outside the normal HTTP message flow (unit/integration tests, manual invocations); holder cleanup (session expiry/restart) removing the entry before the tool callback runs; load-balanced deployments where the MCP message landed on a different instance than the tool execution; custom routing bypassing handleMessageEndpoint.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Failed to retrieve MCP sync server exchange from context
- Session ID is empty – it should have been set earlier by…
- Tool execution timeout or error
- Invalid URI construction
- Session is required in McpAsyncServerExchange
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/42200714feabd31f.
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:813
return exception instanceof IllegalStateException
&& StringUtils.hasText(exception.getMessage())
&& exception.getMessage().startsWith(SDK_COMPATIBILITY_ERROR_PREFIX);
}
/**
* Gets the origin ServerWebExchange for the given session ID.
*
* @param sessionId the session ID
* @return the origin ServerWebExchange
* @throws IllegalStateException if exchange cannot be retrieved
*/
private ServerWebExchange getOriginExchange(final String sessionId) {
final ServerWebExchange exchange = ShenyuMcpExchangeHolder.get(sessionId);
if (Objects.nonNull(exchange)) {
LOG.debug("Found existing exchange for session: {}", sessionId);
return exchange;
}
throw new IllegalStateException("No ServerWebExchange found for session '" + sessionId
+ "'. It should have been stored by handleMessageEndpoint before the tool was invoked.");
}
}
View on GitHub (pinned to 567142e072)