지난 글에서 MCP 서버를 Railway에 올렸다.
"파일 정리 에이전트"가 작업을 끝내고, 그 결과를 "슬랙 알림 에이전트"한테 넘기고 싶다.
근데 방법이 없다. 에이전트끼리 대화하는 표준이 없다.
MCP는 AI ↔ 도구 사이의 프로토콜이다.
그럼 AI ↔ AI 사이는를 A2A라고 부른다.
A2A란
A2A(Agent-to-Agent)는 Google이 2025년 4월에 공개한 오픈 프로토콜이다.
AI 에이전트끼리 서로를 발견하고, 작업을 위임하고, 결과를 주고받기 위한 표준이다.
쉽게 말하면:
MCP = AI가 도구(DB, API, 파일)를 쓰는 방법
A2A = AI가 다른 AI에게 일을 시키는 방법
| 구분 | MCP | A2A |
|---|---|---|
| 통신 대상 | 도구 (DB, API, 파일시스템) | 다른 에이전트 |
| 방향 | 클라이언트 → 서버 | 에이전트 ↔ 에이전트 |
| 주체 | Anthropic | Google (오픈소스) |
| 역할 | AI의 손 | AI의 동료 |
| 현재 상태 | 표준화됨 | 초안 공개, 빠르게 확산 중 |
둘은 경쟁이 아니다. 같이 쓰는 거다.
핵심 개념 3가지
1. Agent Card
에이전트가 자기 자신을 소개하는 명함이다./.well-known/agent.json 경로에 노출된다.
# agent.json
{
"name": "FileOrganizerAgent",
"description": "파일을 분석하고 폴더 구조를 제안하는 에이전트",
"url": "https://file-agent.up.railway.app",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"skills": [
{
"id": "scan_folder",
"name": "폴더 스캔",
"description": "지정된 경로의 파일 목록을 반환합니다",
"inputModes": ["text"],
"outputModes": ["text", "data"]
},
{
"id": "organize_files",
"name": "파일 정리",
"description": "AI가 폴더 구조를 제안하고 적용합니다",
"inputModes": ["text"],
"outputModes": ["text"]
}
]
}
다른 에이전트가 이 카드를 읽고 "이 에이전트한테 뭘 시킬 수 있겠구나"를 파악한다.
2. Task
에이전트 간 작업 단위다. 상태가 있다.
submitted → working → completed
→ failed
→ input-required ← 사람 개입 필요
작업 하나가 오래 걸릴 수 있기 때문에 비동기로 처리된다.
3. Message & Part
에이전트끼리 주고받는 메시지 형식이다.
{
"role": "user",
"parts": [
{ "type": "text", "text": "다운로드 폴더 정리해줘" },
{ "type": "data", "data": { "path": "/Users/me/Downloads" } }
]
}
텍스트, 데이터, 파일을 섞어서 보낼 수 있다.
A2A 서버 만들기 (Python)
from a2a.server import A2AServer, AgentCard, AgentSkill
from a2a.types import Task, Message
# Agent Card 정의
card = AgentCard(
name="FileOrganizerAgent",
description="파일 정리 에이전트",
url="https://file-agent.up.railway.app",
skills=[
AgentSkill(
id="organize_files",
name="파일 정리",
description="폴더를 스캔하고 AI로 분류합니다"
)
]
)
server = A2AServer(agent_card=card)
@server.on_task("organize_files")
async def handle_organize(task: Task) -> Task:
path = task.message.data.get("path", "")
# 실제 파일 정리 로직 (MCP 서버 호출 또는 직접 처리)
result = await organize_directory(path)
task.status = "completed"
task.artifacts = [{"type": "data", "data": result}]
return task
server.run(host="0.0.0.0", port=8001)
다른 에이전트에서 호출하기
from a2a.client import A2AClient
# 에이전트 발견
client = A2AClient("https://file-agent.up.railway.app")
card = await client.get_agent_card()
print(card.skills) # 이 에이전트가 뭘 할 수 있는지 확인
# 작업 위임
task = await client.send_task(
skill_id="organize_files",
message={
"role": "user",
"parts": [
{"type": "text", "text": "다운로드 폴더 정리해줘"},
{"type": "data", "data": {"path": "/Users/me/Downloads"}}
]
}
)
# 결과 대기
result = await client.wait_for_completion(task.id)
print(result.artifacts)
코드가 MCP랑 비슷해 보이지만 방향이 다르다.
MCP는 Claude가 도구를 쓰는 것이고, A2A는 에이전트가 에이전트를 쓰는 것이다.
MCP + A2A 같이 쓰면
지금까지 만든 것들을 연결하면 이런 그림이 나온다.
사용자
│
▼
오케스트레이터 에이전트 (Claude)
├── MCP → sales-db 서버 (DB 조회)
├── MCP → file-organizer 서버 (파일 정리)
│
└── A2A → 슬랙 알림 에이전트 ──── MCP → Slack API
→ 리포트 생성 에이전트 ── MCP → PDF 서버
Claude는 MCP로 도구를 쓰고, A2A로 전문화된 에이전트들에게 작업을 위임한다.
각 에이전트는 자기 영역에서 또 MCP 서버를 쓴다.
이게 "멀티에이전트 시스템"이다.
A2A는 아직 완성된 표준이 아니다.
- 인증 방식이 명확하지 않다
- 라이브러리가 아직 불안정하다
- 실제 프로덕션에서 쓰는 사례가 많지 않다
그러나 방향은 맞다. 에이전트가 늘어날수록 에이전트 간 통신 표준은 반드시 필요하다.
MCP가 처음 나왔을 때도 비슷한 상황이었다. 지금은 Claude, Cursor, Windsurf 다 쓴다.
A2A도 그렇게 될 거라고 본다.
마치며
MCP 시리즈를 쭉 따라왔다면 이제 그림이 보일 것이다.
MCP는 AI가 세상과 연결되는 방법이고 A2A는 AI가 AI와 협력하는 방법이다
앞으로는 여러 에이전트가 역할을 나눠 가지고, 서로 대화하면서 복잡한 작업을 처리한다.
그 표준의 한쪽이 MCP고, 다른 쪽이 A2A다. 둘 다 알아두면 앞으로 나올 에이전트 아키텍처를 읽는 데 훨씬 수월해진다.