{"record":{"id":"afd53ae7eb463902","repo":"spring-projects/spring-ai","slug":"unsupported-exchange-type-exchangetype-for-met-afd53a","errorCode":null,"errorMessage":"Unsupported exchange type: ${exchangeType} for method: ${method} in ${declaringClass}","messagePattern":"Unsupported exchange type: (.+?) for method: (.+?) in (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java","lineNumber":97,"sourceCode":"\t\t\t\t\t\t+ syncServerExchange.getClass().getName() + \" for Async 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\t\telse if (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tif (exchange instanceof McpAsyncServerExchange asyncServerExchange) {\n\t\t\t\treturn asyncServerExchange;\n\t\t\t}\n\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Unsupported exchange type: \" + (exchange != null ? exchange.getClass().getName() : \"null\")\n\t\t\t\t\t\t\t+ \" for Async method: \" + method.getName() + \" in \" + method.getDeclaringClass().getName());\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 exchange and request.\n\t * <p>\n\t * This method extracts URI variable values from the request URI, builds the arguments\n\t * for the method call, invokes the method, and converts the result to a\n\t * ReadResourceResult.\n\t * @param exchange The server exchange, may be null if the method doesn't require it\n\t * @param request The resource request, must not be null\n\t * @return A Mono that emits the resource result\n\t * @throws McpError if there is an error invoking the resource method\n\t * @throws IllegalArgumentException if the request is null or if URI variable\n\t * extraction fails\n\t */\n\t@Override","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AsyncMcpResourceMethodCallback.java#L79-L115","documentation":"The final fall-through throw in AsyncMcpResourceMethodCallback.assignExchangeType: the method's exchange parameter is not McpTransportContext, McpSyncServerExchange, or McpAsyncServerExchange, so the callback cannot map the runtime exchange to it at all. Only these three exchange types are supported for async resource methods.","triggerScenarios":"Declaring an @McpResource method with an unsupported first/exchange parameter type (e.g., a custom wrapper, String, HttpServletRequest) and then invoking the async callback.","commonSituations":"Developers assuming arbitrary context objects can be injected into resource methods; refactoring that replaced McpTransportContext with a custom context class.","solutions":["Change the exchange parameter to one of the supported types: McpTransportContext, McpAsyncServerExchange, or McpSyncServerExchange.","Pass extra context via the request object or method-level fields, not via the exchange parameter slot.","Review the MCP annotations documentation for the exact list of injectable parameter types."],"exampleFix":"// before\n@McpResource(uri = \"x://{id}\")\npublic ReadResourceResult read(MyCustomContext ctx, String id) { ... }\n\n// after\n@McpResource(uri = \"x://{id}\")\npublic ReadResourceResult read(McpTransportContext ctx, String id) { ... }","handlingStrategy":"validation","validationCode":"Set<Class<?>> ALLOWED = Set.of(McpTransportContext.class, McpAsyncServerExchange.class, McpSyncServerExchange.class);\nArrays.stream(method.getParameterTypes())\n    .filter(ALLOWED::contains)\n    .skip(1) // more than one exchange-like param is suspicious\n    .findAny()\n    .ifPresent(p -> { throw new IllegalStateException(\"Multiple/unsupported exchange params: \" + p); });","typeGuard":"static boolean isSupportedExchangeType(Class<?> p) {\n    return McpTransportContext.class == p || McpAsyncServerExchange.class == p || McpSyncServerExchange.class == p;\n}","tryCatchPattern":"try {\n    asyncMcpServer.annotate(provider);\n} catch (IllegalArgumentException e) {\n    throw new IllegalStateException(\"Fix exchange parameter type in \" + provider.getClass(), e);\n}","preventionTips":["Only use the three documented exchange parameter types","Do not invent custom context wrappers for the exchange slot","Check the MCP annotations javadoc when adding new handler signatures"],"tags":["mcp","java","async","unsupported-parameter"],"backgroundTag":"unsupported-operation","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"}