本教程是 Claude Code 系列的第三轮,聚焦 MCP 服务器开发中一个最容易被忽视却最关键的决策点:什么时候该用 Tool,什么时候该用 Resource,什么时候该用 Prompt。绝大多数入门教程只教你"写一个 Tool",而现实里 90% 的 MCP 服务器都把本该是 Resource 的数据、本该是 Prompt 的工作流,错误地塞进了 Tool 里,导致上下文爆炸、模型乱调用、用户体验割裂。本篇用一个完整的"代码仓库知识库"服务器,把三个原语同时暴露,讲透取舍逻辑。
学习目标:读完本教程,你将能够——
适用人群:已经能跑通至少一个最小 MCP 服务器(会写 @mcp.tool)、想从"能跑"升级到"设计正确"的开发者。如果你还没写过任何 MCP 服务器,建议先完成第一轮的入门教程。
pip install "mcp[cli]",或社区版 pip install fastmcp# 官方 Python SDK(推荐新手,文档最全)
pip install "mcp[cli]"
# 或社区版 FastMCP 2.x(API 兼容,工具链更丰富)
pip install fastmcp
两个包的装饰器写法几乎一致。下文代码以官方 SDK 的
from mcp.server.fastmcp import FastMCP为主;若你用社区版,只需把导入改成from fastmcp import FastMCP,其余不动。这是本教程刻意选的兼容点,避免你被"到底是哪个包"卡住。
MCP 规范(2025-11-25 版)明确把服务器能力划分为三类,最关键的区分维度不是"功能",而是控制权归属(control):
| 原语 | 控制权 | 触发者 | 典型语义 | 代价 |
|---|---|---|---|---|
| Tools | 模型控制(model-controlled) | LLM 自主决定何时调用 | 动作、副作用、查询计算 | 每次调用消耗 token,模型可能误触发 |
| Resources | 应用控制(application-driven) | 宿主/用户在 UI 里显式选取 | 只读上下文数据:文件、schema、文档 | 占用上下文窗口,但调用语义明确 |
| Prompts | 用户控制(user-controlled) | 用户点斜杠命令显式触发 | 工作流模板、角色扮演、结构化指令 | 强制结构化,灵活性低 |
规范原文给的三句话定义非常清楚:
最常见的错误:把一份 API 文档当成 Tool 返回。后果是模型为了"读文档"白白烧掉一轮 tool call,而且文档内容会进入 tool result,和真正的调用结果混在一起,难以管理。正确做法是把它做成 Resource,让宿主按需注入。
repo://readme、repo://docs/{name}。用 URI 模板实现动态资源。下面这张架构图把三原语在客户端—服务器之间的流向画清楚:
flowchart LR
U([用户]) -->|选择 slash command| P[Prompt 用户控制]
U -->|在列表里点选| R[Resource 应用控制]
H[宿主 Host/Claude Code] -->|按需注入上下文| R
H -->|渲染后下发给模型| P
LLM[LLM 模型] -->|自主决策调用| T[Tool 模型控制]
P --> LLM
R --> LLM
T -->|执行副作用/查询| S[(外部系统\nGit/DB/API)]
T --> LLM
subgraph server[你的 MCP 服务器]
R
P
T
end
我们要构建的 repo-kb 服务器面向一个真实痛点:让 Claude Code 理解一个陌生仓库。同一个目标,三种原语各管一段——
review_pr、explain_module(结构化的工作流模板)。mkdir repo-kb && cd repo-kb
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]"
新建 server.py:
# server.py
from pathlib import Path
from mcp.server.fastmcp import FastMCP, Context
REPO_ROOT = Path(__file__).resolve().parent / "sample-repo"
mcp = FastMCP("repo-kb")
预期结果:python server.py 启动后不报错(因为还没有入口,后面补)。先准备一个样本仓库:
mkdir -p sample-repo/docs
echo "# Sample Repo\n这是一个用于演示的仓库。" > sample-repo/README.md
echo "贡献指南……" > sample-repo/docs/architecture.md
静态资源(固定 URI)和资源模板(带参数的 URI)分开写:
# 静态资源:固定 URI,宿主可在列表里直接看到
@mcp.resource("repo://readme")
def readme() -> str:
"""仓库的 README 全文。"""
return (REPO_ROOT / "README.md").read_text(encoding="utf-8")
# 资源模板:URI 里带 {name},按需实例化
@mcp.resource("repo://docs/{name}")
def doc_by_name(name: str) -> str:
"""按文档名读取 docs/ 下的 Markdown。"""
path = REPO_ROOT / "docs" / f"{name}.md"
if not path.exists():
raise FileNotFoundError(f"文档不存在: {name}")
return path.read_text(encoding="utf-8")
关键点:函数签名的类型注解会被 SDK 反射成参数 schema;@mcp.resource 接收的字符串就是 URI 模板,{name} 既是路由参数也会成为函数入参。读取时客户端发 resources/read,规范要求服务器声明 resources capability(FastMCP 自动处理)。
import subprocess
@mcp.tool
def search_code(query: str, ctx: Context) -> str:
"""
在仓库里用 ripgrep 搜索代码片段。
Args:
query: 搜索关键词或正则。
Returns:
命中的文件名与行号列表。
"""
ctx.info(f"开始搜索: {query}") # 向客户端推送进度日志
try:
out = subprocess.run(
["rg", "-n", query, str(REPO_ROOT)],
capture_output=True, text=True, timeout=10,
)
hits = out.stdout.strip().splitlines()
ctx.info(f"命中 {len(hits)} 处")
return "\n".join(hits) or "无匹配"
except FileNotFoundError:
return "未安装 ripgrep,请先安装 rg"
为什么这是 Tool 而不是 Resource:搜索需要模型判断"要不要搜、搜什么",结果还会随查询变化——这是 model-controlled 的典型场景。注意 ctx: Context 是可选注入参数,SDK 会自动传入,不会暴露给模型的工具 schema。
@mcp.prompt
def review_pr(pr_url: str) -> str:
"""
以资深代码审查者的视角审查一个 Pull Request。
会自动附带仓库 README 作为背景。
"""
bg = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
return (
f"你是一位严格的资深代码审查者。\n"
f"仓库背景:\n{bg}\n\n"
f"请审查以下 PR,从正确性、可读性、安全性三个维度给出意见:\n{pr_url}"
)
@mcp.prompt
def explain_module(module: str) -> str:
"""用费曼学习法向新人解释某个模块。"""
return f"请像向完全的新人讲解那样,解释仓库中「{module}」模块的职责与核心数据流。"
为什么这是 Prompt 而不是 Tool:它的产出是"给模型的指令文本"而非副作用,且期望由用户主动以斜杠命令触发(如 /review_pr https://github.com/.../pull/42)。Prompt 在规范里返回的是一组 Message(用户/助手角色),FastMCP 允许直接返回字符串,SDK 会自动包成 UserMessage。
文件末尾加上:
if __name__ == "__main__":
mcp.run(transport="stdio")
常见问题:
| 现象 | 原因 | 解决 |
|---|---|---|
启动报 ImportError: mcp |
没装 SDK 或装错虚拟环境 | 确认 pip install "mcp[cli]" 在当前 venv |
资源模板 {name} 不生效 |
URI 没用花括号或参数名与函数形参不一致 | 二者必须同名 |
ctx.info 抛错 |
没给函数加 ctx: Context 形参 |
加上即可,SDK 自动注入 |
MCP Inspector 是官方调试工具,最大的价值是它把 Tools / Resources / Prompts 三个面板分开,你能直观看到三原语是否各自就位:
npx @modelcontextprotocol/inspector python server.py
浏览器打开提示的本地地址后:
search_code;repo://readme,模板 repo://docs/{name} 也能填参读取;review_pr、explain_module,可填 pr_url 后预览生成的消息。预期结果:三块面板各自有内容。如果某块是空的,说明对应原语没被注册(多半是装饰器漏写或函数没返回值)。
最贴近真实使用的验证是接进 Claude Code:
claude mcp add repo-kb -- python /绝对路径/server.py
# 或在项目根写 .mcp.json
.mcp.json 写法:
{
"mcpServers": {
"repo-kb": {
"command": "python",
"args": ["/绝对路径/server.py"]
}
}
}
接入后,在 Claude Code 里:
/ 应能看到 review_pr、explain_module 两个斜杠命令(Prompt 通道);search_code(Tool 通道);repo://readme(Resource 通道)。flowchart TD
A[新能力] --> B{有副作用或外部查询?}
B -- 是 --> C{需不需要模型自主决定<br/>何时执行?}
C -- 是 --> T[Tool 工具]
C -- 否 --> R2[考虑做成内部函数<br/>而非对外原语]
B -- 否 --> D{是只读上下文数据?}
D -- 是 --> R[Resource 资源]
D -- 否 --> E{是带参数的<br/>结构化工作流指令?}
E -- 是 --> P[Prompt 提示词]
E -- 否 --> F[多半不该做成原语<br/>重新评估需求]
style T fill:#ffe0e0
style R fill:#e0f5ff
style P fill:#fff4d0
把这张图打印出来贴在屏幕边,能挡掉 80% 的设计纠结。一个高频误判:"返回一段文档"该是 Tool 还是 Resource——按图走,没有副作用、是只读上下文 → Resource,哪怕你想"按查询动态生成",也用资源模板 docs://{name} 而非工具。
真正强大的 MCP 服务器会让三个原语互相协作:
explain_module Prompt 在内部要求模型先调用 search_code Tool 收集证据;ctx.info 流回宿主,让用户看到"模型在干嘛"。@mcp.prompt
def audit_security() -> str:
"""安全审计工作流:先用 search_code 找危险调用,再逐条评估。"""
return (
"请按以下步骤执行安全审计:\n"
"1. 调用 search_code 搜索 eval( 、exec( 、subprocess、os.system\n"
"2. 对每个命中点评估是否存在用户可控输入\n"
"3. 输出风险清单与修复建议"
)
这种"Prompt 编排 Tool"的模式,正是 MCP 区别于普通函数调用库的核心价值:它把人控制的流程骨架(Prompt)、模型控制的执行动作(Tool)、应用控制的静态知识(Resource)分层解耦,每一层的控制权和可见性都清晰。
| 维度 | 评估 |
|---|---|
| 适用训练营 | MCP 服务器开发中级营、AI Agent 工具链营、企业 RAG/上下文工程营 |
| 不适合 | 零基础营(需先有 Tool 基础)、纯前端营(本篇偏 Python 后端) |
| 教学时长 | 讲解 45 分钟 + 动手 90 分钟 + 调试答疑 45 分钟,合计约 3 课时 |
| 可考核点 | ① 能否独立判定新能力归 Tool/Resource/Prompt;② Inspector 三面板是否齐全;③ 是否避免"把文档塞进 Tool" |
| 延伸作业 | 给学员一个真实 API,要求把列表查询做成 Resource、写入做成 Tool、常用请求模板做成 Prompt,并写出取舍理由 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
