Skip to main content
在本教程中,我们将构建一个简单的 MCP 天气服务器,并将其连接到主机(Claude for Desktop)。

我们将构建什么

我们将构建一个提供两个工具的服务器:get_alertsget_forecast。然后我们将把服务器连接到一个 MCP 主机(在本例中是 Claude for Desktop):
服务器可以连接到任何客户端。为了简单起见,我们在这里选择了 Claude for Desktop,但我们也提供了关于 构建你自己的客户端 的指南,以及 其他客户端列表

核心 MCP 概念

MCP 服务器可以提供三种主要能力:
  1. 资源:类似文件的数据,客户端可以读取(如 API 响应或文件内容)
  2. 工具:可由 LLM 调用的函数(需要用户批准)
  3. 提示:预先编写的模板,帮助用户完成特定任务
本教程将主要聚焦于工具。
让我们开始构建我们的天气服务器吧! 你可以在这里找到我们将要构建的完整代码。

先决知识

本快速入门假设你已经熟悉:
  • Python
  • 像 Claude 这样的 LLM

登录 MCP 服务器

在实现 MCP 服务器时,请小心处理日志输出方式:基于 STDIO 的服务器: 永远不要向 stdout 写入内容。向 stdout 写入会破坏 JSON-RPC 消息并导致你的服务器失效。print() 函数默认会写入 stdout,但可以通过 file=sys.stderr 安全使用。基于 HTTP 的服务器: 标准输出日志是没问题的,因为它不会干扰 HTTP 响应。

最佳实践

  • 使用一个写入 stderr 或写入文件的日志库。

快速示例

import sys
import logging

# ❌ 不好(STDIO)
print("Processing request")

# ✅ 好(STDIO)
print("Processing request", file=sys.stderr)

# ✅ 好(STDIO)
logging.info("Processing request")

系统要求

  • 安装了 Python 3.10 或更高版本。
  • 你必须使用 Python MCP SDK 1.2.0 或更高版本。

配置你的环境

首先,安装 uv 并设置我们的 Python 项目和环境:
curl -LsSf https://astral.sh/uv/install.sh | sh
确保在之后重新启动你的终端,以确保能识别到 uv 命令。接下来,我们创建并设置我们的项目:
# 为我们的项目创建一个新目录
uv init weather
cd weather

# 创建虚拟环境并激活
uv venv
source .venv/bin/activate

# 安装依赖
uv add "mcp[cli]" httpx

# 创建我们的服务器文件
touch weather.py
现在让我们进入构建你的服务器。

构建你的服务器

导入包并设置实例

将以下内容添加到你的 weather.py 文件顶部:
from typing import Any

import httpx
from mcp.server.fastmcp import FastMCP

# 初始化 FastMCP 服务器
mcp = FastMCP("weather")

# 常量
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
FastMCP 类使用 Python 的类型标注和文档字符串来自动生成工具定义,这使得创建和维护 MCP 工具变得容易。

辅助函数

接下来,让我们添加用于查询并格式化来自国家气象服务(National Weather Service)API 的数据的辅助函数:
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 httpx.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: 两位字母的美国州代码(例如 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 "无法获取警报,或未找到任何警报。"

    if not data["features"]:
        return "该州没有正在生效的警报。"

    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: 位置的纬度
        longitude: 位置的经度
    """
    # 首先获取预报网格端点
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "无法获取该位置的预报数据。"

    # 从 points 响应中获取预报 URL
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "无法获取更详细的预报。"

    # 将 periods 格式化为可读的预报
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # 仅显示接下来的 5 个时间段
        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)

运行服务器

最后,让我们初始化并运行服务器:
def main():
    # 初始化并运行服务器
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
你的服务器已经完成了! 运行 uv run weather.py 来启动 MCP 服务器,它将监听来自 MCP 主机的消息。现在,让我们使用现有的 MCP 主机来测试你的服务器——Claude for Desktop。

使用 Claude for Desktop 测试你的服务器

使用 Claude for Desktop 测试你的服务器

Claude for Desktop 目前在 Linux 上不可用。Linux 用户可以继续阅读 构建客户端 教程,以构建一个连接到我们刚刚搭建完成的 MCP 服务器的 MCP 客户端。
首先,确保你已安装 Claude for Desktop。 你可以在这里安装最新版本。 如果你已经安装了 Claude for Desktop,请确认已更新到最新版本。我们需要为你想使用的任意 MCP 服务器配置 Claude for Desktop。 为此,在文本编辑器中打开 Claude for Desktop 应用配置文件:~/Library/Application Support/Claude/claude_desktop_config.json。 如果文件不存在,请先创建它。例如,如果你安装了 VS Code
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
然后在 mcpServers 键中添加你的服务器。 只有当至少有一个服务器被正确配置时,MCP UI 元素才会显示在 Claude for Desktop 中。在这种情况下,我们将按下面方式添加我们的单个天气服务器:
{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
        "run",
        "weather.py"
      ]
    }
  }
}
你可能需要在 command 字段中填写 uv 可执行文件的完整路径。 你可以在 macOS/Linux 上运行 which uv,在 Windows 上运行 where uv 来获取它。
请确保在 cwd(此处为目录参数)中传入你服务器的绝对路径。 你可以在 macOS/Linux 上通过运行 pwd 获取;在 Windows 的命令提示符中通过运行 cd 获取。 在 Windows 中,请记得在 JSON 路径里使用双反斜杠(\\)或正斜杠(/)。
这会告诉 Claude for Desktop:
  1. 有一个名为“weather”的 MCP 服务器
  2. 通过运行 uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py 来启动它
保存文件,然后重启 Claude for Desktop

使用命令进行测试

让我们确保 Desktop 版的 Claude 能够正确识别我们在 weather 服务器中暴露的两个工具。你可以通过查看“添加文件、连接器和更多 /” 图标来确认:
点击加号图标后,将鼠标悬停在“连接器(Connectors)”菜单上。你应该会看到列出的 weather 服务器:
如果你的服务器没有被 Desktop 版的 Claude 识别,请继续查看 故障排查(Troubleshooting) 部分以获取调试建议。 如果你的服务器已经出现在“连接器(Connectors)”菜单中,你现在可以通过在 Desktop 版的 Claude 中运行以下命令来测试你的服务器:
  • 萨克拉门托(Sacramento)的天气怎么样?
  • 德克萨斯(Texas)有哪些生效的天气警报(active weather alerts)?
由于这是美国国家气象服务(National Weather service),因此这些查询只对美国境内的位置有效。

后台发生了什么

当你提出问题时:
  1. 客户端将你的问题发送给 Claude
  2. Claude 分析可用工具,并决定使用哪一个或哪些工具
  3. 客户端通过 MCP 服务器执行所选工具
  4. 将结果发送回 Claude
  5. Claude 生成自然语言回复
  6. 回复会显示给你!

故障排查(Troubleshooting)

从 Claude for Desktop 获取日志与 MCP 相关的 Claude.app 日志会写入 ~/Library/Logs/Claude 目录中的日志文件:
  • mcp.log 将包含关于 MCP 连接和连接失败的常规日志。
  • 名称为 mcp-server-SERVERNAME.log 的文件将包含来自指定服务器的错误(stderr)日志。
你可以运行以下命令来列出最近的日志,并持续跟踪任何新日志:
# 检查 Claude 的日志以查看错误
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
服务器未在 Claude 中显示
  1. 检查你的 claude_desktop_config.json 文件语法
  2. 确保你的项目路径是绝对路径,而不是相对路径
  3. 完全重启 Desktop 版的 Claude
要正确重启 Desktop 版的 Claude,你必须彻底退出应用程序:
  • Windows:在系统托盘中(可能在“隐藏图标(hidden icons)”菜单里)右键单击 Claude 图标,然后选择“退出(Quit)”或“离开(Exit)”。
  • macOS:使用 Cmd+Q,或在菜单栏中选择“退出 Claude(Quit Claude)”。
仅仅关闭窗口并不会完全退出应用程序,而你的 MCP 服务器配置更改也不会生效。
工具调用静默失败如果 Claude 尝试使用这些工具但失败了:
  1. 查看 Claude 的日志以查找错误
  2. 验证你的服务器构建和运行没有错误
  3. 尝试重启 Desktop 版的 Claude
都没有效果。该怎么办?请参考我们的 调试指南 以获得更好的调试工具和更详细的指导。
错误:无法获取网格点数据(Failed to retrieve grid point data)这通常意味着以下情况之一:
  1. 坐标超出了美国范围
  2. NWS API 出现了问题
  3. 你被限流了(rate limited)
修复方法:
  • 确认你使用的是美国坐标
  • 在请求之间加入一点小延迟
  • 查看 NWS API 状态页面
错误:[STATE] 没有活动警报(No active alerts for [STATE])这不是错误——只是表示该州目前没有天气警报。尝试换一个州,或在严重天气发生时再查看。
如需更高级的故障排查,请查看我们关于 调试 MCP 的指南

下一步

构建客户端(Building a client)

了解如何构建你自己的 MCP 客户端,以连接到你的服务器

示例服务器(Example servers)

查看我们官方 MCP 服务器和实现的画廊

调试指南(Debugging Guide)

学习如何有效调试 MCP 服务器和集成

使用 Agent Skills 构建(Build with Agent Skills)

使用 agent skills 引导 AI 代码助手完成服务器设计