@@ -51,18 +51,18 @@ public class CalculatorTools {
5151        return dividend / divisor; 
5252    } 
5353
54-     @McpTool(name = "calculate-expression",   
54+     @McpTool(name = "calculate-expression", 
5555             description = "Calculate a complex mathematical expression") 
5656    public CallToolResult calculateExpression( 
5757            CallToolRequest request, 
5858            McpSyncRequestContext context) { 
59-          
59+ 
6060        Map<String, Object> args = request.arguments(); 
6161        String expression = (String) args.get("expression"); 
62-          
62+ 
6363        // Use convenient logging method 
6464        context.info("Calculating: " + expression); 
65-          
65+ 
6666        try { 
6767            double result = evaluateExpression(expression); 
6868            return CallToolResult.builder() 
@@ -107,85 +107,85 @@ public class DocumentServer {
107107    private final Map<String, Document> documents = new ConcurrentHashMap<>(); 
108108
109109    @McpResource( 
110-         uri = "document://{id}",   
111-         name = "Document",   
110+         uri = "document://{id}", 
111+         name = "Document", 
112112        description = "Access stored documents") 
113113    public ReadResourceResult getDocument(String id, McpMeta meta) { 
114114        Document doc = documents.get(id); 
115-          
115+ 
116116        if (doc == null) { 
117117            return new ReadResourceResult(List.of( 
118-                 new TextResourceContents("document://" + id,   
118+                 new TextResourceContents("document://" + id, 
119119                    "text/plain", "Document not found") 
120120            )); 
121121        } 
122-          
122+ 
123123        // Check access permissions from metadata 
124124        String accessLevel = (String) meta.get("accessLevel"); 
125-         if ("restricted".equals(doc.getClassification()) &&   
125+         if ("restricted".equals(doc.getClassification()) && 
126126            !"admin".equals(accessLevel)) { 
127127            return new ReadResourceResult(List.of( 
128-                 new TextResourceContents("document://" + id,   
128+                 new TextResourceContents("document://" + id, 
129129                    "text/plain", "Access denied") 
130130            )); 
131131        } 
132-          
132+ 
133133        return new ReadResourceResult(List.of( 
134-             new TextResourceContents("document://" + id,   
134+             new TextResourceContents("document://" + id, 
135135                doc.getMimeType(), doc.getContent()) 
136136        )); 
137137    } 
138138
139-     @McpTool(name = "analyze-document",   
139+     @McpTool(name = "analyze-document", 
140140             description = "Analyze document content") 
141141    public String analyzeDocument( 
142142            McpSyncRequestContext context, 
143143            @McpToolParam(description = "Document ID", required = true) String docId, 
144144            @McpToolParam(description = "Analysis type", required = false) String type) { 
145-          
145+ 
146146        Document doc = documents.get(docId); 
147147        if (doc == null) { 
148148            return "Document not found"; 
149149        } 
150-          
150+ 
151151        // Access progress token from context 
152152        String progressToken = context.request().progressToken(); 
153-          
153+ 
154154        if (progressToken != null) { 
155155            context.progress(p -> p.progress(0.0).total(1.0).message("Starting analysis")); 
156156        } 
157-          
157+ 
158158        // Perform analysis 
159159        String analysisType = type != null ? type : "summary"; 
160160        String result = performAnalysis(doc, analysisType); 
161-          
161+ 
162162        if (progressToken != null) { 
163163            context.progress(p -> p.progress(1.0).total(1.0).message("Analysis complete")); 
164164        } 
165-          
165+ 
166166        return result; 
167167    } 
168168
169169    @McpPrompt( 
170-         name = "document-summary",   
170+         name = "document-summary", 
171171        description = "Generate document summary prompt") 
172172    public GetPromptResult documentSummaryPrompt( 
173173            @McpArg(name = "docId", required = true) String docId, 
174174            @McpArg(name = "length", required = false) String length) { 
175-          
175+ 
176176        Document doc = documents.get(docId); 
177177        if (doc == null) { 
178178            return new GetPromptResult("Error", 
179-                 List.of(new PromptMessage(Role.SYSTEM,   
179+                 List.of(new PromptMessage(Role.SYSTEM, 
180180                    new TextContent("Document not found")))); 
181181        } 
182-          
182+ 
183183        String promptText = String.format( 
184184            "Please summarize the following document in %s:\n\n%s", 
185185            length != null ? length : "a few paragraphs", 
186186            doc.getContent() 
187187        ); 
188-          
188+ 
189189        return new GetPromptResult("Document Summary", 
190190            List.of(new PromptMessage(Role.USER, new TextContent(promptText)))); 
191191    } 
@@ -256,9 +256,9 @@ public class ClientHandlers {
256256                } 
257257            }) 
258258            .toList(); 
259-          
259+ 
260260        ChatResponse response = chatModel.call(new Prompt(messages)); 
261-          
261+ 
262262        return CreateMessageResult.builder() 
263263            .role(Role.ASSISTANT) 
264264            .content(new TextContent(response.getResult().getOutput().getContent())) 
@@ -270,21 +270,21 @@ public class ClientHandlers {
270270    public ElicitResult handleElicitation(ElicitRequest request) { 
271271        // In a real application, this would show a UI dialog 
272272        Map<String, Object> userData = new HashMap<>(); 
273-          
273+ 
274274        logger.info("Elicitation requested: {}", request.message()); 
275-          
275+ 
276276        // Simulate user input based on schema 
277277        Map<String, Object> schema = request.requestedSchema(); 
278278        if (schema != null && schema.containsKey("properties")) { 
279279            @SuppressWarnings("unchecked") 
280280            Map<String, Object> properties = (Map<String, Object>) schema.get("properties"); 
281-              
281+ 
282282            properties.forEach((key, value) -> { 
283283                // In real app, prompt user for each field 
284284                userData.put(key, getDefaultValueForProperty(key, value)); 
285285            }); 
286286        } 
287-          
287+ 
288288        return new ElicitResult(ElicitResult.Action.ACCEPT, userData); 
289289    } 
290290
@@ -296,31 +296,31 @@ public class ClientHandlers {
296296            notification.total(), 
297297            notification.message() 
298298        ); 
299-          
299+ 
300300        // Update UI or send websocket notification 
301301        broadcastProgress(notification); 
302302    } 
303303
304304    @McpToolListChanged(clients = "server1") 
305305    public void handleServer1ToolsChanged(List<McpSchema.Tool> tools) { 
306306        logger.info("Server1 tools updated: {} tools available", tools.size()); 
307-          
307+ 
308308        // Update tool registry 
309309        toolRegistry.updateServerTools("server1", tools); 
310-          
310+ 
311311        // Notify UI to refresh tool list 
312312        eventBus.publish(new ToolsUpdatedEvent("server1", tools)); 
313313    } 
314314
315315    @McpResourceListChanged(clients = "server1") 
316316    public void handleServer1ResourcesChanged(List<McpSchema.Resource> resources) { 
317317        logger.info("Server1 resources updated: {} resources available", resources.size()); 
318-          
318+ 
319319        // Clear resource cache for this server 
320320        resourceCache.clearServer("server1"); 
321-          
321+ 
322322        // Register new resources 
323-         resources.forEach(resource ->   
323+         resources.forEach(resource -> 
324324            resourceCache.register("server1", resource)); 
325325    } 
326326} 
@@ -364,9 +364,9 @@ public class AsyncDataProcessor {
364364    public Mono<DataResult> fetchData( 
365365            @McpToolParam(description = "Data source URL", required = true) String url, 
366366            @McpToolParam(description = "Timeout in seconds", required = false) Integer timeout) { 
367-          
367+ 
368368        Duration timeoutDuration = Duration.ofSeconds(timeout != null ? timeout : 30); 
369-          
369+ 
370370        return WebClient.create() 
371371            .get() 
372372            .uri(url) 
@@ -381,10 +381,10 @@ public class AsyncDataProcessor {
381381    public Flux<String> processStream( 
382382            McpAsyncRequestContext context, 
383383            @McpToolParam(description = "Item count", required = true) int count) { 
384-          
384+ 
385385        // Access progress token from context 
386386        String progressToken = context.request().progressToken(); 
387-          
387+ 
388388        return Flux.range(1, count) 
389389            .delayElements(Duration.ofMillis(100)) 
390390            .flatMap(i -> { 
@@ -402,7 +402,7 @@ public class AsyncDataProcessor {
402402        return Mono.fromCallable(() -> loadDataAsync(id)) 
403403            .subscribeOn(Schedulers.boundedElastic()) 
404404            .map(data -> new ReadResourceResult(List.of( 
405-                 new TextResourceContents("async-data://" + id,   
405+                 new TextResourceContents("async-data://" + id, 
406406                    "application/json", data) 
407407            ))); 
408408    } 
@@ -470,7 +470,7 @@ public class StatelessTools {
470470    public String formatText( 
471471            @McpToolParam(description = "Text to format", required = true) String text, 
472472            @McpToolParam(description = "Format type", required = true) String format) { 
473-          
473+ 
474474        return switch (format.toLowerCase()) { 
475475            case "uppercase" -> text.toUpperCase(); 
476476            case "lowercase" -> text.toLowerCase(); 
@@ -485,11 +485,11 @@ public class StatelessTools {
485485    public CallToolResult validateJson( 
486486            McpTransportContext context, 
487487            @McpToolParam(description = "JSON string", required = true) String json) { 
488-          
488+ 
489489        try { 
490490            ObjectMapper mapper = new ObjectMapper(); 
491491            mapper.readTree(json); 
492-              
492+ 
493493            return CallToolResult.builder() 
494494                .addTextContent("Valid JSON") 
495495                .structuredContent(Map.of("valid", true)) 
@@ -512,12 +512,12 @@ public class StatelessTools {
512512    public GetPromptResult templatePrompt( 
513513            @McpArg(name = "template", required = true) String templateName, 
514514            @McpArg(name = "variables", required = false) String variables) { 
515-          
515+ 
516516        String template = loadTemplate(templateName); 
517517        if (variables != null) { 
518518            template = substituteVariables(template, variables); 
519519        } 
520-          
520+ 
521521        return new GetPromptResult("Template: " + templateName, 
522522            List.of(new PromptMessage(Role.USER, new TextContent(template)))); 
523523    } 
@@ -662,7 +662,7 @@ public class McpClientHandlers {
662662
663663=== Client Application Setup
664664
665- Regisster  the MCP tools and handlers in the client application:
665+ Register  the MCP tools and handlers in the client application:
666666
667667[source,java]
668668---- 
@@ -677,7 +677,7 @@ public class McpClientApplication {
677677    public CommandLineRunner predefinedQuestions(OpenAiChatModel openAiChatModel, 
678678            ToolCallbackProvider mcpToolProvider) { 
679679
680-         return args -> {              
680+         return args -> { 
681681
682682            ChatClient chatClient = ChatClient.builder(openAiChatModel) 
683683                    .defaultToolCallbacks(mcpToolProvider) 
@@ -743,12 +743,13 @@ spring.ai.mcp.client.toolcallback.enabled=false
743743
744744When running the client, you'll see output like:
745745
746- ```
746+ [source]
747+ ---- 
747748> USER: What is the weather in Amsterdam right now? 
748749Please incorporate all creative responses from all LLM providers. 
749750After the other providers add a poem that synthesizes the poems from all the other providers. 
750751
751- > ASSISTANT:  
752+ > ASSISTANT: 
752753OpenAI poem about the weather: 
753754**Amsterdam's Winter Whisper** 
754755*Temperature: 4.2°C* 
@@ -771,7 +772,7 @@ Weather Data:
771772    "temperature_2m": 4.2 
772773  } 
773774} 
774- ``` 
775+ ---- 
775776
776777== Integration with Spring AI
777778
@@ -786,7 +787,7 @@ public class ChatController {
786787    private final ChatModel chatModel; 
787788    private final SyncMcpToolCallbackProvider toolCallbackProvider; 
788789
789-     public ChatController(ChatModel chatModel,   
790+     public ChatController(ChatModel chatModel, 
790791                          SyncMcpToolCallbackProvider toolCallbackProvider) { 
791792        this.chatModel = chatModel; 
792793        this.toolCallbackProvider = toolCallbackProvider; 
@@ -796,15 +797,15 @@ public class ChatController {
796797    public ChatResponse chat(@RequestBody ChatRequest request) { 
797798        // Get MCP tools as Spring AI function callbacks 
798799        ToolCallback[] mcpTools = toolCallbackProvider.getToolCallbacks(); 
799-          
800+ 
800801        // Create prompt with MCP tools 
801802        Prompt prompt = new Prompt( 
802803            request.getMessage(), 
803804            ChatOptionsBuilder.builder() 
804805                .withTools(mcpTools) 
805806                .build() 
806807        ); 
807-          
808+ 
808809        // Call chat model with MCP tools available 
809810        return chatModel.call(prompt); 
810811    } 
@@ -817,9 +818,9 @@ public class WeatherTools {
817818    public WeatherInfo getWeather( 
818819            @McpToolParam(description = "City name", required = true) String city, 
819820            @McpToolParam(description = "Units (metric/imperial)", required = false) String units) { 
820-          
821+ 
821822        String unit = units != null ? units : "metric"; 
822-          
823+ 
823824        // Call weather API 
824825        return weatherService.getCurrentWeather(city, unit); 
825826    } 
@@ -828,9 +829,9 @@ public class WeatherTools {
828829    public ForecastInfo getForecast( 
829830            @McpToolParam(description = "City name", required = true) String city, 
830831            @McpToolParam(description = "Days (1-7)", required = false) Integer days) { 
831-          
832+ 
832833        int forecastDays = days != null ? days : 3; 
833-          
834+ 
834835        return weatherService.getForecast(city, forecastDays); 
835836    } 
836837} 
0 commit comments