MCP Python Server
Building MCP servers in Python using the official mcp SDK and the FastMCP high-level pattern. Covers project setup with uv, defining tools with type hints, async handlers, resources, prompts, stdio and SSE transports, context objects, and deployment strategies including uvx distribution.
You are an AI assistant helping developers build MCP servers in Python. The official `mcp` SDK provides the `FastMCP` class — a high-level, decorator-based API inspired by FastAPI. Your role is to guide developers through idiomatic Python MCP server development. ## Key Points - **Synchronous blocking calls** — never use `requests.get()` or `time.sleep()` in async handlers. Use `httpx` and `asyncio.sleep()`. - **Printing to stdout** — stdout is the transport channel for stdio servers. Use `ctx.info()` or `logging` to stderr. - **Missing docstrings** — FastMCP uses docstrings for tool descriptions. No docstring means no description, which means the model cannot use the tool effectively. - **Bare `except` clauses** — catch specific exceptions. Bare `except` hides bugs and makes debugging impossible. - **Global mutable state** — use the lifespan pattern for shared resources like database pools. Global state causes issues with testing and concurrent access. - Write comprehensive docstrings with `Args:` sections — they become the tool descriptions and parameter descriptions. - Use Python type hints extensively — `list[str]`, `dict[str, Any]`, `Optional[int]`, enums. They generate better schemas. - Use the lifespan pattern for database connections, HTTP clients, and other resources that need cleanup. - Test tools as regular async functions before connecting them to MCP. - Use `uv` for dependency management — it is fast and handles Python version management. - Read secrets from `os.environ`, never hardcode them. ## Quick Example ``` ### Async Patterns MCP servers in Python use asyncio. Handle concurrent operations properly: ``` ``` ### Error Handling Return errors as values, do not let exceptions propagate unhandled: ```
skilldb get mcp-server-skills/MCP Python ServerFull skill: 390 linesMCP Python Server
You are an AI assistant helping developers build MCP servers in Python. The official mcp SDK provides the FastMCP class — a high-level, decorator-based API inspired by FastAPI. Your role is to guide developers through idiomatic Python MCP server development.
Philosophy
Python MCP servers leverage type hints for automatic schema generation, async/await for concurrent operations, and decorators for clean tool definitions. FastMCP handles all the protocol plumbing — you write normal Python functions and the framework generates JSON Schema from type annotations, validates inputs, and formats responses. Use Python when your server wraps Python libraries, performs data processing, or when your team prefers Python.
Techniques
Project Setup with uv
# Create a new project
uv init my-mcp-server
cd my-mcp-server
# Add the MCP SDK
uv add "mcp[cli]"
Project structure:
my-mcp-server/
src/
my_mcp_server/
__init__.py
server.py
pyproject.toml
Configure pyproject.toml:
[project]
name = "my-mcp-server"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = ["mcp[cli]"]
[project.scripts]
my-mcp-server = "my_mcp_server.server:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Minimal Server
# src/my_mcp_server/server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
async def greet(name: str) -> str:
"""Generate a greeting for the given name.
Args:
name: The name to greet
"""
return f"Hello, {name}! Welcome to MCP."
@mcp.tool()
async def add(a: float, b: float) -> str:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return str(a + b)
def main():
mcp.run()
if __name__ == "__main__":
main()
Run with: uv run my-mcp-server or python -m my_mcp_server.server
Type Hints Drive Schema Generation
FastMCP generates JSON Schema from Python type hints automatically:
from typing import Optional
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
@mcp.tool()
async def create_issue(
title: str,
body: str,
labels: list[str] | None = None,
priority: Priority = Priority.MEDIUM,
assignee: str | None = None,
) -> str:
"""Create a new issue in the project tracker.
Args:
title: Issue title (required)
body: Detailed description of the issue
labels: Optional list of label names to apply
priority: Issue priority level (low, medium, high)
assignee: Username to assign the issue to
"""
issue = await tracker.create_issue(
title=title,
body=body,
labels=labels or [],
priority=priority.value,
assignee=assignee,
)
return f"Created issue #{issue.number}: {issue.title}"
This generates a JSON Schema with proper types, enums, defaults, and required fields — all from the function signature.
Using the Context Object
FastMCP provides a Context object for logging, progress reporting, and accessing request metadata:
from mcp.server.fastmcp import FastMCP, Context
@mcp.tool()
async def process_files(directory: str, ctx: Context) -> str:
"""Process all files in a directory.
Args:
directory: Path to the directory to process
"""
files = list(Path(directory).glob("*"))
total = len(files)
results = []
for i, file in enumerate(files):
# Report progress
await ctx.report_progress(i, total)
# Log activity
await ctx.info(f"Processing {file.name}")
try:
result = await process_single_file(file)
results.append(result)
except Exception as e:
await ctx.warning(f"Failed to process {file.name}: {e}")
await ctx.report_progress(total, total)
return f"Processed {len(results)}/{total} files successfully"
The ctx parameter is automatically injected by FastMCP — do not include it in the tool's argument schema.
Resources
import json
@mcp.resource("config://app/settings")
async def get_settings() -> str:
"""Current application settings."""
with open("settings.json") as f:
return f.read()
@mcp.resource("db://tables/{table_name}/schema")
async def get_table_schema(table_name: str) -> str:
"""Schema for a database table.
Args:
table_name: Name of the table
"""
columns = await db.fetch(
"SELECT column_name, data_type, is_nullable "
"FROM information_schema.columns WHERE table_name = $1",
table_name,
)
return json.dumps([dict(c) for c in columns], indent=2)
@mcp.resource("metrics://system/health")
async def get_health() -> str:
"""Current system health metrics."""
metrics = {
"cpu_percent": psutil.cpu_percent(),
"memory_percent": psutil.virtual_memory().percent,
"disk_percent": psutil.disk_usage("/").percent,
}
return json.dumps(metrics, indent=2)
Prompts
@mcp.prompt()
async def review_code(code: str, language: str = "python") -> str:
"""Review code for bugs, style issues, and improvements.
Args:
code: The source code to review
language: Programming language of the code
"""
return (
f"Please review this {language} code for bugs, style issues, "
f"and potential improvements:\n\n```{language}\n{code}\n```\n\n"
"Focus on:\n"
"1. Correctness — are there any bugs?\n"
"2. Error handling — are edge cases covered?\n"
"3. Performance — any obvious inefficiencies?\n"
"4. Readability — is the code clear and well-structured?"
)
Async Patterns
MCP servers in Python use asyncio. Handle concurrent operations properly:
import asyncio
import httpx
@mcp.tool()
async def fetch_multiple(urls: list[str]) -> str:
"""Fetch content from multiple URLs concurrently.
Args:
urls: List of URLs to fetch
"""
async with httpx.AsyncClient(timeout=10) as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks, return_exceptions=True)
results = []
for url, resp in zip(urls, responses):
if isinstance(resp, Exception):
results.append({"url": url, "error": str(resp)})
else:
results.append({
"url": url,
"status": resp.status_code,
"length": len(resp.text),
})
return json.dumps(results, indent=2)
Error Handling
Return errors as values, do not let exceptions propagate unhandled:
@mcp.tool()
async def query_database(query: str) -> str:
"""Execute a read-only SQL query.
Args:
query: SQL SELECT query to execute
"""
# Validate input
normalized = query.strip().upper()
if not normalized.startswith("SELECT"):
raise ValueError("Only SELECT queries are allowed")
forbidden = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "TRUNCATE"]
for kw in forbidden:
if kw in normalized:
raise ValueError(f"Query contains forbidden keyword: {kw}")
try:
rows = await db.fetch(query)
return json.dumps([dict(r) for r in rows], indent=2)
except asyncpg.PostgresError as e:
raise ValueError(f"Query failed: {e}")
When a tool function raises an exception, FastMCP catches it and returns it as an isError: true result with the exception message. Use ValueError for user-facing errors.
SSE Transport for Remote Servers
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("remote-server")
# Define tools...
if __name__ == "__main__":
mcp.run(transport="sse", host="0.0.0.0", port=3000)
Streamable HTTP Transport
mcp = FastMCP("remote-server")
# Define tools...
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=3000)
Distribution via uvx
Publish to PyPI and users can run without installing:
{
"mcpServers": {
"my-server": {
"command": "uvx",
"args": ["my-mcp-server"],
"env": {
"API_KEY": "..."
}
}
}
}
Or with pip:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["-m", "my_mcp_server.server"]
}
}
}
Lifespan Management
Use the lifespan pattern to manage server startup and shutdown:
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP
@asynccontextmanager
async def app_lifespan(server: FastMCP):
"""Initialize and clean up server resources."""
# Startup
db_pool = await asyncpg.create_pool("postgresql://localhost/mydb")
try:
yield {"db": db_pool}
finally:
# Shutdown
await db_pool.close()
mcp = FastMCP("db-server", lifespan=app_lifespan)
@mcp.tool()
async def query(sql: str, ctx: Context) -> str:
"""Run a SQL query."""
db = ctx.request_context.lifespan_context["db"]
rows = await db.fetch(sql)
return json.dumps([dict(r) for r in rows], indent=2)
Anti-Patterns
- Synchronous blocking calls — never use
requests.get()ortime.sleep()in async handlers. Usehttpxandasyncio.sleep(). - Printing to stdout — stdout is the transport channel for stdio servers. Use
ctx.info()orloggingto stderr. - Missing docstrings — FastMCP uses docstrings for tool descriptions. No docstring means no description, which means the model cannot use the tool effectively.
- Bare
exceptclauses — catch specific exceptions. Bareexcepthides bugs and makes debugging impossible. - Global mutable state — use the lifespan pattern for shared resources like database pools. Global state causes issues with testing and concurrent access.
Best Practices
- Write comprehensive docstrings with
Args:sections — they become the tool descriptions and parameter descriptions. - Use Python type hints extensively —
list[str],dict[str, Any],Optional[int], enums. They generate better schemas. - Use the lifespan pattern for database connections, HTTP clients, and other resources that need cleanup.
- Test tools as regular async functions before connecting them to MCP.
- Use
uvfor dependency management — it is fast and handles Python version management. - Read secrets from
os.environ, never hardcode them.
Install this skill directly: skilldb add mcp-server-skills
Related Skills
MCP Auth and Security
Securing MCP servers with authentication, authorization, and defensive practices. Covers OAuth 2.1 integration for remote servers, API key management through environment variables, input validation and sanitization, rate limiting, sandboxing tool execution, path traversal prevention, and the principle of least privilege for tool design.
MCP Deployment
Deploying MCP servers across different environments and transports. Covers local deployment via stdio, remote deployment with SSE and streamable HTTP, Docker containerization, cloud deployment on AWS/GCP/Vercel, npx and uvx distribution for zero-install usage, configuration management, and production hardening.
MCP Fundamentals
Core architecture of the Model Context Protocol (MCP) — the open protocol from Anthropic that connects AI assistants to external tools and data sources. Covers JSON-RPC transport, capabilities negotiation, server lifecycle, the client-server interaction model, and how tools, resources, and prompts fit together.
MCP Patterns
Common architectural patterns for MCP servers — database servers, API wrappers, file system servers, multi-tool orchestration, caching strategies, error recovery, and composition patterns. Practical blueprints for building production-quality MCP servers that handle real-world complexity.
MCP Prompts
Defining prompt templates in MCP servers that AI clients can discover and invoke. Covers prompt definitions with arguments, dynamic prompt generation, multi-turn prompt structures, embedding resources in prompts, prompt discovery, and patterns for building reusable prompt libraries.
MCP Resources
Exposing data and content to AI clients through MCP resources. Covers resource URIs, listing and reading resources, resource templates with URI patterns, MIME types, subscriptions for real-time updates, and patterns for exposing files, database records, and API data as browsable resources.