From e8a266467cdf0b06846074b46cb2c82f029711b2 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Thu, 10 Apr 2025 20:27:50 +0200 Subject: [PATCH] feat(mcp): Add MCP logging support and refactor code - Add logging consumer to MCP client specification - Implement logging notifications for sampling start/finish in WeatherService - Refactor WeatherService to use McpToolUtils and StringBuilder Signed-off-by: Christian Tzolov --- .../samples/client/McpClientApplication.java | 5 ++ .../ai/mcp/sample/server/WeatherService.java | 68 +++++++++++-------- .../ai/mcp/samples/brave/Application.java | 1 - 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/model-context-protocol/sampling/mcp-sampling-client/src/main/java/org/springframework/ai/mcp/samples/client/McpClientApplication.java b/model-context-protocol/sampling/mcp-sampling-client/src/main/java/org/springframework/ai/mcp/samples/client/McpClientApplication.java index cca303f..8ebe15d 100644 --- a/model-context-protocol/sampling/mcp-sampling-client/src/main/java/org/springframework/ai/mcp/samples/client/McpClientApplication.java +++ b/model-context-protocol/sampling/mcp-sampling-client/src/main/java/org/springframework/ai/mcp/samples/client/McpClientApplication.java @@ -68,6 +68,11 @@ public class McpClientApplication { McpSyncClientCustomizer samplingCustomizer(Map chatClients) { return (name, mcpClientSpec) -> { + + mcpClientSpec = mcpClientSpec.loggingConsumer(logingMessage -> { + System.out.println("MCP LOGGING: [" + logingMessage.level() + "] " + logingMessage.data()); + }); + mcpClientSpec.sampling(llmRequest -> { var userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text(); String modelHint = llmRequest.modelPreferences().hints().get(0).name(); diff --git a/model-context-protocol/sampling/mcp-weather-webmvc-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java b/model-context-protocol/sampling/mcp-weather-webmvc-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java index baac096..1c0494d 100644 --- a/model-context-protocol/sampling/mcp-weather-webmvc-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java +++ b/model-context-protocol/sampling/mcp-weather-webmvc-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java @@ -21,10 +21,13 @@ import java.util.List; import io.modelcontextprotocol.server.McpSyncServerExchange; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; +import io.modelcontextprotocol.spec.McpSchema.LoggingLevel; +import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; import io.modelcontextprotocol.spec.McpSchema.ModelPreferences; import org.slf4j.Logger; import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.mcp.McpToolUtils; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; @@ -72,43 +75,54 @@ public class WeatherService { public String callMcpSampling(ToolContext toolContext, WeatherResponse weatherResponse) { - String openAiWeatherPoem = ""; - String anthropicWeatherPoem = ""; + StringBuilder openAiWeatherPoem = new StringBuilder(); + StringBuilder anthropicWeatherPoem = new StringBuilder(); - if (toolContext != null && toolContext.getContext().containsKey("exchange")) { + McpToolUtils.getMcpExchange(toolContext) + .ifPresent(exchange -> { - // Spring AI MCP Auto-configuration injects the McpSyncServerExchange into the ToolContext under the key "exchange" - McpSyncServerExchange exchange = (McpSyncServerExchange) toolContext.getContext().get("exchange"); - if (exchange.getClientCapabilities().sampling() != null) { - var messageRequestBuilder = McpSchema.CreateMessageRequest.builder() - .systemPrompt("You are a poet!") - .messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER, - new McpSchema.TextContent( - "Please write a poem about thius weather forecast (temperature is in Celsious). Use markdown format :\n " - + ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse))))); + exchange.loggingNotification(LoggingMessageNotification.builder() + .level(LoggingLevel.INFO) + .data("Start sampling") + .build()); - var opeAiLlmMessageRequest = messageRequestBuilder - .modelPreferences(ModelPreferences.builder().addHint("openai").build()) - .build(); - CreateMessageResult openAiLlmResponse = exchange.createMessage(opeAiLlmMessageRequest); + if (exchange.getClientCapabilities().sampling() != null) { + var messageRequestBuilder = McpSchema.CreateMessageRequest.builder() + .systemPrompt("You are a poet!") + .messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER, + new McpSchema.TextContent( + "Please write a poem about thius weather forecast (temperature is in Celsious). Use markdown format :\n " + + ModelOptionsUtils + .toJsonStringPrettyPrinter(weatherResponse))))); - openAiWeatherPoem = ((McpSchema.TextContent) openAiLlmResponse.content()).text(); + var opeAiLlmMessageRequest = messageRequestBuilder + .modelPreferences(ModelPreferences.builder().addHint("openai").build()) + .build(); + CreateMessageResult openAiLlmResponse = exchange.createMessage(opeAiLlmMessageRequest); - var anthropicLlmMessageRequest = messageRequestBuilder - .modelPreferences(ModelPreferences.builder().addHint("anthropic").build()) - .build(); - CreateMessageResult anthropicAiLlmResponse = exchange.createMessage(anthropicLlmMessageRequest); + openAiWeatherPoem.append(((McpSchema.TextContent) openAiLlmResponse.content()).text()); - anthropicWeatherPoem = ((McpSchema.TextContent) anthropicAiLlmResponse.content()).text(); + var anthropicLlmMessageRequest = messageRequestBuilder + .modelPreferences(ModelPreferences.builder().addHint("anthropic").build()) + .build(); + CreateMessageResult anthropicAiLlmResponse = exchange.createMessage(anthropicLlmMessageRequest); - } - } + anthropicWeatherPoem.append(((McpSchema.TextContent) anthropicAiLlmResponse.content()).text()); - String responseWithPoems = "OpenAI poem about the weather: " + openAiWeatherPoem + "\n\n" + - "Anthropic poem about the weather: " + anthropicWeatherPoem + "\n" + } + + exchange.loggingNotification(LoggingMessageNotification.builder() + .level(LoggingLevel.INFO) + .data("Finish Sampling") + .build()); + + }); + + String responseWithPoems = "OpenAI poem about the weather: " + openAiWeatherPoem.toString() + "\n\n" + + "Anthropic poem about the weather: " + anthropicWeatherPoem.toString() + "\n" + ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse); - logger.info(anthropicWeatherPoem, responseWithPoems); + logger.info(anthropicWeatherPoem.toString(), responseWithPoems.toString()); return responseWithPoems; diff --git a/model-context-protocol/web-search/brave-starter/src/main/java/org/springframework/ai/mcp/samples/brave/Application.java b/model-context-protocol/web-search/brave-starter/src/main/java/org/springframework/ai/mcp/samples/brave/Application.java index 1923941..b2aabc8 100644 --- a/model-context-protocol/web-search/brave-starter/src/main/java/org/springframework/ai/mcp/samples/brave/Application.java +++ b/model-context-protocol/web-search/brave-starter/src/main/java/org/springframework/ai/mcp/samples/brave/Application.java @@ -21,7 +21,6 @@ import io.modelcontextprotocol.client.McpSyncClient; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; -import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;