|
31 | 31 |
|
32 | 32 | import org.springframework.ai.mcp.client.McpClient; |
33 | 33 | import org.springframework.ai.mcp.client.transport.SseClientTransport; |
| 34 | +import org.springframework.ai.mcp.server.McpServer.ToolRegistration; |
34 | 35 | import org.springframework.ai.mcp.server.transport.SseServerTransport; |
35 | 36 | import org.springframework.ai.mcp.spec.McpError; |
36 | 37 | import org.springframework.ai.mcp.spec.McpSchema; |
| 38 | +import org.springframework.ai.mcp.spec.McpSchema.CallToolResult; |
37 | 39 | import org.springframework.ai.mcp.spec.McpSchema.ClientCapabilities; |
38 | 40 | import org.springframework.ai.mcp.spec.McpSchema.CreateMessageRequest; |
39 | 41 | import org.springframework.ai.mcp.spec.McpSchema.CreateMessageResult; |
40 | 42 | import org.springframework.ai.mcp.spec.McpSchema.InitializeResult; |
41 | 43 | import org.springframework.ai.mcp.spec.McpSchema.Role; |
42 | 44 | import org.springframework.ai.mcp.spec.McpSchema.Root; |
| 45 | +import org.springframework.ai.mcp.spec.McpSchema.ServerCapabilities; |
| 46 | +import org.springframework.ai.mcp.spec.McpSchema.Tool; |
43 | 47 | import org.springframework.http.server.reactive.HttpHandler; |
44 | 48 | import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; |
| 49 | +import org.springframework.web.client.RestClient; |
45 | 50 | import org.springframework.web.reactive.function.client.WebClient; |
46 | 51 | import org.springframework.web.reactive.function.server.RouterFunctions; |
47 | 52 |
|
@@ -314,4 +319,111 @@ void testRootsServerCloseWithActiveSubscription() { |
314 | 319 | mcpClient.close(); |
315 | 320 | } |
316 | 321 |
|
| 322 | + // --------------------------------------- |
| 323 | + // Tools Tests |
| 324 | + // --------------------------------------- |
| 325 | + @Test |
| 326 | + void testToolCallSuccess() { |
| 327 | + |
| 328 | + var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); |
| 329 | + ToolRegistration tool1 = new ToolRegistration( |
| 330 | + new McpSchema.Tool("tool1", "tool1 description", Map.of("city", "String")), request -> { |
| 331 | + // perform a blocking call to a remote service |
| 332 | + String response = RestClient.create() |
| 333 | + .get() |
| 334 | + .uri("https://github.com/spring-projects-experimental/spring-ai-mcp/blob/main/README.md") |
| 335 | + .retrieve() |
| 336 | + .body(String.class); |
| 337 | + assertThat(response).isNotBlank(); |
| 338 | + return callResponse; |
| 339 | + }); |
| 340 | + |
| 341 | + var mcpServer = McpServer.using(mcpServerTransport) |
| 342 | + .capabilities(ServerCapabilities.builder().tools(true).build()) |
| 343 | + .tools(tool1) |
| 344 | + .sync(); |
| 345 | + |
| 346 | + var mcpClient = clientBuilder.sync(); |
| 347 | + |
| 348 | + InitializeResult initResult = mcpClient.initialize(); |
| 349 | + assertThat(initResult).isNotNull(); |
| 350 | + |
| 351 | + assertThat(mcpClient.listTools().tools()).contains(tool1.tool()); |
| 352 | + |
| 353 | + CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())); |
| 354 | + |
| 355 | + assertThat(response).isNotNull(); |
| 356 | + assertThat(response).isEqualTo(callResponse); |
| 357 | + |
| 358 | + mcpClient.close(); |
| 359 | + mcpServer.close(); |
| 360 | + } |
| 361 | + |
| 362 | + @Test |
| 363 | + void testToolListChangeHandlingSuccess() { |
| 364 | + |
| 365 | + var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); |
| 366 | + ToolRegistration tool1 = new ToolRegistration( |
| 367 | + new McpSchema.Tool("tool1", "tool1 description", Map.of("city", "String")), request -> { |
| 368 | + // perform a blocking call to a remote service |
| 369 | + String response = RestClient.create() |
| 370 | + .get() |
| 371 | + .uri("https://github.com/spring-projects-experimental/spring-ai-mcp/blob/main/README.md") |
| 372 | + .retrieve() |
| 373 | + .body(String.class); |
| 374 | + assertThat(response).isNotBlank(); |
| 375 | + return callResponse; |
| 376 | + }); |
| 377 | + |
| 378 | + var mcpServer = McpServer.using(mcpServerTransport) |
| 379 | + .capabilities(ServerCapabilities.builder().tools(true).build()) |
| 380 | + .tools(tool1) |
| 381 | + .sync(); |
| 382 | + |
| 383 | + AtomicReference<List<Tool>> rootsRef = new AtomicReference<>(); |
| 384 | + var mcpClient = clientBuilder.toolsChangeConsumer(toolsUpdate -> { |
| 385 | + // perform a blocking call to a remote service |
| 386 | + String response = RestClient.create() |
| 387 | + .get() |
| 388 | + .uri("https://github.com/spring-projects-experimental/spring-ai-mcp/blob/main/README.md") |
| 389 | + .retrieve() |
| 390 | + .body(String.class); |
| 391 | + assertThat(response).isNotBlank(); |
| 392 | + rootsRef.set(toolsUpdate); |
| 393 | + }).sync(); |
| 394 | + |
| 395 | + InitializeResult initResult = mcpClient.initialize(); |
| 396 | + assertThat(initResult).isNotNull(); |
| 397 | + |
| 398 | + assertThat(rootsRef.get()).isNull(); |
| 399 | + |
| 400 | + assertThat(mcpClient.listTools().tools()).contains(tool1.tool()); |
| 401 | + |
| 402 | + mcpServer.notifyToolsListChanged(); |
| 403 | + |
| 404 | + await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { |
| 405 | + assertThat(rootsRef.get()).containsAll(List.of(tool1.tool())); |
| 406 | + }); |
| 407 | + |
| 408 | + // Remove a tool |
| 409 | + mcpServer.removeTool("tool1"); |
| 410 | + |
| 411 | + await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { |
| 412 | + assertThat(rootsRef.get()).isEmpty(); |
| 413 | + }); |
| 414 | + |
| 415 | + // Add a new tool |
| 416 | + ToolRegistration tool2 = new ToolRegistration( |
| 417 | + new McpSchema.Tool("tool2", "tool2 description", Map.of("city", "String")), request -> callResponse); |
| 418 | + |
| 419 | + mcpServer.addTool(tool2); |
| 420 | + |
| 421 | + await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { |
| 422 | + assertThat(rootsRef.get()).containsAll(List.of(tool2.tool())); |
| 423 | + }); |
| 424 | + |
| 425 | + mcpClient.close(); |
| 426 | + mcpServer.close(); |
| 427 | + } |
| 428 | + |
317 | 429 | } |
0 commit comments