{"record":{"id":"636be7b64cdfb094","repo":"spring-projects/spring-ai","slug":"method-parameters-must-be-exchange-completereques","errorCode":null,"errorMessage":"Method parameters must be exchange, CompleteRequest, CompleteArgument, or String: {method} in {class} has parameter of type {paramType}","messagePattern":"Method parameters must be exchange, CompleteRequest, CompleteArgument, or String: (.+?) in (.+?) has parameter of type (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AbstractMcpCompleteMethodCallback.java","lineNumber":254,"sourceCode":"\t\t\t\t}\n\t\t\t\thasExchangeParam = true;\n\t\t\t}\n\t\t\telse if (CompleteRequest.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasRequestParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one CompleteRequest parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasRequestParam = true;\n\t\t\t}\n\t\t\telse if (CompleteRequest.CompleteArgument.class.isAssignableFrom(paramType)) {\n\t\t\t\tif (hasArgumentParam) {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Method cannot have more than one CompleteArgument parameter: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName());\n\t\t\t\t}\n\t\t\t\thasArgumentParam = true;\n\t\t\t}\n\t\t\telse if (!String.class.isAssignableFrom(paramType)) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Method parameters must be exchange, CompleteRequest, CompleteArgument, or String: \"\n\t\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName()\n\t\t\t\t\t\t\t\t+ \" has parameter of type \" + paramType.getName());\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Builds the arguments array for invoking the method.\n\t * <p>\n\t * This method constructs an array of arguments based on the method's parameter types\n\t * and the available values (exchange, request, argument).\n\t * @param method The method to build arguments for\n\t * @param exchangeOrContext The server exchange or transport context\n\t * @param request The complete request\n\t * @return An array of arguments for the method invocation\n\t */\n\tprotected Object[] buildArgs(Method method, Object exchangeOrContext, CompleteRequest request) {","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AbstractMcpCompleteMethodCallback.java#L236-L272","documentation":"Thrown when a @McpComplete method parameter is not one of the allowed types: MCP exchange, McpTransportContext, McpSyncRequestContext/McpAsyncRequestContext, CompleteRequest, CompleteRequest.CompleteArgument, or String. Any other parameter type makes the method signature invalid for completion callbacks.","triggerScenarios":"Adding an arbitrary typed parameter (e.g. Integer, custom DTO, Map) to a @McpComplete method; forgetting that only String is accepted for the value being completed.","commonSituations":"Using tool-style signatures (custom input DTOs) in a completion method; IDE-generated signatures with wrong types; migrating from @McpTool annotations where richer parameter types are allowed.","solutions":["Change unsupported parameters to allowed types (String for the value, plus at most one each of exchange/context/CompleteRequest/CompleteArgument)","Remove extra parameters entirely if not needed","Use @McpProgressToken or McpMeta only if that data is needed, as those are exempt from this check"],"exampleFix":"// before\n@McpComplete(prompt=\"code\")\npublic String complete(Integer index, CompleteRequest req) { ... }\n// after\n@McpComplete(prompt=\"code\")\npublic String complete(CompleteRequest.CompleteArgument argument) { ... }","handlingStrategy":"validation","validationCode":"Set<Class<?>> allowed = Set.of(String.class, McpTransportContext.class, McpSyncRequestContext.class, McpAsyncRequestContext.class, CompleteRequest.class, CompleteRequest.CompleteArgument.class);\nfor (Class<?> t : m.getParameterTypes()) {\n  boolean ok = allowed.stream().anyMatch(a -> a.isAssignableFrom(t)) || t.isAnnotationPresent(McpProgressToken.class) || McpMeta.class.isAssignableFrom(t);\n  if (!ok) throw new IllegalStateException(\"Unsupported completion parameter type \" + t.getName() + \" in \" + m);\n}","typeGuard":"boolean isAllowedCompletionParam(Class<?> t) {\n  return String.class.isAssignableFrom(t) || McpTransportContext.class.isAssignableFrom(t)\n    || McpSyncRequestContext.class.isAssignableFrom(t) || McpAsyncRequestContext.class.isAssignableFrom(t)\n    || CompleteRequest.class.isAssignableFrom(t) || CompleteRequest.CompleteArgument.class.isAssignableFrom(t);\n}","tryCatchPattern":"try {\n    callbackBuilder.build();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Method parameters must be\")) { /* fix or remove the offending parameter type */ }\n    throw e;\n}","preventionTips":["Only use String for the value being completed; completion methods do not accept arbitrary DTOs unlike @McpTool","Copy signatures from official Spring AI MCP completion examples","Keep a contract test listing allowed parameter types for all @McpComplete methods"],"tags":["mcp","method-validation","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"}