MCP · PATTERNS

실무 패턴 & 안티패턴

✅ 검증된 패턴 카탈로그 ❌ 피해야 할 안티패턴 📋 프로덕션 체크리스트

실제 MCP Server 운영에서 검증된 설계 패턴과 자주 범하는 실수를 정리합니다. 프로덕션 배포 전 체크리스트로 활용하세요.

✅ 실무 패턴

패턴 1 — Typed Schema

Tool 입력을 최대한 구체적인 JSON Schema로 정의합니다. LLM이 올바른 타입과 값을 전달할 확률이 높아지고, 잘못된 입력을 서버에 도달하기 전에 차단합니다.

pythonTyped Schema 패턴
from typing import Literal
from pydantic import BaseModel, Field

# ❌ 안티패턴 — 너무 느슨한 스키마
@mcp.tool()
def bad_search(params: dict) -> str: ...

# ✅ 패턴 — 구체적인 타입과 제약
@mcp.tool()
def search_products(
    query: str = Field(description="검색 키워드", min_length=1, max_length=200),
    category: Literal["electronics", "clothing", "books"] = "electronics",
    max_price: float = Field(default=100.0, ge=0, le=10000),
    page: int = Field(default=1, ge=1, le=100),
) -> list[dict]:
    """상품을 검색합니다. category는 electronics/clothing/books 중 하나."""
    ...

패턴 2 — Resource Cache

자주 읽히는 Resource(설정, 스키마 등)를 캐시해 반복 I/O를 줄입니다. TTL을 설정해 신선도를 유지합니다.

pythonTTL 캐시 패턴
import time
from functools import lru_cache

_cache: dict = {}
CACHE_TTL = 300  # 5분

def cached_resource(key: str, loader):
    entry = _cache.get(key)
    if entry and time.time() - entry["ts"] < CACHE_TTL:
        return entry["data"]
    data = loader()
    _cache[key] = {"data": data, "ts": time.time()}
    return data

@mcp.resource("db://schema")
def db_schema() -> str:
    """DB 스키마 (5분 캐시)."""
    return cached_resource("db_schema", lambda: fetch_db_schema())

패턴 3 — Single-Responsibility Tool

Tool 하나는 명확히 하나의 작업만 합니다. LLM이 Tool을 정확히 선택하고 조합하도록 설계합니다.

❌ 안티패턴
God Tool
manage_data(action, entity, id, payload) — action에 따라 CRUD를 모두 처리. LLM이 action 값을 잘못 추론하기 쉬움
✓ 패턴
분리된 Tool
get_user(id), create_user(data), delete_user(id) — 명확한 이름과 단일 목적. 어노테이션도 각각 적절하게 설정

패턴 4 — Progress Notification

오래 걸리는 Tool은 중간 진행 상황을 알림으로 전송해 클라이언트가 타임아웃 없이 대기할 수 있도록 합니다.

pythonProgress 알림 패턴 (저수준 API)
from mcp.server import Server
from mcp.types import ProgressNotification

server = Server("my-server")

@server.call_tool()
async def long_analysis(name, arguments, ctx):
    total_steps = 5
    for i in range(total_steps):
        # 진행 상황 알림 전송
        await ctx.session.send_progress_notification(
            progress_token=arguments.get("_progressToken"),
            progress=i + 1,
            total=total_steps
        )
        await process_step(i)
    return [{"type": "text", "text": "분석 완료"}]

❌ 안티패턴

❌ 안티패턴
God Server
한 서버에 20개 이상의 Tool을 몰아넣는 패턴. LLM의 Tool 선택 정확도 저하, 유지보수 불가. 도메인별로 서버를 분리하세요
❌ 안티패턴
Stateful Tool
Tool 핸들러가 서버 메모리에 상태를 저장하는 패턴. 멀티 클라이언트 환경에서 상태 충돌. 상태는 외부 DB나 클라이언트 측에 두세요
❌ 안티패턴
Silent Error
Tool이 실패해도 빈 문자열이나 None을 반환. LLM이 실패를 인지하지 못해 잘못된 결론. 반드시 raise 또는 isError로 명시
❌ 안티패턴
모호한 Tool 설명
"데이터를 처리합니다" 같은 설명. LLM이 언제 사용해야 할지 모름. 입력/출력/사이드이펙트를 명확히 기술하세요

📋 프로덕션 배포 체크리스트

🚀 MCP Server 프로덕션 체크리스트
  • 모든 Tool에 명확한 description 작성 (입력, 출력, 사이드이펙트 명시)
  • Tool 파라미터에 JSON Schema 제약 (maxLength, minimum, enum 등)
  • 파괴적 Tool에 destructiveHint: true 어노테이션 설정
  • 원격 서버에 인증 미들웨어 적용 (OAuth 2.1 또는 API Key)
  • 파일 경로 입력에 경로 순회 방어 로직 구현
  • SQL/외부 명령 입력에 인젝션 방어 (parameterized query)
  • Tool 에러를 McpError 또는 isError: true로 명시적 반환
  • 자주 읽히는 Resource에 캐시 + TTL 적용
  • 서버당 Tool 수 10개 이하 유지 (도메인별 분리)
  • mcp dev server.py로 Inspector에서 모든 Tool 테스트 완료