Spring Boot Starter for building Model Context Protocol (MCP) servers with auto-configuration, annotation-based tool/resource/prompt definitions, and support for STDIO, SSE, and Streamable-HTTP transports
Context objects and event classes for MCP server operations, connection metadata, and change notifications.
Connection metadata record containing information about an MCP client connection.
/**
* Connection metadata for an MCP client
* Location: org.springframework.ai.mcp.McpConnectionInfo
*/
record McpConnectionInfo(
McpSchema.ClientCapabilities clientCapabilities,
McpSchema.Implementation clientInfo,
McpSchema.InitializeResult initializeResult
) {
/**
* Create a builder for McpConnectionInfo
*/
static Builder builder() { ... }
interface Builder {
/**
* Set client capabilities
*/
Builder clientCapabilities(McpSchema.ClientCapabilities capabilities);
/**
* Set client implementation info
*/
Builder clientInfo(McpSchema.Implementation info);
/**
* Set initialization result
*/
Builder initializeResult(McpSchema.InitializeResult result);
/**
* Build the McpConnectionInfo instance
*/
McpConnectionInfo build();
}
}Usage Examples:
Used in filters and customizers:
import org.springframework.ai.mcp.McpConnectionInfo;
import org.springframework.ai.mcp.McpToolFilter;
@Bean
public McpToolFilter connectionAwareFilter() {
return (McpConnectionInfo connectionInfo, Tool tool) -> {
// Access client information
String clientName = connectionInfo.clientInfo().name();
String clientVersion = connectionInfo.clientInfo().version();
// Access client capabilities
boolean supportsSampling = connectionInfo.clientCapabilities().sampling() != null;
// Filter based on connection metadata
if ("legacy-client".equals(clientName)) {
// Only allow basic tools for legacy clients
return tool.name().startsWith("basic_");
}
return true; // Allow all tools for modern clients
};
}Building connection info:
McpConnectionInfo connectionInfo = McpConnectionInfo.builder()
.clientCapabilities(new ClientCapabilities(
new Experimental(),
new Roots(true),
new Sampling()
))
.clientInfo(new Implementation("my-client", "1.0.0"))
.initializeResult(new InitializeResult(
"1.0",
new Implementation("mcp-server", "1.1.2"),
new ServerCapabilities(/* ... */)
))
.build();Used in tool name prefix generators:
@Bean
public McpToolNamePrefixGenerator customPrefixGenerator() {
return (McpConnectionInfo connectionInfo, Tool tool) -> {
// Use connection info to generate unique prefixes
String clientName = connectionInfo.clientInfo().name();
String toolTitle = tool.title() != null ? tool.title() : "";
return McpToolUtils.format(clientName + "_" + toolTitle + "_" + tool.name());
};
}Spring application event published when MCP tools change, enabling automatic cache invalidation and tool list updates.
/**
* Event published when tools from an MCP connection change
* Location: org.springframework.ai.mcp.McpToolsChangedEvent
* Extends: org.springframework.context.ApplicationEvent
*/
class McpToolsChangedEvent extends ApplicationEvent {
/**
* Create a tools changed event
* @param connectionName The name of the connection
* @param tools The updated list of tools
*/
McpToolsChangedEvent(String connectionName, List<Tool> tools);
/**
* Get the connection name that changed
*/
String getConnectionName();
/**
* Get the updated list of tools
*/
List<Tool> getTools();
}Usage Examples:
Publishing tool changes:
import org.springframework.ai.mcp.McpToolsChangedEvent;
import org.springframework.context.ApplicationEventPublisher;
@Component
public class ToolRegistry {
private final ApplicationEventPublisher eventPublisher;
public ToolRegistry(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void updateTools(String connectionName, List<Tool> newTools) {
// Update tool registry
toolMap.put(connectionName, newTools);
// Publish event to notify listeners
eventPublisher.publishEvent(
new McpToolsChangedEvent(connectionName, newTools)
);
}
}Listening for tool changes:
import org.springframework.ai.mcp.McpToolsChangedEvent;
import org.springframework.context.event.EventListener;
@Component
public class ToolCacheManager {
@EventListener
public void handleToolsChanged(McpToolsChangedEvent event) {
String connectionName = event.getConnectionName();
List<Tool> updatedTools = event.getTools();
System.out.println("Tools changed for connection: " + connectionName);
System.out.println("New tool count: " + updatedTools.size());
// Invalidate cache for this connection
toolCache.remove(connectionName);
// Rebuild cache with new tools
updatedTools.forEach(tool -> {
toolCache.put(tool.name(), tool);
});
}
}Automatic cache invalidation in tool callback providers:
// SyncMcpToolCallbackProvider and AsyncMcpToolCallbackProvider both
// implement ApplicationListener<McpToolsChangedEvent> automatically
@Bean
public SyncMcpToolCallbackProvider toolProvider(List<McpSyncClient> clients) {
// This provider automatically listens for McpToolsChangedEvent
// and invalidates its cache when tools change
return SyncMcpToolCallbackProvider.builder()
.mcpClients(clients)
.build();
}
// When tools change, the provider automatically calls invalidateCache()
// internally, ensuring fresh tools are returned on next getToolCallbacks()Custom event handling:
@Component
public class ToolMonitor implements ApplicationListener<McpToolsChangedEvent> {
@Override
public void onApplicationEvent(McpToolsChangedEvent event) {
// Log tool changes for monitoring
logger.info("Connection '{}' now has {} tools",
event.getConnectionName(),
event.getTools().size());
// Send metrics
metrics.gauge("mcp.tools.count",
event.getTools().size(),
"connection", event.getConnectionName());
// Notify administrators
if (event.getTools().isEmpty()) {
alertService.send("No tools available for: " + event.getConnectionName());
}
}
}@Configuration
public class FilterConfig {
@Bean
public McpToolFilter productionFilter() {
return (connectionInfo, tool) -> {
// Get environment from connection metadata
String environment = connectionInfo.clientInfo().name();
// Only allow safe tools in production
if ("production".equals(environment)) {
return !tool.annotations().destructiveHint();
}
return true; // Allow all in dev/test
};
}
}@Service
public class DynamicToolService {
private final ApplicationEventPublisher eventPublisher;
private final Map<String, List<Tool>> toolRegistry = new ConcurrentHashMap<>();
@Scheduled(fixedRate = 60000) // Check every minute
public void refreshTools() {
for (String connectionName : getActiveConnections()) {
List<Tool> freshTools = fetchToolsFromServer(connectionName);
if (!Objects.equals(toolRegistry.get(connectionName), freshTools)) {
// Tools changed - publish event
toolRegistry.put(connectionName, freshTools);
eventPublisher.publishEvent(
new McpToolsChangedEvent(connectionName, freshTools)
);
}
}
}
}@Bean
public McpSyncClientCustomizer connectionCustomizer() {
return (String name, McpClient.SyncSpec spec) -> {
// Customize client based on connection name
if ("high-priority-server".equals(name)) {
spec.timeout(Duration.ofSeconds(60));
} else {
spec.timeout(Duration.ofSeconds(30));
}
};
}import org.springframework.ai.mcp.McpConnectionInfo;
import org.springframework.ai.mcp.McpToolsChangedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springaicommunity.mcp.schema.McpSchema.Tool;
import org.springaicommunity.mcp.schema.McpSchema.ClientCapabilities;
import org.springaicommunity.mcp.schema.McpSchema.Implementation;
import org.springaicommunity.mcp.schema.McpSchema.InitializeResult;