{"record":{"id":"373f505171b4da73","repo":"spring-projects/spring-ai","slug":"method-must-have-exactly-1-parameter-list-mcpsche-373f50","errorCode":null,"errorMessage":"Method must have exactly 1 parameter (List<McpSchema.Tool>): \" + method.getName() + \" in \" + method.getDeclaringClass().getName() + \" has \" + parameters.length + \" parameters","messagePattern":"Method must have exactly 1 parameter \\(List<McpSchema\\.Tool>\\): \" \\+ method\\.getName\\(\\) \\+ \" in \" \\+ method\\.getDeclaringClass\\(\\)\\.getName\\(\\) \\+ \" has \" \\+ parameters\\.length \\+ \" parameters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/AbstractMcpToolListChangedMethodCallback.java","lineNumber":94,"sourceCode":"\t * Validates that the method return type is compatible with the tool list changed\n\t * consumer callback. This method should be implemented by subclasses to handle\n\t * specific return type validation.\n\t * @param method The method to validate\n\t * @throws IllegalArgumentException if the return type is not compatible\n\t */\n\tprotected abstract void validateReturnType(Method method);\n\n\t/**\n\t * Validates method parameters. This method provides common validation logic.\n\t * @param method The method to validate\n\t * @throws IllegalArgumentException if the parameters are not compatible\n\t */\n\tprotected void validateParameters(Method method) {\n\t\tParameter[] parameters = method.getParameters();\n\n\t\t// Check parameter count - must have exactly 1 parameter\n\t\tif (parameters.length != 1) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Method must have exactly 1 parameter (List<McpSchema.Tool>): \" + method.getName() + \" in \"\n\t\t\t\t\t\t\t+ method.getDeclaringClass().getName() + \" has \" + parameters.length + \" parameters\");\n\t\t}\n\n\t\t// Check parameter type - must be List<McpSchema.Tool>\n\t\tClass<?> paramType = parameters[0].getType();\n\t\tif (!List.class.isAssignableFrom(paramType)) {\n\t\t\tthrow new IllegalArgumentException(\"Parameter must be of type List<McpSchema.Tool>: \" + method.getName()\n\t\t\t\t\t+ \" in \" + method.getDeclaringClass().getName() + \" has parameter of type \" + paramType.getName());\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.\n\t * @param method The method to build arguments for","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/AbstractMcpToolListChangedMethodCallback.java#L76-L112","documentation":"validateParameters enforces that the tool-list-changed callback method takes exactly one parameter of type List<McpSchema.Tool>. The library invokes the method with a single argument (the updated tools list), so any other arity would fail at invocation time; it rejects the signature eagerly with the method name, declaring class, and actual parameter count in the message.","triggerScenarios":"Registering a handler method annotated/registered as a tool-list-changed consumer whose signature has 0 parameters, 2+ parameters (e.g. (List<McpSchema.Tool> tools, McpSyncClient client)), or varargs — validateParameters throws IllegalArgumentException when method.getParameters().length != 1.","commonSituations":"Developers writing callback handlers modeled on other MCP listener APIs that pass extra context parameters, or adding a second parameter (like a session or client) to an existing one-argument handler during refactoring.","solutions":["Change the handler method signature to accept exactly one parameter: void myHandler(List<McpSchema.Tool> tools)","Move extra context (client, session) into bean fields or constructor injection instead of method parameters","Verify with reflection before building: beanClass.getMethod(\"name\", List.class) — this also enforces the correct arity"],"exampleFix":"// before\npublic void onToolListChanged(List<McpSchema.Tool> tools, McpSyncClient client) { ... }\n// after\npublic void onToolListChanged(List<McpSchema.Tool> tools) { ... } // inject client via field if needed","handlingStrategy":"validation","validationCode":"if (handlerMethod.getParameterCount() != 1 || !java.util.List.class.isAssignableFrom(handlerMethod.getParameterTypes()[0])) throw new IllegalArgumentException(handlerMethod + \" must take exactly one List<McpSchema.Tool> parameter\");","typeGuard":"static boolean validToolListChangedSignature(java.lang.reflect.Method m) { return m != null && m.getParameterCount() == 1 && java.util.List.class.isAssignableFrom(m.getParameterTypes()[0]); }","tryCatchPattern":"try { validateHandler(bean.getClass().getMethod(\"onToolListChanged\", java.util.List.class)); } catch (IllegalArgumentException e) { fail(\"Invalid handler signature: \" + e.getMessage()); }","preventionTips":["Write handlers with the exact signature void name(List<McpSchema.Tool> tools)","Inject context (clients, services) via fields/constructor, not handler parameters","Add a startup self-check that reflects over registered handlers"],"tags":["java","reflection","method-signature","validation"],"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-14T05:17:10.506Z"}