@@ -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()
@@ -109,85 +109,85 @@ public class DocumentServer {
109109 private final Map<String, Document> documents = new ConcurrentHashMap<>();
110110
111111 @McpResource(
112- uri = "document://{id}",
113- name = "Document",
112+ uri = "document://{id}",
113+ name = "Document",
114114 description = "Access stored documents")
115115 public ReadResourceResult getDocument(String id, McpMeta meta) {
116116 Document doc = documents.get(id);
117-
117+
118118 if (doc == null) {
119119 return new ReadResourceResult(List.of(
120- new TextResourceContents("document://" + id,
120+ new TextResourceContents("document://" + id,
121121 "text/plain", "Document not found")
122122 ));
123123 }
124-
124+
125125 // Check access permissions from metadata
126126 String accessLevel = (String) meta.get("accessLevel");
127- if ("restricted".equals(doc.getClassification()) &&
127+ if ("restricted".equals(doc.getClassification()) &&
128128 !"admin".equals(accessLevel)) {
129129 return new ReadResourceResult(List.of(
130- new TextResourceContents("document://" + id,
130+ new TextResourceContents("document://" + id,
131131 "text/plain", "Access denied")
132132 ));
133133 }
134-
134+
135135 return new ReadResourceResult(List.of(
136- new TextResourceContents("document://" + id,
136+ new TextResourceContents("document://" + id,
137137 doc.getMimeType(), doc.getContent())
138138 ));
139139 }
140140
141- @McpTool(name = "analyze-document",
141+ @McpTool(name = "analyze-document",
142142 description = "Analyze document content")
143143 public String analyzeDocument(
144144 McpSyncRequestContext context,
145145 @McpToolParam(description = "Document ID", required = true) String docId,
146146 @McpToolParam(description = "Analysis type", required = false) String type) {
147-
147+
148148 Document doc = documents.get(docId);
149149 if (doc == null) {
150150 return "Document not found";
151151 }
152-
152+
153153 // Access progress token from context
154154 String progressToken = context.request().progressToken();
155-
155+
156156 if (progressToken != null) {
157157 context.progress(p -> p.progress(0.0).total(1.0).message("Starting analysis"));
158158 }
159-
159+
160160 // Perform analysis
161161 String analysisType = type != null ? type : "summary";
162162 String result = performAnalysis(doc, analysisType);
163-
163+
164164 if (progressToken != null) {
165165 context.progress(p -> p.progress(1.0).total(1.0).message("Analysis complete"));
166166 }
167-
167+
168168 return result;
169169 }
170170
171171 @McpPrompt(
172- name = "document-summary",
172+ name = "document-summary",
173173 description = "Generate document summary prompt")
174174 public GetPromptResult documentSummaryPrompt(
175175 @McpArg(name = "docId", required = true) String docId,
176176 @McpArg(name = "length", required = false) String length) {
177-
177+
178178 Document doc = documents.get(docId);
179179 if (doc == null) {
180180 return new GetPromptResult("Error",
181- List.of(new PromptMessage(Role.SYSTEM,
181+ List.of(new PromptMessage(Role.SYSTEM,
182182 new TextContent("Document not found"))));
183183 }
184-
184+
185185 String promptText = String.format(
186186 "Please summarize the following document in %s:\n\n%s",
187187 length != null ? length : "a few paragraphs",
188188 doc.getContent()
189189 );
190-
190+
191191 return new GetPromptResult("Document Summary",
192192 List.of(new PromptMessage(Role.USER, new TextContent(promptText))));
193193 }
@@ -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