How to Set Up a Python MCP Server for In-House Legacy Systems with FastMCP in 10 Minutes
Subtitle: LLM Internal System Integration | Practical Guide to Model Context Protocol Implementation
You do not need to replace decades-old in-house systems. With FastMCP, you can create an environment in just 10 minutes where LLMs like Claude or GPT-4 can converse directly with in-house systems using natural language by overlaying AI interfaces onto existing legacy code. You can implement scenarios much faster than expected, such as an AI automatically querying the HR system to answer the question, "Tell me Team Leader Kim Cheol-su how many days of annual leave he has left this year."
By following this guide, you can attach the @mcp.tool decorator to your existing in-house system to build an MCP server and complete a demo querying a legacy database in natural language from Claude Desktop within 10 minutes. The explanation covers everything from basic tool declarations to automatic OpenAPI conversion, multi-server integration, and security considerations, all at a level ready for immediate practical application.
If you already have REST API or database connection code, you can reuse it as is. The only new thing to create is a few lines of decorators.
Target Audience for this Article: Python backend developers. Uses Python concepts such as async/await, decorators, and session pools. If you are new to Python, it is recommended that you check the "What is a Python Decorator?" callout below first.
Key Concepts
Problems Solved by MCP: N×M Integration Hell
To connect AI models with internal systems, it was previously necessary to create a custom plugin for each system. If there are three AI models and five systems to connect, up to 15 connectors are required. MCP solves this "N×M integration problem."
MCP (Model Context Protocol) — An open standard released by Anthropic in November 2024. It is a protocol that enables LLMs to communicate with external tools, data sources, and systems in a standardized manner. Once you create an MCP server, it can be used by all AI clients that support MCP, such as Claude, GPT-4, and Azure AI Agent.
As of 2025, both OpenAI and Microsoft Azure have officially adopted MCP, and the number of publicly available MCP servers has exceeded 5,800. Monthly SDK downloads have surpassed 97 million, establishing it as the de facto industry standard for AI-system integration.
Three Components of FastMCP
FastMCP is a high-level framework that enables you to build MCP servers in a Pythonic way. FastMCP 1.0 was integrated into the official MCP Python SDK, and the currently independently managed 2.x version is downloaded more than 1 million times a day.
The MCP server consists of three core components.
| Component | Decorator | Role |
|---|---|---|
| Tools | @mcp.tool |
Functions called by the LLM — DB queries, API calls, calculations, etc. |
| Resources | @mcp.resource |
Read-only data exposure — files, settings, schemas, etc. |
| Prompts | @mcp.prompt |
Reusable prompt templates for complex tasks |
This article focuses on @mcp.tool, which is the most widely used in practice. @mcp.resource is introduced in Example 2 for schema exposure purposes, and @mcp.prompt is used for automating repetitive workflows but is excluded from the scope of this article.
Why 10 Minutes Is Possible
There are two key points.
- Decorator-based Tool Registration — Simply appending
@mcp.toolto an existing function makes it a tool recognized by LLM. No separate schema definition or JSON serialization code is required. - Automatic OpenAPI Spec Conversion — Use
FastMCP.from_openapi()to convert legacy REST APIs with Swagger/OpenAPI specifications into an MCP server in a single line.
# 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"What is a Python Decorator? — An expression starting with @ above a function, like @mcp.tool, is a decorator. It is Python syntax that wraps a function to grant additional behavior; in this case, it means "register this function as an MCP Tool." It is conceptually equivalent to Java's @RestController and Spring's @GetMapping.
stdio vs HTTP Transport — stdio connects with local clients like Claude Desktop via inter-process communication. transport="http" deploys to a remote server, allowing multiple clients to access it over the network. We recommend stdio for prototypes and HTTP for production.
Practical Application
Example 1: Immediately converting a legacy REST API to an OpenAPI
If you have Swagger documentation in your internal system, you can automatically generate an MCP server with a single line of code. All API endpoints are automatically mapped to the Tool without the need to define the Tool manually.
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())| Code Element | Role |
|---|---|
async with httpx.AsyncClient |
Safely clean up clients when the event loop closes |
FastMCP.from_openapi() |
Automatically convert OpenAPI specifications to a tool |
mcp.run_async() |
Run server within the same event loop (prevent loop crashes) |
Now, if you enter "Tell me Team Leader Kim Cheol-su's remaining vacation days for this year" in Claude, the MCP server automatically calls the legacy HR API and returns the result.
If the REST API conversion has been completed, how should we handle cases where we need to connect directly to the DB without an API?
Example 2: Legacy Oracle DB Direct Query Tool
The most common form encountered in legacy systems is a direct connection to an Oracle DB. In cloud-native environments, the same pattern can be applied to PostgreSQL or MySQL; Oracle was chosen as an example because it is the most prevalent in legacy environments within the finance and manufacturing sectors.
You can declare the function with the @mcp.tool decorator, and reuse existing connection code such as 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()Dogstrings Are LLM Guides — @mcp.tool A function's docstring is a description that the LLM refers to when selecting a tool. The more specifically you write about when to use the tool and what the argument types are, the higher the accuracy of the LLM's tool selection.
SQL Injection Prevention — Using bind variables such as :1 and :2 prevents user input from being directly inserted into SQL queries. It is recommended to always process external input received from tools in this way.
If you have established a direct DB connection, situations frequently arise where you need to link multiple systems, such as Jira, Slack, and ERP, into a single endpoint.
Example 3: Multi-Server Integration Gateway
When an MCP server is configured for each system, it can be integrated into a single gateway using mcp.mount(). The AI agent accesses all on-premises systems by knowing only one endpoint.
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)| Components | Description |
|---|---|
Jira tools are registered under the gateway.mount("/jira", jira_mcp) |
/jira prefix |
| Single Endpoint | http://gateway:8000 Access All Systems as One |
| Prefix Separation | Used to prevent tool name conflicts and as a unit for permission management |
Pros and Cons Analysis
Advantages
| Item | Content |
|---|---|
| Rapid Development | Reduce development time by up to 5x with decorator-based registration. Build a server within minutes with minimal code. |
| Reuse Existing Code | Abstraction of complex MCP specifications. Register existing Python functions as tools as is |
| Automatic OpenAPI Conversion | Instantly migrate legacy REST APIs with Swagger specifications to an MCP server without code modification |
| Multi-Server Combination | Integrate multiple MCP servers into a single gateway app with mount() |
| Transport Flexibility | Supports stdio (local), HTTP (remote), and SSE (legacy compatible) |
| Built-in Debugging | Real-time testing and logging via MCP Inspector (React UI) integration |
| Built-in Production Features | OAuth 2.1 Authentication, Session Management Built-in |
Disadvantages and Precautions
The risks to watch out for are highlighted in bold.
| Item | Content | Response Plan |
|---|---|---|
| ⚠️ Basic Security Vulnerability | Basic HTTP deployments lack authentication and encryption. Even on internal networks, it is vulnerable. | Add an authentication layer using OAuth 2.1 or API Gateway (Kong, Nginx). Detailed configuration will be covered in the next post. |
| ⚠️ RCE Vulnerability Risk | Serious security issues if file access and shell commands are exposed via tools without input validation | Internal and external inputs within the tool must be used only after whitelist validation |
| Overexposure to Tools | The more tools available, the higher the probability that the LLM will select the wrong tool | Manually remove unnecessary endpoints after automatic OpenAPI conversion |
| Limitations of Automated Conversion Quality | OpenAPI automated generation servers may experience LLM performance degradation compared to manual design servers | Automated conversion for prototypes, manual optimization of core tools for production |
| Performance Bottleneck | Response delays may occur in high-traffic environments | Add result caching, establish horizontal scaling (container replication) strategies |
| Learning Curve for Advanced Features | Lack of Documentation for Advanced Features such as Proxy, Filtering, and Middleware | Actively Utilize Official GitHub Issues and the Discord Community |
RCE (Remote Code Execution) — Remote code execution vulnerability. A serious security issue that allows an attacker to execute arbitrary commands on a server. This vulnerability occurs if the tool passes user input directly to subprocess or eval.
The Most Common Mistakes in Practice
To avoid duplication with the items in the disadvantages table, we will focus here on "why this mistake is repeated."
- Deploying in HTTP Mode Without Authentication — Since
mcp.run(transport="http")produces results quickly, it is often deployed to the internal network in its current state. The mindset that "it is just the internal network anyway" leads to complacency. Since anyone within the company can access it, it is a good habit to configure API Key middleware, even in a development environment. - Omitting or Writing Poorly Written Docstrings — When focusing on writing code, it feels like you will add docstrings later. However, the LLM determines which tool to use and when based on the docstring. If the docstring is inadequate, the LLM may call the wrong tool or not use it at all.
- Deploying OpenAPI Automatic Conversion Results to Production Without Review — Because
from_openapi()operates quickly, you may be tempted to skip the verification step. Automatically generated servers include unnecessary endpoints, which lowers the accuracy of LLM tool selection. We recommend reviewing the conversion results first with MCP Inspector and going through the process of keeping only the essential tools.
In Conclusion
FastMCP is the fastest way to overlay AI-understood interfaces without replacing legacy systems. By combining the @mcp.tool decorator with OpenAPI auto-conversion, you can see the first interaction between your legacy system and Claude Desktop converse in natural language within 10 minutes.
3 Steps to Start Right Now:
- You can install FastMCP and create your first tool. After installing it with
pip install fastmcporuv add fastmcp, you can append@mcp.toolto an internal DB query function and run it withmcp.run(). If you open the MCP Inspector withnpx @modelcontextprotocol/inspector, you can check in real-time whether the tool is being displayed correctly. - If you have Swagger documentation in your internal system, you can try
FastMCP.from_openapi(). All endpoints are automatically converted into the tool with just a single OpenAPI JSON URL. It is recommended that you also review the conversion results in the MCP Inspector and clean up unnecessary endpoints. - You can connect with Claude Desktop to operate your internal systems using natural language. If you register the MCP server path in
claude_desktop_config.jsonin Claude Desktop, you can experience conversing with legacy systems directly in the chat window with questions such as "Show me last month's inventory status."
Next Post: We will cover how to enhance FastMCP servers to a production level by applying OAuth 2.1 authentication and OpenTelemetry observability.
Reference Materials
If you are just starting out, check these three places first:
- FastMCP Official Quickstart | gofastmcp.com
- FastMCP GitHub | jlowin/fastmcp
- MCP Inspector GitHub | modelcontextprotocol/inspector
For Advanced Study:
- FastMCP OpenAPI Integration Official Documentation | gofastmcp.com
- FastMCP Server Operation Official Documentation | gofastmcp.com
- Anthropic MCP Announcement Blog | anthropic.com
- MCP Official Spec Document | modelcontextprotocol.io
- Tutorial on Building an MCP Server & Client with FastMCP 2.0 | DataCamp
- Automatically generate LLM tools using OpenAPI specifications | DEV.to
- First MCP Server Setup Guide | freeCodeCamp
- FastMCP PyPI Page | pypi.org
- Remote MCP Deployment Security Precautions | CardinalOps