콘텐츠로 이동

9. 프로토콜 계층

“"9. 프로토콜 계층"” 챕터 컨셉 일러스트

한 줄 정의. 프로토콜 계층은 에이전트가 도구·데이터·다른 에이전트에 표준화된 방식으로 연결되도록 하는 약속이며, 그 중심에 “AI 앱용 USB-C”라 불리는 MCP가 있다.

MCP 클라이언트와 서버가 호스트 안에서 1대1로 연결되고, 로컬은 stdio, 원격은 Streamable HTTP로 이어지는 구조

왜 중요한가: MCP의 정체성과 USB-C 비유

섹션 제목: “왜 중요한가: MCP의 정체성과 USB-C 비유”

MCP(Model Context Protocol)의 공식 정의는 “AI 애플리케이션을 외부 시스템에 연결하기 위한 오픈소스 표준(open-source standard for connecting AI applications to external systems)“입니다1. 이 한 문장에 두 가지 설계 의도가 들어 있습니다. 첫째는 오픈소스 표준이라는 점이고, 둘째는 연결의 주체가 모델이 아니라 AI 애플리케이션이라는 점입니다.

MCP 공식 도입 문서는 이를 USB-C에 빗댑니다. 정확한 문구는 다음과 같습니다 — “MCP를 AI 애플리케이션을 위한 USB-C 포트라고 생각하라. USB-C가 전자 기기를 연결하는 표준화된 방식을 제공하듯, MCP는 AI 애플리케이션을 외부 시스템에 연결하는 표준화된 방식을 제공한다(Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.)”1. 비유의 핵심은 “꽂는 쪽과 꽂히는 쪽이 서로의 내부 사정을 몰라도 같은 모양의 포트만 맞으면 동작한다”는 데 있습니다.

MCP가 실제로 연결해 주는 대상은 세 종류입니다 — 로컬 파일이나 데이터베이스 같은 데이터 소스, 검색엔진·계산기 같은 도구, 그리고 특정 작업에 특화된 워크플로(프롬프트)입니다1. 중요한 한정이 하나 더 있습니다. MCP는 프로토콜에만 관여하며, “AI 애플리케이션이 LLM을 어떻게 사용하는지, 혹은 제공된 컨텍스트를 어떻게 관리하는지는 규정하지 않습니다(does not dictate how AI applications use LLMs or manage the provided context)”2. 즉 MCP는 배선 규격이지 두뇌의 사용법이 아닙니다.

이 경계 덕분에 같은 서버를 Claude Code가 쓰든 다른 호스트가 쓰든, 컨텍스트 관리 전략이 달라도 프로토콜은 동일하게 유지됩니다.

프로젝트로서의 MCP 범위도 알아 둘 필요가 있습니다. MCP는 구현 요구사항을 담은 스펙, 언어별 SDK, MCP Inspector 같은 개발 도구, 그리고 modelcontextprotocol/servers의 레퍼런스 서버 구현까지를 포함합니다2. 스펙만 던지는 표준이 아니라 검증 도구와 참조 구현을 함께 제공한다는 점이 채택 속도를 끌어올린 요인입니다.

이 장은 스킬이 “에이전트 안에 능력을 담는 법”이었다면, 그 능력을 에이전트 밖의 세계와 잇는 배선을 다룹니다. 도구 호출을 표준 채널로 노출하는 도구 연결(MCP), 에이전트 간 위임(A2A), 기존 API 재활용(OpenAPI), 그리고 그 모든 연결을 감싸는 인증 경계가 여기에 속합니다.

참여자: 호스트·클라이언트·서버와 1:1 전용 연결

섹션 제목: “참여자: 호스트·클라이언트·서버와 1:1 전용 연결”

MCP는 세 참여자로 구성됩니다2.

  • 호스트(Host): 하나 이상의 MCP 클라이언트를 조율·관리하는 AI 애플리케이션입니다. Claude Code, Claude Desktop, VS Code가 호스트입니다.
  • 클라이언트(Client): 서버 하나당 하나씩 만들어지는 컴포넌트로, 해당 서버와의 연결을 유지하며 컨텍스트를 받아 호스트에 전달합니다.
  • 서버(Server): 컨텍스트를 제공하는 프로그램입니다. 로컬에서 실행되든 원격에서 실행되든 모두 “MCP server”라 부릅니다 — 명칭은 실행 위치와 무관합니다2.

핵심 설계는 1:1 전용 연결입니다. 호스트는 “MCP 서버마다 하나의 MCP 클라이언트를 생성(creating one MCP client for each MCP server)“하고, 각 클라이언트는 자기 서버하고만 dedicated connection을 유지합니다2.

공식 예시를 그대로 따라가 보면 — VS Code가 호스트로서 Sentry 서버에 연결하면 클라이언트 객체를 하나 인스턴스화하고, 이어 filesystem 서버에 연결하면 또 다른 클라이언트 객체를 인스턴스화합니다2. 이 1:1 규칙 덕분에 클라이언트마다 협상된 capability·프로토콜 버전·연결 상태가 깔끔하게 격리됩니다.

두 계층: 데이터 계층과 전송 계층

섹션 제목: “두 계층: 데이터 계층과 전송 계층”

MCP는 두 계층으로 나뉩니다2. 안쪽이 데이터 계층, 바깥쪽이 전송 계층입니다.

데이터 계층(inner)은 JSON-RPC 2.0 기반의 교환 프로토콜로, 메시지의 구조와 의미를 정의합니다. 여기에는 생명주기 관리, 서버 기능(tools/resources/prompts), 클라이언트 기능(sampling/elicitation/logging), 그리고 유틸리티 기능(notifications, progress tracking)이 포함됩니다2.

전송 계층(outer)은 통신 채널, 연결 수립, 메시지 프레이밍, 그리고 인증(authorization)을 담당합니다2. 전송 세부를 추상화하기 때문에, 어떤 전송을 쓰든 데이터 계층은 동일한 JSON-RPC 2.0 메시지 포맷을 그대로 사용합니다. 이 분리가 곧 “stdio든 HTTP든 같은 메시지가 통한다”를 가능하게 하는 구조적 이유입니다.

MCP는 stateful protocol입니다 — 생명주기 관리가 필요하기 때문입니다2. 다만 Streamable HTTP 전송을 쓰면 일부 subset을 stateless로 만들 수 있습니다2. 상태가 있다는 것은 곧 연결을 열고 닫는 명시적 절차(다음 절의 핸드셰이크)가 있다는 뜻이고, stateless 옵션은 무상태 HTTP 인프라(로드밸런서 뒤의 수평 확장 등)에 얹기 위한 탈출구입니다.

생명주기: initialize 핸드셰이크와 capability 협상

섹션 제목: “생명주기: initialize 핸드셰이크와 capability 협상”

stateful 프로토콜이므로 연결은 핸드셰이크로 시작합니다. 클라이언트가 initialize 요청을 보내며, 핵심 필드는 protocolVersion(예: 2025-06-18), capabilities, clientInfo{name, version}입니다2. 서버는 자신의 protocolVersion, capabilities(예: tools:{listChanged:true}, resources:{}), serverInfo{name, version}로 응답합니다2.

// 클라이언트 → 서버: initialize 요청
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": { "elicitation": {} },
"clientInfo": { "name": "example-client", "version": "1.0.0" }
}
}
// 서버 → 클라이언트: initialize 응답
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": true },
"resources": {}
},
"serverInfo": { "name": "example-server", "version": "1.0.0" }
}
}
// 클라이언트 → 서버: 준비 완료 알림 (응답 없음)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

protocolVersion은 양쪽이 호환 가능한 버전을 협상하는 데 쓰입니다. 규칙은 엄격합니다 — “상호 호환 버전이 협상되지 않으면 연결을 종료해야 한다(connection should be terminated)”2. 현행 스펙 버전 문자열은 2025-06-18이며, 인가 스펙과 아키텍처 예시 모두 같은 값을 씁니다23.

capability 협상이 단순한 형식 교환이 아니라는 점이 중요합니다. 각 capability는 구체적 동작을 선언합니다.

서버의 "tools":{"listChanged":true}는 두 가지를 동시에 뜻합니다 — tools 프리미티브를 지원한다는 것, 그리고 도구 목록이 바뀌면 tools/list_changed 알림을 보낼 수 있다는 것입니다2. 클라이언트의 "elicitation":{}는 자신이 elicitation/create 호출을 받을 수 있음을 선언합니다2. 즉 핸드셰이크가 끝난 시점에 양쪽은 “상대가 무엇을 할 수 있고 어떤 알림을 보낼 수 있는지”를 이미 알고 있게 됩니다.

프리미티브와 메서드 이름: 발견 후 실행

섹션 제목: “프리미티브와 메서드 이름: 발견 후 실행”

프리미티브는 MCP에서 가장 중요한 개념으로, 클라이언트와 서버가 서로에게 무엇을 제공할 수 있는지를 정의합니다.

서버가 노출하는 프리미티브는 세 가지입니다2.

프리미티브역할발견조회/실행
ToolsAI가 호출해 행동을 수행하는 실행 함수tools/listtools/call
Resources컨텍스트를 제공하는 데이터 소스resources/listresources/read
Prompts상호작용을 구조화하는 템플릿prompts/listprompts/get

메서드 명명 규칙은 발견 = */list, 조회/검색 = */get, 일부 실행 = tools/call입니다2.

반대로 클라이언트도 세 가지 프리미티브를 노출하며, 각각 정확한 메서드명이 있습니다2.

  • Sampling = sampling/createMessage: 서버가 호스트의 LLM에게 완성을 요청합니다. 이 덕분에 서버는 자체 LLM SDK 없이도 모델에 접근할 수 있고, 모델에 독립적으로 동작합니다.
  • Elicitation = elicitation/create: 서버가 사용자에게 추가 정보나 확인을 요청합니다.
  • Logging: 서버가 클라이언트로 로그를 전송합니다.

Tool 객체의 메타데이터 필드도 자세히 정의되어 있습니다2name(서버 네임스페이스 내에서 고유한 식별자), title(사람이 읽는 표시명), description, 그리고 inputSchema(JSON Schema, required 배열 포함)입니다. name은 명확한 패턴을 권장합니다.

예컨대 calculator_arithmetic처럼 도메인을 드러내는 이름이 좋고, 단순한 calculate는 지양됩니다2 — 한 호스트가 여러 서버를 동시에 물릴 때 이름 충돌과 모델의 오선택을 줄이기 위해서입니다.

도구 호출의 응답은 content 배열로 돌아오며, 각 객체에 type 필드(예: "text")가 붙어 텍스트·이미지·리소스 등 멀티포맷을 담습니다2.

// 1) 도구 목록 발견
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
// 2) 도구 호출 — 발견한 정확한 name과 inputSchema에 맞춘 인자
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": { "location": "San Francisco", "units": "imperial" }
}
}
// 3) 서버 응답 — content 배열로 결과 반환
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{ "type": "text", "text": "Current weather in San Francisco: 68°F, partly cloudy" }
]
}
}

이 교환의 핵심 리듬이 발견 후 실행입니다. */list가 반환하는 목록은 동적으로 바뀔 수 있으므로2, 클라이언트는 먼저 tools/list로 무엇이 있는지 알아낸 뒤에야 호출합니다.

그리고 tools/callname은 발견 응답의 name과 정확히 일치해야 합니다2 — 부분 일치나 별칭이 통하지 않습니다. 목록이 동적이라는 사실과 정확 일치 요구가 합쳐지면, 클라이언트가 캐싱한 옛 도구 이름으로 호출했다가 실패하는 엣지케이스가 생깁니다.

다음 절의 알림 메커니즘이 바로 이 문제를 막습니다.

전송 계층은 두 가지 표준 방식을 정의합니다2.

stdio는 같은 머신의 로컬 프로세스 간에 표준 입출력 스트림을 사용합니다. 네트워크 스택을 거치지 않으므로 오버헤드가 없고, 공식 문서는 이를 최적 성능(optimal performance)으로 표현합니다2. 호스트가 서버 프로세스를 직접 띄우고 그 stdin/stdout으로 JSON-RPC 메시지를 주고받는 형태라, filesystem 서버처럼 로컬 리소스에 붙는 경우에 적합합니다.

Streamable HTTP는 클라이언트→서버 메시지에 HTTP POST를 쓰고, 선택적으로 Server-Sent Events(SSE)를 얹어 스트리밍합니다2. 원격 서버 통신을 가능하게 하며, 베어러 토큰·API 키·커스텀 헤더 같은 표준 HTTP 인증을 지원합니다2. 인증 토큰을 획득하는 방법으로는 OAuth 사용을 권장합니다(“MCP recommends using OAuth”)2.

두 전송이 공존할 수 있는 이유는 다시 한 번 전송 계층의 추상화 덕분입니다 — 통신 세부를 감추므로 동일한 JSON-RPC 2.0 포맷이 양쪽 모두에서 동작합니다2. 서버 작성자는 메시지 처리 로직을 한 번만 짜고 전송만 바꿔 끼울 수 있습니다.

알림(notification)은 id가 없는 JSON-RPC 2.0 메시지로, 응답을 기대하지 않습니다2. 요청-응답 쌍이 아니라 단방향 통보이기 때문에, 서버가 클라이언트에게 “상황이 바뀌었다”고 능동적으로 알리는 용도에 쓰입니다.

대표적인 알림이 notifications/tools/list_changed입니다. 서버의 도구 목록이 바뀌면 발신되는데, capability에서 listChanged:true를 선언한 서버만 보낼 수 있습니다2. 핸드셰이크에서 선언하지 않았다면 클라이언트는 이 알림을 기대하지도 않습니다.

// 서버 → 클라이언트: 도구 목록이 변경되었다는 알림 (id 없음)
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

클라이언트의 전형적 반응은 이 알림을 받은 즉시 tools/list를 다시 요청해 목록을 갱신하는 것입니다2. 이 패턴의 이점은 폴링이 불필요하다는 데 있습니다 — 클라이언트가 주기적으로 목록을 확인할 필요 없이, 변경이 있을 때만 통보받아 갱신합니다. 앞 절에서 짚은 “캐싱한 옛 이름으로 호출 실패” 엣지케이스를 이 메커니즘이 닫아 줍니다.

같은 패턴은 tools에 국한되지 않고 resources·prompts 등 다른 프리미티브로도 확장됩니다2. 그리고 초기화 완료를 알리는 notifications/initialized도 같은 알림 메커니즘의 일부입니다2.

A2A: 정의·거버넌스·MCP와의 상보 관계

섹션 제목: “A2A: 정의·거버넌스·MCP와의 상보 관계”

MCP가 “에이전트와 도구”를 잇는다면, A2A(Agent2Agent)는 “에이전트와 에이전트”를 잇습니다. 두 프로토콜은 경쟁이 아니라 상보적입니다 — MCP는 “에이전트가 자신의 도구·API·리소스에 연결하는 방식을 표준화(standardizes how an agent connects to its tools, APIs, and resources)“하고, A2A는 독립적인 에이전트들이 “서로를 발견하고, 작업을 위임하고, 결과를 공유(discover each other, delegate tasks, and share results)“하게 합니다4. 이렇게 여러 에이전트를 엮어 일을 나누는 방식은 오케스트레이션 패턴에서 더 깊이 다룹니다.

거버넌스 측면에서 A2A는 원래 Google이 만들어 Linux Foundation에 이관했습니다4. Technical Steering Committee는 AWS, Cisco, Google, IBM(IBM Research), Microsoft, Salesforce, SAP, ServiceNow로 구성됩니다4 — SAP와 ServiceNow까지 포함된 점이 종종 누락됩니다. 라이선스는 Apache License 2.0, 스펙 버전은 1.0입니다4.

역할 정의는 다음과 같습니다5 — A2A Client(Client Agent)는 사용자를 대신해 행동하는 앱·서비스·에이전트이고, A2A Server(Remote Agent)는 A2A 프로토콜을 구현한 HTTP 엔드포인트를 노출하는 에이전트(또는 에이전트 시스템)입니다.

A2A의 핵심 설계 원칙은 불투명성(opacity)입니다. 정확한 문구는 “에이전트들은 내부 메모리·도구·독점 로직을 공유할 필요 없이 상호작용하며, 이는 보안을 보장하고 지식재산을 보호한다(Agents interact without needing to share internal memory, tools, or proprietary logic, ensuring security and preserving intellectual property.)”입니다4. 즉 상대 에이전트를 블랙박스로 다룹니다.

이것이 MCP의 도구 연결과 본질적으로 다른 지점입니다 — MCP 서버는 자신의 도구 스키마를 투명하게 노출하지만, A2A의 상대 에이전트는 내부를 감춘 채 능력만 광고합니다.

A2A 핵심 객체와 메서드·전송·태스크 상태

섹션 제목: “A2A 핵심 객체와 메서드·전송·태스크 상태”

A2A에서 최초 발견의 출발점은 Agent Card입니다. 정체성·능력·엔드포인트·skills·인증 요구사항을 담은 JSON 메타데이터로, well-known 경로는 /.well-known/agent-card.json입니다56. 클라이언트는 이 경로를 조회해 상대 에이전트가 무엇을 할 수 있고 어디로 어떻게 요청해야 하는지를 알아냅니다.

주요 객체는 다음과 같습니다5.

  • Task: 고유 ID와 정의된 생명주기를 가진 stateful 작업 단위.
  • Message: role('user' 또는 'agent')과 content를 가진 단일 대화 턴.
  • Part: text / file(URL 또는 inline bytes) / structured data 중 하나를 담는 콘텐츠 컨테이너로, 필드는 text, raw, url, data입니다.
  • Artifact: 작업의 산출물로, artifactId·name·여러 Part를 포함합니다.

전송 바인딩은 세 가지입니다 — JSON-RPC 2.0, gRPC, HTTP+JSON/REST6. 통신은 HTTP(S) 위에서 이루어지며 JSON-RPC 2.0을 페이로드 포맷으로 씁니다5. MCP가 JSON-RPC 단일 데이터 계층인 것과 달리, A2A는 세 바인딩을 허용해 gRPC 기반 인프라나 순수 REST 클라이언트도 수용합니다.

v1.0 스펙의 메서드 매핑 기준으로, 추상 오퍼레이션마다 바인딩별 메서드명이 대응합니다6. JSON-RPC와 gRPC는 다음 메서드를 씁니다6.

  • SendMessage
  • SendStreamingMessage
  • GetTask
  • ListTasks
  • CancelTask
  • SubscribeToTask
  • CreateTaskPushNotificationConfig
  • GetTaskPushNotificationConfig
  • ListTaskPushNotificationConfigs
  • DeleteTaskPushNotificationConfig
  • GetExtendedAgentCard

REST 바인딩은 같은 오퍼레이션을 POST /message:send, POST /message:stream, GET /tasks/{id} 같은 엔드포인트로 노출합니다6. 스트리밍은 Server-Sent Events(SSE)로, 비동기 알림은 Push Notifications(웹훅)로 처리합니다6. 장기 실행 태스크에서 SSE 연결이 끊겨도, 클라이언트가 등록해 둔 push notification 웹훅으로 완료를 통보받을 수 있다는 뜻입니다.

OpenAPI 도구·커넥터·MCP 레지스트리

섹션 제목: “OpenAPI 도구·커넥터·MCP 레지스트리”

기존 REST API에는 이미 OpenAPI라는 표준 서술이 있습니다. 에이전트 플랫폼은 이 명세를 읽어 각 엔드포인트를 도구로 변환하므로, 회사가 이미 가진 API를 새 프로토콜로 다시 감싸지 않고도 에이전트에 붙일 수 있습니다. “커넥터”는 외부 서비스를 에이전트에 연결하는 사전 포장된 통합을 가리키는 말로 쓰이며, MCP 서버가 그 한 형태입니다.

연결할 서버가 늘면 “어디에 무엇이 있는가”를 찾는 문제가 생깁니다. 이를 위해 공식 MCP 레지스트리가 있습니다.

정의는 “MCP 서버를 위한 커뮤니티 주도 레지스트리 서비스(community driven registry service for MCP servers)“로, 서버 메타데이터를 보관하고 발견을 돕는 플랫폼입니다7. 공식 레지스트리는 하위 레지스트리·마켓플레이스가 소비하는 단일 소스 역할을 하며, 메타데이터와 게시자 네임스페이스 검증·인증을 일괄 처리합니다7.

인증과 네임스페이스 검증 방식은 네 가지입니다7 — GitHub OAuth(직접 로그인), GitHub OIDC(GitHub Actions에서 게시), DNS 검증, HTTP 검증. 네임스페이스 포맷은 다음과 같이 정확히 정해져 있습니다7.

  • io.github.{username}/server-name — 해당 GitHub 사용자 로그인이 필요합니다.
  • {domain}/{namespace}/server-name — DNS 또는 HTTP로 도메인 소유를 검증해야 합니다.

게시 워크플로는 mcp-publisher CLI로 진행합니다7make publisher로 빌드하고, GitHub/OIDC/도메인 검증으로 인증한 뒤, server.json을 데이터 모델 대비 검증해 제출합니다.

원격 연결에는 신뢰 경계가 필요합니다. 이 경계는 안전장치가 다루는 권한·신뢰 모델과 직접 맞닿아 있습니다.

MCP 인가는 OPTIONAL입니다3. 다만 전송에 따라 권고 수위가 갈립니다 — HTTP 기반 전송은 이 스펙을 SHOULD 준수하고, STDIO 전송은 이 스펙을 SHOULD NOT 따르며 대신 “환경에서 자격증명을 취득(retrieve credentials from the environment)“합니다3. 로컬 stdio 서버에 OAuth를 강제하지 않는 이유는, 같은 머신의 프로세스 신뢰 경계 안에서는 환경 변수로 자격증명을 넘기는 편이 단순하고 안전하기 때문입니다.

역할 매핑은 OAuth 2.1 용어로 명확히 정의됩니다3 — 보호된 MCP 서버는 OAuth 2.1 resource server, MCP 클라이언트는 OAuth 2.1 client(리소스 소유자를 대신해 요청), authorization server는 토큰을 발급합니다. authorization server는 리소스 서버와 동거할 수도, 별개 엔티티일 수도 있습니다3.

기반 표준은 정확한 RFC 번호로 못 박혀 있습니다3.

표준RFC요건
OAuth 2.1draft-ietf-oauth-v2-1-13기반 프레임워크
Authorization Server MetadataRFC 8414인가서버 MUST 제공, 클라이언트 MUST 사용
Dynamic Client Registration (DCR)RFC 7591SHOULD (MUST 아님)
Protected Resource MetadataRFC 9728서버 MUST 구현, 클라이언트 MUST 사용

MUST 요건을 풀어 보면 — 서버는 RFC 9728(Protected Resource Metadata)을 구현해야 하고, 클라이언트는 이를 인가서버 발견에 써야 합니다. 서버 메타데이터의 authorization_servers 필드에는 최소 1개의 인가서버가 포함되어야 합니다.

인가서버는 RFC 8414를 제공해야 하고 클라이언트는 이를 사용해야 합니다3. 반면 DCR(RFC 7591)은 SHOULD입니다 — 미지원 인가서버에 대해서는 클라이언트 ID를 하드코딩하거나 사용자 UI 입력으로 대체할 수 있습니다3.

인가 흐름: 401·WWW-Authenticate·PKCE·resource 파라미터

섹션 제목: “인가 흐름: 401·WWW-Authenticate·PKCE·resource 파라미터”

전체 흐름은 다음 순서로 진행됩니다3.

  1. 토큰 없는 요청 → 서버가 HTTP 401 Unauthorized + WWW-Authenticate 헤더를 돌려줍니다(RFC 9728 §5.1, resource_metadata URL 지시).
  2. 클라이언트가 GET /.well-known/oauth-protected-resource로 리소스 메타데이터를 가져옵니다.
  3. 리소스 메타데이터에서 인가서버 URL을 추출하고 GET /.well-known/oauth-authorization-server를 조회합니다.
  4. OAuth 2.1 흐름을 거쳐 토큰을 받습니다.
  5. Authorization: Bearer <token> 헤더로 원래 요청을 재시도합니다.

이 흐름에는 여러 MUST 규칙이 박혀 있습니다.

PKCE는 MUST입니다(OAuth 2.1 §7.5.2)3. 인가 코드 탈취·주입 공격을 막기 위해 verifier-challenge 쌍을 씁니다 — 인가 코드가 중간에 가로채여도 verifier 없이는 토큰으로 교환할 수 없습니다.

resource 파라미터(RFC 8707, Resource Indicators)도 MUST입니다3. 클라이언트는 이를 구현해야 하고, “인가 요청과 토큰 요청 양쪽 모두에 포함”해야 하며, MCP 서버를 식별해야 하고, 서버의 canonical URI를 써야 합니다. 인가서버의 지원 여부와 무관하게 항상 전송해야 합니다3. 이 파라미터가 곧 “이 토큰은 어느 서버를 위한 것인가”를 못 박는 장치입니다.

에러 코드도 표준화되어 있습니다3 — 401 Unauthorized(인가 필요 또는 토큰 무효), 403 Forbidden(스코프 부족 또는 권한 부족), 400 Bad Request(잘못된 인가 요청). 무효·만료 토큰은 401로 응답해야 합니다(MUST). 통신 보안 측면에서는 모든 인가서버 엔드포인트가 HTTPS여야 하고(MUST), 리다이렉트 URI는 localhost 또는 HTTPS여야 하며(MUST), 공개 클라이언트는 refresh token을 회전해야 합니다(MUST)3.

토큰 대상 검증·패스스루 금지·혼동된 대리인

섹션 제목: “토큰 대상 검증·패스스루 금지·혼동된 대리인”

OAuth 흐름에서 보안의 무게중심은 토큰의 대상(audience) 검증에 있습니다.

서버는 접근 토큰이 “자신을 대상으로(intended audience) 발급되었는지” 검증해야 합니다(RFC 8707 §2). 검증에 실패하면 401로 응답해야 합니다(MUST)3. 동시에 클라이언트는 MCP 서버의 인가서버가 발급한 토큰 외에는 그 서버로 보내서는 안 되며(MUST NOT), 서버 역시 다른 토큰을 수락하거나 중계해서는 안 됩니다(MUST NOT)3.

여기서 명시적으로 금지되는 것이 토큰 패스스루입니다. MCP 서버가 상류 API를 호출할 때는 자신이 OAuth 클라이언트로서 별개의 토큰을 써야 하며, “MCP 클라이언트로부터 받은 토큰을 그대로 패스스루하면 안 됩니다(MUST NOT)”3.

마지막으로 토큰 도난에 대비해, 인가서버는 short-lived 토큰을 발급하는 것이 SHOULD이며, 안전한 토큰 저장은 MUST입니다3. 수명이 짧은 토큰은 탈취되더라도 악용 가능한 시간 창이 좁아진다는 점에서, 위 검증 규칙들과 함께 다층 방어를 이룹니다.

  • MCP의 세 참여자(호스트·클라이언트·서버)와 1:1 전용 연결, 그리고 원격 서버는 N:1로 받는다는 비대칭을 설명할 수 있다.
  • 데이터/전송 두 계층, initialize 핸드셰이크와 capability 협상, notifications/initialized·notifications/tools/list_changed의 역할을 안다.
  • 서버 프리미티브 3종과 정확 메서드(tools/call·resources/read·prompts/get), “발견 후 정확 일치 호출” 패턴을 안다.
  • A2A의 상보 관계·불투명성 원칙·Task 생명주기 상태와, MCP 레지스트리 네임스페이스 포맷을 안다.
  • OAuth 2.1 인가 흐름(401·WWW-Authenticate·PKCE·resource 파라미터)과 audience 검증·토큰 패스스루 금지·confused deputy 방어를 설명할 수 있다.
  1. MCP — What is the Model Context Protocol (intro) 2 3 4

  2. MCP — Architecture overview 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

  3. MCP Specification 2025-06-18 — Authorization (OAuth 2.1) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

  4. A2A Protocol — Home 2 3 4 5

  5. A2A Protocol — Key Concepts 2 3 4

  6. A2A Protocol — Specification 2 3 4 5 6 7

  7. Official MCP Registry (modelcontextprotocol/registry) 2 3 4 5 6 7