spring-projects/spring-ai · error · IllegalArgumentException

Stateless Streamable-Http prompt method must not declare par

Error message

Stateless Streamable-Http prompt method must not declare parameter of type: {paramTypeName}. Use McpTransportContext instead. Method: {methodName} in {className}

What it means

SyncStatelessMcpResourceMethodCallback.validateParamType rejects resource methods used in stateless Streamable-HTTP mode that declare McpSyncServerExchange or McpAsyncServerExchange parameters. Stateless handling has no server session/exchange; only McpTransportContext is available, so the method fails registration with IllegalArgumentException.

Source

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

 * @author Vadzim Shurmialiou
 * @author Craig Walls
 */
public final class SyncStatelessMcpResourceMethodCallback extends AbstractMcpResourceMethodCallback
		implements BiFunction<McpTransportContext, ReadResourceRequest, ReadResourceResult> {

	private SyncStatelessMcpResourceMethodCallback(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 (McpSyncServerExchange.class.isAssignableFrom(paramType)
				|| McpAsyncServerExchange.class.isAssignableFrom(paramType)) {

			throw new IllegalArgumentException(
					"Stateless Streamable-Http prompt method must not declare parameter of type: " + paramType.getName()
							+ ". Use McpTransportContext 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: "

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Replace McpSyncServerExchange/McpAsyncServerExchange parameters with McpTransportContext.
  2. Remove the exchange parameter entirely if the handler does not need transport metadata.
  3. If exchange state is genuinely required, run the server in stateful mode instead of stateless Streamable-HTTP.

Example fix

// before
@McpResource(uri = "data://{id}")
public String get(McpSyncServerExchange exchange, String id) { ... }

// after
@McpResource(uri = "data://{id}")
public String get(McpTransportContext context, String id) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

void checkStatelessParams(Class<?>[] paramTypes) {
    for (Class<?> p : paramTypes) {
        if (McpSyncServerExchange.class.isAssignableFrom(p) || McpAsyncServerExchange.class.isAssignableFrom(p))
            throw new IllegalArgumentException("Stateless methods must use McpTransportContext, not " + p.getName());
    }
}

Type guard

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

Try / catch

try {
    statelessServer.addResource(resource, callback);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Use McpTransportContext instead")) {
        log.error("Stateless Streamable-HTTP handler cannot take server exchange; switch to McpTransportContext", e);
    } throw e;
}

Prevention

When it happens

Trigger: Configuring an MCP server in stateless Streamable-HTTP mode while an @McpResource method signature includes either exchange type. Validation fires when the method callback is created.

Common situations: Reusing the same annotated resource class across stateful and stateless server setups, or migrating a stateful server to stateless mode for horizontal scaling without removing exchange parameters.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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