{"record":{"id":"82089f5f92e1aae4","repo":"spring-projects/spring-ai","slug":"unsupported-sync-exchange-type-syncserverexchang","errorCode":null,"errorMessage":"Unsupported Sync exchange type: {syncServerExchange.getClass().getName()} for Sync method: {method.getName()} in {method.getDeclaringClass().getName()}","messagePattern":"Unsupported Sync 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/AsyncStatelessMcpPromptMethodCallback.java","lineNumber":74,"sourceCode":"\t\tif (McpSyncServerExchange.class.isAssignableFrom(paramType)\n\t\t\t\t|| McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Stateless Streamable-Http prompt method must not declare parameter of type: \" + paramType.getName()\n\t\t\t\t\t\t\t+ \". Use McpTransportContext instead.\" + \" Method: \" + this.method.getName() + \" in \"\n\t\t\t\t\t\t\t+ this.method.getDeclaringClass().getName());\n\t\t}\n\t}\n\n\t@Override\n\tprotected Object assignExchangeType(Class<?> paramType, Object exchange) {\n\n\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\tthrow new IllegalArgumentException(\"Unsupported Sync exchange type: \"\n\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Sync method: \" + method.getName() + \" in \"\n\t\t\t\t\t\t+ method.getDeclaringClass().getName());\n\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange.transportContext();\n\t\t\t}\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 context and request.\n\t * <p>\n\t * This method builds the arguments for the method call, invokes the method, and","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncStatelessMcpPromptMethodCallback.java#L56-L92","documentation":"When an async stateless prompt method declares an McpTransportContext parameter, assignExchangeType converts the runtime exchange object into that context. A McpSyncServerExchange arriving at an async (stateless) method is a Sync exchange, which is not convertible, so IllegalArgumentException is thrown indicating the mismatch.","triggerScenarios":"A method declaring McpTransportContext is invoked, but the callback receives a McpSyncServerExchange as the exchange argument — e.g. the method callback is wired into a sync server dispatch path.","commonSituations":"Mixing sync and async MCP servers with the same annotated bean; registering an async stateless callback on a sync server transport.","solutions":["Register the annotated bean with the async MCP server so it receives async exchanges/transport contexts","Use separate beans (or separate callbacks) for sync and async servers","Verify the server type (McpAsyncServer vs McpSyncServer) matches the callback type"],"exampleFix":"// before\nMcpServer.sync(mcpTransportProvider).prompts(...).build(); // with async stateless callback\n// after\nMcpServer.async(mcpStreamableServerTransportProvider).prompts(...).build();","handlingStrategy":"validation","validationCode":"assert server instanceof McpAsyncServer : \"Async stateless callbacks require an async server\";","typeGuard":"boolean compatible(Object exchange) { return exchange instanceof McpTransportContext || exchange instanceof McpAsyncServerExchange; }","tryCatchPattern":"try { result = callback.apply(exchange, request); } catch (IllegalArgumentException e) { throw new McpError(\"Exchange/server mismatch: \" + e.getMessage()); }","preventionTips":["Never share one annotated bean between sync and async servers","Match McpServer.sync/async to the callback class","Add integration tests exercising each registered prompt end-to-end"],"tags":["mcp","sync-async-mismatch","exchange-type","prompt-method"],"backgroundTag":"type-mismatch","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}