CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-mcp

Spring Framework integration for Model Context Protocol (MCP), providing Spring AI function calling capabilities and Spring-friendly abstractions for MCP clients and MCP servers

Overview
Eval results
Files

event-handling.mddocs/reference/

Event Handling

React to tool changes in MCP servers through Spring's application event system using McpToolsChangedEvent.

Import

import org.springframework.ai.mcp.McpToolsChangedEvent;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ApplicationEventPublisher;
import java.util.List;

McpToolsChangedEvent

public class McpToolsChangedEvent extends ApplicationEvent {
    public McpToolsChangedEvent(String connectionName, List<McpSchema.Tool> tools);

    String getConnectionName();
    List<McpSchema.Tool> getTools();
}

Event class indicating that tools have changed in an MCP connection.

Constructor

public McpToolsChangedEvent(String connectionName, List<McpSchema.Tool> tools)

Parameters

  • connectionName: Name identifying the MCP connection where tools changed
  • tools: Updated list of tools available from the connection

Methods

String getConnectionName()

Returns the name of the MCP connection where tools changed.

List<McpSchema.Tool> getTools()

Returns the updated list of tools available from the connection.

Publishing Events

Using ApplicationEventPublisher

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class McpConnectionManager {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void notifyToolsChanged(String serverName, List<McpSchema.Tool> newTools) {
        McpToolsChangedEvent event = new McpToolsChangedEvent(serverName, newTools);
        eventPublisher.publishEvent(event);
    }

    public void refreshServerTools(McpSyncClient client) {
        String serverName = client.getClientInfo().name();

        // Get updated tool list
        McpSchema.ListToolsResult result = client.listTools();
        List<McpSchema.Tool> tools = result.tools();

        // Notify listeners
        notifyToolsChanged(serverName, tools);
    }
}

Listening to Events

Automatic Listening in Providers

Both SyncMcpToolCallbackProvider and AsyncMcpToolCallbackProvider automatically listen for McpToolsChangedEvent and invalidate their caches when received.

// Providers automatically implement ApplicationListener<McpToolsChangedEvent>
public class SyncMcpToolCallbackProvider
    implements ToolCallbackProvider, ApplicationListener<McpToolsChangedEvent> {

    @Override
    public void onApplicationEvent(McpToolsChangedEvent event) {
        this.invalidateCache();  // Automatically invalidates on event
    }
}

Custom Event Listeners

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class McpToolEventHandler {

    @EventListener
    public void handleToolsChanged(McpToolsChangedEvent event) {
        System.out.println("Tools changed in connection: " + event.getConnectionName());
        System.out.println("New tool count: " + event.getTools().size());

        // Log each tool
        for (McpSchema.Tool tool : event.getTools()) {
            System.out.println("  - " + tool.name() + ": " + tool.description());
        }
    }
}

Implementing ApplicationListener

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class CustomMcpListener implements ApplicationListener<McpToolsChangedEvent> {

    @Override
    public void onApplicationEvent(McpToolsChangedEvent event) {
        String connectionName = event.getConnectionName();
        List<McpSchema.Tool> tools = event.getTools();

        // Custom handling logic
        updateToolRegistry(connectionName, tools);
        notifyClients(connectionName);
    }

    private void updateToolRegistry(String connection, List<McpSchema.Tool> tools) {
        // Update your tool registry
    }

    private void notifyClients(String connection) {
        // Notify connected clients
    }
}

Complete Example

import org.springframework.ai.mcp.*;
import org.springframework.context.*;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.Scheduled;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;

@Component
public class McpToolMonitor {

    private final ApplicationEventPublisher eventPublisher;
    private final List<McpSyncClient> mcpClients;
    private final Map<String, List<McpSchema.Tool>> lastKnownTools = new ConcurrentHashMap<>();

    @Autowired
    public McpToolMonitor(ApplicationEventPublisher eventPublisher,
                          List<McpSyncClient> mcpClients) {
        this.eventPublisher = eventPublisher;
        this.mcpClients = mcpClients;
    }

    // Periodically check for tool changes
    @Scheduled(fixedDelay = 60000)  // Every 60 seconds
    public void checkForToolChanges() {
        for (McpSyncClient client : mcpClients) {
            String clientName = client.getClientInfo().name();

            try {
                // Get current tools
                McpSchema.ListToolsResult result = client.listTools();
                List<McpSchema.Tool> currentTools = result.tools();

                // Check if tools have changed
                List<McpSchema.Tool> previousTools = lastKnownTools.get(clientName);

                if (previousTools == null || !toolsEqual(previousTools, currentTools)) {
                    // Tools have changed - publish event
                    McpToolsChangedEvent event =
                        new McpToolsChangedEvent(clientName, currentTools);
                    eventPublisher.publishEvent(event);

                    // Update cache
                    lastKnownTools.put(clientName, currentTools);

                    System.out.println("Tools changed for " + clientName);
                }
            } catch (Exception e) {
                System.err.println("Error checking tools for " + clientName + ": " + e.getMessage());
            }
        }
    }

    private boolean toolsEqual(List<McpSchema.Tool> tools1, List<McpSchema.Tool> tools2) {
        if (tools1.size() != tools2.size()) return false;

        Set<String> names1 = tools1.stream().map(McpSchema.Tool::name).collect(Collectors.toSet());
        Set<String> names2 = tools2.stream().map(McpSchema.Tool::name).collect(Collectors.toSet());

        return names1.equals(names2);
    }
}

@Component
public class McpToolChangeLogger {

    @EventListener
    public void logToolChanges(McpToolsChangedEvent event) {
        System.out.println("=== MCP Tools Changed ===");
        System.out.println("Connection: " + event.getConnectionName());
        System.out.println("Tool Count: " + event.getTools().size());

        for (McpSchema.Tool tool : event.getTools()) {
            System.out.println("  - " + tool.name());
        }

        System.out.println("========================");
    }
}

Integration with Tool Providers

import org.springframework.ai.mcp.*;
import org.springframework.stereotype.Service;

@Service
public class DynamicToolService {

    private final SyncMcpToolCallbackProvider toolProvider;
    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public DynamicToolService(SyncMcpToolCallbackProvider toolProvider,
                              ApplicationEventPublisher eventPublisher) {
        this.toolProvider = toolProvider;
        this.eventPublisher = eventPublisher;
    }

    public void refreshTools(String connectionName) {
        // Manually trigger a refresh
        McpToolsChangedEvent event = new McpToolsChangedEvent(connectionName, List.of());
        eventPublisher.publishEvent(event);

        // The toolProvider automatically invalidates its cache
        // Next call to getToolCallbacks() will re-discover tools
        ToolCallback[] freshCallbacks = toolProvider.getToolCallbacks();
        System.out.println("Refreshed " + freshCallbacks.length + " tools");
    }

    public ToolCallback[] getCurrentTools() {
        return toolProvider.getToolCallbacks();
    }
}

Async Event Handling

import org.springframework.scheduling.annotation.Async;
import org.springframework.context.event.EventListener;

@Component
public class AsyncMcpToolHandler {

    @Async
    @EventListener
    public void handleToolsChangedAsync(McpToolsChangedEvent event) {
        // Handle event asynchronously
        System.out.println("Async handling tools changed for: " + event.getConnectionName());

        // Perform expensive operations without blocking
        analyzeToolChanges(event.getTools());
        updateDashboard(event.getConnectionName());
        notifyWebSocketClients(event);
    }

    private void analyzeToolChanges(List<McpSchema.Tool> tools) {
        // Expensive analysis
    }

    private void updateDashboard(String connectionName) {
        // Update monitoring dashboard
    }

    private void notifyWebSocketClients(McpToolsChangedEvent event) {
        // Notify WebSocket clients
    }
}

Event Ordering

import org.springframework.core.annotation.Order;
import org.springframework.context.event.EventListener;

@Component
public class OrderedMcpListeners {

    @EventListener
    @Order(1)
    public void highPriorityHandler(McpToolsChangedEvent event) {
        // Runs first
        System.out.println("High priority: " + event.getConnectionName());
    }

    @EventListener
    @Order(2)
    public void normalPriorityHandler(McpToolsChangedEvent event) {
        // Runs second
        System.out.println("Normal priority: " + event.getConnectionName());
    }

    @EventListener
    @Order(3)
    public void lowPriorityHandler(McpToolsChangedEvent event) {
        // Runs last
        System.out.println("Low priority: " + event.getConnectionName());
    }
}

Related Components

  • Synchronous Tool Discovery - Automatic cache invalidation on events
  • Asynchronous Tool Discovery - Async provider event handling

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-mcp

docs

index.md

tile.json