← Back to tutorials

Building an MCP Server from Scratch 2026: Create Custom AI Tools with Python

A complete MCP server development tutorial that lets Claude and Cursor call your custom functions

Why Build Your Own MCP Server

While existing MCP server libraries are already rich, there are still many scenarios that require customization:

  • Connecting to internal company APIs (external tools can't access)
  • Handling specific business logic
  • Integrating with private databases
  • Wrapping complex workflows into simple tools
  • Development Environment Setup

    bash
    

    Install the MCP Python SDK

    pip install mcp

    Or use uv

    uv add mcp

    The Simplest MCP Server

    python
    

    simple_server.py

    from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import mcp.types as types

    Create a server instance

    app = Server("my-tools")

    Register the list of tools

    @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="get_weather", description="Get weather information for a specified city", inputSchema={ "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"] } ), Tool( name="calculate_discount", description="Calculate the price after discount", inputSchema={ "type": "object", "properties": { "original_price": {"type": "number"}, "discount_percent": {"type": "number"} }, "required": ["original_price", "discount_percent"] } ) ]

    Implement tool logic

    @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_weather": city = arguments["city"] # In a real project, call the actual weather API weather_data = {"city": city, "temp": "25°C", "condition": "Sunny"} return [TextContent(type="text", text=str(weather_data))] elif name == "calculate_discount": original = arguments["original_price"] discount = arguments["discount_percent"] final_price = original * (1 - discount / 100) return [TextContent( type="text", text=f"Original price {original} yuan, after {100-discount}% off: {final_price:.2f} yuan" )] raise ValueError(f"Unknown tool: {name}")

    Start the server (stdio mode)

    if __name__ == "__main__": import asyncio asyncio.run(stdio_server(app))

    Register with Claude Desktop

    json
    {
      "mcpServers": {
        "my-tools": {
          "command": "python",
          "args": ["/path/to/simple_server.py"]
        }
      }
    }
    

    Hands-on: Building a Database Query Tool

    python
    import sqlite3
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
    from mcp.types import Tool, TextContent

    app = Server("db-tools") DB_PATH = "./mydata.db"

    @app.list_tools() async def list_tools(): return [ Tool( name="query_database", description="Execute a SQL query (read-only) and return results", inputSchema={ "type": "object", "properties": { "sql": {"type": "string", "description": "SELECT SQL statement"} }, "required": ["sql"] } ) ]

    @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "query_database": sql = arguments["sql"] # Security check: only allow SELECT if not sql.strip().upper().startswith('SELECT'): return [TextContent(type="text", text="Error: Only SELECT queries are allowed")] conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() cursor.execute(sql) columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() conn.close() result = f"Columns: {columns}\nResults ({len(rows)} rows):\n" for row in rows[:20]: # Return at most 20 rows result += str(row) + "\n" return [TextContent(type="text", text=result)]

    if __name__ == "__main__": import asyncio asyncio.run(stdio_server(app))

    Debugging Tips

    bash
    

    Test if the server starts correctly

    echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python simple_server.py

    Use MCP Inspector for visual testing

    npx @modelcontextprotocol/inspector python simple_server.py

    Publishing to the MCP Community

  • Package as a Python package (pyproject.toml)
  • Publish to PyPI: pip publish
  • Submit your MCP server on mcp.so or GitHub
  • Users can then install directly with uvx your-package-name
  • Also available in 中文.