Before you start
This lesson builds the weather server from the official MCP build guide. It is the server most Python tutorials start from, and it has every part a first server needs: a server object, two tools, a docstring that becomes the schema and the stdio transport.
One trap comes before any code. Version 2 of the Python SDK renamed FastMCP to MCPServer, and the old mcp.server.fastmcp import path is gone. A tutorial that still uses it targets the 1.x line. pip install mcp now installs 2.x, and the code below is for 2.x.
- Python 3.11 or newer.
uv, the Python package manager the guide uses. The README says plainpip install "mcp[cli]"works too.- A patched Node.js, but only for the Inspector in the last step: 22.23.4 or later on the 22 line, 24.22.1 or later on the 24 line, or 26.12.1 and up. Lesson 3 covers it.
- An internet connection. The tools call the US National Weather Service API, which covers US locations only.
- Claude Desktop, if you want to talk to the finished server. Any MCP host will do.
First v2 beta
Version 2.0.0b1 arrived with full support for the 2026-07-28 spec.
Source: MCP Python SDK release v2.0.0b1 (read November 5, 2026)
Version 2.0.0
The stable v2 came out the day the spec did, with
MCPServerin place ofFastMCP.Source: MCP Python SDK release v2.0.0 (read November 5, 2026)
Version 2.1.0
The
Clientclass acceptsStdioServerParameters, and prompt messages can carry images and audio.Source: MCP Python SDK release v2.1.0 (read November 5, 2026)
Version 2.2.0
The HTTP client follows redirects only inside the endpoint's origin, and idle Streamable HTTP sessions expire after half an hour. Version 1.30.0 got the same changes.
Source: MCP Python SDK release v2.2.0 (read November 5, 2026)
Version 2.3.0
Mostly fixes, plus three new options.
Source: MCP Python SDK release v2.3.0 (read November 5, 2026)
Build the server in seven steps
The guide gives the code in four pieces: the imports, two helper functions, the two tools and the run call. Pasted in that order they make one file, weather.py.
from typing import Any
import httpx2
from mcp.server import MCPServer
# Initialize MCPServer
mcp = MCPServer("weather")
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
async with httpx2.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
Instructions: {props.get("instruction", "No specific instructions provided")}
"""
@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data:
return "Unable to fetch forecast data for this location."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
forecasts.append(forecast)
return "\n---\n".join(forecasts)
if __name__ == "__main__":
mcp.run(transport="stdio")
- Install uv. On macOS or Linux run
curl -LsSf https://astral.sh/uv/install.sh | sh. On Windows, runpowershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex". Then restart the terminal so theuvcommand is found. - Create the project with
uv init weather, then move into it withcd weather. - Create and activate a virtual environment. Run
uv venv, thensource .venv/bin/activateon macOS and Linux or.venv\Scripts\activateon Windows. - Install the SDK with
uv add "mcp[cli]". For Windows the guide writes it without the quotes:uv add mcp[cli]. - Create
weather.pyand paste in the code above. - Run
uv run weather.py. It prints nothing and waits, because it is listening on stdin for a host. Stop it with Ctrl+C. - Open it in the Inspector with
uv run mcp dev weather.py, and callget_alertswith a two-letter state code such asCA.
What each part of the file does
The decorator @mcp.tool() turns an ordinary function into a tool a model can call. The SDK reads the function itself. Its name becomes the tool's name, its docstring becomes the description the model reads, and its type hints become the input schema. Write the docstring for the model, because the model is the one who reads it.
The README shows the same idea in 15 lines: one add tool and one greeting resource. If the weather server looks long, start there. Most of the extra length is the helper that calls the weather API, and none of it is MCP.
The tools here use async def because they wait on a web request.
| In the file | What it does | What the host sees |
|---|---|---|
mcp = MCPServer("weather") | Creates the server object and names it | A server called weather |
@mcp.tool() on get_alerts | Registers the function as a tool | A tool with one text input, state |
@mcp.tool() on get_forecast | Registers a second tool | A tool with two number inputs, latitude and longitude |
mcp.run(transport="stdio") | Reads requests on stdin and answers on stdout | A local process the host starts and stops |
Why print() breaks a stdio server
Over stdio, standard output carries the protocol itself. Every line the server writes there has to be a JSON-RPC message, so one stray print() corrupts the stream and the host drops the connection. The guide says to keep print() out of a stdio server entirely.
Use the standard logging module, which writes to stderr, with one logger per module. Over HTTP the rule does not apply, because standard output does not touch the HTTP responses.
import logging
logger = logging.getLogger(__name__)
# Bad (stdio)
print("Processing request")
# Good (stdio)
logger.info("Processing request") # writes to stderr
Run it in Claude Desktop
The guide tests the server in Claude for Desktop. Open claude_desktop_config.json, add the server under mcpServers, save the file and restart the app. Below is the guide's entry for macOS and Linux, and its Windows form writes the path with double backslashes, such as C:\ABSOLUTE\PATH\TO\PARENT\FOLDER\weather.
The path to the project folder must be absolute, and you may need the full path to uv in the command field. Run which uv or where uv to print it. Then quit Claude Desktop completely and open it again. Lesson 4 shows this app and two others side by side.
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
What MCP's co-creator and FastMCP's creator say about Python SDK v2
David Soria Parra, a member of technical staff at Anthropic and co-creator of MCP, posted on X on 1 July 2026 that the first v2 betas of the Python and TypeScript SDKs were out. He asked builders to test them and report on ergonomics before the stateless spec landed on 28 July. Most tutorials from before that date target 1.x.
Jeremiah Lowin, founder and CEO of Prefect and creator of FastMCP, posted on X on release day that FastMCP v4 is rebuilt on the v2 Python SDK. That FastMCP is a separate package: uv add fastmcp, then from fastmcp import FastMCP. Its welcome page says it created the high-level API the official SDK took in during 2024. A tutorial that imports fastmcp targets that package, and one that imports mcp.server.fastmcp is SDK 1.x code.
The old import tripped up enough people that version 2.1.1 now points it at the migration guide. The maintainers backported the warning to 2.0.1 a day later, on 26 August, because a lot of people were running into the error. Port a 1.x server with that guide open.
FastMCP v4 is totally rebuilt on top of the v2 Python SDK
Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. X cuts both posts short with Show more. Select one to open the post on X.
Common mistakes
- Importing
FastMCPfrommcp.server.fastmcp. That path is gone in 2.x. Usefrom mcp.server import MCPServer. - Calling
print()in a stdio server. The host sees garbage on the wire and disconnects. Uselogging. - A relative path in the host's config. Hosts start the server from their own working directory, so
weather.pyalone fails. Use the absolute path. - Editing the host's config and not restarting the host. Hosts read their config at launch.
- Asking for a forecast outside the US. The weather API behind the tools covers US locations only.
- Trusting the error message. The helper
make_nws_requestswallows every exception and returnsNone, so every network problem reads as "Unable to fetch alerts". Log the exception inside it while you debug.
Try it yourself
Add the README's resource to weather.py, restart uv run mcp dev weather.py and look for it in the Inspector. Resources are the second thing a server can offer, next to tools.
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
- It appears under resource templates, not under resources, because it takes a parameter.
- Read it with the name World and check that the reply is
Hello, World!. - Break the server on purpose with a
print()and watch what the Inspector shows.
Next: the same server in TypeScript
Lesson 2 builds a server with the official TypeScript SDK, and lesson 3 goes through the Inspector tab by tab. If the protocol itself is new to you, what is MCP explains the architecture.
A server you run locally can do whatever your user account can do. Are MCP servers safe? covers what to check before you run one someone else wrote.
Check yourself
Which import creates the server in the 2.x Python SDK?
Show answer
B.
from mcp.server import MCPServerSDK 2.x renamedFastMCPtoMCPServer, and the oldmcp.server.fastmcppath is gone.Which part of a tool function becomes the description a model reads?
Show answer
A. Its docstring The name becomes the tool's name and the type hints become the input schema, so the docstring is the description and you write it for the model.
Why must a stdio server never call
print()?Show answer
C. Standard output carries the protocol, so a stray line corrupts the stream Over stdio, standard output is the protocol itself, so you log with
logging, which writes to stderr.Every network problem in the weather server reads as "Unable to fetch alerts". Why?
Show answer
D. The helper swallows every exception and returns
Nonemake_nws_requestcatches every exception, so log the exception inside it while you debug.
Frequently asked questions
Will the weather server run on Python 3.10?
Possibly. The build guide and the package's PyPI listing say 3.10 or higher, while the SDK's own docs ask for one version newer. The pages disagree, so use 3.11 for the lesson and treat 3.10 as unconfirmed.
What else changed in the Python SDK between 1.x and 2.x?
The decorators @mcp.tool(), @mcp.resource() and @mcp.prompt() work as before. Transport options such as host and port moved from the constructor to run(), the HTTP client is now httpx2, the WebSocket transport is gone, and a new Client class sits on top of ClientSession. To follow an older tutorial, pin mcp>=1.28,<2.
Do I need uv, or does pip work?
Pip works. The README gives pip install "mcp[cli]" as the alternative to uv add "mcp[cli]". The official guide uses uv because it also runs the file with uv run, and the Claude Desktop config in the guide calls uv too.
What does the cli part of mcp[cli] add?
It adds the mcp command with mcp dev to open a server in the Inspector, mcp run to run it and mcp install to register it with Claude Desktop. The extra installs typer and python-dotenv, and a deployed server may not need it.
How do I run the server over HTTP instead of stdio?
Run uv run mcp run server.py --transport streamable-http. The README then connects a client at http://localhost:8000/mcp. Stdio fits a host that starts the server on your machine, and Streamable HTTP is the transport you deploy.
Does the weather server need an API key?
No key appears in the guide's code. It sends a User-Agent header to the National Weather Service API and nothing else. That API covers US locations only.
Can a tool be a plain function instead of async?
Yes. According to the SDK's v2 notes, a plain def tool, resource or prompt runs on a worker thread. Use async def when the tool waits on the network, as the weather tools do.
Sources
- MCP docs: Build an MCP server https://modelcontextprotocol.io/docs/develop/build-server
- MCP Python SDK: README https://github.com/modelcontextprotocol/python-sdk
- MCP Python SDK docs: Installation https://py.sdk.modelcontextprotocol.io/get-started/installation/
- MCP Python SDK docs: First steps https://py.sdk.modelcontextprotocol.io/get-started/first-steps/
- MCP Python SDK docs: Connect to a real host https://py.sdk.modelcontextprotocol.io/get-started/real-host/
- MCP Python SDK docs: What's new in v2 https://py.sdk.modelcontextprotocol.io/whats-new/
- MCP docs: Connect to local MCP servers https://modelcontextprotocol.io/docs/develop/connect-local-servers
- MCP Python SDK docs: Migration guide https://py.sdk.modelcontextprotocol.io/migration/
- FastMCP: Welcome https://gofastmcp.com/getting-started/welcome
- MCP Inspector: README https://github.com/modelcontextprotocol/inspector
- MCP Python SDK release v2.0.0b1 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0b1
- MCP Python SDK release v2.0.0 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0
- MCP Python SDK release v2.0.1 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.1
- MCP Python SDK release v2.1.0 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.1.0
- MCP Python SDK release v2.1.1 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.1.1
- MCP Python SDK release v2.2.0 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.2.0
- MCP Python SDK release v2.3.0 https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.3.0
- David Soria Parra on X: first betas of the Python and TypeScript SDK v2 https://x.com/dsp_/status/2072307451982958925
- Jeremiah Lowin on X: FastMCP v4 rebuilt on the v2 Python SDK https://x.com/jlowin/status/2082217726949445778


