From 36504465587d474e460bdac95ead7c7996970304 Mon Sep 17 00:00:00 2001 From: Michael Vorburger Date: Tue, 19 Aug 2025 00:25:33 +0200 Subject: [PATCH 1/2] feat: Introduce Slf4jLoggingConsumer --- .../client/Slf4jLoggingConsumer.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java b/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java new file mode 100644 index 000000000..c5f173595 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025 the original author or authors. + */ +package io.modelcontextprotocol.client; + +import java.util.function.Consumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +import io.modelcontextprotocol.client.McpClient.SyncSpec; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; + +/** + * MCP Client-side consumer which logs received messages from MCP Servers using SLF4J. + * + *

+ * Use this for {@link SyncSpec#loggingConsumer(Consumer)} to log received MCP messages. + * + * @author Michael Vorburger.ch + */ +public class Slf4jLoggingConsumer implements Consumer { + + // This class originated in + // https://github.com/enola-dev/enola/blob/ffc004666ea7f71357562ef12464d2b9fdbf9dbd/java/dev/enola/ai/mcp/McpServer.java#L29 + // where it was used for https://docs.enola.dev/concepts/mcp/. + // + // It then found its way into Google's Agent Development Kit (ADK) in + // https://github.com/google/adk-java/pull/370. It's now been moved here to be useful + // to others. + + @Override + public void accept(LoggingMessageNotification notif) { + Logger log = LoggerFactory.getLogger(notif.logger()); + if (notif.meta().isEmpty()) { + // If no meta, then just log the data as a message + log.atLevel(convert(notif.level())).log(notif.data()); + } + else { + // If there is meta, then log it as a structured log message + var builder = log.atLevel(convert(notif.level())).setMessage(notif.data()); + notif.meta().forEach((key, value) -> builder.addKeyValue(key, value)); + builder.log(); + } + } + + private Level convert(McpSchema.LoggingLevel level) { + return switch (level) { + case DEBUG -> Level.DEBUG; + case INFO, NOTICE -> Level.INFO; + case WARNING -> Level.WARN; + case ERROR, CRITICAL, ALERT, EMERGENCY -> Level.ERROR; + }; + } + +} From bea17fba9a247e560b9a1e0652bf60b568d00221 Mon Sep 17 00:00:00 2001 From: Michael Vorburger Date: Tue, 19 Aug 2025 01:38:04 +0200 Subject: [PATCH 2/2] fix: Slf4jLoggingConsumer use itself as Logger, not the server's --- .../modelcontextprotocol/client/Slf4jLoggingConsumer.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java b/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java index c5f173595..f35ba2329 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java @@ -31,16 +31,17 @@ public class Slf4jLoggingConsumer implements Consumer builder.addKeyValue(key, value)); builder.log(); }