본문 바로가기

프로그래밍/AI

[MCP] FastMCP rules (@mcp.tool)

https://github.com/modelcontextprotocol/python-sdk

 

GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients

The official Python SDK for Model Context Protocol servers and clients - modelcontextprotocol/python-sdk

github.com

Model Context Protocol(MCP) 서버를 개발할 때, FastMCP는 개발자의 생산성을 극대화해주는 강력한 도구이다. 복잡한 JSON 스키마를 직접 짤 필요없이, Python 함수에 Docstring과 Type Hint 만 잘 작성하면 나머지(Tool Definition)는 FastMCP가 알아서 처리해 준다. @mcp.tool()

+ langchain 의 @tool 로 mcp 도구를 정의해도 문법은 99% 동일함 


⭐ FastMCP docstring rules ⭐


Input Description (Argument) 
방법 1 : 표준 Docstring 포멧 사용 (Google Style 등) 
@mcp.tool()
def send_email(recipient: str, subject: str, body: str) -> str:
    """
    사용자에게 이메일을 전송합니다.

    Args:
        recipient: 이메일을 받을 수신자의 주소 (예: user@example.com)
        subject: 이메일의 제목
        body: 이메일 본문 내용
    """
    # 이메일 전송 로직...
    return "Email sent successfully"
  • FastMCP는 파이썬의 표준 독스트링 포맷을 파싱하여 인자 설명을 추출합니다. (Google Style)
    • 장점 : 뛰어난 가독성, 별도의 라이브러리 없이도 mcp tool로서 기능할 수 있음.
    • 단점 : Docstring에 규칙(들여쓰기 등)이 맞지 않는 부분이 있으면 Error 발생 
  • 추천 상황 : 단순한 텍스트 입력이나, 복잡한 검증이 필요없는 일반적인 도구를 만들때
Input Description (Argument) 
방법 2 : Annotated와 Field 사용
from typing import Annotated
from pydantic import Field

@mcp.tool()
def create_user(
    username: Annotated[str, Field(description="사용자의 고유 ID, 5자 이상이어야 함")],
    age: Annotated[int, Field(description="사용자의 나이", ge=18)]
) -> str:
    """새로운 사용자를 시스템에 등록합니다."""
    return f"User {username} created."
  • Pydantic의 Field를 사용하면 설명뿐만 아니라 제약 조건(최소값, 길이 등)까지 스키마에 포함
    • 장점 : 강력한 제약조건을 통해 LLM에게 형식에 맞지 않는 값을 보내는 것을 원천 차단
    • 단점 : 지저분한 코드, Annotated와 Field 라이브러리 사용법을 익혀야 함
  • 추천 상황 : 결제 금액, ID, 날짜 등 형식이 틀리면 안 되는 중요한 데이터를 다루거나, LLM이 자꾸 엉뚱한 값을 넣을 때

⭐ Concept's Detail ⭐


Google Style Detail 
def get_techtree_path(track_name: str) -> Dict[str, Any]:
    """
    (기본 설명 생략...)

    Args:
        track_name: Exact name of the track.

    Returns:
        Dictionary containing the roadmap.

    Notes:
        **IMPORTANT FOR LLM:**
        - Use this structured data to create...
        - Focus on Sequence...
    """
  •  표준 Docstring 포멧은 약속된 해더(Args, Returns, 등) 만을 사용해서 작성해야한다. 
    • (Top) : 함수(def)에 대한 기본 설명 (여기에 Notes 에 해당되는 내용을 넣어도 됨)
    • Args: 입력되는 인자에 대한 설명 (':' 표시를 누락해서는 안됨) 
    • Returns: 출력 결과에 대한 형식 및 설명
    • Notes: 추가적인 설명 
  • 표준 섹션 헤더(Keywords) 종류 
    • Args:, Arguments:, Returns:, Raises:, Yields:, Attributes:, Examples:, Note:, Notes:, Todo:, References:
Annotated & Field Library Detail
from typing import Annotated
from pydantic import Field
  • Annotated : 변수에 추가정보(메타데이터)를 붙이기 위해 선언하는 포장지 역할
  • Field : 설명(description), 제약조건(validation), 기본값 등을 정의하는 실제 내용물 역할 
from typing import Annotated
from pydantic import Field
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("TechTree")

@mcp.tool()
def create_roadmap(
    topic: Annotated[
        str, 
        Field(
            description="학습하고 싶은 기술 주제", 
            min_length=2, 
            max_length=50
        )
    ],
    level: Annotated[
        int, 
        Field(
            description="난이도 (1: 초급, 5: 전문가)", 
            ge=1, 
            le=5
        )
    ],
    language: Annotated[
        str, 
        Field(
            description="응답 언어 (kr 또는 en)", 
            pattern=r"^(kr|en)$"
        )
    ] = "kr"  # 기본값 설정
) -> str:
    """사용자 맞춤형 학습 로드맵을 생성합니다."""
    return f"Generating {topic} roadmap (Level {level}) in {language}..."
  • description : LLM에게 이 인자가 무엇인지, 어떤 값을 넣어야 하는지 자연어로 설명
  • min_length / max_length : 문자열(str) 입력값의 글자 수 최소 및 최대 한계를 지정
  • ge (greater or equal) / le (less or equal) : 숫자(int) 입력값의 최소값과 최대값의 범위를 지정
  • pattern : 정규표현식(Regex)을 사용하여 특정 형식(예: kr 또는 en)만 입력되도록 강제

⭐ Structured Output


단순 텍스트 반환 
: 가장 기본적인 형태. 사람이 읽기에는 좋지만 LLM이 데이터를 2차 가공하기엔 불편함. 
@mcp.tool()
def simple_hello(name: str) -> str:
    """인사를 반환합니다."""
    return f"Hello, {name}!"
  • def def_name() -> str 
    • 단순 python 함수에서 : 단순히 사람을 위한 출력 타입 힌트 주석 (타입 에러 x)
    • @mcp.tool() 사용시 : FastMCP가 내부적으로 Pydantic을 사용하여 유효성 검사 / 가능하면 타입 변환 시도
구조화된 데이터 반환 (Pydantic Model)
: 단순 문자열 대신 JSON 객체를 반환. LLM은 이 구조를 완벽하게 이해하고 필요한 데이터만 사용
from pydantic import BaseModel, Field

class RoadmapStep(BaseModel):
    step_number: int
    title: str
    description: str

class RoadmapOutput(BaseModel):
    topic: str
    total_steps: int
    steps: list[RoadmapStep] # 중첩 구조도 가능
    estimated_weeks: int = Field(description="예상 소요 주(week)")
  • 반환할 데이터의 구조(Schema) 정의 ; RoadSmapStep, RoadmapOutput
  • pydantic.BaseModel을 상속받아 결과물의 "설계도"를 만듦 
  • Field 함수는 타입 힌트 외에도 내용이나 규칙에 대한 description을 추가 하고 싶을 때 사용
@mcp.tool()
def create_roadmap(...) -> RoadmapOutput: # <--- 핵심 포인트!
    # ... 로직 ...
  • 함수 반환 타입(Type Hint) 명시 : FastMCP에게 스키마를 생성하라는 의미 
  • "-> " 뒤에 위에서 만든 모델 클래스를 적어줌 ; RoadmapOutput로 정의된 구조
# ... (로직 수행 후)
    return RoadmapOutput(
        topic="Python AI",
        total_steps=3,
        steps=[
            RoadmapStep(step_number=1, title="Basic", description="Syntax..."),
            RoadmapStep(step_number=2, title="Advanced", description="OOP...")
        ],
        estimated_weeks=4
    )
  • 객체 생성 및 반환 함수 내부에서 모델의 인스턴스를 생성해서 리턴
  • RoadmapOutput 구조 클래스에 대한 인스턴스를 생성하여 리턴
  • RoadmapStep 은 RoadmapOutput 내부 데이터의 구조 인스턴스로서 사용됨

-------------------------------------------------

- Gemini 3  model (Pro)의 도움을 받아 작성되었습니다. 

-------------------------------------------------