TodayAI

GuidesMCP

构建你的第一个 MCP Server

用 Python 做一个最小 MCP weather server,暴露 get_alerts / get_forecast,并接入 Claude for Desktop 验证。

基于 Model Context Protocol Docs 整理 · 官方资料 ↗

MCP Server 不是 Agent。它做的事情更简单:把一个程序里的能力,以统一协议暴露给 Claude Desktop、IDE 或其他 MCP Client。

Host 负责承载对话体验,Client 负责和 Server 通信,Server 则提供具体能力。对初次接触来说,最常见的能力就是 Tool:一个有名字、有参数说明、能返回结果的函数。

这篇直接做一个真实最小 Server:按 MCP 官方教程搭建 weather server,暴露 get_alerts / get_forecast,并让宿主真正看见这些工具。

MCP Server 到底是什么

可以把关系理解成四层:Host、Client、Server、Tool。

Host 是用户直接面对的应用,比如 Claude for Desktop。Client 在 Host 内部,负责按 MCP 协议去连接 Server。Server 是你写的那个程序;它不负责“自己思考”,而是暴露能力。Tool 则是这些能力里最直观的一种:模型看到工具描述后,可以在需要时请求调用。

所以你写 MCP Server,本质不是在训练一个新模型,而是在给现有客户端提供一套标准接口。天气查询、内部 API、数据库只读查询,都可以先做成 Tool。

先把项目跑起来

官方 Python 示例用 uv 管理环境。你需要 Python 3.10+、已安装的 uv,以及后面通过 mcp[cli] 装上的 Python MCP SDK。

  • Python 3.10+
  • 已安装 uv
  • 熟悉基础 Python
  • 若用 Claude for Desktop 验证:macOS / Windows(官方注明 Linux 桌面端尚不可用,可改做 Client 教程验证)

官方 Python 环境初始化

bash
uv init weather
cd weather
uv venv
source .venv/bin/activate
uv add "mcp[cli]"
touch weather.py

写第一个 Tool

当前官方文档使用 from mcp.server import MCPServer。先创建 server 实例,再准备请求美国国家气象局(NWS)的辅助函数。STDIO 场景下用 httpx2 发请求,失败时返回 None,而不是把异常直接打到 stdout。

python
from typing import Any
from mcp.server import MCPServer
import httpx2

mcp = MCPServer("weather")
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

async def make_nws_request(url: str) -> dict[str, Any] | None:
    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:
    props = feature["properties"]
    return (
        f"Event: {props.get('event', 'Unknown')}\n"
        f"Area: {props.get('areaDesc', 'Unknown')}\n"
        f"Severity: {props.get('severity', 'Unknown')}\n"
        f"Description: {props.get('description', 'No description available')}"
    )

真正的工具用 @mcp.tool() 定义。函数名、类型提示和 docstring 都会进入工具 schema;模型正是靠这些信息判断“什么时候该调用、该传什么参数”。

python
@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."
    return "\n---\n".join(format_alert(f) for f in data["features"])

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location."""
    points = await make_nws_request(f"{NWS_API_BASE}/points/{latitude},{longitude}")
    if not points:
        return "Unable to fetch forecast data for this location."
    forecast = await make_nws_request(points["properties"]["forecast"])
    if not forecast:
        return "Unable to fetch detailed forecast."
    periods = forecast["properties"]["periods"][:5]
    return "\n---\n".join(
        f"{p['name']}: {p['temperature']}{p['temperatureUnit']} / {p['detailedForecast']}"
        for p in periods
    )

启动 Server

STDIO server 绝不能把日志 print 到 stdout,否则会破坏 JSON-RPC 通信。需要日志时写到 stderr。入口调用 mcp.run(transport="stdio")。

python
if __name__ == "__main__":
    mcp.run(transport="stdio")

# 启动:
# uv run weather.py

让 Claude Desktop / Client 看见它

把 server 写进 Claude for Desktop 的配置。关键是 command / args 指向你的绝对路径,并用 uv run 启动 weather.py。

claude_desktop_config.json 示例(路径换成你的绝对路径)

json
{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
        "run",
        "weather.py"
      ]
    }
  }
}

改完配置后重启 Claude for Desktop。如果在 Linux 上没有桌面端,可以改做官方的 Build an MCP client 来验证同一个 server。

怎么确认连接成功

连接成功时,你应该能在宿主里看到 weather 这个 server,并且对话中可以触发 get_alerts / get_forecast。工具返回的是与美国州天气相关的文本;这依赖 NWS 网络可用性。

  • uv run weather.py 能启动,且没有因为 stdout 日志崩溃
  • Claude for Desktop 能识别 weather server
  • 对话中可触发 get_alerts / get_forecast
  • 工具返回可读的天气相关文本

为什么模型知道该什么时候调用 Tool

模型并不是“猜到你写了一个 Python 函数”。它看到的是工具 schema:名字、参数类型、以及 docstring 里的说明。

所以描述写清楚非常重要。get_alerts 明确要求两字母州代码,模型才更可能在用户问 California alerts 时传入 CA。描述含糊、参数命名随便,调用质量会明显下降。

最容易出错的地方

宿主看不到 server

确认配置文件路径、绝对目录、JSON 合法,并重启 Claude for Desktop。

server 一启动就异常

检查是否有 print / 日志写到 stdout;改为 logging 到 stderr。

Linux 无法用 Claude for Desktop

按官方说明改做 Build an MCP client 来验证 server。

接下来可以继续做什么

对照官方完整 weather.py 补全细节,或者把 Tool 换成你自己的内部 API。下一步更自然的扩展,是做 MCP Client,理解对端如何发现工具、发起调用,并把结果回传给模型。

  • 阅读 GitHub:quickstart-resources/weather-server-python
  • 继续做「构建可以连接 MCP Server 的 Client」

官方资料

Model Context Protocol Docs

Build an MCP server