FastMCP로 사내 레거시 시스템을 위한 Python MCP 서버를 10분 만에 구축하는 방법
부제: LLM 사내 시스템 연동 | Model Context Protocol 구현 실전 가이드
수십 년 된 사내 시스템을 교체하지 않아도 됩니다. FastMCP를 사용하면 기존 레거시 코드에 AI 인터페이스를 덧씌우는 방식으로, Claude나 GPT-4 같은 LLM이 자연어로 사내 시스템과 직접 대화할 수 있는 환경을 10분 만에 만들 수 있습니다. "김철수 팀장 올해 연차 잔여일 알려줘"라는 질문에 AI가 자동으로 HR 시스템을 조회해 답하는 장면, 생각보다 훨씬 빨리 구현할 수 있습니다.
이 글을 따라 하면 기존 사내 시스템에 @mcp.tool 데코레이터를 붙여 MCP 서버를 구축하고, Claude Desktop에서 자연어로 레거시 DB를 조회하는 데모를 10분 안에 완성할 수 있습니다. 기본 Tool 선언부터 OpenAPI 자동 변환, 멀티 서버 통합, 보안 고려사항까지 실무에서 바로 쓸 수 있는 수준으로 설명합니다.
이미 REST API나 DB 연동 코드가 있다면, 그 코드를 그대로 재사용할 수 있습니다. 새로 만들 것은 데코레이터 몇 줄뿐입니다.
이 글의 대상 독자: Python 백엔드 개발자.
async/await, 데코레이터, 세션 풀 같은 Python 개념을 사용합니다. Python이 처음이라면 아래 "Python 데코레이터란?" 콜아웃을 먼저 확인하시면 좋습니다.
핵심 개념
MCP가 해결하는 문제: N×M 통합 지옥
AI 모델을 사내 시스템과 연결하려면 기존에는 시스템마다 커스텀 플러그인을 만들어야 했습니다. AI 모델이 3개, 연결할 시스템이 5개라면 최대 15개의 커넥터가 필요합니다. MCP는 이 "N×M 통합 문제"를 해결합니다.
MCP(Model Context Protocol) — Anthropic이 2024년 11월 공개한 오픈 표준. LLM이 외부 도구·데이터 소스·시스템과 표준화된 방식으로 통신할 수 있게 하는 프로토콜입니다. MCP 서버 하나를 만들면 Claude, GPT-4, Azure AI Agent 등 MCP를 지원하는 모든 AI 클라이언트에서 사용할 수 있습니다.
2025년 기준 OpenAI, Microsoft Azure가 모두 MCP를 공식 채택했고, 공개된 MCP 서버만 5,800개를 넘어섰습니다. 월간 SDK 다운로드 수는 9,700만 회를 돌파하며 사실상 AI-시스템 연동의 업계 표준으로 자리를 잡았습니다.
FastMCP의 세 가지 구성 요소
FastMCP는 MCP 서버를 Pythonic하게 구축할 수 있는 고수준 프레임워크입니다. FastMCP 1.0은 공식 MCP Python SDK에 통합되었고, 현재 독립 관리되는 2.x 버전은 하루 100만 회 이상 다운로드되고 있습니다.
MCP 서버는 세 가지 핵심 컴포넌트로 이루어집니다.
| 컴포넌트 | 데코레이터 | 역할 |
|---|---|---|
| Tools | @mcp.tool |
LLM이 호출하는 함수 — DB 조회, API 호출, 계산 등 |
| Resources | @mcp.resource |
읽기 전용 데이터 노출 — 파일, 설정, 스키마 등 |
| Prompts | @mcp.prompt |
복잡한 작업을 위한 재사용 가능한 프롬프트 템플릿 |
이 글에서는 실무에서 가장 많이 쓰이는 @mcp.tool을 중심으로 설명합니다. @mcp.resource는 예시 2에서 스키마 노출 용도로 함께 소개하며, @mcp.prompt는 반복적인 워크플로 자동화에 활용되지만 이번 글 범위에서는 제외합니다.
왜 10분이 가능한가
핵심은 두 가지입니다.
- 데코레이터 기반 도구 등록 — 기존 함수에
@mcp.tool을 붙이는 것만으로 LLM이 인식하는 도구가 됩니다. 별도 스키마 정의나 JSON 직렬화 코드가 필요 없습니다. - OpenAPI 스펙 자동 변환 —
FastMCP.from_openapi()를 사용하면 Swagger/OpenAPI 스펙이 있는 레거시 REST API를 한 줄로 MCP 서버로 변환합니다.
# pip install fastmcp 또는 uv add fastmcp
from fastmcp import FastMCP
# from legacy_hr_module import legacy_hr_db ← 기존 DB 연결 모듈 그대로 가져오기
mcp = FastMCP("사내 시스템 MCP 서버")
@mcp.tool
def query_employee(employee_id: str) -> dict:
"""레거시 HR 시스템에서 직원 정보를 조회합니다."""
return legacy_hr_db.get_employee(employee_id) # 기존 코드 재사용
if __name__ == "__main__":
mcp.run() # 로컬은 stdio, 원격 배포는 transport="http"Python 데코레이터란? —
@mcp.tool처럼 함수 위에@로 시작하는 표현이 데코레이터입니다. 함수를 감싸 추가 동작을 부여하는 Python 문법으로, 여기서는 "이 함수를 MCP Tool로 등록하라"는 의미입니다. Java의@RestController, Spring의@GetMapping과 개념적으로 같습니다.
stdio vs HTTP 트랜스포트 —
stdio는 Claude Desktop 같은 로컬 클라이언트와 프로세스 간 통신으로 연결합니다.transport="http"는 원격 서버로 배포해 여러 클라이언트가 네트워크로 접근할 수 있게 합니다. 프로토타입은 stdio, 프로덕션은 HTTP를 권장합니다.
실전 적용
예시 1: 레거시 REST API를 OpenAPI로 즉시 변환
사내 시스템에 Swagger 문서가 있다면 코드 한 줄로 MCP 서버를 자동 생성할 수 있습니다. 수동으로 Tool을 정의할 필요 없이 모든 API 엔드포인트가 자동으로 Tool로 매핑됩니다.
import asyncio
import httpx
from fastmcp import FastMCP
async def main():
# AsyncClient를 async with로 관리해 이벤트 루프 종료 시 안전하게 정리
async with httpx.AsyncClient(base_url="http://legacy-hr-system") as client:
# OpenAPI 스펙을 가져와 MCP 서버 자동 생성
response = await client.get("/openapi.json")
openapi_spec = response.json()
mcp = FastMCP.from_openapi(
openapi_spec=openapi_spec,
client=client,
name="HR-MCP"
)
# run_async()로 동일한 이벤트 루프 안에서 서버 실행
await mcp.run_async(transport="http")
if __name__ == "__main__":
asyncio.run(main())| 코드 요소 | 역할 |
|---|---|
async with httpx.AsyncClient |
이벤트 루프가 닫힐 때 클라이언트를 안전하게 정리 |
FastMCP.from_openapi() |
OpenAPI 스펙을 Tool로 자동 변환 |
mcp.run_async() |
동일한 이벤트 루프 안에서 서버 실행 (루프 충돌 방지) |
이제 Claude에서 "김철수 팀장의 올해 휴가 잔여일 알려줘"라고 입력하면 MCP 서버가 레거시 HR API를 자동으로 호출해 결과를 돌려줍니다.
REST API 변환이 됐다면, API 없이 DB에 직접 붙어야 하는 경우는 어떻게 처리할까요?
예시 2: 레거시 Oracle DB 직접 조회 Tool
레거시 시스템 중 가장 흔히 마주치는 형태가 Oracle DB 직접 연결입니다. 클라우드 네이티브 환경에서는 PostgreSQL이나 MySQL로 동일한 패턴을 적용할 수 있으며, Oracle을 예시로 선택한 이유는 금융·제조업 레거시 환경에서 가장 보편적이기 때문입니다.
@mcp.tool 데코레이터로 함수를 선언하면 되며, cx_Oracle 같은 기존 연결 코드를 그대로 재사용할 수 있습니다.
import os
import cx_Oracle
from fastmcp import FastMCP
mcp = FastMCP("Oracle DB MCP 서버")
# DB 연결 풀 초기화 (기존 코드)
# 자격증명은 환경 변수로 관리하고 코드에 직접 쓰지 않습니다
pool = cx_Oracle.SessionPool(
user="readonly_user",
password=os.getenv("DB_PASSWORD"), # 환경 변수에서 읽기
dsn="legacy-oracle:1521/PROD"
)
@mcp.tool
def get_inventory(product_code: str, warehouse_id: int) -> dict:
"""
창고별 재고 현황을 조회합니다.
Args:
product_code: 제품 코드 (예: 'PROD-001')
warehouse_id: 창고 ID
"""
with pool.acquire() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT stock_qty, last_updated FROM inventory "
"WHERE product_code = :1 AND warehouse_id = :2",
[product_code, warehouse_id]
)
row = cursor.fetchone()
if row:
return {"stock_qty": row[0], "last_updated": str(row[1])}
return {"error": "재고 정보를 찾을 수 없습니다."}
@mcp.resource("inventory://schema")
def get_schema() -> str:
"""재고 테이블 스키마 정보를 반환합니다."""
return "inventory(product_code VARCHAR, warehouse_id INT, stock_qty INT, last_updated DATE)"
if __name__ == "__main__":
mcp.run()독스트링이 곧 LLM 안내문 —
@mcp.tool함수의 독스트링은 LLM이 도구를 선택할 때 참고하는 설명입니다. 어떤 경우에 이 도구를 써야 하는지, 인자 형식이 무엇인지 구체적으로 작성할수록 LLM의 도구 선택 정확도가 높아집니다.
SQL 인젝션 방지 —
:1,:2같은 바인드 변수를 사용하면 사용자 입력이 SQL 쿼리에 직접 삽입되지 않습니다. Tool로 받은 외부 입력은 항상 이 방식으로 처리하는 것을 권장합니다.
DB 직접 연결까지 됐다면, Jira·Slack·ERP처럼 여러 시스템을 하나의 엔드포인트로 묶어야 하는 상황도 자주 생깁니다.
예시 3: 멀티 서버 통합 게이트웨이
각 시스템별 MCP 서버가 구성되어 있을 때, mcp.mount()로 단일 게이트웨이에 통합할 수 있습니다. AI 에이전트는 하나의 엔드포인트만 알면 모든 사내 시스템에 접근합니다.
from fastmcp import FastMCP
# 각 시스템별 서버 (별도 파일 또는 모듈로 관리)
from servers.jira_server import jira_mcp
from servers.slack_server import slack_mcp
from servers.erp_server import erp_mcp
# 통합 게이트웨이 서버
gateway = FastMCP("사내 통합 게이트웨이")
# prefix를 지정하면 도구 이름이 충돌하지 않음
# FastMCP 2.x 버전에 따라 mount() 인자 이름이 다를 수 있으므로
# 공식 문서(gofastmcp.com)에서 현재 버전의 API를 확인하는 것을 권장합니다
gateway.mount("/jira", jira_mcp)
gateway.mount("/slack", slack_mcp)
gateway.mount("/erp", erp_mcp)
if __name__ == "__main__":
# 하나의 엔드포인트로 전체 사내 시스템 접근 가능
gateway.run(transport="http", port=8000)| 구성 요소 | 설명 |
|---|---|
gateway.mount("/jira", jira_mcp) |
/jira prefix 하위에 Jira 도구들이 등록됨 |
| 단일 엔드포인트 | http://gateway:8000 하나로 모든 시스템 접근 |
| prefix 분리 | 도구 이름 충돌 방지 및 권한 관리 단위로 활용 |
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 빠른 개발 | 데코레이터 기반 등록으로 개발 시간 최대 5배 단축. 최소 코드로 수 분 내 서버 구축 가능 |
| 기존 코드 재사용 | 복잡한 MCP 스펙 추상화. 기존 Python 함수를 그대로 Tool로 등록 |
| OpenAPI 자동 변환 | Swagger 스펙이 있는 레거시 REST API를 코드 수정 없이 즉시 MCP 서버로 전환 |
| 멀티 서버 조합 | mount()로 여러 MCP 서버를 단일 게이트웨이 앱으로 통합 |
| 트랜스포트 유연성 | stdio(로컬), HTTP(원격), SSE(레거시 호환) 모두 지원 |
| 내장 디버깅 | MCP Inspector(React UI) 연동으로 실시간 테스트 및 로깅 |
| 프로덕션 기능 내장 | OAuth 2.1 인증, 세션 관리 기본 제공 |
단점 및 주의사항
가장 주의해야 할 위험은 굵게 표시했습니다.
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| ⚠️ 기본 보안 취약 | 기본 HTTP 배포는 인증·암호화 없음. 내부 네트워크라도 무방비 상태 | OAuth 2.1 또는 API Gateway(Kong, Nginx)로 인증 레이어 추가. 상세 설정은 다음 글에서 다룹니다 |
| ⚠️ RCE 취약점 위험 | 파일 접근·쉘 명령을 입력 검증 없이 Tool로 노출 시 심각한 보안 문제 | Tool 내 외부 입력은 반드시 화이트리스트 검증 후 사용 |
| Tool 과다 노출 | Tool이 많을수록 LLM이 잘못된 Tool을 선택할 확률 증가 | OpenAPI 자동 변환 후 불필요한 엔드포인트는 수동으로 제거 |
| 자동 변환 품질 한계 | OpenAPI 자동 생성 서버는 수동 설계 서버 대비 LLM 성능 저하 가능 | 프로토타입은 자동 변환, 프로덕션은 핵심 Tool 수동 최적화 |
| 성능 병목 | 트래픽이 높은 환경에서 응답 지연 발생 가능 | 결과 캐싱 추가, 수평 확장(컨테이너 복제) 전략 마련 |
| 고급 기능 학습 곡선 | 프록시·필터링·미들웨어 등 고급 기능 문서 부족 | 공식 GitHub 이슈와 Discord 커뮤니티 적극 활용 |
RCE(Remote Code Execution) — 원격 코드 실행 취약점. 공격자가 서버에서 임의의 명령을 실행할 수 있게 되는 심각한 보안 문제입니다. Tool에서 사용자 입력을
subprocess나eval에 그대로 전달하면 이 취약점이 발생합니다.
실무에서 가장 흔한 실수
단점 표의 항목과 중복되지 않도록, 여기서는 "왜 이 실수가 반복되는가"에 집중합니다.
- 인증 없이 HTTP 모드로 배포하기 —
mcp.run(transport="http")는 빠르게 결과가 나오다 보니 그 상태 그대로 내부망에 올리는 경우가 많습니다. "어차피 사내 네트워크"라는 생각이 방심을 만듭니다. 사내 누구든 접근할 수 있는 상태이므로, 개발 환경이라도 API Key 미들웨어를 함께 설정하는 습관을 들이면 좋습니다. - 독스트링을 생략하거나 대충 작성하기 — 코드 작성에 집중하다 보면 독스트링은 나중에 추가할 것처럼 느껴집니다. 그러나 LLM은 독스트링을 기반으로 어떤 Tool을 언제 쓸지 결정합니다. 독스트링이 부실하면 LLM이 엉뚱한 Tool을 호출하거나 아예 사용하지 않는 상황이 발생합니다.
- OpenAPI 자동 변환 결과를 검토 없이 프로덕션에 올리기 —
from_openapi()가 빠르게 동작하다 보니 검증 단계를 건너뛰고 싶어집니다. 자동 생성된 서버에는 불필요한 엔드포인트가 포함되어 있고, 그만큼 LLM의 도구 선택 정확도가 낮아집니다. MCP Inspector로 변환 결과를 먼저 검토하고, 핵심 Tool만 남기는 과정을 거치는 것을 권장합니다.
마치며
FastMCP는 레거시 시스템을 교체하지 않고도 AI가 이해하는 인터페이스를 덧씌울 수 있는 가장 빠른 경로입니다. @mcp.tool 데코레이터와 OpenAPI 자동 변환을 조합하면, 10분 안에 레거시 시스템과 Claude Desktop이 자연어로 대화하는 첫 동작을 확인할 수 있습니다.
지금 바로 시작할 수 있는 3단계:
- FastMCP를 설치하고 첫 Tool을 만들어볼 수 있습니다.
pip install fastmcp또는uv add fastmcp로 설치한 뒤, 사내 DB 조회 함수 하나에@mcp.tool을 붙이고mcp.run()으로 실행할 수 있습니다.npx @modelcontextprotocol/inspector로 MCP Inspector를 열면 도구가 제대로 노출되는지 실시간으로 확인할 수 있습니다. - 사내 시스템에 Swagger 문서가 있다면
FastMCP.from_openapi()를 시도해볼 수 있습니다. OpenAPI JSON URL 하나만 있으면 모든 엔드포인트가 자동으로 Tool로 변환됩니다. 변환 결과를 MCP Inspector에서 검토하고 불필요한 엔드포인트를 정리하는 과정도 함께 진행해 보시면 좋습니다. - Claude Desktop과 연결해 자연어로 사내 시스템을 조작해볼 수 있습니다. Claude Desktop의
claude_desktop_config.json에 MCP 서버 경로를 등록하면, 채팅창에서 바로 "지난달 재고 현황 보여줘" 같은 질문으로 레거시 시스템과 대화하는 경험을 확인할 수 있습니다.
다음 글: FastMCP 서버에 OAuth 2.1 인증과 OpenTelemetry 관측성을 적용해 프로덕션 수준으로 강화하는 방법을 다룰 예정입니다.
참고 자료
처음 시작한다면 이 세 곳부터 확인하세요:
- FastMCP 공식 퀵스타트 | gofastmcp.com
- FastMCP GitHub | jlowin/fastmcp
- MCP Inspector GitHub | modelcontextprotocol/inspector
심화 학습용:
- FastMCP OpenAPI 통합 공식 문서 | gofastmcp.com
- FastMCP 서버 운영 공식 문서 | gofastmcp.com
- Anthropic MCP 발표 블로그 | anthropic.com
- MCP 공식 스펙 문서 | modelcontextprotocol.io
- FastMCP 2.0으로 MCP 서버·클라이언트 구축 튜토리얼 | DataCamp
- OpenAPI 스펙으로 LLM 도구 자동 생성 | DEV.to
- 첫 MCP 서버 구축 가이드 | freeCodeCamp
- FastMCP PyPI 페이지 | pypi.org
- 원격 MCP 배포 보안 주의사항 | CardinalOps