How to Build MCP Servers with Python: Best Practices
The Model Context Protocol (MCP) gives AI agents a standard way to connect to external data and tools. This guide walks through building MCP servers with the Python SDK, covering async patterns, error handling, and production setup.
Why Build MCP Servers in Python?
The Python SDK for MCP lets you wrap existing Python libraries and scripts into tools that Claude and other agents can call directly. Python already dominates AI and data science, so your agents get native access to libraries like Pandas, Boto3, and everything else on PyPI. Building an MCP server turns standalone scripts into "skills" an LLM can invoke on the fly. Instead of hard-coding logic, you expose capabilities. Python works well as glue here because you can plug into legacy systems, custom APIs, and vector databases without fighting language-specific bindings. Python is also the native language of the modern AI stack. LangChain, LlamaIndex, and Semantic Kernel all prioritize Python support. Writing your MCP server in Python means less context switching and no serialization overhead between your agent logic and backend tools. This pays off most when your tools run data processing pipelines or interact with local ML models through PyTorch or TensorFlow.
Setting Up Your Development Environment
You'll need Python 3.10 or higher. Use uv or poetry for dependency management so builds stay reproducible, especially in production. Standard pip works for quick experiments, but anything you plan to deploy needs strict environment isolation. uv is worth a look for its speed in containerized workflows. Also, drop a .python-version file in your repo so every developer and CI pipeline uses the same runtime. Small version differences cause subtle bugs that are annoying to track down. Start by installing the official MCP SDK:
pip install mcp
This guide uses asyncio throughout. MCP is async-first by design, which matters when your server handles concurrent requests from multiple agents or long-running tasks like file processing and web scraping.
Creating Your First Python MCP Server
A basic MCP server defines a set of tools that an AI can call. Here's a minimal example using the FastMCP helper, a high-level wrapper good for quick starts. For larger projects, the raw SDK gives you more control.
Step 1: Initialize the Server
Create a file named server.py:
from mcp.server.fastmcp import FastMCP
### Initialize the server
mcp = FastMCP("My Weather Agent")
@mcp.tool()
def get_weather(location: str) -> str:
"""Get the weather for a specific location."""
### In a real app, call an external API here
return f"The weather in {location} is sunny and 72°F."
if __name__ == "__main__":
mcp.run()
This code creates an MCP server instance, registers a function as a tool with a decorator, and starts the server loop. mcp.run() handles the transport layer for you. By default it uses stdio, which works well for local integration with desktop clients like Cursor or the Claude Desktop app. The SDK also supports custom transports if you need something different.
Step 2: Configuring the Client
Once your server code is ready, you need to tell your MCP client where to find it. If you are using the Claude Desktop app, you will edit your claude_desktop_config.json file. ```json
{
"mcpServers": {
"weather-agent": {
"command": "uv",
"args": ["run", "server.py"]
}
}
}
This configuration tells Claude to launch your Python script as a subprocess. The `uv run` command ensures it runs within the correct virtual environment with all dependencies available. After saving this file and restarting Claude, your new 'Get Weather' tool will be available in the chat interface.
Best Practices for Async Implementation
Async handling trips up a lot of MCP server builders. If your tool does any I/O (API calls, database queries, reading large files), you need to use async def. A synchronous function that blocks the main thread will freeze your entire MCP server. The agent times out, and you lose the conversation context.
Bad (Blocking):
import requests
@mcp.tool()
def fetch_data(url: str):
### This blocks the server! return requests.get(url).text
Good (Async):
import httpx
@mcp.tool()
async def fetch_data(url: str):
async with httpx.AsyncClient() as client:
resp = await client.get(url)
return resp.text
Using async keeps your server responsive so it can handle multiple tool calls at once. With async def, Python's event loop manages concurrency for you. This matters because an agent might trigger several resource reads while a tool is still processing.
Managing Resources and State
Beyond tools, MCP lets you expose "Resources," which are data the agent can read directly, like files or database rows. Stateful agents need persistent storage. Writing to local disk works until you're in a container and everything disappears on restart. Resources give the agent context it needs to make decisions. Expose local logs, database schemas, or documentation as Resources, and the AI can explore that information on its own.
Exposing Data with Resources While tools represent actions, resources represent data. You can think of resources as file-like objects that the model can read. Here is how you define a resource in Python:
@mcp.resource("config://app-settings")
def get_config() -> str:
"""Read the current application configuration."""
import json
return json.dumps({"debug": True, "version": "1.0.0"})
Resources let the model browse information without executing code. You could expose a database schema, a log file, or a list of API endpoints as a resource. The model reads that context before deciding which tool to call, so it makes better decisions on its own.
Fast.io Advantage: Fast.io works well as a backend for MCP servers. You can use the API to read and write state that persists across server restarts. A dedicated storage layer for agent context cuts retrieval latency compared to standard object storage, so conversations don't stall waiting on file reads.
Error Handling and Debugging
AI agents aren't human users. They need structured error messages to recover from failures. When your Python code raises an exception, the MCP server should catch it and return a useful error to the model instead of crashing.
Structured Error Pattern:
@mcp.tool()
async def calculate_risk(score: int) -> str:
try:
if score < 0:
raise ValueError("Score cannot be negative")
### ... calculation logic ... return "Risk calculated."
except ValueError as e:
### The agent sees this message and can correct its input
return f"Error: {str(e)}. Please provide a positive score."
Expect the LLM to send invalid arguments, and validate inputs with Pydantic models or type hints. Debugging MCP servers is tricky because they often run as hidden processes. Use Python's logging module and redirect logs to a file or remote service, not standard output (that's reserved for protocol communication). Without logging, you'll spend a lot of time wondering why a tool call failed silently.
Using the MCP Inspector
Debugging stdio-based servers can be frustrating since you can't see the input and output streams directly. The mcp-inspector tool is a web-based debugger that sits between your client and server. It shows the JSON-RPC messages flowing back and forth, so you can spot malformed payloads or unexpected errors quickly. Running your server through the inspector during development beats staring at log files.
Frequently Asked Questions
Is there an official MCP SDK for Python?
Yes, Anthropic provides an official Python SDK (`pip install mcp`) that simplifies server creation, protocol negotiation, and transport management.
Can I run an MCP server on Fast.io?
Fast.io specializes in storage and tool hosting for agents. You can use Fast.io as the persistent storage layer for your Python MCP servers, or use the built-in Fast.io MCP server to give your agents instant access to file management tools without writing code.
What is the difference between stdio and SSE transports?
Stdio transport runs the server as a subprocess, communicating via standard input/output, which is simpler for local desktop apps (like Cursor or Claude Desktop). SSE (Server-Sent Events) runs over HTTP, making it suitable for remote servers and distributed agent systems.
Give Your Agents Persistent Memory
Stop building stateless bots. Use Fast.io's free tier to give your Python MCP agents 50GB of persistent storage, built-in RAG, and file management tools.