MCP Server Building Guide -- Spring Boot Edition

Why Build an MCP Server with Spring Boot?

MCP (Model Context Protocol) is a standard protocol for AI to call external tools. While FastMCP is widely used in the Python ecosystem, if you’re already running Spring Boot-based services, using Spring AI’s MCP starter is the natural choice.

Simply adding annotations to existing Spring Bean methods turns them into an MCP server. You can expose existing business logic to AI without any additional infrastructure.

Dependency Setup

Add the Spring AI BOM and MCP Server starter.

Maven

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

<dependencies>
    <!-- STDIO transport (for Claude Desktop connection) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server</artifactId>
    </dependency>

    <!-- HTTP/SSE transport (for remote server) -->
    <!--
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    </dependency>
    -->
</dependencies>

Gradle

dependencyManagement {
    imports {
        mavenBom "org.springframework.ai:spring-ai-bom:1.1.4"
    }
}

dependencies {
    implementation "org.springframework.ai:spring-ai-starter-mcp-server"
}

application.properties

When using STDIO transport, console output must be disabled. If Spring banners or logs print to stdout, they will corrupt JSON-RPC messages.

spring.application.name=my-mcp-server

# MCP server configuration
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC

# Required for STDIO transport -- block console output
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/mcp-server.log

When using HTTP/SSE transport, remove the web-application-type and banner-mode settings, and use the spring-ai-starter-mcp-server-webmvc dependency instead.

Implementing Tools

Expose Spring Bean methods as MCP Tools using the @Tool annotation.

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class TodoService {

    public record TodoItem(String id, String title, boolean done, String createdAt) {}

    private final Map<String, TodoItem> todos = new ConcurrentHashMap<>();
    private int nextId = 1;

    @Tool(name = "createTodo", description = "Creates a new todo item")
    public String createTodo(
            @ToolParam(description = "Todo title") String title) {
        String id = String.valueOf(nextId++);
        String now = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        todos.put(id, new TodoItem(id, title, false, now));
        return "Todo #" + id + " '" + title + "' created";
    }

    @Tool(name = "listTodos", description = "Lists all todo items")
    public List<TodoItem> listTodos() {
        return new ArrayList<>(todos.values());
    }

    @Tool(name = "completeTodo", description = "Marks a todo item as complete")
    public String completeTodo(
            @ToolParam(description = "ID of the todo to complete") String id) {
        TodoItem item = todos.get(id);
        if (item == null) {
            return "Todo #" + id + " not found";
        }
        todos.put(id, new TodoItem(item.id(), item.title(), true, item.createdAt()));
        return "Todo #" + id + " '" + item.title() + "' marked as complete";
    }

    @Tool(name = "deleteTodo", description = "Deletes a todo item")
    public String deleteTodo(
            @ToolParam(description = "ID of the todo to delete") String id) {
        TodoItem item = todos.remove(id);
        if (item == null) {
            return "Todo #" + id + " not found";
        }
        return "Todo #" + id + " '" + item.title() + "' deleted";
    }
}

The description in @ToolParam is passed to the AI, so clearly describe what each parameter means.

Registering Tools with MCP

In the Spring Boot main class, use ToolCallbacks.from() to auto-register the Bean’s Tool methods.

import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }

    @Bean
    public List<ToolCallback> todoTools(TodoService todoService) {
        return ToolCallbacks.from(todoService);
    }
}

ToolCallbacks.from(bean) scans all methods annotated with @Tool in the Bean and registers them as MCP Tools. If you have multiple Service Beans, register each one separately.

@Bean
public List<ToolCallback> allTools(
        TodoService todoService,
        WeatherService weatherService) {
    var tools = new ArrayList<ToolCallback>();
    tools.addAll(ToolCallbacks.from(todoService));
    tools.addAll(ToolCallbacks.from(weatherService));
    return tools;
}

Build and Run

# Build JAR with Maven
./mvnw clean package -DskipTests

# Run (STDIO mode will wait for input rather than exiting immediately)
java -jar target/mcp-server-0.0.1-SNAPSHOT.jar

# Gradle
./gradlew bootJar
java -jar build/libs/mcp-server-0.0.1-SNAPSHOT.jar

Connecting to Claude Desktop

Register the built JAR in the Claude Desktop configuration.

Config file location:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "todo-server": {
      "command": "java",
      "args": [
        "-jar",
        "/path/to/mcp-server-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

Connecting from Claude Code:

claude mcp add todo-server -- java -jar /path/to/mcp-server-0.0.1-SNAPSHOT.jar
claude mcp list

After configuration, restart Claude Desktop and the AI will recognize the createTodo, listTodos, completeTodo, and deleteTodo Tools.

Switching to HTTP/SSE Transport

To run as a remote server, switch to the WebMVC starter.

<!-- pom.xml: Use WebMVC starter instead of STDIO starter -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
# application.properties
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.version=1.0.0
# Remove web-application-type=none (enable web server)
# Remove banner-mode=off
# Remove logging.pattern.console=
server.port=8080

Connecting to a remote server from Claude Desktop:

{
  "mcpServers": {
    "todo-server-remote": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:8080/sse"]
    }
  }
}

STDIO vs HTTP Comparison

ItemSTDIOHTTP/SSE
TransportStandard I/O streamsHTTP POST + Server-Sent Events
NetworkNot required (local process)Required (can be remote)
Concurrent connectionsSingle clientMultiple clients
AuthenticationNone (local)OAuth, API Key, etc.
DeploymentRun JAR file directlyDeploy as regular Spring Boot server
Use caseLocal development, Claude DesktopProduction, team-shared server

Summary

StepDetails
1. Dependenciesspring-ai-starter-mcp-server (STDIO) or -webmvc (HTTP)
2. ConfigurationSet server name and transport type in application.properties
3. Implement Tools@Tool annotation on @Service Bean methods
4. RegisterReturn ToolCallbacks.from(service) as a @Bean
5. Buildmvnw package to generate JAR
6. ConnectRegister JAR path in Claude Desktop config

Adding MCP to an existing Spring Boot service only requires two things: @Tool annotations and ToolCallbacks.from() Bean registration. Without modifying existing business logic, simply adding annotations above Service methods turns them into tools that AI can call.

Was this article helpful?