MCPServer: your first tool
An MCP server is a Python file. Put a decorator on a function and the SDK builds the name, description and schema that lesson 1 wrote by hand.
from mcp.server import MCPServer
mcp = MCPServer("Shop support")
ORDERS = {
"A17": {"item": "blue mug", "status": "shipped", "total": 12.50},
"B42": {"item": "desk lamp", "status": "processing", "total": 48.00},
}
@mcp.tool()
def lookup_order(order_id: str) -> str:
"""Look up an order by its id and say where it is."""
order = ORDERS[order_id]
return f"Order {order_id}: {order['item']}, {order['status']}."MCPServer("Shop support") creates the server and names it. @mcp.tool() registers lookup_order as a tool, a function a model can call. The order data is a dictionary for now.
The SDK reads three things from the function: the name from the function name, the description from the docstring, and the arguments from the type hints.
What a client is told
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
for tool in tools.tools:
print(tool.name)
print(tool.description)
print(tool.input_schema)
asyncio.run(main())Ignore the client code for a moment; lesson 3 explains it. Look at what it printed: the same name, description and schema you wrote by hand in lesson 1, now built from order_id: str and the docstring. Change the function and the schema changes with it.
The title keys come from Pydantic, which builds the schema. The parts that matter to a model are properties, the types and required.
Two import paths
The SDK has two halves. The server is from mcp.server import MCPServer; the client is from mcp import Client. There is no from mcp import MCPServer.
- Give
lookup_ordera second parameter,include_total: bool = False, and list the tools again. What changed inrequired? - Delete the docstring and look at
tool.description. - Add a second tool,
count_orders() -> int, and list the tools.
You understood something today that you didn't yesterday.