Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 initialize nê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 đổiNộ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
resultTypeMọ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à cacheScopeBắ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ỗiBase 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: id là chuỗi hoặc số nguyên; “Unlike base JSON-RPC, the ID MUST NOT be null”.
  • Response thành công: cùng id với request và có result chứa resultType.
  • Response lỗi: có error với code nguyên và message, tùy chọn data; cùng id vớ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ừ stdin và ghi lên stdout, 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 ra stderr (“MAY write UTF-8 strings to stderr for any logging purposes”). Đóng stdin là 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 _metaBắt buộcÝ nghĩa
io.modelcontextprotocol/protocolVersioncóBản giao thức của yêu cầu này
io.modelcontextprotocol/clientCapabilitiescóNăng lực của client liên quan tới yêu cầu
io.modelcontextprotocol/clientInfokhôngTên và phiên bản client (nên gửi)
io.modelcontextprotocol/logLevelkhôngMứ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-01 nhận -32022 kèm data.supported; thử lại với 2026-07-28 thì 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/discover và tools/list có đủ trường bắt buộc. resultType là complete, server/discover nêu bản, năng lực và serverInfo, tools/list có ttlMs và 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à add với tham số chuỗi đều trả JSON-RPC thành công (resultType là complete) với isError: true và lời giải thích trong content: đây là lỗi thực thi công cụ. Tool không tồn tại và arguments là mảng thì là lỗi giao thức -32602.
  • Mười hai ca lỗi giao thức ra đúng mã. -32700 cho JSON hỏng; -32600 cho mảng, giá trị không phải object, method không phải chuỗi và id null; -32601 cho method lạ và initialize; -32602 cho thiếu _meta, thiếu clientCapabilities, tool lạ và arguments sai dạng; -32022 cho bản không hỗ trợ. Với initialize, 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ó id khô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 stdin thì server thoát với mã 0, và nhật ký chỉ nằm ở stderr (23 dòng) trong khi stdout có 21 dòng, đều là JSON-RPC.
  • Một dòng print thừa phá khung. Khi server in nhật ký ra stdout, dòng đầu tiên client đọc không phải JSON và json.loads báo JSONDecodeError; đú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ênCa trong labCăn cứ
-32700Parse errordòng không phải JSONJSON-RPC: “Invalid JSON was received by the server”; id là null vì không đọc được
-32600Invalid Requestmảng; giá trị không phải object; method không phải chuỗi; id nullJSON-RPC; MCP: id không được null; stdio: mỗi dòng một thông điệp
-32601Method not foundmethod 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
-32602Invalid paramsthiếu _meta hoặc clientCapabilities; tool lạ; arguments sai dạngMCP: thiếu trường _meta bắt buộc; trang Tools: ví dụ “Unknown tool” dùng -32602
-32022UnsupportedProtocolVersionbản 1900-01-01 và 2099-01-01MCP: lỗi kèm data.supported và data.requested
-32021MissingRequiredClientCapabilitykhông kiểm: công cụ của lab không đòi năng lực nào của clientMCP: kèm data.requiredCapabilities
-32020HeaderMismatchkhông kiểm: chỉ liên quan Streamable HTTPbảng mã lỗi của MCP
-32603Internal errorkhô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: truelỗi thực thi công cụchia cho 0; tham số sai kiểutrang 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ệcAi chịu trách nhiệmCăn cứ trong đặc tả
Quyết định có chạy một công cụ hay khôngHost 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ềnNgườ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ựcHTTP có khung Authorization; stdio lấy thông tin xác thực từ môi trườngstdio “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 địnhmô tả hành vi như annotations “should be considered untrusted, unless obtained from a trusted server”
Tin vào serverInfo và clientInfoKhô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 raServer“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ánClientmụ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 (header MCP-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ề initialize khi 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, -32021 và -32020 chưa kiểm. Lab không có đường dẫn lỗi nội bộ, không thử id kiể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à Timer 60 giây tự kết thúc server nếu treo.

Học tiếp và nguồn

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.