{"record":{"id":"be142a36abf2f0ad","repo":"spring-projects/spring-ai","slug":"async-prompt-method-must-not-declare-parameter-of","errorCode":null,"errorMessage":"Async prompt method must not declare parameter of type: {paramType.getName()}. Use McpAsyncServerExchange instead. Method: {this.method.getName()} in {this.method.getDeclaringClass().getName()}","messagePattern":"Async prompt method must not declare parameter of type: (.+?)\\. Use McpAsyncServerExchange instead\\. 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":55,"sourceCode":" *\n * This class provides a way to convert methods annotated with {@link McpPrompt} into\n * callback functions that can be used to handle prompt requests asynchronously. It\n * supports various method signatures and return types.\n *\n * @author Christian Tzolov\n */\npublic final class AsyncMcpPromptMethodCallback extends AbstractMcpPromptMethodCallback\n\t\timplements BiFunction<McpAsyncServerExchange, GetPromptRequest, Mono<GetPromptResult>> {\n\n\tprivate AsyncMcpPromptMethodCallback(Builder builder) {\n\t\tsuper(builder.method, builder.bean, builder.prompt);\n\t}\n\n\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}","sourceCodeStart":37,"sourceCodeEnd":73,"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#L37-L73","documentation":"AsyncMcpPromptMethodCallback.validateParamType rejects McpSyncServerExchange parameters in methods bound to an async (reactive) prompt callback. A synchronous exchange cannot be provided to an async handler, so registration fails immediately with this IllegalArgumentException, directing you to use McpAsyncServerExchange instead.","triggerScenarios":"Registering an @McpPrompt method on an async server (or processed by the async callback) with signature `myPrompt(McpSyncServerExchange exchange, ...)`. validateParameters calls validateParamType for every parameter and throws on the sync exchange type.","commonSituations":"Copying a sync handler to an async server without changing the exchange type; migrating a server from sync to async transport; shared utility prompt classes used by both sync and async servers.","solutions":["Replace McpSyncServerExchange with McpAsyncServerExchange in the async method signature.","If the method truly needs synchronous semantics, register it with the sync callback/server variant (McpSyncServer) instead.","Use McpTransportContext if you only need request metadata/transport context, not the full exchange."],"exampleFix":"// before\n@McpPrompt(name = \"explain\")\npublic Mono<String> explain(McpSyncServerExchange exchange, @McpArg String topic) { ... }\n// after\n@McpPrompt(name = \"explain\")\npublic Mono<String> explain(McpAsyncServerExchange exchange, @McpArg String topic) { ... }","handlingStrategy":"validation","validationCode":"for (Parameter p : method.getParameters()) {\n    if (isAsyncHandler && McpSyncServerExchange.class.isAssignableFrom(p.getType())) {\n        throw new IllegalStateException(\"Async method uses McpSyncServerExchange: \" + method);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pair async methods (Mono/Flux returns) with McpAsyncServerExchange, sync methods with McpSyncServerExchange.","Keep separate bean sets for sync and async servers instead of sharing handler classes.","Validate handler signatures in a context-loading test."],"tags":["mcp","async","exchange-type","method-signature"],"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"}