spring-projects/spring-ai · error · IllegalArgumentException
Sync complete methods should use McpSyncRequestContext inste
Error message
Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: {method} in {class} What it means
Thrown when a @McpComplete method has a non-reactive (sync) return type but declares an McpAsyncRequestContext parameter. Sync completions must use McpSyncRequestContext; the async context is reserved for reactive return types (Mono/Flux).
Source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AbstractMcpCompleteMethodCallback.java:219
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(
"Async complete methods should use McpAsyncRequestContext instead of McpSyncRequestContext 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(
"Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasRequestContextParam = true;
}
else if (McpTransportContext.class.isAssignableFrom(paramType)) {
if (hasTransportContext) {
throw new IllegalArgumentException("Method cannot have more than one transport context parameter: "
+ method.getName() + " in " + method.getDeclaringClass().getName());
}
hasTransportContext = true;
}
else if (isExchangeType(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
- Replace the McpAsyncRequestContext parameter with McpSyncRequestContext
- Or make the return type reactive (Mono/Flux) to keep McpAsyncRequestContext
- Rebuild and re-register the callback
Example fix
// before
@McpComplete(uri="completion://{lang}")
public String complete(McpAsyncRequestContext ctx, CompleteRequest req) { ... }
// after
@McpComplete(uri="completion://{lang}")
public String complete(McpSyncRequestContext ctx, CompleteRequest req) { ... } Defensive patterns
Strategy: validation
Validate before calling
boolean reactive = Mono.class.isAssignableFrom(m.getReturnType()) || Flux.class.isAssignableFrom(m.getReturnType());
if (!reactive && Arrays.stream(m.getParameterTypes()).anyMatch(McpAsyncRequestContext.class::isAssignableFrom))
throw new IllegalStateException("Sync method must use McpSyncRequestContext: " + m); Type guard
boolean syncMethodUsesSyncContext(Method m) {
boolean reactive = Mono.class.isAssignableFrom(m.getReturnType()) || Flux.class.isAssignableFrom(m.getReturnType());
boolean asyncCtx = Arrays.stream(m.getParameterTypes()).anyMatch(McpAsyncRequestContext.class::isAssignableFrom);
return !reactive ? !asyncCtx : true;
} Try / catch
try {
callbackBuilder.build();
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Sync complete methods should use McpSyncRequestContext")) { /* swap to McpSyncRequestContext */ }
throw e;
} Prevention
- Mirror rule: async context only with Mono/Flux returns, sync context otherwise
- When converting Mono<T> returns back to plain types, always update the context parameter too
- Add a startup test that registers every completion method
When it happens
Trigger: Declaring a completion method returning String/CompleteResult (not Mono/Flux) while taking McpAsyncRequestContext as a parameter.
Common situations: Copying an async completion example into a sync server setup; switching the return type from Mono to a plain type during a refactor without changing the context parameter.
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 complete methods should use McpAsyncRequestContext ins
- Method cannot have more than one transport context parameter
- Method cannot have more than one exchange parameter: {method
- Method cannot have more than one CompleteRequest parameter:
- Method cannot have more than one CompleteArgument parameter:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/618824c5f724f8c1.
Report an issue: GitHub.