MCP: FastMCP

calico·2026년 1월 16일

Artificial Intelligence

목록 보기
165/175

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 시대에 맞게 행동·지식·사고를 구조화하는 서버를 가장 낮은 비용으로 만드는 도구다.

profile
All views expressed here are solely my own and do not represent those of any affiliated organization.

0개의 댓글