|
| 1 | +# Dynamic Tool Update Example |
| 2 | + |
| 3 | +This example demonstrates how a Model Context Protocol (MCP) server can dynamically update its available tools at runtime, and how a client can detect and use these updated tools. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The MCP protocol allows AI models to access external tools and resources through a standardized interface. This example showcases a key feature of MCP: the ability to dynamically update the available tools on the server side, with the client detecting and utilizing these changes. |
| 8 | + |
| 9 | +## Key Components |
| 10 | + |
| 11 | +### Server |
| 12 | + |
| 13 | +The server application consists of: |
| 14 | + |
| 15 | +1. **WeatherService**: Initially provides a weather forecast tool that retrieves temperature data for a specific location. |
| 16 | +2. **MathTools**: Contains mathematical operations (sum, multiply, divide) that are added dynamically to the server. |
| 17 | +3. **ServerApplication**: Manages the server lifecycle and handles the dynamic tool update process. |
| 18 | + |
| 19 | +### Client |
| 20 | + |
| 21 | +The client application: |
| 22 | + |
| 23 | +1. Connects to the MCP server |
| 24 | +2. Retrieves the initial list of available tools |
| 25 | +3. Triggers the server to update its tools |
| 26 | +4. Detects the tool changes |
| 27 | +5. Retrieves the updated list of tools |
| 28 | + |
| 29 | +## How It Works |
| 30 | + |
| 31 | +1. **Initial Setup**: When the server starts, it only exposes the weather forecast tool. |
| 32 | + |
| 33 | +2. **Dynamic Update Process**: |
| 34 | + - The client sends a request to the server's `/updateTools` endpoint |
| 35 | + - The server receives this signal and adds the math tools to its available tools |
| 36 | + - The server notifies connected clients about the tool changes |
| 37 | + - The client receives the notification and can now use the new tools |
| 38 | + |
| 39 | +3. **Tool Discovery**: The client can query the available tools at any time, demonstrating that the tool list has been updated. |
| 40 | + |
| 41 | +## Implementation Details |
| 42 | + |
| 43 | +### Server-Side Implementation |
| 44 | + |
| 45 | +The server uses Spring AI's MCP server implementation: |
| 46 | + |
| 47 | +```java |
| 48 | +@Bean |
| 49 | +public ToolCallbackProvider weatherTools(WeatherService weatherService) { |
| 50 | + return MethodToolCallbackProvider.builder().toolObjects(weatherService).build(); |
| 51 | +} |
| 52 | + |
| 53 | +@Bean |
| 54 | +public CommandLineRunner predefinedQuestions(McpSyncServer mcpSyncServer) { |
| 55 | + return args -> { |
| 56 | + logger.info("Server: " + mcpSyncServer.getServerInfo()); |
| 57 | + |
| 58 | + latch.await(); // Wait for update signal |
| 59 | + |
| 60 | + // Add math tools dynamically |
| 61 | + List<SyncToolSpecification> newTools = McpToolUtils |
| 62 | + .toSyncToolSpecifications(ToolCallbacks.from(new MathTools())); |
| 63 | + |
| 64 | + mcpSyncServer.addTool(newTools.iterator().next()); |
| 65 | + |
| 66 | + logger.info("Tools updated: "); |
| 67 | + }; |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +### Client-Side Implementation |
| 72 | + |
| 73 | +The client connects to the server and registers a callback to be notified when tools change: |
| 74 | + |
| 75 | +```java |
| 76 | +@Bean |
| 77 | +McpSyncClientCustomizer customizeMcpClient() { |
| 78 | + return (name, mcpClientSpec) -> { |
| 79 | + mcpClientSpec.toolsChangeConsumer(tv -> { |
| 80 | + logger.info("\nMCP TOOLS CHANGE: " + tv); |
| 81 | + latch.countDown(); |
| 82 | + }); |
| 83 | + }; |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +The client retrieves the available tools before and after the update: |
| 88 | + |
| 89 | +```java |
| 90 | +// Get initial tools |
| 91 | +List<ToolDescription> toolDescriptions = chatClientBuilder.build() |
| 92 | + .prompt("What tools are available?") |
| 93 | + .toolCallbacks(tools) |
| 94 | + .call() |
| 95 | + .entity(new ParameterizedTypeReference<List<ToolDescription>>() {}); |
| 96 | + |
| 97 | +// Signal the server to update tools |
| 98 | +String signal = RestClient.builder().build().get() |
| 99 | + .uri("http://localhost:8080/updateTools").retrieve().body(String.class); |
| 100 | + |
| 101 | +// Wait for tool change notification |
| 102 | +latch.await(); |
| 103 | + |
| 104 | +// Get updated tools |
| 105 | +toolDescriptions = chatClientBuilder.build() |
| 106 | + .prompt("What tools are available?") |
| 107 | + .toolCallbacks(tools) |
| 108 | + .call() |
| 109 | + .entity(new ParameterizedTypeReference<List<ToolDescription>>() {}); |
| 110 | +``` |
| 111 | + |
| 112 | +## Key Insight |
| 113 | + |
| 114 | +The example demonstrates a crucial aspect of the MCP implementation in Spring AI: |
| 115 | + |
| 116 | +> The client implementation relies on the fact that the `ToolCallbackProvider#getToolCallbacks` implementation for MCP will always get the current list of MCP tools from the server. |
| 117 | +
|
| 118 | +This means that whenever a client requests the available tools, it will always get the most up-to-date list from the server, without needing to restart or reinitialize the client. |
| 119 | + |
| 120 | +## Running the Example |
| 121 | + |
| 122 | +1. Start the server application: |
| 123 | + ``` |
| 124 | + cd server |
| 125 | + ./mvnw spring-boot:run |
| 126 | + ``` |
| 127 | + |
| 128 | +2. In a separate terminal, start the client application: |
| 129 | + ``` |
| 130 | + cd client |
| 131 | + ./mvnw spring-boot:run |
| 132 | + ``` |
| 133 | + |
| 134 | +3. Observe the console output to see: |
| 135 | + - The initial list of tools (only weather forecast) |
| 136 | + - The tool update notification |
| 137 | + - The updated list of tools (weather forecast + math operations) |
| 138 | + |
| 139 | +## MCP Protocol Specification |
| 140 | + |
| 141 | +For more information about the MCP protocol, refer to the official specification: |
| 142 | +[https://modelcontextprotocol.io/specification/2024-11-05/server/tools](https://modelcontextprotocol.io/specification/2024-11-05/server/tools) |
0 commit comments