본문으로 건너뛰기

MCP 서버 구축하기

이 튜토리얼에서는 간단한 MCP 날씨 서버를 구축하고 호스트인 Claude Desktop에 연결합니다.

구축할 내용

두 가지 도구, 즉 get_alertsget_forecast를 제공하는 서버를 구축합니다. 그런 다음 이 서버를 MCP 호스트(이 예에서는 Claude Desktop)에 연결합니다.

MCP 핵심 개념

MCP 서버는 다음과 같은 세 가지 주요 기능을 제공할 수 있습니다.

  1. 리소스: 클라이언트가 읽을 수 있는 파일 형태의 데이터(API 응답이나 파일 내용 등)
  2. 도구: LLM이 호출할 수 있는 함수(사용자 승인 필요)
  3. 프롬프트: 사용자가 특정 작업을 수행하도록 돕는 미리 작성된 템플릿

이 튜토리얼에서는 주로 도구에 중점을 둡니다.

날씨 서버 구축을 시작해 보겠습니다! 여기에서 이번에 구축할 전체 코드를 확인할 수 있습니다.

사전 지식

이 빠른 시작 가이드는 다음 항목에 익숙하다고 가정합니다.

  • Python
  • Claude와 같은 LLM

MCP 서버 로깅

MCP 서버를 구현할 때는 로깅을 처리하는 방식에 주의해야 합니다.

STDIO 기반 서버: stdout에 절대 쓰지 마세요. stdout에 쓰면 JSON-RPC 메시지가 손상되어 서버가 작동하지 않습니다. print() 함수는 기본적으로 stdout에 쓰므로 STDIO 서버에서는 사용하지 마세요.

HTTP 기반 서버: 표준 출력 로깅은 HTTP 응답을 방해하지 않으므로 사용해도 됩니다.

모범 사례

  • stderr에 기록하는 표준 라이브러리의 logging 모듈을 사용합니다.
  • logging.getLogger(__name__)을 사용해 모듈마다 로거를 하나씩 만들고 도구에서 호출합니다.

빠른 예제

import logging

logger = logging.getLogger(__name__)

# ❌ Bad (STDIO)
print("Processing request")

# ✅ Good (STDIO)
logger.info("Processing request") # writes to stderr

시스템 요구 사항

  • Python 3.10 이상이 설치되어 있어야 합니다.
  • Python MCP SDK 2.0.0 이상을 사용해야 합니다.

환경 설정

먼저 uv를 설치하고 Python 프로젝트와 환경을 설정합니다.

curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

그런 다음 터미널을 다시 시작하여 uv 명령을 인식하도록 합니다.

이제 프로젝트를 만들고 설정해 보겠습니다.

# Create a new directory for our project
uv init weather
cd weather

# Create virtual environment and activate it
uv venv
source .venv/bin/activate

# Install dependencies
uv add "mcp[cli]"

# Create our server file
touch weather.py
# Create a new directory for our project
uv init weather
cd weather

# Create virtual environment and activate it
uv venv
.venv\Scripts\activate

# Install dependencies
uv add mcp[cli]

# Create our server file
new-item weather.py

이제 서버 구축을 시작해 보겠습니다.

서버 구축하기

패키지 가져오기 및 인스턴스 설정

weather.py 파일 맨 위에 다음 내용을 추가합니다.

from typing import Any

import httpx2
from mcp.server import MCPServer

# Initialize MCPServer
mcp = MCPServer("weather")

# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

httpx2는 SDK 자체에서 사용하는 HTTP 클라이언트이므로 mcp를 설치할 때 이미 함께 설치되었습니다.

MCPServer 클래스는 Python 타입 힌트와 독스트링을 사용해 도구 정의를 자동으로 생성하므로 MCP 도구를 쉽게 만들고 유지 관리할 수 있습니다.

헬퍼 함수

다음으로 National Weather Service API의 데이터를 조회하고 형식을 지정하는 헬퍼 함수를 추가합니다.

async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
async with httpx2.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None


def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
Instructions: {props.get("instruction", "No specific instructions provided")}
"""

도구 실행 구현하기

도구 실행 핸들러는 각 도구의 로직을 실제로 실행합니다. 이제 이를 추가해 보겠습니다.

@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.

Args:
state: Two-letter US state code (e.g. CA, NY)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)

if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."

if not data["features"]:
return "No active alerts for this state."

alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)


@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.

Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)

if not points_data:
return "Unable to fetch forecast data for this location."

# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)

if not forecast_data:
return "Unable to fetch detailed forecast."

# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
forecasts.append(forecast)

return "\n---\n".join(forecasts)

서버 실행하기

마지막으로 서버를 초기화하고 실행합니다.

if __name__ == "__main__":
mcp.run(transport="stdio")

서버가 완성되었습니다! uv run weather.py을 실행하여 MCP 서버를 시작하면 MCP 호스트에서 오는 메시지를 수신합니다.

이제 기존 MCP 호스트인 Claude Desktop에서 서버를 테스트해 보겠습니다.

Claude Desktop으로 서버 테스트하기

먼저 Claude Desktop이 설치되어 있는지 확인하세요. 여기에서 최신 버전을 설치할 수 있습니다. Claude Desktop이 이미 설치되어 있다면 최신 버전으로 업데이트되어 있는지 확인하세요.

사용하려는 MCP 서버를 Claude Desktop에 설정해야 합니다. 텍스트 편집기에서 ~/Library/Application Support/Claude/claude_desktop_config.json에 있는 Claude Desktop 앱 구성 파일을 여세요. 파일이 없다면 새로 만드세요.

예를 들어 VS Code가 설치되어 있다면 다음과 같이 엽니다.

code ~/.config/Claude/claude_desktop_config.json
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
code $env:AppData\Claude\claude_desktop_config.json

그런 다음 mcpServers 키에 서버를 추가합니다. 서버가 하나 이상 올바르게 구성되어 있어야 Claude Desktop에 MCP UI 요소가 표시됩니다.

여기서는 다음과 같이 날씨 서버 하나를 추가합니다.

{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
"run",
"weather.py"
]
}
}
}

이 구성은 Claude Desktop에 다음 내용을 알려 줍니다.

  1. 이름이 "weather"인 MCP 서버가 있습니다.
  2. uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py을 실행해 서버를 시작합니다.

파일을 저장하고 Claude Desktop을 다시 시작하세요.

명령으로 테스트하기

weather 서버가 제공하는 두 도구를 Claude Desktop이 인식하는지 확인해 보겠습니다. "파일, 커넥터 및 기타 항목 추가 /" 아이콘을 찾으면 됩니다.

더하기 아이콘을 클릭한 후 "Connectors" 메뉴 위에 마우스를 올리세요. 목록에 weather 서버가 표시되어야 합니다.

Claude Desktop이 서버를 인식하지 못한다면 디버깅 도움말은 문제 해결 섹션을 참조하세요.

서버가 "Connectors" 메뉴에 표시되면 Claude Desktop에서 다음 명령을 실행해 서버를 테스트할 수 있습니다.

  • 새크라멘토의 날씨는 어때?
  • 텍사스에 현재 발효 중인 기상 특보가 있나요?

내부 동작 방식

질문하면 다음 과정이 진행됩니다.

  1. 클라이언트가 사용자의 질문을 Claude에 전송합니다.
  2. Claude가 사용 가능한 도구를 분석하고 사용할 도구를 결정합니다.
  3. 클라이언트가 MCP 서버를 통해 선택한 도구를 실행합니다.
  4. 결과가 Claude로 다시 전송됩니다.
  5. Claude가 자연어 응답을 작성합니다.
  6. 응답이 사용자에게 표시됩니다!

문제 해결

Claude Desktop 통합 문제

Claude Desktop 로그 확인하기

Claude.app의 MCP 관련 로그는 ~/Library/Logs/Claude(macOS) 또는 ~/.config/Claude/logs/(Linux)의 로그 파일에 기록됩니다.

  • mcp.log에는 MCP 연결 및 연결 실패에 관한 일반 로그가 포함됩니다.
  • mcp-server-SERVERNAME.log 형식의 파일에는 해당 서버의 stderr 출력이 포함됩니다. Stdio 서버는 모든 로그에 stderr를 사용할 수 있으므로 이 파일에는 오류만 기록되는 것은 아닙니다.

다음 명령을 실행하면 최근 로그를 나열하고 새 로그를 계속 확인할 수 있습니다.

# Check Claude's logs for errors
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
# Check Claude's logs for errors
tail -n 20 -f ~/.config/Claude/logs/mcp*.log

Claude에 서버가 표시되지 않는 경우

  1. claude_desktop_config.json 파일의 구문을 확인합니다.
  2. 프로젝트 경로가 상대 경로가 아닌 절대 경로인지 확인합니다.
  3. Claude Desktop을 완전히 다시 시작합니다.

도구 호출이 아무 메시지 없이 실패하는 경우

Claude가 도구를 사용하려고 하지만 실패하는 경우 다음을 확인하세요.

  1. Claude 로그에서 오류를 확인합니다.
  2. 서버가 오류 없이 빌드되고 실행되는지 확인합니다.
  3. Claude Desktop을 다시 시작해 봅니다.

어떤 방법으로도 해결되지 않으면 어떻게 해야 하나요?

더 나은 디버깅 도구와 자세한 안내는 디버깅 가이드를 참조하세요.

Weather API 문제

오류: 그리드 지점 데이터를 가져오지 못했습니다

일반적으로 다음 중 하나가 원인입니다.

  1. 좌표가 미국 밖에 있습니다.
  2. NWS API에 문제가 발생했습니다.
  3. 요청 속도 제한이 적용되었습니다.

해결 방법:

  • 미국 내 좌표를 사용하고 있는지 확인합니다.
  • 요청 사이에 짧은 지연 시간을 추가합니다.
  • NWS API 상태 페이지를 확인합니다.

오류: [STATE]에 현재 발효 중인 특보가 없습니다

이는 오류가 아닙니다. 해당 주에 현재 발효 중인 기상 특보가 없다는 의미입니다. 다른 주를 시도하거나 악천후가 발생했을 때 다시 확인하세요.

다음 단계