MCP(Model Context Protocol)는 LLM이 외부 시스템과 상호작용하는 방식을 표준화한 프로토콜
단순히 “모델이 함수를 호출한다”는 수준을 넘어서,
서버가 어떤 기능을 제공하는지 선언하고
클라이언트가 이를 자동으로 발견하며
모델이 상황(Context)에 따라 선택적으로 활용하도록 만드는 계약 구조
즉, MCP는 LLM을 “텍스트 생성기”가 아니라 행동 가능한 에이전트로 만들기 위한 인터페이스 표준
외부 기능의 정의 방식
기능 발견(Discovery) 메커니즘
기능 호출(Call) 방식
입력 / 출력 스키마
실행 결과 및 오류 전달 규칙
MCP Server
Tool / Resource / Prompt를 정의하고 노출
“이 서버는 이런 행동과 지식을 제공한다”를 선언하는 주체
MCP Client
서버에 연결하여 Discovery 수행
모델의 선택을 실제 서버 호출로 변환
LLM
Discovery 결과를 바탕으로
현재 대화/작업 맥락에서 어떤 기능을 쓸지 판단
MCP Server가 자신이 제공하는 기능 메타데이터를 노출
MCP Client가 연결 시 이를 자동으로 수집
LLM이 현재 컨텍스트를 기준으로 적절한 기능을 선택
Client가 선택 결과를 서버 호출로 변환
서버 실행 결과를 다시 모델 컨텍스트로 전달
이 구조의 핵심은 모델이 직접 서버를 호출하지 않는다는 점
모델은 “선택”만 하고, 실행 책임은 Client가 가진다
@mcp.tool()
def add(a: int, b: int) -> int:
return a + b
Tool은 실제로 무언가를 수행하는 실행 단위
계산, 파일 쓰기, DB 변경, 외부 API 호출 등 사이드 이펙트 허용
“지금 이 상황에서 모델이 행동을 취해야 한다”는 의미를 가짐
상태 변경이 가능하므로, 설계상 가장 강력하고 위험한 Primitive
따라서 Tool은 반드시 명확한 책임 범위를 가져야 함
@mcp.resource("config://app/version")
def app_version() -> str:
return "1.3.2"
Resource는 모델이 참조할 수 있는 사실(Fact) 제공자
REST의 GET과 유사하지만, 네트워크 요청 개념은 아님
특징
읽기 전용
무거운 계산 금지
사이드 이펙트 금지
목적은 모델이 “결정을 내리기 위한 재료”를 제공하는 것
URI는 위치가 아니라 의미적 식별자
@mcp.prompt(title="요약")
def summarize(text: str) -> str:
return f"{text}를 3줄로 요약해라"
Prompt는 행동이 아니라 사고 방식의 재사용
모델이 직접 호출하지 않고, 클라이언트 또는 사용자가 선택
팀 단위로 “이런 상황에서는 이런 사고 프레임을 쓴다”를 표준화 가능
Tool과 달리 실행 결과가 시스템 상태를 바꾸지 않음
from mcp.types import PromptMessage
@mcp.prompt()
def review(code: str):
return [
PromptMessage(role="system", content="너는 시니어 리뷰어다"),
PromptMessage(role="user", content=code),
]
단순 문자열이 아니라 역할 기반 메시지 구조
복잡한 사고 유도를 안정적으로 재사용 가능
기존 Low-level MCP
FastMCP
기존 방식
@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)
FastMCP 방식
@mcp.tool()
def echo(msg: str) -> str:
"""메시지를 반환합니다."""
return f"Echo: {msg}"
mcp.run(transport="stdio")
함수 정의 = Tool 정의
타입 힌트와 Docstring이 곧 MCP 메타데이터
실행부는 “어떻게 연결할 것인가”만 결정
기존 방식
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 내부에서 일관되게 처리
uvicorn.run
HTTP 서버를 실행할 뿐
MCP의 존재를 모름
mcp.run
MCP 서버를 의미 단위로 실행
“이 서버는 LLM을 위한 도구 저장소다”라는 전제를 가짐
MCP Initialize / Discovery / Call 흐름 자동 구성
Tool / Resource / Prompt 메타데이터 자동 노출
타입 인트로스펙션 기반 JSON Schema 생성
전송 방식(STDIO, SSE)에 따른 어댑터 자동 적용
@mcp.tool()
def work(ctx: Context):
ctx.info("작업 중")
Context는 실행 중인 Tool이 현재 상황을 모델에게 전달하는 채널
단순 로그가 아니라 Agent UX의 일부
중간 진행 상황, 경고, 오류를 모델 추론에 반영 가능
복잡한 멀티 스텝 작업에서 특히 중요
서버 시작 시 리소스 초기화
서버 종료 시 안전한 정리
DB, 캐시, 외부 SDK 관리
FastMCP 서버를 “일회성 스크립트”가 아니라 서비스로 만드는 요소
Tool / Resource / Prompt 목록 자동 제공
입력 / 출력 스키마 자동 노출
Docstring 기반 설명 포함
클라이언트는 별도 설정 없이 기능 인벤토리 확보
구조적으로 OpenAPI와 유사하지만 대상은 LLM
이 경계를 흐리면 모델 판단이 불안정해지고 시스템 유지보수가 급격히 어려워진다
Tool은 행동
Resource는 사실
Prompt는 사고 방식
Context는 현재 상황
Lifespan은 서버의 생과 사
uvicorn.run은 “웹 서버 실행”
mcp.run은 “MCP 표준을 따르는 Agent Capability Server 실행”
FastMCP는 LLM 시대에 맞게 행동·지식·사고를 구조화하는 서버를 가장 낮은 비용으로 만드는 도구다.