{"record":{"id":"d3bf70fb638d4d88","repo":"spring-projects/spring-ai","slug":"stateless-streamable-http-prompt-method-must-not-d-d3bf70","errorCode":null,"errorMessage":"Stateless Streamable-Http prompt method must not declare parameter of type: ${paramType}. Use McpTransportContext instead. Method: ${method} in ${declaringClass}","messagePattern":"Stateless Streamable-Http prompt method must not declare parameter of type: (.+?)\\. Use McpTransportContext instead\\. Method: (.+?) in (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java","lineNumber":57,"sourceCode":" * callback functions that can be used to handle prompt requests in stateless\n * environments. It supports various method signatures and return types.\n *\n * @author Christian Tzolov\n */\npublic final class SyncStatelessMcpPromptMethodCallback extends AbstractMcpPromptMethodCallback\n\t\timplements BiFunction<McpTransportContext, GetPromptRequest, GetPromptResult> {\n\n\tprivate SyncStatelessMcpPromptMethodCallback(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\treturn syncServerExchange.transportContext();\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unsupported Async exchange type: \"","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncStatelessMcpPromptMethodCallback.java#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@McpPrompt(description = \"report\")\npublic String report(McpSyncServerExchange exchange, String period) {\n    return exchange.transportContext().toString() + period;\n}\n\n// after\n@McpPrompt(description = \"report\")\npublic String report(McpTransportContext transportContext, String period) {\n    return transportContext.toString() + period;\n}","handlingStrategy":"validation","validationCode":"static boolean statelessPromptParamsValid(Method m) {\n    return java.util.Arrays.stream(m.getParameterTypes())\n        .noneMatch(t -> McpSyncServerExchange.class.isAssignableFrom(t)\n            || McpAsyncServerExchange.class.isAssignableFrom(t));\n}\n// assert statelessPromptParamsValid(promptMethod) when server is stateless;","typeGuard":null,"tryCatchPattern":"try {\n    registerAnnotatedPrompts(statelessServer);\n} catch (IllegalArgumentException e) {\n    log.error(\"Stateless prompt method declares exchange param: {}\", e.getMessage());\n}","preventionTips":["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."],"tags":["mcp","java","stateless","streamable-http","invalid-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"}