Lesson 10 / 31
Your First Server With the Python SDK
Turn ordinary functions into tools, resources and prompts.
Decorators do the protocol work
Official SDKs (Python, TypeScript, Java, C#, Kotlin and others) hide the JSON-RPC details. In the Python SDK you create a server object and register functions with decorators: @tool() turns a function into a tool, taking the name from the function, the description from the docstring and the input schema from the type hints; @resource("uri") exposes data; @prompt() exposes a template. run("stdio") serves it over standard input and output. The server you saw earlier is about 20 lines. Note that the SDK is under active development: version 1.x called the class FastMCP, while 2.x renamed it MCPServer and changed other APIs, so pin the SDK version and follow the migration guide when upgrading.
A small, honest interface
Good servers expose few well-described tools, validate input and return helpful errors.
What the SDK generated from the type hints, run
I ran this against the real server shown earlier, using the official mcp Python SDK 2.2.0 in a virtual environment; the client side is raw JSON-RPC over stdio with no SDK. Save the server as server.py in the same folder. SDK names and defaults change between versions, so check the current documentation. The tools/list result includes an input JSON Schema built from order_id: str, marking it required. The same session is shown in full in the lifecycle topic; here the focus is the generated schema line.
import json, subprocess, sys
proc = subprocess.Popen([sys.executable, "server.py"], stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
def send(msg):
proc.stdin.write(json.dumps(msg) + "\n"); proc.stdin.flush()
def call(id_, method, params=None):
send({"jsonrpc": "2.0", "id": id_, "method": method, **({"params": params} if params is not None else {})})
return json.loads(proc.stdout.readline())
init = call(1, "initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "demo-client", "version": "0.1"}})
print("server:", init["result"]["serverInfo"]["name"], "| protocol:", init["result"]["protocolVersion"])
print("capabilities:", sorted(init["result"]["capabilities"]))
send({"jsonrpc": "2.0", "method": "notifications/initialized"}) # a notification: no id, no reply
tools = call(2, "tools/list")["result"]["tools"]
print("tools:", [t["name"] for t in tools])
print("input schema:", json.dumps(tools[0]["inputSchema"]))
ok = call(3, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "481516"}})
print("call ok :", ok["result"]["content"][0]["text"], "| isError:", ok["result"].get("isError"))
bad = call(4, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "12"}})
print("call bad id :", bad["result"]["content"][0]["text"])
missing = call(5, "tools/call", {"name": "get_order_status", "arguments": {}})
print("missing arg :", "isError" , missing["result"].get("isError"), "|", missing["result"]["content"][0]["text"][:60].replace(chr(10), " "))
unknown = call(6, "no/such/method")
print("unknown :", unknown["error"]["code"], unknown["error"]["message"])
res = call(7, "resources/list")["result"]["resources"]
print("resources:", [r["uri"] for r in res])
print("read:", call(8, "resources/read", {"uri": "policy://refunds"})["result"]["contents"][0]["text"])
pr = call(9, "prompts/get", {"name": "triage", "arguments": {"ticket": "I was charged twice"}})
print("prompt:", pr["result"]["messages"][0]["content"]["text"])
proc.stdin.close(); proc.wait()
Output:
server: demo-orders | protocol: 2025-06-18
capabilities: ['experimental', 'prompts', 'resources', 'tools']
tools: ['get_order_status']
input schema: {"properties": {"order_id": {"title": "Order Id", "type": "string"}}, "required": ["order_id"], "type": "object", "title": "get_order_statusArguments"}
call ok : {
"order_id": "481516",
"status": "shipped"
} | isError: False
call bad id : {
"error": "order_id must be exactly 6 digits"
}
missing arg : isError True | Error executing tool get_order_status: 1 validation error fo
unknown : -32601 Method not found
resources: ['policy://refunds']
read: Refunds above 5000 rupees need approval.
prompt: Classify this ticket as billing, technical or other: I was charged twiceQuick check: Where does the SDK get a tool's description?
- From the function's docstring
- From the file size
- From the GPU
- It cannot
Answer
From the function's docstring — Docstrings, names and type hints become the tool definition the model reads.