{"record":{"id":"66f1c1dee41bdb86","repo":"spring-projects/spring-ai","slug":"unsupported-async-exchange-type-exchangetype-f-66f1c1","errorCode":null,"errorMessage":"Unsupported Async exchange type: ${exchangeType} for Async method: ${method} in ${declaringClass}","messagePattern":"Unsupported Async exchange type: (.+?) for Async method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java","lineNumber":78,"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":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java#L60-L96","documentation":"Thrown by AsyncMcpResourceMethodCallback.assignExchangeType when the annotated async method expects a McpTransportContext parameter but the runtime exchange object is a McpSyncServerExchange. An async callback cannot be driven by a sync exchange, so it cannot supply a matching transport context and fails fast.","triggerScenarios":"Invoking an @McpResource method callback (declared to take McpTransportContext) that was routed through a sync server call path, so the exchange argument passed to assignExchangeType is a McpSyncServerExchange instance.","commonSituations":"Registering the same annotated resource provider on both a sync and an async server and calling it via the sync path; wiring the async callback into a sync spec builder by mistake.","solutions":["Route the request through the async server/spec builder so an McpAsyncServerExchange (or McpTransportContext) is passed.","If the method should be sync, move it to a sync callback registration matching the exchange you actually have.","Audit the server configuration so each annotated provider is attached to exactly one server flavor consistent with its exchange parameter."],"exampleFix":"// before (sync path driving async callback)\nsyncServer.callResource(...); // exchange = McpSyncServerExchange\n\n// after\nasyncServer.callResource(...); // exchange = McpAsyncServerExchange / McpTransportContext","handlingStrategy":"try-catch","validationCode":"if (exchange instanceof McpSyncServerExchange) {\n    throw new IllegalStateException(\"Async callback invoked from sync path\");\n}","typeGuard":"static boolean isSyncExchange(Object ex) { return ex instanceof McpSyncServerExchange; }","tryCatchPattern":"try {\n    return callback.call(exchange, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Unsupported Async exchange type\")) {\n        throw new IllegalStateException(\"Routed async callback through sync dispatcher\", e);\n    }\n    throw e;\n}","preventionTips":["Register each provider on exactly one server flavor","Use McpTransportContext parameters for provider methods shared by sync and async servers","Cover routing in integration tests so the wrong dispatcher fails in CI"],"tags":["mcp","java","async","exchange-mismatch"],"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"}