MCP 서버 (Python, SDK, 구현)
이전 글에서 Claude Desktop에 외부 MCP 서버를 붙이는 방법을 다뤘다면, 이번 글은 그 반대 방향이다. 즉 사내 시스템이나 개인 프로젝트의 데이터를 외부 호스트에 노출하기 위해 직접 MCP 서버를 만드는 과정을 정리한다. 결론부터 말하면 Python SDK가 워낙 잘 다듬어져 있어 30분 안에 첫 서버를 띄울 수 있다. 제가 처음 커스텀 서버를 만들어 본 게 2025년 말이었는데, 솔직히 그때까지만 해도 "프로토콜 서버"라는 단어가 무겁게 느껴져 시도조차 미루고 있었고, 막상 해보니 데코레이터 두 줄로 끝나는 구조라 헛웃음이 났다.

MCP 서버의 기본 구조와 Python SDK 설치
MCP 서버는 호스트(Claude Desktop 등)와 JSON-RPC 2.0 메시지를 주고받는 가벼운 프로세스다. 호스트가 띄워 주는 자식 프로세스로 동작하며, stdin/stdout(표준 입출력) 위에서 메시지를 교환한다. 여기서 표준 입출력 통신이란 별도의 네트워크 포트 없이 운영체제가 제공하는 파일 디스크립터로 메시지를 주고받는 방식을 가리킨다. 이 구조 덕분에 서버는 인터넷에 공개되지 않아도 되고, 보안 경계도 호스트가 띄우는 동일 사용자 권한 안에 갇힌다.
Python SDK는 공식 저장소에서 배포되며 pip 또는 uv로 설치할 수 있다. uv는 최근 사실상 표준이 된 Python 패키지·환경 관리자로, 설치 속도가 pip 대비 10배 이상 빠르다. 명령은 단순하다.
uv add mcp
# 또는
pip install mcp
설치가 끝나면 SDK 안의 FastMCP 클래스를 가져와 서버 객체를 만든다. FastMCP는 표준 MCP 사양을 데코레이터 기반으로 감싼 고수준 API로, 클래스를 직접 상속하지 않고도 함수에 데코레이터만 붙여 도구·리소스를 등록할 수 있게 해 준다(출처: MCP Python SDK 공식 저장소). 솔직히 처음에는 "데코레이터 붙이는 게 정말 끝일까" 싶었는데, 실제로 첫 도구를 호스트에서 호출했을 때 그게 끝이었음을 확인하고 나서야 SDK가 얼마나 잘 추상화되어 있는지 체감했다.
Tool, Resource, Prompt 등록하기
MCP 서버가 외부에 노출하는 단위는 세 가지로 정해져 있다. Tool(도구), Resource(리소스), Prompt(프롬프트)다. Tool은 모델이 호출할 수 있는 함수, Resource는 모델이 읽을 수 있는 데이터, Prompt는 자주 쓰는 대화 템플릿이다. FastMCP에서는 이 셋을 모두 데코레이터로 표현한다.
다음은 가장 단순한 Tool 등록 예시다. 함수의 시그니처와 docstring을 SDK가 자동으로 읽어 JSON 스키마로 변환하기 때문에, 호스트 측에서 별도의 스키마 파일을 작성할 필요가 없다.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("내_첫_서버")
@mcp.tool()
def add(a: int, b: int) -> int:
"""두 정수를 더한다."""
return a + b
@mcp.resource("config://app")
def get_config() -> str:
"""앱 설정 파일을 문자열로 반환한다."""
return open("config.json").read()
if __name__ == "__main__":
mcp.run()
여기서 docstring(독스트링)이란 함수 첫 줄에 붙이는 큰따옴표 세 개로 둘러싼 설명문을 의미하는데, 모델은 이 설명을 보고 도구를 언제 써야 할지 판단한다. 따라서 docstring은 단순한 주석이 아니라 사실상 모델에게 보내는 사용 설명서이며, 정확하고 구체적으로 적을수록 도구 호출 정확도가 올라간다. 제 경험상 같은 도구라도 "두 정수를 더한다" 한 줄과 "두 정수를 받아 합을 반환한다. 음수도 허용한다" 두 줄의 호출 정확도 차이가 꽤 컸고, 그 후로는 docstring을 다른 어떤 코드보다 신경 써서 쓰는 습관이 생겼다.
30분 만에 만드는 첫 번째 MCP 서버
위 예시 코드를 server.py로 저장하고 다음 명령으로 실행하면 서버가 표준 입출력 위에서 호스트와의 연결을 기다리는 상태가 된다.
uv run server.py
서버 자체는 단독으로 실행되지 않고 호스트가 spawn(자식 프로세스 생성)해 주어야 하므로, 다음 단계는 Claude Desktop의 claude_desktop_config.json에 서버를 등록하는 일이다. 등록 방식은 외부 서버를 붙일 때와 동일하다.
{
"mcpServers": {
"내_첫_서버": {
"command": "uv",
"args": ["run", "/절대경로/server.py"]
}
}
}
Claude Desktop을 재시작하면 좌하단에 연결된 서버 수가 1 증가하고, 채팅창에서 "두 수 좀 더해줘, 17이랑 25" 같은 자연어 명령을 던지면 자동으로 add 도구를 호출한 결과를 보여 준다(출처: MCP 공식 Quickstart). 솔직히 이건 예상 밖이었는데, 도구 이름을 명시적으로 부르지 않아도 모델이 docstring을 읽고 의도를 매칭해 도구를 골라낸다는 점이 가장 인상적이었다. 즉 사용자는 "내 시스템이 무엇을 할 수 있는지"만 자연어로 말하면 되고, 함수 시그니처와 호출은 모델이 알아서 채워 준다는 의미다.
이 30분짜리 첫 서버를 변형해 사내 데이터베이스 조회용 도구, GitHub Issues 자동 생성 도구, 사내 위키 검색 도구 같은 실용 서버로 발전시키는 일은 의외로 빠르게 진행된다. 핵심 작업이 함수 본문 한두 줄을 바꾸는 일에 가깝기 때문이다. 제가 학교 동아리에서 만든 첫 실용 서버는 회의록 검색 도구였는데, 마크다운 파일이 모인 폴더를 가리키는 함수 한 개로 시작했고, 이후 태깅·날짜 필터 기능을 추가하면서 한 달 만에 동아리 전체가 Claude로 회의록을 검색하는 흐름이 자리 잡았다. 다음 글에서는 이런 실무 활용 사례를 카테고리별로 정리해 본다.
메타 디스크립션: Python MCP SDK와 FastMCP를 활용해 30분 안에 첫 MCP 서버를 만드는 전 과정을 설치, Tool·Resource 등록, 호스트 연동까지 정리합니다.