spring-projects/spring-ai · error · java.lang.IllegalArgumentException
Sync complete methods should use McpSyncRequestContext…
Error message
Sync complete methods should use McpSyncRequestContext instead of McpAsyncRequestContext parameter: {method.getName()} in {method.getDeclaringClass().getName()} What it means
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.
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.
Example fix
// before
@McpPrompt(name = "p")
public Mono<String> complete(String q, McpSyncRequestContext ctx) { ... }
// after
@McpPrompt(name = "p")
public Mono<String> complete(String q, McpAsyncRequestContext ctx) { ... } Defensive patterns
Strategy: validation
Validate before calling
boolean syncCtx = Arrays.stream(method.getParameterTypes())
.anyMatch(McpSyncRequestContext.class::isAssignableFrom);
boolean reactive = Mono.class.isAssignableFrom(method.getReturnType())
|| Flux.class.isAssignableFrom(method.getReturnType());
if (syncCtx && reactive) throw new IllegalStateException("Use McpAsyncRequestContext with reactive return types"); Try / catch
try { registry.register(bean); } catch (IllegalArgumentException e) { log.error("Sync/async mismatch: {}", e.getMessage()); } Prevention
- 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
When it happens
Trigger: Declaring an @McpPrompt method like public Mono<String> complete(String q, McpSyncRequestContext ctx) — a sync context with Mono/Flux return type.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Async complete methods should use McpAsyncRequestContext…
- Async prompt method must not declare parameter of type
- Currently only methods with a single ElicitRequest…
- Method cannot have more than one McpMeta parameter
- Method cannot have more than one @McpProgressToken parameter
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/cac2e0d032f726ba.
Report an issue: GitHub.
Appendix: source
Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AbstractMcpPromptMethodCallback.java:153
}
// Skip McpMeta parameters from validation
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;
continue;
}
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 (isSupportedExchangeOrContextType(paramType)) {View on GitHub (pinned to 98a7beda4f)