목차
- 먼저 핵심만
- MCP 이전: 에이전트의 한계
- MCP의 구조: 서버-클라이언트-도구
- Host (호스트)
- Client (클라이언트)
- Server (서버)
- 통신 흐름
- 연결 레이어가 생기면 바뀌는 것들
- 도구 목록이 동적으로 바뀝니다
- 도구 통합의 책임이 분리됩니다
- 에이전트의 능력 범위가 설정으로 결정됩니다
- 에이전트 간 도구 생태계가 공유됩니다
- 설계가 달라지는 지점
- 도구 등록이 "발견"으로 바뀝니다
- 권한의 경계가 달라집니다
- 에러 처리의 범위가 넓어집니다
- 도구 설명이 곧 인터페이스입니다
- MCP의 현실적 한계
- 레이턴시가 추가됩니다
- 보안 표면이 넓어집니다
- 버전 관리가 복잡해집니다
- 상태 관리의 어려움
- 실무에 가져갈 수 있는 원칙
- 요약
먼저 핵심만
코딩 에이전트에 MCP(Model Context Protocol)를 붙이면, 에이전트가 쓸 수 있는 도구가 "빌드 타임에 결정되는 것"에서 "런타임에 발견하는 것"으로 바뀝니다. 이건 단순히 도구 개수가 느는 문제가 아닙니다. 에이전트의 설계 자체가 달라지는 구조적 전환입니다.
이 글에서는 MCP 이전 에이전트의 한계, MCP의 구조, 연결 레이어가 만드는 변화, 그리고 현실적 한계까지 순서대로 뜯어봅니다.
MCP 이전: 에이전트의 한계
MCP가 없던 시절, 코딩 에이전트가 외부 도구를 쓰려면 어떻게 했을까요?
직접 다 만들었습니다.
파일을 읽으려면 파일 읽기 함수를 구현하고, Git을 쓰려면 Git CLI 래퍼를 만들고, DB에 쿼리하려면 DB 드라이버를 에이전트 안에 넣었습니다. 각 도구마다 연결 코드를 따로 짰고, 에러 처리도 따로, 인증도 따로였습니다.
이 방식의 문제는 세 가지입니다.
첫째, 통합 비용이 도구 수에 비례합니다. 10개 도구를 지원하려면 10개 통합 코드를 짜야 합니다. 100개면 100개입니다. 추상화 없이 개별 구현이 쌓이면, 유지보수 비용이 기하급수적으로 늘어납니다.
둘째, 벤더 종속이 생깁니다. 에이전트 A가 만든 GitHub 통합은 에이전트 B에서 못 씁니다. 같은 기능인데 에이전트마다 다른 방식으로 구현하니까요. 도구 생태계가 에이전트별로 파편화됩니다.
셋째, 도구 목록이 빌드 타임에 고정됩니다. 에이전트를 배포할 때 지원하는 도구가 결정됩니다. 사용자가 새 도구를 추가하려면 에이전트 코드를 고쳐야 합니다. 플러그인 아키텍처가 아니라 모놀리식 구조인 셈입니다.
의사코드로 표현하면 이런 모양입니다.
# MCP 이전: 하드코딩된 도구 통합
class CodingAgent:
def __init__(self):
self.tools = {
"read_file": FileReader(), # 직접 구현
"git_commit": GitWrapper(), # 직접 구현
"run_test": TestRunner(), # 직접 구현
"query_db": PostgresClient(), # 직접 구현
}
def handle_tool_call(self, name, params):
if name not in self.tools:
return "지원하지 않는 도구입니다"
return self.tools[name].execute(params)
도구마다 개별 클래스가 필요하고, 새 도구를 추가하려면 에이전트 코드를 수정해야 합니다. 확장의 단위가 "코드 변경"입니다.
MCP의 구조: 서버-클라이언트-도구
MCP는 이 문제를 프로토콜로 풀었습니다. 구조를 뜯어보면 세 계층으로 나뉩니다.

Host (호스트)
에이전트가 실행되는 환경입니다. IDE(VS Code, Cursor), CLI(Claude Code), 혹은 데스크톱 앱(Claude Desktop)이 여기에 해당합니다. 호스트는 사용자와 직접 상호작용하는 계층이고, 내부에 하나 이상의 MCP 클라이언트를 생성합니다.
Client (클라이언트)
호스트 안에서 동작하며, 각 MCP 서버와 1:1로 연결됩니다. JSON-RPC 기반으로 서버와 통신하고, 서버가 제공하는 도구/리소스/프롬프트 목록을 가져옵니다. 하나의 호스트 안에 여러 클라이언트가 존재할 수 있고, 각각 독립된 서버를 담당합니다.
Server (서버)
실제로 도구를 제공하는 프로세스입니다. GitHub MCP 서버는 이슈 생성, PR 조회 같은 도구를 노출하고, 파일시스템 MCP 서버는 파일 읽기/쓰기를 제공합니다. 서버는 세 가지를 제공할 수 있습니다.
| 제공 항목 | 제어 주체 | 설명 |
|---|---|---|
| Tools | 모델 | LLM이 판단해서 호출하는 함수 |
| Resources | 앱 | 앱이 필요에 따라 가져오는 데이터 |
| Prompts | 사용자 | 사용자가 선택하는 상호작용 템플릿 |
핵심은 이 세 항목의 제어 주체가 다르다는 점입니다. Tools는 LLM이 언제 호출할지 결정하고, Resources는 호스트 앱이 컨텍스트로 가져올지 결정하고, Prompts는 사용자가 명시적으로 선택합니다.
통신 흐름
MCP의 통신은 JSON-RPC 2.0을 기반으로 합니다. 클라이언트와 서버가 초기화 핸드셰이크를 거친 뒤, 도구 목록 조회 → 도구 호출 → 결과 반환 순서로 진행됩니다.
# MCP 통신 흐름 (의사코드)
# 1. 초기화
client → server: initialize(capabilities, protocolVersion)
server → client: { capabilities: { tools: true, resources: true } }
client → server: initialized()
# 2. 도구 발견
client → server: tools/list()
server → client: [
{ name: "create_issue", description: "GitHub 이슈 생성", inputSchema: {...} },
{ name: "search_code", description: "코드 검색", inputSchema: {...} }
]
# 3. 도구 호출
client → server: tools/call("create_issue", { title: "버그 수정", body: "..." })
server → client: { content: [{ type: "text", text: "이슈 #42 생성 완료" }] }
전송 계층은 분리되어 있습니다. 로컬 프로세스면 stdio, 원격이면 HTTP(Streamable HTTP), 양방향이 필요하면 WebSocket을 씁니다. 프로토콜 자체는 전송 방식에 무관합니다.
연결 레이어가 생기면 바뀌는 것들
MCP라는 연결 레이어가 에이전트에 붙으면, 구체적으로 무엇이 달라질까요?
도구 목록이 동적으로 바뀝니다
MCP 이전에는 에이전트가 알고 있는 도구가 코드에 박혀 있었습니다. MCP를 붙이면 tools/list 호출로 런타임에 도구를 발견합니다. 사용자가 새 MCP 서버를 설정 파일에 추가하면, 에이전트 재빌드 없이 새 도구가 나타납니다.
# MCP 이후: 동적 도구 발견
class CodingAgent:
def __init__(self, mcp_servers):
self.clients = []
for server in mcp_servers:
client = MCPClient(server)
client.initialize()
self.clients.append(client)
def get_available_tools(self):
tools = []
for client in self.clients:
tools.extend(client.list_tools()) # 런타임에 도구 발견
return tools
def handle_tool_call(self, name, params):
for client in self.clients:
if client.has_tool(name):
return client.call_tool(name, params)
return "사용 가능한 도구가 없습니다"
확장의 단위가 "코드 변경"에서 "설정 변경"으로 바뀌었습니다.
도구 통합의 책임이 분리됩니다
MCP 이전에는 에이전트 개발자가 도구 통합까지 책임졌습니다. MCP 이후에는 도구 제공자가 MCP 서버를 만들고, 에이전트 개발자는 MCP 클라이언트만 구현하면 됩니다.
GitHub 통합을 예로 들면, MCP 이전에는 에이전트마다 GitHub API 래퍼를 직접 짰습니다. MCP 이후에는 GitHub MCP 서버 하나를 Claude Code에서도, Cursor에서도, Windsurf에서도 꽂아 쓸 수 있습니다. 한 번 만든 MCP 서버가 여러 에이전트에서 재사용됩니다.
에이전트의 능력 범위가 설정으로 결정됩니다
같은 에이전트라도 어떤 MCP 서버를 연결하느냐에 따라 완전히 다른 일을 할 수 있습니다. 파일시스템 서버를 붙이면 파일을 다루고, DB 서버를 붙이면 쿼리를 날리고, 브라우저 서버를 붙이면 웹을 조작합니다.
실제로 Claude Code의 경우, .mcp.json 설정 하나로 에이전트가 접근 가능한 외부 세계가 결정됩니다. 개발자마다 다른 MCP 서버 조합을 쓸 수 있고, 프로젝트마다 다른 도구 세트를 구성할 수 있습니다.
에이전트 간 도구 생태계가 공유됩니다
MCP가 표준이니까, 한쪽에서 만든 MCP 서버를 다른 에이전트에서도 쓸 수 있습니다. 이건 웹에서 REST API가 했던 역할과 비슷합니다. REST가 서버 간 데이터 교환을 표준화했듯, MCP는 AI 에이전트와 도구 간 연결을 표준화합니다.
결과적으로 "에이전트를 잘 만드는 것"과 "좋은 도구를 만드는 것"이 분리됩니다. 에이전트 개발자는 에이전트 루프와 UX에 집중하고, 도구 개발자는 도구의 품질에 집중하는 분업 구조가 가능해집니다.
설계가 달라지는 지점
연결 레이어가 생기면 에이전트의 내부 설계에서 몇 가지 근본적인 변화가 발생합니다.
도구 등록이 "발견"으로 바뀝니다
MCP 이전에는 도구를 에이전트 코드에 등록했습니다. import하고, 인스턴스를 만들고, 에이전트에 주입하는 방식입니다. MCP 이후에는 에이전트가 서버에 "어떤 도구가 있나요?"라고 물어봅니다. 도구의 이름, 설명, 파라미터 스키마를 런타임에 받아옵니다.
이게 왜 중요하냐면, LLM에게 넘기는 도구 목록이 동적으로 구성된다는 뜻이기 때문입니다. 에이전트가 LLM에게 "너는 이 도구들을 쓸 수 있어"라고 알려주는 시스템 프롬프트가 MCP 서버의 상태에 따라 바뀝니다.
권한의 경계가 달라집니다
내장 도구는 에이전트 프로세스 안에서 실행됩니다. 에이전트의 권한이 곧 도구의 권한입니다. 하지만 MCP 도구는 별도 프로세스(서버)에서 실행됩니다. 에이전트와 도구 사이에 프로세스 경계가 생깁니다.
이 경계는 양면이 있습니다. 보안 측면에서는 도구가 에이전트 메모리에 직접 접근하지 못하니 격리가 됩니다. 하지만 신뢰 측면에서는, 외부에서 가져온 MCP 서버가 악의적 결과를 반환할 수 있다는 새로운 위험이 생깁니다.
에러 처리의 범위가 넓어집니다
내장 도구의 에러는 대부분 예외(exception)로 처리됩니다. MCP 도구의 에러는 여기에 네트워크 에러, 서버 프로세스 종료, 타임아웃, 프로토콜 버전 불일치까지 추가됩니다. 에이전트는 "도구가 실패했다"와 "도구에 연결할 수 없다"를 구분해야 합니다.
# 에러 처리의 복잡도 증가
try:
result = mcp_client.call_tool("create_issue", params)
except ToolExecutionError:
# 도구는 실행됐는데 실패한 경우
retry_or_report(result)
except ConnectionError:
# 서버에 연결 자체가 안 되는 경우
fallback_or_skip()
except TimeoutError:
# 서버가 응답을 안 하는 경우
cancel_and_notify()
except ProtocolError:
# 프로토콜 버전이 맞지 않는 경우
reconnect_with_negotiation()
도구 설명이 곧 인터페이스입니다
MCP에서 LLM은 도구의 코드를 보지 못합니다. description과 inputSchema만 봅니다. 따라서 도구의 설명 텍스트가 사실상 LLM과의 인터페이스 계약입니다. 설명이 모호하면 LLM이 도구를 잘못 호출하고, 스키마가 부정확하면 파라미터가 틀립니다.
이건 REST API에서 OpenAPI 스펙이 중요한 이유와 같습니다. 차이가 있다면, REST API의 클라이언트는 사람이 작성한 코드이고, MCP의 클라이언트는 LLM이라는 점입니다. LLM은 타입 에러를 컴파일러처럼 잡아주지 않습니다.
MCP의 현실적 한계
구조를 뜯어보면 MCP가 만능은 아닙니다. 실무에서 부딪히는 한계가 분명히 있습니다.
레이턴시가 추가됩니다
내장 도구는 함수 호출이니까 마이크로초 단위입니다. MCP 도구는 프로세스 간 통신이 들어갑니다. stdio면 수 밀리초, HTTP면 수십~수백 밀리초입니다. 에이전트가 한 턴에 도구를 5번 호출하면, 이 지연이 누적됩니다.
코딩 에이전트는 파일 읽기, grep, 파일 쓰기를 한 턴에 반복하는 경우가 많습니다. 이런 고빈도 도구를 MCP로 돌리면 체감 속도 차이가 납니다. 그래서 실제로 Claude Code는 핵심 도구(파일 읽기/쓰기, Bash 실행)는 내장으로 두고, 확장 도구만 MCP로 처리합니다.
보안 표면이 넓어집니다
MCP 서버를 추가할 때마다 에이전트가 신뢰해야 하는 외부 코드가 늘어납니다. npm 패키지 생태계에서 공급망 공격이 문제가 되듯, MCP 서버 생태계에서도 같은 문제가 발생할 수 있습니다.
악의적 MCP 서버가 도구 실행 결과에 프롬프트 인젝션을 심을 수 있습니다. 에이전트가 결과를 그대로 LLM에 넘기면, LLM의 행동을 조작할 수 있습니다. MCP 2025-11-25 스펙에서 OAuth 2.1 기반 인증 프레임워크를 도입한 것도 이런 보안 문제를 인식했기 때문입니다.
버전 관리가 복잡해집니다
MCP 프로토콜 자체도 버전이 있고, 각 MCP 서버의 도구 스키마도 버전이 있습니다. 서버가 도구의 파라미터를 바꾸면, 기존에 잘 돌던 에이전트 워크플로우가 깨질 수 있습니다. API 버저닝 문제가 그대로 MCP에도 나타납니다.
상태 관리의 어려움
MCP는 상태가 있는(stateful) 세션 프로토콜입니다. 서버가 재시작되면 세션이 끊기고, 로드 밸런서 뒤에 여러 서버 인스턴스를 두면 세션 고정(sticky session)이 필요합니다. 스케일링이 쉽지 않습니다. 2026년 MCP 로드맵에서 이 문제를 해결하기 위해 Streamable HTTP 전송 개선을 핵심 과제로 잡은 것도 이 때문입니다.
실무에 가져갈 수 있는 원칙
MCP의 구조를 뜯어본 뒤, 실무에서 에이전트를 설계할 때 적용할 수 있는 원칙을 정리합니다.
고빈도 도구는 내장, 확장 도구는 MCP로. 파일 읽기/쓰기처럼 매 턴 호출되는 도구를 MCP로 빼면 레이턴시가 누적됩니다. 핵심 도구는 내장으로 두고, 특정 프로젝트에서만 쓰는 도구(특정 DB, 내부 API, 사내 도구)를 MCP로 연결하는 것이 현실적입니다.
도구 설명에 투자하세요. MCP 서버를 만들 때 구현 코드보다 description을 더 정성 들여 쓰세요. LLM이 도구를 올바르게 호출하느냐는 description의 품질에 달려 있습니다. "언제 이 도구를 쓰고, 언제 쓰지 말아야 하는지"를 명확히 적어야 합니다.
신뢰 경계를 명시적으로 설정하세요. 모든 MCP 서버를 동등하게 신뢰하지 마세요. 사내에서 만든 서버와 오픈소스 서버의 권한을 다르게 설정하고, MCP 도구의 실행 결과를 LLM에 넘기기 전에 검증 레이어를 두는 것을 고려하세요.
점진적으로 도입하세요. 처음부터 모든 도구를 MCP로 전환할 필요 없습니다. 기존 에이전트에 MCP 클라이언트를 하나 붙이고, 한두 개 서버부터 연결해보면서 레이턴시와 안정성을 검증하세요.
요약
- MCP 이전의 에이전트는 도구마다 개별 통합 코드를 작성해야 했고, 도구 목록이 빌드 타임에 고정되는 구조였다
- MCP는 Host → Client → Server 3계층 구조로, 에이전트와 도구 사이에 표준화된 연결 레이어를 둔다
- 연결 레이어가 생기면 도구 발견이 동적으로 바뀌고, 도구 생태계가 에이전트 간에 공유되며, 통합 책임이 분리된다
- 현실적으로는 레이턴시 추가, 보안 표면 확대, 버전 관리 복잡도, 상태 관리 어려움이 존재한다
- 실무 원칙: 고빈도 도구는 내장으로, 도구 설명에 투자하고, 신뢰 경계를 명시적으로 설정하며, 점진적으로 도입한다
관련글:
- AI 코딩 에이전트 설계 원리 7가지 — 도구 호출 추상화 원리
- AI 코딩 에이전트 보안 사고는 어떻게 터지는가 — 연결 레이어의 보안 리스크
'AI 코딩 에이전트' 카테고리의 다른 글
| Kafka 동기화 트랜잭션에서 실제로 자주 터지는 장애 5가지 (0) | 2026.04.04 |
|---|---|
| AI 코딩 에이전트 보안 사고는 어떻게 터지는가: 권한, 토큰, 쉘의 삼중주 (0) | 2026.04.04 |
| AI 코딩 에이전트는 왜 '완전 자동'이 아니라 '감독 가능한 자동화'여야 하는가 (1) | 2026.04.04 |
| Cursor 3는 Claude Code, Codex와 뭐가 다른가: AI 코딩 에이전트 UX 비교 (0) | 2026.04.04 |
| Claude Code 해부학 이후: AI 코딩 에이전트 설계 원리 7가지 (5) | 2026.04.04 |
