{"record":{"id":"cac2e0d032f726ba","repo":"spring-projects/spring-ai","slug":"sync-complete-methods-should-use-mcpsyncrequestcon-cac2e0","errorCode":null,"errorMessage":"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: {method.getName()} in {method.getDeclaringClass().getName()}","messagePattern":"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: (.+?) 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/AbstractMcpPromptMethodCallback.java","lineNumber":153,"sourceCode":"\t\t\t}\n\n\t\t\t// Skip McpMeta parameters from validation\n\t\t\tif (McpMeta.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasMetaParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one McpMeta parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasMetaParam = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (McpSyncRequestContext.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasRequestContextParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one request context parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\tif (McpPredicates.isReactiveReturnType.test(method)) {\n\t\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\t\"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: \"\n\t\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasRequestContextParam = true;\n\t\t\t}\n\t\t\telse if (McpAsyncRequestContext.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasRequestContextParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one request context parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\tif (McpPredicates.isNotReactiveReturnType.test(method)) {\n\t\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\t\"Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext parameter: \"\n\t\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasRequestContextParam = true;\n\t\t\t}\n\t\t\telse if (isSupportedExchangeOrContextType(paramType)) {","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AbstractMcpPromptMethodCallback.java#L135-L171","documentation":"When an @McpPrompt method takes an McpSyncRequestContext parameter, validateParameters() also requires a non-reactive return type (checked via McpPredicates.isReactiveReturnType). A sync context paired with a reactive return type (e.g. Mono/Flux) is a mismatch, since synchronous context APIs cannot drive reactive completion, and throws IllegalArgumentException.","triggerScenarios":"Declaring an @McpPrompt method like public Mono<String> complete(String q, McpSyncRequestContext ctx) — a sync context with Mono/Flux return type.","commonSituations":"Migrating a method to reactive by changing only the return type while keeping McpSyncRequestContext; copying a reactive example and pairing it with the sync context; mixing patterns from sync and async samples in documentation.","solutions":["Switch the parameter to McpAsyncRequestContext so it matches the reactive return type.","Or keep McpSyncRequestContext and change the return type to a plain (non-reactive) type like String.","Align sync/async: sync context + plain return, or async context + Mono/Flux return."],"exampleFix":"// before\n@McpPrompt(name = \"p\")\npublic Mono<String> complete(String q, McpSyncRequestContext ctx) { ... }\n// after\n@McpPrompt(name = \"p\")\npublic Mono<String> complete(String q, McpAsyncRequestContext ctx) { ... }","handlingStrategy":"validation","validationCode":"boolean syncCtx = Arrays.stream(method.getParameterTypes())\n    .anyMatch(McpSyncRequestContext.class::isAssignableFrom);\nboolean reactive = Mono.class.isAssignableFrom(method.getReturnType())\n    || Flux.class.isAssignableFrom(method.getReturnType());\nif (syncCtx && reactive) throw new IllegalStateException(\"Use McpAsyncRequestContext with reactive return types\");","typeGuard":null,"tryCatchPattern":"try { registry.register(bean); } catch (IllegalArgumentException e) { log.error(\"Sync/async mismatch: {}\", e.getMessage()); }","preventionTips":["Pair McpSyncRequestContext only with plain return types","When adding Mono/Flux returns, switch the context to McpAsyncRequestContext in the same change","Run callback registration at startup so mismatches fail before serving requests"],"tags":["java","mcp","reactive","method-signature"],"backgroundTag":"invalid-argument-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}