Tất cả bài viết Ảnh bìa: MCP không phải API: cách kết nối LLM với tool và dữ liệu
17 tháng 7, 2026 · 12 phút đọc

MCP không phải API: cách kết nối LLM với tool và dữ liệu

Giải thích kiến trúc Model Context Protocol, sự khác nhau giữa tools, resources và prompts, kèm một MCP server Python có thể chạy và kiểm tra bằng Inspector.

MCPAIDeveloper Tools

Một ứng dụng AI cần kiểm tra tồn kho trước khi trả lời khách hàng. Nếu không có kết nối ra bên ngoài, mô hình chỉ có thể dựa vào dữ liệu từng xuất hiện trong context hoặc những gì đã học khi huấn luyện. Nó không biết chiếc bàn phím mã KB-75 còn bao nhiêu sản phẩm tại thời điểm người dùng đặt câu hỏi.

Ta có thể viết riêng một đoạn tích hợp cho từng ứng dụng, nhưng cách này sớm tạo ra một ma trận adapter: mỗi host AI phải hiểu API, cơ chế xác thực và định dạng tool của từng hệ thống. Model Context Protocol (MCP) giải quyết phần giao tiếp ấy bằng một protocol chung. MCP quy định cách ứng dụng AI khám phá dữ liệu và chức năng do server cung cấp, cách gửi yêu cầu, nhận kết quả và trao đổi thông tin về các capability được hỗ trợ. Protocol không quy định mô hình phải suy luận thế nào, cũng không tự biến một API thành an toàn.1

Điểm phân biệt này đáng giữ ngay từ đầu: MCP là lớp giao tiếp, không phải mô hình AI, agent framework hay database.

Ba vai trò trong một kết nối MCP

Kiến trúc MCP có ba vai trò chính:1

  • Host là ứng dụng AI mà người dùng đang tương tác, chẳng hạn một IDE hoặc ứng dụng chat.
  • Client là thành phần bên trong host, duy trì kết nối với một MCP server.
  • Server cung cấp dữ liệu hoặc chức năng theo protocol MCP.

Một host có thể kết nối nhiều server. Theo mô hình kiến trúc được tài liệu MCP mô tả, host tạo một client riêng cho mỗi server, thay vì dùng một client duy nhất chia sẻ trạng thái cho mọi kết nối. Cách tách này tạo ra ranh giới rõ hơn cho capability, session và thông tin xác thực.

flowchart LR
    U[Người dùng] --> H[MCP host]
    H --> C1[MCP client A]
    H --> C2[MCP client B]
    C1 --> S1[Server tồn kho]
    C2 --> S2[Server tài liệu]
    S1 --> D[(Inventory API)]
    S2 --> R[(Document store)]

MCP chia giao tiếp thành hai lớp. Data layer dùng JSON-RPC 2.0 để biểu diễn request, response, notification, lifecycle và các primitive. Transport layer chịu trách nhiệm chuyển các message đó giữa client và server.1

Hai transport chính hiện nay là stdio và Streamable HTTP. Với stdio, host khởi chạy server như một process con rồi trao đổi message qua standard input và standard output. Mô hình này hợp với tích hợp cục bộ. Streamable HTTP dùng HTTP POST cho message từ client đến server và có thể dùng Server-Sent Events để streaming; nó phù hợp hơn khi server chạy từ xa hoặc phục vụ nhiều client.2

Transport không thay đổi ý nghĩa của request. Một lời gọi tools/call vẫn là tools/call, dù message đi qua pipe của process hay qua HTTP.

Tools, resources và prompts không phải ba tên gọi cho cùng một thứ

MCP server có thể cung cấp ba primitive cốt lõi: tools, resources và prompts.1 Chúng khác nhau ở mục đích sử dụng:

PrimitiveCung cấp gì?Ví dụ
ToolMột thao tác có thể thực thiTra tồn kho, tạo ticket, gửi truy vấn
ResourceDữ liệu có thể đọc theo URISchema, tài liệu, nội dung file
PromptTemplate tương tác có thể tái sử dụngQuy trình kiểm tra tồn kho, mẫu review

Tool phù hợp khi hệ thống cần thực hiện tính toán, gọi API hoặc tạo side effect. Mỗi tool có tên, mô tả và inputSchema; server có thể trả nội dung dạng text, image, resource link hoặc structured content. Specification yêu cầu client xem annotation của tool là thông tin không đáng tin cậy nếu annotation không đến từ server mà client tin cậy.3

Resource phù hợp với dữ liệu mà client có thể đọc và đưa vào context. Resource không nên bị biến thành tool chỉ vì SDK đăng ký tool thuận tiện hơn. Chẳng hạn, schema của bảng tồn kho là dữ liệu tham chiếu; inventory://schema diễn đạt mục đích ấy rõ hơn một tool tên get_schema.

Prompt là template có tên và tham số. Nó hữu ích khi muốn cung cấp một quy trình tương tác đã được chuẩn bị, nhưng prompt không phải system prompt bí mật và cũng không tạo ra ranh giới bảo mật. Host vẫn quyết định cách hiển thị hoặc sử dụng nội dung nhận được.

Tôi thường chọn primitive bằng một câu hỏi thực dụng: người dùng đang cần đọc dữ liệu, thực hiện thao tác, hay khởi tạo một cách làm việc? Câu trả lời lần lượt nghiêng về resource, tool và prompt. Có những trường hợp nằm giữa hai nhóm; khi đó nên ưu tiên semantics mà client cần, không ưu tiên decorator viết ngắn nhất.

Một phiên làm việc diễn ra như thế nào?

MCP là protocol có lifecycle. Khi bắt đầu kết nối, client gửi initialize với phiên bản protocol, capability và thông tin nhận diện. Server trả lại phiên bản đã chọn cùng capability mà nó hỗ trợ. Sau đó client gửi notification notifications/initialized để báo rằng giai đoạn khởi tạo đã hoàn tất.1

Khi cần dùng tool, luồng chính có thể được rút gọn như sau:

sequenceDiagram
    participant H as Host
    participant C as MCP client
    participant S as MCP server
    H->>C: Mở kết nối
    C->>S: initialize
    S-->>C: version + capabilities
    C->>S: notifications/initialized
    C->>S: tools/list
    S-->>C: tên + mô tả + inputSchema
    H->>H: Mô hình chọn tool và arguments
    C->>S: tools/call
    S-->>C: content hoặc structuredContent
    H-->>H: Đưa kết quả về luồng hội thoại

Hai bước tools/listtools/call tách discovery khỏi execution. Host không cần hard-code toàn bộ tool ngay trong giao diện tích hợp. Server cũng có thể thông báo khi danh sách tool thay đổi nếu hai bên đã thương lượng capability tương ứng.

Ở tầng JSON-RPC, một request gọi tool có dạng:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "lookup_inventory",
    "arguments": {
      "sku": "KB-75"
    }
  }
}

id dùng để ghép response với request. method xác định thao tác protocol, còn arguments phải khớp schema mà server đã công bố. SDK thường che phần message này, nhưng hiểu nó vẫn hữu ích khi debug: lỗi có thể nằm ở lựa chọn tool của mô hình, validation arguments, transport, hoặc logic nghiệp vụ phía server. Bốn trường hợp đó cần cách xử lý khác nhau.

Viết một MCP server tồn kho bằng Python

Ví dụ dưới đây dùng Python SDK chính thức và FastMCP. Tại ngày bài viết được xuất bản, nhánh v1.x vẫn là bản stable; tài liệu SDK khuyến nghị chặn major version nếu dự án chưa sẵn sàng cho v2.4

Khởi tạo project:

uv init inventory-mcp
cd inventory-mcp
uv add "mcp[cli]>=1.27,<2"

Tạo file server.py:

from pydantic import BaseModel, Field
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("inventory-demo")

INVENTORY = {
    "KB-75": {"name": "Bàn phím 75%", "quantity": 12},
    "MOUSE-01": {"name": "Chuột không dây", "quantity": 0},
}


class InventoryResult(BaseModel):
    sku: str
    name: str
    quantity: int = Field(ge=0)
    available: bool


@mcp.tool()
def lookup_inventory(sku: str) -> InventoryResult:
    """Tra tồn kho hiện tại theo mã SKU chính xác."""
    normalized_sku = sku.strip().upper()
    item = INVENTORY.get(normalized_sku)

    if item is None:
        raise ValueError(f"Không tìm thấy SKU: {normalized_sku}")

    quantity = item["quantity"]
    return InventoryResult(
        sku=normalized_sku,
        name=item["name"],
        quantity=quantity,
        available=quantity > 0,
    )


@mcp.resource("inventory://schema")
def inventory_schema() -> str:
    """Mô tả các trường dữ liệu của inventory."""
    return (
        "sku: mã sản phẩm; name: tên hiển thị; "
        "quantity: số lượng có thể bán; available: quantity > 0"
    )


@mcp.prompt()
def check_stock(sku: str) -> str:
    """Tạo yêu cầu kiểm tra tồn kho, không tự suy đoán số lượng."""
    return (
        f"Hãy gọi lookup_inventory với SKU {sku}. "
        "Trả lời bằng tên sản phẩm và số lượng; "
        "nếu tool báo lỗi thì nói rõ không tìm thấy SKU."
    )


if __name__ == "__main__":
    mcp.run(transport="stdio")

Ví dụ cố ý dùng một dictionary trong memory để phần MCP không bị lẫn với chi tiết database. Trong hệ thống thật, lookup_inventory có thể gọi inventory API hoặc repository. Cơ chế protocol không thay đổi: type hint và model đầu ra giúp SDK tạo schema; handler nhận arguments đã được chuyển thành kiểu Python; kết quả được đóng gói thành response MCP.

Chạy server:

uv run server.py

Với stdio, process sẽ chờ JSON-RPC message trên standard input. Không nên dùng print() để ghi log ra standard output, vì dữ liệu log sẽ trộn vào luồng protocol và làm client không parse được message. Tài liệu quickstart chính thức khuyến nghị ghi log qua stderr hoặc logging library được cấu hình phù hợp.5

Có thể kiểm tra server bằng MCP Inspector:

npx -y @modelcontextprotocol/inspector uv run server.py

Trong Inspector, hãy thực hiện lần lượt:

  • kiểm tra capability sau khi kết nối;
  • mở danh sách tools và xem inputSchema của lookup_inventory;
  • gọi tool với KB-75, rồi thử UNKNOWN để quan sát execution error;
  • đọc resource inventory://schema;
  • lấy prompt check_stock với một SKU cụ thể.

Việc thử cả trường hợp thành công lẫn lỗi quan trọng hơn một demo chỉ trả kết quả đúng. Tool description và error message là dữ liệu host có thể đưa lại cho mô hình; nếu chúng mơ hồ, mô hình khó sửa arguments ở lượt tiếp theo.

MCP không tự cấp quyền cho mô hình

Khi một tool đã xuất hiện trong danh sách, điều đó không có nghĩa mọi lời gọi đều nên được thực thi. Specification khuyến nghị phải có con người trong vòng lặp đối với tool invocation: giao diện cần cho người dùng biết tool nào đang được đưa cho mô hình, khi nào tool được gọi và kết quả của thao tác là gì; thao tác nhạy cảm nên có bước xác nhận.3

Ranh giới bảo mật nên được đặt trong code phía server, không chỉ trong prompt. Một server production cần xem xét ít nhất các điểm sau:

  • validate input bằng schema và kiểm tra thêm các ràng buộc nghiệp vụ;
  • cấp credential theo nguyên tắc quyền tối thiểu;
  • tách tool chỉ đọc khỏi tool tạo side effect;
  • đặt timeout, giới hạn kích thước dữ liệu và kiểm soát số lần gọi;
  • không ghi access token, dữ liệu cá nhân hoặc payload nhạy cảm vào log;
  • yêu cầu xác nhận trước thao tác khó hoàn tác;
  • kiểm tra authorization tại thời điểm thực thi, thay vì tin rằng host đã kiểm tra.

Với Streamable HTTP, authorization của MCP dựa trên các chuẩn OAuth liên quan và là capability tùy chọn ở tầng transport. Specification yêu cầu token phải được ràng buộc với đúng resource server; token passthrough sang hệ thống phía sau bị cấm vì làm mất kiểm soát audience và audit.67 Với stdio, credential thường được lấy từ environment của process thay vì áp dụng flow OAuth dành cho HTTP.

Cũng cần phân biệt lỗi protocol với lỗi thực thi tool. JSON-RPC message sai cấu trúc hoặc gọi method không tồn tại là lỗi ở tầng protocol. SKU không tồn tại, số lượng không hợp lệ hoặc upstream API timeout là lỗi trong quá trình thực thi tool. Bản specification hiện hành làm rõ rằng lỗi validation input của tool nên được trả như tool execution error để mô hình có cơ hội điều chỉnh lời gọi, thay vì biến nó thành protocol error.8

Khi nào MCP đáng dùng?

MCP có giá trị rõ nhất khi cùng một capability cần phục vụ nhiều host, hoặc một host cần kết nối nhiều nguồn dữ liệu và tool theo một mô hình thống nhất. Nó cũng phù hợp khi đội phát triển muốn tách lifecycle của tích hợp AI khỏi API nghiệp vụ: server MCP trở thành adapter có schema, discovery và quy tắc authorization riêng.

Ngược lại, nếu một ứng dụng chỉ có một function nội bộ, không có nhu cầu tái sử dụng và không cần client MCP, thêm protocol có thể chỉ làm tăng số thành phần phải vận hành. MCP cũng không thay thế API hiện có. Server trong ví dụ vẫn cần nguồn tồn kho thật; protocol chỉ chuẩn hóa cách host AI tìm và gọi chức năng đó.

Tôi cho rằng cách đánh giá thực tế nhất là bắt đầu từ ranh giới hệ thống. Nếu chức năng cần được nhiều AI host khám phá, cần schema rõ và cần policy thực thi độc lập với prompt, MCP là một lựa chọn đáng cân nhắc. Nếu vấn đề chỉ là gọi một hàm trong cùng process, function call thông thường có thể đã đủ.

Điều còn lại sau ví dụ tồn kho không phải decorator @mcp.tool(), mà là sự phân chia trách nhiệm: mô hình đề xuất hành động, host điều phối và xin xác nhận khi cần, còn server kiểm tra quyền rồi thực thi bằng dữ liệu thật. MCP làm đường biên ấy có cùng một ngôn ngữ giao tiếp; chất lượng và độ an toàn vẫn phụ thuộc vào cách ta thiết kế từng phía.

Tài liệu tham khảo

  1. Model Context Protocol. Architecture overview. modelcontextprotocol.io
  2. Model Context Protocol (2025). Transports — Specification 2025-11-25. modelcontextprotocol.io
  3. Model Context Protocol (2025). Tools — Specification 2025-11-25. modelcontextprotocol.io
  4. Model Context Protocol. Python SDK. GitHub
  5. Model Context Protocol. Build an MCP server. modelcontextprotocol.io
  6. Model Context Protocol (2025). Authorization — Specification 2025-11-25. modelcontextprotocol.io
  7. Model Context Protocol. Security Best Practices. modelcontextprotocol.io
  8. Model Context Protocol (2025). Key Changes — Specification 2025-11-25. modelcontextprotocol.io