Giải thích MCP theo đặc tả: Server thực sự là gì
Một dòng JSON vào subprocess, 13 định nghĩa tool trả về, đối chiếu bản 2026-07-28 đã bỏ handshake.
Trên trang này
Cài một MCP server đã phát hành, gửi cho nó một dòng JSON, rồi đọc thứ trả về.
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| npx @modelcontextprotocol/server-everything stdio{"result":{"tools":[{"name":"echo","title":"Echo Tool","description":"Echoes
back the input string","inputSchema":{"$schema":"http://json-schema.org/draft-07/
schema#","type":"object","properties":{"message":{"type":"string","description":
"Message to echo"}},"required":["message"]},"annotations":{"readOnlyHint":true,
… … 7,663 bytes on one line …
"jsonrpc":"2.0","id":1}Mười ba định nghĩa tool, trên một dòng duy nhất, từ một process đã đọc một dòng từ standard input của nó. Bạn vừa nói chuyện bằng Model Context Protocol, không SDK, không thư viện client và không framework. Toàn bộ chỉ có vậy: một transport, một định dạng thông điệp, và một tập nhỏ các method có tên.
Chương 18 định nghĩa tool gồm hai phần — một JSON Schema mà model nhìn thấy, và một endpoint trong code của bạn mà model không bao giờ thấy. Chương 23 xây một harness giữ một catalogue các tool đó. Cả hai đều chưa trả lời câu hỏi quyết định liệu thứ này có tái sử dụng được hay không: ai viết schema, và nó đi từ người viết vào prompt của bạn bằng cách nào? MCP là một câu trả lời cho câu hỏi đó, và rất đáng đọc bản gốc, vì gần như mọi thứ được viết về nó đều mô tả một revision không còn tồn tại.
Có ba điều sai trong lệnh bạn vừa chạy, và mỗi điều là một phần của chương này. Nó không mang protocol version, nên một server tuân thủ đặc tả sẽ từ chối. Dù vậy nó vẫn nhận được câu trả lời, vì một lý do mà specification gọi là hazard chứ không phải feature. Và nó hỏi một trong ba primitive mà không hề khám phá rằng hai primitive còn lại tồn tại.
Vấn đề nó giải quyết, và phép so sánh mà chính spec đưa ra
Liên kết đến mục: Vấn đề nó giải quyết, và phép so sánh mà chính spec đưa raTrước khi nói về wire, hãy làm phép tính. Bạn có ứng dụng AI và thứ chúng cần truy cập — lịch, ticket tracker, database kho dữ liệu, công cụ thiết kế. Không có một contract chung, ai đó phải viết tích hợp, và mỗi tích hợp là một schema cộng một endpoint cộng một câu chuyện xác thực cộng một gánh nặng bảo trì. Có contract chung, nhà cung cấp tool viết một server, nhà cung cấp ứng dụng viết một client, và tổng số là .
Đây không phải quan sát mới, và specification nói rõ ý tưởng đó đến từ đâu:
MCP takes some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools. In a similar way, MCP standardizes how to integrate additional context and tools into the ecosystem of AI applications.1
Hãy hiểu phép so sánh đó theo nghĩa đen, không phải như một lời khen. Trước protocol đó, hỗ trợ một ngôn ngữ trong editor nghĩa là một plugin cho mỗi editor; sau đó, một nhóm ngôn ngữ phát hành một server và mọi editor đều có được hỗ trợ. Thước đo thành công không phải là sự thanh nhã, mà là số lượng tích hợp ngừng nhân lên. Điều tương tự diễn ra ở đây: giá trị nằm ở số lượng implementation, không nằm ở thiết kế. Một protocol mà chỉ hai sản phẩm nói được chỉ là một định dạng dữ liệu kèm thêm nghi thức.
Thực sự có gì trên wire
Liên kết đến mục: Thực sự có gì trên wireThông điệp MCP là JSON-RPC 2.0. Một request là object có jsonrpc, một id, một method và params tùy chọn; response mang cùng id và hoặc result hoặc error; notification là request không có id và không nhận reply. Specification thêm ba ràng buộc bên trên: id phải là string hoặc number và không được là null, không được trùng với một request khác còn đang chạy, và mọi result phải mang trường resultType.2
Trên stdio transport — transport mà lệnh ở trên dùng — quy tắc framing là mỗi thông điệp một dòng:
Messages are delimited by newlines, and MUST NOT contain embedded newlines. […] The server MUST NOT write anything to its
stdoutthat is not a valid MCP message.3
Mệnh đề cuối là cách phổ biến nhất khiến một server tự làm bị hỏng, và nó hỏng trong im lặng: một console.log lạc chỗ, một progress bar, một deprecation warning từ dependency, rồi line parser của client đụng phải thứ không phải JSON. Lối thoát nằm trong cùng phần đó — server may write anything it likes to stderr, và client should not treat that as an error. Reference server ở trên in Starting default (STDIO) server... mỗi lần khởi chạy, trên stderr, nên pipe vẫn chạy được.
Transport chuẩn còn lại là Streamable HTTP: mỗi message là một POST tới một endpoint duy nhất, và reply hoặc là một JSON object hoặc là một stream Server-Sent Events theo phạm vi request — wire format mà Chương 14 đã parse bằng tay. Semantics giống hệt trên cả hai, vì transport là một binding: nó định nghĩa framing và delivery, không định nghĩa meaning.4
Điều sai đầu tiên: không có version
Liên kết đến mục: Điều sai đầu tiên: không có versionLệnh ở trên gửi tools/list và không gì khác. Theo revision hiện tại, request đó malformed, và một server tuân thủ phải reject nó.
Kể từ 2026-07-28, MCP là stateless protocol, và specification nói điều đó không vòng vo:
The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.2
Vì vậy mọi request đều mang protocol version riêng và client capabilities riêng, trong một object _meta dành riêng bên trong params. Hai trường trong đó là bắt buộc trên từng request; một request thiếu một trong hai là malformed và server must answer -32602:2
key _meta | bắt buộc | đó là gì |
|---|---|---|
io.modelcontextprotocol/protocolVersion | có | revision mà request này nói, ví dụ "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | có | client có thể làm gì cho server trên request này |
io.modelcontextprotocol/clientInfo | không (nhưng should) | tên và version của client, chỉ để hiển thị và log |
io.modelcontextprotocol/logLevel | không | mức log tối thiểu mà server nên emit cho request này |
Viết đầy đủ, một tools/list đúng là như sau — và đây là lần cuối chương này hiển thị metadata đầy đủ, vì từ đây nó có trên mọi request:
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},
"io.modelcontextprotocol/clientInfo":{"name":"bare-hands","version":"0.0.1"}}}}Capability object chính là negotiation. Không còn bước negotiation riêng nữa: client khai báo nó có thể làm gì trên mỗi request, server khai báo nó có thể làm gì trong result, và không bên nào được dùng một feature mà bên kia chưa claim. Một server cần capability mà client không khai báo must answer -32021 và nêu capability bị thiếu trong data.requiredCapabilities. Một server không nói version được yêu cầu must answer -32022 và liệt kê các version nó nói được.2
Client muốn có câu trả lời ngay từ đầu có thể hỏi: server/discover là RPC bắt buộc, trả về supported versions, capabilities, identity và một block instructions tùy chọn trong một round trip.5 Gọi nó là tùy chọn. Implement nó thì không.
Điều sai thứ hai: server là legacy
Liên kết đến mục: Điều sai thứ hai: server là legacyLệnh đã chạy được. Theo revision hiện tại, nó không nên chạy được, và lý do nên được đo hơn là viết thành một đoạn văn, vì nó là trạng thái của cả ecosystem trong một dòng.
Probe reference server theo cách specification bảo một modern client probe:
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}' \
| npx @modelcontextprotocol/server-everything stdio{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}Đó là nhánh thứ ba của compatibility rule: một DiscoverResult nghĩa là modern, một lỗi modern được nhận diện nghĩa là modern-nhưng-sai-version, và bất cứ thứ gì khác — gồm cả -32601 — nghĩa là legacy, hãy fall back về handshake initialize.3 Vậy hãy làm thế, yêu cầu revision hiện tại:
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28",
"capabilities":{},"clientInfo":{"name":"bare-hands","version":"0.0.1"}}}
← {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":true},
"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},
"logging":{},"tasks":{…},"completions":{}},"serverInfo":{"name":"mcp-servers/everything",
"title":"Everything Reference Server","version":"2.0.0"},"instructions":"…"}}Client yêu cầu 2026-07-28 và server trả lời 2025-11-25. Ngày 7 tháng 9 năm 2026, official reference server — npm package @modelcontextprotocol/server-everything, version 2026.8.31, phát hành ngày 31 tháng 8 năm 2026 — chưa implement revision hiện tại. TypeScript SDK mà nó dựa trên cũng vậy: release 1.30.0 ra ngày 27 tháng 7 năm 2026, một ngày trước revision đó.
Hãy đọc hệ quả thay vì chuyện bên lề. Gần như mọi thứ được viết về MCP mô tả một protocol có handshake initialize, một session, một request roots/list mà server gửi cho client, và HTTP+SSE transport. Cả bốn đều đã biến mất hoặc đang rời đi. Khi bạn đọc bất cứ thứ gì về MCP, kể cả trang này, điều đầu tiên cần tìm là revision number.
Và lý do lệnh đầu tiên chạy được được specification mô tả là hazard, không phải feature:
some legacy servers do not validate that a request arrives after
initializeand would process an era-ambiguous method (such astools/call) under legacy semantics. Probing yields a deterministic failure instead.3
Đã đo: gửi tools/list tới server đó mà không handshake nào vẫn trả về toàn bộ catalogue. Một method lẽ ra phải bị từ chối đã được phục vụ, chính xác là lý do specification nói phải probe bằng server/discover trước ngay cả khi bạn chỉ hỗ trợ modern versions.
Ba vai trò, và câu đáng trích dẫn nhất trong toàn bộ tài liệu
Liên kết đến mục: Ba vai trò, và câu đáng trích dẫn nhất trong toàn bộ tài liệuMCP có ba bên, và sự khác biệt giữa hai bên đầu là thứ mọi người hay nhập làm một:
Host. Ứng dụng: sản phẩm chat, editor, agent. Nó sở hữu conversation, model, credentials và consent của user. Nó tạo client và áp đặt security boundary giữa chúng.
Client. Một connector bên trong host. Mỗi client nói chuyện với chính xác một server — quan hệ 1:1 nghiêm ngặt — và gắn protocol version cùng capabilities vào mọi request nó route.
Server. Một process hoặc service phơi ra resources, tools và prompts. Nó có thể local hoặc remote, hoạt động độc lập, và toàn bộ công việc của nó là một phạm vi tập trung.6
Quy tắc “chính xác một server” đó không phải sổ sách. Nó là thứ làm cho design principle bên dưới có thể implement, và đây là câu nên lấy từ specification nếu bạn chỉ lấy một câu:
Servers should not be able to read the whole conversation, nor "see into" other servers. Servers receive only necessary contextual information. Full conversation history stays with the host. Each server maintains isolation. Cross-server interactions are controlled by the host.6
Điều đó lật ngược mental model mà hầu hết mọi người mang tới. Một weather server bạn kết nối vào assistant của mình không thấy bạn đã hỏi gì. Nó thấy một tools/call với arguments mà model đã chọn, và không gì khác — không các lượt trước, không system prompt của bạn, không kết quả mà calendar server vừa trả về. Nếu hai server cần phối hợp, host cố ý mang một giá trị từ server này sang server kia, vì model yêu cầu. Đó là lý do isolation là security property mà Chương 30 dựa vào: một server bị compromise có blast radius nhỏ, được định nghĩa rõ, và việc mở rộng nó đòi hỏi host phải hợp tác.
Điều thứ ba: ba primitive, sắp theo ai có quyền quyết định
Liên kết đến mục: Điều thứ ba: ba primitive, sắp theo ai có quyền quyết địnhLệnh đầu tiên hỏi server đó về tools và nhận được mười ba. Hỏi nó hai câu còn lại thì nó cũng trả lời: resources/list trả về bảy, prompts/list trả về bốn. Không cái nào xuất hiện, vì không có gì hỏi. Điều đó đưa ta tới xương sống sư phạm của MCP, nằm trong specification dưới dạng một bảng mà hầu như không ai trích:
| Primitive | Control | Description | Example |
|---|---|---|---|
| Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options |
| Resources | Application-controlled | Contextual data attached and managed by the client | File contents, git history |
| Tools | Model-controlled | Functions exposed to the LLM to take actions | API POST requests, file writing |
Không phải “ba cách để phơi ra một capability”. Mà là ba câu trả lời cho ai quyết định việc này xảy ra. Model quyết định gọi một tool. Ứng dụng quyết định gắn một resource. Con người quyết định chạy một prompt. Hiểu sai thì feature vẫn chạy, nhưng chạy sai thời điểm và vì sai lý do.
Cách rõ nhất để cảm nhận là một calendar. Đây là một server phơi ra cùng một calendar ba lần, mỗi lần như một primitive, trong một trăm dòng Node thuần không dependencies:
const TOOL = {
name: "create_event",
description: "Create a calendar event. Writes to the calendar.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "Event title." },
startsAt: { type: "string", format: "date-time", description: "Start, ISO 8601 UTC." },
},
required: ["title"],
},
};
switch (method) {
case "resources/read":
return ok(id, { contents: [{ uri: "calendar://week",
mimeType: "application/json", text: JSON.stringify(EVENTS) }],
ttlMs: 60000, cacheScope: "private" });
case "prompts/get":
return ok(id, { description: PROMPT.description, messages: [{ role: "user",
content: { type: "text", text: `Read calendar://week and draft a plan. ` +
`Focus: ${params.arguments?.focus ?? "balance"}.` } }] });
case "tools/list":
return ok(id, { tools: [TOOL], ttlMs: 300000, cacheScope: "public" });
}Chạy nó và hỏi theo cả ba cách. Output thật, mỗi message một dòng trên wire, được wrap lại ở đây cho trang, với request _meta và identity block của server đã lược bỏ:
→ resources/read {"uri":"calendar://week"}
← {"resultType":"complete","contents":[{"uri":"calendar://week",
"mimeType":"application/json","text":"[{\"id\":\"e1\",\"title\":\"Standup\",
\"startsAt\":\"2026-09-07T09:00:00Z\"},{\"id\":\"e2\",\"title\":\"Design review\",
\"startsAt\":\"2026-09-09T15:00:00Z\"}]"}],"ttlMs":60000,"cacheScope":"private"}
→ prompts/get {"name":"prepare_week","arguments":{"focus":"deep work"}}
← {"resultType":"complete","description":"Read the week and draft a plan.",
"messages":[{"role":"user","content":{"type":"text",
"text":"Read calendar://week and draft a plan. Focus: deep work."}}]}
→ tools/call {"name":"create_event","arguments":{"title":"Dentist",
"startsAt":"2026-09-10T08:30:00Z"}}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],
"structuredContent":{"id":"e3","title":"Dentist","startsAt":"2026-09-10T08:30:00Z"},
"isError":false}Ba method, ba shape, một calendar. Giờ là điểm chính:
Đọc tuần là một resource
Liên kết đến mục: Đọc tuần là một resourceNó được định địa chỉ bằng URI, nó inert, và ứng dụng quyết định có gắn nó vào conversation hay không. Không có gì trong protocol cho phép model tự với tới nó. Result mang ttlMs và cacheScope, mới trong revision này, để client có thể cache tuần đó trong một phút thay vì polling.
Tạo event là một tool
Liên kết đến mục: Tạo event là một toolNó có schema, có side effects, và model quyết định khi nào gọi nó. Result của nó mang isError, chính là trường mà Chương 18 đã lập luận cần có: một validation failure trở về dưới dạng tool result để model đọc và sửa, không phải protocol error.
“Prepare my week” là một prompt
Liên kết đến mục: “Prepare my week” là một promptNó là template có tên, nhận argument, mà con người invoke — slash command trong menu. Nó trả về messages, không phải một câu trả lời. Nó là cách để tác giả server ship phrasing hoạt động tốt với tool của chính họ, tức đúng loại tri thức mà tác giả server có còn user thì không.
Hầu như ai cũng biến cả ba thứ này thành tool. Kết quả là một catalogue nơi một read đáng lẽ ứng dụng phải âm thầm attach lại cạnh tranh attention của model với một write cần approval, và thứ duy nhất con người muốn có button lại bị chôn trong schema. Làm đúng không tốn gì, và được quyết định trước khi bạn viết một dòng.
Server không thể gọi bạn
Liên kết đến mục: Server không thể gọi bạnCalendar tool có một argument bắt buộc, title, và một startsAt tùy chọn. Yêu cầu nó tạo event mà không có ngày, và một thứ thú vị trả về:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"}}
← {"resultType":"input_required",
"inputRequests":{"when":{"method":"elicitation/create","params":{"mode":"form",
"message":"When should \"Dentist\" start?",
"requestedSchema":{"type":"object",
"properties":{"startsAt":{"type":"string","format":"date-time"}},
"required":["startsAt"]}}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}Server không gửi request. Nó trả lời request được giao, với resultType: "input_required" và mô tả thứ nó vẫn cần. Client thu câu trả lời từ con người, rồi gửi lại original call — với id mới, mang inputResponses và echo lại opaque requestState:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"},
"inputResponses":{"when":{"action":"accept",
"content":{"startsAt":"2026-09-10T08:30:00Z"}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],"isError":false}Đây là Multi Round-Trip Requests, được giới thiệu trong revision hiện tại, và nó thay thế thiết kế cũ nơi server gửi JSON-RPC requests ngược về clients. Transport specification giờ nêu quy tắc thẳng thừng: “servers do not initiate JSON-RPC requests and clients do not send JSON-RPC responses”.4 Chỉ có một hướng chủ động, và nó thuộc về host.
Hai feature phía client chạy trên cơ chế đó, và một cái có tên dễ làm bạn vấp.
Elicitation là server hỏi con người một thứ gì đó: một form với JSON Schema bị giới hạn có chủ ý — flat objects, primitive properties, không nesting — để client nào cũng render được mà không cần layout engine. Nó mang một quy tắc cứng: servers must not use form mode to ask for “passwords, API keys, access tokens, or payment credentials”, và must use URL mode for those, tức đưa user tới một trang mà client không bao giờ đọc.7
Sampling là server hỏi model của host để tạo generation, để server có thể thông minh mà không giữ API key. Và đây là cảnh báo về từ vựng, vì từ này đã có nghĩa khác trong khóa này: đây không phải sampling của Chương 17. Ở đây không có gì liên quan đến temperature, top-p hay hình dạng của probability distribution. Nó là một nested model call đi ngược qua protocol.
Có lý do thứ hai để không vội dùng nó: tính tới revision này, sampling đã deprecated, cùng roots và logging, theo SEP-2577, với migration gợi ý rất thẳng — “integrate directly with LLM provider APIs instead of Sampling”.8 Ý tưởng này không thất bại về kỹ thuật; nó không biện minh được surface area của mình, và một protocol có thể loại bỏ thứ không cần thiết thì lành mạnh hơn một protocol không thể.
Cố tình làm hỏng: connections không phải sessions
Liên kết đến mục: Cố tình làm hỏng: connections không phải sessionsStatelessness nghe như chi tiết wire-format cho tới khi bạn test. Lấy exchange ba message ở trên và chạy mỗi message trong một process riêng — một node calendar.mjs mới, không shared memory, không mang gì qua:
process A tools/call (no date) → resultType: input_required
requestState: eyJ0aXRsZSI6IkRlbnRpc3QifQ==
process B tools/call (with the answer, same requestState)
→ resultType: complete
"Created e3: Dentist at 2026-09-10T08:30:00Z"
process C resources/read calendar://week
→ events: 2 (Standup, Design review)Process B, vốn chưa từng thấy câu hỏi, đã hoàn tất một multi-round-trip call mà process A bắt đầu. Đó là ý nghĩa của requestState: continuation đi trong message, nên không gì phụ thuộc vào process có phải cùng một process hay không.
Process C là thất bại. Event đã được tạo và không có ở đó — vì toy server giữ EVENTS trong một module-level array, và module-level array là connection state. Note của specification gọi đúng tên lỗi này:
an open connection, such as a STDIO process, is not a conversation or session: clients may interleave unrelated requests on the same transport, and a server must not treat connection or process identity as a proxy for conversation or session continuity.2
Cách sửa được chỉ định không phải session. Đó là một explicit handle: creation tool trả về một opaque identifier, và mọi call sau đó nhận nó như argument bình thường. Protocol hoàn toàn không có khái niệm đó — “from the wire's perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls”.9 Điều này đặt model chịu trách nhiệm mang nó, và đặt server chịu trách nhiệm validate rằng caller này được phép dùng nó trên từng call, vì handle là một cái tên chứ không phải permission.
Một server tốn gì trước khi làm bất cứ thứ gì
Liên kết đến mục: Một server tốn gì trước khi làm bất cứ thứ gìMọi tool mà server phơi ra là một schema đi vào prompt của bạn trên mọi request, và Chương 24 đã đo điều đó làm gì với window. MCP thêm một line item thứ hai dễ bị bỏ lỡ, nên cả hai đều đáng đếm trên reference server ở trên.
13 tool definitions (name + description + inputSchema): 1,307 tokens
cheapest tool, get-tiny-image 52
costliest tool, gzip-file-as-resource 235
server `instructions`, returned by discovery: 312 tokens
------
one server, connected, before it is used: 1,619 tokensHai quan sát. Thứ nhất là số học: kết nối năm server cỡ này và khoảng tám nghìn token trong window của bạn đã bị chiếm trên mọi turn, mãi mãi, dù model có dùng chúng hay không — đó là cơ chế phía sau mức giảm từ 150.000 xuống 2.000 mà Chương 24 đã trích, và là lý do just-in-time tool discovery tồn tại.
Thứ hai là một security note khoác áo kế toán. instructions là natural-language text, do tác giả server viết, rơi vào prompt của host, và tool descriptions bên cạnh nó cũng vậy. Specification nói phải làm gì về việc đó trong security principles của chính nó: tool annotations and descriptions “should be considered untrusted, unless obtained from a trusted server”, và hosts “must obtain explicit user consent before invoking any tool”.1 Kết nối một MCP server không phải là thêm một dependency. Đó là cấp cho một người lạ 1.619 token trong system prompt của bạn và quyền được gọi. Chương 30 là chuyện xảy ra khi người lạ đó có ác ý.
Phần có ngày tháng: revision 2026-07-28, và nó làm hỏng gì
Liên kết đến mục: Phần có ngày tháng: revision 2026-07-28, và nó làm hỏng gìMọi thứ trong phần này đúng với protocol revision 2026-07-28, bản hiện tại, đọc ngày 7 tháng 9 năm 2026. Revisions được ghi ngày theo YYYY-MM-DD và ngày đó là lần cuối một backwards-incompatible change được thực hiện.10 Normative document là một TypeScript file, schema/2026-07-28/schema.ts; JSON Schema cạnh nó được generate từ đó, nên specification ở đây được đọc trong TypeScript và dạy MCP từ bất cứ thứ gì khác là dạy một bản dịch.
| What changed | Was | Is now | Breaks |
|---|---|---|---|
| Handshake | initialize + notifications/initialized, một lần cho mỗi connection | bị bỏ; mọi request mang version và capabilities _meta | mọi client viết trước revision này |
| Sessions | header Mcp-Session-Id, state theo phạm vi connection | bị bỏ; state đi trong explicit handles do server mint | list endpoints từng thay đổi theo connection |
| Discovery | suy ra từ result initialize | server/discover, servers must implement | không gì, nhưng giờ bắt buộc implement |
| Server-to-client calls | server gửi roots/list, sampling/createMessage, elicitation/create | InputRequiredResult và client retry | mọi server từng push request tới client |
| Result shape | object bất kỳ | bắt buộc resultType: "complete" hoặc "input_required" | không gì: trường vắng mặt phải được đọc là "complete" |
| Subscriptions | HTTP GET stream, resources/subscribe | một stream subscriptions/listen với opt-in types | GET endpoint không còn |
| Stream resumption | replay Last-Event-ID trên Streamable HTTP | bị bỏ; stream đứt làm mất request, re-issue với id mới | clients dựa vào redelivery |
| Roots | một client feature mà servers có thể hỏi | deprecated (SEP-2577); truyền paths như tool arguments hoặc resource URIs | chưa gì — cửa sổ mười hai tháng |
| Sampling and logging | client features | deprecated (SEP-2577) | chưa gì — cửa sổ mười hai tháng |
| HTTP+SSE transport | deprecated từ 2025-03-26 | Deprecated theo lifecycle policy (SEP-2596) | migrate sang Streamable HTTP |
| Client registration | OAuth 2.0 Dynamic Client Registration, RFC 7591 | deprecated để chuyển sang Client ID Metadata Documents | giữ cho authorization servers không có chúng |
| Error codes | -32002 cho resource not found | -32602; -32020–-32099 reserved cho spec | new codes -32020, -32021, -32022 |
Thay đổi governance bên dưới bảng đó quan trọng hơn bất kỳ dòng đơn lẻ nào. Revision này đã adopted một feature lifecycle and deprecation policy: features là Active, Deprecated hoặc Removed, một deprecated feature ghi migration path và ở lại specification ít nhất mười hai tháng trước khi đủ điều kiện removal, và có một registry liệt kê mọi thứ hiện ở trạng thái Deprecated.8 Trước policy đó, “deprecated” trong một AI protocol có nghĩa là bất cứ điều gì bài blog gần nhất nói. Bây giờ nó có nghĩa là một ngày.
Hiện chi tiết
Extensions, phần mà chưa ai viết nhiều đến.
Ngoài core, MCP định nghĩa các extensions tùy chọn — “always opt-in and require explicit support from both client and server”, được khai báo qua trường extensions trong capabilities của client và server.1 Ba cái đáng biết tên:
- Tasks (
io.modelcontextprotocol/tasks), được chuyển khỏi core protocol thành official extension trong revision này: thực thi bất đồng bộ các long-running operations, polling quatasks/get, input giữa chừng quatasks/update, và durable handles. Đây là câu trả lời cho một tool mất hai mươi phút, thứ Chương 23 xử lý bằng progress event và một signal chạm tới tool. - Skills over MCP, một working group làm cho agent skills — chủ đề của Chương 28 — discoverable và consumable qua protocol.
- MCP Apps, UI tương tác render inline trong conversation: charts, forms, video players.
Và lưu ý “negotiated” giờ nghĩa là gì: không có initialization để negotiate, nên extension được khai báo per request như mọi thứ khác.
MCP nằm ở đâu, so với mọi thứ nó hay bị nhầm lẫn
Liên kết đến mục: MCP nằm ở đâu, so với mọi thứ nó hay bị nhầm lẫnĐây là vocabulary của toàn bộ khối trong một chỗ.
| Nó là gì | Ai nói với ai | Khi nào nó là câu trả lời | |
|---|---|---|---|
| Một API thường | Interface cho một chương trình | code của bạn ↔ một service | Bạn đang viết caller. Bạn kiểm soát schema, auth và error handling, và không có discovery problem cần giải quyết. |
| MCP | Protocol để phơi ra tools, data và templates cho một ứng dụng AI | host ↔ server, mỗi bên một client | Người khác đã viết capability và nhiều hosts nên dùng được nó mà không cần bespoke integration. |
| RAG | Kỹ thuật tìm text và đưa nó vào prompt | code của bạn ↔ index của bạn | Model cần biết một thứ. Chương 19. MCP là một cách deliver retriever; nó không phải retriever. |
| Agent skills | Một folder có SKILL.md mà model đọc | model ↔ một document | Tri thức mang tính procedural — chúng ta làm việc này thế nào — và là prose, không phải function. Chương 28. |
| A2A | Protocol để agents cộng tác như peers | agent ↔ agent | Phía bên kia reasoning, planning và giữ state qua một task dài, thay vì trả lời một call. |
| ACP | Từng là một agent-communication protocol riêng | — | Nó không còn là phép so sánh sống nữa. Xem bên dưới. |
Hai dòng trong đó xứng đáng mỗi dòng một câu, vì đó là nơi nhầm lẫn thực sự tồn tại.
MCP so với A2A không phải một cuộc đối đầu, và cả hai specifications đều nói vậy. Documentation của A2A vạch ranh giới bằng thứ nằm ở đầu bên kia: MCP “defines how an AI agent interacts with and utilizes individual tools and resources, such as a database or an API”, trong đó tool thực hiện “specific, often stateless, functions”; A2A xử lý agents, “more autonomous systems” có thể “reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues”. Tóm tắt của chính nó là câu cần nhớ: “A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.”11 Hai cái lồng vào nhau — một ứng dụng dùng A2A để đến các agents khác, và mỗi agent dùng MCP để đến tools của chính nó. Chương 25 vạch ranh giới đó bên trong một process, giữa việc hỏi một sub-agent và giao conversation cho nó; A2A vạch ranh giới đó giữa các tổ chức.
MCP so với ACP là một phép so sánh với tiền đề đã cũ, chính vì vậy nó đáng được trả lời. Agent Communication Protocol từng là một open standard riêng cho agent-to-agent messaging. Documentation của chính nó giờ mở đầu bằng thông báo: “ACP is now part of A2A under the Linux Foundation!”12 Câu trả lời trung thực cho “MCP hay ACP?” vào tháng 9 năm 2026 là câu hỏi đó có ít hơn một lựa chọn so với các trang đang xếp hạng cho nó gợi ý.
Và phép so sánh mọi người hỏi nhiều nhất, mcp vs api, lại có câu trả lời ít thú vị nhất: MCP là một API. Thứ nó thêm không phải power, mà là conventions — một tập method names cố định, một discovery call, một control hierarchy trên các primitive, và một isolation model. Bạn từ bỏ tự do thiết kế interface của riêng mình và nhận về mọi host nói protocol đó, chính là trade-off mà mọi protocol từng đưa ra.
Tiếp theo là gì
Liên kết đến mục: Tiếp theo là gìGiờ bạn có thể đọc specification mà không cần người dịch, phân biệt resource với tool với prompt theo ai có quyền quyết định, gõ một request bằng tay khi client library nói dối bạn, và gắn ngày cho bất cứ bài viết MCP nào bạn đọc bằng deprecated features mà nó vẫn dạy như hiện hành.
Thứ bạn chưa làm là ship một cái. Chương 27 viết cùng một server hai lần — TypeScript và Python, đặt cạnh nhau, vì MCP là vùng lãnh thổ thực sự song ngữ duy nhất trong khóa này và các con số nói vậy theo cả hai hướng. Nó trình bày đúng hai live transports, inspector, packaging, và nửa protocol mà chương này cố ý để lại: authorization. Vì khoảnh khắc server của bạn là remote thay vì subprocess trên chính laptop của bạn, client của một người lạ sẽ trình một token, và quy tắc của specification về thứ bạn được làm với nó nghiêm ngặt khác thường.
Điều đó đặt ra câu hỏi mà chương tiếp theo phải trả lời, và nó không hề thân thiện: nếu một token đến server của bạn và nó được issued cho audience của người khác, chính xác thì điều gì ngăn bạn forward nó?
Nguồn và phương pháp
Liên kết đến mục: Nguồn và phương phápMọi quotation, method name, error code và rule trong chương này được đọc từ Model Context Protocol specification, revision 2026-07-28, vào 7 tháng 9 năm 2026. Mọi trace được tạo local trên Node 22: toy calendar server dài 101 dòng không dependencies, và reference server là npm package đã phát hành được nêu bên dưới. Không paid API nào được gọi để viết chương này — không thứ gì ở đây cần model, và bản thân đó chính là điểm chính.
Các phép đo: @modelcontextprotocol/server-everything@2026.8.31, phát hành ngày 31 tháng 8 năm 2026, built on @modelcontextprotocol/sdk@1.30.0, phát hành ngày 27 tháng 7 năm 2026 — một ngày trước revision mà chương này mô tả. Nó trả lời server/discover bằng -32601, negotiate 2025-11-25 khi được yêu cầu 2026-07-28, và serve tools/list mà không handshake nào. Catalogue của nó gồm 13 tools trong 7.663 bytes; token counts là o200k_base qua tiktoken, trên name, description và inputSchema của mỗi definition, tức thứ provider render vào prompt của bạn chứ không phải trọng lượng của JSON-RPC frame.
Anthropic, Code execution with MCP: building more efficient agents, 4 tháng 11 năm 2025, là nguồn của con số 150.000-xuống-2.000, được trích và dùng trong Chương 24 và chỉ được tham chiếu ở đây.
Tài liệu tham khảo
Liên kết đến mục: Tài liệu tham khảo-
Specification,
modelcontextprotocol.io/specification/latest(redirect tới/2026-07-28), đọc ngày 7 tháng 9 năm 2026. Nguồn của phép so sánh với Language Server Protocol; phát biểu rằng specification “based on the TypeScript schema inschema.ts”; tóm tắt base-protocol (“Stateless, self-contained requests”, “Per-request capability negotiation”); danh sách extension (Tasks, Skills over MCP, MCP Apps) và phát biểu rằng extensions “are always opt-in and require explicit support from both client and server”; cũng như Security và Trust & Safety principles, gồm “Hosts must obtain explicit user consent before invoking any tool” và cách xem tool annotations là untrusted. ↩ ↩2 ↩3 -
Base Protocol,
modelcontextprotocol.io/specification/2026-07-28/basic. Nguồn của các ràng buộc JSON-RPC (non-null id, không reuse id, bắt buộcresultType); phần Statelessness và note rằng một open stdio process không phải session; bảng reserved-key_metavà trạng thái required/optional của từng per-request field; quy tắc-32602cho missing required field; quy tắcMissingRequiredClientCapability(-32021); và error-code allocation policy. ↩ ↩2 ↩3 ↩4 ↩5 -
stdio transport,
modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. Nguồn của newline-delimited framing rules, yêu cầu purity củastdout, allowance củastderr, và three-outcome backward-compatibility probe — gồm warning rằng một số legacy servers xử lý era-ambiguous methods không handshake, điều mà phép đo trong chương này tái hiện. ↩ ↩2 ↩3 -
Transports overview,
modelcontextprotocol.io/specification/2026-07-28/basic/transports. Nguồn của framing “a transport is a binding” và phát biểu rằng servers không initiate JSON-RPC requests và clients không gửi JSON-RPC responses. ↩ ↩2 -
Discovery,
modelcontextprotocol.io/specification/2026-07-28/server/discover. Nguồn của trạng thái bắt buộc củaserver/discover, shape củaDiscoverResult, và trườnginstructionsđược mô tả là “optional natural-language guidance for LLMs on how to use this server effectively”. ↩ -
Architecture,
modelcontextprotocol.io/specification/2026-07-28/architecture. Nguồn của các định nghĩa host/client/server, quy tắc 1:1 client-to-server, bốn design principles, trong đó isolation principle được trích ở đây không có bullet thứ năm, “Host process enforces security boundaries”, và phần capability-negotiation. ↩ ↩2 -
Elicitation,
.../client/elicitation, và Sampling,.../client/sampling. Nguồn của hai elicitation modes và restricted schema của chúng; lệnh cấm request credentials qua form mode; sampling definition, human-in-the-loop requirement của nó, và deprecation warning gắn với nó. ↩ -
Key Changes,
modelcontextprotocol.io/specification/2026-07-28/changelog, và Feature lifecycle and deprecation policy,.../community/feature-lifecycle. Nguồn của mọi dòng trong change table: removal of sessions và headerMcp-Session-Id(SEP-2567); statelessness và removal ofinitialize(SEP-2575);server/discover(SEP-2575);subscriptions/listen(SEP-2575); Multi Round-Trip Requests vàresultType(SEP-2322); removal of stream resumability (SEP-2575); deprecation of Roots, Sampling and Logging (SEP-2577); reclassification of HTTP+SSE (SEP-2596); deprecation of Dynamic Client Registration để chuyển sang Client ID Metadata Documents; error-code renumbering; và twelve-month deprecation window. ↩ ↩2 -
Tools,
modelcontextprotocol.io/specification/2026-07-28/server/tools, và Server Features,.../server. Nguồn của control-hierarchy table được tái hiện ở trên; các shapetools/listvàtools/call; phân biệtisErrorgiữa protocol errors và tool execution errors; tool-name rules và namespace note khuyến nghị “prefixing tool names with a server identifier”; cùng guidance non-normative “Stateful Tools” về explicit handles. ↩ -
Versioning,
modelcontextprotocol.io/specification/versioning. Nguồn của schemeYYYY-MM-DD, các trạng thái revision Draft/Current/Final, xác nhận rằng 2026-07-28 là current, và per-request negotiation rules. Bảng SDK tier tạimodelcontextprotocol.io/docs/sdkliệt kê TypeScript, Python, C#, Go và Rust ở Tier 1, Java và Ruby ở Tier 2, và Swift, PHP và Kotlin ở Tier 3. ↩ -
A2A Protocol, version 1.0.0,
a2a-protocol.org— specification và trang A2A and MCP: Relationship and Distinction, đọc ngày 7 tháng 9 năm 2026. Nguồn của phân biệt tools-so-với-agents, phát biểu rằng hai protocols “address distinct but highly complementary needs”, và formulation partnering/using. ↩ -
Agent Communication Protocol,
agentcommunicationprotocol.dev, đọc ngày 7 tháng 9 năm 2026: “ACP is now part of A2A under the Linux Foundation!”, một banner được thêm phía trên một specification vẫn được serve đầy đủ — architecture, agent manifest, agent discovery, message structure, stateful agents, run lifecycle và REST endpoint list đều vẫn trả 200. Specification không biến mất; project thì có. ↩