{"record":{"id":"64b115760e07790b","repo":"apache/shenyu","slug":"mcpsyncserverexchange-is-required-in-toolcontext","errorCode":null,"errorMessage":"McpSyncServerExchange is required in ToolContext","messagePattern":"McpSyncServerExchange is required in ToolContext","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java","lineNumber":140,"sourceCode":"    }\n\n    /**\n     * Get McpSyncServerExchange from ToolContext.\n     *\n     * @param toolContext the tool context\n     * @return the McpSyncServerExchange instance\n     */\n    public static McpSyncServerExchange getMcpSyncServerExchange(final ToolContext toolContext) {\n        if (Objects.isNull(toolContext)) {\n            throw new IllegalArgumentException(\"ToolContext is required\");\n        }\n        Map<String, Object> contextMap = toolContext.getContext();\n        if (Objects.isNull(contextMap) || contextMap.isEmpty()) {\n            throw new IllegalArgumentException(\"ToolContext is required\");\n        }\n        McpSyncServerExchange mcpSyncServerExchange = (McpSyncServerExchange) contextMap.get(\"exchange\");\n        if (Objects.isNull(mcpSyncServerExchange)) {\n            throw new IllegalArgumentException(\"McpSyncServerExchange is required in ToolContext\");\n        }\n        return mcpSyncServerExchange;\n    }\n\n    /**\n     * Get sessionId from McpSyncServerExchange.\n     *\n     * <p>Uses reflection to access internal SDK fields. If reflection fails,\n     * an IllegalStateException is thrown with SDK compatibility information.\n     *\n     * @param mcpSyncServerExchange the McpSyncServerExchange instance\n     * @return the session id string\n     * @throws IllegalStateException if SDK reflection fails (API incompatibility)\n     */\n    public static String getSessionId(final McpSyncServerExchange mcpSyncServerExchange) {\n        return getSession(mcpSyncServerExchange).getId();\n    }\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/session/McpSessionHelper.java#L122-L158","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Ensure the McpSyncServerExchange is stored under the exact key \"exchange\" in the ToolContext map","Route tool calls through the standard ShenYu MCP server execution path so the framework sets the key itself","If you construct the context yourself, add Map.of(\"exchange\", mcpSyncServerExchange)","Check that no code overwrites or clears the 'exchange' entry before the callback runs"],"exampleFix":"// before\nnew ToolContext(Map.of(\"sessionId\", sessionId))\n// after\nnew ToolContext(Map.of(\"sessionId\", sessionId, \"exchange\", mcpSyncServerExchange))","handlingStrategy":"validation","validationCode":"Object ex = toolContext.getContext().get(\"exchange\");\nif (!(ex instanceof McpSyncServerExchange)) {\n    throw new IllegalArgumentException(\"ToolContext map must contain key \\\"exchange\\\" -> McpSyncServerExchange\");\n}","typeGuard":"boolean hasMcpExchange(ToolContext ctx) {\n    return ctx != null && ctx.getContext() != null\n        && ctx.getContext().get(\"exchange\") instanceof McpSyncServerExchange;\n}","tryCatchPattern":"try {\n    McpSessionHelper.getMcpSyncServerExchange(toolContext);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"McpSyncServerExchange is required in ToolContext\")) {\n        LOG.error(\"ToolContext missing \\\"exchange\\\" key; expected McpSyncServerExchange\", e);\n    } else throw e;\n}","preventionTips":["Store the exchange under the exact key \"exchange\"","Do not overwrite/clear the key in custom middleware","Check the key contract after any Spring AI/MCP SDK upgrade"],"tags":["mcp","missing-key","context"],"backgroundTag":"missing-config-key","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}