A2A Protocol 입문: AgentCard, Task, Part로 이해하는 에이전트 간 통신
목차
A2A(Agent-to-Agent) Protocol — Google 주도, 에이전트 간 통신 표준.
MCP가 에이전트의 “손”이라면, A2A는 에이전트의 “입”이다.

AI 에이전트가 하나일 때는 문제가 없다. 사용자 요청을 받고, 도구를 호출하고, 결과를 돌려준다. 하지만 에이전트가 여러 개가 되는 순간 새로운 문제가 생긴다. 데이터 분석 에이전트가 고객 응대 에이전트에게 “이 고객의 최근 문의 내역을 알려줘”라고 요청하려면, 둘이 이해하는 공통 언어가 필요하다. 특히 두 에이전트가 서로 다른 프레임워크로 만들어졌다면, 내부 구현과 무관하게 통신할 수 있는 표준 규약이 필수다.
MCP(Model Context Protocol)는 이 문제를 해결하지 못한다. MCP는 에이전트가 외부 도구(API, DB, 파일시스템)를 호출하는 표준이지, 에이전트끼리 대화하는 표준이 아니다. A2A(Agent-to-Agent) Protocol은 바로 이 빈자리를 채운다. Google이 2025년 4월에 초안을 발표했고, 현재 v1.0까지 도달한 상태다.
이 글에서는 A2A의 핵심 개념과 동작 방식을 정리한다.
이 글의 코드 예시는 A2A v0.3 기준이다. 2026년 3월 v1.0 릴리스로 Part 구조 평탄화, AgentCard
supportedInterfaces[]재설계, 멀티 바인딩(gRPC/HTTP/JSON-RPC) 지원 등 상당한 변경이 있었다. 개념은 동일하므로 이 글로 기초를 잡고, v1.0 변경사항과 마이그레이션 가이드는 다음 글에서 확인하자.
MCP와 A2A: 역할이 다르다
멀티 에이전트 시스템을 처음 접하면 “MCP로 다 되는 거 아닌가?”라는 의문이 생길 수 있다. 둘은 해결하는 문제가 다르다.
MCP(Model Context Protocol)는 에이전트가 외부 도구를 호출하는 인터페이스다. “이 API를 호출해줘”, “이 파일을 읽어줘” 같은 도구 사용을 표준화한다.
A2A(Agent-to-Agent Protocol)는 에이전트끼리 대화하는 인터페이스다. “이 작업을 처리해줘”, “진행 상황을 알려줘” 같은 에이전트 간 협업을 표준화한다.
flowchart LR
subgraph 에이전트 A
A[분석 Agent]
end
subgraph 도구
T1[Database]
T2[Search API]
end
subgraph 에이전트 B
B[응대 Agent]
end
A -->|MCP| T1
A -->|MCP| T2
A <-->|A2A| BMCP로 연결된 도구는 지능이 없다. 호출하면 결과를 돌려줄 뿐이다. 반면 A2A로 연결된 상대는 또 다른 에이전트다. 자체적으로 판단하고, 추가 질문을 하고, 비동기적으로 작업을 진행한다. 이 차이가 프로토콜 설계에 그대로 반영된다.
그렇다면 “MCP 위에 래퍼를 씌우면 에이전트 간 호출도 되지 않나?”라는 의문이 자연스럽다. 기술적으로 가능하다. MCP 도구로 다른 에이전트의 API를 호출하는 패턴이다. 하지만 A2A가 해결하는 문제는 단순 호출이 아니다.
| 관점 | MCP 래퍼 우회 | A2A |
|---|---|---|
| Discovery | 수동으로 URL 관리 | AgentCard + .well-known 자동 발견 |
| 상태 관리 | 없음 (fire-and-forget) | Task 생명주기 (submitted→working→completed) |
| 중간 결과 | 폴링 직접 구현 | SSE 스트리밍 내장 |
| 추가 질문 | 불가 | input-required 상태로 대화형 협업 |
| 표준화 | 각자 구현 | 프로토콜 표준, SDK 제공 |
실제로 MCP tool로 다른 에이전트의 API를 래핑하는 혼종 패턴은 이미 널리 쓰인다. 에이전트 수가 적고 같은 팀이 관리한다면 이 방식이 더 단순할 수 있다. 하지만 에이전트 수가 늘어나거나 외부 파트너와 연동해야 할 때, Task 생명주기 관리, 인증 위임, 다자간 비동기 협업을 MCP 래퍼 위에 직접 구현하면 사실상 A2A를 재발명하게 된다.
기존 gRPC/OpenAPI + SSE 조합으로도 유사한 구조를 만들 수 있다. 차이는 표준화에 있다. A2A는 AgentCard 기반 Discovery, Task 상태 모델, Part 메시지 구조를 프로토콜 수준에서 정의해서, 서로 다른 팀이 만든 에이전트가 별도 협의 없이 상호운용할 수 있다.
A2A의 핵심 개념
A2A는 네 가지 핵심 개념으로 구성된다.
AgentCard: 에이전트의 명함
AgentCard는 에이전트가 자신을 소개하는 JSON 문서다. “나는 누구이고, 무엇을 할 수 있으며, 어디로 요청을 보내면 된다”는 정보를 담고 있다. 아래는 v0.3 기준의 예시다(v1.0에서 구조가 크게 바뀌었는데, 이는 다음 글에서 다룬다).
{
"name": "고객 분석 에이전트",
"description": "고객 행동 데이터를 분석하고 인사이트를 제공합니다",
"url": "https://agent.example.com/a2a",
"protocolVersion": "0.3.0",
"capabilities": {
"streaming": true
},
"skills": [
{
"id": "customer-analysis",
"name": "고객 분석",
"description": "고객 세그먼트별 행동 패턴 분석",
"tags": ["analytics", "customer"],
"inputModes": ["text/plain"],
"outputModes": ["text/plain", "application/json"]
}
]
}클라이언트는 .well-known/agent-card.json 경로로 에이전트의 AgentCard를 가져온다. 이것이 A2A의 Discovery 메커니즘이다. 에이전트를 호출하기 전에 먼저 명함을 확인하고, 원하는 skill이 있는지, 어떤 입출력을 지원하는지를 파악한 뒤 요청을 보낸다.
Task: 작업의 생명주기
A2A에서 모든 작업은 Task로 관리된다. 클라이언트가 메시지를 보내면 서버가 Task를 생성하고, Task는 상태를 거치며 진행된다.
stateDiagram-v2
[*] --> submitted: 클라이언트 요청
submitted --> working: 에이전트 처리 시작
working --> completed: 결과 반환
working --> input_required: 추가 정보 필요
input_required --> working: 클라이언트 응답
working --> failed: 오류 발생
working --> canceled: 클라이언트 취소input-required 상태가 A2A의 특징적인 부분이다. 에이전트가 작업 중간에 “추가 정보가 필요합니다”라고 요청할 수 있다. 단순한 요청-응답이 아니라 대화형 협업이 가능한 구조다.
Task에는 고유 ID가 부여되고, 클라이언트는 이 ID로 상태를 조회하거나(tasks/get), 취소를 요청할 수 있다(tasks/cancel).
Message와 Part: 메시지의 구조
에이전트 간에 주고받는 메시지는 Message 객체로 표현되고, 각 Message는 하나 이상의 Part를 포함한다.
{
"role": "user",
"parts": [
{"kind": "text", "text": "최근 30일 고객 이탈률을 분석해줘"},
{"kind": "data", "data": {"segment": "premium", "period": 30}}
]
}Part는 세 가지 타입이 있다.
| Part 타입 | 용도 | 예시 |
|---|---|---|
TextPart | 자연어 텍스트 | 질문, 답변, 설명 |
FilePart | 파일 참조 | URL 기반 이미지, 문서 |
DataPart | 구조화 데이터 | JSON 페이로드, 분석 결과 |
텍스트만 주고받는 것이 아니라, 구조화된 데이터와 파일을 함께 전달할 수 있다. 분석 에이전트가 차트 이미지와 JSON 데이터를 동시에 반환하는 식이다.
Artifact: 작업의 결과물
에이전트가 Task를 처리하면서 생성한 결과물은 Artifact로 반환된다. Artifact도 Part의 배열로 구성된다.
{
"artifactId": "analysis-result-001",
"name": "고객 이탈 분석 결과",
"parts": [
{"kind": "text", "text": "프리미엄 세그먼트의 30일 이탈률은 4.2%입니다..."},
{"kind": "data", "data": {"churn_rate": 0.042, "trend": "decreasing"}}
]
}A2A 통신 흐름
실제로 A2A 통신이 어떻게 이루어지는지 전체 흐름을 살펴보자.
sequenceDiagram
participant C as Client Agent
participant S as Server Agent
C->>S: GET /.well-known/agent-card.json
S-->>C: AgentCard (skills, capabilities)
C->>S: POST /a2a (message/send)
Note right of S: Task 생성 (submitted → working)
S-->>C: SSE: TaskStatusUpdate (working)
S-->>C: SSE: TaskArtifactUpdate (중간 결과)
S-->>C: SSE: TaskStatusUpdate (completed)- 클라이언트가 서버의 AgentCard를 가져와 어떤 skill을 지원하는지 확인한다
- JSON-RPC(JSON 형식으로 원격 함수를 호출하는 프로토콜) 형식으로
message/send요청을 보낸다 - 서버는 SSE(Server-Sent Events, 서버가 클라이언트에게 이벤트를 단방향으로 푸시하는 HTTP 기술)로 진행 상황을 스트리밍한다
- Task가 완료되면 Artifact와 함께 최종 상태를 반환한다
JSON-RPC 요청의 실제 모습은 이렇다.
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "최근 30일 고객 이탈률을 분석해줘"}]
},
"metadata": {
"conversationId": "conv-abc123"
}
}
}Python SDK로 최소 구현해보기
A2A를 직접 확인하는 가장 빠른 방법은 Python SDK로 클라이언트를 만들어보는 것이다.
import httpx
import json
# 1. AgentCard 가져오기 (Discovery)
agent_url = "https://agent.example.com"
card = httpx.get(f"{agent_url}/.well-known/agent-card.json").json()
print(f"에이전트: {card['name']}, skills: {[s['id'] for s in card['skills']]}")
# 2. message/send (JSON-RPC 요청)
request = {
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "최근 30일 분석해줘"}]
}
}
}
# 3. SSE 스트리밍 수신
with httpx.stream("POST", f"{agent_url}/a2a", json=request) as response:
for line in response.iter_lines():
if line.startswith("data: "):
event = json.loads(line[6:])
if "kind" in event:
print(f"[{event['kind']}] {event.get('state', '')}")SDK를 사용하면 이 과정이 더 간결해진다. pip install a2a-sdk로 설치하면 AgentCard 파싱, Task 관리, SSE 이벤트 처리를 자동으로 해준다.
A2A 도입 판단 기준
모든 상황에서 A2A가 필요한 것은 아니다. 도입 전에 다음 질문을 순서대로 확인하자.
- 에이전트 간 비동기 Task 위임이 필요한가? → 아니라면 MCP 또는 직접 API 호출로 충분하다
- MCP tool call로 해결 가능한가? → 에이전트 수가 적고 같은 팀이 관리한다면, MCP 래핑이 더 단순하다
- 멀티 벤더 상호운용이나 표준 Discovery가 필요한가? → 이 시점부터 A2A의 가치가 드러난다
기존 메시지 버스(Kafka, NATS)나 단순 REST+큐 조합으로 해결 가능한지도 먼저 따져야 한다. A2A는 에이전트 고유의 문제(Discovery, Task 생명주기, 대화형 협업)를 해결하는 데 강점이 있지만, 범용 메시징 인프라를 대체하는 것은 아니다.
도입 시 고려해야 할 비용
- 에이전트 수가 늘면 장애 전파 경로도 늘어난다. 운영 환경에서는 Task ID를 분산 트레이싱의 correlation ID로 활용하는 것을 권장한다
- A2A 엣지 서버를 운영하면 유지보수 대상이 하나 더 생긴다
- 프로토콜 버전 업데이트 시 클라이언트/서버 동시 마이그레이션이 필요할 수 있다
- A2A는 현재 Google이 주도하는 사양이며, W3C나 IETF 같은 중립 표준 기구로의 이관 여부가 생태계 확산의 변수다
정리
- MCP는 도구 호출, A2A는 에이전트 간 대화. 둘은 보완 관계이지 경쟁이 아니다
- AgentCard로 발견, Task로 상태 관리, Part로 메시지 구성. 이 세 가지가 A2A의 핵심이다
- MCP 래퍼로 우회 가능하지만, Discovery, Task 생명주기, 대화형 협업은 A2A가 프로토콜 수준에서 해결한다
- Python/TypeScript SDK가 제공되어, 대부분의 프로토콜 처리를 직접 구현할 필요가 없다
A2A는 현재 v1.0까지 릴리스되었고, 0.3에서 1.0으로 넘어가면서 상당한 규모의 breaking change가 있었다. 다음 글에서는 v1.0에서 무엇이 바뀌었는지, 그리고 0.3 구현체를 어떻게 전환하는지를 다룬다.