Turn a Spring Boot CRUD Service into an AI‑Powered MCP Endpoint
This guide shows how to convert a traditional Spring Boot book‑management service into a Model Context Protocol (MCP) server that lets large language models interact with the service via natural‑language commands, covering dependency setup, MCP configuration, tool annotations, chat client integration, and end‑to‑end testing.
Model Context Protocol (MCP)
MCP acts as a universal adapter that lets a large language model (LLM) communicate with backend services using a single, standardized interface, similar to how gRPC standardizes RPC calls. By annotating service methods with @Tool and @ToolParam, developers expose those methods as AI‑callable functions.
Step‑by‑Step Migration of a Spring Boot Book Service to MCP
Domain model : define a Book entity with fields id, title, category, author, publicationDate, and isbn. The full entity code is provided in the source repository.
Repository and service interfaces : create a BookRepository (Spring Data JPA) and a BookService interface with methods for querying by author and category.
Add MCP dependencies to pom.xml:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
</dependency>
<!-- Anthropic model support -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>
<!-- MCP server (WebMVC) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>These are preview versions, so additional Maven repositories (Spring Milestones, Snapshots, Central Snapshots) must be added:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>Proxy configuration (if required) for Anthropic access:
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ProxyConfig {
private final String PROXY_HOST = "127.0.0.1";
private final int PROXY_PORT = 10080;
@PostConstruct
public void setSystemProxy() {
System.setProperty("http.proxyHost", PROXY_HOST);
System.setProperty("http.proxyPort", String.valueOf(PROXY_PORT));
System.setProperty("https.proxyHost", PROXY_HOST);
System.setProperty("https.proxyPort", String.valueOf(PROXY_PORT));
System.out.println("System proxy configured: http://" + PROXY_HOST + ":" + PROXY_PORT);
}
}Enable MCP server in application.yml (or application.properties):
spring:
ai:
anthropic:
api-key: YOUR_ANTHROPIC_API_KEY
mcp:
server:
enabled: true
name: book-management-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messageAnnotate service methods with @Tool and @ToolParam to expose them to the LLM:
@Tool(name="findBooksByTitle", description="Search books by partial title")
public List<Book> findBooksByTitle(@ToolParam(description="Title keyword") String title) {
return bookRepository.findByTitleContaining(title);
}
@Tool(name="findBooksByAuthor", description="Exact author query")
public List<Book> findBooksByAuthor(@ToolParam(description="Author name") String author) {
return bookRepository.findByAuthor(author);
}
@Tool(name="findBooksByCategory", description="Exact category query")
public List<Book> findBooksByCategory(@ToolParam(description="Category") String category) {
return bookRepository.findByCategory(category);
}Register the tools in an MCP server configuration class:
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider bookToolCallbackProvider(BookService bookService) {
return MethodToolCallbackProvider.builder()
.toolObjects(bookService)
.build();
}
}Configure the ChatClient to use a system prompt and register the tools:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatClientConfig {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("You are a book‑management assistant. You can search by title, author, or category. Respond in a concise, friendly format.")
.defaultTools(toolCallbackProvider)
.build();
}
}Alternative function‑bean approach : expose query methods as @Bean Function<String, List<Book>> and reference them by name in the ChatClient configuration.
@Service
public class BookQueryService {
@Resource
private BookService bookService;
@Bean
public Function<String, List<Book>> findBooksByTitle() {
return title -> bookService.findBooksByTitle(title);
}
@Bean
public Function<String, List<Book>> findBooksByAuthor() {
return author -> bookService.findBooksByAuthor(author);
}
@Bean
public Function<String, List<Book>> findBooksByCategory() {
return category -> bookService.findBooksByCategory(category);
}
}ChatClient registration using the bean names:
return builder
.defaultSystem("You are a book‑management assistant…")
.defaultTools("findBooksByTitle", "findBooksByAuthor", "findBooksByCategory")
.build();Expose a REST controller that forwards user messages to the ChatClient:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Resource
private ChatClient chatClient;
@PostMapping
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
try {
String content = chatClient.prompt()
.user(request.getMessage())
.call()
.content();
return ResponseEntity.ok(new ChatResponse(content));
} catch (Exception e) {
return ResponseEntity.ok(new ChatResponse("Error: " + e.getMessage()));
}
}
}Initialize sample data with a CommandLineRunner implementation that inserts a list of books at startup:
@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {
@Resource
private BookRepository bookRepository;
@Override
public void run(String... args) throws Exception {
List<Book> sampleBooks = Arrays.asList(
new Book(null, "Spring实战(第6版)", "编程", "Craig Walls", LocalDate.of(2022,1,15), "9787115582247"),
new Book(null, "深入理解Java虚拟机", "编程", "周志明", LocalDate.of(2019,12,1), "9787111641247"),
new Book(null, "Java编程思想(第4版)", "编程", "Bruce Eckel", LocalDate.of(2007,6,1), "9787111213826"),
new Book(null, "算法(第4版)", "计算机科学", "Robert Sedgewick", LocalDate.of(2012,10,1), "9787115293800"),
new Book(null, "云原生架构", "架构设计", "张三", LocalDate.of(2023,3,15), "9781234567890"),
new Book(null, "微服务设计模式", "架构设计", "张三", LocalDate.of(2021,8,20), "9789876543210"),
new Book(null, "领域驱动设计", "架构设计", "Eric Evans", LocalDate.of(2010,4,10), "9787111214748"),
new Book(null, "高性能MySQL", "数据库", "Baron Schwartz", LocalDate.of(2013,5,25), "9787111464747"),
new Book(null, "Redis实战", "数据库", "Josiah L. Carlson", LocalDate.of(2015,9,30), "9787115419378"),
new Book(null, "深入浅出Docker", "容器技术", "李四", LocalDate.of(2022,11,20), "9787123456789")
);
bookRepository.saveAll(sampleBooks);
System.out.println("Data initialization complete, loaded " + sampleBooks.size() + " books.");
}
}Testing the Integration
After starting the application, sending a natural‑language request such as 帮我查找所有2023年出版的图书 to /api/chat triggers the LLM to invoke the appropriate @Tool method, retrieve matching records from the database, and return them in a readable format.
Conclusion
Integrating Spring Boot with MCP turns a conventional CRUD service into an AI‑driven assistant with minimal code changes. MCP provides a standardized bridge between LLMs and backend services, paving the way for "conversation‑as‑a‑service" architectures.
Code repository: https://github.com/Pitayafruits/spring-boot-mcp-demo
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
