MCP vs A2A
와 는 둘 다 “가 외부와 말하는 방법”을 표준화한다. 그런데 무엇을 표준화하는지가 정반대 방향이다. 같은 기능을 둘 중 어디로 노출할지 — 이 챕터 끝나면 30초 안에 답할 수 있다.
두 프로토콜, 다른 자리
| 축 | ||
|---|---|---|
| 표준화 대상 | ·리소스·프롬프트 | |
| 주 사용자 | LLM 호스트 앱 ↔ 도구 | 에이전트 ↔ 에이전트 |
| 호출 방향 | LLM이 부른다 | 동료가 위임한다 |
| 작업 모델 | request/response | 상태 머신 |
한 줄: MCP는 손, A2A는 동료.
MCP가 노출하는 3대 자원
의 핵심 추상은 셋이다.
- — 실행 가능한 함수. 부수효과 있음.
- — 읽기 전용 데이터. 파일·URL·DB row.
- — 매개변수화된 재사용 프롬프트.
A2A의 자원은 하나다 — . 작업 단위가 더 굵고, 내부에 자체 도구·메모리·LLM 호출을 품을 수 있다.
| 비교 축 | MCP 자원 3종 | A2A 자원 1종 |
|---|---|---|
| 입자 크기 | 작다 (함수 호출) | 크다 (전체 작업) |
| 상태 | 보통 무상태 | |
| 호출자 부담 | 도구 결과 조립 | 결과만 받음 |
같은 기능, 어느 쪽에?
문서 요약 기능이 있다. 로 낼까, 로 낼까? 결정 기준 3가지:
- 재량의 폭 — LLM이 어떻게 부를지 결정해도 되면 MCP. 호출 측이 일관된 작업 정의를 원하면 A2A.
- 상태 보유 — 단발 함수 호출이면 MCP. 진행 추적·재개·취소가 필요하면 A2A.
- 결과 형태 — 즉시 응답 + LLM이 조립이면 MCP. 산출물(보고서·파일)이 필요하면 A2A .
규칙적으로 헷갈리면 이렇게: “LLM이 이 함수를 다섯 번 연달아 부를 가능성이 있나?” → 그렇다 = MCP, 아니다 = A2A.
MCP Transport: stdio vs HTTP
은 표준 두 가지를 제공한다.
- : 로컬 프로세스의 표준 입출력. Claude Desktop·VS Code 같은 호스트가 자식 프로세스를 띄울 때 쓴다.
- (또는 Streamable HTTP): 원격 서버. 보안·인증을 얹기 좋다.
| 축 | ||
|---|---|---|
| 거리 | 같은 머신 | 원격 |
| 인증 | OS 사용자 | OAuth·API Key |
| 다중 클라이언트 | 1:1 | N:1 |
| 적합 | 로컬 도구 | 공용 서버 |
MCP 서버 (Python)
공식 SDK는 mcp 패키지의 FastMCP. 한 줄, 한 줄로 띄운다. 데코레이터로 와 를 등록한다.
# Verified against: https://github.com/modelcontextprotocol/python-sdk
# Verified at: 2026-06-02
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("calculator")
@mcp.tool()
def add(a: int, b: int) -> int:
"""두 수를 더한다."""
return a + b
@mcp.resource("user://{name}")
def user(name: str) -> str:
return f"profile of {name}"
if __name__ == "__main__":
# 로컬 stdio (Claude Desktop 등이 자식으로 띄움)
mcp.run(transport="stdio")
# 또는 원격 HTTP:
# mcp.run(transport="streamable-http")MCP 클라이언트의 모양
는 보통 호스트 앱 안에 산다. 시작 시 에 list_tools로 목록을 받고, LLM이 부르겠다고 한 도구만 골라 call_tool로 실행한다.
# Verified against: https://github.com/modelcontextprotocol/python-sdk
# Verified at: 2026-06-02
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])
r = await session.call_tool("add", {"a": 2, "b": 3})
print(r.content)
asyncio.run(main())A2A와 비교한 호출 흐름
핵심 차이는 누가 을 부르는가다.
- : 호출 측이 LLM이다. LLM이 를 보고 다음을 결정한다.
- : 호출 측이 *다른 *다. 자신의 LLM은 안에서 알아서 돈다.
그래서 같은 “PDF 요약” 기능이라도, MCP는 문장 추출 도구에 가깝고, A2A는 요약 에이전트다.
Claude Desktop 통합 예
가 가장 먼저 자리잡은 곳은 Anthropic의 Claude Desktop. 설정 파일에 를 등록하면, 대화 중 이 목록에서 자동 라우팅한다.
{
"mcpServers": {
"calculator": {
"command": "python",
"args": ["/path/to/server.py"]
}
}
}
호스트 앱 → 로 자식 프로세스 띄움 → 도구 목록 가져옴 → 사용자 발화에 따라 자동 호출. 는 보이지 않게 동작한다.
같이 쓰는 경우 — MCP 안의 A2A 스킬
흥미로운 패턴: 을 얇은 로 감싸 에게 노출하는 것. 이렇게 하면 같은 LLM 호스트가 일반 와 동료 를 구분 없이 부른다.
# Verified against: https://github.com/modelcontextprotocol/python-sdk
# Verified at: 2026-06-02
from mcp.server.fastmcp import FastMCP
import httpx
mcp = FastMCP("a2a-bridge")
@mcp.tool()
async def call_researcher(topic: str) -> str:
"""A2A Researcher에게 보고서 요청."""
body = {
"jsonrpc": "2.0", "id": 1, "method": "message/send",
"params": {"message": {"messageId": "m1", "role": "user",
"parts": [{"text": topic}]}},
}
async with httpx.AsyncClient() as c:
r = await c.post("https://example.com/a2a/v1", json=body)
return r.json()["result"]["status"]결정표
| 상황 | 선택 | 이유 |
|---|---|---|
read_file, search_web | 작고 무상태, 이 조합 | |
| 보고서 작성 | 작업 단위 크고 상태 추적 | |
| 같은 머신 빠른 도구 | 오버헤드 0 | |
| 사내 공용 도구 게이트웨이 | 인증·다중 클라이언트 | |
| 외부 회사가 만든 에이전트 | A2A | 표준 인증·상태·취소 |
규칙: 사람 한 명이 손으로 할 수 있을 만큼 단순한 일은 MCP. 다른 사람을 시킬 만한 일이면 A2A.
안티 패턴 모음
- 에 거대 작업 박기:
generate_report같은 30분짜리 . 타임아웃·재개 없음. → 로. - A2A로 read_file 노출: 만들고 상태 머신 돌리는 비용이 본전을 못 뽑는다. → MCP로.
- 둘 다 노출: 같은 기능을 양쪽으로 내면 LLM이 헷갈린다. 한쪽만 정한다.
- 혼동: 원격 서비스에 를 쓰려 한다. 가 정답.
큰 그림은 단순하다 — 입자 크기로 갈라라.
다음 챕터로
이제 흥미로운 경계가 보인다. 는 도구를 표준화하고, 는 에이전트를 표준화한다. 그런데 에이전트를 도구처럼 부른다면? 다음 장에서는 ··의 경계를 다시 본다.