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

  1. Always obtain McpSyncServerExchange from the live MCP request (the ToolContext 'exchange' entry), never construct it manually
  2. Pin the MCP SDK to the tested version 0.17.0 and check startup logs for reflection resolution errors
  3. Inspect the instance with a debugger/toString to confirm the internal exchange field is populated before calling getSessionId
  4. 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

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


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)