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: ${paramType}. Use McpTransportContext instead. Method: ${method} in ${declaringClass}

What it means

In a stateless Streamable-Http MCP server, prompt methods cannot take McpSyncServerExchange or McpAsyncServerExchange parameters because those objects carry session state that does not exist in stateless mode. The framework's validateParamType rejects such parameters and tells you to use McpTransportContext instead, which carries per-request transport metadata without a session.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java:57

 * callback functions that can be used to handle prompt requests in stateless
 * environments. It supports various method signatures and return types.
 *
 * @author Christian Tzolov
 */
public final class SyncStatelessMcpPromptMethodCallback extends AbstractMcpPromptMethodCallback
		implements BiFunction<McpTransportContext, GetPromptRequest, GetPromptResult> {

	private SyncStatelessMcpPromptMethodCallback(Builder builder) {
		super(builder.method, builder.bean, builder.prompt);
	}

	@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 the McpSyncServerExchange/McpAsyncServerExchange parameter with McpTransportContext.
  2. Update the method body to use transportContext instead of exchange methods (e.g. session-based lookups).
  3. If session state is genuinely required, run the server in stateful mode instead of stateless Streamable-Http mode.

Example fix

// before
@McpPrompt(description = "report")
public String report(McpSyncServerExchange exchange, String period) {
    return exchange.transportContext().toString() + period;
}

// after
@McpPrompt(description = "report")
public String report(McpTransportContext transportContext, String period) {
    return transportContext.toString() + period;
}
Defensive patterns

Strategy: validation

Validate before calling

static boolean statelessPromptParamsValid(Method m) {
    return java.util.Arrays.stream(m.getParameterTypes())
        .noneMatch(t -> McpSyncServerExchange.class.isAssignableFrom(t)
            || McpAsyncServerExchange.class.isAssignableFrom(t));
}
// assert statelessPromptParamsValid(promptMethod) when server is stateless;

Try / catch

try {
    registerAnnotatedPrompts(statelessServer);
} catch (IllegalArgumentException e) {
    log.error("Stateless prompt method declares exchange param: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Declaring a @McpPrompt method in a stateless Streamable-Http server with a parameter of type McpSyncServerExchange or McpAsyncServerExchange; validateParamType checks isAssignableFrom on each parameter and throws IllegalArgumentException.

Common situations: Migrating an existing stateful server's annotated methods to a stateless deployment; copy-pasting prompt methods between stateful and stateless server configurations; following examples written for the stateful transport.

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