Mindket

用官方 SDK 写一个 Python MCP 服务器

黑色横幅,标题是“MCP 实战课”,下面一行写着“用 Python 或 TypeScript 写 MCP 服务器,测试并接入”。

要用 Python 写 MCP 服务器,先用 uv 安装官方的 mcp 包,再创建一个 MCPServer,用 @mcp.tool() 把每个函数注册成工具。SDK 会根据函数的类型注解和文档字符串生成工具的参数结构。最后用 mcp.run(transport="stdio") 运行这个文件,Claude Desktop 这类宿主就能启动它并调用工具。

本节内容

  • 你会写出一个带两个工具的天气服务器:get_alerts 和 get_forecast,全部放在一个 Python 文件里。
  • SDK 根据函数的类型注解和文档字符串生成每个工具的参数结构,不用手写 JSON Schema。
  • stdio 服务器不能用 print():标准输出上多出任何内容都会破坏协议,日志要写到 stderr。
  • SDK 2.x 把 FastMCP 改名为 MCPServer,所以还在导入 FastMCP 的旧教程会和现在对不上。
全部课程
本页目录
  1. 开始之前
  2. 七步写出服务器
  3. 文件里每一部分在做什么
  4. 为什么 print() 会搞坏 stdio 服务器
  5. 在 Claude Desktop 里运行
  6. MCP 联合创建者和 FastMCP 作者怎么说 Python SDK v2
  7. 常见错误
  8. 动手试试
  9. 下一课:用 TypeScript 写同一个服务器
  10. 自测一下
  11. 常见问题
  12. 资料来源

开始之前

这一课做的是 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 宿主都可以。
Python SDK v2,从第一个测试版说起
  1. 第一个 v2 测试版

    2.0.0b1 发布,完整支持 2026-07-28 版规范。

    来源:MCP Python SDK release v2.0.0b1 (2026年11月5日 查阅)

  2. 2.0.0 版

    稳定的 v2 和新规范同一天发布,用 MCPServer 取代了 FastMCP。

    来源:MCP Python SDK release v2.0.0 (2026年11月5日 查阅)

  3. 2.1.0 版

    Client 类可以接受 StdioServerParameters,提示词消息也能带图片和音频。

    来源:MCP Python SDK release v2.1.0 (2026年11月5日 查阅)

  4. 2.2.0 版

    HTTP 客户端只在端点的同一来源内跟随重定向,闲置的 Streamable HTTP 会话半小时后过期。1.30.0 版也做了同样的改动。

    来源:MCP Python SDK release v2.2.0 (2026年11月5日 查阅)

  5. 2.3.0 版

    以修复为主,另外加了三个新选项。

    来源:MCP Python SDK release v2.3.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")
  1. 安装 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 命令。
  2. 用 uv init weather 创建项目,再用 cd weather 进入目录。
  3. 创建并激活虚拟环境。先运行 uv venv,再运行 source .venv/bin/activate(macOS 和 Linux)或 .venv\Scripts\activate(Windows)。
  4. 用 uv add "mcp[cli]" 安装 SDK。Windows 下指南写的是不带引号的 uv add mcp[cli]。
  5. 新建 weather.py,把上面的代码粘进去。
  6. 运行 uv run weather.py。它不会输出任何东西,只是停在那里,因为它在 stdin 上等宿主发消息。按 Ctrl+C 停止。
  7. 用 uv run mcp dev weather.py 在 Inspector 里打开它,用两个字母的州代码(比如 CA)调用 get_alerts。

文件里每一部分在做什么

装饰器 @mcp.tool() 能把一个普通函数变成模型可以调用的工具。SDK 直接读这个函数本身:函数名成为工具名,文档字符串成为模型读到的说明,类型注解成为输入参数的结构。文档字符串是写给模型看的,因为读它的就是模型。

README 用 15 行演示了同样的思路:一个 add 工具加一个问候资源。觉得天气服务器太长,可以先从那里入手。多出来的篇幅大部分是调用天气 API 的辅助函数,和 MCP 本身无关。

这里的工具用了 async def,因为它们要等网络请求。

weather.py 里让它成为 MCP 服务器的四行
文件里的写法它做什么宿主看到什么
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 重新构建

Jeremiah Lowin, Prefect 创始人兼 CEO,FastMCP 作者,引文为译文 · X (2026年11月5日 查阅)
David Soria Parra(@dsp_)2026 年 7 月 1 日在 X 上发帖:新的无状态 MCP 规范将在 7 月 28 日发布,团队赶在那之前发布了 Typescript SDK v2.0.0 和 Python SDK v2.0.0 的第一批测试版,这会让构建 MCP 服务器和客户端容易得多
David Soria Parra 在 X 上,2026 年 7 月 1 日
Jeremiah Lowin(@jlowin)2026 年 7 月 28 日在 X 上发帖:新规范发布了,FastMCP v4 测试版也一起来了。FastMCP v4 完全基于 v2 Python SDK 重新构建,你既能用上新协议的全部功能,又能保留熟悉的 FastMCP 写法
Jeremiah Lowin 在 X 上,2026 年 7 月 28 日

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 服务器安全吗?。

自测一下

  1. 在 Python SDK 2.x 里,用哪条导入来创建服务器?

    查看答案

    B. from mcp.server import MCPServer SDK 2.x 把 FastMCP 改名为 MCPServer,旧的 mcp.server.fastmcp 路径已经没有了。

  2. 工具函数的哪一部分会变成模型读到的描述?

    查看答案

    A. 它的文档字符串 函数名成为工具名,类型注解成为输入结构,文档字符串就是描述,所以要为模型来写它。

  3. stdio 服务器为什么绝对不能调用 print()?

    查看答案

    C. 标准输出传的是协议数据,多出来的一行会破坏这条数据流 在 stdio 上,标准输出就是协议本身,所以日志要用 logging,它写到的是 stderr。

  4. 天气服务器里,每个网络问题都显示成“Unable to fetch alerts”,这是为什么?

    查看答案

    D. 辅助函数吞掉了所有异常,并返回 None make_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,天气工具就是这样写的。

资料来源

  1. MCP docs: Build an MCP server (2026年11月4日 查阅)https://modelcontextprotocol.io/docs/develop/build-server
  2. MCP Python SDK: README (2026年11月4日 查阅)https://github.com/modelcontextprotocol/python-sdk
  3. MCP Python SDK docs: Installation (2026年11月4日 查阅)https://py.sdk.modelcontextprotocol.io/get-started/installation/
  4. MCP Python SDK docs: First steps (2026年11月4日 查阅)https://py.sdk.modelcontextprotocol.io/get-started/first-steps/
  5. MCP Python SDK docs: Connect to a real host (2026年11月4日 查阅)https://py.sdk.modelcontextprotocol.io/get-started/real-host/
  6. MCP Python SDK docs: What's new in v2 (2026年11月4日 查阅)https://py.sdk.modelcontextprotocol.io/whats-new/
  7. MCP docs: Connect to local MCP servers (2026年11月4日 查阅)https://modelcontextprotocol.io/docs/develop/connect-local-servers
  8. MCP Python SDK docs: Migration guide (2026年11月5日 查阅)https://py.sdk.modelcontextprotocol.io/migration/
  9. FastMCP: Welcome (2026年11月5日 查阅)https://gofastmcp.com/getting-started/welcome
  10. MCP Inspector: README (2026年11月5日 查阅)https://github.com/modelcontextprotocol/inspector
  11. MCP Python SDK release v2.0.0b1 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0b1
  12. MCP Python SDK release v2.0.0 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0
  13. MCP Python SDK release v2.0.1 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.1
  14. MCP Python SDK release v2.1.0 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.1.0
  15. MCP Python SDK release v2.1.1 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.1.1
  16. MCP Python SDK release v2.2.0 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.2.0
  17. MCP Python SDK release v2.3.0 (2026年11月5日 查阅)https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.3.0
  18. David Soria Parra on X: first betas of the Python and TypeScript SDK v2 (2026年11月5日 查阅)https://x.com/dsp_/status/2072307451982958925
  19. Jeremiah Lowin on X: FastMCP v4 rebuilt on the v2 Python SDK (2026年11月5日 查阅)https://x.com/jlowin/status/2082217726949445778

分享本节课

看看别的 MCP 服务器能做什么

到 Mindket 上浏览 MCP 服务器,和你刚写的这个比一比。

浏览 MCP 服务器

本节课的 Markdown 版本 · MCP 实战课:写、测试并接入 MCP 服务器 · 全部课程