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.
Before you start
Section titled “Before you start”- Python 3.10 or later. We tested on 3.13.
- uv to manage the project and its virtual environment. Plain
pipandvenvwork too; the commands below use uv. - Node.js 22.19 or later, only for the MCP Inspector used in testing.
Build it
Section titled “Build it”uv init --bare hello-server && cd hello-serveruv add mcp==2.3.0The 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.
"""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 cancompare SDKs with the same calls."""
import sysfrom 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. Setversionyourself: 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(hereSum), a Pydantic model or a dataclass gave the tool an output schema and astructuredContentresult in our tests. With a plain-> dictwe got only a text result. mcp.run()uses stdio by default. Anything you print for people must go tosys.stderr; stdout carries the protocol.
Test it
Section titled “Test it”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 jsonExpectedstructuredContent 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.
Connect it to a client
Section titled “Connect it to a client”A client needs a command that works from any directory. uv’s --directory option runs the project in place:
uv --directory /absolute/path/to/hello-server run server.pyIn 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.