MCP (Model Context Protocol) đang thành chuẩn kết nối tool cho AI
1. Tổng quan
Các mô hình ngôn ngữ lớn (LLM) chỉ thực sự hữu ích khi đọc được dữ liệu thật và gọi được hệ thống thật: tra đơn hàng, đọc ticket, chạy query. Trước đây, mỗi ứng dụng AI tự viết cách tích hợp riêng cho từng tool. Model Context Protocol (MCP) ra đời để chuẩn hoá phần kết nối này.
Bài viết giải thích MCP là gì và vì sao nó đang được quan tâm. Sau đó bạn sẽ viết một MCP server nhỏ bằng Spring Boot, cấu hình client để gọi nó, và biết khi nào không nên dùng MCP.
2. MCP là gì?
MCP là một giao thức mở. Nó định nghĩa cách một ứng dụng AI phát hiện và gọi các khả năng bên ngoài: tool, dữ liệu và prompt mẫu. Bạn có thể hình dung MCP như “cổng USB” cho AI. Thiết bị nào tuân theo chuẩn thì cắm vào host nào hỗ trợ chuẩn cũng chạy được.
2.1. Ba vai trò chính
- Host: ứng dụng người dùng tương tác, ví dụ một AI assistant trên desktop, một IDE hay chatbot nội bộ.
- Client: thành phần nằm trong host. Mỗi client giữ kết nối một-một với một server.
- Server: chương trình cung cấp khả năng cho AI, ví dụ server đọc database đơn hàng hay server truy cập file system.
2.2. Ba loại “primitive” phía server
- Tools: hàm mà model có thể quyết định gọi, như
findOrderhaycancelOrder. - Resources: dữ liệu dạng chỉ đọc, như nội dung file hoặc bản ghi cấu hình, được đưa vào context.
- Prompts: mẫu prompt dựng sẵn mà người dùng chọn để dùng.
2.3. Giao thức bên dưới
MCP dùng JSON-RPC 2.0. Phổ biến nhất là hai kiểu transport: stdio, khi host khởi chạy server như một process con, và HTTP, khi server chạy từ xa. Một lời gọi tool trông như sau:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "findOrder",
"arguments": { "orderId": "ORD-1001" }
}
}
Luồng hoạt động cơ bản gồm ba bước:
- Client gửi
initializeđể hai bên trao đổi phiên bản giao thức và khả năng hỗ trợ. - Client gọi
tools/listđể lấy danh sách tool, kèm mô tả và JSON Schema của tham số. - Khi model quyết định dùng tool, client gửi
tools/callnhư ví dụ trên rồi trả kết quả về cho model.
Điểm quan trọng: model không gọi server trực tiếp. Host đứng giữa, nên host có thể hỏi người dùng xác nhận trước khi chạy một tool nhạy cảm.
3. Vì sao MCP đang được quan tâm?
3.1. Bài toán N × M
Giả sử có N ứng dụng AI và M hệ thống cần kết nối. Không có chuẩn chung thì bạn phải viết N × M bản tích hợp. Khi có một giao thức chung, mỗi ứng dụng chỉ cần hỗ trợ MCP một lần, và mỗi hệ thống chỉ cần một MCP server. Bài toán còn N + M.
3.2. Tách biệt trách nhiệm
Team sở hữu hệ thống đơn hàng viết và vận hành MCP server của mình. Team làm chatbot chỉ cần trỏ tới server đó. Logic nghiệp vụ, quyền truy cập và audit log nằm đúng chỗ, không bị nhân bản vào từng prompt hay từng ứng dụng.
3.3. Không khoá vào một nhà cung cấp model
Function calling của từng nhà cung cấp LLM có định dạng riêng. MCP nằm ở tầng trên định dạng đó. Server bạn viết hôm nay vẫn dùng được khi đổi model hay đổi host.
3.4. Hệ sinh thái đang lớn dần
Ngày càng nhiều AI assistant, IDE và framework hỗ trợ MCP. Có SDK cho nhiều ngôn ngữ, trong đó Java có SDK chính thức và Spring AI tích hợp sẵn. Đây là lý do nhiều người coi MCP là ứng viên sáng giá cho chuẩn kết nối tool. Dù vậy, giao thức vẫn đang tiếp tục phát triển, nên bạn cần theo dõi spec khi triển khai lâu dài.
4. Viết MCP server bằng Spring Boot
Bạn sẽ dựng một server cung cấp hai tool tra cứu và huỷ đơn hàng, chạy qua stdio.
4.1. Dependency
Spring AI cung cấp starter cho MCP server. Hãy import BOM của Spring AI và dùng phiên bản mới nhất tương thích với Spring Boot 3.x của bạn:
<properties>
<java.version>21</java.version>
<spring-ai.version><!-- phiên bản Spring AI hiện hành --></spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
</dependencies>
Starter spring-ai-starter-mcp-server hỗ trợ transport stdio. Nếu muốn chạy server qua HTTP, Spring AI có starter riêng cho Web MVC và WebFlux.
Lưu ý: Tên artifact của Spring AI đã từng thay đổi giữa các bản milestone. Hãy kiểm tra tài liệu ứng với phiên bản bạn chọn.
4.2. Domain và tool
public record Order(String id, String customerName, double total, String status) {}
@Service
public class OrderTools {
private final Map<String, Order> orders = new ConcurrentHashMap<>(Map.of(
"ORD-1001", new Order("ORD-1001", "Nguyen Van An", 350_000, "SHIPPING"),
"ORD-1002", new Order("ORD-1002", "Tran Thi Binh", 120_000, "PENDING")
));
@Tool(description = "Tra cứu đơn hàng theo mã, trả về khách hàng, tổng tiền và trạng thái")
public Order findOrder(@ToolParam(description = "Mã đơn, ví dụ ORD-1001") String orderId) {
Order order = orders.get(orderId);
if (order == null) {
throw new IllegalArgumentException("Không tìm thấy đơn hàng " + orderId);
}
return order;
}
@Tool(description = "Huỷ đơn hàng. Chỉ huỷ được đơn ở trạng thái PENDING")
public String cancelOrder(@ToolParam(description = "Mã đơn cần huỷ") String orderId) {
Order order = findOrder(orderId);
if (!"PENDING".equals(order.status())) {
return "Không thể huỷ: đơn đang ở trạng thái " + order.status();
}
orders.put(orderId, new Order(order.id(), order.customerName(), order.total(), "CANCELLED"));
return "Đã huỷ đơn " + orderId;
}
}
Hai annotation đóng vai trò chính:
@Tool(góiorg.springframework.ai.tool.annotation) đánh dấu method là tool. Phầndescriptionchính là thứ model đọc để quyết định có gọi tool hay không. Mô tả càng rõ thì model chọn càng đúng.@ToolParammô tả từng tham số. Spring AI sinh JSON Schema từ kiểu tham số và mô tả này.
Lưu ý cancelOrder trả về một thông báo thay vì ném exception khi vi phạm quy tắc nghiệp vụ. Model đọc được thông báo đó và giải thích lại cho người dùng một cách tự nhiên.
4.3. Đăng ký tool với MCP server
@SpringBootApplication
public class OrderMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMcpServerApplication.class, args);
}
@Bean
ToolCallbackProvider orderToolProvider(OrderTools orderTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderTools)
.build();
}
}
MethodToolCallbackProvider quét các method có @Tool trong orderTools. Auto-configuration của MCP server sẽ tìm mọi bean ToolCallbackProvider và công bố chúng qua tools/list.
4.4. Cấu hình cho stdio
spring.ai.mcp.server.name=order-mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.stdio=true
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/order-mcp-server.log
Với stdio, stdout là kênh truyền JSON-RPC. Mọi thứ khác in ra stdout sẽ làm hỏng giao thức, kể cả banner và log. Vì vậy cấu hình trên tắt banner, tắt log ra console và chuyển log sang file.
Lưu ý: Đây là lỗi hay gặp nhất khi mới làm MCP server qua stdio. Một dòng
System.out.printlnđể debug cũng đủ khiến client báo lỗi parse hoặc ngắt kết nối. Hãy dùng logger ghi ra file.
Build ra file jar:
./mvnw clean package -DskipTests
5. Kết nối từ client
Nhiều host cấu hình server qua một file JSON với khoá mcpServers. Tên file và vị trí của nó phụ thuộc vào từng host:
{
"mcpServers": {
"orders": {
"command": "java",
"args": ["-jar", "/opt/mcp/order-mcp-server.jar"]
}
}
}
Khi khởi động, host chạy lệnh java -jar ... như một process con, gửi initialize rồi tools/list. Bạn hỏi: “Đơn ORD-1002 còn huỷ được không? Nếu được thì huỷ giúp tôi.” Model sẽ gọi findOrder rồi cancelOrder. Kết quả trả về cho lời gọi thứ hai có dạng:
{"content":[{"type":"text","text":"Đã huỷ đơn ORD-1002"}],"isError":false}
Thông thường host sẽ hiển thị hộp thoại xin phép trước khi chạy tool. Bạn nên giữ hành vi này với các tool có tác dụng phụ như cancelOrder.
Nếu muốn ứng dụng Spring của bạn đóng vai client, Spring AI cũng có starter MCP client. Starter này biến tool của các server bên ngoài thành tool dùng được với ChatClient.
6. Test hành vi của tool
Tool chỉ là method Java bình thường, nên bạn test được mà không cần khởi động MCP:
class OrderToolsTest {
private final OrderTools tools = new OrderTools();
@Test
void shouldCancelPendingOrder() {
String result = tools.cancelOrder("ORD-1002");
assertThat(result).isEqualTo("Đã huỷ đơn ORD-1002");
assertThat(tools.findOrder("ORD-1002").status()).isEqualTo("CANCELLED");
}
@Test
void shouldRefuseToCancelShippingOrder() {
assertThat(tools.cancelOrder("ORD-1001"))
.contains("Không thể huỷ")
.contains("SHIPPING");
}
@Test
void shouldFailWhenOrderNotFound() {
assertThatThrownBy(() -> tools.findOrder("ORD-9999"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("ORD-9999");
}
}
Ba test bao quát luồng thành công, quy tắc nghiệp vụ và trường hợp không tìm thấy đơn. Phần giao thức đã có SDK lo. Thứ bạn cần đảm bảo nhất là logic nghiệp vụ trả về kết quả đúng và thông báo dễ hiểu cho model.
Muốn kiểm tra end-to-end, bạn có thể dùng các công cụ inspector cho MCP. Chúng cho phép liệt kê tool và gọi thử bằng tay.
7. Áp dụng thực tế: nên lưu ý gì
- Thiết kế tool theo nghiệp vụ, không theo bảng dữ liệu.
findOrdertốt hơn một toolexecuteSqlvạn năng. Tool hẹp vừa dễ kiểm soát quyền, vừa giúp model chọn đúng. - Phân quyền tối thiểu. Server nên dùng tài khoản chỉ có đúng quyền cần thiết. Hãy tách tool đọc và tool ghi, và cân nhắc chỉ bật tool ghi cho một số môi trường.
- Cảnh giác prompt injection. Dữ liệu tool trả về, ví dụ ghi chú do khách hàng nhập, có thể chứa câu lệnh nhằm điều khiển model. Đừng để kết quả từ một tool tự động kích hoạt hành động nguy hiểm mà không có người xác nhận.
- Chỉ dùng server đáng tin. Một MCP server bên thứ ba chạy qua stdio là một process trên máy bạn, với quyền của bạn. Mô tả tool cũng có thể bị cài nội dung độc hại. Hãy đọc mã nguồn hoặc chỉ dùng nguồn bạn tin cậy.
- Xác thực cho server từ xa. Khi chạy qua HTTP, hãy đặt server sau cơ chế xác thực phù hợp và ghi audit log cho mọi lời gọi tool.
- Giữ số lượng tool vừa phải. Mỗi tool tốn context cho phần mô tả. Quá nhiều tool khiến model dễ chọn nhầm.
8. Khi nào KHÔNG nên dùng MCP
- Ứng dụng nhỏ, chỉ một model, chỉ vài function. Function calling trực tiếp của framework, ví dụ
@Tooldùng ngay vớiChatClient, đơn giản hơn. Bạn không cần thêm một process và một tầng giao thức. - Luồng xử lý cố định, không cần model quyết định. Nếu bước 1 luôn gọi API A rồi bước 2 luôn gọi API B, cứ viết code bình thường. Đừng giao cho LLM việc mà một câu
iflàm tốt hơn. - Yêu cầu độ trễ cực thấp hoặc tính tất định cao. Mỗi vòng model → client → server cộng thêm độ trễ. Kết quả của model cũng không hoàn toàn tất định.
- Thao tác không thể hoàn tác mà không có người duyệt. Chuyển tiền hay xoá dữ liệu hàng loạt không nên phơi ra như tool tự động. Nếu bắt buộc phải có, hãy luôn kèm bước xác nhận.
- Bạn cần một API cho hệ thống khác gọi. MCP sinh ra để phục vụ AI. Giữa các service với nhau, REST, gRPC hay messaging vẫn là lựa chọn đúng.
9. Kết luận
MCP giải quyết một vấn đề rất thực tế: kết nối AI với tool và dữ liệu theo một cách chung, thay vì mỗi ứng dụng tự viết một kiểu. Nhờ mô hình host–client–server, ba primitive tools/resources/prompts và nền JSON-RPC, một server bạn viết có thể dùng lại ở nhiều host khác nhau.
Với Spring AI, viết MCP server chỉ cần vài annotation @Tool, một bean ToolCallbackProvider và cấu hình đúng cho stdio. Phần khó nằm ở chỗ khác: thiết kế tool hẹp và rõ, phân quyền chặt, đề phòng prompt injection. Bạn cũng cần tỉnh táo nhận ra những trường hợp mà code thông thường vẫn là giải pháp tốt hơn.