一个场景:每次查 API 都要离开编辑器

你正在 Claude Code 里写一个需要天气数据的功能。你知道 Open-Meteo 有免费天气 API,但 Claude 无法直接调用外部 API——你得切换窗口、复制文档里的端点、拿到返回结果、再粘贴给 Claude。

重复三次后你开始想:有没有办法让 Claude 直接调用我指定的 API?

有。MCP Server 就是做这个的——用 Python 写一个轻量服务,把任意 API 封装成 Claude 能直接调用的「工具」。本文带你从零构建一个可运行的 MCP Server,从环境搭建到调试部署,每一步都有可复制的代码。

MCP 架构示意图

一、环境准备

1.1 我们需要什么

1.2 创建项目

# 创建项目目录并初始化
mkdir my-first-mcp-server && cd my-first-mcp-server
uv init

# 安装依赖
uv add fastmcp httpx

如果用 pip:

pip install fastmcp httpx

安装完成后,创建一个 server.py 文件。这就是整个项目的核心——一个文件,不到 100 行代码。

二、基础篇:构建天气查询工具

2.1 创建 FastMCP 实例

# server.py
from fastmcp import FastMCP

mcp = FastMCP("WeatherServer")

"WeatherServer" 是这个 Server 在 Claude Code 中的显示名称。保持简短,有意义即可。

2.2 注册第一个工具

工具(Tool)是 MCP Server 的核心——它定义了一个 Claude 可以直接调用的函数。用 @mcp.tool() 装饰器注册:

import httpx

@mcp.tool()
async def get_weather(city: str, country: str = "CN") -> str:
    """查询指定城市的当前天气状况。

    Args:
        city: 城市名称,例如 "Beijing"、"Shanghai"
        country: 国家代码(ISO 3166),默认为 "CN"
    """
    # 第一步:通过 geocoding API 获取城市坐标
    geo_url = "https://geocoding-api.open-meteo.com/v1/search"
    params = {"name": city, "count": 1, "language": "zh"}

    async with httpx.AsyncClient() as client:
        geo_resp = await client.get(geo_url, params=params)
        geo_data = geo_resp.json()

        if not geo_data.get("results"):
            return f"未找到城市 '{city}',请检查名称是否正确。"

        location = geo_data["results"][0]
        lat, lon = location["latitude"], location["longitude"]
        name = location.get("name", city)

        # 第二步:用坐标查询天气
        weather_url = "https://api.open-meteo.com/v1/forecast"
        weather_params = {
            "latitude": lat,
            "longitude": lon,
            "current_weather": True,
        }
        weather_resp = await client.get(weather_url, params=weather_params)
        weather_data = weather_resp.json()
        current = weather_data["current_weather"]

        return (
            f"{name}\n"
            f"温度:{current['temperature']}°C\n"
            f"风速:{current['windspeed']} km/h\n"
            f"风向:{current['winddirection']}°\n"
            f"更新时间:{current['time']}"
        )

这段代码有三个关键设计,它们直接影响 Claude 能否正确调用你的工具:

我们用了 Open-Meteo API——它免费、无需注册 API Key,非常适合学习和原型开发。生产环境可以替换为任何 HTTP API 或数据库查询。

2.3 启动服务器

# 在 server.py 末尾添加
if __name__ == "__main__":
    mcp.run()
python server.py

mcp.run() 默认以 stdio 模式运行——Claude Code 通过标准输入/输出与你的 Server 通信。没有端口、没有 HTTP 服务器、不需要额外的进程管理。

三、进阶篇:添加资源和提示模板

MCP 除了工具(Tool),还有两种原语:资源(Resource)和提示模板(Prompt)。一个完整的 MCP Server 通常会组合使用这三种。

3.1 资源:暴露只读数据

资源是 Claude 可以「读取」的只读数据端点,适合暴露配置信息、文档片段、状态快照:

@mcp.resource("config://server-info")
def get_server_info() -> str:
    """返回服务器的配置信息"""
    import json
    return json.dumps({
        "name": "WeatherServer",
        "version": "1.0.0",
        "api": "Open-Meteo (free, no API key required)",
        "capabilities": ["weather_query", "uuid_generation", "timestamp"],
    }, ensure_ascii=False, indent=2)

资源的 URI(如 config://server-info)是 Claude 引用该数据的地址。当 Claude 需要了解这个 Server 的能力时,会自动读取这个端点。

3.2 提示模板:定义可复用的交互

@mcp.prompt()
def weather_analysis(city: str) -> str:
    """生成天气分析提示"""
    return f"""请分析 {city} 的当前天气状况,并给出以下建议:
1. 出行建议(是否需要带伞、穿什么衣服)
2. 户外活动适宜度
3. 未来几小时的气象风险

请用中文回复,语气友好。"""

提示模板让用户可以快速启动常见任务——输入城市名即可获得结构化的天气分析提示,无需重复输入指令格式。

3.3 输入验证与错误处理

生产环境中,外部输入必须验证。FastMCP 与 Pydantic 深度集成:

from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    city: str = Field(..., min_length=1, max_length=50, description="城市名称")
    country: str = Field(default="CN", pattern=r"^[A-Z]{2}$", description="国家代码")

@mcp.tool()
async def get_weather_safe(city: str, country: str = "CN") -> str:
    """查询天气(带输入验证)。

    Args:
        city: 城市名称
        country: 两位大写国家代码
    """
    # 手动验证 + 友好错误提示
    if not city or not city.strip():
        return "错误:城市名称不能为空。"
    if len(country) != 2 or not country.isupper():
        return "错误:国家代码应为两位大写字母(如 CN、US)。"

    try:
        # ... 与上面相同的 API 调用逻辑
        pass
    except httpx.TimeoutException:
        return "请求超时:天气服务暂时不可用,请稍后重试。"
    except Exception as e:
        return f"查询失败:{type(e).__name__}——{e}"

错误处理的关键原则:返回清晰的错误描述,而不是裸异常。Claude 看到可理解的错误信息后可以自动调整参数重试——看到「国家代码应为两位大写字母」就会把 "cn" 改成 "CN"

四、添加更多实用工具

一个 MCP Server 可以包含任意多个工具。下面为我们的 WeatherServer 添加两个纯本地计算的工具,展示 MCP 对本地逻辑和外部 API 的统一封装能力:

import uuid
from datetime import datetime, timezone

@mcp.tool()
def generate_uuid() -> str:
    """生成一个 UUID v4 随机标识符。每次调用返回不同的值。"""
    return str(uuid.uuid4())

@mcp.tool()
def current_timestamp() -> str:
    """获取当前 Unix 时间戳和 UTC 时间。"""
    now = datetime.now(timezone.utc)
    ts = int(now.timestamp())
    iso = now.isoformat()
    return f"Unix 时间戳: {ts}\nISO 格式: {iso}"

至此,你的 MCP Server 拥有了三类能力:

能力 实现方式 典型场景
外部 API 调用 async def + httpx 天气、新闻、数据查询
本地计算 同步 def UUID、时间戳、文本处理
配置暴露 @mcp.resource() 版本信息、API 文档

这正是 MCP 的设计哲学:将外部能力与本地逻辑统一封装为 LLM 可调用的标准接口

五、调试与测试

5.1 开发模式热重载

FastMCP 内置了开发服务器,支持修改代码后自动重载:

# 启动 MCP Inspector 进行交互式调试
uv run fastmcp dev server.py

这会打开一个 Web 界面,你可以在其中直接测试每个工具——传参、查看返回值、检查错误,非常直观。

5.2 配置 Claude Code

要让 Claude Code 发现你的 MCP Server,在 .claude/settings.json 中注册:

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["run", "python", "server.py"],
      "cwd": "${project}/my-first-mcp-server"
    }
  }
}

也可以在用户级配置 ~/.claude/settings.json 中注册,对所有项目生效。

5.3 验证

配置完成后,在 Claude Code 中直接问:

北京今天天气怎么样?

如果配置正确,Claude 会自动识别 get_weather 工具并调用它。

5.4 常见问题排查

症状 最可能的原因 解决
Claude 说「没有可用工具」 Server 进程启动失败 在终端单独运行 python server.py 看报错
工具调用超时 外部 API 网络延迟 在代码中加 httpx.Timeout(10.0)
Claude 传错参数 类型注解不完整 给每个参数加类型注解 + docstring 描述
工具列表为空 @mcp.tool() 未生效 确认装饰器语法正确、文件路径配置正确

六、生产部署要点

6.1 传输协议选择

传输方式 适用场景 特点
stdio 本地开发、个人使用 零配置,Claude Code 直接启动子进程
Streamable HTTP 团队共享、远程访问 多客户端连接,需部署为独立服务

切换为 HTTP 传输只需一行改动:

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

6.2 安全清单

七、总结

要点 说明
MCP Server 的本质 用 Python 函数封装能力,通过装饰器暴露给 Claude
三种原语 @mcp.tool()(可调用函数)、@mcp.resource()(只读数据)、@mcp.prompt()(提示模板)
类型注解 不是装饰——Claude 据此理解参数类型,缺少会导致传参错误
docstring Claude 理解工具用途的核心来源,写得越具体越好
错误处理 返回友好字符串而非裸异常,Claude 会自动调整重试
调试 mcp dev 热重载 + Claude Code 直接测试

从系列整体角度看:教程2 讲清了 MCP 是什么,本文则让你真正能动手写一个。当你理解了 MCP Server 的开发模式后,再回头看教程3 和教程4——Superpowers 和 gstack 的 Skills 定义,本质上就是在告诉 Claude「何时以及如何使用像 MCP 这样的工具」。

你的下一步:挑一个日常需要手动查询的 API,花 30 分钟把它封装成一个 MCP Server。之后每一次需要它时,Claude 会自动帮你调用——这才是 AI 编程该有的体验。


参考资料


延伸阅读

--:--
加载中…