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 {class}

What it means

Thrown when a @McpComplete-annotated method has a reactive (async) return type (e.g. Mono/Flux) but declares an McpSyncRequestContext parameter. Spring AI MCP requires the context type to match the method's sync/async style: reactive completions must use McpAsyncRequestContext. The mismatch is detected during builder validation via validateParameters().

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AbstractMcpCompleteMethodCallback.java:206

			}

			// 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(
							"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;
			}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Replace the McpSyncRequestContext parameter with McpAsyncRequestContext
  2. Or change the return type to a non-reactive type (e.g. String or CompleteResult) to keep McpSyncRequestContext
  3. Rebuild/re-register the completion callback after fixing the signature

Example fix

// before
@McpComplete(prompt="language")
public Mono<CompleteResult> complete(McpSyncRequestContext ctx, CompleteRequest req) { ... }
// after
@McpComplete(prompt="language")
public Mono<CompleteResult> complete(McpAsyncRequestContext ctx, CompleteRequest req) { ... }
Defensive patterns

Strategy: validation

Validate before calling

boolean isReactive = Mono.class.isAssignableFrom(m.getReturnType()) || Flux.class.isAssignableFrom(m.getReturnType());
boolean hasSyncCtx = Arrays.stream(m.getParameterTypes()).anyMatch(McpSyncRequestContext.class::isAssignableFrom);
boolean hasAsyncCtx = Arrays.stream(m.getParameterTypes()).anyMatch(McpAsyncRequestContext.class::isAssignableFrom);
if (isReactive && hasSyncCtx || !isReactive && hasAsyncCtx) throw new IllegalStateException("Context type must match return type: " + m);

Type guard

boolean contextMatchesReturn(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;
}

Try / catch

try {
    builder.method(m).bean(bean).build();
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("McpAsyncRequestContext")) { /* fix signature: swap context param type */ }
    throw e;
}

Prevention

When it happens

Trigger: Declaring a completion method returning Mono<CompleteResult>/Flux while taking McpSyncRequestContext as a parameter; registering such a method through the AbstractMcpCompleteMethodCallback builder.

Common situations: Developers copy a sync completion example and change only the return type to Mono/Flux (or vice versa after a refactor); migrating code between sync and async MCP server setups and forgetting to swap the context parameter type.

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/893bf37c5019c517. Report an issue: GitHub.