MCP 서버를 구축하는 단계별 방법
요약
Model Context Protocol(MCP) 서버를 구축하기 위한 단계별 가이드를 제공합니다. Python SDK를 사용하여 도구, 리소스, 프롬프트를 정의하고 stdio 또는 HTTP를 통해 클라이언트와 연결하는 방법을 설명합니다.
핵심 포인트
- MCP 서버의 3대 요소: 도구(tools), 리소스(resources), 프롬프트(prompts)
- Python SDK와 uv를 활용한 효율적인 개발 환경 구축
- FastMCP를 이용한 고수준 API 기반의 서버 스캐폴딩 방법
- MCP 사양 버전(spec revision)에 따른 호환성 주의 사항
요약
MCP 서버를 구축하려면: 공식 MCP SDK를 설치하고, 타입이 지정된 입력값(typed inputs)으로 도구(tools)를 선언하며, 선택적으로 리소스(resources)와 프롬프트(prompts)를 노출합니다. 그 다음 stdio 또는 HTTP를 통해 서버를 실행하고, Claude와 같은 MCP 클라이언트에 연결하여 테스트합니다. 최소한의 Python 서버는 약 10줄 정도이며, 핵심 작업은 무엇을 노출할지 선택하고 모든 입력값을 검증하는 것입니다.
이 내용은 _구축(build)_에 관한 것입니다. MCP가 무엇인지, MCP의 세 가지 기본 요소(primitives)는 무엇인지, 그리고 API와 어떻게 다른지에 대해서는 Model Context Protocol이란 무엇인가를 먼저 확인하십시오. 이 페이지는 해당 내용을 알고 있다는 가정하에 바로 코드로 넘어갑니다.
사전 요구 사항
로컬에서 서버를 실행하는 데 필요한 사항은 매우 적습니다:
- 공식 SDK가 있는 언어. Python과 TypeScript가 가장 성숙해 있으며, 동일한 프로토콜이 다른 언어용으로도 구현되어 있습니다. 이 가이드는 Python SDK(대부분의 사람들이 검색하는 보조 경로)를 사용하며, TypeScript SDK와 동일한 부분에 대해서는 참고 사항을 제공합니다.
- Python 3.10 이상 및 환경 관리를 위한 uv (권장) 또는
pip. - 테스트를 위한 MCP 클라이언트 — Claude Desktop 또는 SDK와 함께 제공되는 MCP Inspector. 서버 자체를 구축하거나 실행하는 데 클라우드 자격 증명(credentials)은 필요하지 않습니다.
개념적으로 서버는 세 가지 요소 — 도구(tools) (모델이 호출 가능한 함수), 리소스(resources) (읽을 수 있는 데이터), 프롬프트(prompts) (재사용 가능한 템플릿)를 노출합니다. 아래 단계는 해당 순서대로 이를 추가합니다. 정확한 SDK 시그니처(signatures)는 계속 진화하므로, 코드 스니펫은 현재 형태를 나타내는 것으로 간주하고 배포 전에 최신 문서를 확인하십시오.
이 빌드가 어떤 사양 개정(spec revision)을 대상으로 하는지. 여기의 코드는 MCP 개정 버전 2025-11-25를 대상으로 합니다. 이는 사양의 버전 페이지에서 여전히 현재 프로토콜 버전으로 명시하고 있는 개정 버전입니다. 2026-07-28 개정 버전이 이미 발표되었으며, 이는 와이어 포맷(wire format)을 실질적으로 재설계했습니다. 2025-11-25를 대상으로 구축된 서버는 현재까지는 규격을 준수합니다. 새로운 개정 버전이 서버 작성자에게 미치는 변화는 아래에 설명되어 있으므로, 지금 바로 구축하고 향후 전환을 계획할 수 있습니다.
1단계: 서버 스캐폴딩 (scaffold the server)
프로젝트를 생성하고, SDK를 설치한 다음, 실행 가능한 가장 작은 규모의 서버를 작성합니다. uv를 사용하는 경우:
uv init weather
cd weather
uv venv
...
그 다음, 서버 객체와 엔트리 포인트(entry point)가 포함된 server.py를 생성합니다. FastMCP는 고수준(high-level) Python API입니다. 서버의 이름을 지정하고 전송 계층(transport)을 통해 실행합니다.
from mcp.server.fastmcp import FastMCP
# 서버의 이름을 지정합니다. 클라이언트는 연결 시 이 이름을 확인합니다.
...
이제 실행이 가능하지만, 아직 아무것도 노출하지 않는 상태입니다. TypeScript SDK에서는 이에 상응하는 작업이 @modelcontextprotocol/sdk에서 McpServer를 생성하고 이를 전송 계층(transport)에 연결하는 것입니다. 구조는 동일하며 문법만 다릅니다.
2단계: 도구 (tool) 정의
도구(tool)는 모델이 호출하기로 결정할 수 있는 함수입니다. Python SDK에서는 일반적인 타입 힌트가 지정된 함수에 데코레이터(decorator)를 사용합니다. 이때 타입 힌트(type hints)는 입력 JSON 스키마(JSON schema)가 되며, 독스트링(docstring)은 모델이 도구 호출 시점을 결정하기 위해 읽는 설명(description)이 됩니다.
@mcp.tool()
def add(a: int, b: int) -> int:
"""두 숫자를 더하고 합계를 반환합니다."""
...
실제 도구는 데이터베이스 쿼리, 내부 API, 파일 작업과 같이 유용한 무언가를 감싸는 역할을 합니다. 핸들러(handler)는 단순한 함수이므로, 여기서 기존 서비스를 호출하고 모델이 사용할 수 있는 결과를 반환하면 됩니다.
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""특정 위치의 일기 예보를 가져옵니다."""
...
여기서 중요한 두 가지 사항이 있으며, 둘 다 아키텍처(architectural)와 관련이 있습니다. 첫째, 스키마(schema)는 모델이 채워 넣어야 하는 _계약(contract)_이므로 인자(argument) 이름과 설명을 정확하게 유지해야 합니다. 둘째, 함수는 해당 프로세스가 가진 권한 내에서 실행되므로, 필요한 최소한의 범위로 제한(scope)해야 합니다. 이 내용은 5단계에서 다시 다루겠습니다.
3단계: 리소스(resources) 및 프롬프트(prompts) 추가
도구(Tools)는 동작을 수행합니다. 반면 **리소스 (resources)**는 모델이 읽을 수 있는 데이터를 노출하며, **프롬프트 (prompts)**는 재사용 가능한 템플릿입니다. 모든 서버가 이 세 가지를 모두 가질 필요는 없습니다. 사용 사례(use case)에 필요한 것만 추가하세요.
리소스는 URI로 주소가 지정됩니다. 정적(static)일 수도 있고, 클라이언트가 채워 넣을 매개변수가 포함된 템플릿일 수도 있습니다.
@mcp.resource("config://app")
def get_config() -> str:
"""모델이 읽을 수 있는 정적 설정 (Static configuration)."""
...
프롬프트는 서버가 게시하는 매개변수화된 템플릿으로, 이를 통해 공통 워크플로(workflow)를 매번 클라이언트마다 새로 작성하지 않고 한 번만 작성하여 사용할 수 있습니다.
@mcp.prompt()
def summarize_forecast(city: str) -> str:
return f"{city}의 일기 예보를 두 문장으로 요약해 주세요."
이러한 구분은 의도적인 것입니다. 클라이언트는 모델이 도구(tools)를 자율적으로 호출하게 두는 동시에, 사용자에게 리소스와 프롬프트를 노출할 수 있습니다(컨텍스트로 가져오거나 메뉴에서 선택할 수 있도록). 이들을 분리하여 유지하는 것이 여러 클라이언트에 걸쳐 서버를 예측 가능하게 만드는 핵심입니다.
4단계: 실행 및 연결
실제로 사용하게 될 두 가지 전송(transports) 방식이 있습니다:
- stdio — 클라이언트가 실행하는 로컬 서브프로세스 (subprocess)로 서버가 동작합니다. 이는 데스크톱 클라이언트와 로컬 개발의 기본값입니다:
mcp.run(transport="stdio"). - Streamable HTTP — 호스팅되거나 공유되는 서버를 위해 원격 클라이언트가 연결하는 웹 서비스 (web service)로 서버가 동작합니다:
mcp.run(transport="streamable-http"). 이 방식은 전송 프로토콜 개정안2026-07-28에서 가장 많이 변경되었습니다: 프로토콜 레벨의 세션 (sessions)과Mcp-Session-Id헤더가 제거되었으며,Mcp-Method,Mcp-Name,MCP-Protocol-Version이 필수 요청 헤더 (request headers)가 되었습니다. 귀하의2025-11-25서버는 계속 작동합니다 — 원격 배포를 계획하기 전에 2026-07-28 개정안의 변경 사항을 확인하세요.
로컬 stdio 서버를 Claude Desktop에 연결하려면, 설정 파일의 경로를 서버를 실행하는 명령어로 지정하세요. 가장 빠른 방법은 SDK가 대신 설치하도록 하는 것입니다:
uv run mcp install server.py
또는 claude_desktop_config.json에 직접 항목을 작성하세요 (절대 경로를 사용해야 합니다):
{
"mcpServers": {
"weather": {
...
클라이언트를 재시작하면 도구 (tools), 리소스 (resources), 프롬프트 (prompts)가 나타납니다. 이제 서버는 이에 연결되는 에이전트 (agent)의 도구/액션 레이어 (tools/action layer)의 일부가 됩니다. 이는 특정 앱에 종속되지 않고 모든 MCP 호환 클라이언트에서 재사용할 수 있습니다.
5단계: 테스트 및 보안 강화
계약 (contract)의 양쪽 측면인 탐색 (discovery, 클라이언트가 귀하의 기능을 인식하는가?)과 실행 (execution, 호출이 올바르게 실행되고 반환되는가?)을 모두 테스트하세요. SDK에는 클라이언트 연결 설정 없이도 정확히 이 용도로 사용할 수 있는 인스펙터 (inspector)가 포함되어 있습니다:
uv run mcp dev server.py
이를 통해 MCP Inspector가 열리며, 여기서 서버가 광고하는 도구, 리소스, 프롬프트를 나열하고 모델이 접근하기 전에 테스트 인자 (test arguments)를 사용하여 이를 호출해 볼 수 있습니다.
그다음에는 서버를 보안상 매우 중요한 (security-critical) 요소로 취급해야 합니다. 왜냐하면 서버는 신뢰할 수 없는 호출자(untrusted caller)를 대신하여 실행되기 때문입니다. 즉, 모델의 인자(arguments)가 잘못되었거나, 형식이 맞지 않거나, 혹은 모델이 방금 읽은 프롬프트 주입 (prompt-injected) 콘텐츠에 의해 유도될 수 있습니다. 반드시 지켜야 할 사항은 다음과 같습니다:
- 모든 입력값 검증 (Validate every input). 작업을 수행하기 전에 스키마 (schema) 및 자체 제약 조건에 따라 인자(arguments)를 확인하십시오. 모델의 출력은 정의상 신뢰할 수 없는 입력 (untrusted input)입니다.
- 최소 권한 원칙 (Least privilege). 각 도구 (tool)의 자격 증명 (credentials)과 범위를 필요한 최소한으로 제한하여, 혼동되거나 탈취된 호출이 의도한 범위를 벗어나지 않도록 하십시오.
- 되돌릴 수 없는 작업에는 인간 참여 (Human-in-the-loop on the irreversible). 삭제, 결제, 외부 메시지 전송과 같이 쉽게 되돌릴 수 없는 모든 작업에는 명시적인 승인을 요구하십시오.
도구의 경계가 곧 공격 표면 (attack surface)입니다
모델이 행동을 취할 수 있는 순간, 조작된 입력 또한 그 행동을 취할 수 있습니다. 모든 도구 입력을 스키마 및 자체 규칙에 따라 검증하고, 각 도구의 권한을 최소한으로 제한하며, 되돌릴 수 없는 작업은 인간의 승인을 거치도록 하십시오. 이것은 퀵스타트 (quickstart)에서는 생략되지만, 프로덕션 (production) 환경에서는 생략할 수 없는 부분입니다.
2026-07-28 개정판이 서버 작성자에게 변경하는 점
위의 모든 내용은 2025-11-25 개정판을 대상으로 합니다. 2026-07-28 개정판이 발행되었으며, 먼저 이해해야 할 차이점은 "발행됨"과 "현재" 사이의 간극입니다. 스펙 리포지토리 (spec repo)의 스키마 상수 (schema constant)는 LATEST_PROTOCOL_VERSION = "2026-07-28"로 읽히지만, 문서 사이트의 버전 페이지에는 여전히 토씨 하나 틀리지 않고 "현재 프로토콜 버전은 2025-11-25입니다"라고 명시되어 있습니다. 이전 개정판만 지원하는 서버도 현재는 규격에 부합 (conformant)합니다. 버전을 업그레이드할 때 변경되는 사항은 다음과 같습니다:
-
핸드셰이크 (handshake)가 사라졌습니다. 이번 개정판은 MCP를 상태 비저장 (stateless) 방식으로 만들기 위해
initialize/notifications/initialized핸드셰이크를 제거합니다: "이제 모든 요청은_meta에 프로토콜 버전과 클라이언트 기능 (capabilities)을 포함합니다", 그리고 서버는 "동일한 연결 상의 이전 요청에 의존해서는 안 됩니다 (MUST NOT)". Streamable HTTP에서는 프로토콜 수준의 세션과Mcp-Session-Id헤더도 함께 제거됩니다. -
프로토콜 수준에서 세션을 대체하는 것은 없습니다. 호출 간의 상태 (cross-call state)는 명시적으로 변하며, 서버에서 생성된 핸들 (handles)은 일반적인 도구 인자 (tool arguments)로 전달됩니다. 만약 귀하의 도구가 클라이언트가 연결 초기에 설정한 무언가에 의존한다면, 해당 의존성은 도구 스키마 (schema)의 파라미터가 되어야 합니다.
-
server/discover를 반드시 구현해야 합니다. 서버는 새로운 RPC를 반드시 (MUST) 구현해야 합니다. 클라이언트는 이를 먼저 호출할 수도 있지만 (MAY), 대신 다른 RPC를 인라인으로 호출할 수도 있습니다. 특정 클라이언트가 이를 사용하든 안 하든, 구현하는 것은 귀하의 의무입니다. -
결과에 캐싱 힌트 (caching hints)가 포함됩니다. 캐싱 페이지는 규범적 (normative)입니다: 서버는 다음 6가지 작업에 대한 완료된 결과에
ttlMs와cacheScope를 반드시 (MUST) 포함해야 합니다 —server/discover,tools/list,prompts/list,resources/list,resources/templates/list, 그리고resources/read.ttlMs를 생략하면 클라이언트는 이를0으로 간주하므로, 호출 실패보다는 캐싱 기회를 놓치는 비용이 발생합니다. 또한 모든 결과에는 필수적인resultType이 추가됩니다. -
JSON-RPC 에러 코드가 재번호 매기기 되었습니다.
HeaderMismatch는-32001→-32020으로,MissingRequiredClientCapability는-32003→-32021로,UnsupportedProtocolVersion은-32004→-32022로 변경되었습니다. 이전 노트를 기준으로 작성된 에러 핸들링 (error handling)은 잘못된 코드를 방출하게 됩니다.
이 모든 것이 이번 주에 배포하는 서버를 쓸모없게 만드는 것은 아닙니다. 이번 개정판은 호환성 매트릭스 (compatibility matrix)를 게시하며, 구세대 클라이언트 (dual-era client)에 의해 도달되는 레거시 서버 (legacy server) 행에는 "작동함 (Works)"이라고 표시됩니다. 지원 중단 예정 (Deprecated) 기능들은 "지원 중단 기간 동안 완전히 기능이 유지"되며, Roots, Sampling, Logging 및 동적 클라이언트 등록 (dynamic client registration)은 각각 "2027-07-28 또는 그 이후에 출시된 첫 번째 개정판"이라는 게시된 가장 빠른 제거 시점을 가집니다. 이는 최소 12개월이며, 해당 날짜는 일정이 아닌 최저 기준선 (floor)입니다.
만약 클라이언트 측도 함께 유지 관리한다면, 하위 호환성 조사 (backward-compatibility probe) 방식은 전송 방식 (transport)에 따라 달라지며, 이는 대부분의 기술 문서들이 생략하는 세부 사항입니다. stdio 방식에서는 server/discover로 조사하며, 인식된 최신 에러가 아닌 모든 에러에 대해 폴백 (fall back)을 수행합니다. Streamable HTTP에는 그러한 조사 방식이 없습니다. 대신 최신 요청을 시도한 후, 폴백하기 전에 400 Bad Request의 본문 (body)을 검사해야 합니다. 왜냐하면 최신 서버들은 버전 및 기능 에러에 대해서도 400을 반환하기 때문입니다. 이 본문을 읽는 것이 핵심적인 부분입니다. 이를 무시하는 클라이언트는 눈에 보이지 않게 성능이 저하됩니다. 이러한 비대칭성을 숙지할 가치가 있습니다: 레거시 서버는 계속 작동하지만, 레거시 클라이언트는 그렇지 않습니다. 최신 서버에 대한 레거시 클라이언트의 매트릭스 행은 "실패 (Fails)"로 표시되는데, 이는 레거시 클라이언트에게는 "앞으로 나아가는 메커니즘 (fall-forward mechanism)"이 없기 때문입니다.
여기서 자연스러운 다음 단계는 이 서버를 완전한 에이전트 (agent)로 구성하는 것입니다: 도구/액션 (tools/action) 레이어가 오케스트레이션 (orchestration), 메모리 (memory), 그리고 검색 (retrieval) 사이에 어떻게 위치하는지는 agentic AI architecture에서 다루며, 더 광범위한 기술 세트는 curriculum에 있습니다.
자주 묻는 질문 (Frequently asked questions)
MCP 서버를 어떻게 구축하나요?
공식 MCP SDK를 설치하고, 이름이 지정된 서버 객체를 생성하며, 도구(tools)를 타입이 지정된 함수로 선언합니다 (이때 타입 힌트가 입력 스키마(input schema)가 됩니다). 선택적으로 리소스(resources)와 프롬프트(prompts)를 노출할 수 있으며, stdio 또는 스트리밍 가능한 HTTP를 통해 서버를 실행한 다음, Claude와 같은 MCP 클라이언트를 연결하여 탐색(discovery) 및 실행을 테스트합니다. 최소한의 서버는 약 10줄 정도면 충분하지만, 실제 작업은 무엇을 노출할지 선택하고 모든 입력을 검증하는 것입니다.
어떤 언어로 MCP 서버를 구축할 수 있나요?
MCP SDK가 있는 모든 언어로 가능합니다. Python과 TypeScript가 가장 성숙하고 문서화가 잘 되어 있으며, 다른 여러 언어를 위한 SDK도 존재합니다. MCP는 라이브러리가 아니라 프로토콜이므로, 한 언어로 작성된 서버는 다른 어떤 언어로 작성된 클라이언트와도 상호 운용됩니다. 따라서 기존의 도구와 API가 이미 구현되어 있는 언어를 선택하십시오.
Python에서 MCP 서버를 어떻게 구축하나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기