The Model Context Protocol (MCP), originally introduced by Anthropic, is an open standard designed to standardize how Large Language Models (LLMs) connect with external context sources—such as databases, local file systems, third-party APIs, and custom enterprise logic.

By integrating MCP support into Spring AI, Java developers can decouple tool execution from model providers. This guide outlines how MCP works within the Spring ecosystem, how to set up an MCP client to consume external tools, how to build custom MCP tools, and how to expose Spring components as MCP servers.

1. Core Concepts: Spring AI & MCP

In traditional function calling (tool calling), each LLM provider or framework has custom ways to register tools. MCP standardizes this by separating the architecture into three primary components:

  1. MCP Server: Exposes capabilities such as Tools (executable functions), Resources (data/files), and Prompts (reusable templates).
  2. MCP Client: Connects to one or more MCP Servers, discovers available capabilities, and bridges them to the LLM.
  3. Transport Protocol: The communication channel between Client and Server, typically:
    • STDIO: Process-based communication (ideal for local tooling such as CLI utilities or local databases).
    • SSE (Server-Sent Events / HTTP): Web-based, asynchronous streaming communication (ideal for remote microservices).

In a Spring AI application, Spring AI acts as the MCP Client (or Server), wrapping MCP tool definitions directly into Spring AI’s native ToolCallback interface.

2. Prerequisites & Dependency Setup

Prerequisites

  • Java 17 or higher
  • Spring Boot 3.2+ or 3.3+
  • Spring AI 1.0.0-M6 or later (includes spring-ai-mcp starter modules)

Maven Dependencies

Add the Spring AI BOM and the MCP Client starter to your pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-M6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Core Spring AI Starter (e.g., OpenAI, Ollama, or Anthropic) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>

    <!-- Spring AI MCP Client Starter -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

3. Configuring an MCP Client in Spring AI

Spring AI provides auto-configuration for MCP clients. You can configure connections to local STDIO-based servers or remote SSE-based servers via application.yml.

Example application.yml Setup

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
    mcp:
      client:
        enabled: true
        name: spring-ai-mcp-client
        version: 1.0.0
        stdio:
          connections:
            # Example 1: Connecting to a SQLite MCP server via npx
            sqlite-server:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-sqlite"
                - "--db-path"
                - "./data/app.db"
            # Example 2: Connecting to a File System MCP server
            filesystem-server:
              command: npx
              args:
                - "-y"
                - "@modelcontextprotocol/server-filesystem"
                - "./allowed-dir"

4. Programmatic Configuration & Using ChatClient

Once the MCP client auto-configuration initializes, Spring AI converts all discovered tools into ToolCallback beans that can be injected into ChatClient.

Java Configuration Class

package com.example.mcp.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class McpConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder, SyncMcpToolCallbackProvider mcpToolProvider) {
        return builder
                // Automatically registers all tools discovered from connected MCP servers
                .defaultTools(mcpToolProvider.getToolCallbacks())
                .build();
    }
}

Building an Interactive Service

Here is how you can use the ChatClient inside a REST Controller or Service to execute prompts that leverage MCP tools seamlessly:

package com.example.mcp.service;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class AssistantService {

    private final ChatClient chatClient;

    public AssistantService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    public String processUserQuery(String query) {
        return this.chatClient.prompt()
                .user(query)
                .call()
                .content();
    }
}

5. Manual / Advanced MCP Client Assembly

If you prefer programmatic control over client lifecycle and transports rather than Spring Boot auto-configuration, you can construct McpClient manually:

package com.example.mcp.config;

import org.springframework.ai.mcp.client.McpClient;
import org.springframework.ai.mcp.client.transport.ServerParameters;
import org.springframework.ai.mcp.client.transport.StdioClientTransport;
import org.springframework.ai.mcp.spring.McpToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class ManualMcpConfig {

    @Bean
    public McpClient customMcpClient() {
        ServerParameters params = ServerParameters.builder("npx")
                .args("-y", "@modelcontextprotocol/server-sqlite", "--db-path", "./data/app.db")
                .build();

        StdioClientTransport transport = new StdioClientTransport(params);

        McpClient mcpClient = McpClient.sync(transport)
                .clientInfo("custom-spring-app", "1.0.0")
                .build();

        mcpClient.initialize();
        return mcpClient;
    }

    @Bean
    public McpToolCallbackProvider mcpToolCallbackProvider(McpClient mcpClient) {
        return new McpToolCallbackProvider(List.of(mcpClient));
    }
}

6. Creating Custom MCP Tools in Spring AI

Creating custom tools for the Model Context Protocol in Spring AI can be done in two ways: Declaratively using Spring AI annotations, or Programmatically using low-level MCP SDK interfaces.

6.1 Declarative Tools with @Tool

The easiest way to define tools is using the @Tool annotation on standard Spring Boot components. Spring AI automatically parses method parameters, generates JSON schemas, and converts return types.

Defining Strongly-Typed DTOs

Define Java records or classes to model input arguments and execution outputs cleanly:

package com.example.mcp.tools.dto;

import com.fasterxml.jackson.annotation.JsonPropertyDescription;

public record OrderDetailsRequest(
    @JsonPropertyDescription("The unique identifier of the customer order, e.g., ORD-99182")
    String orderId,
    
    @JsonPropertyDescription("Optional flag to include full shipping tracking history")
    boolean includeTrackingHistory
) {}

public record OrderStatusResponse(
    String orderId,
    String status,
    String estimatedDelivery,
    List<String> statusLogs
) {}

Implementing the Tool Component

package com.example.mcp.tools;

import com.example.mcp.tools.dto.OrderDetailsRequest;
import com.example.mcp.tools.dto.OrderStatusResponse;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class OrderManagementTools {

    @Tool(
        name = "lookupOrderStatus",
        description = "Retrieves the current status and tracking information for a given order ID."
    )
    public OrderStatusResponse lookupOrderStatus(OrderDetailsRequest request) {
        // Business logic to look up order details from DB or downstream API
        if ("ORD-99182".equalsIgnoreCase(request.orderId())) {
            List<String> logs = request.includeTrackingHistory()
                    ? List.of("Order Placed", "Dispatched from warehouse", "In Transit")
                    : List.of("In Transit");

            return new OrderStatusResponse(
                    request.orderId(),
                    "IN_TRANSIT",
                    "2026-08-01",
                    logs
            );
        }
        
        return new OrderStatusResponse(
                request.orderId(),
                "NOT_FOUND",
                "N/A",
                List.of()
        );
    }
}

6.2 Programmatic Tool Creation (McpServerFeatures)

For dynamic tool registration—such as tools loaded conditionally at runtime or generated based on external configuration—you can register tools programmatically via McpServerFeatures.SyncToolRegistration.

package com.example.mcp.config;

import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class DynamicMcpToolConfig {

    @Bean
    public McpServerFeatures.SyncToolRegistration currencyConverterTool() {
        // 1. Define the tool metadata and input schema
        McpSchema.Tool tool = new McpSchema.Tool(
                "convertCurrency",
                "Converts an amount from a base currency to a target currency using live rates.",
                """
                {
                  "type": "object",
                  "properties": {
                    "from": { "type": "string", "description": "ISO 4217 base currency code, e.g. USD" },
                    "to": { "type": "string", "description": "ISO 4217 target currency code, e.g. EUR" },
                    "amount": { "type": "number", "description": "The monetary amount to convert" }
                  },
                  "required": ["from", "to", "amount"]
                }
                """
        );

        // 2. Register tool execution logic returning structured MCP TextContent
        return new McpServerFeatures.SyncToolRegistration(tool, arguments -> {
            String from = (String) arguments.get("from");
            String to = (String) arguments.get("to");
            Double amount = Double.valueOf(arguments.get("amount").toString());

            // Example conversion logic
            double exchangeRate = 0.92; // Mock conversion rate USD -> EUR
            double converted = amount * exchangeRate;

            String responseMessage = String.format("%.2f %s = %.2f %s (Rate: %.4f)",
                    amount, from.toUpperCase(), converted, to.toUpperCase(), exchangeRate);

            return new McpSchema.CallToolResult(
                    List.of(new McpSchema.TextContent(responseMessage)),
                    false // isError flag
            );
        });
    }
}

6.3 Handling Errors & Returning Rich Results

When building custom MCP tools, tools can return formatted text, JSON strings, or error payloads. Returning structured JSON helps LLMs process response data accurately:

package com.example.mcp.tools;

import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class SystemDiagnosticsTools {

    @Tool(description = "Executes diagnostic ping against a target internal hostname.")
    public McpSchema.CallToolResult pingHost(String hostname) {
        try {
            if ("invalid-host".equalsIgnoreCase(hostname)) {
                return new McpSchema.CallToolResult(
                        List.of(new McpSchema.TextContent("Host reachability check failed: Unknown host")),
                        true // Signals to the LLM that the tool call produced an error
                );
            }

            String jsonPayload = """
                {
                  "host": "%s",
                  "status": "REACHABLE",
                  "latencyMs": 14.2
                }
                """.formatted(hostname);

            return new McpSchema.CallToolResult(
                    List.of(new McpSchema.TextContent(jsonPayload)),
                    false
            );
        } catch (Exception e) {
            return new McpSchema.CallToolResult(
                    List.of(new McpSchema.TextContent("Execution error: " + e.getMessage())),
                    true
            );
        }
    }
}

7. Exposing Custom Spring Beans as an MCP Server

In addition to consuming external MCP servers, your Spring Boot application can act as an MCP Server, exposing all declared tools (annotated with @Tool or configured programmatically) to external clients.

Add Server Dependency

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>

Exposing Tools with @Tool

package com.example.mcp.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class InventoryTools {

    @Tool(description = "Check current inventory stock levels for a given SKU item.")
    public int checkInventoryStock(String sku) {
        if ("ITEM-123".equalsIgnoreCase(sku)) {
            return 42;
        }
        return 0;
    }
}

Server Configuration

spring:
  ai:
    mcp:
      server:
        name: inventory-mcp-server
        version: 1.0.0
        transport: sse # Options: stdio, sse
        sse:
          path: /mcp/sse

When started, external clients (such as Claude Desktop or other Spring AI applications) can connect to http://localhost:8080/mcp/sse and discover and execute checkInventoryStock or any other registered MCP tools.

8. Best Practices & Security Considerations

  1. Transport Selection:
    • Use STDIO for local CLI binaries, local desktop integrations, or sidecar containers in Kubernetes.
    • Use SSE (Server-Sent Events) for networked microservices, API gateways, and remote deployments.
  2. Access Control & Schema Documentation:
    • Always use @JsonPropertyDescription or detailed parameter descriptions. LLMs rely directly on schema descriptions to decide when and how to call your tools.
    • Restrict file system tools or sensitive API tools to strictly authorized scopes.
  3. Error Handling:
    • When tool execution fails, set the isError flag on CallToolResult to true and return descriptive error messages so the model can attempt self-correction or report issues cleanly.
  4. Monitoring & Logging:
    • Enable debug logging for MCP transport packets during development:logging: level: org.springframework.ai.mcp: DEBUG

Conclusion

Combining Spring AI with Model Context Protocol (MCP) gives Java applications a standardized, plug-and-playable infrastructure for context expansion and tool execution. By abstracting tool protocols away from specific model vendor APIs, Spring AI enables developers to easily build, publish, and consume ecosystem tools (databases, custom domain logic, file systems, APIs) while retaining clean, idiomatic Spring Boot application code.


Discover more from GhostProgrammer - Jeff Miller

Subscribe to get the latest posts sent to your email.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.