에이전트 시리즈 (10): MCP 프로토콜 — 도구 생태계의 표준화
요약
MCP(Model Context Protocol)는 에이전트마다 도구를 개별적으로 정의하던 기존 방식의 한계를 극복하기 위한 표준 프로토콜입니다. 도구를 독립적인 서비스로 분리하여 JSON-RPC를 통해 여러 에이전트가 공유하고 동적으로 발견할 수 있는 아키텍처를 제공합니다.
핵심 포인트
- 기존 함수 호출 방식의 코드 중복 및 유지보수 문제 해결
- 도구를 독립된 프로세스로 분리하여 언어 독립성 확보
- JSON-RPC 기반의 프로세스 간(cross-process) 호출 지원
- 동적 도구 발견(Dynamic Tool Discovery)을 통한 확장성 강화
더 많은 도구, 더 많은 혼란
에이전트 (Agent)를 구축한 후, 가장 먼저 하는 일은 보통 검색, 코드 실행, 데이터베이스 쿼리, API 호출과 같은 도구 (tools)를 부여하는 것입니다.
전통적인 함수 호출 (Function Calling) 방식에서 도구 정의는 다음과 같이 보입니다:
@tool
def search_jira(query: str) -> str:
"""Jira 티켓 검색"""
...
이 방식은 매우 잘 작동합니다 — 두 번째 에이전트를 구축하기 시작할 때까지는 말이죠.
문제점: 에이전트 B 역시 search_jira가 필요합니다. 함수를 임포트(import)하거나 복사해 옵니다. 그다음 에이전트 C, 에이전트 D도 마찬가지입니다. 도구 정의가 코드베이스 전반에 걸쳐 흩어지기 시작합니다. 어느 날 search_jira의 로직을 업데이트해야 한다면, 네 개의 서로 다른 파일에 흩어져 있는 참조 파일들을 모두 찾아내야 합니다.
이것이 바로 **MCP (Model Context Protocol)**가 해결하고자 하는 문제입니다: 도구를 "각 에이전트가 스스로 정의하는 것"에서 "프로토콜을 통해 어떤 에이전트든 연결할 수 있는 공유 서비스"로 전환하는 것입니다.
MCP의 3계층 아키텍처
MCP는 도구 호출을 세 가지 역할로 나눕니다:
┌────────────────────────────────────────────────────────────────┐
│ MCP 아키텍처 (Architecture) │
├──────────────────┬──────────────────────────────────────────────┤
...
함수 호출 (Function Calling)과의 핵심적인 아키텍처 차이점은 서버 (Server)가 독립적인 프로세스라는 점입니다:
- 함수 호출 (Function Calling): 도구는 에이전트 코드 내에 작성된 Python 함수이며, 프로세스 내부 (in-process)에서 호출됩니다.
- MCP: 도구는 독립적인 서비스이며, JSON-RPC를 통해 프로세스 간 (cross-process) 호출됩니다.
독립적인 프로세스라는 것은 다음을 의미합니다: 도구는 어떤 언어로든 구현될 수 있고, 수많은 에이전트가 공유할 수 있으며, 에이전트 코드를 수정하지 않고도 업데이트할 수 있습니다.
데모 1: 전통적인 함수 호출 (Function Calling)의 한계
@lc_tool
def calculator(expression: str) -> str:
"""간단한 산술식을 계산합니다."""
...
세 가지 테스트 질문, 실제 실행:
Q: 2 ** 10 + 100 / 4는 무엇인가요?
A: 2 ** 10 + 100 / 4의 결과는 1049.0입니다.
...
완벽하게 작동합니다. 문제는 아키텍처에 있습니다:
이 파일에 정의된 도구: ['calculator', 'text_stats', 'weather_mock']
만약 에이전트 B도 이 도구들이 필요하다면: 복사-붙여넣기 또는 재임포트(re-import)
...
데모 2: MCP 서버 — 동적 도구 발견 (Dynamic Tool Discovery)
FastMCP를 사용하여 독립형 MCP 서버로 구현된 동일한 세 가지 도구:
# tools_server.py
from mcp.server.fastmcp import FastMCP
...
클라이언트 측 — 연결 및 발견:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
...
측정된 list_tools() 응답:
Server: demo-tools
Discovered 3 tools:
● calculator — 간단한 산술 식을 계산합니다 (예: '2 ** 10', '100 / 7').
...
핵심 포인트: 클라이언트 코드에는 calculator, text_stats, 또는 weather_mock에 대한 언급이 전혀 없습니다. 클라이언트는 오직 list_tools()만을 호출했으며, 도구 카탈로그는 서버로부터 동적으로 가져왔습니다.
직접적인 도구 호출 (LLM 미사용):
calculator('2 ** 10 + 100 / 4') → 2 ** 10 + 100 / 4 = 1049.0
weather_mock('Shanghai') → {"city": "Shanghai", "temp": 22, "condition": "cloudy", "humidity": 75}
text_stats(...) → {"words": 9, "sentences": 3, "chars": 53}
데모 3: MCP 도구를 사용하는 LLM 에이전트
langchain-mcp-adapters는 MCP 도구 스키마(schema)를 LangChain 도구 객체로 자동 변환합니다. 에이전트 코드는 데모 1과 거의 동일해 보이지만, 차이점은 도구가 어디에서 오느냐 하는 점입니다:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient(
...
참고: MCP 도구는 비동기(async) 방식입니다. 반드시 await agent.ainvoke()를 사용해야 합니다. 동기 방식인 agent.invoke()를 사용하면 NotImplementedError가 발생합니다.
세 가지 질문에 대한 멀티 도구 테스트, 실제 결과:
Questions:
1. What is sqrt(144) + 2 ** 8?
2. What's the weather in Shenzhen?
...
세 개의 CallToolRequest 이벤트가 모두 서버 로그에 나타났습니다. 즉, 에이전트가 세 가지 질문 모두를 MCP를 통해 라우팅(routing)했습니다.
날씨 관련 면책 조항은 유용한 교육적 순간을 제공합니다: LLM은 description(설명)을 주의 깊게 읽습니다. 도구가 "데모용 — 실제 데이터 아님"이라고 명시하면, LLM은 답변에 적절한 제한 사항을 덧붙입니다. 좋은 도구 설명은 LLM을 더 나은 의사 결정자로 만듭니다.
MCP vs Function Calling: 비교 매트릭스 (Comparison Matrix)
차원 (Dimension) Function Calling (함수 호출) MCP
──────────────────────────────────────────────────────────────────────
도구 정의 (Tool definition) 에이전트 코드 내에 존재 독립적인 MCP 서버 내에 존재
...
MCP 생태계 (The MCP Ecosystem)
MCP 가치의 상당 부분은 점점 늘어나고 있는 기존 서버 카탈로그에서 나옵니다:
공식 MCP 서버 (@modelcontextprotocol/):
server-filesystem — 로컬 파일 읽기/쓰기
server-github — GitHub 저장소, 이슈, PR 작업
...
MCP 서버 개발 체크리스트 (MCP Server Development Checklist)
도구 설계 (Tool Design)
- 각 도구는 명확한 입출력 스키마 (input/output schema)를 가지며 한 가지 일만 수행합니다.
-
description(설명)을 신중하게 작성하십시오 — LLM은 이를 사용하여 도구를 호출할지 여부를 결정합니다. - 위험한 작업 (파일 쓰기, 명령 실행)은 허용 목록 (allowlists) 또는 확인 절차가 필요합니다.
서버 구현 (Server Implementation)
- 빠른 개발을 위해 FastMCP를 사용하십시오. 세밀한 제어가 필요한 경우 저수준(low-level)인
mcp.server.Server를 사용하십시오. - 도구 함수는 문자열 (MCP 프로토콜의 TextContent 타입)을 반환해야 합니다.
- 에러 핸들링 (Error handling): 서술적인 에러 문자열을 반환하십시오. 예외(exception)가 프로토콜 계층으로 전파되도록 두지 마십시오.
전송 방식 선택 (Transport Selection)
- 로컬 도구 → stdio (서브프로세스, 설정 불필요)
- 원격/공유 도구 → HTTP + SSE (인증 및 네트워크 설정 필요)
- 프로덕션 (Production) → 커넥션 풀링 (connection pooling) 및 타임아웃 설정을 고려하십시오.
클라이언트 통합 (Client Integration)
- LangChain/LangGraph 통합을 위해
langchain-mcp-adapters를 사용하십시오. - MCP 도구는 비동기(async) 방식입니다:
agent.invoke()대신await agent.ainvoke()를 사용하십시오. - 여러 서버 사용 시:
MultiServerMCPClient가 모든 연결을 중앙에서 관리합니다.
요약 (Summary)
다섯 가지 핵심 요점:
- MCP는 단순한 도구 호출(Tool Calling)이 아닌 도구 관리 문제를 해결합니다: Function Calling (함수 호출)은 에이전트별로 도구를 바인딩하는 방식이지만, MCP는 에이전트를 가로지르는 도구 서비스(Cross-agent tool services)입니다.
- 동적 발견(Dynamic discovery)이 핵심 역량입니다:
list_tools()를 통해 에이전트는 도구의 이름을 미리 알지 못하더라도 도구를 찾아내고 사용할 수 있습니다. - 독립적인 프로세스는 언어 간 도구 사용을 가능하게 합니다: JavaScript MCP 서버를 Python 에이전트가 호출할 수 있습니다. 이는 JSON-RPC가 언어에 구애받지 않기(Language-agnostic) 때문입니다.
- Claude Code의 도구는 MCP입니다: Claude Code를 사용하여 파일을 읽거나 명령을 실행할 때마다, 바로 이 프로토콜이 작동하고 있는 것입니다.
- 비동기(Async)는 MCP 도구 계약의 핵심입니다: MCP 도구는 비동기 호출을 통해 실행됩니다. LangChain 통합 시
invoke()가 아닌ainvoke()가 필요합니다.
다음 예고: A2A 프로토콜과 에이전트 네트워크 (A2A Protocol and Agent Networks) — 에이전트들은 서로 어떻게 협업할까요? MCP는 에이전트 ↔ 도구(Agent ↔ Tool)를 다루고, A2A는 에이전트 ↔ 에이전트(Agent ↔ Agent)를 다룹니다.
참고 문헌 (References)
- Model Context Protocol 공식 사이트
- MCP Python SDK
- langchain-mcp-adapters
- 이 시리즈의 전체 데모 코드: agent-09-mcp
저의 홈페이지에서 더 유용한 지식과 흥미로운 제품들을 찾아보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기