Skip to content

Commit 89ff9eb

Browse files
committed
fix(mcp): Implement tool de-duplication by name in MCP server
Add de-duplication logic for tools in the MCP server configuration, ensuring that tools with the same name are not registered multiple times. The implementation keeps the first occurrence of each tool name and discards duplicates. - Modified toSyncToolSpecifications and toAsyncToolSpecification methods to de-duplicate tools - Updated tests to verify that duplicate tools are properly filtered out Signed-off-by: Christian Tzolov <[email protected]>
1 parent 3b543cf commit 89ff9eb

File tree

2 files changed

+40
-14
lines changed

2 files changed

+40
-14
lines changed

Diff for: auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java

+38-12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.function.BiConsumer;
2222
import java.util.function.BiFunction;
23+
import java.util.stream.Collectors;
2324

2425
import io.modelcontextprotocol.server.McpAsyncServer;
2526
import io.modelcontextprotocol.server.McpAsyncServerExchange;
@@ -145,12 +146,25 @@ public List<McpServerFeatures.SyncToolSpecification> syncTools(ObjectProvider<Li
145146

146147
private List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(List<ToolCallback> tools,
147148
McpServerProperties serverProperties) {
148-
return tools.stream().map(tool -> {
149-
String toolName = tool.getToolDefinition().name();
150-
MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName))
151-
? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
152-
return McpToolUtils.toSyncToolSpecification(tool, mimeType);
153-
}).toList();
149+
150+
// De-duplicate tools by their name, keeping the first occurrence of each tool
151+
// name
152+
return tools.stream()
153+
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key:
154+
// tool
155+
// name
156+
tool -> tool, // Value: the tool itself
157+
(existing, replacement) -> existing)) // On duplicate key, keep the
158+
// existing tool
159+
.values()
160+
.stream()
161+
.map(tool -> {
162+
String toolName = tool.getToolDefinition().name();
163+
MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName))
164+
? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
165+
return McpToolUtils.toSyncToolSpecification(tool, mimeType);
166+
})
167+
.toList();
154168
}
155169

156170
@Bean
@@ -231,12 +245,24 @@ public List<McpServerFeatures.AsyncToolSpecification> asyncTools(ObjectProvider<
231245

232246
private List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecification(List<ToolCallback> tools,
233247
McpServerProperties serverProperties) {
234-
return tools.stream().map(tool -> {
235-
String toolName = tool.getToolDefinition().name();
236-
MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName))
237-
? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
238-
return McpToolUtils.toAsyncToolSpecification(tool, mimeType);
239-
}).toList();
248+
// De-duplicate tools by their name, keeping the first occurrence of each tool
249+
// name
250+
return tools.stream()
251+
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key:
252+
// tool
253+
// name
254+
tool -> tool, // Value: the tool itself
255+
(existing, replacement) -> existing)) // On duplicate key, keep the
256+
// existing tool
257+
.values()
258+
.stream()
259+
.map(tool -> {
260+
String toolName = tool.getToolDefinition().name();
261+
MimeType mimeType = (serverProperties.getToolResponseMimeType().containsKey(toolName))
262+
? MimeType.valueOf(serverProperties.getToolResponseMimeType().get(toolName)) : null;
263+
return McpToolUtils.toAsyncToolSpecification(tool, mimeType);
264+
})
265+
.toList();
240266
}
241267

242268
@Bean

Diff for: auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfigurationIT.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ void serverCapabilitiesConfiguration() {
161161
void toolRegistrationConfiguration() {
162162
this.contextRunner.withUserConfiguration(TestToolConfiguration.class).run(context -> {
163163
List<SyncToolSpecification> tools = context.getBean("syncTools", List.class);
164-
assertThat(tools).hasSize(2);
164+
assertThat(tools).hasSize(1);
165165
});
166166
}
167167

@@ -187,7 +187,7 @@ void asyncToolRegistrationConfiguration() {
187187
.withUserConfiguration(TestToolConfiguration.class)
188188
.run(context -> {
189189
List<AsyncToolSpecification> tools = context.getBean("asyncTools", List.class);
190-
assertThat(tools).hasSize(2);
190+
assertThat(tools).hasSize(1);
191191
});
192192
}
193193

0 commit comments

Comments
 (0)