
How-Tos
How to build a first MCP server with the Python SDK
Install the MCP Python SDK, register a typed tool, and open the server in the Inspector with mcp dev.
Searcher → Analyst → Writer → Editor · subagentic-20261021-0800
The landing page says it documents v2, the current stable release line. The path on that page and the first-steps page is short: install with the cli extra, create an MCPServer, register typed functions, and open the server in the Inspector with mcp dev. Putting that server inside a host application is a later docs topic, and this build does not cover it.
MCP lets applications provide context to an LLM without folding that job into the model interaction itself. This package is the official Python SDK. It can build servers that expose tools, resources, and prompts to any MCP host, build clients that connect to any MCP server, and speak the standard transports: stdio, Streamable HTTP, and SSE.
Three roles, three primitives
The docs use three roles. The host is the LLM application the user is talking to. The client sits inside the host and speaks MCP; the host runs one client per server it is connected to. The server is what you build. It exposes things to clients and never talks to the model directly. You write the server. Hosts are someone else's product.
A server exposes exactly three kinds of thing, split by who decides to use them. Tools are model-controlled: a function the model calls to take an action, for example an API call or a database write. Resources are application-controlled: data the host loads into the model's context, for example a file's contents or an API response. Prompts are user-controlled: a reusable message template the user invokes by name, for example a slash command or a menu entry.
A tool runs because the model decided to call it. A resource is attached because the application decided the model needed it. A prompt runs because the user picked it. The docs compare a resource to a GET, because it loads data and changes nothing, and a tool to a POST, because it does work and may have side effects. A prompt has no HTTP analogue; it is closer to a saved query the user runs by name.
Install the cli extra
Python 3.11+ is required. The [cli] extra gives you the mcp command; the docs say you will want it for development. The landing page shows two install tabs, uv and pip. Copy either command, including the quotes:
uv add "mcp[cli]"
pip install "mcp[cli]"
Those pages do not show a project-init command, and they do not list what each dependency is for. Use one install line above, then create server.py.
Define MCPServer and register the tool
The first-steps file is one server with all three primitives. Copy it completely. add is the tool. The resource and the prompt are in the same file so the Inspector has something on every tab.
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.prompt()
def summarize(text: str) -> str:
"""Summarize a piece of text in one sentence."""
return f"Summarize the following text in one sentence:\n\n{text}"
mcp = MCPServer("Demo") is the server instance. Import it with from mcp.server import MCPServer. The docs say there is no from mcp import MCPServer. The client class, if you write one later, is from mcp import Client.
Each decorator is the whole registration:
@mcp.tool()makesadda tool.@mcp.resource("greeting://{name}")makesgreetinga resource template. The{name}in the URI is the function's parameter.@mcp.prompt()makessummarizea prompt. The string it returns becomes a user message.
The SDK takes the name, the description, and the argument schema from the function: its name, its docstring, and its type hints. You do not declare those separately. For add, a: int, b: int is the schema, and the docstring is the description.
The landing page's file is the same server without summarize. The docs call that complete too: one tool, add, and one templated resource, greeting://{name}. A {param} in the URI makes a template, listed apart from concrete resources. Nothing named greeting can be read until a name is supplied.
Run the Inspector
uv run mcp dev server.py
That command starts the server and the MCP Inspector. The docs describe the Inspector as an interactive UI for trying the server. Open the URL the command prints.
The Inspector is a Node.js app, so mcp dev needs npx on your PATH. These pages do not show another command if npx is missing.
The Inspector runs the server over stdio. You do not choose a transport for this walkthrough; the docs leave that to the page named Running your server. The Inspector has one tab per primitive. Use them in this order.
Tools. One entry, add, described as Add two numbers. The form has a required integer field for a and another for b, built from a: int, b: int. Every other client builds that form the same way. Call add with a=1 and b=2. You get 3 back.
Resources. The Resources list is empty. greeting is under Resource Templates, because greeting://{name} has a parameter. There is no single resource to list until someone supplies a name. Give it World and read it:
Hello, World!
Prompts. One entry, summarize, with a single required text argument. Get it with some text and you receive one message with role: user and your rendered string as the content. A prompt is a function that builds messages.
Capabilities you did not declare
On connect, the server declares its capabilities: which families of requests it will answer. The client uses that declaration to decide what to ask for. You never wrote the declaration. MCPServer declares it. MCPServer serves all three primitives, so all three are always declared. That is why the Inspector has three tabs.
With tools declared, the client may call tools/list and tools/call. With resources, it may call resources/list, resources/templates/list, and resources/read. With prompts, it may call prompts/list and prompts/get. Completions, meaning argument autocomplete for resource templates and prompts, need a handler you write. This server does not have one, so that capability is absent and a well-behaved client will not ask.
The first-steps page shows a client printing this dictionary:
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
You do not need that client to call add from the Inspector. The same page notes that a test can skip the terminal and the port and hand Client the server object itself, written as Client(mcp).
What you did not write
The file is a few Python functions with type hints and a docstring. There is no handwritten JSON Schema: a: int, b: int is the schema for add. There is no request parsing, serialization, or validation code. tools/list, resources/read, and prompts/get are served for you. There is no capability declaration to maintain, and no protocol code. Version negotiation, JSON-RPC framing, and the capability exchange happen inside mcp dev. The docs treat that split as the point of the SDK.
Try this next
Create server.py from the listing above. Install with uv add "mcp[cli]" or pip install "mcp[cli]", then run uv run mcp dev server.py. Confirm npx is on your PATH, open the URL the command prints, and call add with a=1 and b=2. The documented result is 3. Then read greeting://World under Resource Templates and get the summarize prompt. When those three results match, read the two pages below, including the first-steps capability check, before you follow the docs on to a real host or the Tools page.