FastMCP turns a plain Python function into an agent-callable tool with one decorator. No SDK boilerplate, no schema written by hand. You drop six lines in a file, run it, and you have a real MCP server speaking JSON-RPC over stdio. This is the minimum legal server, and it is the spine of everything that follows tonight.
One install. The package is fastmcp (v3.x). It pulls the MCP wire protocol and a CLI with it.
pip install fastmcp
Save this as server.py. Every line is load-bearing. There is no seventh line.
from fastmcp import FastMCP
mcp = FastMCP("hello-toolsmith")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two integers and return the sum."""
return a + b
if __name__ == "__main__":
mcp.run()
FastMCP is the server. It owns the registry of tools, resources, and prompts, and it knows how to speak the wire protocol.a: int, b: int). The docstring becomes the tool description, which is the agent's primary signal for when to call it.Run the file. It will sit silently waiting for JSON-RPC on stdin. Silence is correct: a stdio server has no banner, it is a pipe.
python server.py
Press Ctrl+C to stop it. Now let's actually talk to it.
You do not need a fancy client to prove the server works. Pipe three JSON-RPC messages into it and read the reply. This is exactly what an agent sends: initialize, then notifications/initialized, then tools/call.
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":3}}}' \
| python server.py
@(
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
'{"jsonrpc":"2.0","method":"notifications/initialized"}'
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":3}}}'
) | python server.py
import json, subprocess, sys
msgs = [
{"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "ping", "version": "0"}}},
{"jsonrpc": "2.0", "method": "notifications/initialized"},
{"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": "add", "arguments": {"a": 2, "b": 3}}},
]
payload = "".join(json.dumps(m) + "\n" for m in msgs)
p = subprocess.run([sys.executable, "server.py"],
input=payload, capture_output=True, text=True)
print(p.stdout)
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"5"}],
"structuredContent":{"result":5},"isError":false}}
The initialize line also returns the server's capabilities and your tool list. The "text":"5" in the id-2 reply is your function's return value, marshalled back across the wire. The server ran your Python. That is an MCP call.
You spoke the MCP handshake by hand. A real client always sends initialize first to negotiate protocol version and capabilities, then the notifications/initialized acknowledgement, and only then is it allowed to call tools. The decorator turned add into a tool whose schema (two integer args, returns an integer) was inferred from your type hints. No schema file. No SDK class. Six lines.
If you prefer a UI over raw JSON, FastMCP ships an inspector. It opens a local web view where you can see the tool list and fire add with a form.
fastmcp dev server.py
You can also drop this server into Claude Desktop or Cline by pointing their MCP config at python /full/path/server.py. Same server, real agent client.
cp1252 on Windows. The instant your print() or docstring contains a fancy character (a curly quote, an arrow glyph, an em dash), a Windows stdout crashes with UnicodeEncodeError and the server dies on connect. Keep tool code ASCII: use -> not the arrow glyph, straight quotes, PASS / FAIL not check marks. If you genuinely need Unicode IO, add import sys; sys.stdout.reconfigure(encoding="utf-8") at the top. This single rule has killed live workshops.