开始之前
这一课做的是 MCP 官方构建指南里的天气服务器。多数 Python 教程都从它起步,第一个服务器该有的部分它都有:服务器对象、两个工具、会变成参数结构的文档字符串,还有 stdio 传输。
动手之前先避一个坑。Python SDK 的 2.0 版把 FastMCP 改名为 MCPServer,旧的 mcp.server.fastmcp 导入路径已经没有了。教程里要是还在用它,那是按 1.x 写的。现在 pip install mcp 装的是 2.x,下面的代码也是 2.x 的。
- Python 3.11 或更高。
uv,指南用的 Python 包管理器。README 说直接用pip install "mcp[cli]"也行。- 打过补丁的 Node.js,只在最后一步的 Inspector 里用到:22 系列要 22.23.4 或更高,24 系列要 24.22.1 或更高,或者 26.12.1 及以上。第 3 课会细讲。
- 要能上网。工具调用的是美国国家气象局的 API,它只覆盖美国境内的地点。
- Claude Desktop,想和写好的服务器对话的话。任何 MCP 宿主都可以。
2.1.0 版
Client类可以接受StdioServerParameters,提示词消息也能带图片和音频。来源:MCP Python SDK release v2.1.0 (2026年11月5日 查阅)
2.2.0 版
HTTP 客户端只在端点的同一来源内跟随重定向,闲置的 Streamable HTTP 会话半小时后过期。1.30.0 版也做了同样的改动。
来源:MCP Python SDK release v2.2.0 (2026年11月5日 查阅)
七步写出服务器
指南把代码分成四段:导入、两个辅助函数、两个工具和运行入口。按这个顺序粘在一起,就是一个完整的 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")
- 安装 uv。macOS 或 Linux 运行
curl -LsSf https://astral.sh/uv/install.sh | sh。Windows 运行powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"。然后重启终端,让系统找到uv命令。 - 用
uv init weather创建项目,再用cd weather进入目录。 - 创建并激活虚拟环境。先运行
uv venv,再运行source .venv/bin/activate(macOS 和 Linux)或.venv\Scripts\activate(Windows)。 - 用
uv add "mcp[cli]"安装 SDK。Windows 下指南写的是不带引号的uv add mcp[cli]。 - 新建
weather.py,把上面的代码粘进去。 - 运行
uv run weather.py。它不会输出任何东西,只是停在那里,因为它在 stdin 上等宿主发消息。按 Ctrl+C 停止。 - 用
uv run mcp dev weather.py在 Inspector 里打开它,用两个字母的州代码(比如CA)调用get_alerts。
文件里每一部分在做什么
装饰器 @mcp.tool() 能把一个普通函数变成模型可以调用的工具。SDK 直接读这个函数本身:函数名成为工具名,文档字符串成为模型读到的说明,类型注解成为输入参数的结构。文档字符串是写给模型看的,因为读它的就是模型。
README 用 15 行演示了同样的思路:一个 add 工具加一个问候资源。觉得天气服务器太长,可以先从那里入手。多出来的篇幅大部分是调用天气 API 的辅助函数,和 MCP 本身无关。
这里的工具用了 async def,因为它们要等网络请求。
| 文件里的写法 | 它做什么 | 宿主看到什么 |
|---|---|---|
mcp = MCPServer("weather") | 创建服务器对象并给它起名 | 一个叫 weather 的服务器 |
get_alerts 上的 @mcp.tool() | 把函数注册成工具 | 一个工具,有一个文本参数 state |
get_forecast 上的 @mcp.tool() | 注册第二个工具 | 一个工具,有两个数字参数 latitude 和 longitude |
mcp.run(transport="stdio") | 从 stdin 读请求,往 stdout 写回复 | 一个由宿主启动和关闭的本地进程 |
为什么 print() 会搞坏 stdio 服务器
走 stdio 时,标准输出承载的就是协议本身。服务器往那里写的每一行都必须是 JSON-RPC 消息,所以一个多余的 print() 就会弄乱数据流,宿主随即断开连接。指南的意思很明确:stdio 服务器里完全不要出现 print()。
请用标准库的 logging 模块,它写到 stderr,每个模块用一个 logger。走 HTTP 就没有这个限制,因为标准输出不会碰到 HTTP 响应。
import logging
logger = logging.getLogger(__name__)
# Bad (stdio)
print("Processing request")
# Good (stdio)
logger.info("Processing request") # writes to stderr
在 Claude Desktop 里运行
指南用 Claude for Desktop 来测试服务器。打开 claude_desktop_config.json,在 mcpServers 下加上这个服务器,保存文件,再重启应用。下面是指南里 macOS 和 Linux 的条目,它的 Windows 写法把路径写成双反斜杠,比如 C:\ABSOLUTE\PATH\TO\PARENT\FOLDER\weather。
项目文件夹的路径必须是绝对路径,command 字段里可能还得写 uv 的完整路径,运行 which uv 或 where uv 就能查到。之后要把 Claude Desktop 彻底退出再重新打开。第 4 课会把这个应用和另外两个应用放在一起演示。
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
MCP 联合创建者和 FastMCP 作者怎么说 Python SDK v2
David Soria Parra 是 Anthropic 的技术人员,也是 MCP 的联合创建者。他在 2026 年 7 月 1 日在 X 上宣布,Python 和 TypeScript SDK 的第一批 v2 测试版已经发布,请开发者赶在 7 月 28 日无状态规范落地之前试用,并就好不好用给出反馈。那个日期之前的教程,大多是按 1.x 写的。
Jeremiah Lowin 是 Prefect 的创始人兼 CEO,也是 FastMCP 的作者。他在规范发布当天在 X 上说,FastMCP v4 完全基于 v2 Python SDK 重写。
这个 FastMCP 是独立的包,先 uv add fastmcp,再 from fastmcp import FastMCP。它的欢迎页写着,官方 SDK 在 2024 年吸收的高层 API 正是由它首创。导入 fastmcp 的教程用的是这个独立包,导入 mcp.server.fastmcp 的则是 SDK 1.x 的代码。
被旧导入路径绊倒的人不少,所以 2.1.1 版让它直接指向迁移指南。第二天,也就是 8 月 26 日,维护者又把这条提示补进了 2.0.1,原因是很多人都碰上了这个错误。移植 1.x 的服务器时,把这份指南开在旁边。
FastMCP v4 完全基于 v2 Python SDK 重新构建
X 官方嵌入卡片的截图,拍摄于 2026 年 11 月 5 日,时间为 UTC。两条帖子都被 X 用“Show more”截断了。点击截图可到 X 上打开原帖。
常见错误
- 从
mcp.server.fastmcp导入FastMCP。这条路径在 2.x 里已经没有了,请用from mcp.server import MCPServer。 - 在 stdio 服务器里调用
print()。宿主在数据流里读到乱码就会断开,请改用logging。 - 在宿主的配置里写相对路径。宿主从自己的工作目录启动服务器,光写
weather.py会失败,要写绝对路径。 - 改了宿主的配置却没有重启宿主。宿主只在启动时读取配置。
- 查询美国以外的预报。工具背后的天气 API 只覆盖美国境内。
- 轻信报错信息。辅助函数
make_nws_request会吞掉所有异常并返回None,所以任何网络问题看起来都是“Unable to fetch alerts”。调试时,请在那里把异常记进日志。
动手试试
把 README 里的那个资源加到 weather.py,重新运行 uv run mcp dev weather.py,到 Inspector 里找找它。资源是服务器能提供的第二类东西,和工具并列。
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
- 它会出现在资源模板里,而不是资源里,因为它带一个参数。
- 用名字 World 读取它,确认回复是
Hello, World!。 - 故意加一个
print()把服务器弄坏,看看 Inspector 会显示什么。
下一课:用 TypeScript 写同一个服务器
第 2 课用官方 TypeScript SDK写一个服务器,第 3 课再逐个标签页讲解 Inspector。如果你对协议本身还不熟,可以先看什么是 MCP,里面讲了整体架构。
本地运行的服务器能做你的用户账户能做的任何事。运行别人写的服务器之前,该检查什么,见MCP 服务器安全吗?。
自测一下
在 Python SDK 2.x 里,用哪条导入来创建服务器?
查看答案
B.
from mcp.server import MCPServerSDK 2.x 把FastMCP改名为MCPServer,旧的mcp.server.fastmcp路径已经没有了。工具函数的哪一部分会变成模型读到的描述?
查看答案
A. 它的文档字符串 函数名成为工具名,类型注解成为输入结构,文档字符串就是描述,所以要为模型来写它。
stdio 服务器为什么绝对不能调用
print()?查看答案
C. 标准输出传的是协议数据,多出来的一行会破坏这条数据流 在 stdio 上,标准输出就是协议本身,所以日志要用
logging,它写到的是 stderr。天气服务器里,每个网络问题都显示成“Unable to fetch alerts”,这是为什么?
查看答案
D. 辅助函数吞掉了所有异常,并返回
Nonemake_nws_request会捕获所有异常,所以调试时要在里面把异常记录下来。
常见问题
天气服务器能在 Python 3.10 上运行吗?
也许可以。构建指南和这个包在 PyPI 上的页面写的是 3.10 及以上,SDK 自己的文档却要求再高一个版本。几处说法不一致,所以这一课请用 3.11,3.10 只能算没有确认。
Python SDK 从 1.x 到 2.x,除了改名还变了什么?
@mcp.tool()、@mcp.resource() 和 @mcp.prompt() 这几个装饰器用法不变。host、port 这类传输选项从构造函数移到了 run(),HTTP 客户端换成了 httpx2,WebSocket 传输被移除,新的 Client 类建在 ClientSession 之上。要照着旧教程做,就把版本固定成 mcp>=1.28,<2。
一定要用 uv 吗,pip 行不行?
pip 也行。README 给的备选写法是 pip install "mcp[cli]",对应 uv add "mcp[cli]"。官方指南用 uv,是因为它也用 uv run 来运行文件,指南里的 Claude Desktop 配置同样调用了 uv。
mcp[cli] 里的 cli 多装了什么?
多装了 mcp 命令:mcp dev 在 Inspector 里打开服务器,mcp run 运行服务器,mcp install 把它登记到 Claude Desktop。这个附加包会装上 typer 和 python-dotenv,部署上线的服务器可能用不到。
怎样让服务器走 HTTP 而不是 stdio?
运行 uv run mcp run server.py --transport streamable-http。README 接着用客户端连接 http://localhost:8000/mcp。stdio 适合由宿主在你的电脑上启动服务器,Streamable HTTP 才是用来部署的传输方式。
这个天气服务器需要 API 密钥吗?
指南的代码里没有密钥。它只给美国国家气象局的 API 发送一个 User-Agent 请求头,别的什么都不带。这个 API 只覆盖美国境内。
工具可以写成普通函数,不用 async 吗?
可以。按 SDK v2 的说明,普通的 def 工具、资源和提示词会在工作线程里运行。工具要等网络时用 async def,天气工具就是这样写的。
资料来源
- 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


