22. 패키징·배포·버전 관리

🎯 이 장의 목표
  • 배포 경로(PyPI·Docker·공식 Registry)와 각 트레이드오프를 안다
  • Docker로 원격 서버를 컨테이너화하는 기본 패턴을 익힌다
  • 시맨틱 버전·CI/CD·재현성으로 안전하게 배포·업데이트한다

8부는 "동작하는 서버"를 "실제로 쓰이는 서버"로 만드는 단계입니다. 한 분석은 원격 MCP 엔드포인트 2,181개 중 52%가 죽어 있고 9%만 완전 정상이라고 보고했습니다 — "MCP는 어렵지 않다. 프로덕션에서 돌리는 것이 어렵다"는 말이 핵심입니다. 이 부가 그 간극을 다룹니다.

배포 경로 — 무엇을 어디에

flowchart TD
    SRV["완성한 서버"] --> Q{"누가 어떻게 쓰나?"}
    Q -->|"개발자가 로컬에 설치"| PKG["패키지 레지스트리\nPyPI / npm (uvx/npx로 실행)"]
    Q -->|"재현성·프로덕션"| DOCKER["Docker 이미지\n(의존성·런타임 번들)"]
    Q -->|"발견성·공유"| REG["공식 MCP Registry\n(카탈로그 등재)"]
    classDef q fill:#bfdbfe,stroke:#1d4ed8,color:#000;
    classDef opt fill:#5eead4,stroke:#0f766e,color:#000;
    class SRV,Q q;
    class PKG,DOCKER,REG opt;

2026년 현재 생태계는 최소 여섯 가지 배포 경로를 제공하며 각각 트레이드오프가 다릅니다. 대부분의 서버는 둘 이상을 노립니다 — 개발자용 패키지 레지스트리(PyPI/npm) 하나와, 발견성을 위한 공식 MCP Registry.

경로장점단점/주의
PyPI (uvx/pip)파이썬 개발자에 친숙, uvx로 즉시 실행의존성 해결 복잡(C 확장·플랫폼 휠), 시작 느릴 수 있음
Docker"내 머신에선 됨" 문제 해결, 의존성·런타임 번들, 샌드박싱이미지 빌드·관리 필요
공식 MCP Registry발견·공유(npm install처럼)등재·메타데이터 관리
클라우드(Cloud Run·ECS·Lambda·Workers)원격 호스팅콜드스타트·수명 관리(23장)
💡 팁
stdio는 패키지로, 원격은 컨테이너로: 로컬 stdio 서버는 PyPI에 올려 uvx my-server로 실행하게 하는 게 자연스럽고(07장), 원격 HTTP 서버는 Docker 이미지로 배포하는 게 표준입니다. 한 서버가 두 형태를 모두 지원할 수도 있습니다(09장의 전송 무관성).

Docker로 원격 서버 컨테이너화

원격(HTTP) 서버의 표준 배포는 Docker입니다. 의존성·런타임·설정을 이미지에 번들해 "works on my machine" 문제를 없애고, 호스트 파일시스템에서 서버를 격리해 폭발 반경을 줄입니다(21장).

서버는 HTTP 전송으로 띄웁니다(08장):

PYTHON
# server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Production Server")

@mcp.tool()
def search_docs(query: str) -> str:
    """내부 문서를 검색한다."""
    return f"'{query}' 검색 결과 3건"

if __name__ == "__main__":
    # 컨테이너에선 0.0.0.0 바인딩 (외부에서 접근)
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
⚠️ 흔한 실수
0.0.0.0 vs 127.0.0.1: 컨테이너 에서는 0.0.0.0으로 바인딩해 컨테이너 네트워크로 노출하고, 바깥 경계(로드밸런서·방화벽)에서 접근을 통제합니다. 로컬 개발 시 호스트에 직접 띄울 때는 127.0.0.1이 안전합니다(08장의 Origin·바인딩 주의).
DOCKERFILE
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt   # 버전 고정 권장
COPY server.py .
EXPOSE 8000
CMD ["python", "server.py"]
🔒 비밀값은 이미지에 넣지 말 것: API 키·토큰을 Dockerfile이나 이미지에 굽지 마세요. 런타임 환경 변수(docker run -e API_KEY=...)나 Docker secrets로 주입하고, Compose에서는 ${API_KEY} 치환 + 커밋하지 않는 .env를 씁니다. 14장·21장에서 본 자격 관리의 연장선입니다.

클라이언트는 URL로 붙습니다:

JSON
{ "mcpServers": { "my-server": { "url": "http://localhost:8000/mcp" } } }
⚠️ 흔한 실수
컨테이너 수명 관리: 한 팀은 설정 실수로 "좀비 Docker 컨테이너 수십 개"를 만들었습니다. 로컬 테스트엔 --rm을 쓰고, 프로덕션은 클라우드 플랫폼이 컨테이너 수명을 관리하게 맡기세요.

헬스 체크

모니터링·오케스트레이터가 서버 준비 상태를 확인하도록 헬스 엔드포인트(예: /health)나 MCP 엔드포인트 체크를 둡니다.

YAML
# docker-compose.yml (발췌)
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8000/mcp"]
  interval: 30s
  timeout: 10s
  retries: 3

버전 관리 — 시맨틱 버전과 CI/CD

서버 패키지06장프로토콜 날짜 버전과 별개로, 시맨틱 버전(SemVer) 을 씁니다(예: 1.4.2). 둘을 혼동하지 마세요 — 프로토콜 리비전은 2025-11-25, 내 서버 버전은 1.4.2.

권장 흐름:

  • 시맨틱 버전으로 패키지 버전을 매기고, 릴리스 PR 머지 시 CI/CD가 자동으로 PyPI에 발행.
  • Docker 이미지는 GHCR(GitHub Container Registry — 깃허브가 제공하는 컨테이너 이미지 저장소)·Docker Hub에 태그와 함께 푸시(CI/CD로 빌드).
  • 의존성을 고정해 재현 가능한 빌드(14장mcp>=1.27,<2 등).
flowchart LR
    PR["릴리스 PR 머지"] --> CI["CI/CD"]
    CI --> PYPI["PyPI 발행\n(SemVer 태그)"]
    CI --> IMG["Docker 이미지\nGHCR/Hub 푸시"]
    CI --> REG["MCP Registry\n메타데이터 갱신"]
    classDef a fill:#bfdbfe,stroke:#1d4ed8,color:#000;
    classDef b fill:#5eead4,stroke:#0f766e,color:#000;
    class PR,CI a;
    class PYPI,IMG,REG b;

안전한 업데이트 — rug-pull을 피하는 쪽에 서기

21장에서 본 rug-pull은 공격자 관점이었습니다. 제작자로서는 반대로, 사용자가 내 업데이트를 신뢰할 수 있게 만들어야 합니다.

🔒 업데이트 위생:
  • 변경 내역(changelog)을 투명하게 공개하고, 도구 동작이 바뀌면 명시합니다.
  • 권한·동작이 크게 바뀌는 업데이트는 사용자가 재검토·재동의할 수 있게 합니다(자동 무音 리로드 지양).
  • 패키지 서명을 하되, 21장의 Postmark 교훈대로 서명은 출처를 말할 뿐 행동을 보증하지 않음을 기억합니다 — 그래서 동작 검증·감사가 함께 필요합니다.
  • 의존성 취약점을 CI에서 스캔합니다(공급망 방어).

미래 (참고, 단정 금지)

2026-07-28 RC는 stateless 프로토콜 코어, Extensions 프레임워크, Tasks(긴 작업), MCP Apps(서버 렌더 UI), 인증 강화, 공식 deprecation 정책을 담는 역대 최대 개정입니다. stateless 코어는 보통의 HTTP 인프라에서 확장되도록 설계됐습니다. 다만 RC는 2026-05-21 기준 잠금된 초안이고 최종본은 2026-07-28 예정이니, 확정 기능으로 단정하지 말고 전환 시 changelog를 확인하세요.

이 장에서 배운 것

  • 배포는 PyPI(개발자) + Docker(프로덕션) + 공식 Registry(발견성) 를 함께 노린다. stdio는 패키지, 원격은 컨테이너.
  • Docker: HTTP 전송 + 0.0.0.0 바인딩, 비밀값은 env/secrets로(이미지에 굽지 않기), 헬스 체크.
  • 서버 패키지는 SemVer(프로토콜 날짜 버전과 별개), CI/CD로 자동 발행, 의존성 고정으로 재현성 확보.
  • 업데이트 위생: 투명한 changelog, 큰 변경 시 재동의, 서명+동작 검증, 의존성 스캔.

✍️ 확인 문제

  1. 로컬 전용 stdio 서버와 다중 사용자 원격 서버는 각각 어떤 배포 경로가 자연스러운가?
  2. Docker 이미지에 API 키를 굽는 것이 왜 위험하고, 올바른 주입 방법은?
  3. "내 서버 버전 1.4.2"와 "프로토콜 리비전 2025-11-25"는 무엇이 다른가?
다음 장: 23. 원격 호스팅·관측성·레이트리밋·성능