{"record":{"id":"04ee32626a5bbed8","repo":"spring-projects/spring-ai","slug":"method-can-have-at-most-2-input-parameters-exclud","errorCode":null,"errorMessage":"Method can have at most 2 input parameters (excluding @McpProgressToken and McpMeta) when no URI variables are present: ${method} in ${declaringClass} has ${count} non-special parameters","messagePattern":"Method can have at most 2 input parameters \\(excluding @McpProgressToken and McpMeta\\) when no URI variables are present: (.+?) in (.+?) has (.+?) non-special parameters","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java","lineNumber":189,"sourceCode":"\t */\n\tprotected void validateParametersWithoutUriVariables(Method method) {\n\t\tParameter[] parameters = method.getParameters();\n\n\t\t// Count parameters excluding @McpProgressToken and McpMeta annotated ones\n\t\tint nonSpecialParamCount = 0;\n\n\t\tfor (Parameter param : parameters) {\n\t\t\tif (!param.isAnnotationPresent(McpProgressToken.class) && !McpMeta.class.isAssignableFrom(param.getType())\n\t\t\t\t\t&& !McpSyncRequestContext.class.isAssignableFrom(param.getType())\n\t\t\t\t\t&& !McpAsyncRequestContext.class.isAssignableFrom(param.getType())\n\t\t\t\t\t&& !isExchangeOrContextType(param.getType())) {\n\t\t\t\tnonSpecialParamCount++;\n\t\t\t}\n\t\t}\n\n\t\t// Check parameter count - must have at most 2 non-special parameters\n\t\tif (nonSpecialParamCount > 2) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Method can have at most 2 input parameters (excluding @McpProgressToken and McpMeta) when no URI variables are present: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName() + \" has \"\n\t\t\t\t\t\t\t+ nonSpecialParamCount + \" non-special parameters\");\n\t\t}\n\n\t\t// Check parameter types\n\t\tboolean hasValidParams = false;\n\t\tboolean hasExchangeParam = false;\n\t\tboolean hasRequestOrUriParam = false;\n\t\tboolean hasMetaParam = false;\n\t\tboolean hasRequestContextParam = false;\n\n\t\tfor (Parameter param : parameters) {\n\t\t\t// Skip @McpProgressToken annotated parameters\n\t\t\tif (param.isAnnotationPresent(McpProgressToken.class)) {\n\t\t\t\tcontinue;\n\t\t\t}\n","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/resource/AbstractMcpResourceMethodCallback.java#L171-L207","documentation":"A @McpResource method without URI variables may declare at most 2 non-special input parameters (special = @McpProgressToken-annotated and McpMeta parameters). validateParametersWithoutUriVariables counts ordinary parameters and throws IllegalArgumentException when more than 2 remain, since with no URI template there are at most a request + request-context slot.","triggerScenarios":"Declaring a @McpResource method with 3+ ordinary parameters (e.g. multiple request objects or plain values) while the resource URI has no URI variables; each parameter not annotated with @McpProgressToken and not of type McpMeta increments the count.","commonSituations":"Copy-pasting tool method signatures into resource methods; trying to pass extra options by adding parameters; misunderstanding that URI variables count as regular parameters when present (different validator applies).","solutions":["Reduce the method to at most 2 non-special parameters (e.g. one request object plus one context).","Group extra inputs into a single request/record parameter.","Annotate auxiliary parameters with @McpProgressToken or use McpMeta so they are excluded from the count only if semantically correct.","If the resource URI actually has URI variables, declare them; the URI-variable validation path allows different signatures."],"exampleFix":"// before\n@McpResource(uri = \"file:///docs\")\npublic String read(String path, String encoding, int maxLines) { ... }\n\n// after\n@McpResource(uri = \"file:///docs\")\npublic String read(ReadOptions options) { ... } // group params into one request object","handlingStrategy":"validation","validationCode":"static boolean resourceParamCountValid(Method m, int uriVarCount) {\n    if (uriVarCount > 0) return true; // different rules apply\n    long nonSpecial = java.util.Arrays.stream(m.getParameters())\n        .filter(p -> p.getAnnotation(McpProgressToken.class) == null\n            && p.getType() != McpMeta.class)\n        .count();\n    return nonSpecial <= 2;\n}","typeGuard":null,"tryCatchPattern":"try {\n    resourceManager.register(callback);\n} catch (IllegalArgumentException e) {\n    throw new IllegalStateException(\"@McpResource signature has too many parameters\", e);\n}","preventionTips":["Keep URI-variable-free resource methods to at most 2 non-special parameters.","Group extra inputs into one request object/record.","Review signatures when copy-pasting tool methods into resource methods."],"tags":["mcp","java","parameter-count","resource-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-14T11:17:12.474Z"}