Giao thức Model Context Protocol (MCP) đã bùng nổ cách đây khoảng 1 năm, mọi người đổ xô đi xây dựng các MCP server. Cơn sốt đó là có thật. Tuy nhiên, hầu hết các MCP server đều gây thất vọng. Đa số các nhà phát triển đổ lỗi cho giao thức. Trên mạng xã hội, cảm giác như giao thức này đang dần lụi tàn.
Nhưng thực tế áp dụng tại các doanh nghiệp lại cho thấy một câu chuyện khác. Các công ty vẫn đang triển khai MCP server. Các tích hợp vẫn đang vận hành. Nhưng kết quả thì sao? Thất vọng. Tại sao vậy?
Các nhà phát triển đang đối xử với MCP như một "vỏ bọc" (wrapper) cho REST API. Thực chất, MCP là Giao diện Người dùng dành cho Agent (tác vụ AI). Đối tượng sử dụng khác nhau đòi hỏi các nguyên tắc thiết kế khác nhau.
Giao thức vẫn ổn. Chỉ là các server thì không.
Bài viết này sẽ phân tích lý do tại sao các MCP server thất bại, 6 thực hành tốt nhất để xây dựng những server thực sự hiệu quả, và cách mà Skills (Kỹ năng) và MCP bổ trợ cho nhau.

MCP là gì?

MCP (Model Context Protocol) cung cấp một kết nối chung giữa các mô hình ngôn ngữ lớn (LLM) với các công cụ, nguồn dữ liệu và dịch vụ bên ngoài. Trước khi có MCP, mỗi lần tích hợp đều yêu cầu các trình kết nối tùy chỉnh (custom connectors). MCP tiêu chuẩn hóa ba thành phần cơ bản:
  • Tools (Công cụ): Các hàm mà Agent có thể gọi (ví dụ: search_documents, create_issue).
  • Resources (Tài nguyên): Dữ liệu mà Agent có thể đọc (tệp tin, bản ghi cơ sở dữ liệu).
  • Prompts (Gợi ý): Các luồng công việc được xây dựng sẵn mà người dùng hoặc Agent có thể kích hoạt.
  • Ý tưởng đằng sau MCP là: xây dựng MCP server một lần và sử dụng với bất kỳ Agent nào.

MCP KHÔNG PHẢI là gì

MCP server không phải là những lớp vỏ mỏng bao quanh API hiện tại của bạn. Một REST API tốt chưa chắc đã là một MCP server tốt. Chúng ta thường lầm tưởng rằng vì LLM "thông minh", chúng có thể sử dụng API như cách con người làm. Điều đó hoàn toàn sai.
Các nguyên tắc thiết kế REST API đề cao tính lắp ghép (composability), tính dễ khám phá (discoverability), tính linh hoạt và sự ổn định. Các endpoint nhỏ để lập trình viên kết hợp lại với nhau. Các schema tự mô tả (self-documenting).
Những nguyên tắc này hiệu quả với lập trình viên là con người, nhưng không hiệu quả với Agent AI. MCP là Giao diện Người dùng cho Agent. Người dùng khác nhau, nguyên tắc thiết kế phải khác nhau.

Nguyên tắc REST APILập trình viên (Con người)Agent AI
Khám phá (Discovery)Rẻ (chỉ cần đọc tài liệu một lần)Đắt (phải gửi kèm schema trong mọi yêu cầu)
Đối tượngKết hợp các endpoint nhỏ linh hoạtGọi công cụ nhiều bước, tốc độ lặp lại chậm
Tính linh hoạt (Flexibility)Càng nhiều lựa chọn càng linh hoạtSự phức tạp dẫn đến ảo giác (hallucination)
Ngoài ra, MCP server không phải là dịch vụ "đổ dữ liệu". Việc ném một lượng lớn dữ liệu thô vào Agent sẽ làm phình cửa sổ ngữ cảnh (context window) của nó.

Ví dụ thực tế: Theo dõi đơn hàng

Giả sử bạn đang xây dựng một Agent để theo dõi đơn hàng.
Với tư cách là một lập trình viên con người, bạn đọc tài liệu API một lần, viết một kịch bản gọi tuần tự GET /users, GET /orders, GET /shipments, gỡ lỗi và triển khai nó.
Một MCP server tồi sẽ phơi bày ba endpoint đó thành ba công cụ (tools). Agent sẽ phải tải mô tả của cả 3 công cụ, thực hiện 3 lượt phản hồi (round-trips) và lưu trữ tất cả các kết quả trung gian vào lịch sử hội thoại.
Một MCP server tốt sẽ chỉ cung cấp một công cụ duy nhất: track_order(email). Công cụ này sẽ gọi cả ba endpoint nội bộ và trả về kết quả cuối cùng: "Đơn hàng #12345 đã được gửi qua FedEx, dự kiến đến vào Thứ Năm." Cùng một kết quả, nhưng chỉ tốn một lần gọi và tập trung vào kết quả đầu ra (outcome-oriented).
MCP là Giao diện Người dùng cho một thực thể không phải con người. Vẫn là tư duy sản phẩm đó, nhưng đối tượng người dùng đã thay đổi.

Cách xây dựng một MCP Server tốt (Các thực hành tốt nhất)

Để tối ưu hóa server, bạn cần phải tinh chỉnh trải nghiệm người dùng. Dưới đây là 6 thực hành tốt nhất để xây dựng một MCP server chất lượng.

1. Tập trung vào Kết quả, không phải Thao tác

  • Sai lầm (Trap): Chuyển đổi các endpoint của REST API sang MCP tool theo tỷ lệ 1:1.
  • Giải pháp (Fix): Thiết kế công cụ xoay quanh mục tiêu mà người dùng hoặc Agent muốn đạt được.
Đừng cung cấp ba công cụ riêng biệt chỉ để kiểm tra trạng thái đơn hàng: get_user_by_email(), list_orders(user_id), get_order_status(order_id). Cách làm này ép Agent phải tự điều phối ba lượt phản hồi (round-trips). Thay vì ba công cụ nguyên tử đó, hãy đưa cho Agent một công cụ cấp cao duy nhất: track_latest_order(email). Hãy thực hiện việc điều phối trong mã nguồn của bạn, chứ không phải trong cửa sổ ngữ cảnh của LLM.

2. Làm phẳng các tham số (Flatten Your Arguments)

  • Sai lầm: Sử dụng các từ điển lồng nhau (nested dictionaries) phức tạp hoặc các đối tượng cấu hình làm đối số.
  • Giải pháp: Sử dụng các kiểu dữ liệu cơ bản ở cấp cao nhất và các kiểu dữ liệu bị ràng buộc.

❌ Tồi✅ Tốt
```def search_orders(filters: dict) -> list``````def search_orders(email: str, status: Literal["pending", "shipped", "delivered"] = "pending", limit: int = 10) -> list```
Agent phải tự đoán cấu trúc của `dict`Cấu trúc rõ ràng, có kiểu dữ liệu và ràng buộc
Dễ gây ra ảo giác về các key, thiếu trường bắt buộcLiteral giới hạn lựa chọn, giá trị mặc định giúp giảm bớt việc ra quyết định cho AI

3. Chỉ dẫn chính là Ngữ cảnh

  • Sai lầm: Để trống docstring (phần mô tả hàm), thông báo lỗi chung chung.
  • Giải pháp: Mọi đoạn văn bản đều là một phần ngữ cảnh của Agent.
Docstring chính là lời chỉ dẫn. Hãy nêu rõ:
  • Khi nào nên dùng công cụ: ("Sử dụng khi người dùng hỏi về trạng thái đơn hàng")
  • Cách định dạng đối số: ("Email phải được viết thường")
  • Kết quả trả về là gì: ("Trả về ID đơn hàng và trạng thái hiện tại")
Thông báo lỗi cũng là ngữ cảnh! Nếu việc gọi công cụ thất bại, đừng ném ra một ngoại lệ (exception) của Python. Hãy trả về một chuỗi văn bản hữu ích: "Không tìm thấy người dùng. Vui lòng thử tìm kiếm bằng địa chỉ email thay thế." Agent sẽ xem lỗi này như một quan sát và sử dụng chỉ dẫn của bạn để tự sửa lỗi trong lượt hội thoại tiếp theo.

4. Sàng lọc quyết liệt (Curate Ruthlessly)

  • Sai lầm: Phơi bày mọi thứ mà API có thể làm. Trả về mọi thứ mà API trả về.
  • Giải pháp: Thiết kế để dễ khám phá, không phải để phơi bày cạn kiệt.
Các Agent hoạt động dưới sự ràng buộc chặt chẽ về ngữ cảnh. Mỗi mô tả công cụ, mỗi dữ liệu trả về, mỗi thông báo lỗi đều cạnh tranh nhau trong cửa sổ ngữ cảnh.
  • Chỉ nên có 5–15 công cụ trên mỗi server.
  • Một server, một nhiệm vụ duy nhất.
  • Xóa bỏ các công cụ không sử dụng.
  • Chia nhỏ theo vai trò (Admin/Người dùng).
Hãy xây dựng để Agent có thể tìm đúng công cụ nhanh chóng và nhận được phản hồi có thể thực thi ngay lập tức.

5. Đặt tên công cụ để dễ khám phá

  • Sai lầm: Tên chung chung như create_issue hoặc send_message.
  • Giải pháp: Tên có tiền tố dịch vụ và hướng hành động.
MCP server của bạn chạy cùng lúc với nhiều server khác. Nếu cả GitHub và Jira đều có công cụ create_issue, Agent sẽ phải đoán. Hãy dùng quy tắc: {dịch vụ}_{hành động}_{tài nguyên}.
Ví dụ: slack_send_message, linear_list_issues, sentry_get_error_details.
Lưu ý: Một số MCP client tự động thêm tiền tố tên server vào công cụ.

6. Phân trang cho các kết quả lớn

  • Sai lầm: Trả về hàng trăm bản ghi cùng lúc.
  • Giải pháp: Phân trang kèm theo siêu dữ liệu (metadata).
  • Tôn trọng tham số limit (mặc định nên là 20–50).
  • Trả về các thông tin như has_more, next_offset, total_count.
  • Tuyệt đối không tải tất cả kết quả vào bộ nhớ.

Ví dụ thực tế: Gmail MCP Server

Bạn đang xây dựng một MCP server cho Gmail. Dưới đây là cách mà hầu hết các lập trình viên tiếp cận so với thiết kế ưu tiên cho Agent (agent-first design).
Tiếp tục bản dịch phần so sánh "Trước và Sau" cho ví dụ Gmail MCP Server và mối quan hệ giữa Skills và MCP:

Trước khi tối ưu (Cách làm cũ)

# Việc đọc một email yêu cầu 2 công cụ + phải hiểu các kiểu dữ liệu lồng nhau
def messages_list(query: str, max_results: int) -> {"messages": [{"id": str, "threadId": str}], "nextPageToken": str}: ...
def messages_get(message_id: str, format: str) -> {"id": str, "snippet": str, "payload": {"headers": list, "body": {"data": str}}}: ...
 
# Việc gửi một email yêu cầu mã hóa base64 cho một tin nhắn MIME
def messages_send(message: {"raw": str}) -> {"id": str, "threadId": str}: ...  # raw = base64url RFC 2822
 
# Việc tạo bản nháp có đối tượng tin nhắn lồng nhau phức tạp
def drafts_create(draft: {"message": {"raw": str}}) -> {"id": str, "message": {"id": str}}: ...
Các vấn đề: Agent buộc phải tự cấu trúc dữ liệu {"raw": base64(...)} và phân tách (parse) phần payload.body.data bị lồng sâu. Tên các hàm quá chung chung.

Sau khi tối ưu (Thiết kế ưu tiên Agent)

# Việc đọc: 2 công cụ với tham số phẳng và dữ liệu trả về đã được sàng lọc
def gmail_search(query: str, limit: int = 10) -> [{"id": str, "subject": str, "sender": str, "date": str, "snippet": str}]: ...
def gmail_read(message_id: str) -> {"subject": str, "sender": str, "body": str, "attachments": [str]}: ...
 
# Việc viết: Các ví dụ đơn giản, không bao gồm cc, bcc để giảm độ phức tạp.
def gmail_send(to: List[str], subject: str, body: str, reply_to_id: str = None) -> {"success": bool, "message_id": str}: ...

Skills và MCP: Bổ trợ chứ không phải Đối đầu

Skills (Kỹ năng) đóng gói các chỉ dẫn, siêu dữ liệu (metadata) và tài nguyên mà Agent sẽ sử dụng khi cần thiết. Chúng dựa trên hệ thống tệp tin (filesystem) với cơ chế tiết lộ thông tin tăng dần (progressive disclosure).
Mỗi Skill có một tên gọi và mô tả cụ thể, vốn là một phần của ngữ cảnh mô hình (Model context). Dựa trên ngữ cảnh được cung cấp, Mô hình/Agent có thể quyết định nên sử dụng Skill đó hay không.
[Image showing how Skills and MCP servers interact with an LLM]
  • MCP đóng vai trò là "đường ống" kỹ thuật để kết nối dữ liệu và công cụ.
  • Skills đóng vai trò là "trí thông minh" và "quy trình" để hướng dẫn Agent cách sử dụng những đường ống đó một cách hiệu quả.

Cấp độKhi nào được nạpChi phí TokenNội dung
Siêu dữ liệu (Metadata)Lúc khởi động~100 tokensName và description từ file YAML
Chỉ dẫn (Instructions)Khi được kích hoạt< 5k tokensNội dung chính của file SKILL.md
Tài nguyên (Resources)Khi cần thiếtKhông giới hạnCác kịch bản (scripts) chạy qua bash
Skills không trực tiếp thêm các định nghĩa công cụ (tool definitions), nhưng có thể bao gồm các kịch bản chạy cục bộ để cung cấp khả năng tương tự như MCP server. Điểm khác biệt là Skills tận dụng các công cụ thực thi chung (như bash) thay vì định nghĩa các schema công cụ mới. Điều này có thể dẫn đến việc Agent cần nhiều bước hơn để thực hiện và khám phá. Ngược lại, MCP cung cấp các giao diện có cấu trúc, giúp mô hình nhận được phản hồi có kiểu dữ liệu rõ ràng và xác thực tham số chuẩn xác.
Không có cái nào tốt hơn cái nào. Lựa chọn tùy thuộc vào trường hợp sử dụng và sở thích của bạn. Trong bối cảnh doanh nghiệp, MCP server tỏa sáng khi các đội ngũ muốn công khai dịch vụ của riêng họ. Skills sẽ bổ trợ cho điều này bằng cách dạy cho Agent biết khi nào và làm thế nào để kết hợp các công cụ đó cho những luồng công việc cụ thể. Hãy sử dụng cả hai.

Kết luận

Khi xây dựng một MCP Server, bạn không phải đang xây dựng cơ sở hạ tầng (infrastructure), mà bạn đang xây dựng một giao diện cho Agent AI.
  • Kết quả quan trọng hơn thao tác: Thiết kế xoay quanh mục tiêu của Agent.
  • Làm phẳng các tham số: Sử dụng các kiểu dữ liệu cơ bản (primitives) và enum.
  • Chỉ dẫn chính là ngữ cảnh: Docstring và thông báo lỗi rất quan trọng.
  • Sàng lọc quyết liệt: Ít công cụ hơn, phản hồi gọn gàng hơn.
  • Đặt tên để dễ khám phá: Sử dụng tên có tiền tố dịch vụ.
  • Phân trang kết quả: Cung cấp siêu dữ liệu cho các danh sách lớn.
MCP là một Giao diện Người dùng dành cho Agent AI. Hãy xây dựng nó đúng với bản chất đó.
Các tài nguyên khác: Block's MCP Playbook, GitHub's Security Guide, FastMCP AI Engineering Summit.
Nguồn bài viết từ Tác giả Phil Schmid