AI Agents & Skills

Build an MCP server in Python in 30 lines

TL;DR

With the Python SDK, an MCP server is type hints, docstrings and mcp.run(). The most common bug is printing to stdout, which breaks the stdio channel; test with MCP Inspector first, use absolute paths and start with read-only tools.

You have an internal API (look up orders, check stock, read reports) and want Claude to call it from Claude Desktop or Claude Code. The standard way today is to wrap that API as an MCP server. With the official Python SDK, a minimal server is about 30 lines. This post goes from zero to Claude calling your tool, plus the mistakes newcomers make most.

Prerequisites

  • Python 3.10 or later and uv (package/project manager).
  • The mcp[cli] package from the official SDK.
uv init order-server
cd order-server
uv venv && source .venv/bin/activate
uv add "mcp[cli]"

Writing the server

The SDK generates tool definitions from type hints and docstrings, so write them carefully: they are the "documentation" the model reads to decide which tool to call and with what arguments.

import logging
from mcp.server import MCPServer

logging.basicConfig(level=logging.INFO)   # writes to stderr
log = logging.getLogger("orders")
mcp = MCPServer("orders")

ORDERS = {"A100": "Shipping", "A101": "Cancelled"}

@mcp.tool()
async def get_order_status(order_id: str) -> str:
    """Look up an order's status by ID.

    Args:
        order_id: Order ID, e.g. A100
    """
    log.info("lookup %s", order_id)
    return ORDERS.get(order_id.upper(), "Order not found.")

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

In older SDK versions and tutorials this class is called FastMCP; the current quickstart uses MCPServer. Besides @mcp.tool(), a server can also expose resources (file-like data to read) and prompts (ready-made prompt templates).

Mistake #1: printing to stdout

With the stdio transport, stdout is the JSON-RPC message channel. A single print() is enough to corrupt communication and make the server "die" for no apparent reason. The docs are emphatic: never write anything to stdout; use the logging module (which writes to stderr by default).

Testing with MCP Inspector

Before connecting Claude, try it with Inspector (needs Node 22.19+):

# Web UI
npx @modelcontextprotocol/inspector uv run server.py

# CLI, good for CI
npx @modelcontextprotocol/inspector --cli uv run server.py --method tools/list

Inspector lets you see the tool list, call tools with any arguments and read every JSON-RPC message. To be sure the server works with both older clients and new-spec clients, run the CLI with --protocol-era legacy and --protocol-era modern.

Connecting to Claude

Claude Desktop: edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS), then restart the app:

{
  "mcpServers": {
    "orders": {
      "command": "uv",
      "args": ["--directory", "/ABSOLUTE/PATH/TO/order-server", "run", "server.py"]
    }
  }
}

Claude Code: claude mcp add orders -- uv --directory /PATH/TO/order-server run server.py, then check with /mcp.

When it doesn't work

SymptomWhat to do
Server doesn't show upUse absolute paths; you may need the full path to uv (which uv)
Connects then drops immediatelyLook for print() or libraries writing to stdout
Need to see logsClaude Desktop on macOS: tail -f ~/Library/Logs/Claude/mcp*.log
Claude doesn't call the toolRewrite the docstring more clearly: what the tool does, when to use it, example arguments

Risks and caveats

  • Tools are a doorway into your systems: tools that write or delete data need permission checks and confirmation; start with read-only tools.
  • Tool results are untrusted data: content returned from external sources can contain prompt injection.
  • Secrets: pass API keys via environment variables (env in the config), never hard-coded.
  • Keep results short: long results eat context; filter and format before returning.

How to apply it

  • Dev: Wrap one read-only internal API as 2–3 tools, test with Inspector, then use it in Claude Code every day.
  • Tech Lead: Add the Inspector CLI tools/list command to CI to catch broken servers before release.
  • PM: List the 5 most frequently asked internal questions; each is a candidate for an MCP tool.

Sources & references

  1. Build an MCP server — Model Context Protocol
  2. MCP Python SDK — Model Context Protocol (GitHub)
  3. MCP Inspector — Model Context Protocol