apache/shenyu · error · IllegalArgumentException

McpSyncServerExchange is required in ToolContext

Error message

McpSyncServerExchange is required in ToolContext

What it means

IllegalArgumentException from McpSessionHelper.getMcpSyncServerExchange when the ToolContext has a non-empty map but no value under the key 'exchange'. The helper expects the MCP tool execution pipeline to place the current McpSyncServerExchange in the context; its absence means the exchange was never stored or stored under a different key.

Solutions

  1. Ensure the McpSyncServerExchange is stored under the exact key "exchange" in the ToolContext map
  2. Route tool calls through the standard ShenYu MCP server execution path so the framework sets the key itself
  3. If you construct the context yourself, add Map.of("exchange", mcpSyncServerExchange)
  4. Check that no code overwrites or clears the 'exchange' entry before the callback runs

Example fix

// before
new ToolContext(Map.of("sessionId", sessionId))
// after
new ToolContext(Map.of("sessionId", sessionId, "exchange", mcpSyncServerExchange))
Defensive patterns

Strategy: validation

Validate before calling

Object ex = toolContext.getContext().get("exchange");
if (!(ex instanceof McpSyncServerExchange)) {
    throw new IllegalArgumentException("ToolContext map must contain key \"exchange\" -> McpSyncServerExchange");
}

Type guard

boolean hasMcpExchange(ToolContext ctx) {
    return ctx != null && ctx.getContext() != null
        && ctx.getContext().get("exchange") instanceof McpSyncServerExchange;
}

Try / catch

try {
    McpSessionHelper.getMcpSyncServerExchange(toolContext);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("McpSyncServerExchange is required in ToolContext")) {
        LOG.error("ToolContext missing \"exchange\" key; expected McpSyncServerExchange", e);
    } else throw e;
}

Prevention

When it happens

Trigger: contextMap.get("exchange") returns null during getMcpSyncServerExchange — the ToolContext map contains other entries but no 'exchange' key, or the value stored was null / of a different type (which then also fails the cast).

Common situations: Custom tool-invocation code putting the exchange under a different key name; constructing ToolContext manually in tests with partial context; an MCP SDK / Spring AI upgrade changing the context key contract.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/64b115760e07790b. 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:140

    }

    /**
     * Get McpSyncServerExchange from ToolContext.
     *
     * @param toolContext the tool context
     * @return the McpSyncServerExchange instance
     */
    public static McpSyncServerExchange getMcpSyncServerExchange(final ToolContext toolContext) {
        if (Objects.isNull(toolContext)) {
            throw new IllegalArgumentException("ToolContext is required");
        }
        Map<String, Object> contextMap = toolContext.getContext();
        if (Objects.isNull(contextMap) || contextMap.isEmpty()) {
            throw new IllegalArgumentException("ToolContext is required");
        }
        McpSyncServerExchange mcpSyncServerExchange = (McpSyncServerExchange) contextMap.get("exchange");
        if (Objects.isNull(mcpSyncServerExchange)) {
            throw new IllegalArgumentException("McpSyncServerExchange is required in ToolContext");
        }
        return mcpSyncServerExchange;
    }

    /**
     * Get sessionId 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 session id string
     * @throws IllegalStateException if SDK reflection fails (API incompatibility)
     */
    public static String getSessionId(final McpSyncServerExchange mcpSyncServerExchange) {
        return getSession(mcpSyncServerExchange).getId();
    }

View on GitHub (pinned to 567142e072)