{"record":{"id":"342a3abedccd26c1","repo":"spring-projects/spring-ai","slug":"sync-prompt-method-must-not-declare-parameter-of-t-342a3a","errorCode":null,"errorMessage":"Sync prompt method must not declare parameter of type: {paramTypeName}. Use McpSyncServerExchange instead. Method: {methodName} in {className}","messagePattern":"Sync prompt method must not declare parameter of type: (.+?)\\. Use McpSyncServerExchange instead\\. Method: (.+?) in (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java","lineNumber":61,"sourceCode":" * @author Christian Tzolov\n * @author Alexandros Pappas\n * @author Vadzim Shurmialiou\n * @author Craig Walls\n */\npublic final class SyncMcpResourceMethodCallback extends AbstractMcpResourceMethodCallback\n\t\timplements BiFunction<McpSyncServerExchange, ReadResourceRequest, ReadResourceResult> {\n\n\tprivate SyncMcpResourceMethodCallback(Builder builder) {\n\t\tsuper(builder.method, builder.bean, builder.uri, builder.name, builder.description, builder.mimeType,\n\t\t\t\tbuilder.resultConverter, builder.uriTemplateManagerFactory, builder.contentType, builder.meta);\n\t\tthis.validateMethod(this.method);\n\t}\n\n\t@Override\n\tprotected void validateParamType(Class<?> paramType) {\n\n\t\tif (McpAsyncServerExchange.class.isAssignableFrom(paramType)) {\n\t\t\tthrow new IllegalArgumentException(\"Sync prompt method must not declare parameter of type: \"\n\t\t\t\t\t+ paramType.getName() + \". Use McpSyncServerExchange instead.\" + \" Method: \" + this.method.getName()\n\t\t\t\t\t+ \" in \" + 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: \"\n\t\t\t\t\t\t+ asyncServerExchange.getClass().getName() + \" for Sync method: \" + method.getName() + \" in \"","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/SyncMcpResourceMethodCallback.java#L43-L79","documentation":"SyncMcpResourceMethodCallback.validateParamType rejects @McpResource methods declared on a synchronous server that take an McpAsyncServerExchange parameter. Sync handlers receive McpSyncServerExchange; the async exchange type is incompatible with the sync execution model, so the library fails at method registration with IllegalArgumentException.","triggerScenarios":"A resource method on a McpSyncServer declares a parameter of type McpAsyncServerExchange (or a subtype). Validation runs when the callback is created/registered for the method.","commonSituations":"Copy-pasting a method between an async server and a sync server configuration, or migrating an async server to sync (e.g., to simplify blocking code) without updating exchange parameter types.","solutions":["Change the parameter type from McpAsyncServerExchange to McpSyncServerExchange.","If the handler needs no exchange, remove the parameter entirely.","If async semantics are required, register the method on an McpAsyncServer instead of a sync one."],"exampleFix":"// before\n@McpResource(uri = \"file://{path}\")\npublic String read(McpAsyncServerExchange exchange, String path) { ... }\n\n// after\n@McpResource(uri = \"file://{path}\")\npublic String read(McpSyncServerExchange exchange, String path) { ... }","handlingStrategy":"type-guard","validationCode":"void checkSyncResourceParams(Class<?>[] paramTypes) {\n    for (Class<?> p : paramTypes) {\n        if (McpAsyncServerExchange.class.isAssignableFrom(p))\n            throw new IllegalArgumentException(\"Sync resource method must use McpSyncServerExchange, not \" + p.getName());\n    }\n}","typeGuard":"static boolean validSyncParam(Class<?> p) {\n    return !McpAsyncServerExchange.class.isAssignableFrom(p);\n}","tryCatchPattern":"try {\n    server.addResource(resource, handler);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Use McpSyncServerExchange instead\")) {\n        log.error(\"Fix resource method signature: replace McpAsyncServerExchange with McpSyncServerExchange\", e);\n    } else { throw e; }\n}","preventionTips":["Keep separate handler classes for sync and async servers to avoid copy-paste of exchange types.","Grep codebase for 'McpAsyncServerExchange' inside sync server configuration packages during review.","Add an architecture test (e.g., ArchUnit) forbidding McpAsyncServerExchange parameters in sync resource classes."],"tags":["mcp","sync-async-mismatch","method-validation","illegal-argument"],"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"}