MCP 서버 직접 만들기: Python FastMCP 30분 빌드 가이드
mcp 서버 만들기 한 줄 요약
uv init로 프로젝트를 만들고uv add "mcp[cli]"로 공식 Python SDK를 넣은 뒤,FastMCP객체와@mcp.tool()함수 하나만 등록하면 mcp 서버 만들기 첫 단계는 끝나요.uv run mcp dev server.py로 Inspector에서 먼저 검증하고, Claude Desktop에는 절대 경로 기반 설정으로 연결하면 로컬 stdio 서버가 보통 30분 안쪽에 돌아가요. 초반에 제일 많이 터지는 건 stdout 오염과 경로 문제라서, 로그는stderr로 보내고 실행 파일은 꼭 절대 경로로 적어야 해요.
처음 mcp 서버 만들기 할 때 print() 한 줄 때문에 반나절을 날렸어요. Inspector에서는 멀쩡해 보이는데 Claude Desktop에 붙이는 순간 조용히 실패하더라고요. 알고 보니 stdout은 JSON-RPC 메시지 전용이었고, 실행 경로도 상대 경로로 적어둔 상태였거든요. 문서만 읽고 바로 붙이면 이런 데서 꼭 한 번 막혀요.
2026년 4월 기준으로 Python 쪽 첫 출발은 공식 mcp 패키지 안의 FastMCP를 쓰는 흐름이 제일 간단해요. mcp[cli] 하나로 개발 도구와 서버 실행 흐름을 같이 가져갈 수 있거든요. 여기서는 파일 검색용 도구 하나를 실제로 만들고, Inspector로 검증하고, Claude Desktop까지 붙이는 최소 단계만 남겨둘게요.
MCP 개념 자체가 아직 흐리면 MCP 서버 사용법: Claude Code 연결 가이드부터 먼저 보고 와도 괜찮아요. 이미 서버를 가져다 써본 적이 있다면 바로 아래 명령부터 따라가면 돼요.
FastMCP 프로젝트 세팅
여기서는 프로젝트 뼈대를 잡고 실행 도구까지 한 번에 세팅해요. 2026년 4월 기준 PyPI에서는 공식 mcp가 1.27.x, standalone fastmcp가 3.x대 두 패키지가 있는데, fastmcp 사용법 자체는 어느 쪽이든 비슷해요. 처음부터 둘을 따로 깔아서 헷갈릴 필요 있을까요?
mcp 서버 만들기 첫 시도라면 from mcp.server.fastmcp import FastMCP 기준으로 가는 편이 단순해요. 패키지 선택만 깔끔하면 뒤 단계가 훨씬 덜 꼬이거든요. uv는 Astral이 만든 Python 패키지·가상환경 도구인데, 공식 Python SDK 문서도 이 흐름을 권장하고 있어요.
체크할 건 이 정도면 돼요.
- Python
3.10+ uv설치 완료- 빈 작업 디렉토리 하나
- 첫 서버는 도구 1개만
# 프로젝트 만들기
uv init mcp-demo --python 3.12
cd mcp-demo
# 공식 MCP Python SDK + CLI 추가
uv add "mcp[cli]"
실행하면 pyproject.toml이 생기고, 가상환경이 잡히고, mcp CLI를 바로 쓸 수 있어요. 개발 환경 자체가 아직 낯설면 Claude Code 사용법: 설치부터 첫 실행까지 5분 가이드를 같이 열어두면 덜 막혀요. 터미널 기반 도구 감각은 여기서 비슷하게 이어지거든요.
첫 번째 도구 만들기
여기서부터는 진짜 mcp python 서버 코드 한 파일만 만들어요. 핵심은 서버 인프라보다 도구 모양을 작게 잡는 거예요. 굳이 처음부터 다섯 개씩 넣을 이유가 있을까요? 하나만 만들면 디버깅이 훨씬 쉬워요.
아래 예시는 파일명을 기준으로 찾는 최소 서버예요. 함수 이름, 파라미터 이름, docstring만 잘 써도 도구 스키마가 깔끔하게 나오더라고요.
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("file-finder")
@mcp.tool()
def search_files(query: str, path: str = ".") -> list[str]:
"""지정한 폴더에서 파일명을 검색해요. 파일 위치가 기억 안 날 때 써요."""
base = Path(path)
if not base.exists():
return []
return [str(p) for p in base.rglob(f"*{query}*") if p.is_file()]
def main() -> None:
# stdio 서버로 직접 실행
mcp.run()
if __name__ == "__main__":
main()
이제 바로 Inspector로 붙여보면 돼요. Inspector는 브라우저에서 도구 호출과 응답을 찍어보는 MCP 전용 디버깅 UI예요. Claude Desktop에 넣기 전에 여기서 먼저 통과시키면 시간을 제일 많이 아낄 수 있어요.
# 개발용 Inspector 열기
uv run mcp dev server.py
# 브라우저에서 http://localhost:6274 이 열려요
Inspector에서 확인할 것:
- 도구 목록에
search_files가 보이는지 query와path파라미터 설명이 읽기 좋게 나오는지- 실제로
query=server입력 후 파일 경로 배열이 돌아오는지
직접 해보면 query=server를 넣었을 때 현재 디렉토리의 server.py가 결과에 잡혀요. 응답이 ["./server.py"] 같은 형태로 나오면 성공이에요. 여기서 에러가 나면 뒤에서 Claude Desktop이 멀쩡할 리가 없으니까 꼭 먼저 통과시키세요.
도구를 나중에 에이전트 루프로 엮을 생각이면 AI 에이전트 만들기: 도구 호출부터 워크플로우까지 실전 가이드도 이어서 보면 좋아요. mcp 서버 만들기에서 도구를 왜 작게 쪼개는지가 거기서 더 또렷해지거든요.
Claude Desktop에 연결하기
claude desktop mcp 연결은 코드보다 경로와 JSON에서 더 자주 막혀요. Inspector에서 잘 되는데 Desktop에서만 안 붙으면 거의 여기가 문제더라고요.
macOS에서는 ~/Library/Application Support/Claude/claude_desktop_config.json, Windows에서는 %APPDATA%\Claude\claude_desktop_config.json이 설정 파일 위치예요. 수동으로 붙일 때는 실행 파일도 절대 경로, 프로젝트 디렉토리도 절대 경로로 적는 게 좋아요. GUI 앱은 셸의 PATH를 그대로 못 읽는 경우가 있어서 uv만 적어두면 못 찾는 일이 생기거든요.
{
"mcpServers": {
"file-finder": {
"command": "/absolute/path/to/uv",
"args": [
"run",
"--directory",
"/absolute/path/to/mcp-demo",
"server.py"
]
}
}
}
수동 편집이 귀찮으면 공식 Python SDK의 설치 도우미도 있어요.
# Claude Desktop 등록 보조
uv run mcp install server.py
실제로 많이 틀리는 세 가지:
command에 절대 경로 대신uv만 적음 —which uv로 전체 경로를 먼저 확인하세요- JSON 마지막 항목 뒤에 쉼표를 남김 — JSON은 trailing comma를 허용하지 않아요
- Claude Desktop 창만 닫고 다시 열어서 설정 반영이 안 됨 — macOS는 Dock에서 우클릭 후 종료, Windows는 시스템 트레이에서 완전 종료 후 재시작해야 해요
저도 처음에 2번에서 걸렸는데, 에디터에 JSON linter 플러그인을 켜두니까 바로 잡히더라고요. 이 세 개만 체크하면 연결 문제 대부분은 해결돼요.
로그 위치나 연결 상태 보는 법이 헷갈리면 Claude Desktop 로컬 MCP 도움말도 같이 켜두세요. 클라이언트 쪽 동선은 MCP 서버 셋업를 같이 보면 더 빨리 기준이 잡혀요.
도구 설명 잘 쓰는 법
mcp 서버 만들기에서 코드보다 더 오래 걸리는 게 도구 이름과 설명 다듬기예요. 서버는 떴는데 왜 엉뚱한 도구만 불러올까요? 십중팔구 이름이 모호하거나 설명이 짧아서예요.
처음에 do_stuff이라는 이름으로 도구를 등록했다가 모델이 계속 엉뚱한 도구를 부르더라고요. 이름을 search_files로 바꾸고 설명에 “파일 위치가 기억 안 날 때 써요”를 추가했더니 호출이 제대로 들어오기 시작했어요. 도구 수가 5개 넘어가면 schema만 읽다가 컨텍스트를 꽤 써버리기 때문에, 첫 서버는 도구 수를 적게 두고 설명을 잘 쓰는 게 이득이에요.
| 항목 | Bad | Good |
|---|---|---|
| 도구 이름 | do_stuff |
search_files |
| 설명 | 파일 관련 작업 |
지정한 폴더에서 파일명을 검색해요. 파일 위치가 기억 안 날 때 써요. |
| 파라미터 | q, p |
query, path |
| 입력 검증 | 없음 | 설명 + 제약 조건 추가 |
파라미터 설명까지 붙이면 모델이 훨씬 덜 헷갈려요. FastMCP는 Annotated와 Pydantic Field를 써서 설명과 제약을 같이 넣을 수 있어요.
from typing import Annotated
from pydantic import Field
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("file-finder")
@mcp.tool()
def search_files(
query: Annotated[str, Field(description="찾고 싶은 파일 이름 일부")],
path: Annotated[str, Field(description="검색을 시작할 폴더")] = ".",
limit: Annotated[int, Field(description="최대 결과 수", ge=1, le=50)] = 10,
) -> list[str]:
"""지정한 폴더에서 파일명을 검색해요. 파일 위치가 기억 안 날 때 써요."""
# 실제 구현은 위 예시 참고 — 여기서는 스키마 정의에 집중
return []
이 단계 감각을 잡아두면 나중에 Claude Code 플러그인 마켓플레이스: 설치부터 커스텀 제작까지로 넘어갈 때도 덜 힘들어요. 배포 형태가 바뀌어도 도구 설계 원칙은 그대로 가거든요.
stdio 넘어서기: Streamable HTTP와 배포
로컬에서 잘 도는데도 바로 원격 배포로 달려가면 인증과 운영이 갑자기 커져요. 지금 당장 로컬에서 잘 쓰고 있다면, HTTP 전환은 진짜 필요한 시점까지 미뤄도 돼요.
공식 MCP transport 문서를 기준으로 보면 지금 표준 축은 stdio와 Streamable HTTP예요. SSE(Server-Sent Events)는 호환성 때문에 남아 있는 구형 흐름이라, 새 프로젝트면 우선순위를 낮춰도 돼요.
| 기준 | stdio | Streamable HTTP | SSE |
|---|---|---|---|
| 시작 난도 | 낮음 | 중간 | 중간 |
| 잘 맞는 단계 | 로컬 개발, 1인 사용 | 원격 공유, 팀 사용, 배포 | 레거시 호환 |
| 연결 방식 | 프로세스 실행 | 단일 HTTP 엔드포인트 | 구형 HTTP+SSE |
| 잘 맞는 환경 | Claude Desktop, Claude Code, Cursor | 원격 서버, 여러 클라이언트 | 기존 구형 클라이언트 |
| 지금 선택 | 첫 서버용 | 배포 시점 | 새로 시작이면 보류 |
저는 팀에서 같이 쓸 일이 생기기 전까지 한 달 넘게 stdio로만 돌렸어요. 로컬에서 잘 되는데 HTTP까지 올려야 할까 고민했는데, 솔직히 혼자 쓸 때는 stdio가 세팅도 간단하고 디버깅도 빨라서 전환할 이유가 없더라고요. HTTP 쪽으로 넘길 필요가 생기면 서버 실행 코드를 조금만 바꾸면 돼요.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
"file-finder",
stateless_http=True,
json_response=True,
)
@mcp.tool()
def health_check() -> dict[str, str]:
"""서버 상태를 간단히 확인해요."""
return {"status": "ok"}
if __name__ == "__main__":
mcp.run(transport="streamable-http")
사용 환경도 같이 생각해야 해요. 로컬 stdio 서버는 Claude Desktop에서 바로 붙이고, Claude Code와 Cursor 쪽으로도 옮기기 쉬워요. Cursor는 에디터와 CLI가 같은 MCP 설정을 같이 쓰니까 자동화 실험할 때 편하더라고요. 반대로 Claude.ai 웹이나 모바일까지 열어야 하면 로컬 서버가 아니라 원격 배포 모델을 따로 봐야 해요. transport 기준은 MCP 공식 transport 문서를 같이 보면 더 빨리 정리돼요.
자주 묻는 질문
Q1: FastMCP 쓰면 되나요, 공식 SDK를 직접 써야 하나요?
A: 첫 서버는 공식 mcp[cli] 안의 FastMCP로 시작하면 돼요. 2026년 4월 기준 공식 mcp는 1.27.x이고, 이 흐름만으로도 로컬 서버 빌드와 디버깅은 충분히 커버돼요. transport edge case나 더 낮은 레벨 제어가 꼭 필요할 때만 내려가도 늦지 않아요.
Q2: stdio 서버 만들었는데 Claude Desktop에서 연결이 안 돼요. 왜죠?
A: 거의 셋 중 하나예요. command 절대 경로, JSON 문법, Claude Desktop 완전 종료 후 재시작 — 이 세 개부터 보세요. 창만 닫는 걸로는 설정 반영이 안 먹을 때가 있어요.
Q3: print()로 디버그하면 왜 서버가 죽나요?
A: stdio 방식에서는 stdout이 MCP 메시지 통로예요. print()가 JSON 메시지를 오염시키면 클라이언트가 파싱을 못 해요. 로그는 sys.stderr.write()나 stderr 핸들러를 쓴 logging으로 보내면 돼요.
Q4: stdio와 Streamable HTTP 중 뭘 먼저 고르면 되나요?
A: 로컬 개발과 개인용이면 stdio부터 가세요. 팀 공유, 원격 배포, 여러 클라이언트 연결이 필요해지는 순간 Streamable HTTP로 넘어가면 돼요. 새 프로젝트라면 SSE는 우선순위를 낮춰도 괜찮아요.
Q5: 도구 설명은 어느 정도로 자세히 써야 하나요?
A: “무엇을 하나”만 쓰면 부족해요. “언제 이 도구를 써야 하는지”까지 들어가야 모델이 덜 헷갈려요. 이름은 동사+명사로, 파라미터는 사람이 읽어도 바로 뜻이 보이게 적는 게 좋아요.
다음 단계
지금은 더미 도구 하나만 로컬 stdio로 붙여보세요. 파일 검색 말고 “파일 내용 읽기” 도구를 하나 더 추가하면 에이전트가 파일을 찾고, 열고, 내용을 확인하는 흐름까지 만들 수 있어요. 그다음 AI 에이전트 만들기: 도구 호출부터 워크플로우까지 실전 가이드로 넘어가면 이 서버를 실제 자동화 루프에 넣는 방법까지 바로 이어져요. 팀 배포까지 볼 거면 Claude Code 플러그인 마켓플레이스: 설치부터 커스텀 제작까지도 같이 보면 돼요.
