MCP từ đầu: một server stdio tối thiểu theo bản đặc tả 2026-07-28 và bảng lỗi
Câu hỏi bài này trả lời: một cuộc gọi công cụ MCP thật ra là những dòng JSON nào trên stdio; bản đặc tả hiện hành đã bỏ bước bắt tay
initializenên mỗi yêu cầu phải tự mang gì; lỗi nào thuộc giao thức và lỗi nào thuộc công cụ; và MCP không làm thay những việc gì?
Cần biết trước: JSON, ý niệm request và response của JSON-RPC, Python. Lab dùng thư viện chuẩn của Python 3.14.4 trên macOS arm64: một server stdio tối thiểu và một client chạy nó như tiến trình con, không dùng SDK. Bài đọc đặc tả bản 2026-07-28, bản mà trang Versioning ngày 2026-10-04 ghi là hiện hành. Nhiều hướng dẫn, SDK cũ và báo cáo vẫn mô tả bắt tay initialize; đặc tả gọi đó là bản “legacy” (2025-11-25 trở về trước), nên nếu thấy initialize trong tài liệu, hãy kiểm xem tài liệu nói về bản nào. Lab chỉ chạy qua stdio và chỉ ở một bản đặc tả; không thử Streamable HTTP, xác thực, subscriptions/listen, multi round-trip, theo dõi tiến độ, hủy hay extension.
Điều đã đổi so với hướng dẫn cũ
Đặc tả MCP đánh phiên bản bằng ngày (YYYY-MM-DD), “to indicate the last date backwards incompatible changes were made”, và trang Versioning ghi “The current protocol version is 2026-07-28”. Mục Key Changes của đặc tả liệt kê thay đổi so với bản 2025-11-25; bài dùng sáu thay đổi ảnh hưởng trực tiếp tới code bên dưới:
| Thay đổi | Nội dung theo đặc tả |
|---|---|
| Không còn bắt tay | “Make MCP stateless: remove the initialize/notifications/initialized handshake.” Mỗi yêu cầu mang bản giao thức và năng lực của client trong _meta |
server/discover | “servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity”; client có thể gọi trước hoặc dùng làm phép dò tương thích trên stdio |
resultType | Mọi kết quả có trường bắt buộc resultType: "complete" cho kết quả thường, "input_required" cho kết quả trung gian của multi round-trip request |
ttlMs và cacheScope | Bắt buộc trên kết quả của tools/list, prompts/list, resources/list, resources/read và resources/templates/list |
| Không còn session | “Remove protocol-level sessions and the Mcp-Session-Id header” (phía HTTP); danh sách công cụ không đổi theo từng kết nối |
| Mã lỗi | Base Protocol định nghĩa -32020 đến -32022; Key Changes: “resource not found” đổi từ -32002 sang -32602 |
Khung thông điệp trên stdio
Mọi thông điệp “MUST follow the JSON-RPC 2.0 specification”. Phần MCP thêm vào hoặc siết lại:
- Request:
idlà chuỗi hoặc số nguyên; “Unlike base JSON-RPC, the ID MUST NOT be null”. - Response thành công: cùng
idvới request và córesultchứaresultType. - Response lỗi: có
errorvớicodenguyên vàmessage, tùy chọndata; cùngidvới request “except in error cases where the ID could not be read due a malformed request”. - Notification: không có
id, và “The receiver MUST NOT send a response”. - stdio: server đọc từ
stdinvà ghi lênstdout, mỗi dòng một thông điệp; “Messages are delimited by newlines, and MUST NOT contain embedded newlines”. “The server MUST NOT write anything to its stdout that is not a valid MCP message”; nhật ký đi rastderr(“MAY write UTF-8 strings to stderr for any logging purposes”). Đóngstdinlà tín hiệu tắt: “Servers SHOULD exit promptly when their standard input is closed”.
Mỗi yêu cầu tự mô tả
Đặc tả nói giao thức là stateless: “all the information needed to process a request is contained in the request itself”, và “Servers MUST NOT rely on prior requests over the same connection to establish context (e.g., capabilities, protocol version, client identity)”. Thông tin đó nằm trong params._meta:
Khóa trong _meta | Bắt buộc | Ý nghĩa |
|---|---|---|
io.modelcontextprotocol/protocolVersion | có | Bản giao thức của yêu cầu này |
io.modelcontextprotocol/clientCapabilities | có | Năng lực của client liên quan tới yêu cầu |
io.modelcontextprotocol/clientInfo | không | Tên và phiên bản client (nên gửi) |
io.modelcontextprotocol/logLevel | không | Mức nhật ký tối thiểu server nên phát |
Luật cho thiếu và sai: “A request missing any required field is malformed; the server MUST reject it with JSON-RPC error code -32602 (Invalid params)”. Bản không hỗ trợ thì trả lỗi -32022 (UnsupportedProtocolVersion) kèm data.supported và data.requested; client chọn một bản chung và gửi lại. Trang Versioning mô tả đúng điều này: “There is no negotiation handshake. Every request carries its protocol version, and the server accepts or rejects each request independently”. server/discover là phương thức server bắt buộc có; client “MAY call it” trước để biết bản và năng lực, nhưng không bắt buộc.
Lab: server và client stdio
Tạo thư mục trống rồi lưu server.py. Nó hiện thực server/discover, tools/list, tools/call với hai công cụ add và divide, kiểm _meta trước khi chạy phương thức, và phân biệt hai loại lỗi. Có bốn chỗ đặc tả không ép một cách làm, lab chọn như sau: id của lỗi Invalid Request được trả lại khi đọc được (đúng với luật MCP ở trên); mảng JSON bị từ chối bằng -32600 vì stdio quy định “Each message is a single JSON-RPC request, notification, or response” (JSON-RPC có batch, nhưng lab không nhận batch); tham số sai kiểu trong arguments là lỗi thực thi công cụ (isError: true) vì trang Tools xếp “Input validation errors” vào loại đó; thông báo không rõ thì bỏ qua và không phản hồi, vì JSON-RPC cấm trả lời thông báo (“The Server MUST NOT reply to a Notification”).
import json
import sys
VERSION = "2026-07-28"
SUPPORTED = [VERSION]
VERSION_KEY = "io.modelcontextprotocol/protocolVersion"
CAPS_KEY = "io.modelcontextprotocol/clientCapabilities"
SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo"
SERVER_INFO = {"name": "demo-tools", "version": "0.1.0"}
PAIR = {
"type": "object",
"properties": {"a": {"type": "number"}, "b": {"type": "number"}},
"required": ["a", "b"],
"additionalProperties": False,
}
TOOLS = [
{"name": "add", "description": "Cộng hai số", "inputSchema": PAIR},
{"name": "divide", "description": "Chia a cho b", "inputSchema": PAIR},
]
class RpcError(Exception):
def __init__(self, code, message, data=None):
super().__init__(message)
self.code, self.message, self.data = code, message, data
def check_arguments(schema, arguments):
for name in schema["required"]:
if name not in arguments:
return f"thiếu tham số {name}"
for name, value in arguments.items():
if name not in schema["properties"]:
return f"tham số lạ {name}"
if isinstance(value, bool) or not isinstance(value, (int, float)):
return f"{name} phải là số"
return None
def divide(a, b):
if b == 0:
raise ValueError("không chia được cho 0")
return a / b
HANDLERS = {"add": lambda a, b: a + b, "divide": divide}
def complete(**body):
return {"resultType": "complete", **body, "_meta": {SERVER_INFO_KEY: SERVER_INFO}}
def discover(params):
return complete(
supportedVersions=SUPPORTED,
capabilities={"tools": {}},
instructions="Hai công cụ số học để thử giao thức.",
ttlMs=60_000,
cacheScope="public",
)
def list_tools(params):
return complete(tools=TOOLS, ttlMs=60_000, cacheScope="public")
def call_tool(params):
name, arguments = params.get("name"), params.get("arguments", {})
if not isinstance(name, str) or not isinstance(arguments, dict):
raise RpcError(-32602, "Invalid params: name phải là chuỗi và arguments phải là object")
tool = next((tool for tool in TOOLS if tool["name"] == name), None)
if tool is None:
raise RpcError(-32602, f"Unknown tool: {name}")
try:
problem = check_arguments(tool["inputSchema"], arguments)
if problem:
raise ValueError(problem)
handler = HANDLERS[name]
value = handler(arguments["a"], arguments["b"])
except ValueError as error:
return complete(content=[{"type": "text", "text": f"Lỗi: {error}"}], isError=True)
return complete(content=[{"type": "text", "text": str(value)}], isError=False)
METHODS = {"server/discover": discover, "tools/list": list_tools, "tools/call": call_tool}
def dispatch(method, params):
if method not in METHODS:
hint = ""
if method == "initialize":
hint = f"; server chỉ nói bản {', '.join(SUPPORTED)}, không có bắt tay initialize"
raise RpcError(-32601, f"Method not found: {method}{hint}")
meta = params.get("_meta") if isinstance(params, dict) else None
if not isinstance(meta, dict):
raise RpcError(-32602, "Invalid params: thiếu _meta")
for key, kind in ((VERSION_KEY, str), (CAPS_KEY, dict)):
if not isinstance(meta.get(key), kind):
raise RpcError(-32602, f"Invalid params: thiếu _meta.{key}")
if meta[VERSION_KEY] not in SUPPORTED:
data = {"supported": SUPPORTED, "requested": meta[VERSION_KEY]}
raise RpcError(-32022, "Unsupported protocol version", data)
handler = METHODS[method]
return handler(params)
def valid_id(value):
return isinstance(value, (str, int)) and not isinstance(value, bool)
def failure(request_id, code, message, data=None):
error = {"code": code, "message": message, **({"data": data} if data is not None else {})}
return {"jsonrpc": "2.0", "id": request_id, "error": error}
def process(message):
if isinstance(message, list):
return failure(None, -32600, "Invalid Request: mỗi dòng là một thông điệp, không nhận batch")
is_envelope = isinstance(message, dict) and message.get("jsonrpc") == "2.0" and isinstance(message.get("method"), str)
if not is_envelope:
known = message.get("id") if isinstance(message, dict) and valid_id(message.get("id")) else None
return failure(known, -32600, "Invalid Request")
if "id" not in message:
return None # thông báo: không bao giờ phản hồi
if not valid_id(message["id"]):
return failure(None, -32600, "Invalid Request: id phải là chuỗi hoặc số nguyên, không được null")
try:
result = dispatch(message["method"], message.get("params", {}))
return {"jsonrpc": "2.0", "id": message["id"], "result": result}
except RpcError as error:
return failure(message["id"], error.code, error.message, error.data)
NOISY = "--noisy" in sys.argv # cố ý sai, chỉ để thấy khung stdio bị hỏng khi nhật ký rơi vào stdout
sys.stdin.reconfigure(encoding="utf-8")
sys.stdout.reconfigure(encoding="utf-8")
for line in sys.stdin:
if not line.strip():
continue
print(f"nhận: {line.strip()[:70]}", file=sys.stderr, flush=True) # nhật ký đi stderr, stdout chỉ có JSON-RPC
if NOISY:
print(f"debug: đang xử lý {line.strip()[:20]}", flush=True)
try:
reply = process(json.loads(line))
except json.JSONDecodeError:
reply = failure(None, -32700, "Parse error")
if reply is not None:
sys.stdout.write(json.dumps(reply, ensure_ascii=False) + "\n")
sys.stdout.flush()
Lưu cases.py: client khởi chạy server.py như tiến trình con và nói chuyện theo từng dòng. Nó thử đàm phán phiên bản (yêu cầu bản 2099-01-01 rồi thử lại với bản server nêu), gọi server/discover và tools/list, chạy ba cuộc gọi công cụ, chạy 12 ca lỗi giao thức và khẳng định từng mã lỗi, gửi hai thông báo không có id, đóng stdin để kiểm server thoát, rồi chạy một server cố ý in nhật ký ra stdout. Thông báo không có phản hồi được kiểm bằng thứ tự dòng: stdio là kênh tuần tự, nên sau mỗi thông báo client gửi tiếp một yêu cầu server/discover và đòi dòng đầu tiên đọc được phải có id của yêu cầu đó; nếu server lỡ trả lời thông báo, dòng đó sẽ chen lên trước và id không khớp.
import json
import subprocess
import sys
import threading
VERSION = "2026-07-28"
VERSION_KEY = "io.modelcontextprotocol/protocolVersion"
CAPS_KEY = "io.modelcontextprotocol/clientCapabilities"
INFO_KEY = "io.modelcontextprotocol/clientInfo"
SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo"
class Client:
def __init__(self, *flags):
self.proc = subprocess.Popen(
[sys.executable, "-B", "server.py", *flags],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding="utf-8", bufsize=1,
) # fmt: skip
self.guard = threading.Timer(60, self.proc.kill) # chốt an toàn nếu server treo
self.guard.daemon = True
self.guard.start()
self.next_id = 0
self.lines = 0
def send(self, text):
self.proc.stdin.write(text + "\n")
self.proc.stdin.flush()
def read(self):
line = self.proc.stdout.readline()
assert line, "server đã đóng stdout"
self.lines += 1
reply = json.loads(line) # mọi dòng trên stdout phải là JSON
assert isinstance(reply, dict) and reply["jsonrpc"] == "2.0"
return reply
def meta(self, version=VERSION):
return {VERSION_KEY: version, CAPS_KEY: {}, INFO_KEY: {"name": "demo-client", "version": "0.1.0"}}
def call(self, method, params=None, version=VERSION):
self.next_id += 1
body = {**(params or {}), "_meta": self.meta(version)}
self.send(json.dumps({"jsonrpc": "2.0", "id": self.next_id, "method": method, "params": body}))
reply = self.read()
assert reply["id"] == self.next_id
return reply
def raw(self, text):
self.send(text)
return self.read()
def close(self):
self.proc.stdin.close()
code = self.proc.wait(timeout=10)
self.guard.cancel()
return code, self.proc.stderr.read()
client = Client()
print("== Đàm phán phiên bản bằng từng yêu cầu")
error = client.call("server/discover", version="2099-01-01")["error"]
print(f"yêu cầu bản 2099-01-01 -> lỗi {error['code']} {error['message']}, data={json.dumps(error['data'])}")
version = error["data"]["supported"][0]
result = client.call("server/discover", version=version)["result"]
print(f"thử lại với bản {version} -> resultType={result['resultType']}, supportedVersions={result['supportedVersions']}")
print(
f"capabilities={sorted(result['capabilities'])}, serverInfo={result['_meta'][SERVER_INFO_KEY]['name']}, "
f"ttlMs={result['ttlMs']}, cacheScope={result['cacheScope']}"
)
print("== Công cụ")
listing = client.call("tools/list")["result"]
again = client.call("tools/list")["result"]
names = [tool["name"] for tool in listing["tools"]]
print(
f"tools/list -> {names}, ttlMs={listing['ttlMs']}, cacheScope={listing['cacheScope']}, "
f"thứ tự ổn định: {listing['tools'] == again['tools']}"
)
for name, arguments in (("add", {"a": 2, "b": 3}), ("divide", {"a": 1, "b": 0}), ("add", {"a": "2", "b": 3})):
result = client.call("tools/call", {"name": name, "arguments": arguments})["result"]
text = result["content"][0]["text"]
print(f"tools/call {name} {json.dumps(arguments)} -> {result['resultType']}, isError={result['isError']}, {text!r}")
print("== Bảng lỗi giao thức")
meta = json.dumps(client.meta())
discover = f'{{"jsonrpc":"2.0","id":9,"method":"server/discover","params":{{"_meta":{meta}}}}}'
codes = []
def row(label, reply, code):
error = reply["error"]
print(f"{label:46} -> {error['code']} id={json.dumps(reply['id'])} {error['message']}")
assert error["code"] == code, (label, reply)
codes.append(code)
row("JSON hỏng", client.raw("{not json"), -32700)
row("mảng thay vì một thông điệp", client.raw("[]"), -32600)
row("giá trị không phải object", client.raw("42"), -32600)
row("method không phải chuỗi", client.raw('{"jsonrpc":"2.0","id":7,"method":5}'), -32600)
row("id là null", client.raw(discover.replace('"id":9', '"id":null')), -32600)
row("method không tồn tại", client.call("tools/delete"), -32601)
row("initialize của bản cũ", client.raw('{"jsonrpc":"2.0","id":8,"method":"initialize","params":{}}'), -32601)
row("thiếu _meta", client.raw('{"jsonrpc":"2.0","id":8,"method":"tools/list"}'), -32602)
only_version = json.dumps({"_meta": {VERSION_KEY: VERSION}})
no_caps = f'{{"jsonrpc":"2.0","id":8,"method":"tools/list","params":{only_version}}}'
row("thiếu clientCapabilities", client.raw(no_caps), -32602)
row("tool không tồn tại", client.call("tools/call", {"name": "nope", "arguments": {}}), -32602)
row("arguments là mảng", client.call("tools/call", {"name": "add", "arguments": [1, 2]}), -32602)
row("bản giao thức không hỗ trợ", client.call("tools/list", version="1900-01-01"), -32022)
print("== Thông báo không có id")
for text in (
'{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}}',
'{"jsonrpc":"2.0","method":"notifications/khong-co"}',
):
client.send(text)
client.call("server/discover") # nếu server lỡ trả lời thông báo, dòng đầu tiên đọc được sẽ không khớp id
print(f"{text[:60]:60} -> không có phản hồi (kiểm bằng thứ tự dòng)")
code, log = client.close()
print(f"== Đóng stdin -> server thoát với mã {code}; stderr có {len(log.splitlines())} dòng nhật ký")
print(f"{client.lines} dòng stdout, đều là JSON-RPC hợp lệ; {len(codes)} lỗi giao thức đã kiểm")
print("== Server in nhật ký ra stdout (cố ý sai)")
noisy = Client("--noisy")
try:
noisy.call("server/discover")
except json.JSONDecodeError as error:
print(f"dòng đầu tiên trên stdout không phải JSON -> {type(error).__name__}")
else:
raise SystemExit("đáng lẽ khung stdio phải hỏng")
noisy.close()
python3 -B cases.py
== Đàm phán phiên bản bằng từng yêu cầu
yêu cầu bản 2099-01-01 -> lỗi -32022 Unsupported protocol version, data={"supported": ["2026-07-28"], "requested": "2099-01-01"}
thử lại với bản 2026-07-28 -> resultType=complete, supportedVersions=['2026-07-28']
capabilities=['tools'], serverInfo=demo-tools, ttlMs=60000, cacheScope=public
== Công cụ
tools/list -> ['add', 'divide'], ttlMs=60000, cacheScope=public, thứ tự ổn định: True
tools/call add {"a": 2, "b": 3} -> complete, isError=False, '5'
tools/call divide {"a": 1, "b": 0} -> complete, isError=True, 'Lỗi: không chia được cho 0'
tools/call add {"a": "2", "b": 3} -> complete, isError=True, 'Lỗi: a phải là số'
== Bảng lỗi giao thức
JSON hỏng -> -32700 id=null Parse error
mảng thay vì một thông điệp -> -32600 id=null Invalid Request: mỗi dòng là một thông điệp, không nhận batch
giá trị không phải object -> -32600 id=null Invalid Request
method không phải chuỗi -> -32600 id=7 Invalid Request
id là null -> -32600 id=null Invalid Request: id phải là chuỗi hoặc số nguyên, không được null
method không tồn tại -> -32601 id=8 Method not found: tools/delete
initialize của bản cũ -> -32601 id=8 Method not found: initialize; server chỉ nói bản 2026-07-28, không có bắt tay initialize
thiếu _meta -> -32602 id=8 Invalid params: thiếu _meta
thiếu clientCapabilities -> -32602 id=8 Invalid params: thiếu _meta.io.modelcontextprotocol/clientCapabilities
tool không tồn tại -> -32602 id=9 Unknown tool: nope
arguments là mảng -> -32602 id=10 Invalid params: name phải là chuỗi và arguments phải là object
bản giao thức không hỗ trợ -> -32022 id=11 Unsupported protocol version
== Thông báo không có id
{"jsonrpc":"2.0","method":"notifications/cancelled","params" -> không có phản hồi (kiểm bằng thứ tự dòng)
{"jsonrpc":"2.0","method":"notifications/khong-co"} -> không có phản hồi (kiểm bằng thứ tự dòng)
== Đóng stdin -> server thoát với mã 0; stderr có 23 dòng nhật ký
21 dòng stdout, đều là JSON-RPC hợp lệ; 12 lỗi giao thức đã kiểm
== Server in nhật ký ra stdout (cố ý sai)
dòng đầu tiên trên stdout không phải JSON -> JSONDecodeError
Đọc kết quả:
- Đàm phán phiên bản không cần bắt tay. Yêu cầu bản
2099-01-01nhận-32022kèmdata.supported; thử lại với2026-07-28thì thành công. Giữa hai yêu cầu server không giữ gì: yêu cầu thứ hai tự mang toàn bộ thông tin của nó. server/discovervàtools/listcó đủ trường bắt buộc.resultTypelàcomplete,server/discovernêu bản, năng lực vàserverInfo,tools/listcóttlMsvàcacheScope, và thứ tự công cụ giống nhau qua hai lần gọi (đặc tả nói servers “SHOULD return tools in a deterministic order”).- Hai loại lỗi nằm ở hai chỗ khác nhau.
divide(1, 0)vàaddvới tham số chuỗi đều trả JSON-RPC thành công (resultTypelàcomplete) vớiisError: truevà lời giải thích trongcontent: đây là lỗi thực thi công cụ. Tool không tồn tại vàargumentslà mảng thì là lỗi giao thức-32602. - Mười hai ca lỗi giao thức ra đúng mã.
-32700cho JSON hỏng;-32600cho mảng, giá trị không phải object,methodkhông phải chuỗi vàidnull;-32601cho method lạ vàinitialize;-32602cho thiếu_meta, thiếuclientCapabilities, tool lạ vàargumentssai dạng;-32022cho bản không hỗ trợ. Vớiinitialize, server nêu bản nó nói trong thông báo lỗi, điều trang Versioning khuyên để client cũ có thứ để hiển thị. - Thông báo không có
idkhông nhận phản hồi, kể cả thông báo không biết. Lab chỉ chứng minh điều này qua thứ tự dòng trên stdio. - Đóng
stdinthì server thoát với mã 0, và nhật ký chỉ nằm ởstderr(23 dòng) trong khistdoutcó 21 dòng, đều là JSON-RPC. - Một dòng
printthừa phá khung. Khi server in nhật ký rastdout, dòng đầu tiên client đọc không phải JSON vàjson.loadsbáoJSONDecodeError; đúng với luật “MUST NOT write anything to its stdout that is not a valid MCP message”.
Bảng mã lỗi đã kiểm và chưa kiểm
| Mã | Tên | Ca trong lab | Căn cứ |
|---|---|---|---|
-32700 | Parse error | dòng không phải JSON | JSON-RPC: “Invalid JSON was received by the server”; id là null vì không đọc được |
-32600 | Invalid Request | mảng; giá trị không phải object; method không phải chuỗi; id null | JSON-RPC; MCP: id không được null; stdio: mỗi dòng một thông điệp |
-32601 | Method not found | method lạ; initialize của bản cũ | JSON-RPC: “The method does not exist / is not available”; ma trận tương thích của trang Versioning |
-32602 | Invalid params | thiếu _meta hoặc clientCapabilities; tool lạ; arguments sai dạng | MCP: thiếu trường _meta bắt buộc; trang Tools: ví dụ “Unknown tool” dùng -32602 |
-32022 | UnsupportedProtocolVersion | bản 1900-01-01 và 2099-01-01 | MCP: lỗi kèm data.supported và data.requested |
-32021 | MissingRequiredClientCapability | không kiểm: công cụ của lab không đòi năng lực nào của client | MCP: kèm data.requiredCapabilities |
-32020 | HeaderMismatch | không kiểm: chỉ liên quan Streamable HTTP | bảng mã lỗi của MCP |
-32603 | Internal error | không kiểm: lab không có đường dẫn nào cố ý gây lỗi nội bộ | JSON-RPC; MCP dùng các mã chuẩn -32700, -32600 đến -32603 |
isError: true | lỗi thực thi công cụ | chia cho 0; tham số sai kiểu | trang Tools: lỗi của công cụ nằm trong kết quả để mô hình tự sửa |
Trang Tools nói rõ ranh giới: lỗi giao thức là “Unknown tool”, yêu cầu sai cấu trúc và lỗi server; lỗi thực thi công cụ là lỗi API, kiểm tra đầu vào và lỗi nghiệp vụ. “Clients SHOULD provide tool execution errors to language models to enable self-correction”; còn lỗi giao thức client chỉ “MAY” đưa cho mô hình vì ít khi giúp phục hồi.
MCP không làm thay những gì
| Việc | Ai chịu trách nhiệm | Căn cứ trong đặc tả |
|---|---|---|
| Quyết định có chạy một công cụ hay không | Host và ứng dụng | “Hosts must obtain explicit user consent before invoking any tool”; “there SHOULD always be a human in the loop with the ability to deny tool invocations” |
| Ép các nguyên tắc về đồng ý và quyền | Người xây ứng dụng, không phải giao thức | “While MCP itself cannot enforce these security principles at the protocol level, implementors SHOULD: Build robust consent and authorization flows into their applications” |
| Xác thực | HTTP có khung Authorization; stdio lấy thông tin xác thực từ môi trường | stdio “SHOULD NOT follow this specification, and instead retrieve credentials from the environment” |
| Tin vào mô tả và nhãn của công cụ | Không tin theo mặc định | mô tả hành vi như annotations “should be considered untrusted, unless obtained from a trusted server” |
Tin vào serverInfo và clientInfo | Không dùng cho quyết định bảo mật | “self-reported by the sender and are not verified by the protocol” |
| Kiểm đầu vào, kiểm soát truy cập, giới hạn tốc độ, làm sạch đầu ra | Server | “Servers MUST: Validate all tool inputs, Implement proper access controls, Rate limit tool invocations, Sanitize tool outputs” |
| Hỏi xác nhận, kiểm kết quả trước khi đưa cho mô hình, timeout, nhật ký kiểm toán | Client | mục Security Considerations của trang Tools (các mệnh đề SHOULD cho client) |
Server của lab chạy mọi cuộc gọi hợp lệ: không hỏi ai, không xác thực, không giới hạn tốc độ. Giao thức chỉ chuyển cuộc gọi tới; cho phép hay không là việc của lớp trên. Cách đặt các cổng đó ở phía host nằm ở Harness tối thiểu cho một task nhỏ và Dựng harness để AI agent làm việc đáng tin.
Giới hạn
- Chỉ một bản đặc tả (
2026-07-28) và chỉ transport stdio. Không thử Streamable HTTP (headerMCP-Protocol-Version, mã HTTP,-32020), xác thực OAuth,subscriptions/listen, multi round-trip request (input_required), theo dõi tiến độ, hủy, resources, prompts, sampling, elicitation, roots hay extension như Tasks và MCP Apps. - Server của lab không phải bản đủ tính năng: bộ kiểm lược đồ chỉ biết kiểu số, không có phân trang, không có
listChanged, không hỗ trợ client cũ (bản cóinitialize). Bài không kiểm client hoặc server của bên thứ ba, và không so sánh với SDK chính thức. - Phép dò tương thích của client hai thời đại (probe bằng
server/discover, rồi lùi vềinitializekhi server không trả lỗi hiện đại nào) nằm ở trang stdio của đặc tả và không có trong lab. - Bảng lỗi chỉ gồm những mã lab gây ra được;
-32603,-32021và-32020chưa kiểm. Lab không có đường dẫn lỗi nội bộ, không thửidkiểu chuỗi dài, thông điệp rất lớn hay Unicode hiếm. - “Không phản hồi thông báo” được kiểm bằng thứ tự dòng chứ không bằng thời gian chờ, và bài không đo độ trễ.
- Bài không bàn độ an toàn của công cụ cụ thể, tiêm lệnh qua mô tả công cụ hay đánh giá nhà cung cấp MCP server; chỉ nêu những gì đặc tả ghi.
- Lab không ghi tệp ngoài thư mục bạn đã tạo; mỗi tiến trình con tự thoát khi
stdinđóng, vàTimer60 giây tự kết thúc server nếu treo.
Học tiếp và nguồn
- Harness tối thiểu cho một task nhỏ: kernel nhỏ và ranh giới quyền, nơi cổng cho phép hoặc từ chối công cụ nên nằm.
- Dựng harness để AI agent làm việc đáng tin: cổng người duyệt và chốt chặn thật.
- Model Context Protocol, Specification (bản 2026-07-28): tổng quan và nguyên tắc an toàn.
- MCP, Base Protocol: thông điệp,
resultType, mã lỗi,_meta, statelessness và xác thực. - MCP, Versioning and Compatibility và Versioning: đàm phán bản qua từng yêu cầu,
-32022, ma trận tương thích, bản hiện hành. - MCP, stdio: khung thông điệp,
stderr, tắt server, dò tương thích. - MCP, Discovery và Tools:
server/discover,tools/list,tools/call, hai loại lỗi, lưu ý bảo mật. - MCP, Key Changes: thay đổi so với bản 2025-11-25.
- JSON-RPC Working Group, JSON-RPC 2.0 Specification: request, response, notification và bảng mã lỗi.
Nguồn trực tuyến đọc ngày 2026-10-04; số đo thuộc Python 3.14.4 trên macOS arm64, chỉ qua stdio và chỉ ở bản đặc tả 2026-07-28.