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

quick-start.mddocs/guides/

Quick Start Guide

This guide will walk you through setting up Spring AI MCP and integrating your first MCP tools.

Prerequisites

  • Java 17 or later
  • Spring Boot 3.x
  • Maven or Gradle
  • An MCP server to connect to

Step 1: Add Dependencies

Maven

<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>

Gradle

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'
}

Gradle Kotlin DSL

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")
}

Step 2: Create MCP Client

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();

Step 3: Discover Tools (Simple Approach)

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");

Step 4: Discover Tools (Provider Approach)

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());
}

Step 5: Integrate with Spring AI ChatClient

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();
    }
}

Step 6: Spring Boot Configuration

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();
    }
}

Step 7: Test Your Integration

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?"

Common Patterns

Working with Multiple MCP Servers

@Bean
public SyncMcpToolCallbackProvider mcpToolProvider(
        McpSyncClient weatherClient,
        McpSyncClient databaseClient,
        McpSyncClient filesystemClient) {
    
    return SyncMcpToolCallbackProvider.builder()
        .mcpClients(weatherClient, databaseClient, filesystemClient)
        .toolNamePrefixGenerator(new DefaultMcpToolNamePrefixGenerator())
        .build();
}

Filtering Tools

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();
}

Using Async Operations

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();
    }
}

Troubleshooting

No Tools Discovered

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());

Connection Errors

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
    }
}

Duplicate Tool Names

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();

Next Steps

  • Real-World Scenarios - See complete usage examples
  • Edge Cases - Handle advanced scenarios
  • Architecture - Understand the design
  • Synchronous Tool Callbacks - Detailed API reference
  • Tool Filtering and Naming - Customization strategies

Complete Minimal Example

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:run

Your MCP tools are now integrated with Spring AI!

Install with Tessl CLI

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

docs

index.md

tile.json