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

  1. Replace the McpAsyncRequestContext parameter with McpSyncRequestContext
  2. Or make the return type reactive (Mono/Flux) to keep McpAsyncRequestContext
  3. 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

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


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