- MCP là giao thức chuẩn hóa kênh giao tiếp giữa ứng dụng AI (MCP Client) và các nguồn dữ liệu/công cụ ngoài (MCP Server), giúp loại bỏ gánh nặng viết mã tích hợp API thủ công.
- Nguyên lý cốt lõi là Separation of Concerns: chuyển dịch định nghĩa tool schema và thực thi từ server ứng dụng sang MCP Server chuyên biệt — nhà cung cấp dịch vụ tự maintain, lập trình viên chỉ kết nối.
- MCP và Tool Use là hai khái niệm bổ trợ nhau: MCP là hạ tầng cung cấp công cụ (ai build và chạy tool), Tool Use là năng lực suy luận của Claude để quyết định khi nào gọi tool nào.
TL;DR — Model Context Protocol (MCP) là giao thức chuẩn hóa kết nối, hoạt động như một "USB-C cho AI": tách biệt tầng ứng dụng (MCP Client) và tầng kết nối dịch vụ (MCP Server). Thay vì tự code API wrapper thủ công cho từng hệ thống, tôi chỉ việc cắm Client vào các MCP Server đã được đóng gói sẵn tool, prompt và resource để cấp công cụ và dữ liệu cho Claude.
1. Bài toán tích hợp API thủ công — gánh nặng bảo trì (The Problem)
Khi tích hợp AI vào các hệ thống doanh nghiệp (GitHub, Slack, Jira, Database...), tôi luôn gặp phải ba rủi ro lớn liên quan đến chi phí bảo trì (maintenance cost) và năng suất của team:
Cơn ác mộng Glue Code
Giả sử tôi đang xây một chat interface nơi user có thể hỏi Claude về dữ liệu GitHub của họ: "What open pull requests are there across all my repositories?"
Để Claude trả lời được, tôi cần cung cấp cho Claude các tools để truy cập GitHub API. Vấn đề là GitHub có hàng nghìn chức năng: repositories, pull requests, issues, projects, actions... Không có MCP, tôi phải:
- Tự viết hàng chục Tool Schema bằng định dạng JSON Schema — mỗi tool một bộ
name,description,input_schema. - Tự viết code thực thi (API wrapper) cho từng hàm:
get_repos(),list_repos(),create_repos(),search_issues(),update_issue(),create_issue(),get_issue(),create_file()... - Tự test và maintain toàn bộ đống code tích hợp đó.
Đây là một lượng công việc khổng lồ — và tất cả chỉ mới cho mỗi GitHub.
Rủi ro từ bên thứ ba (Third-party API changes)
Bất cứ khi nào GitHub thay đổi cấu trúc API hoặc deprecate endpoint cũ, tích hợp của tôi sẽ vỡ. Team dev phải liên tục túc trực để sửa lỗi và cập nhật schema — một vòng lặp bảo trì không có hồi kết.
Góc nhìn thực tế: quản lý dự án offshore VN/JP
Trong các dự án offshore Nhật – Việt tôi đã tham gia, tôi thấy rõ kịch bản này: team Việt Nam xây integration layer gọi API nội bộ của khách hàng Nhật (Backlog, Chatwork, kintone...). Mỗi lần khách đổi API version, team mất 1–2 sprint chỉ để sửa glue code — thời gian đó đáng lẽ dùng để phát triển feature mới. MCP ra đời chính để triệt tiêu lớp glue code này: ai sở hữu API thì người đó maintain integration.
2. MCP giải quyết như thế nào? (How MCP Works)
MCP thay đổi hoàn toàn cách tiếp cận bằng nguyên lý Phân tách trách nhiệm (Separation of Concerns): chuyển dịch gánh nặng định nghĩa tool và thực thi từ server ứng dụng của tôi sang các MCP Server chuyên biệt (dedicated MCP servers).
Kiến trúc 3 thành phần
MCP Client (Ứng dụng của tôi): Đóng vai trò là Client kết nối với mô hình Claude. Nó không cần biết chi tiết GitHub hoạt động ra sao — chỉ cần duy trì kết nối MCP đến Server.
MCP Server (Tác nhân trung gian): Là một ứng dụng gọn nhẹ chạy độc lập, đóng gói toàn bộ logic gọi API của dịch vụ ngoài và tự khai báo (expose) các công cụ dưới dạng một bộ schema chuẩn hóa. Mỗi MCP Server cung cấp 3 loại tài nguyên:
- Tools — các hàm Claude có thể gọi (ví dụ
get_repos()). - Prompts — các prompt template được đóng gói sẵn.
- Resources — dữ liệu/tài liệu Claude có thể đọc.
Outside Service (Dịch vụ bên ngoài): GitHub, AWS, Google Calendar, Postgres Database, Slack...
Ví dụ cụ thể
Thay vì tôi tự viết schema cho hàm get_repos() rồi viết code gọi GitHub REST API, GitHub MCP Server đã dựng sẵn tất cả. Ứng dụng của tôi (MCP Client) chỉ cần kết nối tới Server này. Khi Claude cần tìm repo, luồng xử lý như sau:
- Claude quyết định gọi tool
get_repos(đây là Tool Use — năng lực suy luận của model). - Yêu cầu được gửi từ Claude → MCP Client → MCP Server.
- MCP Server thực thi logic gọi GitHub API, lấy dữ liệu.
- Kết quả trả ngược: MCP Server → MCP Client → Claude.
- Claude đọc kết quả và trả lời user.
Tôi không viết một dòng code GitHub nào — MCP Server lo hết.
3. Cơ chế truyền tải và các loại Message trong MCP (Transport & Message Types)
Dưới góc độ kiến trúc hệ thống, MCP được thiết kế cực kỳ linh hoạt để hoạt động trên nhiều môi trường khác nhau nhờ hai đặc tính cốt lõi:
Độc lập với phương thức truyền tải (Transport Agnostic)
MCP Client và MCP Server có thể giao tiếp với nhau qua bất kỳ giao thức mạng hoặc kênh truyền dữ liệu nào (transport layer). Chúng không bị trói buộc vào một công nghệ mạng cụ thể:
- Standard Input/Output (stdio): Đây là phương thức phổ biến nhất. Client và Server chạy trên cùng một máy, giao tiếp trực tiếp qua dòng đọc/ghi chuẩn (stdin/stdout). Tốc độ cực nhanh và bảo mật cao vì không cần mở cổng mạng ra ngoài.
- WebSockets: Dùng khi Client và Server chạy ở hai máy chủ khác nhau, cần kết nối real-time hai chiều liên tục qua mạng.
- HTTP / SSE (Server-Sent Events): Dùng cho mô hình client-server truyền thống qua web.
Các loại Message chính trong đặc tả MCP (MCP Message Types)
Khi Client kết nối thành công tới Server, chúng trao đổi thông tin bằng các cấu trúc message được định nghĩa sẵn trong đặc tả kỹ thuật (specification) của MCP. Hai cặp message quan trọng nhất cần nắm cho kỳ thi là:
- Truy vấn danh sách công cụ (Tool Discovery):
ListToolsRequest: Client hỏi Server: "Ông đang có những tools nào?"ListToolsResult: Server trả về danh sách các tools kèm schema chi tiết (JSON Schema) của từng tool.
- Thực thi công cụ (Tool Execution):
CallToolRequest: Client yêu cầu Server: "Hãy chạy tool [X] với các tham số [Y] này hộ tôi."CallToolResult: Server thực thi xong và trả về kết quả dữ liệu (hoặc báo lỗi).
4. Luồng xử lý dữ liệu hoàn chỉnh (The End-to-End Flow)
Hãy xem cách một câu hỏi của user: "Tôi có những repositories nào?" đi qua toàn bộ hệ thống tích hợp MCP. Đây là chu trình 12 bước khép kín từ user đến GitHub và trả lại Claude:
Chu trình chia làm 2 pha: Tool Discovery (bước 1–5, lấy danh sách tool) rồi Tool Execution (bước 6–12, chạy tool và trả lời).
| # | Luồng đi | Message / Hành động |
|---|---|---|
| Pha 1 — Tool Discovery | ||
| 1 | User → App Server (tôi) | "What repositories do I have?" |
| 2 | App → MCP Client | Hỏi các tool khả dụng |
| 3 | Client → MCP Server | ListToolsRequest — "Bạn có tool nào?" |
| 4 | Server → Client | ListToolsResult — danh sách tool + JSON Schema (get_repos…) |
| 5 | App → Claude | Gửi câu hỏi user + danh sách Tool Schemas |
| Pha 2 — Tool Execution | ||
| 6 | Claude → App | Suy luận, quyết định gọi get_repos → trả tool use request |
| 7 | App → MCP Client | Yêu cầu chạy tool get_repos |
| 8 | Client → MCP Server | CallToolRequest — chạy get_repos |
| 9 | Server → GitHub API | Gọi GitHub API thực tế |
| 10 | GitHub → Server → Client | Trả repository data, đóng gói thành CallToolResult |
| 11 | App → Claude | Gửi tool result về cho Claude (follow-up message) |
| 12 | Claude → App → User | Soạn câu trả lời cuối: "Your repositories are…" |
Chu trình này nhìn thì phức tạp, nhưng mỗi thành phần làm đúng một vai trò duy nhất. MCP Client đóng vai trò che giấu toàn bộ sự phức tạp trong việc kết nối với server, giúp app của tôi chỉ cần tập trung vào nghiệp vụ chính.
5. Phát triển MCP Server bằng Python SDK (Building an MCP Server)
Việc xây dựng một MCP Server trở nên cực kỳ đơn giản khi tôi sử dụng SDK chính thức của Anthropic (Python hoặc TypeScript). Thay vì phải viết các bộ JSON Schema khổng lồ và phức tạp bằng tay, tôi có thể định nghĩa các công cụ thông qua các decorators (bộ trang trí) và để SDK tự động xử lý phần việc nặng nhọc phía sau.
Khởi tạo MCP Server với FastMCP
FastMCP là một high-level framework nằm trong SDK của Anthropic, giúp việc khởi tạo Server chỉ tốn đúng 1 dòng code:
from mcp.server.fastmcp import FastMCP
# Khởi tạo một MCP Server tên là "DocumentMCP"
mcp = FastMCP("DocumentMCP", log_level="ERROR")
# Giả lập database tài liệu lưu tạm trong bộ nhớ (In-memory dict)
docs = {
"deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
"report.pdf": "The report details the state of a 20m condenser tower.",
"plan.md": "The plan outlines the steps for the project's implementation."
}
Định nghĩa công cụ bằng Decorator (@mcp.tool)
Để đăng ký một hàm Python thông thường thành một công cụ mà Claude có thể hiểu và gọi, tôi chỉ cần bọc nó bằng decorator @mcp.tool.
SDK sẽ tự động đọc Python Type Hints (gợi ý kiểu dữ liệu) và các mô tả Pydantic Field để tự động sinh ra JSON Schema chuẩn gửi cho Claude.
1. Xây dựng công cụ đọc tài liệu (read_doc_contents)
from pydantic import Field
@mcp.tool(
name="read_doc_contents",
description="Read the contents of a document and return it as a string."
)
def read_document(
doc_id: str = Field(description="Id of the document to read")
):
if doc_id not in docs:
# Ném lỗi trực tiếp bằng Python Exception, SDK tự dịch sang CallToolResult lỗi
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
2. Xây dựng công cụ sửa tài liệu (edit_document)
Công cụ này thực hiện thao tác tìm và thay thế (find-and-replace) chuỗi trong tài liệu:
@mcp.tool(
name="edit_document",
description="Edit a document by replacing a string in the documents content with a new string."
)
def edit_document(
doc_id: str = Field(description="Id of the document that will be edited"),
old_str: str = Field(description="The text to replace. Must match exactly, including whitespace."),
new_str: str = Field(description="The new text to insert in place of the old text.")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
docs[doc_id] = docs[doc_id].replace(old_str, new_str)
return f"Document {doc_id} updated successfully."
Lợi ích cốt lõi khi dùng SDK (Thay vì viết tay JSON Schema)
Dưới góc nhìn quản lý phát triển dự án, sử dụng SDK chính hãng giúp tối ưu hóa quy trình làm việc và giảm thiểu bug đáng kể nhờ:
- Không cần viết JSON Schema thủ công: Lập trình viên chỉ cần viết code Python thông thường. Định nghĩa tool schema chuẩn của Anthropic tự sinh ra tự động.
- Tự động Validation dữ liệu đầu vào (Type Checking): Nhờ Python Type Hints và Pydantic, SDK tự động kiểm tra xem tham số Claude truyền xuống có đúng kiểu
str,inthay không trước khi chạy code của tôi. Nếu sai, nó tự báo lỗi về cho Claude mà không làm crash server. - Tích hợp xử lý lỗi tự nhiên: Bất kỳ lỗi logic nào ném ra qua
raise ValueErrorhayraise Exceptiontrong Python đều được SDK bắt lại và chuyển thành kết quả lỗi (is_error=True) gửi về qua giao thức MCP, giúp Claude hiểu lý do lỗi để tự sửa sai hoặc báo lại cho user.
6. Kiểm thử và Gỡ lỗi với MCP Inspector (Testing & Debugging MCP)
Khi phát triển các MCP Server, lập trình viên cần một phương thức nhanh gọn để kiểm tra xem các tools, prompts và resources của mình có hoạt động đúng thiết kế hay không mà không cần phải cắm vào một ứng dụng client đầy đủ hay gọi trực tiếp tới Claude (tránh tốn chi phí và chậm trễ).
Để giải quyết bài toán này, SDK chính hãng cung cấp sẵn một công cụ gỡ lỗi trực quan trên trình duyệt gọi là MCP Inspector.
Cách khởi động Inspector
Đầu tiên, kích hoạt môi trường ảo Python của dự án, sau đó chạy lệnh:
mcp dev mcp_server.py
Lệnh mcp dev sẽ khởi động một development server cục bộ và cung cấp một đường dẫn URL (mặc định là http://127.0.0.1:6274). Mở URL này trên trình duyệt để truy cập giao diện MCP Inspector.
Giao diện và các bước kiểm thử
- Kết nối (Connect): Trên giao diện Web Inspector, nhấn nút Connect để khởi tạo kết nối bắt tay giữa Inspector (MCP Client giả lập) và MCP Server của tôi. Trạng thái sẽ chuyển từ "Disconnected" sang "Connected".
- Khám phá (Discovery): Vào tab Tools để xem toàn bộ danh sách các công cụ mà Server cung cấp (bản chất tương đương việc gửi
ListToolsRequest). - Chạy thử công cụ (Tool Execution): Chọn một tool cụ thể (ví dụ
read_doc_contents). Giao diện sẽ tự động hiển thị các ô nhập liệu cho từng tham số đầu vào của tool dựa trên JSON Schema mà Server trả về. Nhập dữ liệu (ví dụdoc_id: 'plan.md') và nhấn Run Tool. - Kiểm tra kết quả: Trình duyệt sẽ hiển thị trực quan kết quả dữ liệu trả về và trạng thái thành công/thất bại của cuộc gọi.
Tính chất duy trì trạng thái (State Persistence)
Một ưu điểm lớn của MCP Inspector phục vụ cho quá trình tích hợp hệ thống là Duy trì trạng thái (State Persistence):
- Server MCP duy trì trạng thái bộ nhớ hoặc kết nối DB của nó xuyên suốt phiên kết nối (session).
- Ví dụ luồng test liên tục: Tôi có thể gọi tool
edit_documentđể thay thế một đoạn text trong tài liệu, sau đó gọi ngay toolread_doc_contentsđể kiểm tra. Nội dung mới sẽ được cập nhật chính xác. Điều này cho phép giả lập và test toàn bộ luồng nghiệp vụ phức tạp của Agent trước khi bàn giao.
7. Xây dựng MCP Client (Client Side)
Server đã chạy, giờ tới phía client — lớp để code ứng dụng của tôi giao tiếp được với MCP Server và gọi được các chức năng của nó.
Lưu ý thực tế: trong hầu hết dự án thật, tôi chỉ implement một phía — hoặc MCP Client, hoặc MCP Server, hiếm khi cả hai. Khóa này dựng cả hai chỉ để tôi thấy chúng ăn khớp với nhau thế nào.
Hai thành phần của phía client (đừng nhầm)
- MCP Client — class tôi tự viết, bọc lại cho việc dùng session dễ hơn.
- Client Session — kết nối thật tới server, là thành phần có sẵn trong MCP Python SDK (tôi không tự viết).
Vì sao phải bọc Client Session trong class riêng? Vì Client Session cần quản lý tài nguyên (resource management) cẩn thận — kết nối đã mở thì bắt buộc phải dọn dẹp (cleanup) đúng cách khi dùng xong, nếu không sẽ rò rỉ kết nối. Bọc nó trong MCPClient giúp toàn bộ việc cleanup diễn ra tự động (thường qua async context manager) — tôi không phải nhớ đóng kết nối thủ công.
Client cắm vào ứng dụng ở đúng hai điểm
Nhìn lại luồng 12 bước ở mục 4: code CLI của tôi dùng client để (1) lấy danh sách tool gửi cho Claude, và (2) thực thi tool khi Claude yêu cầu. Đúng hai hàm cần implement:
list_tools() — lấy toàn bộ tool từ server
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.tools
Rất gọn: truy cập session (kết nối tới server) → gọi method list_tools() có sẵn của SDK → trả về result.tools.
call_tool() — thực thi một tool cụ thể trên server
async def call_tool(
self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
return await self.session().call_tool(tool_name, tool_input)
Truyền tool_name và tool_input (do Claude cung cấp sau khi suy luận) xuống server, rồi trả về kết quả.
Nối hai đầu: hàm client ↔ message MCP
Hai hàm này chỉ là lớp mỏng gọi thẳng method của session — nhưng bên dưới, chính chúng phát ra cặp message đã học ở mục 3:
| Hàm client (Python SDK) | Message MCP phát ra | Pha |
|---|---|---|
self.list_tools() |
ListToolsRequest → ListToolsResult |
Tool Discovery |
self.call_tool() |
CallToolRequest → CallToolResult |
Tool Execution |
Kiểm thử phía client
- Test riêng client — file client có sẵn test harness ở cuối. Chạy trực tiếp:
Nó sẽ kết nối tới MCP Server và in ra danh sách tool khả dụng (kèmuv run mcp_client.pydescription+input_schema). - Test toàn bộ luồng — chạy app chính:
Thử hỏi: "What is the contents of the report.pdf document?" Hậu trường diễn ra đúng chu trình: (1) app dùng client lấy danh sách tool → (2) gửi Claude kèm câu hỏi → (3) Claude quyết định gọiuv run main.pyread_doc_contents→ (4) app dùng client thực thi tool đó → (5) kết quả trả về cho Claude → (6) Claude soạn câu trả lời cuối.
Góc nhìn thực tế
MCP Client chính là cầu nối giữa logic ứng dụng và chức năng của MCP Server — nó che giấu toàn bộ độ phức tạp của việc kết nối (đúng vai trò "che giấu phức tạp" ở mục 4). Việc tách một wrapper class chuyên lo vòng đời kết nối (connection lifecycle) là pattern tôi luôn khuyến khích trong dự án: mã nghiệp vụ không nên rải rác lệnh mở/đóng kết nối — gom hết vào một chỗ thì vừa gọn, vừa tránh lỗi rò rỉ tài nguyên khó truy vết.
8. Resources — phơi dữ liệu read-only cho client (MCP Resources)
Ngoài Tools, MCP Server còn cấp một loại tài nguyên nữa: Resources. Cách nhớ chuẩn nhất: Resource giống một GET request handler trong HTTP server — dùng để lấy thông tin (fetch), KHÔNG phải để thực hiện hành động. (Tool thì giống POST — làm gì đó, có side-effect.)
⚠️ Resource khác Tool ở chỗ nào? Ai điều khiển?
Đây là ranh giới quan trọng nhất, rất hay ra thi:
| Tool | Resource | |
|---|---|---|
| Ai quyết định dùng | Claude tự suy luận rồi gọi (model-controlled) | Code của tôi (Our Code) chủ động lấy (application-controlled) |
| Dữ liệu tới Claude bằng cách | Claude gọi tool → nhận CallToolResult |
Tôi inject dữ liệu thẳng vào prompt trước khi gửi |
| Ví dụ | read_doc_contents (Claude gọi khi cần) |
@mention một tài liệu |
Ví dụ feature @mention: user gõ @report.pdf trong câu hỏi → code của tôi đọc nội dung file và nhét thẳng (inject) vào prompt trong thẻ <document id="report.pdf">…</document> trước khi gửi cho Claude. Claude không cần gọi tool để lấy nội dung — nó đã nằm sẵn trong prompt. Đây chính là điểm phân biệt Resource (app chủ động cấp dữ liệu) với Tool (Claude chủ động đi lấy).
Cơ chế request-response
Resource theo mẫu request-response, với một cặp message mới (song song ListTools* / CallTool* ở mục 3):
ReadResourceRequest— client gửi kèm một URI để định danh resource cần đọc.ReadResourceResult— server chạy hàm tương ứng và trả về dữ liệu.
Luồng của feature @mention (kết hợp cả hai loại resource bên dưới):
| # | Luồng đi | Message / Hành động |
|---|---|---|
| 1 | User gõ ký tự @ |
— |
| 2 | Our Code → Client → Server | ReadResourceRequest (docs://documents) |
| 3 | Server chạy list_docs(), trả danh sách tên doc |
ReadResourceResult → đổ vào autocomplete |
| 4 | User chọn @report.pdf |
— |
| 5 | Our Code → Client → Server | ReadResourceRequest (docs://documents/report.pdf) |
| 6 | Server chạy fetch_doc("report.pdf"), trả nội dung |
ReadResourceResult |
| 7 | Our Code inject nội dung vào prompt (<document>) rồi gửi Claude |
— |
Hai loại Resource
Phân biệt bằng URI có param hay không:
1. Direct Resource — URI tĩnh, không param
Dùng cho thao tác không cần tham số (vd: liệt kê toàn bộ doc cho autocomplete).
@mcp.resource(
"docs://documents",
mime_type="application/json"
)
def list_docs() -> list[str]:
return list(docs.keys())
2. Templated Resource — URI có param
URI chứa param trong ngoặc nhọn {…}. Python SDK tự parse param từ URI rồi truyền vào hàm dưới dạng keyword argument.
@mcp.resource(
"docs://documents/{doc_id}",
mime_type="text/plain"
)
def fetch_doc(doc_id: str) -> str:
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
Chi tiết triển khai
- Resource trả về kiểu dữ liệu bất kỳ: string, JSON, binary…
mime_typechỉ là gợi ý cho client biết đang nhận loại dữ liệu gì:application/json(dữ liệu có cấu trúc),text/plain(văn bản),application/pdf(file nhị phân). - SDK tự serialize giá trị trả về — tôi cứ
return list(...)/return dict(...), KHÔNG cần tựjson.dumpsthủ công. - Test:
uv run mcp dev mcp_server.py→ Inspector hiện 2 mục riêng: Resources (direct/tĩnh) và Resource Templates (có param — phải nhập giá trị param mới chạy thử được). Inspector cho thấy đúng cấu trúc response (kèm MIME type + dữ liệu đã serialize) mà client sẽ nhận.
Phía client: hàm read_resource
Ở mục 7, MCP Client đã có list_tools() + call_tool() (dành cho Tools). Để đọc resource, client cần thêm hàm thứ ba — read_resource — chính là thứ phát ra ReadResourceRequest trong sơ đồ trên, khép cặp với @mcp.resource phía server.
import json
from pydantic import AnyUrl
async def read_resource(self, uri: str) -> Any:
result = await self.session().read_resource(AnyUrl(uri))
resource = result.contents[0]
if isinstance(resource, types.TextResourceContents):
if resource.mimeType == "application/json":
return json.loads(resource.text)
return resource.text
Giải thích cấu trúc response:
AnyUrl(uri)— server định danh resource bằng URI (vddocs://documents).AnyUrllà kiểu bọc chuỗi URI cho đúng chuẩn (Pydantic yêu cầu).result.contents[0]— server trả về một danh sáchcontents; ta lấy phần tử đầu vì mỗi lần thường chỉ đọc một resource. Mỗi item gồm: nội dung thật (text/data),mimeType(để biết cách parse), và metadata khác.- Rẽ nhánh theo
mimeType(đây là lý do phải khaimime_typephía server ở trên):application/json→json.loadsđể bung chuỗi thành object Python (vd list tên doc từlist_docs).- còn lại → trả text thô (vd nội dung file từ
fetch_doc,text/plain).
Ví dụ khớp hai loại resource: read_resource("docs://documents") (Direct, JSON) trả về list Python để đổ vào autocomplete; read_resource("docs://documents/report.pdf") (Templated, text) trả nguyên nội dung file để inject vào prompt.
Test qua CLI: gõ @ → hiện autocomplete → chọn bằng phím mũi tên + space → nội dung tài liệu chèn thẳng vào prompt → gửi model không cần tool call. Đây là trải nghiệm mượt hơn nhiều so với việc bắt Claude gọi tool riêng để lấy nội dung.
Góc nhìn thực tế
Trong dự án thật, tôi phân tuyến rõ: dữ liệu read-only ổn định (danh mục, cấu hình, nội dung tài liệu) thì để Resource cho app chủ động inject — vừa tiết kiệm một vòng tool-call, vừa đảm bảo Claude luôn có sẵn ngữ cảnh chuẩn. Còn thao tác có side-effect hoặc Claude cần tự quyết khi nào chạy thì mới để Tool. Chọn sai (nhét dữ liệu tra cứu vào Tool) làm tăng số vòng lặp gọi model — chậm và tốn token.
9. Prompts — câu lệnh mẫu pha sẵn (MCP Prompts)
Capability thứ ba của MCP Server (sau Tools và Resources) là Prompts: các hướng dẫn (instruction) chất lượng cao, pha sẵn do chính tác giả server soạn và test kỹ, để client dùng lại thay vì tự viết prompt từ đầu. Coi như những template được chăm chút cho kết quả tốt hơn thứ user tự nghĩ ra.
Vì sao cần Prompt?
User hoàn toàn có thể tự bảo Claude làm việc — vd gõ Convert report.pdf to markdown → chạy được, kết quả tạm ổn. Nhưng nếu tác giả server cung cấp một prompt đã test & eval kỹ (xử lý edge case, theo best practice) thì user ra kết quả ổn định và chất lượng hơn hẳn — mà không cần bản thân giỏi prompt engineering. Tác giả server bỏ công craft/test/eval một lần, mọi user hưởng lợi.
⚠️ Bộ ba control model — "ai kích hoạt?" (điểm thi quan trọng)
Đây là mảnh ghép hoàn thiện bức tranh MCP Server. Mỗi capability có một người điều khiển khác nhau:
| Capability | Ai kích hoạt | Ví dụ |
|---|---|---|
| Tool | Claude (model-controlled) | Claude tự quyết gọi read_doc_contents |
| Resource | Code của tôi (application-controlled) | @mention inject nội dung vào prompt |
| Prompt | User (user-controlled) | User gõ /format chọn câu lệnh |
→ Prompt là cái duy nhất user chủ động chọn, thường qua slash command (/).
Ví dụ: command /format
Workflow của một format command (convert tài liệu sang markdown):
- User gõ
/→ hiện danh sách command khả dụng. - Chọn
format+ nhậpdoc_id. - Claude dùng prompt pha sẵn để đọc và reformat tài liệu.
- Trả về markdown sạch (header, list, table đúng chuẩn).
Định nghĩa Prompt bằng @mcp.prompt
Cùng pattern decorator với @mcp.tool / @mcp.resource:
@mcp.prompt(
name="format",
description="Rewrites the contents of the document in Markdown format."
)
def format_document(
doc_id: str = Field(description="Id of the document to format")
) -> list[base.Message]:
prompt = f"""
Your goal is to reformat a document to be written with markdown syntax.
The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>
Add in headers, bullet points, tables, etc as necessary.
Use the 'edit_document' tool to edit the document. After the document
has been reformatted...
"""
return [
base.UserMessage(prompt)
]
Điểm cần nhớ:
- Hàm trả về một list các message (
base.UserMessage(...)) — được gửi thẳng cho Claude. Có thể nhét nhiềuUserMessage+AssistantMessageđể dựng hội thoại phức tạp hơn. {doc_id}được nội suy (interpolate) vào template — SDK điền giá trị user nhập vào chỗ trống.- Prompt có thể ra lệnh cho Claude dùng Tool (ở đây bảo Claude gọi
edit_document) → Prompt điều phối được cả Tool.
Phía client: list_prompts + get_prompt
Đây là bước cuối hoàn thiện MCP Client. Sau list_tools/call_tool (Tools) và read_resource (Resources), client cần thêm 2 hàm cho Prompts.
list_prompts — liệt kê prompt khả dụng (để đổ lên menu khi user gõ /):
async def list_prompts(self) -> list[types.Prompt]:
result = await self.session().list_prompts()
return result.prompts
get_prompt — lấy một prompt cụ thể, có xử lý nội suy biến (variable interpolation):
async def get_prompt(self, prompt_name, args: dict[str, str]):
result = await self.session().get_prompt(prompt_name, args)
return result.messages
argslà một dict → xuống server trở thành keyword arguments của hàm prompt. Vd promptformat_documentcầndoc_id: client gửiargs = {"doc_id": "plan.md"}→ giá trị được nội suy vào chỗ{doc_id}trong template.get_prompttrả vềresult.messages— list message đã điền sẵn biến, sẵn sàng gửi cho Claude.
Test CLI: gõ / → prompt hiện thành command → chọn format → chọn document → gửi trọn prompt cho Claude → Claude nhận instruction + doc_id rồi tự dùng tool (edit_document) để fetch và xử lý nội dung.
Workflow tổng của Prompt (5 nhịp): viết & eval prompt → định nghĩa bằng @mcp.prompt phía server → client request bất kỳ lúc nào → args client cung cấp thành kwargs của hàm → hàm trả về messages đã format sẵn cho model.
Tới đây MCP Client đã đủ 5 hàm, chia theo capability: Tools (list_tools/call_tool), Resources (read_resource), Prompts (list_prompts/get_prompt).
/format: list_prompts() lấy danh sách lệnh đổ vào menu (pha trên); sau khi user chọn, get_prompt() gửi doc_id để server ráp bộ tin nhắn đã nội suy biến, rồi App gửi trọn bộ sang Claude (pha dưới). Mũi tên nét đứt là chiều trả về.4 lợi ích cốt lõi
- Consistency — user ra kết quả đáng tin, lần nào cũng vậy.
- Expertise — đóng gói kiến thức chuyên môn (domain knowledge) vào prompt.
- Reusability — nhiều client application dùng chung một prompt.
- Maintenance — sửa prompt ở một chỗ, mọi client được nâng cấp theo.
Test
MCP Inspector cho xem chính xác các message sẽ gửi tới Claude, gồm cả cách biến ({doc_id}) được nội suy vào template → kiểm chứng prompt đúng trước khi user bắt đầu phụ thuộc vào nó.
Prompt phát huy tốt nhất khi chuyên biệt cho domain của server: server quản lý tài liệu thì có prompt format/summarize/analyze; server phân tích dữ liệu thì có prompt sinh report/visualization. Mục tiêu: prompt craft kỹ tới mức user thích dùng nó hơn tự viết.
10. Ai viết MCP Server? Và ba câu hỏi dễ nhầm
Ai là người phát triển các MCP Server?
Bất kỳ ai cũng có thể xây dựng MCP Server. Xu hướng tối ưu nhất là chính nhà cung cấp dịch vụ (service providers) sẽ phát hành và duy trì MCP Server cho dịch vụ của họ. Ví dụ: AWS phát hành MCP Server cho các dịch vụ Cloud, GitHub cho repositories, Slack cho messaging. Lập trình viên chỉ cần tải về và kết nối — không cần tự viết lại từ đầu.
MCP khác gì so với việc gọi API trực tiếp?
| Tiêu chí | Gọi API trực tiếp | Dùng MCP |
|---|---|---|
| Tool schema | Tự viết từng cái | Server cung cấp sẵn |
| Code thực thi | Tự viết API wrapper | Server đã implement |
| Xác thực (Auth) | Tự quản lý | Server xử lý |
| Bảo trì khi API đổi | Tôi phải sửa | Nhà cung cấp sửa |
⚠️ MCP có phải là Tool Use không? (Điểm thi quan trọng)
Đây là misconception phổ biến nhất mà bài học nhấn mạnh — và gần như chắc chắn sẽ xuất hiện trong đề thi CCA-F:
- MCP và Tool Use là hai khái niệm bổ trợ nhau nhưng hoàn toàn khác biệt.
- MCP (Model Context Protocol): Là giao thức kết nối — trả lời câu hỏi "Ai xây dựng và thực thi các tool đó?". MCP giúp di chuyển phần triển khai code ra ngoài ứng dụng chính.
- Tool Use (Function Calling): Là năng lực suy luận của mô hình — trả lời câu hỏi "Claude chọn tool nào, truyền tham số gì?". Đây là cơ chế Claude đọc
descriptioncủa tool rồi quyết định có gọi hay không.
Câu tóm gọn để nhớ: MCP là hạ tầng cung cấp công cụ, Tool Use là cách Claude sử dụng các công cụ đó. MCP servers cung cấp tool schemas và functions đã được định nghĩa sẵn cho tôi; Tool Use là cách Claude thực sự gọi những tools đó.
11. Bức tranh lớn: Tools vs Skills vs MCP — ai maintain cái gì?
Khép lại, ta quay về câu hỏi then chốt xuyên suốt khóa MCP: ai chịu trách nhiệm maintain integration?
| Cơ chế | Dùng cho | Ai maintain |
|---|---|---|
| Tools (Custom) | Hệ thống nội bộ (DB, tracker, API riêng) | Tôi |
| Skills | Quy trình (template, checklist, SOP) | Tôi |
| MCP | Service bên thứ ba (GitHub, Slack, AWS...) | Nhà cung cấp |
Câu ngắn gọn mà tôi ghi nhớ từ khóa Claude Platform 101:
Tools = đồ của tôi · Skills = quy trình của tôi · MCP = đồ của người khác.
Đây là lý do MCP tồn tại song song với custom tools và skills — mỗi cái trị một bài toán khác nhau.
Liên quan trong lộ trình
- Đào sâu hands-on — dựng MCP server bằng FastMCP, viết client, resources & prompts từng dòng code: MCP với Claude API (Phần 6).
- MCP trong Claude Code — cách cắm sẵn MCP server (GitHub, Cloudflare…) vào công cụ hằng ngày: Claude Code 101.
Từ khoá cần thuộc
🔴 Core:
- Model Context Protocol (MCP) — giao thức chuẩn hóa kết nối AI với các nguồn dữ liệu/công cụ ngoài.
- MCP Client — phía ứng dụng kết nối với Claude, chịu trách nhiệm nhận yêu cầu và điều hướng đến MCP Server.
- MCP Server — ứng dụng độc lập định nghĩa sẵn và thực thi các tools, prompts, resources; kết nối trực tiếp với Outside Service.
- MCP ≠ Tool Use — MCP là hạ tầng cung cấp (ai build tool), Tool Use là năng lực suy luận (Claude chọn tool nào).
- Transport Agnostic — tính chất độc lập phương thức truyền tải (stdio, WebSockets, HTTP/SSE).
- ListToolsRequest / ListToolsResult — cặp message phục vụ quá trình khám phá công cụ (Tool Discovery).
- CallToolRequest / CallToolResult — cặp message phục vụ quá trình thực thi công cụ (Tool Execution).
- FastMCP — high-level framework của Python SDK dùng để khởi tạo MCP Server nhanh chóng.
@mcp.tool— decorator dùng để đăng ký một hàm Python thành một MCP Tool khả dụng cho Claude.- MCP Inspector (
mcp dev) — công cụ gỡ lỗi và kiểm thử MCP Server trực quan trên giao diện trình duyệt. - Client Session — kết nối thật tới MCP Server, là thành phần có sẵn trong MCP Python SDK (không tự viết).
list_tools()/call_tool()— hai hàm cốt lõi của MCP Client: lấy danh sách tool gửi cho Claude (Tool Discovery) và thực thi tool khi Claude yêu cầu (Tool Execution).- Resources — cơ chế MCP Server phơi dữ liệu read-only (giống GET handler); app chủ động lấy và inject vào prompt (application-controlled), khác Tool (Claude tự gọi, model-controlled).
- ReadResourceRequest / ReadResourceResult — cặp message đọc resource; request kèm một URI định danh resource.
- Direct vs Templated Resource — URI tĩnh không param (
docs://documents) vs URI có param (docs://documents/{doc_id}, SDK tự parse param → keyword argument). @mcp.resource/mime_type— decorator đăng ký resource;mime_typelà gợi ý kiểu dữ liệu (application/json,text/plain,application/pdf), SDK tự serialize giá trị trả về.read_resource()— hàm client (thứ ba, saulist_tools/call_tool) để đọc resource: gọisession().read_resource(AnyUrl(uri)), lấycontents[0], rồi rẽ nhánh theomimeType(application/json→json.loads, còn lại → text thô).- Prompts — capability thứ ba của MCP Server: các instruction/template pha sẵn đã được tác giả server test & eval, để user dùng lại (thường qua slash command) thay vì tự viết prompt.
- Control model trio — Tool = model-controlled (Claude gọi), Resource = application-controlled (code inject), Prompt = user-controlled (user chọn qua
/command). Nhớ trọn bộ ba này là điểm thi hay gặp. @mcp.prompt/base.UserMessage— decorator đăng ký prompt; hàm trả về list message (base.UserMessage/AssistantMessage) gửi thẳng cho Claude, với biến trong template được nội suy (interpolate).list_prompts()/get_prompt()— hai hàm client cho Prompts:list_promptstrảresult.prompts(đổ lên slash menu);get_prompt(name, args)gửiargs(dict → kwargs) để server nội suy biến, trả vềresult.messagessẵn sàng cho Claude. → MCP Client đủ 5 hàm:list_tools/call_tool·read_resource·list_prompts/get_prompt.
🟡 Important:
- Separation of Concerns (Phân tách trách nhiệm) — nguyên lý cốt lõi: tách logic ứng dụng AI và logic tích hợp hệ thống.
- Glue Code (Mã tích hợp) — phần code trung gian viết thủ công để nối API ngoài vào ứng dụng; MCP ra đời để triệt tiêu loại code này.
- Tools = đồ của tôi · Skills = quy trình của tôi · MCP = đồ của người khác — câu phân biệt 3 cơ chế.
- stdio (Standard Input/Output) — phương thức truyền tải phổ biến nhất của MCP trên môi trường local.
- Type Hints / Pydantic Field — các cơ chế trong Python dùng để tự động validate kiểu dữ liệu đầu vào và sinh Tool Schema mô tả chi tiết tham số cho Claude.
- State Persistence (Duy trì trạng thái) — tính chất lưu giữ bộ nhớ/trạng thái của MCP Server xuyên suốt phiên kết nối, cho phép kiểm thử chuỗi hành động liên tục.
- MCP Client (wrapper class) & Resource Management — class tự viết bọc quanh Client Session để tự động cleanup kết nối khi dùng xong, tránh rò rỉ tài nguyên; đóng vai cầu nối giữa logic ứng dụng và MCP Server.
🟢 Good-to-know:
- Outside Service — dịch vụ đầu cuối (GitHub, Slack, Databases...) được tích hợp qua MCP Server.
- Service Providers — nhà cung cấp dịch vụ tự phát hành MCP Server cho dịch vụ của họ.
- WebSockets / SSE (Server-Sent Events) — các giao thức truyền tải mạng khả dụng cho kết nối MCP từ xa.
- In-memory dictionary — kiểu dữ liệu lưu trữ tạm bộ nhớ dùng để giả lập tài liệu trong các ví dụ lập trình server.
http://127.0.0.1:6274— địa chỉ URL mặc định của local MCP Inspector.
Nguồn: Introduction to Model Context Protocol (Anthropic Academy) — Copyright Anthropic. Phần đề thi thử cho khoá này nằm ở tab "Đề thi thử".