Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
* @see McpLogging
* @see SyncMcpLoggingMethodCallback
* @see LoggingMessageNotification
* @deprecated Use {@link SyncMcpLoggingProvider} instead.
*/
@Deprecated
public class SyncMcpLogginProvider {

private final List<Object> loggingConsumerObjects;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2025-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springaicommunity.mcp.provider.logging;

import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.springaicommunity.mcp.annotation.McpLogging;
import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification;
import org.springaicommunity.mcp.method.logging.SyncMcpLoggingMethodCallback;

import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
import io.modelcontextprotocol.util.Assert;
import reactor.core.publisher.Mono;

/**
* Provider for synchronous logging consumer callbacks.
*
* <p>
* This class scans a list of objects for methods annotated with {@link McpLogging} and
* creates {@link Consumer} callbacks for them. These callbacks can be used to handle
* logging message notifications from MCP servers.
*
* <p>
* Example usage: <pre>{@code
* // Create a provider with a list of objects containing @McpLoggingConsumer methods
* SyncMcpLoggingConsumerProvider provider = new SyncMcpLoggingConsumerProvider(List.of(loggingHandler));
*
* // Get the list of logging consumer callbacks
* List<Consumer<LoggingMessageNotification>> consumers = provider.getLoggingConsumers();
*
* // Add the consumers to the client features
* McpClientFeatures.Sync clientFeatures = new McpClientFeatures.Sync(
* clientInfo, clientCapabilities, roots,
* toolsChangeConsumers, resourcesChangeConsumers, promptsChangeConsumers,
* consumers, samplingHandler);
* }</pre>
*
* @author Christian Tzolov
* @see McpLogging
* @see SyncMcpLoggingMethodCallback
* @see LoggingMessageNotification
*/
public class SyncMcpLoggingProvider {

private final List<Object> loggingConsumerObjects;

/**
* Create a new SyncMcpLoggingConsumerProvider.
* @param loggingConsumerObjects the objects containing methods annotated with
* {@link McpLogging}
*/
public SyncMcpLoggingProvider(List<Object> loggingConsumerObjects) {
Assert.notNull(loggingConsumerObjects, "loggingConsumerObjects cannot be null");
this.loggingConsumerObjects = loggingConsumerObjects;
}

/**
* Get the list of logging consumer callbacks.
* @return the list of logging consumer callbacks
*/
public List<SyncLoggingSpecification> getLoggingSpecifications() {

List<SyncLoggingSpecification> loggingConsumers = this.loggingConsumerObjects.stream()
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
.filter(method -> method.isAnnotationPresent(McpLogging.class))
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
.map(mcpLoggingConsumerMethod -> {
var loggingConsumerAnnotation = mcpLoggingConsumerMethod.getAnnotation(McpLogging.class);

Consumer<LoggingMessageNotification> methodCallback = SyncMcpLoggingMethodCallback.builder()
.method(mcpLoggingConsumerMethod)
.bean(consumerObject)
.loggingConsumer(loggingConsumerAnnotation)
.build();

return new SyncLoggingSpecification(loggingConsumerAnnotation.clients(), methodCallback);
})
.toList())
.flatMap(List::stream)
.toList();

return loggingConsumers;
}

/**
* Returns the methods of the given bean class.
* @param bean the bean instance
* @return the methods of the bean class
*/
protected Method[] doGetClassMethods(Object bean) {
return bean.getClass().getDeclaredMethods();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;

/**
* Tests for {@link SyncMcpLogginProvider}.
* Tests for {@link SyncMcpLoggingProvider}.
*
* @author Christian Tzolov
*/
Expand Down Expand Up @@ -60,7 +60,7 @@ public void notAnnotatedMethod(LoggingMessageNotification notification) {
@Test
void testGetLoggingConsumers() {
LoggingHandler loggingHandler = new LoggingHandler();
SyncMcpLogginProvider provider = new SyncMcpLogginProvider(List.of(loggingHandler));
SyncMcpLoggingProvider provider = new SyncMcpLoggingProvider(List.of(loggingHandler));

List<SyncLoggingSpecification> specifications = provider.getLoggingSpecifications();
List<Consumer<LoggingMessageNotification>> consumers = specifications.stream()
Expand Down Expand Up @@ -89,7 +89,7 @@ void testGetLoggingConsumers() {

@Test
void testEmptyList() {
SyncMcpLogginProvider provider = new SyncMcpLogginProvider(List.of());
SyncMcpLoggingProvider provider = new SyncMcpLoggingProvider(List.of());

List<Consumer<LoggingMessageNotification>> consumers = provider.getLoggingSpecifications()
.stream()
Expand All @@ -103,7 +103,7 @@ void testEmptyList() {
void testMultipleObjects() {
LoggingHandler handler1 = new LoggingHandler();
LoggingHandler handler2 = new LoggingHandler();
SyncMcpLogginProvider provider = new SyncMcpLogginProvider(List.of(handler1, handler2));
SyncMcpLoggingProvider provider = new SyncMcpLoggingProvider(List.of(handler1, handler2));

List<Consumer<LoggingMessageNotification>> consumers = provider.getLoggingSpecifications()
.stream()
Expand Down