스펙으로 읽는 MCP: 서버란 실제로 무엇인가
subprocess에 JSON 한 줄을 보내면 tool 정의 13개가 돌아옵니다. handshake를 제거한 2026-07-28 개정판 기준으로 읽습니다.
이 페이지에서
공개된 MCP 서버를 설치하고 JSON 한 줄을 보낸 다음, 무엇이 돌아오는지 읽어 봅니다.
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}standard input에서 한 줄을 읽은 프로세스가 tool 정의 13개를 한 줄로 돌려줬습니다. 이제 SDK도, client library도, framework도 없이 Model Context Protocol로 대화한 것입니다. 전부는 이것입니다. transport, 메시지 형식, 그리고 이름 붙은 작은 method 집합.
Chapter 18은 tool을 두 가지로 정의했습니다. model이 보는 JSON Schema, 그리고 model이 결코 보지 못하는 코드 안의 endpoint입니다. Chapter 23은 그것들의 catalogue를 들고 있는 harness를 만들었습니다. 하지만 둘 다 이것이 재사용 가능한지를 결정하는 질문에는 답하지 않았습니다. schema는 누가 쓰고, 그것이 작성자에게서 어떻게 prompt 안으로 들어오는가? MCP는 그 질문에 대한 하나의 답이며, 원문으로 읽을 가치가 있습니다. MCP에 대해 쓰인 거의 모든 글이 더 이상 존재하지 않는 revision을 설명하고 있기 때문입니다.
방금 실행한 command에는 세 가지가 틀렸고, 각각이 이 장의 한 절입니다. protocol version을 담지 않았으므로 conformant server라면 거부했어야 합니다. 그런데도 답을 받았는데, specification이 feature가 아니라 hazard라고 부르는 이유 때문입니다. 그리고 세 가지 primitive 중 하나를 요청했지만, 나머지 둘이 존재한다는 사실은 discover하지도 않았습니다.
해결하는 문제, 그리고 spec이 스스로 드는 비유
섹션 링크: 해결하는 문제, 그리고 spec이 스스로 드는 비유wire에 앞서 산술부터 보겠습니다. 개의 AI application과 그것들이 닿을 수 있어야 하는 개의 대상이 있습니다. calendar, ticket tracker, warehouse database, design tool 같은 것들입니다. 공유 contract가 없으면 누군가는 개의 integration을 써야 하고, 각각은 schema와 endpoint와 authentication 이야기와 maintenance 부담을 동반합니다. contract가 있으면 tool vendor가 server를 쓰고, application vendor가 client를 쓰며, 총합은 이 됩니다.
새로운 관찰은 아니며, specification은 이것이 누구의 아이디어였는지 말합니다.
MCP는 개발 tool ecosystem 전반에서 programming language 지원을 추가하는 방식을 표준화한 Language Server Protocol에서 어느 정도 영감을 받았다. 비슷한 방식으로 MCP는 AI application ecosystem에 추가 context와 tool을 integration하는 방식을 표준화한다.1
이 비교를 칭찬이 아니라 문자 그대로 받아들이십시오. 그 protocol 이전에는 editor에서 language를 지원한다는 것이 editor마다 plugin 하나를 뜻했습니다. 이후에는 language team이 server 하나를 ship했고 모든 editor가 그것을 얻었습니다. 성공의 척도는 우아함이 아니라 integration 수가 곱셈을 멈췄다는 점이었습니다. 여기서도 같은 결론이 나옵니다. 가치는 design이 아니라 implementation의 수에 있습니다. 두 product만 말하는 protocol은 의식 절차가 덧붙은 data format입니다.
wire 위에는 실제로 무엇이 있는가
섹션 링크: wire 위에는 실제로 무엇이 있는가MCP message는 JSON-RPC 2.0입니다. request는 jsonrpc, id, method와 optional params를 가진 object입니다. response는 같은 id와 result 또는 error를 담습니다. notification은 id가 없는 request이며 reply를 받지 않습니다. specification은 그 위에 세 가지 constraint를 더합니다. id는 string 또는 number여야 하며 null이면 안 되고, 아직 in flight인 다른 request와 충돌하면 안 되며, 모든 result는 resultType field를 담아야 합니다.2
위 command가 사용한 stdio transport에서는 framing rule이 message당 한 줄입니다.
Message는 newline으로 구분되며 embedded newline을 포함해서는 안 된다. […] server는 valid MCP message가 아닌 어떤 것도 자신의
stdout에 써서는 안 된다.3
마지막 조항은 직접 만든 server가 가장 흔히 깨지는 방식이며, 조용히 깨집니다. stray console.log, progress bar, dependency의 deprecation warning 하나면 client의 line parser가 JSON이 아닌 무언가를 만나게 됩니다. escape hatch는 같은 절에 있습니다. server는 원하는 것은 무엇이든 stderr에 써도 되며, client는 그것을 error로 취급하지 않아야 합니다. 위 reference server는 실행할 때마다 stderr에 Starting default (STDIO) server...를 출력하므로 pipe가 계속 작동했습니다.
다른 standard transport는 Streamable HTTP입니다. 각 message는 하나의 endpoint로 보내는 POST이고, reply는 JSON object 또는 request 범위의 Server-Sent Events stream입니다. 이 wire format은 Chapter 14에서 손으로 parsing했습니다. 의미론은 둘 모두에서 동일합니다. transport는 binding이기 때문입니다. transport는 framing과 delivery를 정의하지, meaning을 정의하지 않습니다.4
첫 번째로 틀린 것: version이 없었다
섹션 링크: 첫 번째로 틀린 것: version이 없었다위 command는 tools/list만 보냈고 그 외에는 아무것도 없었습니다. 현재 revision에서 그 request는 malformed이며, conformant server는 거부해야 합니다.
2026-07-28 이후 MCP는 stateless protocol이며, specification은 이를 얼버무리지 않고 말합니다.
Model Context Protocol(MCP)은 stateless protocol이다. request를 처리하는 데 필요한 모든 정보는 request 자체에 포함된다. server는 각 request를 독립적으로 처리한다. 같은 connection이나 stream 위의 이전 request라 해도, 이전 request에서 어떤 state도 추론해서는 안 된다.2
따라서 모든 request는 params 안의 reserved _meta object에 자신만의 protocol version과 client capabilities를 담습니다. 그 field 중 두 개는 모든 request에서 필수입니다. 둘 중 하나라도 빠진 request는 malformed이며 server는 -32602로 답해야 합니다.2
_meta key | required | 무엇인가 |
|---|---|---|
io.modelcontextprotocol/protocolVersion | yes | 이 request가 말하는 revision, 예: "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | yes | 이 request에서 client가 server를 위해 할 수 있는 것 |
io.modelcontextprotocol/clientInfo | no (but should) | 표시와 log 전용 client 이름과 version |
io.modelcontextprotocol/logLevel | no | server가 이 request에 대해 emit해야 하는 최소 log level |
풀어 쓰면 올바른 tools/list는 이렇습니다. 그리고 이 장에서 metadata를 전부 보여주는 것은 이번이 마지막입니다. 여기서부터는 모든 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가 negotiation입니다. 더 이상 별도의 negotiation step은 없습니다. client는 각 request에서 자신이 할 수 있는 일을 선언하고, server는 result에서 자신이 할 수 있는 일을 선언하며, 어느 쪽도 상대가 claim하지 않은 feature를 사용할 수 없습니다. client가 선언하지 않은 capability가 필요한 server는 -32021로 답하고 data.requiredCapabilities에 빠진 capability의 이름을 담아야 합니다. 요청된 version을 말하지 못하는 server는 -32022로 답하고 자신이 말할 수 있는 version 목록을 담아야 합니다.2
답을 미리 알고 싶은 client는 요청할 수 있습니다. server/discover는 supported versions, capabilities, identity, optional instructions block을 한 번의 round trip으로 반환하는 mandatory RPC입니다.5 호출은 optional입니다. 구현은 optional이 아닙니다.
두 번째로 틀린 것: server가 legacy였다
섹션 링크: 두 번째로 틀린 것: server가 legacy였다command는 작동했습니다. 현재 revision에서는 작동하지 말았어야 합니다. 왜 작동했는지는 한 문단보다 측정값으로 보는 편이 낫습니다. 한 줄로 ecosystem 전체의 상태를 보여주기 때문입니다.
specification이 modern client에게 probe하라고 말하는 방식으로 reference server를 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"}}이것이 compatibility rule의 세 번째 branch입니다. DiscoverResult이면 modern, 인식 가능한 modern error이면 modern-but-wrong-version, 그 밖의 무엇이든 — -32601 포함 — legacy이므로 initialize handshake로 fall back합니다.3 그러니 current revision을 요청하며 그렇게 해 봅니다.
→ {"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는 2026-07-28를 요청했고 server는 2025-11-25로 답했습니다. 2026년 9월 7일 기준, official reference server — npm package @modelcontextprotocol/server-everything, version 2026.8.31, 2026년 8월 31일 published — 는 current revision을 구현하지 않습니다. 날짜상 그것이 기반으로 삼은 TypeScript SDK도 마찬가지입니다. release 1.30.0는 2026년 7월 27일에 나왔고, 이 revision이 나온 전날입니다.
소문이 아니라 결과를 읽으십시오. MCP에 대해 쓰인 거의 모든 글은 initialize handshake, session, server가 client에게 보내는 roots/list request, HTTP+SSE transport가 있는 protocol을 설명합니다. 네 가지 모두 사라졌거나 사라지는 중입니다. MCP에 관한 어떤 글을 읽든, 이 페이지를 포함해, 가장 먼저 찾아야 할 것은 revision number입니다.
그리고 맨 처음 command가 작동한 이유는 specification에 feature가 아니라 hazard로 적혀 있습니다.
일부 legacy server는 request가
initialize이후에 도착했는지 validate하지 않으며,tools/call같은 era-ambiguous method를 legacy semantics 아래에서 처리할 수 있다. probing은 대신 deterministic failure를 만든다.3
측정값은 이렇습니다. handshake 없이 그 server에 tools/list를 보내면 full catalogue가 돌아옵니다. 거부됐어야 할 method가 제공된 것입니다. 이것이 바로 modern version만 support하더라도 먼저 server/discover로 probe하라고 specification이 말하는 이유입니다.
세 가지 역할, 그리고 문서 전체에서 인용해야 할 문장
섹션 링크: 세 가지 역할, 그리고 문서 전체에서 인용해야 할 문장MCP에는 세 party가 있으며, 사람들이 헷갈려 합쳐 버리는 구분은 앞의 둘 사이입니다.
Host. application입니다. chat product, editor, agent입니다. conversation, model, credentials, user consent를 소유합니다. client를 만들고 그들 사이의 security boundary를 enforce합니다.
Client. host 안의 connector입니다. 각 client는 정확히 하나의 server와 말합니다. 엄격한 1:1 관계입니다. 그리고 route하는 모든 request에 protocol version과 capabilities를 붙입니다.
Server. resource, tool, prompt를 노출하는 process 또는 service입니다. local일 수도 remote일 수도 있고, 독립적으로 동작하며, 전체 역할은 한 가지 집중된 영역입니다.6
그 “정확히 하나의 server” 규칙은 장부 정리가 아닙니다. 아래 design principle을 implement 가능하게 만드는 요소입니다. specification에서 딱 한 문장만 가져간다면 이 문장입니다.
Server는 전체 conversation을 읽거나 다른 server를 “들여다볼” 수 없어야 한다. server는 필요한 contextual information만 받는다. full conversation history는 host에 남는다. 각 server는 isolation을 유지한다. cross-server interaction은 host가 control한다.6
이것은 대부분의 사람이 갖고 오는 mental model을 뒤집습니다. assistant에 연결한 weather server는 당신이 무엇을 물었는지 보지 않습니다. model이 선택한 arguments가 담긴 tools/call만 볼 뿐입니다. 이전 turn도, system prompt도, 방금 calendar server가 반환한 result도 보지 못합니다. 두 server가 협력해야 한다면 host가 하나의 값을 다른 쪽으로 의도적으로 옮깁니다. model이 그렇게 요청했기 때문입니다. 그래서 isolation은 Chapter 30이 기대는 security property입니다. compromised server의 blast radius는 작고 정의되어 있으며, 그것을 키우려면 host가 협력해야 합니다.
세 번째 것: 누가 control하는지로 정렬한 세 primitive
섹션 링크: 세 번째 것: 누가 control하는지로 정렬한 세 primitive첫 command는 그 server에 tool을 요청했고 13개를 받았습니다. 나머지 두 질문도 해 보면 답합니다. resources/list는 7개를, prompts/list는 4개를 반환합니다. 아무것도 나타나지 않았던 이유는 아무것도 묻지 않았기 때문입니다. 여기서 MCP의 교육적 backbone이 나옵니다. specification 안에 있지만 거의 아무도 인용하지 않는 table입니다.
| Primitive | Control | Description | Example |
|---|---|---|---|
| Prompts | User-controlled | user choice로 호출되는 interactive template | Slash commands, menu options |
| Resources | Application-controlled | client가 attach하고 manage하는 contextual data | File contents, git history |
| Tools | Model-controlled | action을 취하기 위해 LLM에 노출되는 function | API POST requests, file writing |
“capability를 노출하는 세 가지 방법”이 아닙니다. 이 일이 일어나기로 결정하는 주체가 누구인가에 대한 세 가지 답입니다. model은 tool을 call하기로 결정합니다. application은 resource를 attach하기로 결정합니다. 사람은 prompt를 run하기로 결정합니다. 이것을 틀리게 잡아도 feature는 작동하지만, 잘못된 순간에 잘못된 이유로 작동합니다.
가장 명확하게 느끼는 방법은 calendar입니다. 같은 calendar를 각 primitive로 한 번씩, 세 번 노출하는 server입니다. dependency 없이 plain Node 100줄입니다.
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" });
}실행하고 세 방식 모두로 물어봅니다. 실제 output이며, wire에서는 line당 message 하나입니다. 여기서는 페이지를 위해 줄바꿈했고, request _meta와 server identity block은 생략했습니다.
→ 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}세 method, 세 shape, 하나의 calendar입니다. 이제 핵심입니다.
주간 일정 읽기는 resource다
섹션 링크: 주간 일정 읽기는 resource다URI로 address되고, inert하며, application이 conversation에 attach할지 결정합니다. protocol 안에는 model이 스스로 그것을 집어 올 수 있게 하는 것이 없습니다. result는 이 revision에서 새로 들어온 ttlMs와 cacheScope를 담으므로, client는 polling하는 대신 한 minute 동안 week를 cache할 수 있습니다.
event 생성은 tool이다
섹션 링크: event 생성은 tool이다schema가 있고, side effect가 있으며, model이 언제 call할지 결정합니다. result에는 isError가 들어 있습니다. Chapter 18이 주장했던 field입니다. validation failure는 protocol error가 아니라 model이 읽고 고칠 수 있는 tool result로 돌아옵니다.
“내 주간 일정 준비”는 prompt다
섹션 링크: “내 주간 일정 준비”는 prompt다이것은 이름이 있고 argument를 받는 template이며 사람이 호출합니다. menu 안의 slash command입니다. answer가 아니라 messages를 반환합니다. server author가 자신의 tool과 잘 맞는 phrasing을 ship하는 방식입니다. 바로 그 지식은 server author에게 있고 user에게는 없습니다.
거의 모두가 이 세 가지를 전부 tool로 만듭니다. 그러면 application이 조용히 attach했어야 할 read가 approval이 필요한 write와 model의 attention을 두고 경쟁하는 catalogue가 되고, 사람이 button을 원했던 단 하나는 schema 안에 묻힙니다. 올바르게 하는 데 비용은 들지 않으며, 코드 한 줄을 쓰기 전에 결정됩니다.
server는 당신을 call할 수 없다
섹션 링크: server는 당신을 call할 수 없다calendar tool에는 required argument title와 optional startsAt가 있습니다. date 없이 event를 만들라고 요청하면 흥미로운 것이 돌아옵니다.
→ 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는 request를 보내지 않았습니다. 주어진 request에 답했습니다. resultType: "input_required"와 여전히 필요한 것의 description을 담아서입니다. client는 사람에게서 answer를 수집한 뒤 original call을 다시 보냅니다. 새 id를 사용하고, inputResponses를 담고, opaque requestState를 그대로 echo합니다.
→ 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}이것이 current revision에서 도입된 Multi Round-Trip Requests입니다. 그리고 server가 JSON-RPC request를 client에게 되돌려 보내던 older design을 대체했습니다. transport specification은 이제 규칙을 분명히 말합니다. “server는 JSON-RPC request를 initiate하지 않으며 client는 JSON-RPC response를 보내지 않는다”.4 initiative의 방향은 하나뿐이며, 그것은 host에 속합니다.
두 가지 client-side feature가 그 mechanism을 타고 오며, 그중 하나의 이름은 당신을 헷갈리게 할 것입니다.
Elicitation은 server가 사람에게 무언가를 요청하는 것입니다. 일부러 제한된 JSON Schema를 가진 form입니다. flat object, primitive property, nesting 없음. 그래서 어떤 client도 layout engine 없이 render할 수 있습니다. 여기에는 강한 규칙이 붙습니다. server는 “passwords, API keys, access tokens, or payment credentials”를 요청하기 위해 form mode를 사용해서는 안 되며, 그런 경우에는 URL mode를 사용해야 합니다. URL mode는 client가 결코 읽지 않는 페이지로 user를 보냅니다.7
Sampling은 server가 host의 model에게 generation을 요청하는 것입니다. server가 API key를 갖지 않고도 intelligent할 수 있게 하기 위해서입니다. 여기서 vocabulary warning이 필요합니다. 이 단어는 이 course에서 이미 다른 뜻으로 쓰였기 때문입니다. 이것은 Chapter 17의 sampling이 아닙니다. 여기에는 temperature, top-p, probability distribution의 shape에 관한 것이 없습니다. protocol을 거슬러 올라가는 nested model call입니다.
그것에 손대지 말아야 할 두 번째 이유도 있습니다. 이 revision 기준으로 sampling은 deprecated입니다. roots와 logging도 SEP-2577 아래에서 함께 deprecated이며, blunt한 migration 제안이 붙어 있습니다. “Sampling 대신 LLM provider API와 직접 integrate하라”.8 아이디어가 기술적으로 실패한 것은 아닙니다. 표면적을 정당화하지 못한 것입니다. 그리고 무언가를 제거할 수 있는 protocol은 제거할 수 없는 protocol보다 건강합니다.
일부러 깨뜨리기: connection은 session이 아니다
섹션 링크: 일부러 깨뜨리기: connection은 session이 아니다Statelessness는 테스트해 보기 전까지는 wire-format detail처럼 들립니다. 위의 세-message exchange를 가져와 각 message를 별도 process에서 실행해 봅니다. fresh node calendar.mjs, shared memory 없음, 넘겨지는 것 없음입니다.
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가 Process A가 시작한 multi-round-trip call을 완료했습니다. 이것이 requestState의 요점입니다. continuation은 message 안에서 이동하므로, process가 같은지에 의존하는 것이 없습니다.
Process C는 failure입니다. event는 생성되었지만 거기에 없습니다. toy server가 EVENTS를 module-level array에 보관하고, module-level array는 connection state이기 때문입니다. specification의 note는 그 실수를 정확히 이름 붙입니다.
STDIO process 같은 open connection은 conversation이나 session이 아니다. client는 같은 transport 위에서 unrelated request를 interleave할 수 있으며, server는 connection이나 process identity를 conversation 또는 session continuity의 proxy로 취급해서는 안 된다.2
처방된 fix는 session이 아닙니다. explicit handle입니다. creation tool은 opaque identifier를 반환하고, 이후 모든 call은 그것을 ordinary argument로 받습니다. protocol에는 그것에 대한 concept가 전혀 없습니다. “wire의 관점에서 handle은 tool result 안의 ordinary string이고 이후 tool call에 들어가는 ordinary argument”입니다.9 그러면 model이 그것을 들고 다닐 책임을 지고, server는 매 call마다 이 caller가 그것을 사용할 수 있는지 validate할 책임을 집니다. handle은 이름이지 permission이 아니기 때문입니다.
server가 아무것도 하기 전에 치르는 비용
섹션 링크: server가 아무것도 하기 전에 치르는 비용server가 노출하는 모든 tool은 매 request마다 prompt에 들어가는 schema이며, Chapter 24는 그것이 window에 어떤 영향을 주는지 측정했습니다. MCP는 놓치기 쉬운 두 번째 line item을 더하므로, 위 reference server에서 둘 다 세어볼 가치가 있습니다.
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 tokens두 가지 관찰이 있습니다. 첫째는 산술입니다. 이 정도 크기의 server 다섯 개를 연결하면 model이 그중 무엇을 쓰든 말든 매 turn마다 window의 대략 8천 token이 영구히 예약됩니다. 이것이 Chapter 24가 인용한 150,000에서 2,000으로의 감소를 만든 mechanism이며, just-in-time tool discovery가 존재하는 이유입니다.
둘째는 accounting costume을 입은 security note입니다. instructions는 server author가 쓴 natural-language text이며 host의 prompt에 들어갑니다. 옆의 tool description도 마찬가지입니다. specification은 security principle에서 이에 대해 무엇을 해야 하는지 말합니다. tool annotation과 description은 “trusted server에서 얻은 것이 아니라면 untrusted로 간주해야” 하며, host는 “어떤 tool을 invoke하기 전에 명시적 user consent를 얻어야” 합니다.1 MCP server를 연결하는 것은 dependency를 추가하는 일이 아닙니다. 낯선 사람에게 system prompt의 1,619 token과 call될 권리를 부여하는 일입니다. Chapter 30은 그 낯선 사람이 hostile할 때 벌어지는 일입니다.
날짜가 붙은 절: 2026-07-28 revision, 그리고 그것이 깨뜨리는 것
섹션 링크: 날짜가 붙은 절: 2026-07-28 revision, 그리고 그것이 깨뜨리는 것이 절의 모든 내용은 protocol revision 2026-07-28에 대해 참입니다. 2026년 9월 7일에 읽은 current revision입니다. revision은 YYYY-MM-DD 형식의 날짜를 가지며, 그 날짜는 backwards-incompatible change가 마지막으로 이루어진 시점입니다.10 normative document는 TypeScript file인 schema/2026-07-28/schema.ts입니다. 옆의 JSON Schema는 그것에서 generate되므로, 여기서 specification을 TypeScript로 읽는 이유이자 다른 것으로 MCP를 가르치는 것이 translation을 가르치는 이유입니다.
| What changed | Was | Is now | Breaks |
|---|---|---|---|
| The handshake | connection당 한 번, initialize + notifications/initialized | 제거됨. 모든 request가 _meta version과 capabilities를 담음 | 이 revision 이전에 작성된 모든 client |
| Sessions | Mcp-Session-Id header, connection-scoped state | 제거됨. state는 explicit, server-minted handle 안에서 이동 | connection마다 달라지던 list endpoint |
| Discovery | initialize result에서 inferred | server/discover, server가 반드시 implement해야 함 | 없음. 하지만 이제 implement가 mandatory |
| Server-to-client calls | server가 roots/list, sampling/createMessage, elicitation/create를 보냄 | InputRequiredResult와 client retry | client에게 request를 push하던 모든 server |
| Result shape | any object | required resultType: "complete" 또는 "input_required" | 없음. absent field는 "complete"로 읽어야 함 |
| Subscriptions | HTTP GET stream, resources/subscribe | opt-in type이 있는 하나의 subscriptions/listen stream | GET endpoint가 사라짐 |
| Stream resumption | Streamable HTTP에서 Last-Event-ID replay | 제거됨. broken stream은 request를 잃으며, 새 id로 다시 issue | redelivery에 의존하던 client |
| Roots | server가 요청할 수 있던 client feature | deprecated (SEP-2577). path를 tool argument 또는 resource URI로 전달 | 아직 없음 — 12개월 window |
| Sampling and logging | client features | deprecated (SEP-2577) | 아직 없음 — 12개월 window |
| HTTP+SSE transport | 2025-03-26부터 deprecated | lifecycle policy 아래 Deprecated (SEP-2596) | Streamable HTTP로 migrate |
| Client registration | OAuth 2.0 Dynamic Client Registration, RFC 7591 | Client ID Metadata Documents를 선호하며 deprecated | 그것이 없는 authorization server를 위해 유지 |
| Error codes | resource not found에 -32002 | -32602. -32020–-32099는 spec을 위해 reserved | new codes -32020, -32021, -32022 |
그 table 아래의 governance change는 어떤 단일 row보다 중요합니다. 이 revision은 feature lifecycle and deprecation policy를 채택했습니다. feature는 Active, Deprecated 또는 Removed이고, deprecated feature는 migration path를 문서화하며 제거 eligible이 되기 전 최소 12개월 동안 specification에 남습니다. 그리고 현재 Deprecated state에 있는 모든 것을 나열하는 registry가 있습니다.8 그 policy 이전에 AI protocol에서 “deprecated”는 마지막 blog post가 말한 무엇이든을 뜻했습니다. 이제는 날짜를 뜻합니다.
세부 정보 보기
Extensions, 아직 아무도 제대로 쓰지 않은 부분입니다.
core 너머에서 MCP는 optional extensions를 정의합니다. “항상 opt-in이며 client와 server 양쪽의 명시적 support가 필요”하고, client와 server의 capabilities 안 extensions field를 통해 선언됩니다.1 이름으로 알아둘 만한 것이 세 가지 있습니다.
- Tasks (
io.modelcontextprotocol/tasks). 이 revision에서 core protocol 밖의 official extension으로 옮겨졌습니다. long-running operation의 asynchronous execution,tasks/get를 통한 polling,tasks/update를 통한 mid-flight input, durable handle을 제공합니다. 20분 걸리는 tool에 대한 답입니다. Chapter 23은 progress event와 tool에 닿는 signal로 처리했습니다. - Skills over MCP. agent skill — Chapter 28의 주제 — 을 protocol을 통해 discover하고 consume할 수 있게 만드는 working group입니다.
- MCP Apps. conversation 안에 inline으로 render되는 interactive UI입니다. chart, form, video player 같은 것들입니다.
그리고 지금 “negotiated”가 무엇을 뜻하는지 주목하십시오. negotiate할 initialization이 없으므로 extension도 다른 모든 것처럼 request마다 선언됩니다.
MCP가 자주 혼동되는 것들 사이에서 어디에 놓이는가
섹션 링크: MCP가 자주 혼동되는 것들 사이에서 어디에 놓이는가이 block 전체의 vocabulary를 한곳에 모아 봅니다.
| What it is | Who talks to whom | When it is the answer | |
|---|---|---|---|
| A plain API | program을 위한 interface | your code ↔ a service | caller를 직접 쓰고 있을 때. schema, auth, error handling을 control하며 해결해야 할 discovery 문제가 없습니다. |
| MCP | tool, data, template을 AI application에 노출하는 protocol | host ↔ server, 각각 client 하나 | 다른 누군가가 capability를 작성했고 많은 host가 bespoke integration 없이 그것을 사용할 수 있어야 할 때. |
| RAG | text를 찾고 prompt에 넣는 technique | your code ↔ your index | model이 무언가를 알아야 할 때. Chapter 19. MCP는 retriever를 deliver하는 방법일 수 있지만 retriever 자체는 아닙니다. |
| Agent skills | model이 읽는 SKILL.md가 들어 있는 folder | model ↔ a document | knowledge가 procedural할 때 — 우리는 이것을 어떻게 하는가 — 그리고 function이 아니라 prose일 때. Chapter 28. |
| A2A | agent가 peer로 협업하기 위한 protocol | agent ↔ agent | 반대편이 call에 답하는 것이 아니라 reason하고, plan하고, 긴 task 전반에서 state를 유지할 때. |
| ACP | 별도의 agent-communication protocol이었음 | — | 더 이상 live comparison이 아닙니다. 아래를 보십시오. |
그중 둘은 각각 한 문장이 필요합니다. 실제 혼동이 거기에 있기 때문입니다.
MCP against A2A는 rivalry가 아니며, 두 specification 모두 그렇게 말합니다. A2A documentation은 반대편에 무엇이 있는지로 선을 긋습니다. MCP는 “AI agent가 database나 API 같은 개별 tool과 resource와 상호작용하고 활용하는 방식”을 정의하며, tool은 “specific, often stateless, functions”를 수행합니다. A2A는 “more autonomous systems”인 agent를 다루며, 그들은 “reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues”를 합니다. 자체 summary에서 기억할 문장은 이것입니다. “A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.”11 둘은 중첩됩니다. application은 A2A로 다른 agent에 도달하고, 각 agent는 MCP로 자신의 tool에 도달합니다. Chapter 25는 하나의 process 안에서 sub-agent에게 묻는 것과 conversation을 handoff하는 것 사이에 그 선을 그었습니다. A2A는 조직 사이에 그 선을 긋습니다.
MCP against ACP는 stale premise와의 비교이며, 바로 그래서 답할 가치가 있습니다. Agent Communication Protocol은 agent-to-agent messaging을 위한 별도 open standard였습니다. 지금 그 documentation은 이렇게 시작합니다. “ACP is now part of A2A under the Linux Foundation!”12 2026년 9월에 “MCP or ACP?”라는 질문에 대한 정직한 답은, 그 질문이 검색 상위 페이지들이 암시하는 것보다 option이 하나 적다는 것입니다.
그리고 사람들이 가장 많이 요구하는 비교인 mcp vs api는 가장 재미없는 답을 갖습니다. MCP is an API. 그것이 더하는 것은 power가 아니라 conventions입니다. 고정된 method name 집합, discovery call, primitive에 대한 control hierarchy, isolation model입니다. 자신만의 interface를 design할 freedom을 포기하는 대신 protocol을 말하는 모든 host를 얻습니다. 모든 protocol이 언제나 제안해 온 trade입니다.
다음은 어디로 가는가
섹션 링크: 다음은 어디로 가는가이제 translator 없이 specification을 읽을 수 있고, 누가 control하는지로 resource와 tool과 prompt를 구분할 수 있으며, client library가 거짓말할 때 request를 손으로 입력할 수 있고, deprecated feature 중 무엇을 current로 가르치는지로 읽는 MCP article의 날짜를 추정할 수 있습니다.
아직 하지 않은 일은 하나를 ship하는 것입니다. Chapter 27은 같은 server를 두 번 씁니다. TypeScript와 Python을 나란히 씁니다. MCP는 이 course에서 정말로 bilingual한 영역이고, 숫자도 양방향으로 그렇게 말하기 때문입니다. 두 live transport, inspector, packaging, 그리고 이 장이 의도적으로 남겨 둔 protocol의 절반인 authorization을 제대로 다룹니다. server가 당신의 laptop 위 subprocess가 아니라 remote가 되는 순간, 낯선 사람의 client가 token을 제시할 것이고, 그것으로 무엇을 해도 되는지에 대한 specification의 규칙은 유난히 엄격하기 때문입니다.
그러면 다음 장이 답해야 할 질문이 생깁니다. 결코 친절한 질문은 아닙니다. token이 server에 도착했는데 그것이 다른 audience를 위해 issued된 것이라면, 정확히 무엇이 당신이 그것을 forwarding하지 못하게 막습니까?
Sources and method
섹션 링크: Sources and method이 장의 모든 quotation, method name, error code, rule은 2026년 9월 7일에 Model Context Protocol specification revision 2026-07-28에서 읽었습니다. 모든 trace는 Node 22에서 local로 만들었습니다. toy calendar server는 dependency 없이 101줄이고, reference server는 아래 이름의 published npm package입니다. 이 장을 쓰는 데 paid API는 호출하지 않았습니다. 여기에는 model이 필요하지 않으며, 그것 자체가 요점입니다.
측정값: @modelcontextprotocol/server-everything@2026.8.31, 2026년 8월 31일 published, @modelcontextprotocol/sdk@1.30.0 기반, 2026년 7월 27일 published — 이 장이 설명하는 revision 하루 전입니다. 그것은 server/discover에 -32601로 답하고, 2026-07-28를 요청하면 2025-11-25로 negotiate하며, handshake 없이도 tools/list를 제공합니다. catalogue는 13개 tool, 7,663 bytes입니다. token count는 각 definition의 name, description, inputSchema에 대해 tiktoken를 통해 o200k_base로 계산했습니다. 이는 provider가 prompt에 render하는 것이지 JSON-RPC frame의 무게가 아닙니다.
Anthropic, Code execution with MCP: building more efficient agents, 2025년 11월 4일은 150,000-to-2,000 figure의 출처입니다. Chapter 24에서 인용하고 사용했으며 여기서는 reference만 합니다.
-
Specification,
modelcontextprotocol.io/specification/latest(/2026-07-28로 redirect), 2026년 9월 7일 읽음. Language Server Protocol 비교의 출처. specification이 “schema.ts의 TypeScript schema를 기반으로 한다”는 statement의 출처. base-protocol summary(“Stateless, self-contained requests”, “Per-request capability negotiation”), extension list(Tasks, Skills over MCP, MCP Apps)와 extension이 “항상 opt-in이며 client와 server 양쪽의 explicit support가 필요하다”는 statement의 출처. 그리고 “Hosts must obtain explicit user consent before invoking any tool”과 tool annotation을 untrusted로 다루는 내용을 포함한 Security 및 Trust & Safety principle의 출처. ↩ ↩2 ↩3 -
Base Protocol,
modelcontextprotocol.io/specification/2026-07-28/basic. JSON-RPC constraint(non-null id, id reuse 금지, requiredresultType), Statelessness section과 open stdio process가 session이 아니라는 note,_metareserved-key table 및 각 per-request field의 required/optional status, missing required field에 대한-32602rule,MissingRequiredClientCapability(-32021) rule, error-code allocation policy의 출처. ↩ ↩2 ↩3 ↩4 ↩5 -
stdio transport,
modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. newline-delimited framing rule,stdoutpurity requirement,stderrallowance, 세 outcome의 backward-compatibility probe의 출처. 일부 legacy server가 handshake 없이 era-ambiguous method를 처리한다는 warning도 포함하며, 이 장의 measurement가 그것을 재현합니다. ↩ ↩2 ↩3 -
Transports overview,
modelcontextprotocol.io/specification/2026-07-28/basic/transports. “transport는 binding”이라는 framing과 server가 JSON-RPC request를 initiate하지 않고 client가 JSON-RPC response를 보내지 않는다는 statement의 출처. ↩ ↩2 -
Discovery,
modelcontextprotocol.io/specification/2026-07-28/server/discover.server/discover의 mandatory status,DiscoverResult의 shape, 그리고instructionsfield가 “이 server를 효과적으로 사용하는 방법에 대한 LLM용 optional natural-language guidance”라고 described된 부분의 출처. ↩ -
Architecture,
modelcontextprotocol.io/specification/2026-07-28/architecture. host/client/server definition, 1:1 client-to-server rule, 네 가지 design principle의 출처. 그중 isolation principle은 다섯 번째 bullet인 “Host process enforces security boundaries”를 제외하고 여기서 인용했습니다. capability-negotiation section의 출처이기도 합니다. ↩ ↩2 -
Elicitation,
.../client/elicitation, 및 Sampling,.../client/sampling. 두 elicitation mode와 그 restricted schema, form mode로 credential을 요청하는 것에 대한 prohibition, sampling definition과 human-in-the-loop requirement, 그리고 attached deprecation warning의 출처. ↩ -
Key Changes,
modelcontextprotocol.io/specification/2026-07-28/changelog, 및 Feature lifecycle and deprecation policy,.../community/feature-lifecycle. change table의 모든 row 출처. session과Mcp-Session-Idheader 제거(SEP-2567), statelessness와initialize제거(SEP-2575),server/discover(SEP-2575),subscriptions/listen(SEP-2575), Multi Round-Trip Requests와resultType(SEP-2322), stream resumability 제거(SEP-2575), Roots/Sampling/Logging deprecation(SEP-2577), HTTP+SSE reclassification(SEP-2596), Client ID Metadata Documents를 선호하는 Dynamic Client Registration deprecation, error-code renumbering, 12개월 deprecation window의 출처. ↩ ↩2 -
Tools,
modelcontextprotocol.io/specification/2026-07-28/server/tools, 및 Server Features,.../server. 위에 재현한 control-hierarchy table,tools/list와tools/callshape, protocol error와 tool execution error의isErrordistinction, tool-name rule 및 “server identifier로 tool name에 prefix를 붙이라”고 권장하는 namespace note, explicit handle에 관한 non-normative “Stateful Tools” guidance의 출처. ↩ -
Versioning,
modelcontextprotocol.io/specification/versioning.YYYY-MM-DDscheme, Draft/Current/Final revision state, 2026-07-28이 current라는 confirmation, per-request negotiation rule의 출처.modelcontextprotocol.io/docs/sdk의 SDK tier table은 TypeScript, Python, C#, Go, Rust를 Tier 1로, Java와 Ruby를 Tier 2로, Swift, PHP, Kotlin을 Tier 3로 나열합니다. ↩ -
A2A Protocol, version 1.0.0,
a2a-protocol.org— specification 및 A2A and MCP: Relationship and Distinction page, 2026년 9월 7일 읽음. tools-against-agents distinction, 두 protocol이 “distinct but highly complementary needs”를 address한다는 statement, partnering/using formulation의 출처. ↩ -
Agent Communication Protocol,
agentcommunicationprotocol.dev, 2026년 9월 7일 읽음. “ACP is now part of A2A under the Linux Foundation!”라는 banner의 출처입니다. 이 banner는 여전히 전체가 제공되는 specification 위에 추가되었습니다. architecture, agent manifest, agent discovery, message structure, stateful agents, run lifecycle, REST endpoint list는 모두 여전히 200으로 응답합니다. specification은 사라지지 않았고 project가 사라졌습니다. ↩