Transform a Spring Boot CRUD Service into an AI‑Powered MCP Endpoint
This guide walks through converting a traditional Spring Boot book‑management API into a Model Context Protocol (MCP) service that can be invoked via natural‑language prompts, covering dependency setup, proxy configuration, @Tool annotations, MCP server registration, chat client wiring, data initialization, and end‑to‑end testing.
Understanding MCP
The Model Context Protocol (MCP) acts as an AI‑world universal adapter. It abstracts the heterogeneous communication protocols of services and databases into a single contract that a language model can invoke, similar to how gRPC standardizes RPC but purpose‑built for AI agents.
Traditional API vs MCP – concrete comparison
Consider a Spring Boot Book service that provides CRUD operations. In a classic REST client the caller must know the exact endpoint and request format. With MCP the same request – e.g. “Find all books published in 2023” – is expressed as natural language, and the model automatically selects the matching tool method.
Step‑by‑step conversion of the Spring Boot service
1. Import MCP‑related dependencies
<!-- Spring AI core dependency -->
<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 and are not available in Maven Central, so the Spring milestone and snapshot repositories 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>
</repositories>2. Configure proxy (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);
}
}3. Add MCP‑specific properties
# Spring AI API key
spring.ai.anthropic.api-key=YOUR_API_KEY
# Enable MCP server
spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=book-management-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.sse-message-endpoint=/mcp/message4. Annotate service methods with @Tool
Two approaches are possible: direct @Tool / @ToolParam annotations on the implementation class, or exposing the methods as Spring Function beans.
import com.example.entity.Book;
import com.example.repository.BookRepository;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService {
@Resource
private BookRepository bookRepository;
@Override
@Tool(name = "findBooksByTitle", description = "Fuzzy search by book title")
public List<Book> findBooksByTitle(@ToolParam(description = "title keyword") String title) {
return bookRepository.findByTitleContaining(title);
}
@Override
@Tool(name = "findBooksByAuthor", description = "Exact search by author")
public List<Book> findBooksByAuthor(@ToolParam(description = "author name") String author) {
return bookRepository.findByAuthor(author);
}
@Override
@Tool(name = "findBooksByCategory", description = "Exact search by category")
public List<Book> findBooksByCategory(@ToolParam(description = "category") String category) {
return bookRepository.findByCategory(category);
}
}5. Register the tools with the MCP server
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();
}
}6. Configure the chat client to use the registered 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 perform fuzzy title search, author search, and category search. " +
"Reply concisely and format results for readability.")
.defaultTools(toolCallbackProvider)
.build();
}
}Alternative: expose query methods as function beans
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.function.Function;
import java.util.List;
@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);
}
}When using function beans, the chat client registers them by name:
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("You are a book‑management assistant…")
.defaultTools("findBooksByTitle", "findBooksByAuthor", "findBooksByCategory")
.build();
}7. Expose a REST controller for the chat endpoint
import com.example.model.ChatRequest;
import com.example.model.ChatResponse;
import jakarta.annotation.Resource;
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 handling request: " + e.getMessage()));
}
}
}8. Load sample data with a CommandLineRunner
import com.example.entity.Book;
import com.example.repository.BookRepository;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
@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");
}
}9. End‑to‑end test
Sending a POST request to /api/chat with the message “帮我查找所有2023年出版的图书” triggers the LLM to invoke the findBooksByCategory tool, which queries the database and returns the matching records. The response contains the sample books from 2023.
Conclusion
Integrating Spring Boot with MCP turns a conventional CRUD service into an AI‑driven conversational interface with minimal code changes. MCP abstracts the glue logic, allowing developers to focus on business methods while the model handles natural‑language routing. As the MCP ecosystem matures, "dialogue‑as‑a‑service" may become a standard pattern for complex enterprise applications.
Technical reference repository: https://github.com/Pitayafruits/spring-boot-mcp-demo
Java Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
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.
