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

sync-tool-discovery.mddocs/reference/

Synchronous Tool Discovery

The SyncMcpToolCallbackProvider class automatically discovers and manages tool callbacks from one or more synchronous MCP servers. It implements Spring AI's ToolCallbackProvider interface and handles tool listing, filtering, caching, and lifecycle management.

Import

import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.mcp.McpToolFilter;
import org.springframework.ai.mcp.McpToolNamePrefixGenerator;
import org.springframework.ai.mcp.ToolContextToMcpMetaConverter;
import io.modelcontextprotocol.client.McpSyncClient;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;

Class Declaration

public class SyncMcpToolCallbackProvider
    implements ToolCallbackProvider, ApplicationListener<McpToolsChangedEvent>

Creating Providers

Using Builder (Recommended)

SyncMcpToolCallbackProvider.Builder builder();

class Builder {
    Builder mcpClients(List<McpSyncClient> mcpClients);
    Builder mcpClients(McpSyncClient... mcpClients);
    Builder addMcpClient(McpSyncClient mcpClient);
    Builder toolFilter(McpToolFilter toolFilter);
    Builder toolNamePrefixGenerator(McpToolNamePrefixGenerator toolNamePrefixGenerator);
    Builder toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter toolContextToMcpMetaConverter);
    SyncMcpToolCallbackProvider build();
}

Builder Parameters

  • mcpClients (required): One or more McpSyncClient instances. Can be provided as a List, varargs array, or added individually with addMcpClient()
  • toolFilter (optional): Predicate for filtering discovered tools. Defaults to accepting all tools (mcpClient, tool) -> true
  • toolNamePrefixGenerator (optional): Strategy for generating prefixed tool names. Defaults to DefaultMcpToolNamePrefixGenerator
  • toolContextToMcpMetaConverter (optional): Converter for tool context to MCP metadata. Defaults to ToolContextToMcpMetaConverter.defaultConverter()

Usage Examples

// Single MCP client
McpSyncClient client = // ... initialize client
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(client)
    .build();

// Multiple MCP clients
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(client1, client2, client3)
    .build();

// With custom configuration
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(client1, client2)
    .toolFilter((connectionInfo, tool) -> !tool.name().startsWith("internal_"))
    .toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
    .toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
    .build();

// Adding clients one by one
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .addMcpClient(client1)
    .addMcpClient(client2)
    .toolFilter((info, tool) -> tool.description().contains("approved"))
    .build();

Using Constructors (All Deprecated)

@Deprecated
public SyncMcpToolCallbackProvider(McpToolFilter toolFilter, List<McpSyncClient> mcpClients)

@Deprecated
public SyncMcpToolCallbackProvider(List<McpSyncClient> mcpClients)

@Deprecated
public SyncMcpToolCallbackProvider(McpToolFilter toolFilter,
                                    McpToolNamePrefixGenerator toolNamePrefixGenerator,
                                    McpSyncClient... mcpClients)

@Deprecated
public SyncMcpToolCallbackProvider(McpSyncClient... mcpClients)

Note: All constructors are deprecated. Use the builder pattern instead.

Provider Methods

Get Tool Callbacks

ToolCallback[] getToolCallbacks()

Returns an array of all discovered tool callbacks from the configured MCP clients. Results are cached until invalidateCache() is called or a McpToolsChangedEvent is received.

Usage Example

SyncMcpToolCallbackProvider provider = // ... create provider

// Get all tool callbacks
ToolCallback[] callbacks = provider.getToolCallbacks();

// Use with Spring AI
for (ToolCallback callback : callbacks) {
    System.out.println("Tool: " + callback.getToolDefinition().name());
}

Invalidate Cache

void invalidateCache()

Forces re-discovery of tools from MCP servers on the next getToolCallbacks() call. This is useful when you know tools have been added or removed from the MCP servers.

Usage Example

SyncMcpToolCallbackProvider provider = // ... create provider

// Force refresh of tool list
provider.invalidateCache();

// Next call will re-discover all tools
ToolCallback[] updatedCallbacks = provider.getToolCallbacks();

Handle Tool Change Events

void onApplicationEvent(McpToolsChangedEvent event)

Automatically called by Spring when a McpToolsChangedEvent is published. Invalidates the cache to trigger re-discovery.

Usage Example

import org.springframework.context.ApplicationEventPublisher;

// In your Spring component
@Component
public class McpServerManager {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void notifyToolsChanged(String serverName, List<McpSchema.Tool> tools) {
        eventPublisher.publishEvent(new McpToolsChangedEvent(serverName, tools));
        // SyncMcpToolCallbackProvider will automatically invalidate cache
    }
}

Static Factory Methods

Sync Tool Callbacks Factory

static List<ToolCallback> syncToolCallbacks(List<McpSyncClient> mcpClients)

Convenience method to quickly create tool callbacks from multiple MCP clients with default settings.

Usage Example

List<McpSyncClient> clients = List.of(client1, client2);
List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);

Complete Example

import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.mcp.McpToolFilter;
import org.springframework.ai.mcp.DefaultMcpToolNamePrefixGenerator;
import io.modelcontextprotocol.client.McpSyncClient;

// Create multiple MCP clients
McpSyncClient weatherClient = // ... weather service MCP client
McpSyncClient databaseClient = // ... database MCP client
McpSyncClient filesystemClient = // ... filesystem MCP client

// Custom tool filter - only allow certain tools
McpToolFilter filter = (connectionInfo, tool) -> {
    String toolName = tool.name();
    // Allow all weather tools
    if (connectionInfo.clientInfo().name().contains("weather")) {
        return true;
    }
    // Only allow read operations for database
    if (connectionInfo.clientInfo().name().contains("database")) {
        return toolName.startsWith("select_") || toolName.startsWith("query_");
    }
    // Allow specific filesystem operations
    return toolName.equals("list_directory") || toolName.equals("read_file");
};

// Create provider with multiple clients and custom filter
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
    .mcpClients(weatherClient, databaseClient, filesystemClient)
    .toolFilter(filter)
    .toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
    .build();

// Get all discovered and filtered tool callbacks
ToolCallback[] toolCallbacks = provider.getToolCallbacks();

System.out.println("Discovered " + toolCallbacks.length + " tools");
for (ToolCallback callback : toolCallbacks) {
    System.out.println("  - " + callback.getToolDefinition().name());
}

// Later, if tools change
provider.invalidateCache();
ToolCallback[] updatedCallbacks = provider.getToolCallbacks();

Integration with Spring AI

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class AiChatService {

    private final ChatClient chatClient;

    @Autowired
    public AiChatService(ChatModel chatModel,
                         SyncMcpToolCallbackProvider mcpToolProvider) {
        this.chatClient = ChatClient.builder(chatModel)
            .defaultFunctions(mcpToolProvider.getToolCallbacks())
            .build();
    }

    public String chat(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
}

Spring Configuration Example

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class McpConfiguration {

    @Bean
    public SyncMcpToolCallbackProvider mcpToolProvider(
            List<McpSyncClient> mcpClients) {
        return SyncMcpToolCallbackProvider.builder()
            .mcpClients(mcpClients)
            .toolFilter((info, tool) -> {
                // Custom filtering logic
                return !tool.description().contains("[internal]");
            })
            .toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
            .build();
    }

    @Bean
    public McpSyncClient weatherMcpClient() {
        // Create and configure weather MCP client
        return // ... client instance
    }

    @Bean
    public McpSyncClient databaseMcpClient() {
        // Create and configure database MCP client
        return // ... client instance
    }
}

Tool Name Collision Handling

The provider automatically validates that all discovered tools have unique names after prefixing. If duplicate names are detected, an IllegalStateException is thrown during getToolCallbacks().

try {
    ToolCallback[] callbacks = provider.getToolCallbacks();
} catch (IllegalStateException e) {
    // Handle duplicate tool names
    System.err.println("Duplicate tool names detected: " + e.getMessage());
    // Consider using a custom McpToolNamePrefixGenerator to avoid conflicts
}

Caching Behavior

  • Tool callbacks are cached after the first getToolCallbacks() call
  • Cache is invalidated when:
    • invalidateCache() is called explicitly
    • A McpToolsChangedEvent is received
  • Subsequent calls to getToolCallbacks() return cached results until invalidation
  • Thread-safe caching using ReentrantLock for concurrent access

Performance Considerations

  • Tool Discovery: The first call to getToolCallbacks() queries all MCP servers, which may be slow for many servers or slow networks
  • Caching: Subsequent calls are fast as results are cached
  • Thread Safety: Multiple threads can safely call getToolCallbacks() concurrently
  • Cache Invalidation: Use invalidateCache() judiciously as it forces expensive re-discovery

Related Components

  • Synchronous Tool Callbacks - Individual tool callback implementation
  • Asynchronous Tool Discovery - Async alternative
  • Tool Filtering and Naming - Customizing tool selection and naming
  • Event Handling - Tool change events

Install with Tessl CLI

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

docs

index.md

tile.json