一个场景:每次查 API 都要离开编辑器
你正在 Claude Code 里写一个需要天气数据的功能。你知道 Open-Meteo 有免费天气 API,但 Claude 无法直接调用外部 API——你得切换窗口、复制文档里的端点、拿到返回结果、再粘贴给 Claude。
重复三次后你开始想:有没有办法让 Claude 直接调用我指定的 API?
有。MCP Server 就是做这个的——用 Python 写一个轻量服务,把任意 API 封装成 Claude 能直接调用的「工具」。本文带你从零构建一个可运行的 MCP Server,从环境搭建到调试部署,每一步都有可复制的代码。
一、环境准备
1.1 我们需要什么
- Python 3.10+
- uv:快速 Python 包管理器(推荐)或 pip
- FastMCP:Pythonic 的 MCP 框架,用装饰器注册工具,零模板代码
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 能否正确调用你的工具:
- 类型注解不是装饰:
city: str和-> str会被 MCP 协议转换为 JSON Schema,Claude 据此理解参数类型。如果缺少类型注解,Claude 可能传错参数 - docstring 是工具的「使用说明书」:Claude 根据 docstring 判断「这个工具什么时候该调用」。写得越清楚,匹配越准确
- 错误信息要有可操作性:返回
"未找到城市"而不是抛出异常,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 安全清单
- 最小权限:每个工具只给完成任务的必要权限。天气查询不需要文件系统访问
- 输入验证:始终验证外部输入,不信任 LLM 传参
- 敏感信息:API Key 用环境变量,不硬编码
- 速率限制:对外部 API 的调用加频率控制,防止意外大量调用
七、总结
| 要点 | 说明 |
|---|---|
| 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 编程该有的体验。
参考资料
- FastMCP Documentation — FastMCP 官方文档,完整 API 参考和示例
- MCP Specification — MCP 官方协议规范(2025-03-26 版本)
- Open-Meteo API — 免费天气 API,无需注册,适合学习
- FastMCP: The Pythonic Way to Build MCP Servers — KDnuggets 的 FastMCP 入门教程
- 从零搭建 MCP-Server — 摩尔线程文档中心的中文教程
延伸阅读
- 教程2:MCP、Skills 与 Plugins——Claude Code 扩展机制全解析 — 理解 MCP 在整个扩展生态中的位置
- 教程3:Superpowers——从「能写代码」到「工程级交付」 — Skills 如何编排 MCP 工具实现工程化流程
- 教程4:gstack 深度解析——角色驱动的虚拟团队 — 23 个角色化 Skill 的完整拆解