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..f35ba2329 --- /dev/null +++ b/mcp/src/main/java/io/modelcontextprotocol/client/Slf4jLoggingConsumer.java @@ -0,0 +1,59 @@ +/* + * 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. + + private static final Logger LOG = LoggerFactory.getLogger(Slf4jLoggingConsumer.class); + + @Override + public void accept(LoggingMessageNotification notif) { + 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; + }; + } + +}