diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java index 64d80f420..7838a7440 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.stream.Collectors; import io.modelcontextprotocol.server.McpAsyncServer; import io.modelcontextprotocol.server.McpAsyncServerExchange; @@ -145,12 +146,25 @@ public class McpServerAutoConfiguration { private List toSyncToolSpecifications(List tools, McpServerProperties serverProperties) { - return tools.stream().map(tool -> { - String toolName = tool.getToolDefinition().name(); - MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) - ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null; - return McpToolUtils.toSyncToolSpecification(tool, mimeType); - }).toList(); + + // De-duplicate tools by their name, keeping the first occurrence of each tool + // name + return tools.stream() + .collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key: + // tool + // name + tool -> tool, // Value: the tool itself + (existing, replacement) -> existing)) // On duplicate key, keep the + // existing tool + .values() + .stream() + .map(tool -> { + String toolName = tool.getToolDefinition().name(); + MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) + ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null; + return McpToolUtils.toSyncToolSpecification(tool, mimeType); + }) + .toList(); } @Bean @@ -231,12 +245,24 @@ public class McpServerAutoConfiguration { private List toAsyncToolSpecification(List tools, McpServerProperties serverProperties) { - return tools.stream().map(tool -> { - String toolName = tool.getToolDefinition().name(); - MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) - ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null; - return McpToolUtils.toAsyncToolSpecification(tool, mimeType); - }).toList(); + // De-duplicate tools by their name, keeping the first occurrence of each tool + // name + return tools.stream() + .collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key: + // tool + // name + tool -> tool, // Value: the tool itself + (existing, replacement) -> existing)) // On duplicate key, keep the + // existing tool + .values() + .stream() + .map(tool -> { + String toolName = tool.getToolDefinition().name(); + MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName)) + ? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null; + return McpToolUtils.toAsyncToolSpecification(tool, mimeType); + }) + .toList(); } @Bean diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfigurationIT.java index 29eff9e5d..88d9c3580 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfigurationIT.java @@ -161,7 +161,7 @@ public class McpServerAutoConfigurationIT { void toolRegistrationConfiguration() { this.contextRunner.withUserConfiguration(TestToolConfiguration.class).run(context -> { List tools = context.getBean("syncTools", List.class); - assertThat(tools).hasSize(2); + assertThat(tools).hasSize(1); }); } @@ -187,7 +187,7 @@ public class McpServerAutoConfigurationIT { .withUserConfiguration(TestToolConfiguration.class) .run(context -> { List tools = context.getBean("asyncTools", List.class); - assertThat(tools).hasSize(2); + assertThat(tools).hasSize(1); }); }