← Back to Articles
PythonTutorialSDK

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.

By Web MCP GuideFebruary 8, 20265 min read


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:

  • Rich AI ecosystem: Easy integration with NumPy, Pandas, scikit-learn, and more

  • Rapid prototyping: Quick iteration on ideas

  • Async support: Native asyncio for efficient I/O handling

  • Wide adoption: Large community and extensive libraries
  • 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 asyncio

    Create 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 asyncpg

    class 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 os

    API_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!