GenAI Systems Lab Open interactive version →
AI Engineering 12 min read

Build Your First MCP Server in 30 Minutes

Step-by-step walkthrough of building a working MCP server using the Python SDK. Define tools and resources, expose them over stdio transport, register with Claude Desktop, and call them from Claude. Every line of code explained.

The fastest way to understand MCP is to build a server. This walkthrough takes you from zero to a working MCP server registered with Claude Desktop — with every line of code explained.

Step 1: Install the Python SDK

pip install mcp

Step 2: Define the server and expose a tool

from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp import types
import mcp.server.stdio

app = Server("my-tools")

@app.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_weather",
            description="Get current weather for a city",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"}
                },
                "required": ["city"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "get_weather":
        city = arguments["city"]
        # Your real API call here
        return [types.TextContent(type="text", text=f"Weather in {city}: 22°C, partly cloudy")]
    raise ValueError(f"Unknown tool: {name}")

Step 3: Add a resource

@app.list_resources()
async def list_resources() -> list[types.Resource]:
    return [
        types.Resource(
            uri="weather://forecast/today",
            name="Today's Forecast",
            description="Current day weather summary",
            mimeType="text/plain"
        )
    ]

@app.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "weather://forecast/today":
        return "Mostly sunny, high 24°C, low 15°C"
    raise ValueError(f"Unknown resource: {uri}")

Step 4: Run the server

async def main():
    async with mcp.server.stdio.stdio_server() as (read, write):
        await app.run(read, write, InitializationOptions(
            server_name="my-tools",
            server_version="0.1.0",
            capabilities=app.get_capabilities()
        ))

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

Step 5: Register with Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["/absolute/path/to/your/server.py"]
    }
  }
}

Restart Claude Desktop. Claude now has access to your tool and can call it mid-conversation.

The full server is ~60 lines of Python. MCP is intentionally simple — the protocol complexity lives in the SDK, not your code.

Try it interactively

GenAI Systems Lab is a free platform for AI engineers — configure real failure modes, break things, and build the judgment that gets you hired.

Open GenAI Systems Lab →