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

  1. Ensure requests flow through handleMessageEndpoint so the ServerWebExchange is registered in ShenyuMcpExchangeHolder before the tool executes
  2. Verify the sessionId used is the one obtained via McpSessionHelper.getSessionId from the current McpSyncServerExchange, not a stale/foreign id
  3. Check ShenyuMcpExchangeHolder cleanup/expiry logic and gateway restarts — single-instance sticky routing is required since the holder is in-memory
  4. 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

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


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)