|  | 
| 21 | 21 | import java.util.List; | 
| 22 | 22 | import java.util.Map; | 
| 23 | 23 | 
 | 
|  | 24 | +import io.modelcontextprotocol.client.McpSyncClient; | 
| 24 | 25 | import io.modelcontextprotocol.server.McpAsyncServerExchange; | 
| 25 | 26 | import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification; | 
| 26 | 27 | import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; | 
| 27 | 28 | import io.modelcontextprotocol.server.McpSyncServerExchange; | 
| 28 | 29 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; | 
|  | 30 | +import io.modelcontextprotocol.spec.McpSchema.Implementation; | 
|  | 31 | +import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; | 
| 29 | 32 | import io.modelcontextprotocol.spec.McpSchema.TextContent; | 
|  | 33 | +import io.modelcontextprotocol.spec.McpSchema.Tool; | 
| 30 | 34 | import org.junit.jupiter.api.Test; | 
| 31 | 35 | import reactor.test.StepVerifier; | 
| 32 | 36 | 
 | 
| @@ -211,4 +215,118 @@ private ToolCallback createMockToolCallback(String name, RuntimeException error) | 
| 211 | 215 | 		return callback; | 
| 212 | 216 | 	} | 
| 213 | 217 | 
 | 
|  | 218 | +	@Test | 
|  | 219 | +	void getToolCallbacksFromSyncClientsWithEmptyListShouldReturnEmptyList() { | 
|  | 220 | +		List<ToolCallback> result = McpToolUtils.getToolCallbacksFromSyncClients(List.of()); | 
|  | 221 | +		assertThat(result).isEmpty(); | 
|  | 222 | +	} | 
|  | 223 | + | 
|  | 224 | +	@Test | 
|  | 225 | +	void getToolCallbacksFromSyncClientsWithSingleClientShouldReturnToolCallbacks() { | 
|  | 226 | +		McpSyncClient mockClient = mock(McpSyncClient.class); | 
|  | 227 | +		Implementation clientInfo = new Implementation("test-client", "1.0.0"); | 
|  | 228 | + | 
|  | 229 | +		Tool tool1 = mock(Tool.class); | 
|  | 230 | +		when(tool1.name()).thenReturn("tool1"); | 
|  | 231 | +		when(tool1.description()).thenReturn("Test Tool 1"); | 
|  | 232 | + | 
|  | 233 | +		Tool tool2 = mock(Tool.class); | 
|  | 234 | +		when(tool2.name()).thenReturn("tool2"); | 
|  | 235 | +		when(tool2.description()).thenReturn("Test Tool 2"); | 
|  | 236 | + | 
|  | 237 | +		when(mockClient.getClientInfo()).thenReturn(clientInfo); | 
|  | 238 | + | 
|  | 239 | +		ListToolsResult listToolsResult = mock(ListToolsResult.class); | 
|  | 240 | +		when(listToolsResult.tools()).thenReturn(List.of(tool1, tool2)); | 
|  | 241 | +		when(mockClient.listTools()).thenReturn(listToolsResult); | 
|  | 242 | + | 
|  | 243 | +		List<ToolCallback> result = McpToolUtils.getToolCallbacksFromSyncClients(mockClient); | 
|  | 244 | + | 
|  | 245 | +		assertThat(result).hasSize(2); | 
|  | 246 | +		assertThat(result.get(0).getToolDefinition().name()).isEqualTo("test_client_tool1"); | 
|  | 247 | +		assertThat(result.get(1).getToolDefinition().name()).isEqualTo("test_client_tool2"); | 
|  | 248 | + | 
|  | 249 | +		List<ToolCallback> result2 = McpToolUtils.getToolCallbacksFromSyncClients(List.of(mockClient)); | 
|  | 250 | + | 
|  | 251 | +		assertThat(result2).hasSize(2); | 
|  | 252 | +		assertThat(result2.get(0).getToolDefinition().name()).isEqualTo("test_client_tool1"); | 
|  | 253 | +		assertThat(result2.get(1).getToolDefinition().name()).isEqualTo("test_client_tool2"); | 
|  | 254 | +	} | 
|  | 255 | + | 
|  | 256 | +	@Test | 
|  | 257 | +	void getToolCallbacksFromSyncClientsWithMultipleClientsShouldReturnCombinedToolCallbacks() { | 
|  | 258 | + | 
|  | 259 | +		McpSyncClient mockClient1 = mock(McpSyncClient.class); | 
|  | 260 | +		Implementation clientInfo1 = new Implementation("client1", "1.0.0"); | 
|  | 261 | + | 
|  | 262 | +		Tool tool1 = mock(Tool.class); | 
|  | 263 | +		when(tool1.name()).thenReturn("tool1"); | 
|  | 264 | +		when(tool1.description()).thenReturn("Test Tool 1"); | 
|  | 265 | + | 
|  | 266 | +		McpSyncClient mockClient2 = mock(McpSyncClient.class); | 
|  | 267 | +		Implementation clientInfo2 = new Implementation("client2", "1.0.0"); | 
|  | 268 | + | 
|  | 269 | +		Tool tool2 = mock(Tool.class); | 
|  | 270 | +		when(tool2.name()).thenReturn("tool2"); | 
|  | 271 | +		when(tool2.description()).thenReturn("Test Tool 2"); | 
|  | 272 | + | 
|  | 273 | +		when(mockClient1.getClientInfo()).thenReturn(clientInfo1); | 
|  | 274 | + | 
|  | 275 | +		ListToolsResult listToolsResult1 = mock(ListToolsResult.class); | 
|  | 276 | +		when(listToolsResult1.tools()).thenReturn(List.of(tool1)); | 
|  | 277 | +		when(mockClient1.listTools()).thenReturn(listToolsResult1); | 
|  | 278 | + | 
|  | 279 | +		when(mockClient2.getClientInfo()).thenReturn(clientInfo2); | 
|  | 280 | + | 
|  | 281 | +		ListToolsResult listToolsResult2 = mock(ListToolsResult.class); | 
|  | 282 | +		when(listToolsResult2.tools()).thenReturn(List.of(tool2)); | 
|  | 283 | +		when(mockClient2.listTools()).thenReturn(listToolsResult2); | 
|  | 284 | + | 
|  | 285 | +		List<ToolCallback> result = McpToolUtils.getToolCallbacksFromSyncClients(mockClient1, mockClient2); | 
|  | 286 | + | 
|  | 287 | +		assertThat(result).hasSize(2); | 
|  | 288 | +		assertThat(result.get(0).getToolDefinition().name()).isEqualTo("client1_tool1"); | 
|  | 289 | +		assertThat(result.get(1).getToolDefinition().name()).isEqualTo("client2_tool2"); | 
|  | 290 | + | 
|  | 291 | +		List<ToolCallback> result2 = McpToolUtils.getToolCallbacksFromSyncClients(List.of(mockClient1, mockClient2)); | 
|  | 292 | + | 
|  | 293 | +		assertThat(result2).hasSize(2); | 
|  | 294 | +		assertThat(result2.get(0).getToolDefinition().name()).isEqualTo("client1_tool1"); | 
|  | 295 | +		assertThat(result2.get(1).getToolDefinition().name()).isEqualTo("client2_tool2"); | 
|  | 296 | +	} | 
|  | 297 | + | 
|  | 298 | +	@Test | 
|  | 299 | +	void getToolCallbacksFromSyncClientsShouldHandleDuplicateToolNames() { | 
|  | 300 | + | 
|  | 301 | +		McpSyncClient mockClient1 = mock(McpSyncClient.class); | 
|  | 302 | +		Implementation clientInfo1 = new Implementation("client", "1.0.0"); | 
|  | 303 | + | 
|  | 304 | +		Tool tool1 = mock(Tool.class); | 
|  | 305 | +		when(tool1.name()).thenReturn("tool"); | 
|  | 306 | +		when(tool1.description()).thenReturn("Test Tool 1"); | 
|  | 307 | + | 
|  | 308 | +		McpSyncClient mockClient2 = mock(McpSyncClient.class); | 
|  | 309 | +		Implementation clientInfo2 = new Implementation("client", "1.0.0"); | 
|  | 310 | + | 
|  | 311 | +		Tool tool2 = mock(Tool.class); | 
|  | 312 | +		when(tool2.name()).thenReturn("tool"); | 
|  | 313 | +		when(tool2.description()).thenReturn("Test Tool 2"); | 
|  | 314 | + | 
|  | 315 | +		when(mockClient1.getClientInfo()).thenReturn(clientInfo1); | 
|  | 316 | + | 
|  | 317 | +		ListToolsResult listToolsResult1 = mock(ListToolsResult.class); | 
|  | 318 | +		when(listToolsResult1.tools()).thenReturn(List.of(tool1)); | 
|  | 319 | +		when(mockClient1.listTools()).thenReturn(listToolsResult1); | 
|  | 320 | + | 
|  | 321 | +		when(mockClient2.getClientInfo()).thenReturn(clientInfo2); | 
|  | 322 | + | 
|  | 323 | +		ListToolsResult listToolsResult2 = mock(ListToolsResult.class); | 
|  | 324 | +		when(listToolsResult2.tools()).thenReturn(List.of(tool2)); | 
|  | 325 | +		when(mockClient2.listTools()).thenReturn(listToolsResult2); | 
|  | 326 | + | 
|  | 327 | +		assertThatThrownBy(() -> McpToolUtils.getToolCallbacksFromSyncClients(mockClient1, mockClient2)) | 
|  | 328 | +			.isInstanceOf(IllegalStateException.class) | 
|  | 329 | +			.hasMessageContaining("Multiple tools with the same name"); | 
|  | 330 | +	} | 
|  | 331 | + | 
| 214 | 332 | } | 
0 commit comments