第 19 章 · 编写第一个 MCP Server
本章目标:理解 MCP Server 的最小结构,用官方 Python SDK 的装饰器风格写出包含工具、资源与提示模板的完整服务器,并学会用 MCP Inspector 调试它。
13.1 环境准备:安装官方 Python SDK
Anthropic 主导的 Model Context Protocol(MCP)提供了官方 Python SDK,包名为 mcp。推荐安装带 CLI 扩展的版本:
pip install "mcp[cli]"
# 或者使用 uv:
uv add "mcp[cli]"[cli] 这个 extra 会在 SDK 之上额外安装 mcp 命令行工具,提供三个高频子命令:
mcp dev server.py # 启动 MCP Inspector 调试服务器(开发模式)
mcp run server.py # 直接运行服务器
mcp install server.py # 把服务器一键安装到 Claude Desktop如果只想临时跑一次而不污染环境,也可以用 uv 的一次性运行:uv run --with "mcp[cli]" mcp dev server.py。
版本注意
早期教程普遍使用 from mcp.server.fastmcp import FastMCP 这种写法。当前官方主线的 API 已经统一为 MCPServer,本教程严格按最新官方 README 编写。如果你在网上看到 FastMCP 字样,那是旧版集成方式,概念完全相通。
13.2 十五行就是一个完整的 Server
创建 server.py:
from mcp.server import MCPServer
# 创建服务器实例,参数是给客户端展示的名称
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"这就是一个完整可运行的 MCP 服务器:一个工具(tool)、一个模板化资源(resource)。启动调试界面看看效果:
mcp dev server.py
# 会自动拉起 MCP Inspector 并在浏览器打开 http://localhost:5177在 Inspector 的 Tools 标签页里调用 add(a=1, b=2),会得到 3——一条标准的 JSON-RPC 响应走完了完整协议流程。
13.3 类型注解即 Schema,docstring 即描述
回看上面的代码,你没有写的东西比写了的更重要:
- ❌ 没有 JSON Schema ——
a: int, b: int这两个类型注解就是 schema; - ❌ 没有请求解析 —— 协议层由 SDK 处理;
- ❌ 没有参数校验代码 —— Pydantic 在底层自动完成;
- ❌ 没有协议握手 —— initialize / capabilities 协商全自动。
函数的 docstring 会成为工具描述(description),直接进入模型的上下文——模型就是靠它判断"什么时候该调用这个工具"。因此写工具时要遵守两条纪律:
@mcp.tool()
def word_stats(path: str) -> dict:
"""统计一个文本文件的行数、单词数和字符数。
当用户想了解某个文本文件的规模或长度信息时使用此工具。
path 必须是本机可读的文件绝对路径。
"""
from pathlib import Path
text = Path(path).read_text(encoding="utf-8")
lines = text.count("\n") + 1
words = len(text.split())
return {"lines": lines, "words": words, "chars": len(text)}对比一下两种写法给模型的信息量:
| docstring 写法 | 模型的判断依据 |
|---|---|
"stats" | 几乎为零,可能永远不会被正确调用 |
| 一句话说明功能 | 能判断"做什么",但不知道边界 |
| 功能 + 使用时机 + 参数约束(如上) | 三要素齐全,触发可靠 |
参数类型尽量简单
优先使用 str、int、float、bool 和这些类型的列表/字典。复杂嵌套对象虽然支持,但 schema 越复杂,模型传参出错率越高。
13.4 再加两个实用工具:带默认值与错误处理
一个更贴近真实场景的工具集示例:
import os
from mcp.server import MCPServer
mcp = MCPServer("FileTools")
@mcp.tool()
def find_markdown(directory: str, keyword: str = "") -> list[str]:
"""在目录中查找 Markdown 文件,可按关键词过滤文件名。
当用户想定位项目中的 .md 文档时使用。
directory 为要搜索的目录路径;keyword 为文件名包含的关键字(可选)。
"""
if not os.path.isdir(directory):
# 把失败原因返回给模型,而不是抛出裸异常
return [f"错误:目录不存在 {directory}"]
results = []
for root, _dirs, files in os.walk(directory):
for f in files:
if f.endswith(".md") and keyword.lower() in f.lower():
results.append(os.path.join(root, f))
if len(results) >= 20: # 限制结果量,保护上下文
results.append("...(结果已截断)")
return results
return results or ["未找到匹配文件"]
@mcp.tool()
async def disk_usage(path: str, ctx) -> str:
"""查看目录占用的磁盘空间摘要。
需要读取系统状态时使用;path 为目标目录。
"""
total = sum(
f.stat().st_size
for f in __import__("pathlib").Path(path).rglob("*")
if f.is_file()
)
# ctx 是 SDK 注入的上下文对象,可以写日志反馈执行进度
await ctx.info(f"已扫描 {path}")
return f"{path} 共占用 {total / 1024:.1f} KB"两个值得学习的细节:
- 把错误作为正常结果返回:目录不存在时返回一句说明而不是让服务器崩溃,模型拿到文字后能自行向用户解释或修正参数;
- 异步工具原生支持:
async def直接可用,适合 IO 密集操作;ctx参数由 SDK 自动注入(不进入模型可见的参数列表),用于日志与进度上报。
13.5 注册资源与提示模板
除了工具,MCP 还有两类服务端能力。继续在 server.py 里扩展:
# 资源(Resource):把数据以 URI 形式暴露给客户端读取
@mcp.resource("file://changelog")
def get_changelog() -> str:
"""返回项目的 CHANGELOG 内容。"""
with open("CHANGELOG.md", encoding="utf-8") as f:
return f.read()
# 提示模板(Prompt):预置的可复用对话起点,客户端用户主动选择
@mcp.prompt()
def review_commit(hash: str) -> str:
"""生成一次代码评审的起始提示。"""
return f"""请帮我评审提交 {hash}:
1. 先用 git show {hash} 查看改动内容;
2. 从正确性、可读性、测试覆盖三个角度逐条给出意见;
3. 最后给出「建议合入 / 需要修改」的结论。
"""三者的分工要用一张表记清楚:
| 能力 | 注册方式 | 由谁触发 | 典型用途 |
|---|---|---|---|
| Tool 工具 | @mcp.tool() | 模型自主决策调用 | 执行动作、查询数据 |
| Resource 资源 | @mcp.resource(uri) | 应用/用户侧读取 | 暴露文件、数据库行等上下文 |
| Prompt 提示模板 | @mcp.prompt() | 用户主动选择 | 固化高质量工作流入口 |
资源 URI 支持模板参数(如 greeting://{name}),客户端会先请求模板列表,再按实参取值。
13.6 用 MCP Inspector 系统化调试
mcp dev 打开的 Inspector 是调试 MCP 的标准工具,核心工作流:
# 方式一:开发模式(自动重启 + Inspector)
mcp dev server.py
# 方式二:以 Streamable HTTP 传输运行(模拟线上形态)
mcp run server.py --transport streamable-http在 Inspector 中依次验证:
- Connect —— 观察 initialize 握手与能力协商是否成功;
- Tools 标签页 —— List Tools 查看 schema 是否符合预期(重点看参数名与必填标记),再用表单调用来验证返回;
- Resources / Prompts 标签页 —— 分别确认资源可读、模板可展开;
- 故意传入错误参数(如不存在的路径),确认返回的是友好文本而非堆栈。
调试通过后,可以直接 mcp install server.py 把它装进 Claude Desktop,或者按下一章的手工配置方式接入任意客户端。
本章小结
- 官方 Python SDK 安装命令是
pip install "mcp[cli]",[cli]提供mcp dev / run / install; - 当前 API 为
MCPServer:@mcp.tool()定义工具、@mcp.resource(uri)定义资源、@mcp.prompt()定义提示模板; - 类型注解自动生成 JSON Schema,docstring 成为工具描述——两者共同决定模型的调用质量;
- 错误应作为文本结果返回给模型,
ctx上下文对象可用于日志与进度; - Tool 由模型自主调用,Resource 由应用读取,Prompt 由用户选择,三者职责不同;
mcp dev+ MCP Inspector 是标准调试闭环:握手 → 列工具 → 调用 → 异常路径。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 在官方 Python SDK 中,工具参数的 JSON Schema 来自哪里?
2. 关于工具的 docstring,下列说法正确的是?
3. Tool、Resource、Prompt 三者的触发者分别是?
4. 工具执行中出现"目录不存在"这类业务错误,最佳做法是?
🛠️ 动手实践
- 给本章的服务器再加一个
read_note(note_name)工具:从固定目录读取指定的.md笔记并返回内容,注意处理"文件不存在"的情况。 - 注册一个模板资源
note://{name},与上一个工具形成"工具查、资源读"的组合,并在 Inspector 里分别验证两者。 - 写一个
@mcp.prompt()模板weekly_report(days: int),生成周报撰写提示,要求模板正文里对输出格式提出明确要求。
完成练习后,进入下一章:MCP 客户端集成实战,把这个服务器接入真实的 AI 客户端。