spring-projects/spring-ai · error · IllegalArgumentException
Async complete methods should use McpAsyncRequestContext ins
Error message
Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext parameter: ${method} in ${declaringClass} What it means
Thrown when a resource method takes an McpAsyncRequestContext parameter but its return type is not reactive (i.e. not a Mono/Flux-style reactive return). The async context only makes sense for async/reactive completion; a non-reactive return type indicates the developer should use McpSyncRequestContext. The framework rejects the mismatch at method-registration time.
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java:228
if (McpSyncRequestContext.class.isAssignableFrom(paramType)) {
if (hasRequestContextParam) {
throw new IllegalArgumentException("Method cannot have more than one request context parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
if (McpPredicates.isReactiveReturnType.test(method)) {
throw new IllegalArgumentException(
"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (McpAsyncRequestContext.class.isAssignableFrom(paramType)) {
if (hasRequestContextParam) {
throw new IllegalArgumentException("Method cannot have more than one request context parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
if (McpPredicates.isNotReactiveReturnType.test(method)) {
throw new IllegalArgumentException(
"Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (McpMeta.class.isAssignableFrom(paramType)) {
if (hasMetaParam) {
throw new IllegalArgumentException("Method cannot have more than one McpMeta parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasMetaParam = true;
}
else if (isExchangeOrContextType(paramType)) {
if (hasExchangeParam) {
throw new IllegalArgumentException("Method cannot have more than one exchange parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasExchangeParam = true;View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the parameter to McpSyncRequestContext if the method returns a plain (non-reactive) type
- Alternatively change the return type to a reactive type (e.g. Mono<String>) to match McpAsyncRequestContext
- Keep exactly one context parameter and align it with the return type
Example fix
// before
@McpResource(uri = "doc://read")
public String read(McpAsyncRequestContext ctx) { return "data"; }
// after
@McpResource(uri = "doc://read")
public String read(McpSyncRequestContext ctx) { return "data"; } Defensive patterns
Strategy: validation
Validate before calling
boolean hasAsyncCtx = Arrays.stream(method.getParameterTypes())
.anyMatch(McpAsyncRequestContext.class::isAssignableFrom);
boolean reactive = Mono.class.isAssignableFrom(method.getReturnType())
|| Flux.class.isAssignableFrom(method.getReturnType());
if (hasAsyncCtx && !reactive) throw new IllegalStateException(method + " uses McpAsyncRequestContext with a non-reactive return type"); Type guard
static boolean isReactive(Method m) {
return Mono.class.isAssignableFrom(m.getReturnType()) || Flux.class.isAssignableFrom(m.getReturnType());
} Try / catch
try { provider.build(...); }
catch (IllegalArgumentException e) {
if (e.getMessage().contains("Async complete methods should use McpAsyncRequestContext")) { /* align param and return type */ }
else throw e;
} Prevention
- Pair McpAsyncRequestContext only with Mono/Flux return types
- Pair McpSyncRequestContext only with plain return types
- When converting a handler, change parameter and return type together
When it happens
Trigger: A @McpResource method (no URI variables) declares McpAsyncRequestContext while McpPredicates.isNotReactiveReturnType.test(method) is true — e.g. the method returns String/List/void instead of Mono/Flux.
Common situations: Converting a sync handler to async by changing only the parameter but not the return type; mixing blocking and reactive styles after a Spring AI version upgrade; copying an async example's signature onto a non-reactive method.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- ASYNC Providers don't support imperative (non-reactive) retu
- SYNC Providers don't support reactive return types. Skipping
- Async complete methods should use McpAsyncRequestContext ins
- Sync complete methods should use McpSyncRequestContext inste
- Mono return type must be Mono<Void>: {method.getName()} in {
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/5ba0b7b8fc57b86c.
Report an issue: GitHub.