MCP: FastMCP

calico·2026년 1월 16일

Artificial Intelligence

목록 보기
165/186

1. MCP (Model Context Protocol)


  • MCP(Model Context Protocol)는 LLM이 외부 시스템과 상호작용하는 방식을 표준화한 프로토콜

  • 단순히 “모델이 함수를 호출한다”는 수준을 넘어서,

    • 서버가 어떤 기능을 제공하는지 선언하고

    • 클라이언트가 이를 자동으로 발견하며

    • 모델이 상황(Context)에 따라 선택적으로 활용하도록 만드는 계약 구조

  • 즉, MCP는 LLM을 “텍스트 생성기”가 아니라 행동 가능한 에이전트로 만들기 위한 인터페이스 표준


MCP가 표준화하는 것


  • 외부 기능의 정의 방식

    • 함수 이름, 설명, 입력·출력 형태를 모델이 이해할 수 있는 형태로 규정
  • 기능 발견(Discovery) 메커니즘

    • 클라이언트가 서버에 연결했을 때, 어떤 기능들이 있는지 자동 수집 가능
  • 기능 호출(Call) 방식

    • 모델이 선택한 기능을 어떻게 실행 요청으로 변환하는지 규칙화
  • 입력 / 출력 스키마

    • 모델이 파라미터를 안전하게 생성하고 결과를 해석할 수 있도록 구조 보장
  • 실행 결과 및 오류 전달 규칙

    • 실패도 모델의 컨텍스트로 되돌아가도록 설계



2. MCP 아키텍처 개요


구성 요소


  • MCP Server

    • Tool / Resource / Prompt를 정의하고 노출

    • “이 서버는 이런 행동과 지식을 제공한다”를 선언하는 주체

  • MCP Client

    • 서버에 연결하여 Discovery 수행

    • 모델의 선택을 실제 서버 호출로 변환

  • LLM

    • Discovery 결과를 바탕으로

    • 현재 대화/작업 맥락에서 어떤 기능을 쓸지 판단


전체 흐름


  1. MCP Server가 자신이 제공하는 기능 메타데이터를 노출

  2. MCP Client가 연결 시 이를 자동으로 수집

  3. LLM이 현재 컨텍스트를 기준으로 적절한 기능을 선택

  4. Client가 선택 결과를 서버 호출로 변환

  5. 서버 실행 결과를 다시 모델 컨텍스트로 전달

  • 이 구조의 핵심은 모델이 직접 서버를 호출하지 않는다는 점

  • 모델은 “선택”만 하고, 실행 책임은 Client가 가진다



3. MCP의 3대 Primitive


Tool — 행동(Action)


@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b

  • Tool은 실제로 무언가를 수행하는 실행 단위

  • 계산, 파일 쓰기, DB 변경, 외부 API 호출 등 사이드 이펙트 허용

  • “지금 이 상황에서 모델이 행동을 취해야 한다”는 의미를 가짐

  • 상태 변경이 가능하므로, 설계상 가장 강력하고 위험한 Primitive

  • 따라서 Tool은 반드시 명확한 책임 범위를 가져야 함



Resource — 데이터(Fact)


@mcp.resource("config://app/version")
def app_version() -> str:
    return "1.3.2"
  • Resource는 모델이 참조할 수 있는 사실(Fact) 제공자

  • REST의 GET과 유사하지만, 네트워크 요청 개념은 아님

  • 특징

    • 읽기 전용

    • 무거운 계산 금지

    • 사이드 이펙트 금지

  • 목적은 모델이 “결정을 내리기 위한 재료”를 제공하는 것

  • URI는 위치가 아니라 의미적 식별자



Prompt — 사고방식(Instruction)


@mcp.prompt(title="요약")
def summarize(text: str) -> str:
    return f"{text}를 3줄로 요약해라"
  • Prompt는 행동이 아니라 사고 방식의 재사용

  • 모델이 직접 호출하지 않고, 클라이언트 또는 사용자가 선택

  • 팀 단위로 “이런 상황에서는 이런 사고 프레임을 쓴다”를 표준화 가능

  • Tool과 달리 실행 결과가 시스템 상태를 바꾸지 않음


구조화된 Prompt 예시


from mcp.types import PromptMessage

@mcp.prompt()
def review(code: str):
    return [
        PromptMessage(role="system", content="너는 시니어 리뷰어다"),
        PromptMessage(role="user", content=code),
    ]
  • 단순 문자열이 아니라 역할 기반 메시지 구조

  • 복잡한 사고 유도를 안정적으로 재사용 가능



4. Low-level MCP vs FastMCP


철학적 차이


  • 기존 Low-level MCP

    • MCP를 하나의 네트워크 프로토콜로 취급
    • Server, Transport, Schema를 모두 개발자가 직접 다룸
    • 유연하지만 진입 장벽이 높고 실수 가능성 큼

  • FastMCP

    • MCP를 “도구 레지스트리 + 실행 런타임”으로 재정의
    • 개발자는 함수만 작성
    • 인프라와 프로토콜은 프레임워크가 책임



4.2 코드 비교 – STDIO


기존 방식


@server.list_tools()
async def list_tools():
    return [types.Tool(name="echo", ...)]

@server.call_tool()
async def call_tool(name, args):
    if name == "echo":
        return [types.TextContent(text=args["msg"])]

async with stdio_server() as (r, w):
    await server.run(r, w, options)

  • Tool 정의와 실행 로직이 분리
  • MCP 내부 흐름을 정확히 이해해야 함
  • 도구 하나 추가하는데 인프라 코드가 함께 증가

FastMCP 방식


@mcp.tool()
def echo(msg: str) -> str:
    """메시지를 반환합니다."""
    return f"Echo: {msg}"

mcp.run(transport="stdio")

  • 함수 정의 = Tool 정의

  • 타입 힌트와 Docstring이 곧 MCP 메타데이터

  • 실행부는 “어떻게 연결할 것인가”만 결정



4.3 코드 비교 – SSE


기존 방식


async def handle_sse(request):
    transport = SseServerTransport("/message")
    # MCP 브릿지 코드 다량 필요

app = Starlette(routes=[Route("/sse", handle_sse)])
uvicorn.run(app)

  • 웹 서버와 MCP 서버를 억지로 결합

  • 프로토콜 처리, 변환 로직, 에러 핸들링을 직접 구현


FastMCP 방식


mcp.run(
    transport="sse",
    host="0.0.0.0",
    port=8000
)

  • MCP 프로토콜

  • Discovery

  • Schema 생성

  • 웹 서버 구동

    모두 FastMCP 내부에서 일관되게 처리



5. uvicorn.run() vs mcp.run()인가


MCP 관점에서의 핵심 이유


  • uvicorn.run

    • HTTP 서버를 실행할 뿐

    • MCP의 존재를 모름

  • mcp.run

    • MCP 서버를 의미 단위로 실행

    • “이 서버는 LLM을 위한 도구 저장소다”라는 전제를 가짐


기술적으로 mcp.run이 하는 일


  • MCP Initialize / Discovery / Call 흐름 자동 구성

  • Tool / Resource / Prompt 메타데이터 자동 노출

  • 타입 인트로스펙션 기반 JSON Schema 생성

  • 전송 방식(STDIO, SSE)에 따른 어댑터 자동 적용



6. Context — 런타임 제어 핵심


@mcp.tool()
def work(ctx: Context):
    ctx.info("작업 중")

  • Context는 실행 중인 Tool이 현재 상황을 모델에게 전달하는 채널

  • 단순 로그가 아니라 Agent UX의 일부

  • 중간 진행 상황, 경고, 오류를 모델 추론에 반영 가능

  • 복잡한 멀티 스텝 작업에서 특히 중요



7. Lifespan — 서버 생명주기


  • 서버 시작 시 리소스 초기화

  • 서버 종료 시 안전한 정리

  • DB, 캐시, 외부 SDK 관리

  • FastMCP 서버를 “일회성 스크립트”가 아니라 서비스로 만드는 요소



8. Discovery — 자동 기능 발견


  • Tool / Resource / Prompt 목록 자동 제공

  • 입력 / 출력 스키마 자동 노출

  • Docstring 기반 설명 포함

  • 클라이언트는 별도 설정 없이 기능 인벤토리 확보

  • 구조적으로 OpenAPI와 유사하지만 대상은 LLM



9. 설계 원칙 요약


이 경계를 흐리면 모델 판단이 불안정해지고 시스템 유지보수가 급격히 어려워진다

  • Tool은 행동

  • Resource는 사실

  • Prompt는 사고 방식

  • Context는 현재 상황

  • Lifespan은 서버의 생과 사



10. 결론


  • uvicorn.run은 “웹 서버 실행”

  • mcp.run은 “MCP 표준을 따르는 Agent Capability Server 실행”

FastMCP는 LLM 시대에 맞게 행동·지식·사고를 구조화하는 서버를 가장 낮은 비용으로 만드는 도구다.

0개의 댓글