spring-projects/spring-ai · error · IllegalArgumentException

Sync prompt method must not declare parameter of type: {para

Error message

Sync prompt method must not declare parameter of type: {paramTypeName}. Use McpSyncServerExchange instead. Method: {methodName} in {className}

What it means

SyncMcpResourceMethodCallback.validateParamType rejects @McpResource methods declared on a synchronous server that take an McpAsyncServerExchange parameter. Sync handlers receive McpSyncServerExchange; the async exchange type is incompatible with the sync execution model, so the library fails at method registration with IllegalArgumentException.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java:61

 * @author Christian Tzolov
 * @author Alexandros Pappas
 * @author Vadzim Shurmialiou
 * @author Craig Walls
 */
public final class SyncMcpResourceMethodCallback extends AbstractMcpResourceMethodCallback
		implements BiFunction<McpSyncServerExchange, ReadResourceRequest, ReadResourceResult> {

	private SyncMcpResourceMethodCallback(Builder builder) {
		super(builder.method, builder.bean, builder.uri, builder.name, builder.description, builder.mimeType,
				builder.resultConverter, builder.uriTemplateManagerFactory, builder.contentType, builder.meta);
		this.validateMethod(this.method);
	}

	@Override
	protected void validateParamType(Class<?> paramType) {

		if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {
			throw new IllegalArgumentException("Sync prompt method must not declare parameter of type: "
					+ paramType.getName() + ". Use McpSyncServerExchange instead." + " Method: " + this.method.getName()
					+ " in " + this.method.getDeclaringClass().getName());
		}
	}

	@Override
	protected Object assignExchangeType(Class<?> paramType, Object exchange) {

		if (McpTransportContext.class.isAssignableFrom(paramType)) {
			if (exchange instanceof McpTransportContext transportContext) {
				return transportContext;
			}
			else if (exchange instanceof McpSyncServerExchange syncServerExchange) {
				return syncServerExchange.transportContext();
			}
			else if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {
				throw new IllegalArgumentException("Unsupported Async exchange type: "
						+ asyncServerExchange.getClass().getName() + " for Sync method: " + method.getName() + " in "

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Change the parameter type from McpAsyncServerExchange to McpSyncServerExchange.
  2. If the handler needs no exchange, remove the parameter entirely.
  3. If async semantics are required, register the method on an McpAsyncServer instead of a sync one.

Example fix

// before
@McpResource(uri = "file://{path}")
public String read(McpAsyncServerExchange exchange, String path) { ... }

// after
@McpResource(uri = "file://{path}")
public String read(McpSyncServerExchange exchange, String path) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

void checkSyncResourceParams(Class<?>[] paramTypes) {
    for (Class<?> p : paramTypes) {
        if (McpAsyncServerExchange.class.isAssignableFrom(p))
            throw new IllegalArgumentException("Sync resource method must use McpSyncServerExchange, not " + p.getName());
    }
}

Type guard

static boolean validSyncParam(Class<?> p) {
    return !McpAsyncServerExchange.class.isAssignableFrom(p);
}

Try / catch

try {
    server.addResource(resource, handler);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Use McpSyncServerExchange instead")) {
        log.error("Fix resource method signature: replace McpAsyncServerExchange with McpSyncServerExchange", e);
    } else { throw e; }
}

Prevention

When it happens

Trigger: A resource method on a McpSyncServer declares a parameter of type McpAsyncServerExchange (or a subtype). Validation runs when the callback is created/registered for the method.

Common situations: Copy-pasting a method between an async server and a sync server configuration, or migrating an async server to sync (e.g., to simplify blocking code) without updating exchange parameter types.

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/342a3abedccd26c1. Report an issue: GitHub.