이번 포스트에서는 MCP(Model Context Protocol)를 사용하여 자신만의 클라이언트를 구축하고, 다양한 MCP 서버와 연동하여 LLM 기반 챗봇을 만드는 방법을 알아보겠습니다.
개발 환경 준비
이 튜토리얼을 진행하기 위해 필요한 사항은 다음과 같습니다:
- Mac 또는 Windows 컴퓨터
- 최신 버전의 Python 설치
- uv 최신 버전 설치
프로젝트 설정
아래 명령어로 프로젝트와 환경을 설정합니다:
uv init mcp-client
cd mcp-client
uv venv
# 가상 환경 활성화
# Windows:
.venv\Scripts\activate
# MacOS/Linux:
source .venv/bin/activate
# 필요한 패키지 설치
uv add mcp anthropic python-dotenv
rm main.py
touch client.py
API 키 설정
Anthropic API 키를 .env
파일에 저장합니다:
touch .env
.env
파일에 다음 내용을 추가합니다:
ANTHROPIC_API_KEY=<your key here>
.gitignore
파일에 .env
를 추가하여 보안을 유지합니다:
echo ".env" >> .gitignore
클라이언트 구축
기본 구조 설정
client.py
파일에 기본 클래스를 설정합니다:
import asyncio
from typing import Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
class MCPClient:
def __init__(self):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.anthropic = Anthropic()
서버 연결 관리
서버에 연결하는 기능을 추가합니다:
async def connect_to_server(self, server_script_path: str):
is_python = server_script_path.endswith('.py')
is_js = server_script_path.endswith('.js')
if not (is_python or is_js):
raise ValueError("Server script must be a .py or .js file")
command = "python" if is_python else "node"
server_params = StdioServerParameters(command=command, args=[server_script_path], env=None)
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
response = await self.session.list_tools()
tools = response.tools
print("Connected to server with tools:", [tool.name for tool in tools])
질의 처리 로직
질의 처리 및 도구 호출 로직을 구현합니다:
async def process_query(self, query: str) -> str:
messages = [{"role": "user", "content": query}]
response = await self.session.list_tools()
available_tools = [{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
} for tool in response.tools]
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=messages,
tools=available_tools
)
final_text = []
for content in response.content:
if content.type == 'text':
final_text.append(content.text)
elif content.type == 'tool_use':
result = await self.session.call_tool(content.name, content.input)
final_text.append(result.content)
return "\n".join(final_text)
채팅 인터페이스
대화형 채팅 루프를 추가합니다:
async def chat_loop(self):
print("MCP Client Started! Type 'quit' to exit.")
while True:
query = input("\nQuery: ").strip()
if query.lower() == 'quit':
break
response = await self.process_query(query)
print("\n" + response)
async def cleanup(self):
await self.exit_stack.aclose()
메인 실행 부분
메인 실행 로직을 추가합니다:
async def main():
import sys
if len(sys.argv) < 2:
print("Usage: python client.py <path_to_server_script>")
sys.exit(1)
client = MCPClient()
await client.connect_to_server(sys.argv[1])
await client.chat_loop()
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())
클라이언트 실행
클라이언트를 실행하려면:
uv run client.py path/to/server.py
예:
uv run client.py ./server/weather.py
작동 원리
- 사용자의 질의가 Claude로 전달됩니다.
- Claude가 적절한 도구를 선택합니다.
- 클라이언트는 선택된 도구를 MCP 서버를 통해 실행합니다.
- 결과가 다시 Claude로 전달됩니다.
- Claude가 자연어 응답을 제공합니다.
모범 사례 및 보안
- API 키 보안 유지
- 적절한 오류 처리 및 리소스 관리
- 서버 응답 검증 및 보안 메커니즘 사용
참고 자료
- https://modelcontextprotocol.io/introduction