How to build an MCP server in Python
The Model Context Protocol (MCP) lets an AI client — Claude Desktop, an IDE, your own agent — call tools, read resources and use prompts that you expose from a server. In Python you get a working server in about fifteen lines. Getting one you can actually run in front of users takes more. This guide does both: the fast path first, then the gaps.
1. Set up the project
Use a real environment manager. The official SDK ships as mcp:
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]"
If you're on Poetry, poetry add "mcp[cli]" is equivalent. Pin your Python to
3.11+ — the async story is much nicer.
2. Define a server and a tool
The high-level FastMCP API is the quickest way in. A tool is just a typed
function with a docstring — the SDK turns the signature into a JSON schema the client sees.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
def get_forecast(city: str, days: int = 1) -> str:
"""Return a short forecast for a city."""
# call your real API here
return f"{city}: clear, {days}d ahead"
if __name__ == "__main__":
mcp.run() # stdio transport by default
That's a valid MCP server. mcp.run() speaks JSON-RPC over stdio —
stdin and stdout — which is how desktop clients launch and talk to local servers.
3. Run it and connect a client
For a fast feedback loop, the SDK ships an inspector:
mcp dev server.py
To use it from a desktop client, register the command (python server.py) in the
client's MCP config. The client spawns your process and pipes JSON-RPC through stdio.
stdout —
a stray print(), a library banner — gets injected into the JSON-RPC stream and
the client disconnects. All logging must go to stderr.
4. Add tools, resources and prompts
Beyond tools, MCP has resources (readable data the model can pull in, like
files or rows) and prompts (reusable templated messages). The decorators
mirror tools: @mcp.resource("scheme://{id}") and @mcp.prompt().
Keep tool functions small and validate their inputs — the model will send you
arguments you didn't expect.
5. Where the quickstart ends and production begins
The code above runs. It is not ready for anyone but you. Before it touches real traffic you need:
- stderr-only logging with structured JSON, so stdout stays pristine.
- An auth gate — any client that can launch the process can call every tool. Gate privileged tools behind a token.
- Rate limiting — an agent in a loop will call your tool hundreds of times a minute and melt the API behind it.
- Retries with backoff on every outbound call, or one transient 503 fails the whole tool invocation.
- Input validation at the boundary, not deep in the handler.
- Graceful shutdown so SIGTERM during a deploy doesn't truncate an in-flight response.
- Health checks and a hardened container once you move off your laptop.
None of this is hard individually. Together it's the week of unglamorous work that stands between "it runs on my machine" and "it runs for users." Write a test that drives the server over the real protocol so you know your changes hold.
Or start from a base that already has all of it
The Plinth MCP Server Starter Kit is a typed, tested Python template (plus a matching TypeScript one) with auth gating, rate limiting, backoff, structured stderr logging, graceful shutdown, input validation, health checks and a hardened Dockerfile already wired up — and 23 Python tests driving it over the real MCP protocol.
Get the kit — $39