MCP 서버 — 기초부터 프로덕션까지
요약
Model Context Protocol(MCP) 서버를 기초부터 프로덕션 환경까지 구축하는 실전 가이드입니다. Python의 FastMCP를 사용하여 도구, 리소스, 프롬프트를 구현하고 Docker, 인증, CI/CD를 포함한 완성형 아키텍처를 구축하는 방법을 다룹니다.
핵심 포인트
- MCP는 AI 모델과 외부 데이터/도구를 연결하는 표준 프로토콜입니다.
- Tools, Resources, Prompts라는 세 가지 핵심 프리미티브를 제공합니다.
- Docker화, 인증, 모니터링을 포함한 프로덕션급 서버 구축 과정을 안내합니다.
- Claude Desktop, VS Code, Cursor 등 다양한 호스트와 연동 가능합니다.
서론 (Introduction)
이것은 강의가 아니라 빌드 가이드입니다. 이 과정을 마치면 여러분은 20줄짜리 스크립트로 시작하여 Claude Desktop, VS Code, Cursor에 연결되고, Docker화(Dockerized)되어 인증, 테스트, 모니터링이 가능하며 CI/CD 파이프라인을 통해 배포되는 Model Context Protocol (MCP) 서버를 갖게 될 것입니다.
여기에 있는 모든 명령은 실제로 작동합니다. 모든 코드 블록은 실행 가능하거나 명시적으로 부분적(partial)이라고 표시되어 있습니다. 만약 여러분이 이론에 매몰되지 않고 MCP 숙련도를 쌓고 싶은 SDET, 자동화 엔지니어, 백엔드 개발자 또는 AI 엔지니어라면, 이 가이드는 바로 여러분을 위해 작성되었습니다.
우리는 공식 MCP SDK (mcp)와 그 고수준 API인 FastMCP를 사용하여 Python으로 구축합니다. 동일한 개념들이 TypeScript SDK에도 깔끔하게 매핑되며, 중요한 차이점이 있는 경우 별도로 표시하겠습니다.
여러분이 구축하게 될 것
다음 기능을 제공하는 toolhub라는 이름의 프로덕션급 MCP 서버를 구축합니다:
- 도구 (Tools) — 실제 백엔드에 접속하는 주문 조회 도구 및 SQL 안전 쿼리(SQL-safe query) 도구.
- 리소스 (Resources) — URI로 접근 가능한 읽기 전용 데이터 엔드포인트 (설정, 카탈로그).
- 프롬프트 (Prompts) — 호스트 앱이 필요할 때마다 가져올 수 있는 재사용 가능한 템플릿 프롬프트.
그 위에 다음과 같은 계층이 추가됩니다: 구조화된 로깅 (structured logging), 토큰 인증 (token authentication), 환경 기반 설정 (environment-based config), Docker 패키징, 단위 및 통합 테스트 (unit and integration tests), GitHub Actions 파이프라인, 상태 확인 (health checks), 그리고 Prometheus 메트릭 엔드포인트.
최종 아키텍처 (Final Architecture)
┌──────────────────────────────────────┐
│ MCP HOSTS │
│ Claude Desktop │ VS Code │ Cursor │
...
1. MCP 서버란 무엇인가 (2분 요약 버전)
MCP는 AI 애플리케이션이 외부 도구 및 데이터에 연결되는 방식을 표준화하는 개방형 프로토콜입니다. LLM을 위한 USB-C 포트라고 생각하면 됩니다. 하나의 커넥터로 많은 장치를 연결하는 것과 같습니다. 이는 JSON-RPC 2.0을 기반으로 구축되었으며, 2024년 말 Anthropic에 의해 오픈 소스로 공개되었습니다. 2026년까지 Anthropic, OpenAI, Google, Microsoft, AWS 등 모든 주요 벤더에 의해 지원될 것입니다.
세 가지 역할이 있습니다:
| 역할 (Role) | 정의 | 예시 |
|---|---|---|
| Host (호스트) | 사용자가 대화하는 AI 앱 | Claude Desktop, Cursor, VS Code |
| ... |
서버는 정확히 세 가지 프리미티브 (Primitives)를 노출합니다:
- Tools (도구) — 모델이 호출할 수 있는 함수 (부수 효과 (side effects) 허용). 모델 제어 (Model-controlled).
- Resources (리소스) — 호스트가 컨텍스트 (context)로 로드할 수 있는 읽기 전용 데이터로, URI로 주소가 지정됨. 앱 제어 (App-controlled).
- Prompts (프롬프트) — 사용자가 호출할 수 있는 재사용 가능한 프롬프트 템플릿 (prompt templates). 사용자 제어 (User-controlled).
이러한 구분은 중요합니다. 도구는 _작업 (actions)_을 위한 것이고, 리소스는 _컨텍스트 (context)_를 위한 것이며, 프롬프트는 _지름길 (shortcuts)_을 위한 것입니다. 이들을 혼동하는 것이 가장 흔한 설계 실수입니다. 다음으로 넘어갑니다.
2. 아키텍처 (Architecture)
MCP는 JSON-RPC 2.0 기반의 클라이언트-서버 (client-server) 구조입니다. 호스트는 사용자의 서버를 생성하거나 연결하며, initialize 핸드셰이크 (handshake) 과정에서 기능 (capabilities)을 협상한 후, 타입이 지정된 메시지 (typed messages)를 교환합니다.
**전송 방식 (Transports)**는 바이트가 어떻게 이동할지를 결정합니다:
| 전송 방식 (Transport) | 사용 사례 | 비고 |
|---|---|---|
| stdio | 로컬 서버, 동일한 머신 | 호스트가 프로세스를 생성하고 stdin/stdout을 통해 통신합니다. 빠르고 단순하며 네트워크가 필요 없습니다. |
| ... |
경험 법칙: 로컬 데스크톱 통합에는 stdio를, 원격 또는 공유되는 모든 것에는 Streamable HTTP를 사용하세요. 실제 서버는 실행되는 위치에 따라 각각을 지원해야 하므로, 우리는 두 가지 모두를 구축합니다.
3. 사전 요구 사항 (Prerequisites)
다음이 필요합니다:
- Python 3.10+ (3.12 권장)
- uv — 빠른 Python 패키지/프로젝트 관리자 (pip도 작동하지만, uv가 더 매끄럽습니다)
- Docker — 패키징용
- Node.js 18+ — MCP Inspector 및 TypeScript 예제용
- 테스트를 위한 MCP 호스트: Claude Desktop, VS Code, 또는 Cursor
설치된 버전을 확인하세요:
python3 --version # 3.10 이상
docker --version
node --version
4. 모든 항목 설치하기
uv를 설치합니다:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
...
MCP Inspector (서버를 점검하기 위한 브라우저 도구로, 매우 유용합니다)를 설치합니다:
npx @modelcontextprotocol/inspector --help
다음 단계에서 SDK 자체를 프로젝트 내부에 설치하므로, 격리된 환경에 위치하게 됩니다.
5. 프로젝트 설정 (Project Setup)
uv를 사용하여 프로젝트를 생성하고 초기화합니다:
uv init toolhub
cd toolhub
의존성(dependencies)을 추가합니다. SDK 버전을 v2 미만으로 고정하세요 — v1.x는 프로덕션(production) 환경에 권장되는 안정적인 라인입니다. pip/uv는 v2 프리릴리스(pre-release) 버전을 자동으로 선택하지 않지만, 버전을 고정해 두면 v2가 안정화되었을 때 발생할 수 있는 문제를 방지할 수 있습니다:
uv add "mcp[cli]>=1.27,<2"
uv add pydantic python-dotenv structlog httpx
uv add --dev pytest pytest-asyncio ruff mypy
간단한 무결성 검사(sanity check):
uv run python -c "import mcp; print('mcp ok')"
흔한 실수: 일반
pip를 사용하여mcp를 전역(globally)으로 설치하는 것입니다. 서로 다른 호스트는 서로 다른 인터프리터(interpreters)로 서버를 실행합니다. 전역 설치는 "내 컴퓨터에서는 작동하지만" 다른 곳에서는 작동하지 않음을 의미합니다. 항상 프로젝트 로컬(project-local) 상태를 유지하세요.
6. 폴더 구조 (Folder Structure)
우리가 확장해 나갈 레이아웃은 다음과 같습니다. 나중에 무언가를 덧붙이는 느낌이 들지 않도록 지금 설정해 두세요:
toolhub/
├── pyproject.toml
├── uv.lock
...
스켈레톤(skeleton)을 생성합니다:
mkdir -p src/toolhub/{tools,resources,prompts} tests .github/workflows
touch src/toolhub/{__init__.py,server.py,config.py,logging_conf.py,auth.py,errors.py,backends.py}
touch src/toolhub/tools/{__init__.py,orders.py}
...
7. 첫 번째 MCP 서버 작성하기
최소한의 기능으로 시작하세요. 다음 내용을 src/toolhub/server.py에 넣습니다:
# src/toolhub/server.py
from mcp.server.fastmcp import FastMCP
...
실행합니다:
uv run python -m toolhub.server
서버는 stdin에서 대기 중인 상태로 머뭅니다 — 이는 stdio 방식에서 정상입니다. Ctrl+C로 종료하세요. 실제로 상호작용하려면 인스펙터(Inspector)를 사용하세요:
npx @modelcontextprotocol/inspector uv run python -m toolhub.server
인스펙터가 브라우저 UI를 엽니다. Tools 항목 아래에서 ping을 호출하면 pong을 받게 됩니다. 여러분이 작성하지 않은 것에 주목하세요: JSON 스키마(JSON Schema), 요청 파싱(request parsing), 검증(validation) 코드를 전혀 작성하지 않았습니다. 타입 힌트(-> str)가 곧 스키마이며, 독스트링(docstring)이 모델이 읽는 도구 설명(tool description)이 됩니다.
8. 도구 등록하기 (Registering Tools)
도구(Tools)는 모델이 호출할 수 있는 동작(actions)입니다. 도구들을 별도의 모듈로 관리하고, 공유된 mcp 인스턴스에 등록하여 사용하세요.
src/toolhub/tools/orders.py:
# src/toolhub/tools/orders.py
from pydantic import BaseModel, Field
...
server.py에 연결하기:
# src/toolhub/server.py
from mcp.server.fastmcp import FastMCP
from toolhub.tools import orders
...
도구 설계 모범 사례 (Tool design best practices):
- 사람이 아닌 모델을 위한 설명을 작성하세요. 모델은 독스트링 (docstring)을 기반으로 도구를 선택합니다. "order fn"보다는 "ID를 통해 주문을 조회합니다"가 훨씬 낫습니다.
- 타입이 지정된 객체 (Pydantic models)를 반환하세요. SDK가 자동으로 구조화된 출력 (structured output)을 생성하므로, 클라이언트는 문자열로 변환된 덩어리가 아닌 깔끔한 JSON을 받게 됩니다.
- 하나의 도구에는 하나의 작업만 할당하세요.
do_everything(action, payload)와 같은 디스패처 (dispatcher)를 만들지 마세요. 모델은 이를 추론할 수 없습니다. - 동사를 사용하여 이름을 지으세요:
get_order,create_ticket,cancel_shipment등.
흔한 실수: 거대한 페이로드 (payload)를 반환하는 것입니다. 모든 도구 결과는 모델의 컨텍스트 (context)로 다시 입력되며 토큰 (tokens) 비용을 발생시킵니다. 모델에 필요한 필드만 반환하고, 나머지는 페이지네이션 (pagination) 처리를 하세요.
9. 리소스 API (Resource APIs)
리소스 (Resources)는 URI로 접근 가능한 읽기 전용 데이터입니다. 호스트 (host)가 이를 컨텍스트에 로드하며, 모델은 도구를 호출하는 방식처럼 리소스를 "호출"하지 않습니다. 설정, 카탈로그, 문서와 같이 안정적인 참조 데이터에는 리소스를 사용하세요.
src/toolhub/resources/catalog.py:
# src/toolhub/resources/catalog.py
_CATALOG = {
"SKU-1": {"name": "Wireless Mouse", "price": 999, "stock": 42},
...
두 가지 패턴이 제시됩니다: 정적 (static) 리소스 (catalog://all)와 {sku}가 URI에서 바인딩되는 템플릿화된 (templated) 리소스 (catalog://item/{sku})입니다. server.py에서 catalog.register(mcp)를 사용하여 등록하세요.
도구 vs 리소스 — 결정 기준:
| 질문 | 도구 (Tool) | 리소스 (Resource) |
|---|---|---|
| 부수 효과 (side effects)가 있는가? | 예 | 절대 없음 |
| ... |
10. 프롬프트 템플릿 (Prompt Templates)
프롬프트 (Prompts)는 사용자가 이름으로 호출할 수 있는 재사용 가능한 매개변수화된 프롬프트 조각 (prompt snippets)입니다. 팀의 워크플로우를 표준화하는 데 매우 유용합니다 (예: "이 주문 분쟁을 요약해줘", "테스트 계획을 생성해줘").
src/toolhub/prompts/templates.py:
# src/toolhub/prompts/templates.py
def register(mcp):
@mcp.prompt()
...
templates.register(mcp)를 사용하여 등록합니다. Claude Desktop에서는 사용자가 선택할 수 있는 슬래시 명령 (slash-command) 스타일의 프롬프트로 나타납니다.
MCP 더 빠르게 배우기
단일 기사보다 더 깊이 있는, 완전한 프로덕션 준비 완료 가이드, 인터뷰 질문, 테스트 전략 및 실습 MCP 리소스를 원하신다면 다음을 확인해 볼 가치가 있습니다:
HimanshuAI Playbook Store — https://himanshuai.gumroad.com/
추천: MCP Mastery Pack — https://himanshuai.gumroad.com/l/MCP-Mastery-Pack
Mastery Pack은 이 기사의 패턴들을 작동하는 참조 프로젝트와 인터뷰 준비 세트로 묶어 제공하므로, 시행착오를 줄이고 더 빠르게 배포할 수 있습니다. MCP가 워크플로우에 포함되기 시작한 GenAI 또는 SDET 역할을 준비 중이라면 매우 유용합니다.
11. 에러 핸들링 (Error Handling)
가공되지 않은 스택 트레이스 (stack trace)가 프로토콜 경계를 넘지 않도록 하세요. 자체 예외 (exceptions)를 정의하고 이를 깔끔한 도구 에러 (tool errors)로 변환해야 합니다.
src/toolhub/errors.py:
# src/toolhub/errors.py
class ToolHubError(Exception):
"""알려진, 사용자에게 안전한 에러를 위한 기본 클래스."""
...
FastMCP는 도구 내부에서 발생한 예외를 포착하여 클라이언트에 에러 결과로 반환합니다. 중요한 점은 사용자가 _메시지 (message)_를 제어한다는 것입니다. 안전한 텍스트와 함께 자체적인 타입 지정 에러 (typed errors)를 발생시키세요:
# 도구 내부
from toolhub.errors import NotFound
...
규칙:
- 에러 메시지에 비밀 정보(secrets), SQL, 파일 경로 또는 내부 호스트 이름을 절대 노출하지 마세요.
- 사용자 에러 (잘못된 입력 → 모델에게 무엇을 수정해야 하는지 알림)와 시스템 에러 (백엔드 다운 → 일반적인 "일시적으로 사용 불가능" 메시지)를 구분하세요.
- 경계 지점에서 Pydantic 타입을 사용하여 입력을 검증하세요. 코드가 실행되기 전에 SDK가 잘못된 형식의 호출을 거부하도록 하세요.
흔한 실수:
except Exception: return "error". 이렇게 하면 모든 진단 정보를 잃게 됩니다. 서버 측에서는 전체 예외(exception)를 로그로 남기고, 클라이언트에는 안전한 요약본만 반환하세요.
12. 로깅 (Logging)
stdio 서버에는 한 가지 엄격한 규칙이 있습니다: 절대로 stdout에 로그를 작성하지 마세요. stdout은 JSON-RPC 채널입니다. 잘못된 print() 호출 하나가 프로토콜을 손상시키고 호스트의 연결을 끊어버립니다. 로그는 stderr (또는 파일)에 작성하세요.
src/toolhub/logging_conf.py:
# src/toolhub/logging_conf.py
import logging
import sys
...
사용 방법:
log = configure_logging()
log.info("tool.called", tool="get_order", order_id=order_id)
구조화된 JSON 로그(Structured JSON logs)를 사용하면 모니터링 스택(Loki, ELK, CloudWatch)에서 정규 표현식(regex)을 복잡하게 사용하지 않고도 tool, order_id 또는 level별로 필터링할 수 있습니다.
13. 인증 (Authentication)
stdio 서버는 호스트의 OS 권한을 상속받으므로, 여기서의 인증은 주로 서버 자체가 무엇에 연결되는지 (DB 자격 증명 보호)에 관한 것입니다. 원격 Streamable HTTP 서버는 네트워크에 노출되므로 호출자를 반드시 인증해야 합니다. 사양(spec)에서는 프로덕션용 원격 서버를 위해 OAuth 2.1을 표준으로 정의합니다.
자체 호스팅(self-hosted) 서비스의 경우, 실용적인 방법은 매 요청마다 검증되는 Bearer token을 사용하는 것이며, ID 제공자(identity provider)를 통합할 때 OAuth 2.1로 업그레이드하는 것입니다.
src/toolhub/auth.py:
# src/toolhub/auth.py
import hmac
from toolhub.errors import Unauthorized
...
Streamable HTTP를 실행할 때 이를 미들웨어(middleware)로 연결하세요. FastMCP는 래핑(wrap)할 수 있는 Starlette/ASGI 앱을 노출합니다:
# partial — 패턴을 보여주며, ASGI 러너에 플러그인 형태로 연결합니다
from starlette.middleware.base import BaseHTTPMiddleware
from toolhub.auth import verify_token
...
인증 모범 사례 (Auth best practices):
- 상수 시간 비교 (
hmac.compare_digest)를 사용하세요 — 비밀값(secrets)에 대해==연산자를 절대 사용하지 마세요. - 토큰을 순환(Rotate)시키세요; 토큰은 시크릿 매니저(secrets manager)에 보관해야 하며, 코드나 리포지토리(repo)에 절대 포함하지 마세요.
- 멀티 테넌트(multi-tenant) 또는 공개 서버의 경우, 수명이 짧은 액세스 토큰(access tokens)을 사용하는 OAuth 2.1로 업그레이드하세요.
- 서드파티 (third-party) stdio 서버를 추가하기 전에 반드시 소스 코드를 검토하세요 — 해당 서버는 사용자의 권한으로 실행됩니다.
14. 환경 변수 (Environment Variables)
설정(Config)은 환경으로부터 가져오며, 절대 하드코딩하지 않습니다. 타입이 지정된 설정 객체(typed settings object)를 사용하세요.
src/toolhub/config.py:
# src/toolhub/config.py
import os
from dataclasses import dataclass
...
.env.example (이 파일은 커밋하되, 실제 .env 파일은 절대 커밋하지 마세요):
# .env.example
TOOLHUB_API_TOKEN=replace-me
DATABASE_URL=postgresql://user:pass@localhost:5432/toolhub
...
.gitignore에는 반드시 다음을 포함해야 합니다:
.env
.venv/
__pycache__/
...
흔한 실수:
.env파일을 커밋하는 것. 이를.gitignore에 추가하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기