diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java index 2fe08af..471cd83 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java @@ -56,7 +56,9 @@ * @see McpLogging * @see SyncMcpLoggingMethodCallback * @see LoggingMessageNotification + * @deprecated Use {@link SyncMcpLoggingProvider} instead. */ +@Deprecated public class SyncMcpLogginProvider { private final List loggingConsumerObjects; diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java new file mode 100644 index 0000000..cfc77bb --- /dev/null +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java @@ -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. + * + *

+ * 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. + * + *

+ * Example usage:

{@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> consumers = provider.getLoggingConsumers();
+ *
+ * // Add the consumers to the client features
+ * McpClientFeatures.Sync clientFeatures = new McpClientFeatures.Sync(
+ *     clientInfo, clientCapabilities, roots,
+ *     toolsChangeConsumers, resourcesChangeConsumers, promptsChangeConsumers,
+ *     consumers, samplingHandler);
+ * }
+ * + * @author Christian Tzolov + * @see McpLogging + * @see SyncMcpLoggingMethodCallback + * @see LoggingMessageNotification + */ +public class SyncMcpLoggingProvider { + + private final List loggingConsumerObjects; + + /** + * Create a new SyncMcpLoggingConsumerProvider. + * @param loggingConsumerObjects the objects containing methods annotated with + * {@link McpLogging} + */ + public SyncMcpLoggingProvider(List 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 getLoggingSpecifications() { + + List 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 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(); + } + +} diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java index 6fbd11f..3ba23de 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java @@ -17,7 +17,7 @@ import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; /** - * Tests for {@link SyncMcpLogginProvider}. + * Tests for {@link SyncMcpLoggingProvider}. * * @author Christian Tzolov */ @@ -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 specifications = provider.getLoggingSpecifications(); List> consumers = specifications.stream() @@ -89,7 +89,7 @@ void testGetLoggingConsumers() { @Test void testEmptyList() { - SyncMcpLogginProvider provider = new SyncMcpLogginProvider(List.of()); + SyncMcpLoggingProvider provider = new SyncMcpLoggingProvider(List.of()); List> consumers = provider.getLoggingSpecifications() .stream() @@ -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> consumers = provider.getLoggingSpecifications() .stream()