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
Integration components for using MCP servers as clients in Spring AI applications. These components allow Spring AI applications to consume tools from remote MCP servers.
Note: This functionality is for client-side integration - using this starter to connect to remote MCP servers and consume their tools in your Spring AI application.
Synchronous adapter that wraps an MCP tool from a remote server as a Spring AI ToolCallback.
/**
* Synchronous adapter for MCP tools to Spring AI ToolCallback
*/
class SyncMcpToolCallback implements ToolCallback {
static Builder builder() { ... }
/**
* Get the tool definition
*/
ToolDefinition getToolDefinition();
/**
* Get the original MCP tool name
*/
String getOriginalToolName();
/**
* Execute the tool
*/
String call(String toolCallInput);
/**
* Execute the tool with context
*/
String call(String toolCallInput, ToolContext toolContext);
interface Builder {
Builder mcpClient(McpSyncClient client);
Builder tool(Tool tool);
Builder prefixedToolName(String name);
Builder toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter converter);
SyncMcpToolCallback build();
}
}Usage Example:
import org.springframework.ai.mcp.SyncMcpToolCallback;
import org.springaicommunity.mcp.client.McpSyncClient;
@Configuration
public class ClientToolConfig {
@Bean
public ToolCallback remoteCalculator(McpSyncClient calculatorServer) {
// Get tool definition from remote server
Tool addTool = calculatorServer.listTools().stream()
.filter(t -> "add".equals(t.name()))
.findFirst()
.orElseThrow();
// Wrap as Spring AI ToolCallback
return SyncMcpToolCallback.builder()
.mcpClient(calculatorServer)
.tool(addTool)
.build();
}
}Asynchronous adapter for MCP tools with reactive execution.
/**
* Asynchronous adapter for MCP tools to Spring AI ToolCallback
*/
class AsyncMcpToolCallback implements ToolCallback {
static Builder builder() { ... }
ToolDefinition getToolDefinition();
String getOriginalToolName();
String call(String toolCallInput);
String call(String toolCallInput, ToolContext toolContext);
interface Builder {
Builder mcpClient(McpAsyncClient client);
Builder tool(Tool tool);
Builder prefixedToolName(String name);
Builder toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter converter);
AsyncMcpToolCallback build();
}
}Discovers and provides tools from multiple MCP servers synchronously.
/**
* Provides tool callbacks from multiple sync MCP servers
*/
class SyncMcpToolCallbackProvider implements ToolCallbackProvider, ApplicationListener<McpToolsChangedEvent> {
static Builder builder() { ... }
/**
* Get all discovered tool callbacks
*/
ToolCallback[] getToolCallbacks();
/**
* Invalidate cache and force re-discovery
*/
void invalidateCache();
/**
* Static helper to get callbacks from clients
*/
static List<ToolCallback> syncToolCallbacks(List<McpSyncClient> clients);
interface Builder {
Builder mcpClients(List<McpSyncClient> clients);
Builder mcpClients(McpSyncClient... clients);
Builder addMcpClient(McpSyncClient client);
Builder toolFilter(McpToolFilter filter);
Builder toolNamePrefixGenerator(McpToolNamePrefixGenerator generator);
Builder toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter converter);
SyncMcpToolCallbackProvider build();
}
}Usage Example:
@Configuration
public class MultiServerConfig {
@Bean
public SyncMcpToolCallbackProvider allServerTools(
McpSyncClient weatherServer,
McpSyncClient calculatorServer,
McpSyncClient databaseServer) {
return SyncMcpToolCallbackProvider.builder()
.mcpClients(weatherServer, calculatorServer, databaseServer)
.toolFilter((connectionInfo, tool) -> {
// Filter out internal tools
return !tool.name().startsWith("internal_");
})
.toolNamePrefixGenerator((connectionInfo, tool) -> {
// Prefix with server name
String serverName = connectionInfo.clientInfo().name();
return serverName + "_" + tool.name();
})
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
SyncMcpToolCallbackProvider toolProvider) {
return builder
.defaultTools(toolProvider.getToolCallbacks())
.build();
}
}Discovers and provides tools from multiple MCP servers asynchronously.
/**
* Provides tool callbacks from multiple async MCP servers
*/
class AsyncMcpToolCallbackProvider implements ToolCallbackProvider, ApplicationListener<McpToolsChangedEvent> {
static Builder builder() { ... }
ToolCallback[] getToolCallbacks();
void invalidateCache();
/**
* Reactive stream of tool callbacks
*/
static Flux<ToolCallback> asyncToolCallbacks(List<McpAsyncClient> clients);
interface Builder {
Builder toolFilter(McpToolFilter filter);
Builder mcpClients(List<McpAsyncClient> clients);
Builder mcpClients(McpAsyncClient... clients);
Builder toolNamePrefixGenerator(McpToolNamePrefixGenerator generator);
Builder toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter converter);
AsyncMcpToolCallbackProvider build();
}
}Container for MCP connection metadata.
/**
* Record containing MCP connection metadata
*/
record McpConnectionInfo(
ClientCapabilities clientCapabilities,
Implementation clientInfo,
InitializeResult initializeResult
) {
static Builder builder() { ... }
interface Builder {
Builder clientCapabilities(ClientCapabilities capabilities);
Builder clientInfo(Implementation info);
Builder initializeResult(InitializeResult result);
McpConnectionInfo build();
}
}Filter tools during discovery.
/**
* Functional interface for filtering discovered tools
*/
@FunctionalInterface
interface McpToolFilter extends BiPredicate<McpConnectionInfo, Tool> {
boolean test(McpConnectionInfo connectionInfo, Tool tool);
}Usage Examples:
// Filter by name pattern
McpToolFilter nameFilter = (conn, tool) ->
tool.name().matches("^[a-z_]+$");
// Filter by server
McpToolFilter serverFilter = (conn, tool) ->
"trusted-server".equals(conn.clientInfo().name());
// Filter by description
McpToolFilter descFilter = (conn, tool) ->
tool.description() != null && !tool.description().isEmpty();
// Combine filters
McpToolFilter combined = nameFilter.and(descFilter);Generate prefixed tool names to avoid conflicts.
/**
* Strategy for generating prefixed tool names
*/
interface McpToolNamePrefixGenerator {
String prefixedToolName(McpConnectionInfo connectionInfo, Tool tool);
/**
* Returns tool name without prefix
*/
static McpToolNamePrefixGenerator noPrefix();
}Usage Examples:
// Default: prefix with server name
McpToolNamePrefixGenerator defaultGen = new DefaultMcpToolNamePrefixGenerator();
// No prefix
McpToolNamePrefixGenerator noPrefix = McpToolNamePrefixGenerator.noPrefix();
// Custom prefix with version
McpToolNamePrefixGenerator versionPrefix = (conn, tool) -> {
String version = conn.clientInfo().version();
return "v" + version + "_" + tool.name();
};Convert Spring AI ToolContext to MCP metadata.
/**
* Strategy for converting ToolContext to MCP metadata
*/
interface ToolContextToMcpMetaConverter {
Map<String, Object> convert(ToolContext toolContext);
/**
* Default converter extracting common metadata
*/
static ToolContextToMcpMetaConverter defaultConverter();
/**
* No-op converter returning empty map
*/
static ToolContextToMcpMetaConverter noOp();
}Usage Examples:
// Default converter
ToolContextToMcpMetaConverter defaultConv =
ToolContextToMcpMetaConverter.defaultConverter();
// No-op
ToolContextToMcpMetaConverter noOp =
ToolContextToMcpMetaConverter.noOp();
// Custom converter
ToolContextToMcpMetaConverter custom = (toolContext) -> {
Map<String, Object> meta = new HashMap<>();
meta.put("timestamp", System.currentTimeMillis());
meta.put("user", toolContext.getContext().get("user"));
meta.put("session", toolContext.getContext().get("session"));
return meta;
};Event published when tools change on an MCP server.
/**
* Event published when MCP tools change for a connection
*/
class McpToolsChangedEvent extends ApplicationEvent {
McpToolsChangedEvent(String connectionName, List<Tool> tools);
/**
* Get the connection name where tools changed
*/
String getConnectionName();
/**
* Get the updated list of tools
*/
List<Tool> getTools();
}Usage Example:
@Component
public class ToolChangeHandler {
@EventListener
public void onToolsChanged(McpToolsChangedEvent event) {
String connection = event.getConnectionName();
List<Tool> tools = event.getTools();
System.out.println("Tools updated for: " + connection);
System.out.println("Available tools: " + tools.size());
// Invalidate caches, update UI, etc.
toolCache.invalidate(connection);
}
}@Configuration
public class McpClientIntegrationConfig {
// Define MCP client connections
@Bean
public McpSyncClient weatherServer() {
return McpClient.sync()
.transport(StdioServerTransport.builder()
.command("weather-mcp-server")
.build())
.build();
}
@Bean
public McpSyncClient calculatorServer() {
return McpClient.sync()
.transport(SseClientTransport.builder()
.url("http://calculator-server.example.com/sse")
.build())
.build();
}
// Provide tools from all servers
@Bean
public SyncMcpToolCallbackProvider toolProvider(
List<McpSyncClient> clients) {
return SyncMcpToolCallbackProvider.builder()
.mcpClients(clients)
.toolFilter((conn, tool) -> !tool.name().startsWith("_"))
.toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
.toolContextToMcpMetaConverter(
ToolContextToMcpMetaConverter.defaultConverter())
.build();
}
// Configure Spring AI ChatClient with MCP tools
@Bean
public ChatClient chatClient(
ChatClient.Builder builder,
SyncMcpToolCallbackProvider toolProvider) {
return builder
.defaultTools(toolProvider.getToolCallbacks())
.build();
}
// Listen for tool changes
@EventListener
public void handleToolChanges(McpToolsChangedEvent event) {
System.out.println("Tools changed: " + event.getConnectionName());
// Handle tool updates
}
}This starter provides both server and client capabilities:
| Aspect | Server Usage | Client Usage |
|---|---|---|
| Purpose | Expose your tools via MCP | Consume remote MCP tools |
| Components | @McpTool annotations, specifications | ToolCallback adapters, providers |
| Configuration | spring.ai.mcp.server.* | MCP client beans |
| Integration | Standalone MCP server | Spring AI ChatClient |
| Transport | STDIO, SSE, Streamable HTTP | Client transports |
Most applications will use either server or client functionality, not both.