Giới Thiệu
Trong bài viết trước, chúng ta đã khám phá khái niệm về MCP (Model Context Protocol). Hôm nay, tôi sẽ hướng dẫn bạn cách tạo một client MCP đơn giản trong Java chỉ với 30 dòng mã. Mục tiêu không chỉ là viết mã mà còn hiểu rõ từng dòng mã và lý do tại sao chúng lại quan trọng.
Mã Hoàn Chỉnh
Trước tiên, hãy xem toàn bộ ví dụ hoạt động, sau đó chúng ta sẽ phân tích từng phần:
java
package com.example.mcp;
import java.time.Duration;
import java.util.Map;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
public class SimpleClientMCP {
public static void main(String[] args) {
int requestTimeoutSeconds = 30;
String basePath = System.getProperty("user.home") + "/Documents";
StdioClientTransport stdioTransport = new StdioClientTransport(
ServerParameters.builder("cmd.exe")
.args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath)
.build());
McpSyncClient client = McpClient.sync(stdioTransport)
.requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
.build();
client.initialize();
CallToolRequest request = new CallToolRequest("write_file", Map.of(
"path", basePath + "\\test.txt",
"content", "Olá!\n\nThis is a sample using Model Context Protocol."
));
CallToolResult result = client.callTool(request);
System.out.println(result.toString());
}
}
Khi bạn chạy đoạn mã này, nó sẽ tạo một tệp có tên test.txt trong thư mục Documents của bạn với một số nội dung. Đơn giản đúng không? Nhưng có rất nhiều điều đang diễn ra bên trong.
Kiến Trúc MCP Trong Ví Dụ Này
Trước khi đi sâu vào mã, hãy hiểu những gì thực sự đang diễn ra:
- Ứng dụng Java của bạn (client, được gọi là ứng dụng host trong tài liệu MCP)
- Giao thức MCP (tầng truyền thông, MCP Client)
- Máy chủ filesystem (nhà cung cấp công cụ)
[Java App] ←→ [MCP Protocol] ←→ [Filesystem Server] ←→ [Your Files]
Mã Java của bạn không trực tiếp tương tác với hệ thống tệp. Nó giao tiếp với một máy chủ MCP cung cấp các công cụ hệ thống tệp và máy chủ đó sẽ thực hiện các hoạt động tệp thực tế.
Phân Tích Từng Phần
Các Thư Viện: Những Gì Bạn Cần
java
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
Các thư viện này cung cấp cho bạn chức năng chính của MCP:
- McpClient/McpSyncClient: Client chính xử lý giao tiếp MCP.
- ServerParameters/StdioClientTransport: Cách kết nối với các máy chủ MCP.
- CallToolRequest/CallToolResult: Cách gọi các công cụ và nhận kết quả.
Thiết Lập Cấu Hình Cơ Bản
java
int requestTimeoutSeconds = 30;
String basePath = System.getProperty("user.home") + "/Documents";
Đây là bước thiết lập đơn giản. Thời gian chờ giúp ngăn ứng dụng của bạn bị treo nếu máy chủ MCP không phản hồi. basePath xác định nơi máy chủ filesystem có thể hoạt động - nó vừa là thư mục làm việc vừa là ranh giới bảo mật.
Tạo Transport: Cách Nói Chuyện Với Máy Chủ
java
StdioClientTransport stdioTransport = new StdioClientTransport(
ServerParameters.builder("cmd.exe")
.args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath)
.build());
Đây là nơi mà phép màu bắt đầu. Hãy phân tích:
- STDIO Transport có nghĩa là chúng ta đang giao tiếp với một quá trình con thông qua đầu vào/đầu ra tiêu chuẩn. Giống như có một cuộc trò chuyện qua một ống dẫn.
- ServerParameters.builder("cmd.exe") chỉ định transport chạy một lệnh Windows. Trên Linux/Mac, bạn sẽ sử dụng
"sh"hoặc"bash". - .args("/c", "npx @modelcontextprotocol/server-filesystem " + basePath) xác định lệnh thực tế cần chạy. Điều này khởi động máy chủ MCP filesystem và cho biết nó hoạt động trong thư mục Documents của bạn.
Các bước diễn ra như sau:
- Ứng dụng Java của bạn khởi động một quá trình con:
cmd.exe /c npx @modelcontextprotocol/server-filesystem C:\Users\YourName\Documents - Quá trình con đó chạy máy chủ filesystem Node.js
- Máy chủ khởi động và chờ thông điệp giao thức MCP
- Ứng dụng Java của bạn có thể giao tiếp với nó thông qua stdin/stdout
Xây Dựng Client: Kết Nối MCP
java
McpSyncClient client = McpClient.sync(stdioTransport)
.requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
.build();
- McpClient.sync() tạo một client đồng bộ. Cũng có phiên bản bất đồng bộ, nhưng đồng bộ dễ hiểu và gỡ lỗi hơn.
- .requestTimeout() thiết lập thời gian chờ cho phản hồi. Các hoạt động MCP có thể liên quan đến I/O tệp hoặc gọi mạng, vì vậy thời gian chờ là quan trọng.
- .build() tạo instance client thực tế. Tại thời điểm này, quá trình con đang chạy nhưng chưa thực hiện bắt tay MCP.
Khởi Tạo Kết Nối: Bắt Tay MCP
java
client.initialize();
Dòng mã này thực hiện rất nhiều việc:
- Đàm phán Giao thức: Client và máy chủ đồng ý về phiên bản MCP.
- Trao đổi Khả năng: Họ thông báo cho nhau về các tính năng mà họ hỗ trợ.
- Xác thực (nếu cần): Một số máy chủ yêu cầu thông tin xác thực.
- Khám Phá Công Cụ: Máy chủ thông báo cho client các công cụ có sẵn.
Sau khi initialize() hoàn tất, client của bạn biết chính xác máy chủ có thể làm gì.
Gọi Công Cụ: Công Việc Thực Tế
java
CallToolRequest request = new CallToolRequest("write_file", Map.of(
"path", basePath + "\\test.txt",
"content", "Olá!\n\nThis is a sample using Model Context Protocol."
));
CallToolResult result = client.callTool(request);
- CallToolRequest chỉ định:
- Tên công cụ: "write_file" (điều này phải khớp chính xác với những gì máy chủ cung cấp).
- Tham số: Một bản đồ các tên tham số với giá trị.
Máy chủ filesystem mong đợi các tham số cụ thể cho write_file:
path: Nơi tạo tệp.content: Nội dung sẽ đưa vào tệp.
client.callTool() gửi yêu cầu này đến máy chủ và chờ phản hồi.
Hiểu Kết Quả
java
System.out.println(result.toString());
Kết quả chứa:
- Trạng thái Thành công/thất bại
- Nội dung: Những gì công cụ trả về (thường là thông điệp thành công)
- Chi tiết Lỗi (nếu có gì đó không ổn)
Đối với một lần ghi tệp thành công, bạn sẽ thấy thông điệp như sau:
CallToolResult{content=[TextContent{text=File written successfully}], isError=false}
Điều Gì Đang Diễn Ra Bên Trong
Khi bạn gọi client.callTool(request), đây là quy trình thực tế:
- Ứng dụng Java của bạn tuần tự hóa yêu cầu thành JSON.
- Tầng transport gửi JSON qua stdin đến quá trình con.
- Máy chủ filesystem nhận và phân tích JSON.
- Máy chủ xác thực các tham số (đường dẫn có tồn tại không? nội dung có hợp lệ không?).
- Máy chủ thực hiện hoạt động tệp thực tế.
- Máy chủ gửi phản hồi trở lại dưới dạng JSON qua stdout.
- Tầng transport nhận phản hồi JSON.
- Ứng dụng Java của bạn giải tuần tự hóa phản hồi thành
CallToolResult.
Tất cả những điều này diễn ra một cách minh bạch. Bạn chỉ thấy cuộc gọi công cụ ở cấp độ cao.
Tại Sao Kiến Trúc Này Quan Trọng
- Bảo mật: Máy chủ filesystem chỉ hoạt động trong thư mục bạn chỉ định. Nó không thể truy cập toàn bộ hệ thống của bạn.
- Cô lập: Nếu máy chủ gặp sự cố, nó sẽ không làm sập ứng dụng Java của bạn.
- Độc lập Ngôn ngữ: Máy chủ được viết bằng Node.js, nhưng client của bạn là Java. MCP kết nối các công nghệ khác nhau.
- Khả năng Tái sử dụng Công cụ: Các ứng dụng khác có thể sử dụng cùng một máy chủ filesystem. Bạn không phải viết mã hệ thống tệp; bạn đang sử dụng một công cụ tiêu chuẩn.
Vấn Đề Thường Gặp và Giải Pháp
- Lỗi "Không tìm thấy lệnh": Đảm bảo bạn đã cài đặt Node.js và npm. Lệnh
npxcần có trong PATH của bạn. - Lỗi quyền: Máy chủ chỉ có thể truy cập thư mục bạn chỉ định trong
basePath. Đảm bảo thư mục đó tồn tại và có thể ghi. - Lỗi thời gian chờ: Một số hoạt động (đặc biệt trên ổ đĩa mạng) có thể chậm. Tăng
requestTimeoutSecondsnếu cần. - Vấn đề với dấu phân cách đường dẫn: Trên Windows, sử dụng
\\hoặc/trong các đường dẫn. Hằng sốFile.separatorrất hữu ích cho mã đa nền tảng.
Mở Rộng Ví Dụ Này
Muốn thử thêm các công cụ khác? Máy chủ filesystem cung cấp một số công cụ:
java
// Đọc một tệp
CallToolRequest readRequest = new CallToolRequest("read_file", Map.of(
"path", basePath + "\\test.txt"
));
// Liệt kê nội dung thư mục
CallToolRequest listRequest = new CallToolRequest("list_files", Map.of(
"path", basePath
));
// Di chuyển một tệp
CallToolRequest moveRequest = new CallToolRequest("move_file", Map.of(
"source", basePath + "\\test.txt",
"destination", basePath + "\\renamed.txt"
));
Mỗi công cụ có các tham số khác nhau, nhưng mẫu gọi là như nhau.
Những Gì Chúng Ta Đã Học
Ví dụ đơn giản này minh họa các khái niệm cốt lõi của MCP:
- Transport: Cách client và server giao tiếp (STDIO trong trường hợp này)
- Khởi Tạo: Bắt tay giao thức thiết lập kết nối
- Gọi Công Cụ: Mẫu yêu cầu/phản hồi để sử dụng chức năng của máy chủ
- Xử lý Lỗi: Cách thông báo lỗi trở lại client
Trong bài viết tiếp theo, tôi sẽ cho bạn biết cách kết nối với nhiều máy chủ MCP cùng một lúc và cách khám phá các công cụ một cách động. Chúng ta sẽ xây dựng trên nền tảng này để tạo ra các ứng dụng tinh vi hơn.
Điểm mấu chốt là MCP biến các công cụ thành dịch vụ mạng, nhưng với một giao thức tiêu chuẩn mà bất kỳ client nào cũng có thể sử dụng. Ứng dụng Java của bạn không cần biết cách ghi tệp, nó chỉ cần biết cách giao tiếp qua MCP.
Hãy thử chạy mã này và thử nghiệm với các hoạt động tệp khác nhau. Bạn còn muốn khám phá máy chủ MCP nào khác? Hãy cho tôi biết trong phần bình luận!