{"record":{"id":"a835256a083fe79e","repo":"spring-projects/spring-ai","slug":"unsupported-sync-exchange-type-exchangetype-fo","errorCode":null,"errorMessage":"Unsupported Sync exchange type: ${exchangeType} for Sync method: ${method} in ${declaringClass}","messagePattern":"Unsupported Sync exchange type: (.+?) for Sync method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncStatelessMcpResourceMethodCallback.java","lineNumber":81,"sourceCode":"\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\n\t\t\t}\n\t\t\telse if (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange.transportContext();\n\t\t\t}\n\t\t}\n\n\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t+ \" for method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t}\n\n\t/**\n\t * Apply the callback to the given context and request.\n\t * <p>\n\t * This method extracts URI variable values from the request URI, builds the arguments","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncStatelessMcpResourceMethodCallback.java#L63-L99","documentation":"Thrown by AsyncStatelessMcpResourceMethodCallback.assignExchangeType when the stateless method expects a McpTransportContext but the object supplied as the exchange is a McpSyncServerExchange. A sync session exchange is incompatible with the stateless Streamable-Http path, which must operate without a server session.","triggerScenarios":"Dispatching a resource call through the stateless Streamable-Http callback while passing a McpSyncServerExchange as the exchange argument (method signature declares McpTransportContext).","commonSituations":"Calling a stateless-registered provider from a session-based (sync) server; mixing spec builders so the wrong dispatcher invokes the method.","solutions":["Invoke the resource through the stateless Streamable-Http server path so a McpTransportContext is supplied.","Detach the provider from the session-based sync server registration.","If sync session semantics are truly needed, do not use the stateless callback; register with a stateful server."],"exampleFix":"// before\nsyncServer.dispatch(provider); // passes McpSyncServerExchange\n\n// after\nstatelessStreamableHttpServer.dispatch(provider); // passes McpTransportContext","handlingStrategy":"try-catch","validationCode":"if (exchange instanceof McpSyncServerExchange) {\n    throw new IllegalStateException(\"Stateless callback must not receive a sync session exchange\");\n}","typeGuard":"static boolean isSync(Object ex) { return ex instanceof McpSyncServerExchange; }","tryCatchPattern":"try {\n    return callback.call(ctx, request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Unsupported Sync exchange type\")) {\n        throw new IllegalStateException(\"Wrong dispatcher for stateless provider\", e);\n    }\n    throw e;\n}","preventionTips":["Keep stateless providers registered only on stateless Streamable-Http servers","Do not share provider instances between session-based and stateless servers","Verify dispatcher wiring in integration tests"],"tags":["mcp","java","stateless","exchange-mismatch"],"backgroundTag":"type-mismatch","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"}