feat: Add default logging for MCP notification handlers
- Adds default logging for tools, resources and prompts change notifications. - Introduces Utils class with collection and string helper methods. - Ensures notification handlers are always registered with at least a default logging consumer.
This commit is contained in:
parent
975578b64b
commit
2ea5527ade
2 changed files with 96 additions and 11 deletions
|
|
@ -40,6 +40,7 @@ import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
|
|||
import org.springframework.ai.mcp.spec.McpSchema.PaginatedRequest;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.Root;
|
||||
import org.springframework.ai.mcp.spec.McpTransport;
|
||||
import org.springframework.ai.mcp.util.Utils;
|
||||
|
||||
/**
|
||||
* The Model Context Protocol (MCP) client implementation that provides asynchronous
|
||||
|
|
@ -85,29 +86,44 @@ public class McpAsyncClient {
|
|||
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
|
||||
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers) {
|
||||
|
||||
// Request Handlers
|
||||
Map<String, RequestHandler> requestHanlers = new HashMap<>();
|
||||
|
||||
// Roots List Request Handler
|
||||
if (rootsListProviders != null && !rootsListProviders.isEmpty()) {
|
||||
requestHanlers.put("roots/list", rootsListRequestHandler(rootsListProviders));
|
||||
this.rootCapabilities = new McpSchema.ClientCapabilities.RootCapabilities(rootsListChangedNotification);
|
||||
}
|
||||
|
||||
// Notification Handlers
|
||||
Map<String, NotificationHandler> notificationHandlers = new HashMap<>();
|
||||
|
||||
if (toolsChangeConsumers != null && !toolsChangeConsumers.isEmpty()) {
|
||||
notificationHandlers.put("notifications/tools/list_changed",
|
||||
toolsChangeNotificationHandler(toolsChangeConsumers));
|
||||
// Tools Change Notification
|
||||
List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumersFinal = new ArrayList<>();
|
||||
toolsChangeConsumersFinal.add((notification) -> logger.info("Tools changed: {}", notification));
|
||||
if (!Utils.isEmpty(toolsChangeConsumers)) {
|
||||
toolsChangeConsumersFinal.addAll(toolsChangeConsumers);
|
||||
}
|
||||
notificationHandlers.put("notifications/tools/list_changed",
|
||||
toolsChangeNotificationHandler(toolsChangeConsumersFinal));
|
||||
|
||||
if (resourcesChangeConsumers != null && !resourcesChangeConsumers.isEmpty()) {
|
||||
notificationHandlers.put("notifications/resources/list_changed",
|
||||
resourcesChangeNotificationHandler(resourcesChangeConsumers));
|
||||
// Resources Change Notification
|
||||
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumersFinal = new ArrayList<>();
|
||||
resourcesChangeConsumersFinal.add((notification) -> logger.info("Resources changed: {}", notification));
|
||||
if (!Utils.isEmpty(resourcesChangeConsumers)) {
|
||||
resourcesChangeConsumersFinal.addAll(resourcesChangeConsumers);
|
||||
}
|
||||
notificationHandlers.put("notifications/resources/list_changed",
|
||||
resourcesChangeNotificationHandler(resourcesChangeConsumersFinal));
|
||||
|
||||
if (promptsChangeConsumers != null && !promptsChangeConsumers.isEmpty()) {
|
||||
notificationHandlers.put("notifications/prompts/list_changed",
|
||||
promptsChangeNotificationHandler(promptsChangeConsumers));
|
||||
// Prompts Change Notification
|
||||
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumersFinal = new ArrayList<>();
|
||||
promptsChangeConsumersFinal.add((notification) -> logger.info("Prompts changed: {}", notification));
|
||||
if (!Utils.isEmpty(promptsChangeConsumers)) {
|
||||
promptsChangeConsumersFinal.addAll(promptsChangeConsumers);
|
||||
}
|
||||
notificationHandlers.put("notifications/prompts/list_changed",
|
||||
promptsChangeNotificationHandler(promptsChangeConsumersFinal));
|
||||
|
||||
this.mcpSession = new DefaultMcpSession(requestTimeout, transport, requestHanlers, notificationHandlers);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2024-2024 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.springframework.ai.mcp.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* Check whether the given {@code String} contains actual <em>text</em>.
|
||||
* <p>
|
||||
* More specifically, this method returns {@code true} if the {@code String} is not
|
||||
* {@code null}, its length is greater than 0, and it contains at least one
|
||||
* non-whitespace character.
|
||||
* @param str the {@code String} to check (may be {@code null})
|
||||
* @return {@code true} if the {@code String} is not {@code null}, its length is
|
||||
* greater than 0, and it does not contain whitespace only
|
||||
* @see Character#isWhitespace
|
||||
*/
|
||||
public static boolean hasText(@Nullable String str) {
|
||||
return (str != null && !str.isBlank());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the supplied Collection is {@code null} or empty. Otherwise,
|
||||
* return {@code false}.
|
||||
* @param collection the Collection to check
|
||||
* @return whether the given Collection is empty
|
||||
*/
|
||||
@Contract("null -> true")
|
||||
public static boolean isEmpty(@Nullable Collection<?> collection) {
|
||||
return (collection == null || collection.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the supplied Map is {@code null} or empty. Otherwise, return
|
||||
* {@code false}.
|
||||
* @param map the Map to check
|
||||
* @return whether the given Map is empty
|
||||
*/
|
||||
@Contract("null -> true")
|
||||
public static boolean isEmpty(@Nullable Map<?, ?> map) {
|
||||
return (map == null || map.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue