23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
MCP(Model Context Protocol)는 현재 기준으로 사실상 AI 모델 연동의 표준처럼 사용되고 있는 것 같습니다.
2024년 11월, Anthropic이 처음 공개한 이후 빠르게 주목을 받으며 성장세를 이어가고 있는데요.
아직 발전 중인 단계이지만, JSON-RPC 기반의 심플한 구조와 다양한 SDK, 프레임워크 제공 덕분에 개발자들에게 큰 관심을 받고 있습니다.
MCP는 공식 소개 문서에서도 언급되듯이, ‘AI의 USB-C’로 비유되곤 합니다.
하나의 단일 인터페이스로 다양한 기기와 연결할 수 있는 USB-C처럼, MCP 역시 LLM이 다양한 외부 시스템(도구, 데이터 소스 등)과 일관된 방식으로 통신할 수 있게 해줍니다.
조금 다른 이야기이지만, 저는 이 개념이 기존의 운영체제(OS) 구조와도 맞닿아 있다고 생각합니다.
우리가 사용하는 OS, 특히 리눅스는 디바이스 드라이버를 파일처럼 추상화하여 상호작용하죠.
그리고 이 구조는 HAL(Hardware Abstraction Layer)이라는 공통 규약을 바탕으로 구성되어 있습니다.
이 구조에서 착안하면, LLM과 MCP를 활용해 다양한 외부 시스템(디바이스, API, 내부 도구 등)을 규격화해 제어할 수 있다는 점이 중요합니다.
예를 들어, IoT 디바이스의 경우 그동안 제조사마다 각기 다른 제어 인터페이스를 연동해야 했지만,
MCP 기반으로 통일된 인터페이스를 제공하면 LLM을 통해 자연어로 손쉽게 제어가 가능합니다.
제조사는 자신의 디바이스에 맞는 MCP Server만 제공하면 되는 구조인 것이죠.
실제로 엔트로픽의 Claude는 ‘Computer Use’ 기능을 통해 브라우저를 조작하거나 파일을 열고 편집하는 작업까지 수행할 수 있고,
OpenAI 역시 ‘Operator’ 개념을 통해 LLM이 외부 기능과 직접 연동되도록 하는 실험을 진행하고 있습니다.
이런 흐름을 보면, 조만간 운영체제 자체를 LLM이 직접 제어하는, 완전히 새로운 형태의 OS가 등장할 수 있으리라 기대됩니다.
앞으로 어떤 새로운 프로토콜이 등장할지는 알 수 없지만, 공통 인터페이스라는 구조적 장점만으로도 다양한 가능성을 열어줄 수 있다는 점은 분명해 보입니다.
그런 점에서 MCP는 LLM 활용에 있어 하나의 강력한 시작점을 제시하며, 많은 개발자와 기업의 관심을 받고 있는 것 같습니다.
이번 포스트에서는 Python 기반으로 MCP(Model Context Protocol)를 적용하는 기본적인 코드 구현 방법을 단계적으로 소개해보려 합니다.
MCP Server 구현부터, 간단한 커맨드라인 스크립트를 통한 MCP Tool 호출,
마지막으로 Streamlit을 활용해 자연어로 MCP Tool을 실행하는 Agent 생성까지 차근차근 따라가며 실습해보겠습니다.
MCP의 핵심은 호스트 애플리케이션이 여러 서버에 연결할 수 있는 클라이언트-서버 아키텍처를 따르는 것입니다.
MCP 호스트: MCP를 통해 데이터에 액세스하려는 클로드 데스크톱, IDE 또는 AI 도구와 같은 프로그램
MCP 클라이언트: 서버와 1:1 연결을 유지하는 프로토콜 클라이언트
MCP 서버: 표준화된 모델 컨텍스트 프로토콜(
로컬 데이터 소스)을 통해 각각 특정 기능을 노출하는 경량 프로그램: MCP 서버가 안전하게 액세스할 수 있는 내 컴퓨터의 파일, 데이터베이스 및 서비스
원격 서비스: MCP 서버가 연결할 수 있는 인터넷을 통해 사용 가능한 외부 시스템(예: API를 통해)
위 설명에서 클라이언트-서버 연동 구조라고 하여 MCP Server가 항상 어떤 원격지에 있는 것은 아닙니다.
MCP Host, Client, Server가 모두 한 PC에서도 실행될 수 있고 Server만 원격지에 존재하는 경우도 있습니다.
MCP Server를 로컬 PC에 설치해서 사용 가능한 도구들이 다양하게 존재합니다. (ex, Desktop Commander)
먼저, MCP의 기반이 되는 MCP Server를 직접 만들어보겠습니다. Python 구현체인 FastMCP를 활용해 간단한 MCP Server를 구성해볼 예정인데요.
이 포스트에서는 복잡한 내부 구조보다는 실행에 초점을 맞춰, 빠르게 테스트해볼 수 있는 최소한의 형태로 구현해보겠습니다.
본 포스팅에서 python 패키지 매니저로는 uv를 사용하겠습니다.
mkdir mcp_server
cd mcp_server
uv init --python 3.12식사 메뉴를 추천해주는 MCP Tool 입니다. FastMCP는 type hint와 docstring을 기반으로 자동으로 스키마를 생성하는데요,
이 구조 덕분에 LLM Host가 Tool을 정확하게 이해하고 호출할 수 있습니다.
따라서 함수의 파라미터에 명확한 타입 지정과 상세한 설명(description) 을 적어주는 것이 중요합니다.
reco_food.py
from mcp.server.fastmcp import FastMCP
import random
mcp = FastMCP("reco_food")
menus = {
"중식": ["짜장면", "마라탕"],
"일식": ["스시", "우동"],
"한식": ["비빔밥", "김치찌개"]
}
@mcp.tool()
def recommend_food() -> str:
"""식사 메뉴를 추천하는 함수
Returns:
str: 오늘 메뉴
"""
# 카테고리 무작위 선택
category = random.choice(list(menus.keys()))
# 해당 카테고리에서 음식 무작위 선택
menu = random.choice(menus[category])
return f"오늘의 메뉴는 ({category}): {menu}"
if __name__ == "__main__":
print("Starting MCP server...")
mcp.run()이제 구현한 MCP Server를 실제로 테스트해보기 위해, Claude에 연결해보겠습니다.
Claude Desktop을 이용해 LLM이 MCP Tool을 어떻게 호출하는지 확인해볼 수 있습니다.
Claude -> 설정 -> 개발자 메뉴에 들어가면 MCP Tool을 등록하는 화면이 있습니다. '설정 편집'을 누르고 claude_desktop_config.json을 수정합니다.
해당 설정 파일에 들어갈 내용은 다음과 같습니다. command는 MCP Server를 실행할 명령입니다. args에는 실행에 필요한 여러 파라미터를 넣을 수 있습니다.
{
"mcpServers": {
"reco_food": {
"command": "/Users/victor/.local/bin/uv",
"args": [
"--directory",
"/Users/victor/dev/mcp-example/mcp_server",
"run",
"reco_food_server.py"
]
}
}
}설정을 마친 뒤에는 Claude Desktop 프로그램을 재실행합니다. 정상적으로 연동되었다면, 아래와 같이 Tool 목록에서 앞서 등록한 MCP Tool이 표시되는 것을 확인할 수 있습니다.
이제 "점심 메뉴 추천해줘"라고 입력해보겠습니다. 입력을 하면 다음과 같이 Tool 호출 메뉴가 보입니다.
Tool 호출 허용을 하면 아래와 같이 앞서 만들었던 MCP Tool이 호출되고 실행된 결과를 LLM이 해석하여 자연어로 표현해줍니다.
Claude와 같은 MCP Host를 만들기 전에 Tool을 호출하는 과정부터 진행해보겠습니다.
다음은 입력 파라미터로 mcp server 스크립트 경로를 받아 MCP Client를 동작시키는 코드입니다.
import sys
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def connect_to_server(server_script_path: str):
"""Connect to an MCP server and list available tools"""
if not server_script_path.endswith('.py'):
raise ValueError("Server script must be a .py file")
server_params = StdioServerParameters(
command="python",
args=[server_script_path],
env=None,
)
async with stdio_client(server_params) as (stdio, write):
session = ClientSession(stdio, write)
async with session:
await session.initialize()
response = await session.list_tools()
tools = response.tools
print("\n✅ Connected to server with tools:", [tool.name for tool in tools])
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python list_tools.py <path_to_server_script>")
sys.exit(1)
asyncio.run(connect_to_server(sys.argv[1]))다음과 같이 실행하면 입력한 경로의 mcp server 스크립트를 연결하여 Tool List를 출력하는 것을 확인할 수 있습니다.
uv run list_tools.py ../mcp_server/reco_food_server.py실행결과
[05/30/25 11:50:22] INFO Processing request of type server.py:551
ListToolsRequest
✅ Connected to server with tools: ['recommend_food']한 단계 더 나아가 이번엔 LLM Agent로 명령을 주어 Tool을 호출해보도록 하겠습니다.
아래 코드는 "오늘 점심 메뉴 추천해줘"라는 질의가 고정되어 들어가는 python 스크립트 입니다.
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
async def main():
client = MultiServerMCPClient(
{
"reco_food": {
"command": "uv",
"args": ["--directory", "/Users/victor/dev/mcp/mcp-example/mcp_server", "run", "reco_food_server.py"], # 실제 경로로 대체하세요
"transport": "stdio",
}
}
)
tools = await client.get_tools()
# openai API key가 필요하며, openai 패키지가 설치되어 있어야 합니다
agent = create_react_agent("openai:gpt-4.1", tools)
math_response = await agent.ainvoke({"messages": "오늘 점심 메뉴 추천해줘"})
print("Math Response:", math_response)
# asyncio 루프 실행
if __name__ == "__main__":
asyncio.run(main())실행 결과
실행 결과를 보면 Tool이 호출된 것을 알 수 있고 응답 결과 또한 의도한대로 잘 나온 것을 확인할 수 있습니다.
Math Response: {'messages': [HumanMessage(content='오늘 점심 메뉴 추천해줘', additional_kwargs={}, response_metadata={}, id='a2aec9b0-dac9-4ec7-908a-504419fd23c6'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_xEOFbXG9yk1x5DNr3RgwCxgb', 'function': {'arguments': '{}', 'name': 'recommend_food'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 54, 'total_tokens': 65, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-2025-04-14', 'system_fingerprint': 'fp_51e1070cf2', 'id': 'chatcmpl-BclCe00OQcs4kWBaV8ac5bWb3q5Fq', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--7f6b66d1-7fec-4d53-b589-59a05d023bb9-0', tool_calls=[{'name': 'recommend_food', 'args': {}, 'id': 'call_xEOFbXG9yk1x5DNr3RgwCxgb', 'type': 'tool_call'}], usage_metadata={'input_tokens': 54, 'output_tokens': 11, 'total_tokens': 65, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='오늘의 메뉴는 (중식): 짜장면', name='recommend_food', id='5aa1abc6-88f7-4f3b-8a23-bc0aa34f918b', tool_call_id='call_xEOFbXG9yk1x5DNr3RgwCxgb'), AIMessage(content='오늘의 점심 메뉴로 짜장면을 추천합니다! 맛있게 드세요.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 85, 'total_tokens': 106, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4.1-2025-04-14', 'system_fingerprint': 'fp_51e1070cf2', 'id': 'chatcmpl-BclChShCorugWoZ1etFmoDXp9WnsR', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--e36ba24f-d4b4-4f0b-8643-832e0af70bd9-0', usage_metadata={'input_tokens': 85, 'output_tokens': 21, 'total_tokens': 106, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}이번엔 streamlit을 이용해 인터랙션이 가능한 채팅 형태의 Web UI 프로그램을 만들어보겠습니다.
import streamlit as st
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_core.messages.ai import AIMessageChunk
st.title("ChatGPT-like Clone with MCP (Async)")
# MCP 클라이언트 설정
client = MultiServerMCPClient(
{
"reco_food": {
"command": "uv",
"args": [
"--directory",
"/Users/victor/dev/mcp/mcp-example/mcp_server",
"run",
"reco_food_server.py"
],
"transport": "stdio",
},
"phonebook": {
"command": "uv",
"args": [
"--directory",
"/Users/victor/dev/mcp/mcp-example/mcp_server",
"run",
"phonebook_server.py"
],
"transport": "stdio",
}
}
)
# MCP 도구 비동기 로딩
async def init_agent():
tools = await client.get_tools()
agent = create_react_agent(
"openai:gpt-4o-mini",
tools=tools,
prompt="You are a helpful assistant."
)
return agent
# 세션 상태 초기화
if "messages" not in st.session_state:
st.session_state.messages = []
if "agent" not in st.session_state:
st.session_state.agent = asyncio.run(init_agent())
# LangGraph agent 호출 비동기 함수
async def call_agent_async(agent, messages):
return await agent.ainvoke({"messages": messages})
# 이전 대화 표시
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 사용자 입력 처리
if prompt := st.chat_input("What is up?"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
response = asyncio.run(call_agent_async(st.session_state.agent, st.session_state.messages))
new_messages = response["messages"]
# 마지막 AI 메시지 (최종 사용자 응답용)
last_ai_index = next(
(i for i in reversed(range(len(new_messages))) if new_messages[i].type == "ai" and new_messages[i].content),
None
)
if last_ai_index is not None:
last_ai_msg = new_messages[last_ai_index]
# 직전 tool call AIMessage 확인
tool_call_msg = None
for i in range(last_ai_index - 1, -1, -1):
msg = new_messages[i]
if msg.type == "ai" and msg.additional_kwargs.get("tool_calls"):
tool_call_msg = msg
break
# tool_calls 출력
if tool_call_msg:
st.markdown("**[Tool 호출됨]**")
for call in tool_call_msg.additional_kwargs["tool_calls"]:
function_name = call["function"]["name"]
call_id = call["id"]
st.markdown(f"- `{function_name}` 함수 호출됨")
# tool 응답 출력
tool_result_msg = next(
(m for m in new_messages if m.type == "tool" and m.tool_call_id == call_id),
None
)
if tool_result_msg:
st.markdown(f"**[Tool 응답 결과]**\n- `{function_name}` 결과: {tool_result_msg.content}")
# 마지막 AI 응답 출력
st.markdown(last_ai_msg.content)
# 세션 상태에 저장
st.session_state.messages.append({
"role": "assistant",
"content": last_ai_msg.content
})실행 명령
uv run streamlit run main.py기존에 서비스하던 기능을 Tool로 만들어 다양한 LLM Host와 연동하려는 경우 MCP Server를 Backend API의 Wrapper 형태로 구현해서 동작시킬 수 있습니다.
Backend
간단한 REST API를 만들어보겠습니다. CSV파일에 있는 연락처를 불러와 반환해주는 API입니다.
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import csv
app = FastAPI()
@app.get("/phonebook")
def read_csv_data():
data = []
try:
with open("phonebook.csv", newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
data.append(row)
except FileNotFoundError:
return JSONResponse(status_code=404, content={"message": "CSV file not found"})
return data해당 기능을 Tool로 제공하는 MCP Server는 다음과 같이 구현합니다.
from mcp.server.fastmcp import FastMCP
import requests
mcp = FastMCP("phonebook")
@mcp.tool()
def fetch_phonebook():
"""phoneboo에서 연락처를 가져오는 함수
Returns:
json
"""
url = "http://127.0.0.1:8000/phonebook"
try:
response = requests.get(url)
response.raise_for_status() # HTTP 오류 발생 시 예외 발생
data = response.json()
return data
except requests.exceptions.RequestException as e:
print(f"Error fetching data: {e}")
return None
if __name__ == "__main__":
print("Starting MCP server...")
mcp.run()Backend를 동작시키고 앞서 만든 Web UI에서 연락처를 묻는 질의를 하면 아래와 같이 응답하는 걸 확인할 수 있습니다.
위 동작은 MCP Server에서 외부 Service를 호출하고 그 결과를 MCP Host에 응답결과로 주어 나온 결과를 보여줍니다.
Cluade Desktop, Cursor 등의 MCP Host들도 이것과 유사한 방식으로 구동됩니다.
이번 포스팅에서는 MCP를 활용해 LLM Agent와 어떻게 연동할 수 있는지, 간단한 예제 프로그램을 통해 빠르게 실습해보았습니다.
MCP에는 Tool 선언 외에도 Resource, Prompt 등 다양한 구성 요소가 있지만, 이번 글에서는 핵심 흐름을 빠르게 이해하고 실행해보는 데 초점을 맞춰 필수적인 내용 위주로만 다뤘습니다.
MCP는 아직 발전 초기 단계에 있지만, LLM과 외부 시스템을 연결하는 표준 인터페이스로서 매우 강력한 가능성을 보여주고 있습니다.
이번 포스팅이 MCP의 개념을 이해하고, 실제로 간단한 MCP Server와 Tool을 구현해보는 데 도움이 되었기를 바랍니다.
포스트에 소개했던 코드는 다음 github repository에 업로드하였으니 참고하시기 바랍니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.