MCP와 A2A로 멀티에이전트 시스템 구축하기 — Model Context Protocol·Agent-to-Agent Protocol 실전 통합 가이드
AI 에이전트를 처음 만들어보면서 느끼는 벽이 있습니다. "도구는 붙였는데, 에이전트끼리는 어떻게 대화하게 하지?" 저도 처음엔 각 에이전트마다 직접 HTTP 클라이언트를 짜고, 메시지 포맷을 제각각 정의하다가 결국 스파게티 코드를 만든 경험이 있습니다. 그때 가장 많이 든 생각은 "이거 다들 이렇게 짜는 건가?" 였는데 — 다행히 그렇지 않았습니다.
MCP(Model Context Protocol) 는 Anthropic이 2024년 11월에 공개한 오픈 프로토콜로, 에이전트가 외부 도구와 데이터에 접근하는 방식을 표준화합니다. A2A(Agent-to-Agent Protocol) 는 Google이 2025년 4월에 발표한 프로토콜로, 서로 다른 프레임워크로 만든 에이전트끼리 협업하는 방식을 표준화합니다. 2026년 현재, 이 두 프로토콜을 함께 쓰는 "이중 스택"이 프로덕션 멀티에이전트 아키텍처의 사실상 표준으로 자리잡았습니다.
이 글에서는 MCP와 A2A가 각각 어떤 문제를 해결하는지, 그리고 실제 시스템에서 어떻게 계층적으로 결합되는지를 코드와 함께 살펴봅니다. 고객지원 자동화, 뉴스룸 팩트체크 시나리오를 따라가다 보면 FastMCP로 MCP 서버를 빠르게 띄우고, A2A로 두 에이전트가 실시간 협업하는 구조를 직접 설계할 수 있습니다.
핵심 개념
MCP: 에이전트와 도구 사이의 USB-C
MCP를 한 마디로 설명하면 "AI 에이전트용 USB-C 포트"입니다. 파일 시스템이든, 데이터베이스든, 외부 API든, MCP 서버로 한 번만 래핑해두면 어떤 에이전트도 동일한 인터페이스로 접근할 수 있습니다. 처음엔 "그냥 REST API랑 뭐가 달라?"라고 생각했는데, 핵심 차이는 에이전트가 도구 목록을 런타임에 동적으로 조회하고 선택한다는 점입니다. 도구를 하드코딩하지 않아도 됩니다.
아키텍처는 단순한 클라이언트–서버 모델입니다.
에이전트 (MCP Client)
│
├─ MCP 서버 A (파일 시스템)
├─ MCP 서버 B (데이터베이스)
└─ MCP 서버 C (외부 API)MCP 서버가 제공하는 것은 크게 세 가지입니다.
- Tools: 에이전트가 호출할 수 있는 함수 (파일 읽기, DB 쿼리 등)
- Resources: 에이전트가 참조할 수 있는 데이터 (문서, 설정 파일 등)
- Prompts: 재사용 가능한 프롬프트 템플릿
FastMCP를 쓰면 MCP 서버를 정말 빠르게 만들 수 있습니다. @mcp.tool() 데코레이터 하나면 Python 함수가 바로 MCP 도구가 됩니다.
# pip install fastmcp httpx
from fastmcp import FastMCP
import httpx
mcp = FastMCP("weather-server")
@mcp.tool()
async def get_weather(city: str) -> dict:
"""도시명을 받아 현재 날씨 정보를 반환합니다."""
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.weather.example.com/current",
params={"q": city, "units": "metric"}
)
data = response.json()
return {
"city": city,
"temperature": data["main"]["temp"],
"description": data["weather"][0]["description"]
}
@mcp.tool()
async def get_forecast(city: str, days: int = 5) -> list[dict]:
"""도시의 날씨 예보를 반환합니다."""
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.weather.example.com/forecast",
params={"q": city, "cnt": days, "units": "metric"}
)
return response.json()["list"]
if __name__ == "__main__":
mcp.run()FastMCP: MCP 서버를 Flask처럼 간단하게 만들 수 있는 Python 경량 프레임워크입니다.
pip install fastmcp로 설치하면from fastmcp import FastMCP로 바로 사용할 수 있습니다. 공식 Python SDK(pip install mcp)의from mcp.server.fastmcp import FastMCP와 같은 역할이지만 더 빠른 프로토타이핑에 적합합니다.
클라이언트 측은 TypeScript SDK로 이렇게 구현합니다.
// npm install @modelcontextprotocol/sdk
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "python",
args: ["weather_server.py"]
});
const client = new Client({ name: "weather-agent", version: "1.0.0" });
await client.connect(transport);
// 사용 가능한 도구 목록 동적 조회 — 하드코딩 불필요
const tools = await client.listTools();
console.log("사용 가능한 도구:", tools.tools.map(t => t.name));
// 도구 호출
const result = await client.callTool({
name: "get_weather",
arguments: { city: "Seoul" }
});
console.log("날씨 정보:", result.content);A2A: 에이전트끼리의 언어
MCP가 "에이전트와 도구"의 관계를 정의한다면, A2A는 "에이전트와 에이전트"의 관계를 정의합니다. Python으로 만든 에이전트가 Java로 만든 에이전트에게 작업을 위임하고 결과를 받는 것, 이게 A2A가 해결하는 문제입니다.
저도 처음에 "그냥 REST API 엔드포인트 맞춰서 호출하면 되지 않나?"라고 생각했는데, 에이전트가 여러 개가 되면 "이 에이전트한테 이 작업 맡겨도 되나?"를 판단하는 것 자체가 문제가 됩니다. A2A의 Agent Card가 이 탐색 문제를 해결합니다. 각 에이전트는 자신이 뭘 할 수 있는지를 JSON 문서로 광고하고, 다른 에이전트는 이 카드를 읽고 협업 대상을 찾습니다.
{
"name": "ResearchAgent",
"version": "1.0.0",
"description": "웹 검색과 사실 확인을 담당하는 에이전트",
"url": "https://research-agent.example.com",
"capabilities": {
"streaming": true,
"pushNotifications": true
},
"skills": [
{
"id": "fact-check",
"name": "Fact Check",
"description": "주어진 정보의 사실 여부를 검증합니다",
"inputModes": ["text"],
"outputModes": ["text", "data"]
},
{
"id": "web-search",
"name": "Web Search",
"description": "웹에서 관련 정보를 검색합니다",
"inputModes": ["text"],
"outputModes": ["text", "data"]
}
],
"authentication": {
"schemes": ["bearer"]
}
}각 A2A 에이전트는 /.well-known/agent.json 엔드포인트에 이 카드를 게시합니다. 에이전트 간 실제 통신은 JSON-RPC 2.0 over HTTPS로 이루어지고, 장시간 태스크는 SSE로 진행 상황을 스트리밍합니다.
JSON-RPC 2.0: HTTP를 통해 원격 함수를 호출하는 경량 프로토콜입니다. 요청에 메서드 이름(
"method": "tasks/send")과 파라미터를 JSON으로 담아 보내면 서버가 결과를 JSON으로 응답합니다. REST API를 써봤다면 큰 차이 없이 익힐 수 있습니다.
A2A의 핵심 메서드는 tasks/send(태스크 전송)이고, 실무에서는 tasks/get(상태 조회)과 tasks/cancel(취소)도 자주 쓰입니다. 특히 외부 API 호출이 포함된 장시간 태스크라면 tasks/get으로 주기적으로 상태를 확인하는 패턴이 필수입니다.
# pip install httpx
import httpx
import json
import uuid
from typing import AsyncIterator
async def delegate_task_to_agent(
agent_url: str,
task: str,
context_id: str | None = None
) -> AsyncIterator[dict]:
"""A2A 프로토콜로 다른 에이전트에게 작업을 위임합니다."""
# hash() 대신 uuid4()가 안전합니다 — hash()는 실행마다 값이 달라질 수 있습니다
task_id = str(uuid.uuid4())
payload = {
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"id": task_id,
"contextId": context_id,
"message": {
"role": "user",
"parts": [{"kind": "text", "text": task}]
}
},
"id": 1
}
# 프로덕션에서는 try-except와 타임아웃 설정이 필수입니다
async with httpx.AsyncClient(timeout=60.0) as client:
async with client.stream(
"POST",
f"{agent_url}/a2a",
json=payload,
headers={
"Accept": "text/event-stream",
"Content-Type": "application/json"
}
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
event = json.loads(line[6:])
yield eventSSE(Server-Sent Events): 서버에서 클라이언트로 단방향 스트리밍을 가능하게 하는 HTTP 기반 기술입니다. A2A에서는 장시간 실행되는 태스크의 중간 결과를 실시간으로 전달하는 데 씁니다. WebSocket과 달리 단방향이라 구현이 단순하고, 일반 HTTP 인프라에서 바로 사용할 수 있습니다.
두 프로토콜의 관계: 수직 vs 수평
솔직히 처음 봤을 때 "이게 뭐가 다르지? 둘 다 에이전트 연결하는 거 아닌가?" 싶었는데, 이렇게 생각하면 명확해집니다.
| 구분 | MCP | A2A |
|---|---|---|
| 연결 대상 | 에이전트 ↔ 도구/데이터 | 에이전트 ↔ 에이전트 |
| 통신 방향 | 수직 (도구 접근) | 수평 (피어-투-피어) |
| 통신 모델 | 클라이언트–서버 | 피어-투-피어 |
| 전송 방식 | stdio, HTTP+SSE | JSON-RPC 2.0 over HTTPS, SSE |
| 핵심 목적 | 외부 역량 접근 표준화 | 이기종 에이전트 협업 표준화 |
실제 시스템에서 이 두 프로토콜은 계층적으로 결합됩니다. 각 에이전트는 MCP로 자신의 도구에 접근하면서, A2A로 다른 에이전트에게 작업을 위임합니다.
실전 적용
예시 1: 고객지원 에이전트 — MCP로 도구 연결하기
고객지원 시스템을 만든다고 해봅시다. 에이전트가 CRM과 지식베이스에 접근해야 합니다. MCP 서버로 이 두 시스템을 래핑해두면, 나중에 에이전트를 교체하더라도 서버는 그대로 재사용할 수 있습니다.
# crm_mcp_server.py
# pip install fastmcp
from fastmcp import FastMCP
mcp = FastMCP("crm-server")
# 실제 프로젝트에서는 아래를 CRM DB 클라이언트로 교체하세요
# (SQLite, PostgreSQL, 또는 CRM SaaS API 등)
class _MockDB:
async def find_customer(self, customer_id):
return None # 실제 구현체로 교체 필요
async def vector_search(self, query, limit):
return []
async def create_ticket(self, **kwargs):
return type("Ticket", (), {"id": "TICKET-001"})()
db = _MockDB()
@mcp.tool()
async def get_customer_info(customer_id: str) -> dict:
"""고객 ID로 고객 정보를 조회합니다."""
customer = await db.find_customer(customer_id)
if not customer:
return {"error": f"고객 {customer_id}를 찾을 수 없습니다"}
return {
"id": customer.id,
"name": customer.name,
"email": customer.email,
"subscription_tier": customer.tier,
"open_tickets": customer.open_ticket_count
}
@mcp.tool()
async def search_knowledge_base(query: str, top_k: int = 3) -> list[dict]:
"""지식베이스에서 관련 문서를 검색합니다."""
results = await db.vector_search(query, limit=top_k)
return [
{
"title": doc.title,
"content": doc.content[:500],
"relevance_score": doc.score
}
for doc in results
]
@mcp.tool()
async def create_ticket(
customer_id: str,
subject: str,
description: str,
priority: str = "medium"
) -> dict:
"""새 지원 티켓을 생성합니다."""
ticket = await db.create_ticket(
customer_id=customer_id,
subject=subject,
description=description,
priority=priority
)
return {"ticket_id": ticket.id, "status": "created"}
if __name__ == "__main__":
mcp.run()이제 이 MCP 서버를 활용하는 고객지원 에이전트를 만들어봅니다. 에이전트 루프의 핵심은 end_turn이 올 때까지 도구 호출을 반복하는 것입니다. 처음 구현할 때 이 루프 없이 단발성 호출만 하다가 도구가 전혀 실행되지 않아 한참 헤맸습니다.
# support_agent.py
# pip install anthropic mcp
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def run_support_agent(customer_id: str, user_message: str):
server_params = StdioServerParameters(
command="python",
args=["crm_mcp_server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# MCP 서버에서 도구 목록 동적 조회 — 하드코딩 불필요
tools_result = await session.list_tools()
tools = [
{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
}
for tool in tools_result.tools
]
client = anthropic.Anthropic()
messages = [{"role": "user", "content": user_message}]
# 에이전트 루프: end_turn이 나올 때까지 도구 호출을 반복
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
text_content = next(
block for block in response.content
if block.type == "text"
)
return text_content.text
tool_uses = [
block for block in response.content
if block.type == "tool_use"
]
messages.append({
"role": "assistant",
"content": response.content
})
tool_results = []
for tool_use in tool_uses:
result = await session.call_tool(
tool_use.name,
arguments=tool_use.input
)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": str(result.content)
})
messages.append({
"role": "user",
"content": tool_results
})| 코드 포인트 | 역할 |
|---|---|
stdio_client |
MCP 서버를 subprocess로 실행하고 연결 |
session.list_tools() |
서버가 제공하는 도구 목록 동적 조회 |
| 에이전트 루프 | end_turn이 될 때까지 도구 호출과 응답을 반복 |
session.call_tool() |
실제 MCP 도구 실행 |
예시 2: 뉴스룸 멀티에이전트 — A2A로 에이전트 협업하기
이제 두 프로토콜을 함께 씁니다. 복잡도가 한 단계 올라가지만, 이 구조를 이해하면 프로덕션 멀티에이전트 시스템의 기본 패턴이 보입니다.
시나리오: 기자 에이전트(Reporter)가 기사를 쓰다가 팩트체크가 필요하면 조사 에이전트(Researcher)에게 A2A로 위임합니다. Researcher는 MCP로 뉴스 데이터베이스를 조회하고 결과를 다시 Reporter에게 돌려줍니다.
Researcher 에이전트는 두 가지 역할을 동시에 합니다. A2A 서버(Reporter에게 태스크를 받는 쪽)이면서 MCP 클라이언트(뉴스 DB에 접근하는 쪽)입니다. 이 이중 역할이 이 패턴의 핵심입니다.
먼저 A2A 서버의 기본 뼈대입니다.
# researcher_agent.py — A2A 서버 + MCP 클라이언트
# pip install fastapi uvicorn anthropic mcp
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import anthropic
import json
app = FastAPI()
AGENT_CARD = {
"name": "ResearchAgent",
"version": "1.0.0",
"description": "뉴스 기사 팩트체크와 심층 조사를 담당",
"url": "http://localhost:8001",
"capabilities": {"streaming": True, "pushNotifications": False},
"skills": [
{
"id": "fact-check",
"name": "Fact Check",
"description": "주장의 사실 여부를 데이터베이스로 검증",
"inputModes": ["text"],
"outputModes": ["text"]
}
]
}
@app.get("/.well-known/agent.json")
async def get_agent_card():
"""Agent Card 엔드포인트 — 다른 에이전트가 역량을 탐색하는 진입점"""
return AGENT_CARDAgent Card를 /.well-known/agent.json에 게시하면 다른 에이전트가 이 에이전트의 역량을 자동으로 탐색할 수 있습니다. 이제 실제 태스크를 처리하는 핵심 로직입니다. A2A 요청을 받으면 MCP 서버에 연결해 도구를 호출하고, 결과를 SSE로 스트리밍합니다.
@app.post("/a2a")
async def handle_a2a_request(request: Request):
body = await request.json()
task_text = body["params"]["message"]["parts"][0]["text"]
async def generate_response():
# — MCP 클라이언트 역할: 뉴스 DB MCP 서버에 연결 —
server_params = StdioServerParameters(
command="python",
args=["news_db_mcp_server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools_result = await session.list_tools()
tools = [
{
"name": t.name,
"description": t.description,
"input_schema": t.inputSchema
}
for t in tools_result.tools
]
client = anthropic.Anthropic()
messages = [{"role": "user", "content": task_text}]
# 작업 시작 상태를 Reporter에게 스트리밍
yield f"data: {json.dumps({'status': 'working', 'message': '팩트체크 시작'})}\n\n"
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
result_text = next(
b.text for b in response.content
if b.type == "text"
)
# 최종 결과를 Reporter에게 스트리밍
yield f"data: {json.dumps({'status': 'completed', 'result': result_text})}\n\n"
break
# — MCP 도구 호출 처리 —
tool_uses = [b for b in response.content if b.type == "tool_use"]
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_uses:
yield f"data: {json.dumps({'status': 'working', 'message': f'{tool_use.name} 조회 중'})}\n\n"
result = await session.call_tool(tool_use.name, arguments=tool_use.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": str(result.content)
})
messages.append({"role": "user", "content": tool_results})
return StreamingResponse(generate_response(), media_type="text/event-stream")이제 Reporter 에이전트가 Researcher에게 A2A로 작업을 위임하는 코드입니다.
# reporter_agent.py
# pip install httpx
import httpx
import json
import uuid
async def write_article_with_fact_check(topic: str) -> str:
"""기사 작성 중 팩트체크가 필요한 부분을 Researcher에게 위임합니다."""
# 1. Researcher의 Agent Card를 읽어 역량 확인
async with httpx.AsyncClient() as client:
card_response = await client.get(
"http://localhost:8001/.well-known/agent.json"
)
agent_card = card_response.json()
print(f"협업 에이전트: {agent_card['name']} — {agent_card['description']}")
# 2. A2A로 팩트체크 작업 위임
fact_check_request = {
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"id": str(uuid.uuid4()), # uuid4가 충돌 없이 안전합니다
"message": {
"role": "user",
"parts": [{
"kind": "text",
"text": f"다음 주제에 대한 핵심 사실을 검증해주세요: {topic}"
}]
}
},
"id": 1
}
verified_facts = "" # SSE에서 받는 result는 문자열입니다
async with httpx.AsyncClient(timeout=60.0) as client:
async with client.stream(
"POST",
"http://localhost:8001/a2a",
json=fact_check_request,
headers={"Accept": "text/event-stream"}
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
event = json.loads(line[6:])
if event["status"] == "completed":
verified_facts = event["result"]
elif event["status"] == "working":
print(f" 진행 중: {event['message']}")
# 3. 검증된 사실을 바탕으로 기사 작성
return f"[검증된 정보 기반 기사]\n\n{verified_facts}"| 코드 포인트 | 역할 |
|---|---|
/.well-known/agent.json |
Agent Card 엔드포인트 — 역량 자동 탐색에 사용 |
| SSE 스트리밍 | 장시간 작업의 중간 상태를 실시간으로 전달 |
tasks/send JSON-RPC |
A2A 표준 메서드로 작업 전송 |
| Researcher 내부 MCP | A2A 서버이면서 동시에 MCP 클라이언트로 동작 |
예시 3: 전체 이중 스택 아키텍처 다이어그램
사용자 요청
│
▼
Reporter Agent (MCP Client + A2A Client)
│
├── MCP ──► CRM MCP Server
│ └── CRM Database
│
├── MCP ──► KnowledgeBase MCP Server
│ └── Vector DB
│
└── A2A ──► Researcher Agent (A2A Server + MCP Client)
│
└── MCP ──► NewsDB MCP Server
└── Elasticsearch이 구조에서 각 에이전트는 두 방향으로 통신합니다.
- 수직 방향(MCP): 자신의 도구와 데이터에 표준 인터페이스로 접근
- 수평 방향(A2A): 전문화된 다른 에이전트에게 작업을 위임
장단점 분석
장점
| 항목 | 내용 | 비고 |
|---|---|---|
| 표준화 | OpenAI, Microsoft, Google 모두 MCP를 채택해 생태계 파편화가 해소됩니다 | 2025년 이후 주요 LLM 플랫폼 전반 지원 |
| 재사용성 | MCP 서버 하나를 만들면 어떤 에이전트도 동일한 방식으로 사용할 수 있습니다 | 도구 중복 구현 없음 |
| 이기종 협업 | A2A 덕분에 Python 에이전트와 Java 에이전트가 프레임워크 무관하게 협업 가능합니다 | 벤더 종속 없는 협업 |
| 비동기 지원 | A2A의 SSE 스트리밍으로 장시간 실행 태스크도 자연스럽게 처리됩니다 | tasks/get으로 상태 폴링도 지원 |
| 오픈 거버넌스 | MCP는 2025년 12월 Linux Foundation 산하 AAIF에 기증됐고, A2A 역시 오픈소스 커뮤니티 주도로 운영됩니다 | 장기 중립성 보장 |
| 성숙한 SDK | TypeScript/Python MCP SDK는 이미 프로덕션 수준의 완성도를 갖추고 있습니다 | 2025-11-25 스펙으로 엔터프라이즈 요건 충족 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| MCP 보안 | 악성 MCP 서버가 시스템 프롬프트에 숨겨진 지시를 주입해 에이전트 행동을 조작하는 프롬프트 인젝션 공격 벡터가 연구되고 있습니다 | 최소 권한 원칙 적용, 입력 검증 강화, 신뢰할 수 있는 MCP 서버만 연결 |
| A2A 생태계 미성숙 | 2025년 4월 출시라 프로덕션 레퍼런스가 아직 적습니다 | CrewAI v1.10, Microsoft Agent Framework 1.0 등 지원 프레임워크 활용 |
| 디버깅 난이도 | MCP와 A2A가 중첩된 시스템은 분산 추적 없이 디버깅이 매우 어렵습니다 | OpenTelemetry 기반 분산 트레이싱 필수 도입 |
| 서버 구현 편차 | MCP 서버마다 구현 품질이 천차만별입니다 | 공식 SDK 사용, 커뮤니티 검증된 서버 선택 |
| 벤더 락인 위험 | 특정 클라우드 SDK(Google ADK, Vertex AI 등)에 의존하면 락인이 발생할 수 있습니다 | 프로토콜 레이어와 클라우드 레이어를 분리해 설계 |
최소 권한 원칙(Principle of Least Privilege): 시스템의 각 컴포넌트가 작업 수행에 필요한 최소한의 권한만 갖도록 설계하는 보안 원칙입니다. MCP 서버는 에이전트에게 꼭 필요한 도구만 노출하는 것이 좋습니다.
분산 트레이싱(Distributed Tracing): 여러 컴포넌트에 걸친 요청의 흐름을 추적하는 기술입니다. MCP+A2A 시스템에서는 어느 에이전트가 어느 도구를 언제 호출했는지 전체 흐름을 볼 수 있어야 합니다. OpenTelemetry를 초반부터 붙여두면 나중에 고생을 많이 덜 수 있습니다.
실무에서 가장 흔한 실수
-
MCP와 A2A를 처음부터 동시에 도입하는 것. 처음에 이걸 한꺼번에 적용하려다 어디서 뭐가 터지는지 추적을 못 했습니다. MCP로 단일 에이전트의 도구 통합을 먼저 안정화한 뒤 A2A로 확장하는 순서가 훨씬 수월합니다.
-
Agent Card를 정적으로 관리하는 것. 에이전트의 역량이 바뀌면 Agent Card도 함께 업데이트되어야 합니다. 하드코딩 대신 런타임에 동적으로 생성하는 방식이 유지보수에 훨씬 유리합니다.
-
에러 전파를 고려하지 않는 것. A2A로 위임한 태스크가 실패했을 때 호출 에이전트가 어떻게 처리할지 미리 설계하지 않으면, 에러가 조용히 삼켜지거나 시스템 전체가 블로킹될 수 있습니다. 타임아웃과 폴백 전략을 미리 정의해두는 것을 권장합니다.
마치며
MCP는 에이전트의 팔을, A2A는 에이전트의 언어를 표준화합니다 — 이 두 프로토콜이 결합될 때 비로소 진정한 멀티에이전트 시스템이 완성됩니다.
지금 바로 시작해볼 수 있는 3단계입니다.
-
MCP 서버부터 만들어보시면 좋습니다.
pip install fastmcp를 설치하고, 자주 쓰는 API나 데이터베이스를@mcp.tool()데코레이터 하나로 래핑해보시면 됩니다. 공식 GitHub의 예제 서버들이 좋은 출발점이 됩니다. -
Claude나 다른 LLM 에이전트에서 그 MCP 서버를 연결해볼 수 있습니다.
@modelcontextprotocol/sdk(TypeScript) 또는mcpPyPI 패키지로 클라이언트를 구성하고, 에이전트가 실제로 도구를 호출하는 루프를 직접 구현해보시면 MCP의 작동 방식이 체감됩니다. -
A2A 공식 리포지토리의 샘플을 실행해보시면 좋습니다. GitHub a2aproject/A2A에는 Python과 TypeScript 참조 구현이 있고, 커뮤니티 기여 튜토리얼인 a2a-mcp-tutorial에는 두 프로토콜을 함께 쓰는 실습 예제가 준비되어 있습니다.
다음 글: Apache Kafka를 이벤트 브로커로 활용해 MCP+A2A 멀티에이전트 시스템을 대규모 실시간 처리에 적용하는 아키텍처 패턴을 살펴볼 예정입니다.
참고 자료
공식 사양
공식 리포지토리
블로그 · 사례
- Google Developers Blog — A2A 발표
- GitHub — A2A+MCP 실습 튜토리얼 (커뮤니티 기여)
- Auth0 — MCP vs A2A 비교 가이드
- Elastic — A2A+MCP 뉴스룸 에이전트 사례
- Kai Waehner — Apache Kafka + A2A + MCP 아키텍처
- Infinitus — 헬스케어에서의 MCP와 A2A
- IBM — Agent2Agent Protocol 개요
- DEV Community — MCP 완전 구현 가이드 2026