Skip to main content
Technology & EngineeringMcp Server390 lines

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.

Quick Summary31 lines
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 lines
Paste into your CLAUDE.md or agent config

MCP 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() 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.

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 uv for 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

Get CLI access →

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 Server327L

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 Server353L

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 Server226L

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 Server431L

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 Server287L

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.

Mcp Server238L