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
- Change the parameter type from McpAsyncServerExchange to McpSyncServerExchange.
- If the handler needs no exchange, remove the parameter entirely.
- 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
- Keep separate handler classes for sync and async servers to avoid copy-paste of exchange types.
- Grep codebase for 'McpAsyncServerExchange' inside sync server configuration packages during review.
- Add an architecture test (e.g., ArchUnit) forbidding McpAsyncServerExchange parameters in sync resource classes.
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
- Unsupported Async exchange type: {exchangeClassName} for Syn
- Method must return either ReadResourceResult, List<ResourceC
- Stateless Streamable-Http prompt method must not declare par
- Unsupported Async exchange type: {exchangeClassName} for Syn
- Async complete methods should use McpAsyncRequestContext ins
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/342a3abedccd26c1.
Report an issue: GitHub.