{"record":{"id":"30cfd9cb4b16ff9b","repo":"spring-projects/spring-ai","slug":"stateless-tool-methods-do-not-support-mcpasyncrequ","errorCode":null,"errorMessage":"Stateless tool methods do not support McpAsyncRequestContext parameter.","messagePattern":"Stateless tool methods do not support McpAsyncRequestContext parameter\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/tool/AsyncStatelessMcpToolMethodCallback.java","lineNumber":70,"sourceCode":"\t * The {@code toolCallExceptionClass} argument is ignored: exception handling now\n\t * follows the {@code @Tool} contract based on the exception type. Will be removed in\n\t * 2.1.0.\n\t */\n\t@Deprecated\n\tpublic AsyncStatelessMcpToolMethodCallback(ReturnMode returnMode, java.lang.reflect.Method toolMethod,\n\t\t\tObject toolObject, Class<? extends Throwable> toolCallExceptionClass) {\n\t\tsuper(returnMode, toolMethod, toolObject, toolCallExceptionClass);\n\t}\n\n\t@Override\n\tprotected boolean isExchangeOrContextType(Class<?> paramType) {\n\t\treturn McpTransportContext.class.isAssignableFrom(paramType)\n\t\t\t\t|| McpAsyncRequestContext.class.isAssignableFrom(paramType);\n\t}\n\n\t@Override\n\tprotected McpAsyncRequestContext createRequestContext(McpTransportContext exchange, CallToolRequest request) {\n\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\"Stateless tool methods do not support McpAsyncRequestContext parameter.\");\n\t}\n\n\t@Override\n\tprotected McpTransportContext resolveTransportContext(McpTransportContext context) {\n\t\treturn context;\n\t}\n\n\t/**\n\t * Apply the callback to the given request.\n\t * <p>\n\t * This method builds the arguments for the method call, invokes the method, and\n\t * returns the result asynchronously.\n\t * @param mcpTransportContext The transport context\n\t * @param request The tool call request, must not be null\n\t * @return A Mono containing the result of the method invocation\n\t */\n\t@Override","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/tool/AsyncStatelessMcpToolMethodCallback.java#L52-L88","documentation":"AsyncStatelessMcpToolMethodCallback serves @McpTool methods registered on a stateless async client. Stateless callbacks have no per-conversation exchange, so McpAsyncRequestContext (which needs an active exchange) cannot be created; createRequestContext unconditionally throws UnsupportedOperationException if the method declares such a parameter. Only McpTransportContext parameters are supported.","triggerScenarios":"Declaring a @McpTool method like: @McpTool(name=\"x\") void tool(String arg, McpAsyncRequestContext ctx) on a stateless async tool callback, then invoking it — the framework reaches createRequestContext and throws.","commonSituations":"Developers copy an async tool method that used McpAsyncRequestContext from a stateful setup into a stateless one; migrating code between stateful and stateless MCP client configurations.","solutions":["Remove the McpAsyncRequestContext parameter from the tool method signature.","If exchange/context data is needed, use McpTransportContext as the parameter type instead, which stateless callbacks support.","If per-exchange context is genuinely required, register the tool with the stateful async callback (AsyncMcpToolMethodCallback) instead of the stateless one."],"exampleFix":"// before\n@McpTool(name = \"weather\")\nString weather(String city, McpAsyncRequestContext ctx) { ... }\n\n// after\n@McpTool(name = \"weather\")\nString weather(String city) { ... }","handlingStrategy":"validation","validationCode":"// Fail fast at startup if a stateless tool method declares an unsupported context parameter\nfor (Method m : bean.getClass().getDeclaredMethods()) {\n    if (m.isAnnotationPresent(McpTool.class)) {\n        for (Class<?> p : m.getParameterTypes()) {\n            if (McpAsyncRequestContext.class.isAssignableFrom(p)) {\n                throw new IllegalStateException(\"@McpTool \" + m.getName() + \" cannot take McpAsyncRequestContext in a stateless callback\");\n            }\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never declare McpAsyncRequestContext parameters in stateless tool methods.","Use McpTransportContext when a context parameter is needed.","Document stateless vs stateful callback constraints for your team."],"tags":["unsupported-operation","mcp","stateless","async"],"backgroundTag":"unsupported-operation","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}