{"record":{"id":"79165296b2502d9f","repo":"spring-projects/spring-ai","slug":"stateless-streamable-http-prompt-method-must-not-d","errorCode":null,"errorMessage":"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()}","messagePattern":"Stateless Streamable-Http prompt method must not declare parameter of type: (.+?)\\. Use McpTransportContext instead\\. Method: (.+?) in (.+?)","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncStatelessMcpPromptMethodCallback.java","lineNumber":59,"sourceCode":" * callback functions that can be used to handle prompt requests asynchronously in\n * stateless environments. It supports various method signatures and return types.\n *\n * @author Christian Tzolov\n */\npublic final class AsyncStatelessMcpPromptMethodCallback extends AbstractMcpPromptMethodCallback\n\t\timplements BiFunction<McpTransportContext, GetPromptRequest, Mono<GetPromptResult>> {\n\n\tprivate AsyncStatelessMcpPromptMethodCallback(Builder builder) {\n\t\tsuper(builder.method, builder.bean, builder.prompt);\n\t}\n\n\t@Override\n\tprotected void validateParamType(Class<?> paramType) {\n\n\t\tif (McpSyncServerExchange.class.isAssignableFrom(paramType)\n\t\t\t\t|| McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Stateless Streamable-Http prompt method must not declare parameter of type: \" + paramType.getName()\n\t\t\t\t\t\t\t+ \". Use McpTransportContext instead.\" + \" Method: \" + this.method.getName() + \" in \"\n\t\t\t\t\t\t\t+ this.method.getDeclaringClass().getName());\n\t\t}\n\t}\n\n\t@Override\n\tprotected Object assignExchangeType(Class<?> paramType, Object exchange) {\n\n\t\tif (McpTransportContext.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpTransportContext transportContext) {\n\t\t\t\treturn transportContext;\n\t\t\t}\n\t\t\telse if (exchange instanceof McpSyncServerExchange syncServerExchange) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unsupported Sync exchange type: \"\n\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Sync method: \" + method.getName() + \" in \"\n\t\t\t\t\t\t+ method.getDeclaringClass().getName());\n","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/AsyncStatelessMcpPromptMethodCallback.java#L41-L77","documentation":"In stateless Streamable-HTTP mode there is no per-session server exchange, so an async stateless prompt method must not take McpSyncServerExchange or McpAsyncServerExchange parameters. The callback rejects such parameters at validation time and directs you to use the session-less McpTransportContext instead.","triggerScenarios":"Declaring an @McpPrompt method with a McpSyncServerExchange or McpAsyncServerExchange parameter while the server runs in stateless Streamable-Http mode and is wired via AsyncStatelessMcpPromptMethodCallback.","commonSituations":"Switching a stateful MCP server to stateless Streamable-Http without updating method signatures; copying stateful examples into a stateless configuration.","solutions":["Replace the McpSyncServerExchange/McpAsyncServerExchange parameter with McpTransportContext","Remove the exchange parameter entirely if no transport context is needed","Keep the stateful server mode if the method genuinely needs the exchange"],"exampleFix":"// before\npublic Mono<GetPromptResult> prompt(McpAsyncServerExchange exchange, Request req) {...}\n// after\npublic Mono<GetPromptResult> prompt(McpTransportContext context, Request req) {...}","handlingStrategy":"validation","validationCode":"boolean statelessSafe(Method m) { return Arrays.stream(m.getParameterTypes()).noneMatch(t -> McpSyncServerExchange.class.isAssignableFrom(t) || McpAsyncServerExchange.class.isAssignableFrom(t)); }","typeGuard":null,"tryCatchPattern":"try { registerPrompts(bean); } catch (IllegalArgumentException e) { log.error(\"Stateless method misconfigured: {}\", e.getMessage()); }","preventionTips":["Prefer McpTransportContext parameters in stateless deployments","Audit @McpPrompt signatures when switching stateful to stateless mode","Document that stateless mode forbids exchange parameters"],"tags":["mcp","stateless","streamable-http","prompt-method","parameter-type"],"backgroundTag":"invalid-argument-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}