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

  1. Switch the parameter to McpAsyncRequestContext so it matches the reactive return type.
  2. Or keep McpSyncRequestContext and change the return type to a plain (non-reactive) type like String.
  3. 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

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


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)