MCP (Model Context Protocol) là gì? Giải thích cho dev Java
1. Tổng quan
MCP (Model Context Protocol) là một chuẩn mở giúp LLM (large language model) kết nối với dữ liệu, tool và hệ thống bên ngoài theo một cách thống nhất, thay vì mỗi ứng dụng AI phải tự viết tích hợp riêng cho từng nguồn dữ liệu. Bài này giải thích MCP là gì, kiến trúc của nó ra sao, và hướng dẫn bạn viết một MCP server đơn giản bằng Spring Boot (qua Spring AI) để expose một tool cho LLM gọi. Đọc xong, bạn sẽ hiểu khi nào nên dùng MCP và khi nào nó chỉ là over-engineering.
2. Vấn đề MCP giải quyết
Trước khi có MCP, mỗi ứng dụng AI (chatbot, agent) muốn truy cập database, file system, API nội bộ… đều phải viết adapter riêng. Nếu bạn có M ứng dụng AI và N nguồn dữ liệu, bạn cần tới M × N integration code khác nhau.
MCP chuẩn hóa lớp giao tiếp này: ứng dụng AI (gọi là Host) chỉ cần nói chuẩn MCP một lần, và bất kỳ hệ thống nào expose MCP server đều “nói chuyện” được với nó. Giống như USB-C cho thiết bị điện tử — một chuẩn cắm chung thay cho hàng chục loại cổng khác nhau.
Lưu ý: MCP không phải là framework thay thế REST hay gRPC cho giao tiếp service-to-service thông thường. Nó sinh ra để LLM/agent gọi tool, không phải để hai microservice backend nói chuyện với nhau.
3. Kiến trúc MCP
MCP có 3 vai trò chính:
- Host: ứng dụng chứa LLM (ví dụ Claude Desktop, một agent bạn tự viết).
- Client: thành phần bên trong Host, giữ kết nối 1-1 với một MCP server.
- Server: tiến trình expose capability (tool, resource, prompt) theo chuẩn MCP.
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Host │ 1-1 │ MCP Client │ JSON-RPC │ MCP Server │
│ (chứa LLM) │◄──────►│ │◄──────────►│ (DB, API, file) │
└──────────────┘ └──────────────┘ └──────────────────┘
Giao tiếp giữa client và server dùng JSON-RPC 2.0, truyền qua một trong hai transport phổ biến:
- stdio: server chạy như process con, client/server giao tiếp qua stdin/stdout. Phù hợp khi server chạy local.
- SSE/HTTP (Streamable HTTP): dùng khi server chạy từ xa, qua network.
3.1. Ba loại capability MCP server có thể expose
| Capability | Ý nghĩa | Ví dụ |
|---|---|---|
| Tools | Hàm LLM có thể gọi để thực hiện hành động | getWeather(city), createOrder(...) |
| Resources | Dữ liệu LLM có thể đọc (giống GET) | File log, bản ghi database |
| Prompts | Template prompt được server định nghĩa sẵn | “Tóm tắt đơn hàng theo mẫu X” |
Phần lớn use case thực tế xoay quanh Tools, nên ví dụ dưới đây tập trung vào đó.
4. Viết MCP server bằng Spring AI
Spring AI (từ bản 1.0.0-M6) hỗ trợ MCP qua starter riêng, cho phép bạn expose một bean Java thành MCP tool bằng annotation, không cần tự implement JSON-RPC.
4.1. Thêm dependency
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
4.2. Định nghĩa tool
@Service
public class WeatherService {
@Tool(description = "Lấy nhiệt độ hiện tại của một thành phố")
public String getTemperature(String city) {
return switch (city.toLowerCase()) {
case "ha noi" -> "28°C, nhiều mây";
case "sai gon" -> "32°C, nắng";
default -> "Không có dữ liệu cho thành phố: " + city;
};
}
}
@Toolđánh dấu phương thức này là một MCP tool. Thuộc tínhdescriptionrất quan trọng — LLM dùng chính mô tả này để quyết định có nên gọi tool hay không, và gọi với tham số gì.getTemperaturenhận mộtString city, trả về chuỗi mô tả thời tiết. Tham số và kiểu trả về được MCP server tự sinh JSON schema để LLM hiểu.
4.3. Đăng ký tool với MCP server
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
MethodToolCallbackProvider quét các phương thức có @Tool trong weatherService và đăng ký chúng vào MCP server tự động khi application context khởi động.
4.4. Cấu hình server
spring.ai.mcp.server.name=weather-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.stdio=true
Với stdio=true, server chạy như một process độc lập, giao tiếp qua stdin/stdout — phù hợp để test nhanh với Claude Desktop hoặc một MCP client tự viết. Nếu muốn deploy server như một service độc lập chạy từ xa, bạn đổi sang transport SSE và expose qua HTTP endpoint thay vì stdio.
Khởi động server:
mvn spring-boot:run
Started WeatherMcpApplication in 1.124 seconds
Registered MCP tool: getTemperature
5. Gọi MCP server từ client
Giả sử bạn viết một Host application (ví dụ agent nội bộ) cần gọi tool getTemperature trên.
5.1. Dependency client
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
5.2. Gọi tool
@Component
public class WeatherAgent {
private final McpSyncClient mcpClient;
public WeatherAgent(McpSyncClient mcpClient) {
this.mcpClient = mcpClient;
}
public String askTemperature(String city) {
CallToolRequest request = new CallToolRequest(
"getTemperature",
Map.of("city", city));
CallToolResult result = mcpClient.callTool(request);
return result.content().get(0).text();
}
}
McpSyncClientlà client Spring AI tự tạo bean dựa trên cấu hình transport (stdio hoặc SSE) khai báo trongapplication.yml.CallToolRequestgói tên tool (getTemperature) và tham số (city) thành một JSON-RPC call.result.content()trả về danh sách nội dung phản hồi; ở đây lấy phầntextđầu tiên.
Cấu hình client (ví dụ nối tới server qua stdio bằng cách chạy lại jar server như subprocess):
spring:
ai:
mcp:
client:
stdio:
connections:
weather-server:
command: java
args: -jar weather-mcp-server.jar
6. Test tool bằng JUnit 5 + AssertJ
Vì @Tool chỉ là annotation trên phương thức Java thông thường, bạn test logic nghiệp vụ độc lập với MCP — không cần dựng cả server:
class WeatherServiceTest {
private final WeatherService weatherService = new WeatherService();
@Test
void shouldReturnTemperatureForKnownCity() {
String result = weatherService.getTemperature("Ha Noi");
assertThat(result).contains("28°C");
}
@Test
void shouldReturnFallbackMessageForUnknownCity() {
String result = weatherService.getTemperature("Tokyo");
assertThat(result)
.startsWith("Không có dữ liệu cho thành phố:")
.contains("Tokyo");
}
}
Nếu muốn test luôn phần tích hợp MCP (tool có được đăng ký đúng tên, đúng schema không), bạn có thể viết test ở mức @SpringBootTest và kiểm tra ToolCallbackProvider:
@SpringBootTest
class McpServerConfigTest {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
@Test
void shouldExposeTemperatureTool() {
var toolNames = toolCallbackProvider.getToolCallbacks().stream()
.map(callback -> callback.getToolDefinition().name())
.toList();
assertThat(toolNames).contains("getTemperature");
}
}
Test này đảm bảo mỗi khi bạn refactor WeatherService, tool vẫn được expose đúng tên — tránh trường hợp đổi tên method mà quên cập nhật các chỗ LLM đang reference tới nó.
7. Lỗi hay gặp và khi nào không nên dùng MCP
Mô tả tool quá mơ hồ. Nếu description chỉ viết “lấy dữ liệu”, LLM rất dễ chọn sai tool khi có nhiều tool tương tự. Hãy viết description cụ thể, nêu rõ input/output mong đợi.
Dùng MCP để thay REST/gRPC nội bộ. MCP thêm một lớp JSON-RPC và transport riêng, tối ưu cho ngữ cảnh LLM gọi tool động — không tối ưu cho hai service backend gọi nhau với tần suất cao, độ trễ thấp. Dùng MCP ở đây là over-engineering.
Không kiểm soát quyền của tool. MCP server thường chạy với quyền của process host nó. Nếu bạn expose tool có thể xóa file hay chạy lệnh shell mà không giới hạn tham số đầu vào, LLM (hoặc prompt injection) có thể lạm dụng tool đó. Luôn validate tham số trong tool, đừng tin tưởng mù quáng input từ LLM.
Chạy stdio server trong môi trường serverless. Transport stdio cần spawn process con — không hoạt động tốt trên môi trường serverless/container ngắn hạn. Trường hợp này nên dùng transport SSE/HTTP.
Không đồng bộ version giữa client và server. MCP vẫn đang phát triển nhanh (nhiều breaking change giữa các bản spec). Khi upgrade SDK, kiểm tra kỹ changelog trước khi deploy production.
8. Kết luận
MCP chuẩn hóa cách LLM gọi tool và đọc dữ liệu bên ngoài, giải quyết bài toán tích hợp M × N giữa ứng dụng AI và nguồn dữ liệu. Với Spring AI, bạn có thể expose một service Java thành MCP tool chỉ bằng annotation @Tool, không cần tự implement JSON-RPC. Hãy dùng MCP khi bạn xây dựng agent/chatbot cần gọi tool động theo quyết định của LLM — và tránh dùng nó cho giao tiếp service-to-service thông thường, nơi REST hay gRPC vẫn là lựa chọn phù hợp hơn.