{"record":{"id":"e248d9772310090a","repo":"spring-projects/spring-ai","slug":"unsupported-async-exchange-type-syncserverexchan","errorCode":null,"errorMessage":"Unsupported Async exchange type: {syncServerExchange.getClass().getName()} for Async method: {method.getName()} in {method.getDeclaringClass().getName()}","messagePattern":"Unsupported Async exchange type: (.+?) for Async 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/AsyncMcpPromptMethodCallback.java","lineNumber":69,"sourceCode":"\t@Override\n\tprotected void validateParamType(Class<?> paramType) {\n\n\t\tif (McpSyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tthrow new IllegalArgumentException(\"Async prompt method must not declare parameter of type: \"\n\t\t\t\t\t+ paramType.getName() + \". Use McpAsyncServerExchange instead.\" + \" Method: \"\n\t\t\t\t\t+ this.method.getName() + \" in \" + 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 Async exchange type: \"\n\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Async 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\t\telse if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange;\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 Async method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t}\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncMcpPromptMethodCallback.java#L51-L87","documentation":"In AsyncMcpPromptMethodCallback.assignExchangeType, when a method parameter is a McpTransportContext but the runtime exchange passed by the server is a McpSyncServerExchange, the callback cannot supply a transport context from a sync exchange and throws this IllegalArgumentException at invocation time. It indicates a sync/async mismatch: an async-declared method is being driven by a sync server exchange.","triggerScenarios":"An async prompt method declares `McpTransportContext ctx` but the callback is invoked with a McpSyncServerExchange instance (e.g. the method was registered on both a sync and async server, or the sync callback dispatched it).","commonSituations":"Registering the same prompt bean with both McpSyncServer and McpAsyncServer; mixing sync/async annotation processors over the same bean; refactoring a sync handler to async parameter types without changing server wiring.","solutions":["Register the method with the correct callback variant: async methods only on McpAsyncServer / async annotation processor.","Change the parameter to McpAsyncServerExchange if the surrounding server is sync-driven, or vice versa.","Audit duplicated registrations so the same @McpPrompt method is not wired to both sync and async servers."],"exampleFix":"// before (registered on sync server)\n@McpPrompt(name = \"report\")\npublic Mono<String> report(McpTransportContext ctx) { ... }\n// after (register with async server, or change parameter)\n@McpPrompt(name = \"report\")\npublic Mono<String> report(McpAsyncServerExchange exchange) { ... }","handlingStrategy":"validation","validationCode":"// before registering:\nif (server instanceof McpSyncServer && usesTransportContextOrAsyncParams(method)) {\n    throw new IllegalStateException(\"Method must be registered on the async server: \" + method);\n}","typeGuard":"boolean isSyncExchange(Object exchange) { return exchange instanceof McpSyncServerExchange; }","tryCatchPattern":"try {\n    return callback.apply(exchange, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Unsupported Async exchange type\")) {\n        throw new IllegalStateException(\"Method registered with wrong server variant (sync vs async)\", e);\n    }\n    throw e;\n}","preventionTips":["Register each prompt method with exactly one server variant (sync OR async).","When migrating sync->async, change both return types and exchange parameters together.","Add integration tests exercising every prompt through the actual server instance."],"tags":["mcp","async","sync-async-mismatch","exchange-type"],"backgroundTag":"incompatible-source-type","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"}