Spring Framework integration for Model Context Protocol (MCP), providing Spring AI function calling capabilities and Spring-friendly abstractions for MCP clients and MCP servers
This guide will walk you through setting up Spring AI MCP and integrating your first MCP tools.
<dependencies>
<!-- Spring AI MCP -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>1.1.2</version>
</dependency>
<!-- MCP SDK -->
<dependency>
<groupId>io.modelcontextprotocol</groupId>
<artifactId>mcp-client</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring AI Core -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Project Reactor (for async operations) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>dependencies {
implementation 'org.springframework.ai:spring-ai-mcp:1.1.2'
implementation 'io.modelcontextprotocol:mcp-client:1.0.0'
implementation 'org.springframework.ai:spring-ai-core:1.0.0'
implementation 'io.projectreactor:reactor-core'
}dependencies {
implementation("org.springframework.ai:spring-ai-mcp:1.1.2")
implementation("io.modelcontextprotocol:mcp-client:1.0.0")
implementation("org.springframework.ai:spring-ai-core:1.0.0")
implementation("io.projectreactor:reactor-core")
}First, create an MCP client to connect to your MCP server:
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.McpClient;
// Create synchronous MCP client
McpSyncClient mcpClient = McpClient.sync()
.serverInfo(/* server connection details */)
.build();For asynchronous operations:
import io.modelcontextprotocol.client.McpAsyncClient;
// Create asynchronous MCP client
McpAsyncClient asyncClient = McpClient.async()
.serverInfo(/* server connection details */)
.build();Use the utility method for quick tool discovery:
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
import java.util.List;
// Quick discovery with default settings
List<ToolCallback> callbacks =
McpToolUtils.getToolCallbacksFromSyncClients(mcpClient);
System.out.println("Discovered " + callbacks.size() + " tools");For more control, use a tool callback provider:
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
// Create provider
SyncMcpToolCallbackProvider provider =
SyncMcpToolCallbackProvider.builder()
.mcpClients(mcpClient)
.build();
// Get all discovered tools
ToolCallback[] toolCallbacks = provider.getToolCallbacks();
// Print discovered tools
for (ToolCallback callback : toolCallbacks) {
System.out.println("Tool: " + callback.getToolDefinition().name());
}Connect the discovered tools to Spring AI's ChatClient:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AiChatService {
private final ChatClient chatClient;
@Autowired
public AiChatService(ChatModel chatModel,
SyncMcpToolCallbackProvider mcpToolProvider) {
// Create ChatClient with MCP tools
this.chatClient = ChatClient.builder(chatModel)
.defaultFunctions(mcpToolProvider.getToolCallbacks())
.build();
}
public String chat(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.call()
.content();
}
}Create a Spring Boot configuration class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.McpClient;
@Configuration
public class McpConfiguration {
@Bean
public McpSyncClient weatherMcpClient() {
// Configure your MCP client
return McpClient.sync()
.serverInfo(/* connection details */)
.build();
}
@Bean
public SyncMcpToolCallbackProvider mcpToolProvider(
List<McpSyncClient> mcpClients) {
return SyncMcpToolCallbackProvider.builder()
.mcpClients(mcpClients)
.build();
}
}Create a simple test or controller:
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private AiChatService chatService;
@PostMapping
public String chat(@RequestBody String message) {
return chatService.chat(message);
}
}Test it:
curl -X POST http://localhost:8080/chat \
-H "Content-Type: text/plain" \
-d "What tools are available?"@Bean
public SyncMcpToolCallbackProvider mcpToolProvider(
McpSyncClient weatherClient,
McpSyncClient databaseClient,
McpSyncClient filesystemClient) {
return SyncMcpToolCallbackProvider.builder()
.mcpClients(weatherClient, databaseClient, filesystemClient)
.toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
.build();
}import org.springframework.ai.mcp.McpToolFilter;
@Bean
public SyncMcpToolCallbackProvider mcpToolProvider(
List<McpSyncClient> mcpClients) {
// Only allow non-internal, production-ready tools
McpToolFilter filter = (connectionInfo, tool) ->
!tool.name().startsWith("_") &&
!tool.description().contains("[beta]");
return SyncMcpToolCallbackProvider.builder()
.mcpClients(mcpClients)
.toolFilter(filter)
.build();
}For long-running tools or reactive applications:
import org.springframework.ai.mcp.AsyncMcpToolCallbackProvider;
import io.modelcontextprotocol.client.McpAsyncClient;
@Bean
public AsyncMcpToolCallbackProvider asyncMcpToolProvider(
List<McpAsyncClient> asyncClients) {
return AsyncMcpToolCallbackProvider.builder()
.mcpClients(asyncClients)
.build();
}
@Service
public class AsyncChatService {
private final ChatClient chatClient;
@Autowired
public AsyncChatService(ChatModel chatModel,
AsyncMcpToolCallbackProvider asyncProvider) {
this.chatClient = ChatClient.builder(chatModel)
.defaultFunctions(asyncProvider.getToolCallbacks())
.build();
}
}Problem: getToolCallbacks() returns empty array
Solution:
// Check if MCP client is connected
McpSchema.ListToolsResult result = mcpClient.listTools();
System.out.println("Available tools: " + result.tools().size());Problem: ToolExecutionException with connection errors
Solution: Verify MCP server is running and accessible:
try {
String result = callback.call(input);
} catch (ToolExecutionException e) {
if (e.getCause() instanceof java.net.ConnectException) {
System.err.println("Cannot connect to MCP server");
// Check server address and port
}
}Problem: IllegalStateException about duplicate names
Solution: Use name prefixing:
import org.springframework.ai.mcp.DefaultMcpToolNamePrefixGenerator;
SyncMcpToolCallbackProvider provider =
SyncMcpToolCallbackProvider.builder()
.mcpClients(client1, client2)
.toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
.build();import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.McpClient;
@SpringBootApplication
public class McpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(McpDemoApplication.class, args);
}
@Bean
public McpSyncClient mcpClient() {
return McpClient.sync()
.serverInfo(/* your server details */)
.build();
}
@Bean
public SyncMcpToolCallbackProvider toolProvider(McpSyncClient client) {
return SyncMcpToolCallbackProvider.builder()
.mcpClients(client)
.build();
}
}Run with:
mvn spring-boot:runYour MCP tools are now integrated with Spring AI!