Getting Started with MCP in Python: A Complete Guide
Learn how to build MCP servers using Python. This guide covers installation, basic concepts, and practical examples using the Python SDK.
Python is one of the most popular languages for AI development, making it a natural choice for building MCP servers. If you haven't already, read our introduction to MCP to understand the protocol basics. Let's explore how to get started with the Python MCP SDK.
Why Python for MCP?
Python offers several advantages for MCP development:
Installation
Install the MCP Python SDK:
pip install mcp
For development, also install the inspector:
pip install mcp[dev]
Your First Python MCP Server
Create server.py:
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import asyncioCreate server instance
server = Server("my-python-server")Define a simple tool
@server.tool("greet")
async def greet(name: str) -> list[TextContent]:
"""Greet someone by name."""
return [TextContent(
type="text",
text=f"Hello, {name}! Welcome to MCP with Python."
)]Define a calculation tool
@server.tool("calculate")
async def calculate(
operation: str,
a: float,
b: float
) -> list[TextContent]:
"""Perform basic math operations.
Args:
operation: One of 'add', 'subtract', 'multiply', 'divide'
a: First number
b: Second number
"""
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else float('nan'),
}
if operation not in operations:
return [TextContent(type="text", text=f"Unknown operation: {operation}")]
result = operationsoperation
return [TextContent(type="text", text=f"{a} {operation} {b} = {result}")]Run the server
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)if __name__ == "__main__":
asyncio.run(main())
Understanding the Code
Server Creation:
server = Server("my-python-server")
Creates a new server with a name for identification.
Tool Decorator:
@server.tool("greet")
async def greet(name: str) -> list[TextContent]:
The
@server.tool decorator registers a function as an MCP tool. Type hints are automatically converted to the input schema.Async Functions:
All tool handlers are async, allowing efficient handling of I/O operations.
Adding Resources
Resources provide read-only data to AI applications:
from mcp.types import Resource, TextResourceContents@server.resource("config://app")
async def get_app_config() -> TextResourceContents:
"""Application configuration."""
return TextResourceContents(
uri="config://app",
mimeType="application/json",
text='{"version": "1.0.0", "env": "development"}'
)
Dynamic resources with parameters
@server.resource("file://{path}")
async def read_file(path: str) -> TextResourceContents:
"""Read a file from the filesystem."""
with open(path, 'r') as f:
content = f.read()
return TextResourceContents(
uri=f"file://{path}",
mimeType="text/plain",
text=content
)
Adding Prompts
Prompts provide reusable interaction templates:
from mcp.types import Prompt, PromptMessage, PromptArgument@server.prompt("code-review")
async def code_review(language: str, focus: str = "") -> list[PromptMessage]:
"""Template for code review requests.
Args:
language: Programming language of the code
focus: Optional areas to focus on
"""
focus_text = f"\nFocus particularly on: {focus}" if focus else ""
return [PromptMessage(
role="user",
content=TextContent(
type="text",
text=f"""Please review the following {language} code.{focus_text}
Look for:
Potential bugs
Performance issues
Security vulnerabilities
Code style improvements Provide specific, actionable feedback."""
)
)]
Working with External APIs
Here's a practical example that fetches data from an API:
import httpx@server.tool("get_weather")
async def get_weather(city: str) -> list[TextContent]:
"""Get current weather for a city.
Args:
city: City name to get weather for
"""
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"https://wttr.in/{city}",
params={"format": "j1"},
timeout=10.0
)
response.raise_for_status()
data = response.json()
current = data["current_condition"][0]
temp_c = current["temp_C"]
desc = current["weatherDesc"][0]["value"]
return [TextContent(
type="text",
text=f"Weather in {city}: {temp_c}°C, {desc}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"Error fetching weather: {str(e)}"
)]
Database Integration
Connect to databases for powerful data tools:
import asyncpgclass DatabaseServer:
def __init__(self):
self.server = Server("db-server")
self.pool = None
@self.server.tool("query")
async def query(sql: str) -> list[TextContent]:
"""Execute a read-only SQL query."""
if not sql.strip().upper().startswith("SELECT"):
return [TextContent(type="text", text="Only SELECT queries allowed")]
async with self.pool.acquire() as conn:
rows = await conn.fetch(sql)
return [TextContent(
type="text",
text=str([dict(row) for row in rows])
)]
async def run(self):
self.pool = await asyncpg.create_pool("postgresql://...")
async with stdio_server() as (read, write):
await self.server.run(read, write)
Error Handling
Proper error handling is essential:
from mcp.types import McpError, ErrorCode@server.tool("risky_operation")
async def risky_operation(data: str) -> list[TextContent]:
"""An operation that might fail."""
try:
result = process_data(data)
return [TextContent(type="text", text=result)]
except ValueError as e:
# Return error in content (tool execution error)
return [TextContent(type="text", text=f"Invalid data: {e}")]
except PermissionError:
# Raise MCP error (protocol error)
raise McpError(ErrorCode.INVALID_REQUEST, "Permission denied")
Testing Your Server
Use the MCP Inspector for interactive testing:
mcp dev server.py
Or write unit tests:
import pytest
from mcp.client import Client
from mcp.client.stdio import stdio_client@pytest.mark.asyncio
async def test_greet_tool():
async with stdio_client("python", ["server.py"]) as (read, write):
client = Client("test-client")
await client.connect(read, write)
result = await client.call_tool("greet", {"name": "World"})
assert "Hello, World!" in result.content[0].text
Configuration with Claude Desktop
Add your Python server to Claude Desktop config:
{
"mcpServers": {
"my-python-server": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"PYTHONPATH": "/path/to/project"
}
}
}
}
Best Practices
1. Use async/await: MCP is async-first; embrace it for performance
2. Type everything: Type hints generate accurate schemas
3. Handle errors gracefully: Don't crash, return meaningful errors
4. Document thoroughly: Docstrings become tool descriptions
5. Validate inputs: Don't trust data from the client
6. Log to stderr: Use print(..., file=sys.stderr) for debugging
Common Patterns
Stateful Server:
class StatefulServer:
def __init__(self):
self.server = Server("stateful")
self.cache = {}
@self.server.tool("remember")
async def remember(key: str, value: str):
self.cache[key] = value
return [TextContent(type="text", text=f"Remembered: {key}")]
Environment Configuration:
import osAPI_KEY = os.environ.get("API_KEY")
if not API_KEY:
raise ValueError("API_KEY environment variable required")
Next Steps
Now that you have the basics:
1. Explore the Python SDK source code
2. Build a server for your favorite API
3. Add resources for your project's data
4. Create prompts for your common workflows
5. Share your server with the community
Python's simplicity combined with MCP's power makes for a fantastic development experience. Happy building!