Skip to content

Commit 62b6f8a

Browse files
committed
feat: adds McpLogger and mcp-logging-slf4j
1 parent 17543ed commit 62b6f8a

File tree

20 files changed

+431
-208
lines changed

20 files changed

+431
-208
lines changed

mcp-logging-slf4j/pom.xml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.modelcontextprotocol.sdk</groupId>
8+
<artifactId>mcp-parent</artifactId>
9+
<version>0.11.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>mcp-logging-slf4j</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Java SDK MCP SLF4J</name>
15+
<description>Java SDK Logging SLF4J of the Model Context Protocol</description>
16+
<url>https://github.com/modelcontextprotocol/java-sdk</url>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.modelcontextprotocol.sdk</groupId>
21+
<artifactId>mcp-spi</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>org.slf4j</groupId>
27+
<artifactId>slf4j-api</artifactId>
28+
<version>${slf4j-api.version}</version>
29+
</dependency>
30+
</dependencies>
31+
32+
<!-- This module does not use mockito -->
33+
<build>
34+
<plugins>
35+
<plugin>
36+
<groupId>org.apache.maven.plugins</groupId>
37+
<artifactId>maven-surefire-plugin</artifactId>
38+
<configuration>
39+
<argLine>${surefireArgLine}</argLine>
40+
</configuration>
41+
</plugin>
42+
</plugins>
43+
</build>
44+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*/
4+
5+
package io.modelcontextprotocol.logger;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
/**
11+
* @author Aliaksei Darafeyeu
12+
*/
13+
public class Slf4jMcpLogger implements McpLogger {
14+
15+
private final Logger delegate;
16+
17+
public Slf4jMcpLogger(Class<?> clazz) {
18+
this.delegate = LoggerFactory.getLogger(clazz);
19+
}
20+
21+
@Override
22+
public void debug(final String message) {
23+
delegate.debug(message);
24+
}
25+
26+
@Override
27+
public void info(final String message) {
28+
delegate.info(message);
29+
}
30+
31+
@Override
32+
public void warn(final String message) {
33+
delegate.warn(message);
34+
}
35+
36+
@Override
37+
public void warn(final String message, final Throwable t) {
38+
delegate.warn(message, t);
39+
}
40+
41+
@Override
42+
public void error(final String message, final Throwable t) {
43+
delegate.error(message, t);
44+
}
45+
46+
}

mcp-reactor/pom.xml

+6-6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@
7676
<artifactId>mcp-schema-jackson</artifactId>
7777
<version>${project.version}</version>
7878
</dependency>
79+
<dependency>
80+
<groupId>io.modelcontextprotocol.sdk</groupId>
81+
<artifactId>mcp-logging-slf4j</artifactId>
82+
<version>${project.version}</version>
83+
</dependency>
7984
<!--Reactor-->
8085
<dependency>
8186
<groupId>org.reactivestreams</groupId>
@@ -93,12 +98,7 @@
9398
<version>${jakarta.servlet.version}</version>
9499
<scope>provided</scope>
95100
</dependency>
96-
<!--Logs-->
97-
<dependency>
98-
<groupId>org.slf4j</groupId>
99-
<artifactId>slf4j-api</artifactId>
100-
<version>${slf4j-api.version}</version>
101-
</dependency>
101+
102102
<!--For test-->
103103
<dependency>
104104
<groupId>org.assertj</groupId>

mcp-reactor/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

+16-15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.concurrent.atomic.AtomicBoolean;
1414
import java.util.function.Function;
1515

16+
import io.modelcontextprotocol.logger.McpLogger;
1617
import io.modelcontextprotocol.schema.McpType;
1718
import io.modelcontextprotocol.session.McpClientSession;
1819
import io.modelcontextprotocol.session.McpClientSession.NotificationHandler;
@@ -33,8 +34,6 @@
3334
import io.modelcontextprotocol.spec.McpTransport;
3435
import io.modelcontextprotocol.util.Assert;
3536
import io.modelcontextprotocol.util.Utils;
36-
import org.slf4j.Logger;
37-
import org.slf4j.LoggerFactory;
3837
import reactor.core.publisher.Flux;
3938
import reactor.core.publisher.Mono;
4039
import reactor.core.publisher.Sinks;
@@ -78,8 +77,6 @@
7877
*/
7978
public class McpAsyncClient implements McpClient {
8079

81-
private static final Logger logger = LoggerFactory.getLogger(McpAsyncClient.class);
82-
8380
private static final McpType<Void> VOID_TYPE_REFERENCE = McpType.of(Void.class);
8481

8582
protected final Sinks.One<McpSchema.InitializeResult> initializedSink = Sinks.one();
@@ -145,6 +142,8 @@ public class McpAsyncClient implements McpClient {
145142
*/
146143
private final McpTransport transport;
147144

145+
private final McpLogger logger;
146+
148147
/**
149148
* Supported protocol versions.
150149
*/
@@ -159,7 +158,7 @@ public class McpAsyncClient implements McpClient {
159158
* @param features the MCP Client supported features.
160159
*/
161160
McpAsyncClient(McpClientTransport transport, Duration requestTimeout, Duration initializationTimeout,
162-
McpClientFeatures.Async features) {
161+
McpClientFeatures.Async features, McpLogger logger) {
163162

164163
Assert.notNull(transport, "Transport must not be null");
165164
Assert.notNull(requestTimeout, "Request timeout must not be null");
@@ -170,6 +169,7 @@ public class McpAsyncClient implements McpClient {
170169
this.transport = transport;
171170
this.roots = new ConcurrentHashMap<>(features.roots());
172171
this.initializationTimeout = initializationTimeout;
172+
this.logger = logger;
173173

174174
// Request Handlers
175175
Map<String, RequestHandler<?>> requestHandlers = new HashMap<>();
@@ -194,7 +194,7 @@ public class McpAsyncClient implements McpClient {
194194
// Tools Change Notification
195195
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumersFinal = new ArrayList<>();
196196
toolsChangeConsumersFinal
197-
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Tools changed: {}", notification)));
197+
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Tools changed: %s".formatted(notification))));
198198

199199
if (!Utils.isEmpty(features.toolsChangeConsumers())) {
200200
toolsChangeConsumersFinal.addAll(features.toolsChangeConsumers());
@@ -204,8 +204,8 @@ public class McpAsyncClient implements McpClient {
204204

205205
// Resources Change Notification
206206
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumersFinal = new ArrayList<>();
207-
resourcesChangeConsumersFinal
208-
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Resources changed: {}", notification)));
207+
resourcesChangeConsumersFinal.add((notification) -> Mono
208+
.fromRunnable(() -> logger.debug("Resources changed: %s".formatted(notification))));
209209

210210
if (!Utils.isEmpty(features.resourcesChangeConsumers())) {
211211
resourcesChangeConsumersFinal.addAll(features.resourcesChangeConsumers());
@@ -216,8 +216,8 @@ public class McpAsyncClient implements McpClient {
216216

217217
// Prompts Change Notification
218218
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumersFinal = new ArrayList<>();
219-
promptsChangeConsumersFinal
220-
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Prompts changed: {}", notification)));
219+
promptsChangeConsumersFinal.add(
220+
(notification) -> Mono.fromRunnable(() -> logger.debug("Prompts changed: %s".formatted(notification))));
221221
if (!Utils.isEmpty(features.promptsChangeConsumers())) {
222222
promptsChangeConsumersFinal.addAll(features.promptsChangeConsumers());
223223
}
@@ -226,7 +226,8 @@ public class McpAsyncClient implements McpClient {
226226

227227
// Utility Logging Notification
228228
List<Function<LoggingMessageNotification, Mono<Void>>> loggingConsumersFinal = new ArrayList<>();
229-
loggingConsumersFinal.add((notification) -> Mono.fromRunnable(() -> logger.debug("Logging: {}", notification)));
229+
loggingConsumersFinal
230+
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Logging: %s".formatted(notification))));
230231
if (!Utils.isEmpty(features.loggingConsumers())) {
231232
loggingConsumersFinal.addAll(features.loggingConsumers());
232233
}
@@ -344,9 +345,9 @@ public Mono<McpSchema.InitializeResult> initialize() {
344345
this.serverInstructions = initializeResult.instructions();
345346
this.serverInfo = initializeResult.serverInfo();
346347

347-
logger.info("Server response with Protocol: {}, Capabilities: {}, Info: {} and Instructions {}",
348+
logger.info("Server response with Protocol: %s, Capabilities: %s, Info: %s and Instructions %s".formatted(
348349
initializeResult.protocolVersion(), initializeResult.capabilities(), initializeResult.serverInfo(),
349-
initializeResult.instructions());
350+
initializeResult.instructions()));
350351

351352
if (!this.protocolVersions.contains(initializeResult.protocolVersion())) {
352353
return Mono.error(new McpError(
@@ -414,7 +415,7 @@ public Mono<Void> addRoot(Root root) {
414415

415416
this.roots.put(root.uri(), root);
416417

417-
logger.debug("Added root: {}", root);
418+
logger.debug("Added root: %s".formatted(root));
418419

419420
if (this.clientCapabilities.roots().listChanged()) {
420421
if (this.isInitialized()) {
@@ -445,7 +446,7 @@ public Mono<Void> removeRoot(String rootUri) {
445446
Root removed = this.roots.remove(rootUri);
446447

447448
if (removed != null) {
448-
logger.debug("Removed Root: {}", rootUri);
449+
logger.debug("Removed Root: %s".formatted(rootUri));
449450
if (this.clientCapabilities.roots().listChanged()) {
450451
if (this.isInitialized()) {
451452
return this.rootsListChangedNotification();

mcp-reactor/src/main/java/io/modelcontextprotocol/client/McpClientFactory.java

+36-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.function.Consumer;
1313
import java.util.function.Function;
1414

15+
import io.modelcontextprotocol.logger.McpLogger;
16+
import io.modelcontextprotocol.logger.Slf4jMcpLogger;
1517
import io.modelcontextprotocol.spec.McpClientTransport;
1618
import io.modelcontextprotocol.schema.McpSchema;
1719
import io.modelcontextprotocol.spec.McpTransport;
@@ -95,6 +97,7 @@
9597
*
9698
* @author Christian Tzolov
9799
* @author Dariusz Jędrzejczyk
100+
* @author Aliaksei Darafeyeu
98101
* @see McpAsyncClient
99102
* @see McpSyncClient
100103
* @see McpTransport
@@ -175,6 +178,8 @@ class SyncSpec {
175178

176179
private Function<CreateMessageRequest, CreateMessageResult> samplingHandler;
177180

181+
private McpLogger logger = new Slf4jMcpLogger(McpSyncClient.class);
182+
178183
private SyncSpec(McpClientTransport transport) {
179184
Assert.notNull(transport, "Transport must not be null");
180185
this.transport = transport;
@@ -356,20 +361,31 @@ public SyncSpec loggingConsumers(List<Consumer<McpSchema.LoggingMessageNotificat
356361
return this;
357362
}
358363

364+
/**
365+
* Adds logger.
366+
* @param logger McpLogger
367+
* @return his builder instance for method chaining
368+
*/
369+
public SyncSpec logger(final McpLogger logger) {
370+
Assert.notNull(logger, "logger must not be null");
371+
this.logger = logger;
372+
return this;
373+
}
374+
359375
/**
360376
* Create an instance of {@link McpSyncClient} with the provided configurations or
361377
* sensible defaults.
362378
* @return a new instance of {@link McpSyncClient}.
363379
*/
364380
public McpSyncClient build() {
365-
McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities,
381+
final McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities,
366382
this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers,
367383
this.loggingConsumers, this.samplingHandler);
384+
final McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
385+
final McpAsyncClient delegate = new McpAsyncClient(transport, this.requestTimeout,
386+
this.initializationTimeout, asyncFeatures, logger);
368387

369-
McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
370-
371-
return new McpSyncClient(
372-
new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout, asyncFeatures));
388+
return new McpSyncClient(delegate, logger);
373389
}
374390

375391
}
@@ -414,6 +430,8 @@ class AsyncSpec {
414430

415431
private Function<CreateMessageRequest, Mono<CreateMessageResult>> samplingHandler;
416432

433+
private McpLogger logger = new Slf4jMcpLogger(McpAsyncClient.class);
434+
417435
private AsyncSpec(McpClientTransport transport) {
418436
Assert.notNull(transport, "Transport must not be null");
419437
this.transport = transport;
@@ -597,6 +615,17 @@ public AsyncSpec loggingConsumers(
597615
return this;
598616
}
599617

618+
/**
619+
* Adds logger.
620+
* @param logger McpLogger
621+
* @return his builder instance for method chaining
622+
*/
623+
public AsyncSpec logger(final McpLogger logger) {
624+
Assert.notNull(logger, "logger must not be null");
625+
this.logger = logger;
626+
return this;
627+
}
628+
600629
/**
601630
* Create an instance of {@link McpAsyncClient} with the provided configurations
602631
* or sensible defaults.
@@ -606,7 +635,8 @@ public McpAsyncClient build() {
606635
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
607636
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
608637
this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers,
609-
this.loggingConsumers, this.samplingHandler));
638+
this.loggingConsumers, this.samplingHandler),
639+
logger);
610640
}
611641

612642
}

0 commit comments

Comments
 (0)