Building Production-Ready MCP Servers in Python: A Complete Guide
The Model Context Protocol (MCP) is rapidly becoming the standard for connecting AI agents to external tools and data sources. If you're building AI applications in 2026, understanding MCP isn't optional — it's essential.
What is MCP?
MCP is an open protocol that standardizes how AI models interact with external tools, databases, and services. Think of it as a universal adapter between your AI agent and the rest of the world.
Why it matters:
- Claude, GPT, Gemini, and other models all support MCP
- Build once, use with any AI provider
- Standardized authentication and error handling
- Growing ecosystem of ready-made MCP servers
Building Your First MCP Server
Here's a minimal but functional MCP server:
`python
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio
server = Server("my-mcp-server")
@server.list_tools()
async def list_tools():
return [
Tool(
name="get_weather",
description="Get current weather for a city",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_weather":
return [TextContent(
type="text",
text=f"Weather in {arguments['city']}: 72F, Sunny"
)]
if name == "main":
asyncio.run(server.run_stdio())
`
Production Features
Error Handling
Never let your MCP server crash:
python
@server.call_tool()
async def call_tool(name: str, arguments: dict):
try:
result = await handle_tool(name, arguments)
return [TextContent(type="text", text=result)]
except Exception as e:
return [TextContent(type="text", text=f"Error: {str(e)}")]
Rate Limiting
Protect your server from abuse:
`python
from collections import defaultdict
import time
class RateLimiter:
def init(self, max_requests=100, window=60):
self.max_requests = max_requests
self.window = window
self.requests = defaultdict(list)
def is_allowed(self, client_id):
now = time.time()
self.requests[client_id] = [
t for t in self.requests[client_id]
if now - t < self.window
]
if len(self.requests[client_id]) >= self.max_requests:
return False
self.requests[client_id].append(now)
return True
`
Real-World Example: Database Query Tool
`python
import sqlite3
from contextlib import contextmanager
@contextmanager
def get_db(db_path):
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.close()
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "query_database":
query = arguments["query"].strip()
# Security: only allow SELECT queries
if not query.upper().startswith("SELECT"):
return [TextContent(
type="text",
text="Error: Only SELECT queries allowed"
)]
with get_db(arguments["database"]) as db:
cursor = db.execute(query)
rows = cursor.fetchall()
return [TextContent(
type="text",
text=str([dict(row) for row in rows])
)]
`
Deployment
Docker
dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ src/
ENV PYTHONPATH=/app/src
CMD ["python", "-m", "my_mcp_server"]
Conclusion
Building production-ready MCP servers in Python is straightforward with the right patterns. Start simple, add features incrementally, and always prioritize error handling and security.
The MCP ecosystem is growing rapidly, and developers who can build and deploy MCP servers will have a significant advantage.
Want to accelerate your MCP development? Check out our AI SaaS Boilerplate — includes MCP server templates and deployment configs ready to go.
Top comments (0)