[MCP] MCP 클라이언트 개발하기




이번 포스트에서는 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

작동 원리

  1. 사용자의 질의가 Claude로 전달됩니다.
  2. Claude가 적절한 도구를 선택합니다.
  3. 클라이언트는 선택된 도구를 MCP 서버를 통해 실행합니다.
  4. 결과가 다시 Claude로 전달됩니다.
  5. Claude가 자연어 응답을 제공합니다.

모범 사례 및 보안

  • API 키 보안 유지
  • 적절한 오류 처리 및 리소스 관리
  • 서버 응답 검증 및 보안 메커니즘 사용

참고 자료

  • https://modelcontextprotocol.io/introduction



Leave a Comment