Trước MCP, mọi công cụ AI đều có hệ plugin riêng. Claude Code có một hệ. Cursor có hệ khác. Công cụ cơ sở dữ liệu của bạn chạy trên Claude Code nhưng không chạy trên Cursor, nên bạn viết lại hai lần. Đổi công cụ nghĩa là viết lại toàn bộ tích hợp. Đó là trạng thái của công cụ AI năm 2024 —mỗi công cụ một hòn đảo, mỗi tích hợp làm riêng.
MCP —Model Context Protocol— đã thay đổi điều này vào năm 2025, và năm 2026 nó có ở khắp nơi. Claude Code dùng nó. Cursor hỗ trợ nó. LangChain, LiteLLM và mọi nền tảng AI lớn đều thêm hỗ trợ MCP. Đặc tả MCP chính thức định nghĩa giao thức —là chuẩn mở (dựa trên JSON-RPC) chuẩn hóa cách model AI khám phá và gọi công cụ. Viết một server MCP. Dùng với bất kỳ client tương thích MCP nào.
MCP là gì —và tại sao nó quan trọng
Giao thức, không phải marketing. MCP là giao thức dựa trên JSON-RPC 2.0. Server MCP phơi bày tool, resource và prompt. Client MCP (nhúng trong ứng dụng AI) kết nối server, khám phá những gì có sẵn và gọi tool thay mặt LLM. Lớp transport cắm được —stdio cho công cụ cục bộ, HTTP với SSE cho dịch vụ từ xa.
MCP so với function calling —bổ trợ, không cạnh tranh. Function calling: LLM gọi một công cụ trong một request API đơn. Định nghĩa công cụ gửi trong lời gọi API. MCP: LLM khám phá công cụ có sẵn qua một giao thức chuẩn hóa, rồi gọi chúng —có thể qua nhiều phiên và nhiều công cụ. MCP chuẩn hóa việc khám phá và mô tả công cụ. Function calling thực thi chúng. Bạn dùng MCP để quản lý danh mục công cụ và function calling để gọi chúng —chúng hoạt động cùng nhau. Để so sánh chi tiết các triển khai function calling trên OpenAI, Anthropic, Google và DeepSeek, xem hướng dẫn function calling và sử dụng công cụ của chúng tôi.
Vì sao MCP quan trọng với nhà phát triển API. Trước MCP: bạn viết một công cụ truy vấn cơ sở dữ liệu. Để dùng với Claude Code, bạn viết plugin riêng cho Claude Code. Để dùng với Cursor, bạn viết tích hợp riêng cho Cursor. Để dùng với app tùy chỉnh, bạn viết code tùy chỉnh. Sau MCP: viết một server MCP. Mọi client tương thích MCP đều dùng được. Đây là phép loại suy “USB-C cho công cụ AI” —không hoàn hảo, nhưng là chuẩn đã thắng.
Kiến trúc MCP: server, client và transport
Server MCP. Phơi bày tool, resource và prompt. Viết bằng Python, Node.js hay Go —tùy bạn thích. Chạy cục bộ (transport stdio) hoặc từ xa (transport HTTP+SSE). Server là một chương trình. Nó khởi động. Lắng nghe kết nối. Phản hồi request gọi công cụ. Dừng khi client ngắt kết nối.
Client MCP. Kết nối server MCP, khám phá công cụ có sẵn, gửi request gọi công cụ từ LLM và trả kết quả. Nhúng trong ứng dụng AI —Claude Code, Cursor, app tùy chỉnh của bạn. Client là cầu nối giữa quyết định của LLM (“tôi cần truy vấn cơ sở dữ liệu”) và thực thi công cụ (SELECT * FROM users).
Transport. stdio: server chạy như tiến trình con của client. Đầu vào/đầu ra chuẩn cho giao tiếp. Tốt nhất cho công cụ phát triển cục bộ —không cấu hình mạng, không độ trễ. Transport stdio thêm chưa tới 1ms overhead mỗi round-trip tin nhắn trên máy hiện đại. HTTP+SSE: server chạy như dịch vụ từ xa. HTTP POST cho request, Server-Sent Events cho phản hồi streaming. Tốt nhất cho dịch vụ sản xuất —nhiều client có thể kết nối, server cập nhật độc lập mà không cần khởi động lại IDE của từng nhà phát triển.
Sơ đồ kiến trúc:
LLM → MCP Client → Transport (stdio/HTTP) → MCP Server → External Resources (DB, API, Filesystem)
Xây dựng server MCP đầu tiên của bạn
Server MCP thời tiết + giá cổ phiếu. 15 phút. Hai tool.
# mcp_server.py
import json
import sys
from typing import Any
class MCPServer:
def __init__(self):
self.tools = {
"get_weather": {
"description": "Get current weather for a city. Returns temperature in Celsius.",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'Tokyo'"}
},
"required": ["city"]
}
},
"get_stock_price": {
"description": "Get current stock price for a ticker symbol.",
"inputSchema": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
},
"required": ["symbol"]
}
}
}
def handle_request(self, request: dict) -> dict:
method = request.get("method")
if method == "tools/list":
return {"tools": list(self.tools.values())}
elif method == "tools/call":
tool_name = request["params"]["name"]
tool_args = request["params"]["arguments"]
result = self._execute_tool(tool_name, tool_args)
return {"content": [{"type": "text", "text": str(result)}]}
else:
return {"error": f"Unknown method: {method}"}
def _execute_tool(self, name: str, args: dict) -> Any:
if name == "get_weather":
return f"Weather in {args['city']}: 22°C, partly cloudy"
elif name == "get_stock_price":
return f"{args['symbol']}: $185.50"
else:
return f"Unknown tool: {name}"
# stdio transport —reads JSON-RPC from stdin, writes to stdout
if __name__ == "__main__":
server = MCPServer()
for line in sys.stdin:
request = json.loads(line)
response = server.handle_request(request)
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
Kết nối Claude Desktop. Khởi động nhanh MCP của Anthropic hướng dẫn thiết lập đầy đủ. Thêm phần này vào claude_desktop_config.json của bạn:
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["mcp_server.py"]
}
}
}
Khởi động lại Claude Desktop. Hai tool của bạn —get_weather và get_stock_price— giờ có sẵn. Claude tự động khám phá chúng. Thử: “Thời tiết ở Tokyo thế nào và giá cổ phiếu Apple là bao nhiêu?” Claude sẽ gọi cả hai tool, song song, và tổng hợp câu trả lời.
Để có hướng dẫn đầy đủ về Claude API —xác thực, streaming, sử dụng công cụ và xử lý lỗi— xem hướng dẫn nhà phát triển Claude API của chúng tôi.
Xây dựng server MCP thực tế: công cụ truy vấn cơ sở dữ liệu
Server thời tiết chứng minh MCP hoạt động trong 50 dòng Python. Công cụ sản xuất cần nhiều hơn: query có tham số, connection pooling và thông báo lỗi mà LLM có thể tự sửa theo. Đây là server MCP hoàn chỉnh truy vấn cơ sở dữ liệu SQLite, xây bằng SDK Python chính thức của MCP (pip install mcp). Ba tool —query_users, get_user_by_id, create_user. Sao chép, điều chỉnh, triển khai.
# db_mcp_server.py —production-grade MCP database server
import sqlite3
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
server = Server("database-tools")
# Database connection —created once, reused across all tool calls
_connection: sqlite3.Connection | None = None
def get_db():
global _connection
if _connection is None:
_connection = sqlite3.connect("app.db")
_connection.row_factory = sqlite3.Row
# WAL mode: concurrent reads without locking. Without this,
# two simultaneous query_users calls serialize —the second
# blocks until the first releases the read lock.
_connection.execute("PRAGMA journal_mode=WAL")
return _connection
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="query_users",
description="Run a SELECT query against the users table. "
"Use for searching, filtering, or counting users. "
"Returns results as JSON array. "
"Column names: id, name, email, role, status, created_at.",
inputSchema={
"type": "object",
"properties": {
"where_clause": {
"type": "string",
"description": "SQL WHERE clause without 'WHERE' keyword. "
"Example: \"age > 25 AND status = 'active'\". "
"Leave empty for all users."
},
"limit": {
"type": "integer",
"description": "Max rows to return. Default 50.",
"default": 50
}
}
}
),
Tool(
name="get_user_by_id",
description="Fetch a single user by primary key. Returns user object or null.",
inputSchema={
"type": "object",
"properties": {
"user_id": {
"type": "integer",
"description": "The user's ID in the database."
}
},
"required": ["user_id"]
}
),
Tool(
name="create_user",
description="Insert a new user into the database. "
"Returns the created user with their assigned ID.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "User's full name."},
"email": {"type": "string", "description": "Valid email address."},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"],
"description": "User role. Default: viewer."
}
},
"required": ["name", "email"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
db = get_db()
if name == "query_users":
where = arguments.get("where_clause", "")
limit = arguments.get("limit", 50)
query = "SELECT * FROM users"
params = []
if where.strip():
query += " WHERE " + where
query += " LIMIT ?"
params.append(limit)
try:
rows = db.execute(query, params).fetchall()
except sqlite3.OperationalError as e:
# Return column names so the LLM can self-correct bad WHERE clauses
return [TextContent(
type="text",
text=f"Query failed: {e}. Valid columns: id, name, email, role, status, created_at."
)]
return [TextContent(
type="text",
text=str([dict(r) for r in rows])
)]
elif name == "get_user_by_id":
user_id = arguments["user_id"]
row = db.execute(
"SELECT * FROM users WHERE id = ?", [user_id]
).fetchone()
if row is None:
return [TextContent(type="text", text=f"No user found with id={user_id}")]
return [TextContent(type="text", text=str(dict(row)))]
elif name == "create_user":
name_val = arguments["name"]
email = arguments["email"]
role = arguments.get("role", "viewer")
try:
cursor = db.execute(
"INSERT INTO users (name, email, role) VALUES (?, ?, ?)",
[name_val, email, role]
)
db.commit()
new_id = cursor.lastrowid
except sqlite3.IntegrityError as e:
return [TextContent(
type="text",
text=f"Insert failed: {e}. Email may already exist."
)]
new_user = db.execute(
"SELECT * FROM users WHERE id = ?", [new_id]
).fetchone()
return [TextContent(type="text", text=str(dict(new_user)))]
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationCapabilities(
sampling={},
experimental={},
roots={}
),
notification_options=NotificationOptions()
)
if __name__ == "__main__":
asyncio.run(main())
Bốn quyết định trong code này ngăn các cuộc gọi on-call lúc 3 giờ sáng.
Mô tả tool kèm ví dụ. LLM đọc mô tả tool của bạn để quyết định gọi tool nào. "description": "Query the database" trần trụi không cho model cách nào phân biệt giữa các tool —cho query SELECT COUNT(*) nó sẽ thường xuyên thử get_user_by_id thay vào đó. Không có hướng dẫn tường minh, model thường chọn sai công cụ khi nhiều công cụ có mô tả tương tự. Mô tả trên bao gồm công cụ làm gì, trả về gì và tên cột chính xác. Sự cụ thể đó loại bỏ sự mơ hồ.
Query có tham số —không f-string. db.execute("SELECT * FROM users WHERE id = ?", [user_id]) không bao giờ thành db.execute(f"SELECT * FROM users WHERE id = {user_id}"). Một f-string trong server MCP cơ sở dữ liệu, và một LLM tạo ra user_id = "1 OR 1=1" sẽ rò rỉ mọi dòng trong bảng users của bạn. LLM sẽ tạo đầu vào đó. Đã từng xảy ra trong sản xuất. Query có tham số là yêu cầu cứng, không phải lựa chọn tùy chọn.
Thông báo lỗi kèm gợi ý phục hồi. Khi query_users thất bại vì LLM đoán sai tên cột không tồn tại, lỗi bao gồm danh sách cột hợp lệ: "Valid columns: id, name, email, role, status, created_at." LLM đọc phần này, sửa query và thử lại lời gọi công cụ —không cần can thiệp con người. "OperationalError: no such column" trần trụi khiến Claude nhún vai và người dùng bị chặn.
Tái sử dụng kết nối với chế độ WAL journal. PRAGMA journal_mode=WAL bật đọc đồng thời không khóa. Không có nó, hai lời gọi query_users đồng thời bị tuần tự hóa —cái thứ hai chờ 50ms cho cái thứ nhất nhả read lock. Trong chuỗi công cụ nơi Claude gọi query_users, đọc kết quả, rồi gọi get_user_by_id cho từng dòng, sự chờ khóa đó cộng dồn qua 5 lời gọi tuần tự thành 250ms độ trễ người dùng thấy được. Chế độ WAL loại bỏ nút thắt cổ chai.
MCP trên các nhà cung cấp LLM
Hỗ trợ MCP khác biệt mạnh theo nhà cung cấp. Anthropic tạo ra giao thức và có tích hợp sâu nhất. Mọi người khác đang chạy đua bắt kịp ở tốc độ khác nhau. Đây là vị thế mỗi nhà cung cấp lớn giữa năm 2026.
| Nhà cung cấp | Mức hỗ trợ MCP | Hỗ trợ giao thức vận chuyển | Mức sẵn sàng sản xuất | Chất lượng SDK |
|---|---|---|---|---|
| Anthropic | Bản địa —đơn vị khởi xướng | stdio + HTTP/SSE | Sẵn sàng sản xuất cho cả hai transport | SDK Python, TypeScript chính thức với phủ đầy đủ spec |
| OpenAI | Qua Agents SDK + adapter cộng đồng | stdio (Agents SDK), HTTP qua adapter | Agents SDK: sản xuất. Adapter: chất lượng beta | Do cộng đồng duy trì; không có SDK MCP chính hãng |
| Chỉ có adapter cộng đồng | stdio, HTTP/SSE hạn chế | Chỉ dùng phát triển | Gói cộng đồng giai đoạn đầu; tài liệu thưa thớt | |
| DeepSeek | Đường hướng tương thích OpenAI | Qua bộ công cụ adapter OpenAI | Hoạt động, nhưng không được hỗ trợ | Không có SDK MCP riêng; thừa hưởng các điểm kỳ quặc của adapter OpenAI |
| Nền tảng tổng hợp (lớp hạ tầng —định tuyến đến mọi nhà cung cấp bên dưới) | MCP gateway —kết nối một lần, định tuyến đến mọi nơi | HTTP/SSE với xác thực thống nhất | Sản xuất với xác thực quản lý, rate limit, logging | SDK nền tảng cho Python, JavaScript, LangChain |
Mẫu MCP gateway. Nền tảng tổng hợp đóng vai client MCP, kết nối server MCP của bạn và phơi bày công cụ cho bất kỳ LLM nào qua một endpoint API duy nhất. Bạn viết một server MCP. Bạn dùng nó với GPT-5.5, Claude Opus 4, Gemini 3 và DeepSeek V3 —tất cả qua POST https://api.tokspan.com/v1/chat/completions với cùng một API key. Nền tảng xử lý dịch giao thức để tool MCP của bạn hoạt động bất kể model nào đang hoạt động. Một định nghĩa server nuôi hơn 6 model. Xem khởi động nhanh TokSpan cho thiết lập 5 phút.
MCP so với Function Calling: khi nào dùng loại nào
Quyết định rút gọn về một câu hỏi: công cụ của bạn có cần hoạt động qua nhiều client LLM không?
Chỉ dùng function calling khi bạn gọi trực tiếp một API LLM —OpenAI, Anthropic hay Google— và công cụ của bạn riêng cho một ứng dụng duy nhất. Bạn truyền định nghĩa công cụ trực tiếp trong mảng tools của request chat completions. Hạ tầng bằng không: không tiến trình server riêng, không lớp transport, không đàm phán giao thức. Bot hỗ trợ khách hàng tra cứu trạng thái đơn hàng qua một endpoint REST nội bộ tại GET /orders/:id cần function calling, không phải MCP. Đừng over-engineer điều này —MCP thêm tiến trình server, transport và bắt tay giao thức cho lợi ích bằng không khi bạn có một client.
Dùng MCP + function calling cùng nhau khi bạn có nhiều client AI —Claude Code trên laptop bạn, Cursor trên máy đồng nghiệp, và dashboard tùy chỉnh trên server staging— đều cần các công cụ giống nhau. Công cụ truy vấn cơ sở dữ liệu, công cụ triển khai và trình xem log của bạn được định nghĩa một lần trong server MCP. Mỗi client khám phá chúng qua tools/list và gọi chúng qua function calling lúc chạy.
Danh mục công cụ của bạn được tập trung hóa. Cập nhật mô tả hay schema công cụ lan tỏa tức thì tới mọi client mà không cần triển khai lại. Một đội nền tảng nội bộ trong tổ chức kỹ thuật 200 người dùng mẫu này đã cắt thời gian bảo trì tích hợp công cụ từ 8 giờ mỗi công cụ mới xuống còn 30 phút.
Dùng MCP như gateway khi bạn định tuyến lời gọi công cụ tới nhiều nhà cung cấp LLM qua một nền tảng tổng hợp. Một định nghĩa server MCP nuôi công cụ cho GPT-5.5, Claude Opus 4 và Gemini 3 đồng thời. Nền tảng dịch giữa giao thức khám phá công cụ của MCP và API function calling của từng nhà cung cấp. Bạn không bao giờ viết định nghĩa công cụ riêng theo nhà cung cấp. Bạn không bao giờ gỡ lỗi vì sao công cụ chạy với Claude nhưng thất bại âm thầm với GPT-5.5. Một định nghĩa server nuôi hơn 6 model qua một endpoint duy nhất —không cần cấu hình công cụ theo nhà cung cấp.
Lựa chọn sai —từ kinh nghiệm. Hãy xem kịch bản điển hình: một nhà phát triển trong startup nhỏ dành hai tuần thiết lập server MCP với transport HTTP/SSE, middleware auth và connection pooling cho ba công cụ nội bộ chỉ dùng riêng với Claude Code. Function calling chỉ mất hai giờ. Hai tuần thời gian kỹ thuật đổi lấy hạ tầng phục vụ đúng một client. Bắt đầu với function calling. Chuyển sang MCP khi bạn chạm client thứ hai —không phải trước đó.
MCP trong sản xuất: xác thực, rate limiting và xử lý lỗi
stdio dành cho phát triển. HTTP+SSE dành cho sản xuất. Transport stdio chạy server MCP của bạn như tiến trình con —không xác thực, không bảo mật mạng, một client mỗi tiến trình server. Ổn cho claude_desktop_config.json trên laptop bạn. Sai cho dịch vụ phục vụ 50 nhà phát triển trong tổ chức. Transport HTTP+SSE cho phép triển khai server như dịch vụ độc lập sau load balancer, với auth đúng, giám sát và chu kỳ triển khai độc lập.
Middleware xác thực. Đặc tả transport HTTP của MCP đến giữa năm 2026 vẫn chưa định nghĩa cơ chế auth chuẩn. Bạn tự thêm. Mẫu phổ biến: xác thực API key trong header Authorization: Bearer <key> trước khi xử lý bất kỳ tin nhắn MCP nào. Cho triển khai dựa trên TokSpan, nền tảng xử lý auth ở lớp gateway API —server MCP của bạn nhận request đã xác thực trước tại https://api.tokspan.com/v1/. Nếu tự lưu trữ, gắn middleware auth trước handler MCP.
Một token hết hạn ở độ sâu 3 của chuỗi công cụ —nơi tool_a gọi tool_b gọi tool_c— tạo lỗi JSON-RPC khó hiểu mất 45 phút truy ngược tới lớp auth. Làm đúng auth trước bất kỳ thứ gì khác. Cho checklist bảo mật đầy đủ, xem checklist bảo mật sản xuất của chúng tôi.
# Auth middleware for MCP HTTP server (FastAPI pattern)
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
VALID_TOKENS = {"mcp-secret-abc123", "mcp-prod-xyz789"} # Replace with DB lookup
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if request.url.path.startswith("/mcp/"):
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
if token not in VALID_TOKENS:
raise HTTPException(status_code=401, detail="Invalid MCP API key")
return await call_next(request)
Rate limiting ngăn sự cố khuếch đại bởi LLM. Một công cụ mô tả lỏng lẻo —tool run_sql không enforce LIMIT— và LLM tạo query quét 10 triệu dòng lúc 2 giờ sáng vì người dùng hỏi “hiện hết mọi thứ”. Bộ giới hạn token bucket phía server giới hạn query_users ở 60 lời gọi mỗi phút và create_user ở 10 lời gọi mỗi phút. Dùng Redis cho rate limiting phân tán qua nhiều instance server —INCR + EXPIRE mỗi tên tool mỗi API key.
Không có rate limiting, một prompt LLM hào hứng duy nhất trở thành nguyên nhân gốc của sự cố mất điện cơ sở dữ liệu sản xuất. Bạn sẽ bị gọi lúc 3 giờ sáng. Bạn sẽ truy về định nghĩa công cụ viết trong 15 phút và quên từ sáu sprint trước.
Xử lý lỗi giúp LLM phục hồi. Đừng trả "Error: something went wrong." LLM đọc thông báo lỗi của bạn và thử lại. Trả ba thứ: loại lỗi, tham số cụ thể thất bại và phương án hợp lệ. Lỗi này là trọng lượng chết: "Invalid input." Lỗi này cho phép Claude tự sửa trong một lần thử: "create_user failed: role must be one of [admin, editor, viewer]. Received: 'superadmin'." Khác biệt giữa hai thông báo lỗi là khác biệt giữa chuỗi công cụ tự phục hồi và người dùng kẹt nhìn spinner cho đến khi bỏ cuộc và mở ticket.
Cạm bẫy MCP phổ biến: điều gì vỡ trong thực tế
Đây là bốn chế độ lỗi nhà phát triển MCP chạm trong tháng đầu. Mỗi cái tránh được nếu bạn biết trước khi triển khai.
Mô tả tool mơ hồ. Đây là nguyên nhân số một của bug chọn sai công cụ. "description": "Fetch data" không nói gì cho LLM. Claude Opus 4 sẽ đoán công cụ dùng và đoán sai thường xuyên khi nhiều công cụ có mô tả chồng lấn. Viết mô tả công cụ như giải thích công cụ cho nhà phát triển chưa từng thấy codebase của bạn. Bao gồm công cụ làm gì, trả về gì, ví dụ đầu vào hợp lệ và khi nào KHÔNG dùng.
Mô tả query_users trong server cơ sở dữ liệu trên không dài dòng —nó chính xác đủ để loại bỏ sự mơ hồ.
Giới hạn buffer stdio. Transport stdio dùng pipe của hệ điều hành. Buffer pipe mặc định Linux: 64KB. Tool query_large_dataset của bạn trả 2MB JSON. Lời gọi write() bị chặn. Client treo. Bạn nhìn spinner 30 giây rồi kill -9 tiến trình.
Cho bất kỳ công cụ nào có thể trả hơn 64KB, triển khai phân khối phản hồi —chia kết quả lớn thành trang với tham số page và page_size. Hoặc chuyển sang transport HTTP, nơi giới hạn kích thước phản hồi cấu hình được ở cấp framework server. Đặt giới hạn cứng 512KB phản hồi cho mỗi công cụ. Không công cụ nào trong server MCP nên trả nhiều dữ liệu hơn mức LLM có thể xử lý hữu ích trong một cửa sổ ngữ cảnh.
Xung đột tên công cụ giữa các server. Bạn kết nối hai server MCP: một từ đội cơ sở dữ liệu (get_status —replication lag), một từ đội ops (get_status —sức khỏe triển khai). Cả hai phơi bày công cụ tên get_status. Claude Code thêm tiền tố tên server: database-tools_get_status và ops-tools_get_status. Nhưng một số client MCP, gồm các phiên bản gần đây của Cursor, âm thầm chọn server nào phản hồi tools/list trước. Cách sửa đơn giản đến mức khó tin và bị bỏ qua phổ biến: đặt namespace cho mọi tên công cụ từ ngày đầu. db_query_users. ops_deploy_service. logs_search_errors. Tiền tố hai ký tự ngăn cả một hạng mục lỗi âm thầm.
Khoảng trống xác thực schema. LLM sẽ gửi "user_id": "42" (string) khi schema của bạn nói "type": "integer". Nó sẽ gửi "limit": -5 khi schema nói "minimum": 1. Nó sẽ gửi "role": "superadmin" khi schema nói "enum": ["admin", "editor", "viewer"]. Handler call_tool của bạn phải xác thực mọi đối số, ép kiểu nơi an toàn (int("42") —42) và trả lỗi xác thực cụ thể cho mọi thứ còn lại. Đừng bao giờ giả định LLM tôn trọng JSON Schema của bạn. Nó không. Server của bạn là phòng tuyến cuối giữa đối số công cụ bị ảo giác và cơ sở dữ liệu sản xuất của bạn.
Câu hỏi thường gặp
Tôi có cần MCP nếu đã dùng function calling?
Chúng bổ trợ, không phải hoặc-hay. Function calling thực thi công cụ trong một lời gọi API. MCP chuẩn hóa khám phá và mô tả công cụ qua các ứng dụng. Dùng MCP để định nghĩa danh mục công cụ —nguồn sự thật duy nhất về công cụ nào tồn tại và cách gọi chúng. Dùng function calling lúc chạy để thực sự gọi các công cụ đó. MCP là lớp giao diện. Function calling là lớp thực thi. Nếu bạn có đúng một ứng dụng client và ba công cụ, bỏ qua MCP và dùng function calling trực tiếp. Bạn sẽ biết khi nào cần MCP: khoảnh khắc bạn thấy mình sao chép dán định nghĩa công cụ giữa file cấu hình Claude Code và mảng tools của app tùy chỉnh.
Tôi có thể dùng MCP với model OpenAI không?
Có —qua adapter hoặc OpenAI Agents SDK. Hỗ trợ gốc kém trưởng thành hơn Anthropic nhưng đủ dùng cho mẫu công cụ phổ biến. Nếu dùng nền tảng tổng hợp có hỗ trợ MCP gateway, nền tảng xử lý việc dịch: server MCP của bạn chạy với GPT-5.5 và GPT-5.5-mini mà không cần code MCP riêng OpenAI phía bạn. Đánh đổi: adapter cộng đồng tụt sau SDK chính thức của Anthropic khoảng 3 đến 6 tháng về tính năng đặc tả MCP mới.
MCP sẵn sàng sản xuất năm 2026 chưa?
Cho công cụ cục bộ qua transport stdio: có, và client stdio của Claude Code đã được thử thách qua hàng triệu giờ nhà phát triển. Cho dịch vụ từ xa qua HTTP: có, với lưu ý bạn phải tự thêm middleware xác thực —chuẩn auth của transport HTTP vẫn đang tiến hóa. Lõi giao thức (định dạng tin nhắn JSON-RPC, khám phá công cụ, thực thi công cụ) ổn định và, tại thời điểm viết, chưa có thay đổi phá vỡ kể từ cập nhật đặc tả 2025-03-26. Nếu tự lưu trữ server MCP HTTP, dự trù 2 đến 3 ngày cho thiết lập auth, rate limiting và logging.
MCP liên quan thế nào tới nền tảng tổng hợp API?
Nền tảng tổng hợp có thể đóng vai gateway MCP: bạn kết nối server MCP với nền tảng một lần. Nền tảng định tuyến lời gọi công cụ tới bất kỳ LLM nào —GPT-5.5, Claude Opus 4, Gemini 3, DeepSeek V3. Bạn có sử dụng công cụ đa model mà không cần cấu hình MCP theo nhà cung cấp. Một server MCP. Mọi model. Cho chi tiết cách nền tảng tổng hợp triển khai định tuyến MCP gateway trong thực tế, xem tài liệu tích hợp MCP của TokSpan.
Tôi có thể kết nối nhiều server MCP với một client không?
Có. Claude Code, Cursor và hầu hết client MCP hỗ trợ kết nối nhiều server đồng thời —thêm mỗi server vào file cấu hình với tên duy nhất, và mọi công cụ từ mọi server xuất hiện trong danh sách công cụ có sẵn của LLM. Cạnh sắc: xung đột tên công cụ giữa các server. Đặt tên công cụ với tiền tố namespace (db_query, ops_deploy) từ ngày đầu. Đừng trông cậy client phân biệt —không phải client nào cũng làm, và những client làm thì xử lý không nhất quán.
Điều gì xảy ra khi hai server MCP phơi bày công cụ cùng tên?
Tùy client, và đó là vấn đề. Claude Code thêm tên server để tạo định danh duy nhất: database-tools_get_status và ops-tools_get_status. Cursor âm thầm loại trùng —server phản hồi tools/list đầu tiên thắng. Đừng trông cậy phân biệt phía client chút nào. Tiền tố mỗi tên công cụ với namespace riêng theo server. db_get_status và ops_get_status loại bỏ mơ hồ tại nguồn.
Tôi gỡ lỗi lời gọi công cụ MCP thế nào khi có sự cố?
Kiểm ba nơi, theo thứ tự. Một, log client —Claude Code ghi tin nhắn MCP vào thư mục log của nó. Hai, stderr server của bạn —transport stdio gửi mọi đầu ra stderr tới console client (stdin và stdout dành riêng cho tin nhắn giao thức MCP, nhưng stderr tự do cho logging). Ba, thêm logging JSON có cấu trúc vào server: {"event": "tool_call", "tool": "query_users", "args": {...}, "duration_ms": 45, "error": null}. Log có cấu trúc bắt 80% bug MCP chỉ nổi lên trong các phiên on-call khi bạn 15 phút vào sự cố và không biết lời gọi công cụ nào thất bại hay vì sao.
MCP có hỗ trợ phản hồi streaming từ công cụ không?
Không trong đặc tả hiện tại. Lời gọi công cụ MCP trả một mảng content duy nhất —không phản hồi một phần, không cập nhật tiến trình, không Server-Sent Events từ thực thi công cụ. Cho thao tác chạy lâu như di trú cơ sở dữ liệu hay tạo báo cáo, trả job ID ngay và phơi bày tool get_job_status riêng. LLM hỏi vòng. Mẫu job-ID-cộng-polling là chuẩn de facto mọi server MCP sản xuất lớn dùng giữa năm 2026. Hỗ trợ streaming trực tiếp nằm trên lộ trình đặc tả MCP nhưng chưa có ngày phát hành cam kết.
MCP thống nhất tích hợp công cụ AI 2025–2026 —một server, bất kỳ client nào, mọi model. Nhưng chuẩn hóa mời gọi cạnh tranh. Giao thức Agent-to-Agent (A2A) của Google đang hút sức hút giữa các đội doanh nghiệp cần giao tiếp agent-tới-agent ngoài gọi công cụ đơn giản. OpenAI đang xây hệ sinh thái agent SDK riêng với tích hợp nền tảng sâu hơn. Câu hỏi đáng theo dõi xuyên qua 2027 không phải MCP có sống không —bản thân giao thức vững chắc và đặc tả ổn định— mà là liệu “một giao thức thống trị tất cả” có trụ lâu hơn nhu cầu của ngành cho tích hợp riêng theo nền tảng mang gắn kết chặt hơn và hiệu năng tốt hơn đổi lấy khóa chặt.
Server MCP bạn xây trong phần đầu bài viết này chạy với một client —Claude Desktop. MCP gateway kết nối cùng server đó tới mọi LLM trong stack của bạn, nên định nghĩa công cụ bạn viết một lần nuôi mọi model bạn dùng. Tài liệu tích hợp MCP của TokSpan bao gồm thiết lập gateway, cấu hình auth và checklist sản xuất từ phần trên —biến server stdio 50 dòng thành lớp công cụ đa model sống sót qua việc đổi nhà cung cấp mà không đụng một dòng code công cụ.