Claude Academy
Sign in

Building an MCP Server (Python)

The "Introduction to MCP" course uses the Python SDK throughout. Memorize the basic shape.

Install

uv add "mcp[cli]"
# or
pip install mcp

Minimal stdio server

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

app = Server("hello-mcp")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="echo",
            description="Echo a string back. Use to verify the server is reachable.",
            inputSchema={
                "type": "object",
                "properties": {"text": {"type": "string"}},
                "required": ["text"],
            },
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "echo":
        return [TextContent(type="text", text=arguments["text"])]
    raise ValueError(f"Unknown tool: {name}")

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write, app.create_initialization_options())

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

Wiring to Claude Desktop

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

{
  "mcpServers": {
    "hello": {
      "command": "python",
      "args": ["/path/to/server.py"]
    }
  }
}

Restart Claude Desktop. The tool appears in the tools panel.

FastMCP shorthand

The high-level FastMCP API hides the boilerplate:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("hello-mcp")

@mcp.tool()
def echo(text: str) -> str:
    """Echo a string back. Use to verify the server is reachable."""
    return text

@mcp.resource("file://greeting")
def greeting() -> str:
    """A static greeting."""
    return "Hello, world!"

@mcp.prompt()
def summarize(text: str) -> str:
    """Produce a summary prompt."""
    return f"Summarize:\n\n{text}"

if __name__ == "__main__":
    mcp.run()

The decorated function name + docstring become the tool name + description. Type hints become the JSON schema.

Logging & debugging

  • Use mcp inspector (run via npx @modelcontextprotocol/inspector) to manually call tools, list resources, inspect responses.
  • Log to stderr (not stdout) when using stdio transport — stdout is the wire.
  • Set MCP_LOG_LEVEL=debug in the environment for verbose logs.

See also