JSON-RPC message ở bài trước cần một kênh vận chuyển (transport) để đi từ client sang server. MCP hỗ trợ chủ yếu hai loại, và chọn sai sẽ khiến agent chạy được ở máy dev nhưng chết khi deploy.
1. stdio (Standard Input/Output)
Client khởi chạy server như một tiến trình con (subprocess) và trao đổi message qua stdin/stdout. Mỗi message là một dòng JSON.
- Ưu điểm: cực đơn giản, không cần mạng, không cổng, độ trễ thấp. Bảo mật tốt vì chạy cục bộ.
- Nhược điểm: server phải nằm cùng máy với host. Không dùng được cho server đặt ở xa hay chia sẻ cho nhiều người.
{
"mcpServers": {
"orders": {
"command": "node",
"args": ["/opt/mcp/orders-server.js"],
"env": { "DB_URL": "postgres://localhost/shop" }
}
}
}
Host sẽ tự chạy node /opt/mcp/orders-server.js và nói chuyện qua stdio.
> Lưu ý sống còn với stdio: KHÔNG bao giờ print/console.log ra stdout để debug. stdout là kênh truyền JSON-RPC; log rác sẽ phá hỏng message. Hãy log ra stderr.
2. HTTP + SSE / Streamable HTTP
Server chạy như một dịch vụ web độc lập. Client kết nối qua HTTP. Phản hồi streaming dùng SSE (Server-Sent Events) hoặc cơ chế Streamable HTTP mới hơn.
- Ưu điểm: server đặt ở xa, chia sẻ cho nhiều client/nhiều người, scale được, đặt sau load balancer/gateway.
- Nhược điểm: phức tạp hơn — cần xác thực, HTTPS, quản lý session, CORS.
POST https://mcp.congty.com/orders
Authorization: Bearer <token>
Content-Type: application/json
{ "jsonrpc":"2.0", "id":1, "method":"tools/list" }
Khung quyết định: chọn transport nào?
| Tình huống | Chọn |
|---|---|
| Server chạy cùng máy, dùng cá nhân, truy cập file/DB cục bộ | stdio |
| Server dùng chung cho team, đặt trên server riêng | HTTP/SSE |
| Cần scale, đứng sau API gateway, có auth tập trung | HTTP/SSE |
| Prototype nhanh trên laptop | stdio |
Các bước triển khai transport
- Xác định vị trí server: cùng máy hay từ xa?
- Chọn transport theo bảng trên.
- Với stdio: cấu hình
command/args/envtrong host, đảm bảo log đi stderr. - Với HTTP: dựng endpoint, bật HTTPS, gắn xác thực (Bearer/OAuth), cấu hình URL trong client.
- Kiểm thử handshake trước, rồi mới tới tools/list.
- Ghi lại retry/timeout cho môi trường mạng không ổn định (chỉ HTTP).
Ví dụ tình huống
Bạn viết một MCP server đọc file dự án cho IDE cá nhân → stdio là hoàn hảo. Sáu tháng sau, cả team muốn dùng agent truy vấn CRM đặt trên cloud → bạn dựng phiên bản HTTP/SSE có OAuth. Logic tool (tools/list, tools/call) giữ nguyên; chỉ lớp transport thay đổi. Đây là sức mạnh tách lớp của MCP.
Tách logic khỏi transport ngay từ đầu
Bài học thực chiến quan trọng nhất về transport: đừng nhét logic nghiệp vụ vào lớp transport. Hãy viết các hàm tool/resource thuần túy (nhận tham số, trả kết quả), rồi để SDK gắn chúng vào transport nào tùy chọn. Với FastMCP hay các SDK tương đương, bạn thường chỉ đổi một dòng transport="stdio" sang transport="streamable-http" là chuyển được. Nếu logic tool của bạn gọi trực tiếp sys.stdin hay đụng vào chi tiết HTTP, bạn đã trói mình vào một transport và sẽ phải viết lại khi đổi.
Một lưu ý vận hành với HTTP: hãy quản lý session. Streamable HTTP cho phép nhiều client cùng nói chuyện với một server; mỗi client cần một session id riêng để server không lẫn ngữ cảnh giữa người dùng. Ngoài ra, đặt server sau một reverse proxy (nginx, API gateway) để lo HTTPS, rate limit và auth tập trung — thay vì tự viết mọi thứ trong server. Với stdio thì đơn giản hơn nhiều: một tiến trình, một client, không session.
Sai lầm thường gặp
- Log ra stdout khi dùng stdio → message JSON-RPC hỏng, client báo lỗi parse khó hiểu. Luôn log stderr.
- Dùng stdio cho server cần chia sẻ từ xa → không ai ngoài máy đó gọi được.
- Bật HTTP transport nhưng quên xác thực → phơi bày tools (gồm cả hành động ghi/xóa) ra Internet. Cực nguy hiểm.
- Quên xử lý reconnect với SSE khi kết nối rớt, khiến agent "treo" giữa chừng.
- Đường dẫn tương đối trong
args: host có thể chạy ở thư mục khác. Luôn dùng đường dẫn tuyệt đối.