{"record":{"id":"c39d3644693d3c4c","repo":"spring-projects/spring-ai","slug":"found-2-elicitation-handlers-for-client-s-foun","errorCode":null,"errorMessage":"Found 2 elicitation handlers for client [%s], found in bean with names %s. Only one @McpElicitation handler is allowed per client","messagePattern":"Found 2 elicitation handlers for client \\[(.+?)\\], found in bean with names (.+?)\\. Only one @McpElicitation handler is allowed per client","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/spring/AbstractClientMcpHandlerRegistry.java","lineNumber":104,"sourceCode":"\t\t\t\tthis.allAnnotatedBeans.add(beanName);\n\t\t\t}\n\t\t\tfor (var foundAnnotation : foundAnnotations) {\n\t\t\t\tif (foundAnnotation instanceof McpSampling sampling) {\n\t\t\t\t\tfor (var client : sampling.clients()) {\n\t\t\t\t\t\tsamplingClientToAnnotatedBeans.computeIfAbsent(client, c -> new ArrayList<>()).add(beanName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (foundAnnotation instanceof McpElicitation elicitation) {\n\t\t\t\t\tfor (var client : elicitation.clients()) {\n\t\t\t\t\t\telicitationClientToAnnotatedBeans.computeIfAbsent(client, c -> new ArrayList<>()).add(beanName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (var elicitationEntry : elicitationClientToAnnotatedBeans.entrySet()) {\n\t\t\tif (elicitationEntry.getValue().size() > 1) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Found 2 elicitation handlers for client [%s], found in bean with names %s. Only one @McpElicitation handler is allowed per client\"\n\t\t\t\t\t\t\t.formatted(elicitationEntry.getKey(), new LinkedHashSet<>(elicitationEntry.getValue())));\n\t\t\t}\n\t\t}\n\t\tfor (var samplingEntry : samplingClientToAnnotatedBeans.entrySet()) {\n\t\t\tif (samplingEntry.getValue().size() > 1) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Found 2 sampling handlers for client [%s], found in bean with names %s. Only one @McpSampling handler is allowed per client\"\n\t\t\t\t\t\t\t.formatted(samplingEntry.getKey(), new LinkedHashSet<>(samplingEntry.getValue())));\n\t\t\t}\n\t\t}\n\n\t\tMap<String, McpSchema.ClientCapabilities.Builder> capsPerClient = new HashMap<>();\n\t\tfor (var samplingClient : samplingClientToAnnotatedBeans.keySet()) {\n\t\t\tcapsPerClient.computeIfAbsent(samplingClient, ignored -> McpSchema.ClientCapabilities.builder()).sampling();\n\t\t}\n\t\tfor (var elicitationClient : elicitationClientToAnnotatedBeans.keySet()) {\n\t\t\tcapsPerClient.computeIfAbsent(elicitationClient, ignored -> McpSchema.ClientCapabilities.builder())","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/spring/AbstractClientMcpHandlerRegistry.java#L86-L122","documentation":"During bean-factory post-processing, AbstractClientMcpHandlerRegistry collects beans annotated with @McpElicitation per target client. MCP allows at most one elicitation handler per client, so if two or more annotated beans map to the same client name it throws IllegalArgumentException listing the conflicting bean names. This fails fast at application startup rather than at first elicitation request.","triggerScenarios":"Declaring two (or more) beans whose methods carry @McpElicitation targeting the same MCP client name; application startup with both registered triggers postProcessBeanFactory to throw.","commonSituations":"Two configuration classes each defining an elicitation handler for the same client; a copied/renamed bean left in place alongside the new handler; component scanning picking up duplicate @McpElicitation beans.","solutions":["Remove or disable one of the conflicting @McpElicitation beans so only one handler exists per client (the message lists the offending bean names).","If both handlers are needed, consolidate their logic into a single @McpElicitation method that dispatches internally.","Change one bean's client target so each client has at most one elicitation handler."],"exampleFix":"// before: two beans both annotate @McpElicitation for client \"myClient\"\n@Bean MyElicitationHandlerA handlerA() { ... }\n@Bean MyElicitationHandlerB handlerB() { ... }\n\n// after: single consolidated handler\n@Bean MyElicitationHandler handler() { ... } // merges A and B logic","handlingStrategy":"validation","validationCode":"// At startup, verify exactly one @McpElicitation handler per client before context refresh completes\nMap<String, Long> counts = beansWithMcpElicitation.stream()\n    .collect(Collectors.groupingBy(b -> b.clientName(), Collectors.counting()));\ncounts.forEach((client, n) -> {\n    if (n > 1) throw new IllegalStateException(\"Multiple @McpElicitation handlers for client \" + client);\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep at most one @McpElicitation bean per client name.","Search the codebase for @McpElicitation after adding a new handler.","Centralize handler definitions in a single configuration class to avoid accidental duplicates."],"tags":["configuration","mcp","elicitation","duplicate-handler","startup"],"backgroundTag":"conflicting-config-options","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"}