Skip to content

Your first MCP server in Python

Spec
2026-07-28
Evidence
E2Reproducedarm64
Python SDK
mcp@2.3.0
MCP Inspector
@modelcontextprotocol/inspector@2.12.0
Verified

This guide builds the same one-tool server as the TypeScript guide, in Python: a tool add that returns the sum of two numbers as structured data.

  • Python 3.10 or later. We tested on 3.13.
  • uv to manage the project and its virtual environment. Plain pip and venv work too; the commands below use uv.
  • Node.js 22.19 or later, only for the MCP Inspector used in testing.
TerminalCreate the project and add the SDK
Terminal window
uv init --bare hello-server && cd hello-server
uv add mcp==2.3.0

The package on PyPI is called mcp; this guide uses mcp@2.3.0. Version 2 renamed the main class: tutorials that import FastMCP from mcp.server.fastmcp were written for version 1. See Upgrade from SDK 1.x.

EditorWrite server.py
server.py
"""The smallest useful MCP server: one tool with typed input and structured output.
Every example in examples/mcp/ exposes the same `add` tool so the harness can
compare SDKs with the same calls.
"""
import sys
from typing import TypedDict
from mcp.server import MCPServer
class Sum(TypedDict):
sum: float
mcp = MCPServer("hello-python", version="1.0.0")
@mcp.tool()
def add(a: float, b: float) -> Sum:
"""Add two numbers and return the sum."""
return {"sum": a + b}
if __name__ == "__main__":
# stdout carries JSON-RPC; anything meant for humans goes to stderr.
print("hello-python: ready on stdio", file=sys.stderr)
mcp.run()

What each part does:

  • MCPServer("hello-python", version="1.0.0") creates the server. Set version yourself: without it, the server reports an empty string.
  • @mcp.tool() turns the function into a tool. The name comes from the function, the description from the docstring, and the input schema from the type hints.
  • The return type decides whether there is structured output. A TypedDict (here Sum), a Pydantic model or a dataclass gave the tool an output schema and a structuredContent result in our tests. With a plain -> dict we got only a text result.
  • mcp.run() uses stdio by default. Anything you print for people must go to sys.stderr; stdout carries the protocol.
TerminalCall the tool through the Inspector
Terminal window
npx -y @modelcontextprotocol/inspector@2.12.0 --cli \
uv run server.py --protocol-era auto \
--method tools/call --tool-name add --tool-arg a=2 b=3 --format json

ExpectedstructuredContent is {"sum": 5} and _meta names hello-python 1.0.0.

{"result":{"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"hello-python","version":"1.0.0"}},
"content":[{"type":"text","text":"{\n \"sum\": 5.0\n}"}],"structuredContent":{"sum":5},"isError":false}}

Run the same command with --protocol-era legacy and the _meta field disappears: the Inspector then uses the 2025-11-25 handshake. The server handles both without any extra code. In our tests with the TypeScript client SDK, the default setting negotiated 2025-11-25, and auto or a pin negotiated 2026-07-28.

--method tools/list shows the schemas the SDK generated from your type hints: a and b as numbers, and an output schema titled Sum.

A client needs a command that works from any directory. uv’s --directory option runs the project in place:

Terminal window
uv --directory /absolute/path/to/hello-server run server.py

In a JSON-based client configuration that becomes:

{
"command": "uv",
"args": ["--directory", "/absolute/path/to/hello-server", "run", "server.py"]
}

See Codex for a full client example.

Sources

MCPDev is community documentation. It is not affiliated with the Model Context Protocol project or with the vendors of the clients it covers.