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

  1. Change the parameter to McpSyncRequestContext if the method returns a plain (non-reactive) type
  2. Alternatively change the return type to a reactive type (e.g. Mono<String>) to match McpAsyncRequestContext
  3. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/5ba0b7b8fc57b86c. Report an issue: GitHub.