{"record":{"id":"2574b62eb3606f76","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchange-null-exc-2574b6","errorCode":null,"errorMessage":"Unsupported exchange type: {exchange != null ? exchange.getClass().getName() : \"null\"} for Sync method: {method.getName()} in {method.getDeclaringClass().getName()}","messagePattern":"Unsupported exchange type: (.+?) for Sync method: (.+?) in (.+?)","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncMcpPromptMethodCallback.java","lineNumber":82,"sourceCode":"\t\tif (McpTransportContext.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpTransportContext transportContext) {\n\t\t\t\treturn transportContext;\n\t\t\t}\n\t\t\telse if (exchange instanceof McpSyncServerExchange syncServerExchange) {\n\t\t\t\treturn syncServerExchange.transportContext();\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unsupported Async exchange type: \"\n\t\t\t\t\t\t+ asyncServerExchange.getClass().getName() + \" for Sync method: \" + method.getName() + \" in \"\n\t\t\t\t\t\t+ method.getDeclaringClass().getName());\n\t\t\t}\n\t\t}\n\t\telse if (McpSyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpSyncServerExchange syncServerExchange) {\n\t\t\t\treturn syncServerExchange;\n\t\t\t}\n\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t\t+ \" for Sync method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t}\n\n\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t+ \" for method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t}\n\n\t/**\n\t * Apply the callback to the given exchange and request.\n\t * <p>\n\t * This method builds the arguments for the method call, invokes the method, and\n\t * converts the result to a GetPromptResult.\n\t * @param exchange The server exchange, may be null if the method doesn't require it\n\t * @param request The prompt request, must not be null\n\t * @return The prompt result\n\t * @throws McpError if there is an error invoking the prompt method","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncMcpPromptMethodCallback.java#L64-L100","documentation":"When a sync prompt method declares an McpSyncServerExchange parameter, assignExchangeType requires the runtime exchange to actually be a McpSyncServerExchange. Any other (or null) exchange reaches the fallback throw, indicating the callback was invoked from an incompatible server path.","triggerScenarios":"A method expecting McpSyncServerExchange is invoked with a different exchange object (async exchange, transport context, or null).","commonSituations":"Custom or misconfigured transports calling the callback directly; swapping server implementations without re-registering callbacks; version mismatches between MCP modules.","solutions":["Ensure the sync server supplies a McpSyncServerExchange to the callback","Register the callback only with a sync MCP server","If the exchange is null, fix the dispatch/transport configuration so a real exchange is passed"],"exampleFix":"// before\nasyncCallback applied on sync path with McpTransportContext\n// after\nsyncCallback applied on sync server path receiving McpSyncServerExchange","handlingStrategy":"type-guard","validationCode":"if (exchange == null || !(exchange instanceof McpSyncServerExchange)) throw new IllegalStateException(\"expected McpSyncServerExchange\");","typeGuard":"boolean isSyncExchange(Object e) { return e instanceof McpSyncServerExchange; }","tryCatchPattern":"try { r = callback.apply(exchange, req); } catch (IllegalArgumentException e) { log.error(\"Bad exchange {} for sync prompt\", exchange, e); }","preventionTips":["Do not call callback.apply() directly outside framework dispatch","Keep MCP module versions aligned","Ensure the sync server is the only caller of sync callbacks"],"tags":["mcp","exchange-type","prompt-method","internal"],"backgroundTag":"unsupported-operation","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}