Mình đã giới thiệu với các bạn về Model Context Protocol với khả năng cho phép các LLM models có thể truy cập các dữ liệu sau thời điểm cut-off, hoặc các dữ liệu personal theo một chuẩn chung. Có rất nhiều MCP Server được xây dựng sẵn để chúng ta có thể sử dụng. Nếu các bạn muốn xây dựng cho mình một MCP Server sử dụng Java, các bạn có thể sử dụng Spring AI MCP Server Starter để làm điều này. Cụ thể như thế nào? Chúng ta hãy cùng nhau tìm hiểu trong bài viết này các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Spring Boot project với Model Context Protocol Server dependency như sau:
Spring AI MCP Server hỗ trợ 3 cơ chế transport, bao gồm:
- Standard Input/Output (STDIO): nói nôm na về transport này thì server giao tiếp với client sử dụng standard input (STDIN) và standard output (STDOUT) thay vì sử dụng socket (TCP/UDP), HTTP hay các network protocols khác. Transport STDIO này thích hợp cho các ứng dụng sử dụng command line hoặc desktop app đó các bạn.
- Spring MVC (Server-Sent Events)
- Spring WebFlux (Reactive SSE)
Mặc định thì nó hỗ trợ STDIO transport với dependency spring-ai-starter-mcp-server nhưng nếu các bạn muốn sử dụng HTTP transport với Server-Sent Events thì có thể sử dụng dependency spring-ai-starter-mcp-server-webmvc hoặc HTTP transport với Reactive Server-Sent Events thì cũng có thể sử dụng dependency spring-ai-starter-mcp-server-webflux. Cho ví dụ của bài viết này, mình sẽ sử dụng STDIO transport nên mình sẽ disable web application type cho Spring Boot của mình đi, sử dụng property spring.main.web-application-type như sau:
1 |
spring.main.web-application-type=none |
Mình cũng sẽ cần disable Spring Boot banner và console log đi vì những thông tin này sẽ được gửi xuống client nếu chúng ta vẫn để chúng được in ra, sử dụng những property sau:
1 2 |
spring.main.banner-mode=off logging.pattern.console= |
Có 2 loại MCP Server chúng ta có thể xây dựng sử dụng Spring AI MCP Server là Synchronous Server và Asynchronous Server. Synchronous server sẽ handle request/response synchronously, còn asynchronous server sẽ handle theo cơ chế non-blocking, asynchronously. Các bạn khai báo loại MCP server mà mình muốn xây dụng sử dụng sử dụng property spring.ai.mcp.server.type với giá trị là sync hoặc async. Cho ví dụ của mình, mình sẽ implement Synchronous server nên mình sẽ khai báo như sau:
1 |
spring.ai.mcp.server.type=sync |
Bây giờ mình sẽ xây dựng các tool cho MCP server ví dụ của mình.
Để xây dựng các tool sử dụng Spring AI MCP Server, các bạn có thể khai báo bean của một class có các phương thức được annotate với annotation @Tool. Ví dụ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.huongdanjava.springboot.springai; import java.util.HashMap; import java.util.Map; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; @Service public class HuongDanJava { private Map<String, Integer> users = new HashMap<>(); @Tool(name = "addUser", description = "Add a user with an age to the list. Need to specify user name and age of user") public String addUser(String name, int age) { users.put(name, age); return "Welcome, " + name; } @Tool(name = "getAge", description = "Base on the name in the input, return age of user") public String getAge(String name) { try { int age = users.get(name); return String.format("%s is %s years old", name, age); } catch (Exception e) { return "No user found!"; } } } |
Cho ví dụ này, mình đang expose 2 tool là “addUser” và “getAge” đó các bạn!
Cho mỗi tool, tương ứng với mỗi phương thức:
- Với annotation @Tool, có 2 thuộc tính mà các bạn cần khai báo là name và description. Thuộc tính name dùng để đăng ký với MCP Host còn description sẽ giúp cho các LLM model có thể hiểu được mục đích của tool và select tool cho đúng dựa vào prompt của người dùng, đó các bạn!
- Các tham số của phương thức sẽ được các LLM model dựa vào prompt của người dùng, truyền đúng giá trị. Phương thức sau khi xử lý business logic sẽ trả về kết quả. Các LLM model sẽ dựa vào kết quả này để trả về kết quả cho người dùng theo ngôn ngữ một cách tự nhiên nhất.
Sau khi đã định nghĩa các tool xong, các bạn cần expose các tool này với MCP client bằng cách khai báo bean của một list các ToolCallback, như sau:
1 2 3 4 |
@Bean List<ToolCallback> toolCallbacks(HuongDanJava huongDanJava) { return List.of(ToolCallbacks.from(huongDanJava)); } |
Bây giờ, mình sẽ build MCP Server của mình và sẽ sử dụng Claude Desktop để kiểm tra kết quả.
Các bạn có thể đọc lại bài viết Giới thiệu về Model Context Protocol để hiểu cách cấu hình MCP Server với Claude Desktop nhé!
Mình sẽ cấu hình MCP Server của mình với Claude Desktop như sau:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "mcpServers": { ... "huongdanjava": { "command": "/Users/khanhnguyenj/.sdkman/candidates/java/21.0.4-tem/bin/java", "args": [ "-jar", "/Users/khanhnguyenj/Documents/code/huongdanjava.com/spring-boot-spring-ai-mcp-server/target/spring-boot-spring-ai-mcp-server-0.0.1-SNAPSHOT.jar" ] } } } |
Tập tin /Users/khanhnguyenj/Documents/code/huongdanjava.com/spring-boot-spring-ai-mcp-server/target/spring-boot-spring-ai-mcp-server-0.0.1-SNAPSHOT.jar là tập tin của MCP server mà mình đã build ở trên đó các bạn!
Sau khi restart lại Claude Desktop, các bạn sẽ thấy danh sách các tool của mình được include vào Claude Desktop như sau:
Bây giờ thì các bạn có thể sử dụng Claude Desktop để thực hiện các tác vụ mà các bạn muốn.
Ví dụ, mình request nó thêm một số user với thông tin về tuổi tác, kết quả như sau:
và sau đó lấy thông tin tuổi tác của các user này, kết quả như sau:
Perfect đúng không các bạn? Claude Desktop có thể ghi nhận thông tin và lấy thông tin mà mình muốn. Nó đã trả lời kết quả với ngôn ngữ rất là tự nhiên.