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
- Replace the McpSyncServerExchange/McpAsyncServerExchange parameter with McpTransportContext.
- Update the method body to use transportContext instead of exchange methods (e.g. session-based lookups).
- 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
- In stateless Streamable-Http mode, always use McpTransportContext instead of server exchange parameters.
- Keep separate method sets for stateful and stateless deployments.
- Add a startup smoke test that registers all annotated methods for the target transport mode.
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
- Stateless Streamable-Http prompt method must not declare par
- Method must return either GetPromptResult, List<PromptMessag
- Stateless Streamable-Http prompt method must not declare par
- Unsupported Sync exchange type: ${exchangeType} for Sync met
- Unsupported exchange type: ${exchangeType} for method: ${met
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/d3bf70fb638d4d88.
Report an issue: GitHub.