Skip to content

MCP 서버 실전 운영 (2편) - Private Git 연동과 자동화

MCP 서버 실전 운영 (2편) - Private Git 연동과 자동화

TL;DR

  • 문제: 로컬 MCP 서버는 Claude Desktop에서만 사용 가능, 어디서든 접근 불가
  • 원인: Stdio 프로토콜은 로컬 전용, Private Git 저장소 동기화 필요
  • 해결: Cloud Run 배포 + GitHub Webhook + PAT 인증
  • 효과: Cursor, 웹 Claude에서 접근 가능, Push 후 5초 이내 자동 동기화
  • 한계: 배포 복잡도 증가, Cold Start 2~3초, 월 $0.10~$1 비용

서론

1편에서 로컬 MCP 서버를 만들어봤습니다. 블로그 검색 기능을 AI에게 제공하고, 중복 주제 체크 시간을 10분에서 3초로 줄였죠.

그런데 문제가 있었습니다. Claude Desktop에서만 사용 가능하다는 것.

Cursor에서 코드 작성하다가 “이 기능 전에 구현한 적 있나?”라고 물어보고 싶어도, Claude Desktop을 따로 열어야 했습니다. 불편하더라고요.

이번 포스팅에서는 어디서든 접근 가능한 MCP 서버를 만드는 방법을 다루겠습니다. Private Git 저장소 연동, GitHub Webhook 자동 동기화, Cloud Run 배포까지 실전 운영 노하우를 정리했습니다.


Private Git 저장소 연동 - Trade-off와 실전 경험

로컬 파일만 읽는 MCP 서버는 간단합니다. 그런데 Private Git 저장소를 연동하려면 조금 더 고민이 필요합니다.

두 가지 선택지

옵션 A: Stdio (로컬 MCP 서버)

장점:

  • 로컬 파일 직접 접근 (빠름)
  • 네트워크 불필요
  • 무료

단점:

  • Claude Desktop에서만 사용 가능
  • Cursor, 웹 Claude 사용 불가
  • 다른 사람과 공유 불가능

옵션 B: REST API (클라우드 MCP 서버)

장점:

  • 어디서든 사용 가능 (Cursor, 웹 Claude)
  • 다른 사람과 공유 가능
  • 자동 컨텐츠 동기화 (웹훅)

단점:

  • 서버 배포 필요 (Cloud Run, AWS Lambda 등)
  • 약간의 비용 발생 (월 $0.10~$1)

저의 선택: 하이브리드

저는 두 가지 다 만들었습니다.

로컬 MCP 서버:

  • 글쓰기 작업 (파일 생성, 수정)
  • 빠른 로컬 검색

REST API MCP 서버:

  • 검색, 조회 (어디서든 접근)
  • 실시간 컨텐츠 동기화

각자 역할이 명확해서 둘 다 필요했거든요.


Git 동기화 구현 (핵심 코드)

Private 저장소 접근을 위해 GitHub Personal Access Token(PAT)을 사용합니다.

import os
import subprocess
from pathlib import Path

class GitContentSync:
    def __init__(self, repo_url: str, content_dir: str = "/tmp/blog-content"):
        # PAT로 인증 URL 생성
        gh_token = os.getenv("GH_PAT", "")
        if gh_token:
            self.repo_url = repo_url.replace("https://", f"https://{gh_token}@")

        self.content_dir = Path(content_dir)

    def initialize(self) -> bool:
        """서버 시작 시 Git Clone"""
        env = os.environ.copy()
        env['GIT_TERMINAL_PROMPT'] = '0'

        result = subprocess.run([
            "git", "clone", "--depth", "1",  # 최근 커밋만
            self.repo_url, str(self.content_dir)
        ], env=env, timeout=60)

        return result.returncode == 0

    def pull(self) -> bool:
        """Webhook 수신 시 Git Pull"""
        result = subprocess.run([
            "git", "-C", str(self.content_dir),
            "pull", "origin", "master"
        ], timeout=30)

        return result.returncode == 0

핵심 포인트:

  1. --depth 1: 전체 히스토리 대신 최근 커밋만 → 3~5초 안에 완료
  2. GIT_TERMINAL_PROMPT=0: Docker 컨테이너에서 안전하게 실행
  3. PAT 사용: SSH 키 대신 Token으로 간단하게 인증

GitHub Webhook으로 자동 동기화

블로그 글을 Push하면 자동으로 MCP 서버가 최신 컨텐츠를 동기화하도록 만들었습니다.

Webhook 동작 흐름

sequenceDiagram
    participant Dev as 개발자
    participant Git as GitHub
    participant MCP as MCP Server
    participant Store as Local Storage

    Dev->>Git: git push origin master

    Note over Git: Push 이벤트 발생

    Git->>MCP: POST /webhook/github + HMAC 서명

    Note over MCP: HMAC 검증 (보안)

    MCP->>Store: git pull origin master

    Note over Store: 최신 코드로 업데이트

    Store-->>MCP: Pull 완료
    MCP-->>Git: 200 OK

    Note over MCP: 새로운 포스팅 즉시 AI가 읽기 가능

FastAPI Webhook 엔드포인트

from fastapi import FastAPI, Request, Header, HTTPException
import hmac
import hashlib

app = FastAPI(title="MCP REST API")
git_sync = GitContentSync(repo_url="https://github.com/user/private-blog.git")

@app.on_event("startup")
async def startup_event():
    """서버 시작 시 Git Clone"""
    git_sync.initialize()

@app.post("/webhook/github")
async def github_webhook(
    request: Request,
    x_hub_signature_256: str = Header(None)
):
    """GitHub Webhook 수신"""
    payload_bytes = await request.body()

    # HMAC 서명 검증
    webhook_secret = os.getenv("WEBHOOK_SECRET", "")
    expected_signature = "sha256=" + hmac.new(
        webhook_secret.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_signature, x_hub_signature_256):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Git Pull 실행
    success = git_sync.pull()
    return {"success": success}

핵심 포인트:

  1. HMAC 서명 검증: GitHub에서 온 요청인지 확인 (보안)
  2. 자동 Git Pull: Push 후 5초 이내에 최신 컨텐츠 동기화

실제로 블로그 글을 Push하면 5초 이내에 AI가 새 글을 읽을 수 있습니다.


배포 - GCP Cloud Run vs AWS Lambda

MCP 서버를 배포할 때 고민했던 부분입니다.

선택지 비교

항목GCP Cloud RunAWS Lambda
비용월 $0.10~$1월 $0.20~$2
Cold Start2~3초1~2초
메모리128MB~8GB128MB~10GB
Git Clone가능/tmp 용량 제한
Dockerfile완벽 지원복잡함 (Container Image)

저의 선택: GCP Cloud Run

이유:

  1. Dockerfile 그대로 배포 - 로컬에서 돌리던 컨테이너를 그대로 올릴 수 있음
  2. Git Clone 용이 - /tmp 용량 제약 없음
  3. 간단한 설정 - Lambda Layer나 복잡한 설정 불필요

Dockerfile

FROM python:3.11-slim

WORKDIR /app

# Git 설치 (필수!)
RUN apt-get update && apt-get install -y git

# Python 의존성
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스 코드
COPY src/ ./src/

# 임시 디렉토리 생성
RUN mkdir -p /tmp/blog-content

EXPOSE 8001

CMD ["python", "-m", "uvicorn", "src.rest_api:app", "--host", "0.0.0.0", "--port", "8001"]

핵심 포인트:

  1. Git 설치: Git Clone/Pull을 위해 필수
  2. 임시 디렉토리: /tmp/blog-content에 저장

GitHub Actions로 자동 배포

name: Deploy MCP Server

on:
  push:
    paths:
      - 'mcp-server/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy mcp-api-server \
            --image gcr.io/$PROJECT_ID/mcp-api-server \
            --memory 512Mi \
            --min-instances 0 \
            --max-instances 5

핵심 설정:

  • --min-instances 0: 사용 안 할 때 자동 종료 → 비용 절감
  • --memory 512Mi: Git Clone에 충분한 메모리

실제 운영 비용: 월 $0.10 수준 (무료 티어 활용)


실전 활용 사례 - 블로그 통계 분석

기존 방식:

# 카테고리별 포스팅 개수 수동 계산
find content/blog -name "*.md" | xargs grep "category: Spring" | wc -l
# 20분 소요

MCP 서버 활용:

나: "블로그 통계 보여줘"

AI:
1. get_stats() 호출
2. 결과를 자연어로 정리

AI: "총 54개의 포스팅이 있습니다.

     카테고리별 분포:
     - Spring: 15개 (27.8%)
     - Java: 8개 (14.8%)
     - AI: 7개 (13.0%)

     인기 태그:
     1. JPA (12개)
     2. Performance (9개)"

결과:

  • 통계 생성 시간: 20분 → 즉시
  • 추가 장점: 자연어로 질문 가능

이 접근의 아쉬운 점

2개월간 운영하면서 느낀 현실적인 한계입니다.

초기 구축 비용

  • MCP 서버 개발: 8~10시간
  • 배포 설정: 2~3시간
  • Webhook 연동: 1~2시간
  • 총 12~15시간 투자

반복적인 검색 시간을 줄이면 몇 달 안에 본전이지만, 초기 투자는 필요합니다.

운영 비용

  • GCP Cloud Run: 월 $0.10~$1
  • GitHub PAT 관리 필요
  • Webhook Secret 관리 필요

Cloud Run의 Cold Start 특성

  • --min-instances 0 설정 시 트래픽이 없으면 인스턴스가 자동 종료됨
  • 다시 시작할 때 2~3초 대기
  • Git Clone 시간 3~5초 추가

해결책:

--min-instances 1 # 항상 1개 인스턴스 유지 (월 $5 추가)

저의 선택:

  • --min-instances 0 유지 (비용 절감)
  • Cold Start 감수 (2~3초는 충분히 참을 만함)

💡 참고: 이것은 MCP 서버의 단점이 아니라 Cloud Run의 특성입니다. AWS Lambda, Azure Functions 등 다른 서버리스 플랫폼도 동일합니다.


시스템 점검 체크리스트

배포 전에 이 항목들을 꼭 확인합니다.

  • GitHub PAT: 올바른 권한(repo read) 설정되어 있는가?
  • Webhook Secret: 환경 변수로 안전하게 관리하는가?
  • HMAC 검증: Webhook 서명 검증이 정상 동작하는가?
  • 에러 핸들링: Git Clone/Pull 실패 시 적절한 에러 처리가 있는가?
  • 메모리 설정: Git Clone에 충분한 메모리(512Mi 이상) 할당했는가?

결론

1편에서 로컬 MCP 서버를 만들었고, 이번 2편에서 어디서든 접근 가능한 MCP 서버를 만들었습니다.

핵심 요약:

  1. Private Git 연동: GitHub PAT로 안전하게 인증
  2. 자동 동기화: Webhook으로 Push 후 5초 이내 최신화
  3. Cloud Run 배포: Dockerfile 그대로 배포, 월 $0.10 수준

솔직한 후기:

처음엔 배포 설정이 복잡해 보였습니다. 그런데 막상 해보니 Dockerfile 작성 → GitHub Actions 설정 → Cloud Run 배포 3단계면 끝이더라고요.

12~15시간 투자하면 평생 쓸 도구가 생깁니다. Cursor에서 코드 작성하다가 “이 기능 전에 구현한 적 있나?”라고 물어보면, AI가 3초 안에 답해줍니다.

AI 시대에 백엔드 개발자의 역할:

과거: 웹/앱을 위한 API 개발
현재: 웹/앱 + AI를 위한 API 개발

MCP는 선택이 아니라 필수가 될 겁니다. REST API가 표준이 된 것처럼요.





참고 :

MCP 공식 문서: https://modelcontextprotocol.io
FastMCP Python SDK: https://github.com/jlowin/fastmcp
제 블로그 MCP 서버 코드: https://github.com/Ramsbaby/ramsbaby-blog-starter/tree/master/mcp-server




읽어주셔서 감사합니다.🖐


Ramsbaby
Written byRamsbaby
이 블로그는 직접 개발/운영하는 블로그이므로 당신을 불쾌하게 만드는 불필요한 광고가 없습니다.

#My Github#소개 페이지#Blog OpenSource Github#Blog OpenSource Demo Site