本教程带你构建一个以 Prompts 原语为核心的 MCP 服务器。MCP 有三原语——Tools(工具)、Resources(资源)、Prompts(提示词),但绝大多数教程只讲前两个。Prompts 才是把团队高频提示词"产品化"的关键:客户端能自动列出、自动渲染参数表单、自动拼好 system/user/assistant 消息。学完你能造一个研发提示词库 MCP,让 Claude Desktop、Inspector、任意 MCP 客户端一键调用规范化的代码审查、提交信息、调试提示。适合已用过 FastMCP 写过 Tool、想补齐全三原语的开发者。
很多人写 MCP 服务器只会写 @mcp.tool(),把所有逻辑塞进工具里。这其实浪费了 MCP 设计中最优雅的部分。三原语各司其职:
| 原语 | 本质 | 谁主动 | 触发方式 |
|---|---|---|---|
| Tools | 模型可调用的函数 | 模型自主决定调用 | 模型在生成中触发 |
| Resources | 模型可读取的数据 | 客户端/用户注入上下文 | 客户端挂载到上下文 |
| Prompts | 预制的、带参数的消息模板 | 用户主动选择 | 用户在客户端点击/输入触发 |
关键区别在于 Prompts 是面向"人"的入口。它对应那些你每天都要重复打、每次只改几个变量的高频提示。比如:
<贴代码>"<贴 diff>"<贴报错>"这些提示词,你手敲一年会敲出十几个变体,团队成员各敲各的、质量参差。把它们固化成 MCP Prompt 之后会发生三件事:
这就是 Prompts 的价值:它不是"更弱的工具",而是面向人类工作流的标准化入口。
flowchart LR
User[用户] -->|点击提示词| Client[MCP 客户端]
Client -->|prompts/list| Server[MCP 服务器]
Server -->|返回名称+参数表| Client
Client -->|渲染表单让用户填参| User
User -->|提交参数| Client
Client -->|prompts/get 带参数| Server
Server -->|渲染好的 messages| Client
Client -->|注入对话| Model[大模型]
Model -->|高质量结构化回答| User
开始前你需要:
bash
curl -LsSf https://astral.sh/uv/install.sh | shmcp dev 的 Inspector 网页调试时需要)不需要预先安装
mcp——下面会用uv一键拉起隔离环境。
新建目录并用 uv 初始化:
mkdir prompt-library && cd prompt-library
uv init --python 3.12
uv add "mcp[cli]"
mcp[cli] 是官方 Python SDK,附带 mcp 命令行工具(用于 mcp dev / mcp run)。它内置的 FastMCP 是写 MCP 服务器最省事的方式。
创建 server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("prompt-library")
if __name__ == "__main__":
mcp.run()
预期结果:四行代码,一个能跑的 MCP 服务器骨架。FastMCP("prompt-library") 里的名字会作为服务器的 serverInfo 暴露给客户端。
常见问题:
- mcp run 找不到命令?确认装的是 mcp[cli] 而非裸 mcp。
- 报 uvloop 相关错?Windows 下换 uv add asyncio 或忽略,stdio 传输不依赖它。
在 server.py 的 mcp = FastMCP(...) 之后加:
@mcp.prompt()
def code_review(code: str) -> str:
"""审查代码,找出 bug 和改进建议。"""
return (
"请审查下面这段代码,按【Bug】【安全】【性能】【可读性】四类给出问题,"
"每类若无问题就写'无'。最后给一段重构后的代码。\n\n"
f"```\n{code}\n```"
)
这就是一个完整的 Prompt。注意几个要点:
@mcp.prompt() 注册函数为提示词。函数名 code_review 成为提示词的 name。description,客户端会展示给用户看。code: str 自动成为提示词的必填 argument,类型注解会指导客户端渲染输入框。role=user 的消息。启动 Inspector 看效果:
mcp dev server.py
浏览器打开终端打印的本地地址(通常是 http://localhost:5173),左侧连接成功后点 Prompts 标签页,你会看到:
code_review 审查代码,找出 bug 和改进建议。
点进去,右侧渲染出一个参数表单:
code [_____________]
填入 print("x"),点 Run Prompt,下方返回:
{
"messages": [
{
"role": "user",
"content": { "type": "text", "text": "请审查下面这段代码...```...```" }
}
]
}
这就是 Prompts 的魔法:你没有写任何 UI 代码,客户端完全靠协议字段自动渲染了表单、自动拼好了消息。
常见问题: - 表单里参数框没出现?检查函数参数是否有类型注解——没有注解的参数 SDK 无法推断,会被忽略。 - Inspector 里看不到 Prompts 标签?说明连接失败或服务器崩了,看终端 stderr。
现实中的提示词往往有多个参数。加一个"生成提交信息"的提示,演示可选参数:
@mcp.prompt()
def commit_message(diff: str, scope: str = "", breaking: bool = False) -> str:
"""根据 git diff 生成 Conventional Commits 规范的提交信息。
Args:
diff: git diff 的输出文本
scope: 影响范围,如 auth、api(可留空)
breaking: 是否包含破坏性变更
"""
flag = "(标注 BREAKING CHANGE)" if breaking else ""
return (
f"你是 Git 提交信息专家。根据下面的 diff 生成一条 Conventional Commits 规范的提交信息。"
f"范围 scope={scope or '未指定'}{flag}。只输出三行:type、可选 scope、简短描述,"
f"再附一段正文说明动机。\n\n"
f"```diff\n{diff}\n```"
)
预期结果:Inspector 里这个 prompt 的参数表单会变成:
diff [___________] (必填,文本域)
scope [___________] (选填,默认空)
breaking [ ] (选填,勾选框,默认 false)
带默认值的参数自动变成可选 argument(required: false),无默认值的参数是必填(required: true)。布尔类型在友好客户端里会渲染成勾选框。
为什么把 Args 写在 docstring 里:FastMCP 会解析 Google 风格 docstring,把每个参数的描述喂给客户端,帮用户理解每个框该填什么。这比裸参数名 breaking 友好得多。
常见问题:
- 默认值是可变对象(如 items: list = [])?用 None 兜底后在函数体内处理,避免 Python 可变默认值陷阱。
- 复杂类型(dataclass、Pydantic model)作参数?SDK 也能处理,但为了"填表"友好,提示词参数尽量用 str/int/bool 这类标量。
单个字符串只能产出一条 user 消息。但真正的提示词工程常常需要 system 角色定调 + user 角色给数据。返回消息列表即可:
from mcp.server.fastmcp.prompts import base
@mcp.prompt()
def debug_error(error: str, language: str = "python") -> list[base.Message]:
"""把报错拆成 可能原因 / 排查步骤 / 修复建议 三段输出。"""
return [
base.UserMessage(
"你是一名资深排障工程师,回答永远分三段:"
"**可能原因**、**排查步骤**、**修复建议**。"
"每段用项目符号,排查步骤要可执行。"
),
base.AssistantMessage("明白,我会严格按三段格式输出,步骤可执行。"),
base.UserMessage(f"这是 {language} 报错,帮我拆解:\n\n```\n{error}\n```"),
]
预期结果:prompts/get 返回三条消息——两轮 user、一轮 assistant。base.UserMessage / base.AssistantMessage 是官方 SDK 提供的便捷类,等价于 base.Message(role="user", content=...)。
这种"system 用 user 角色伪装 + assistant 预先确认"的 few-shot 写法,是因为标准 MCP Prompts 协议的 message.role 只规范了 user 和 assistant 两种,把人设约束塞进第一条 user 消息是跨客户端最稳的做法。预置的 assistant 回复起到了"格式锚定"作用,模型会模仿这个结构继续输出。
常见问题:
- 返回的消息顺序很重要,最后一条通常是 user(这样模型有内容可响应)。
- 别忘了 from mcp.server.fastmcp.prompts import base,否则 UserMessage 找不到。
提示词不一定要返回静态拼接的字符串——它可以先跑代码、读文件、查数据库,再把结果嵌进消息。这是它比"存一个文本模板"强的地方。加一个读 Git 提交历史再生成 release notes 的提示:
import subprocess
@mcp.prompt()
def release_notes(since: str = "HEAD~5") -> str:
"""读取指定范围内的 git log,生成 release notes。"""
try:
log = subprocess.check_output(
["git", "log", "--pretty=%s", since],
stderr=subprocess.DEVNULL,
text=True,
).strip()
except Exception:
log = "(无法读取 git log,请确认在 git 仓库内运行)"
return (
"根据下面的提交记录,归类成【新功能】【修复】【重构】【其它】四组,"
"生成中文 release notes,每组用项目符号,合并语义重复的条目。\n\n"
f"{log}"
)
预期结果:用户在客户端点 release_notes、填 since=HEAD~10、点运行——服务器当场执行 git log,把真实提交喂进提示词,返回一条已经上下文齐备的 user 消息。模型拿到后直接产出格式化 release notes。
这意味着 Prompts 是"带业务逻辑的提示词工厂":动态参数 → 服务器侧数据获取 → 渲染成规范消息,全程对模型透明、对用户零负担。
常见问题: - 提示词里跑外部命令要控制超时、处理异常,别让服务器卡死或崩溃导致客户端拿不到响应。 - 涉及敏感数据时,提示词函数里别把密钥直接拼进返回文本——会被注入上下文随对话一起发出去。
Inspector 适合交互式调试,但要写自动化测试或在 CI 里验证,需要一个客户端脚本。新建 test_client.py:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. 列出所有提示词
listing = await session.list_prompts()
print("=== 提示词清单 ===")
for p in listing.prompts:
args = [a.name for a in p.arguments] if p.arguments else []
print(f" {p.name}({', '.join(args)}) — {p.description}")
# 2. 调用一个提示词,填参数
result = await session.get_prompt(
"commit_message",
{"diff": "+def foo(): pass", "scope": "api", "breaking": "false"},
)
print("\n=== commit_message 渲染结果 ===")
for msg in result.messages:
print(f"[{msg.role}] {msg.content.text[:120]}...")
if __name__ == "__main__":
asyncio.run(main())
运行:
uv run python test_client.py
预期结果:
=== 提示词清单 ===
code_review(code) — 审查代码,找出 bug 和改进建议。
commit_message(diff, scope, breaking) — 根据 git diff 生成...
debug_error(error, language) — 把报错拆成 可能原因 / 排查步骤 / 修复建议 三段输出。
release_notes(since) — 读取指定范围内的 git log,生成 release notes。
=== commit_message 渲染结果 ===
[user] 你是 Git 提交信息专家。根据下面的 diff 生成...
这就是协议层的全貌:客户端先 prompts/list 拿清单,再 prompts/get 带参数拿渲染好的 messages。
常见问题:
- get_prompt 的参数值一律传字符串(即使原参数是 bool),由服务器侧反序列化。
- 客户端脚本和服务器要在同一工作目录,否则 release_notes 这种读 git 的提示会失败。
把前面所有片段整合成一个完整可运行的项目。再加两个提示词,凑齐团队研发日常五件套。最终 server.py:
"""研发提示词库 MCP 服务器。"""
import subprocess
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
mcp = FastMCP("prompt-library")
# 1. 代码审查(单参数、字符串返回)
@mcp.prompt()
def code_review(code: str) -> str:
"""审查代码,按【Bug】【安全】【性能】【可读性】四类给问题与重构。"""
return (
"请审查下面这段代码,按【Bug】【安全】【性能】【可读性】四类给出问题,"
"每类若无问题就写'无'。最后给一段重构后的代码。\n\n"
f"```\n{code}\n```"
)
# 2. 提交信息(必填+可选+布尔)
@mcp.prompt()
def commit_message(diff: str, scope: str = "", breaking: bool = False) -> str:
"""根据 git diff 生成 Conventional Commits 提交信息。"""
flag = "(标注 BREAKING CHANGE)" if breaking else ""
return (
f"你是 Git 提交信息专家。根据下面的 diff 生成一条 Conventional Commits 规范提交信息。"
f"scope={scope or '未指定'}{flag}。只输出三行 type、可选 scope、简短描述,再附正文。\n\n"
f"```diff\n{diff}\n```"
)
# 3. 报错排障(多消息、few-shot 锚定)
@mcp.prompt()
def debug_error(error: str, language: str = "python") -> list[base.Message]:
"""把报错拆成 可能原因 / 排查步骤 / 修复建议 三段。"""
return [
base.UserMessage(
"你是资深排障工程师,回答分三段:**可能原因**、**排查步骤**、**修复建议**。"
),
base.AssistantMessage("明白,严格按三段、步骤可执行。"),
base.UserMessage(f"这是 {language} 报错,帮我拆解:\n\n```\n{error}\n```"),
]
# 4. 重构提取(可选默认值 + 引导输出 diff)
@mcp.prompt()
def refactor_extract(code: str, target: str = "函数") -> str:
"""把代码中可复用片段提取成独立单元,输出 before/after diff。"""
return (
f"请重构下面代码:把可复用逻辑提取成独立 {target},命名清晰、单一职责。"
"输出格式:先解释抽取了什么、再给 before/after 代码块。\n\n"
f"```\n{code}\n```"
)
# 5. Release Notes(动态读取 git log)
@mcp.prompt()
def release_notes(since: str = "HEAD~5") -> str:
"""读取指定范围 git log,生成归类后的中文 release notes。"""
try:
log = subprocess.check_output(
["git", "log", "--pretty=%s", since],
stderr=subprocess.DEVNULL,
text=True,
).strip()
except Exception:
log = "(无法读取 git log,请确认在 git 仓库内运行)"
return (
"根据下面提交记录,归类成【新功能】【修复】【重构】【其它】,"
"生成中文 release notes,合并语义重复条目。\n\n"
f"{log}"
)
if __name__ == "__main__":
mcp.run()
目录结构:
prompt-library/
├── server.py # 服务器,含 5 个提示词
├── test_client.py # 客户端验证脚本
├── pyproject.toml # uv 自动生成
└── .python-version
一键运行验证:
uv run python test_client.py # 列清单 + 调用 commit_message
mcp dev server.py # 交互式调试任意提示词
服务器造好了,怎么在日常工具里用上?
Claude Desktop(Prompts 的最佳展示场)——编辑配置文件 claude_desktop_config.json(macOS 在 ~/Library/Application Support/Claude/):
{
"mcpServers": {
"prompt-library": {
"command": "uv",
"args": ["--directory", "/绝对路径/prompt-library", "run", "python", "server.py"]
}
}
}
重启 Claude Desktop 后,提示词会出现在输入框旁的 📎(附件/斜杠)菜单 里,点开看到 code_review、commit_message 等,填参数即注入对话。
Claude Code——用 CLI 注册服务器:
claude mcp add prompt-library -- uv --directory /绝对路径/prompt-library run python server.py
注册后用 claude mcp list 确认在线。在 Claude Code 会话里通过 /mcp 查看状态。提示词原语会被客户端加载,可在支持 prompts 入口的 MCP 客户端中调用;若想稳定地在自动化流程里用,推荐用前面的 test_client.py 脚本直接 prompts/get 渲染好消息再喂给模型。
flowchart TB
subgraph 服务端["server.py(FastMCP)"]
P1[code_review]
P2[commit_message]
P3[debug_error]
P4[refactor_extract]
P5[release_notes]
end
subgraph 客户端["MCP 客户端"]
CD[Claude Desktop<br/>📎 菜单]
CC[Claude Code<br/>claude mcp add]
IN[Inspector<br/>mcp dev]
SC[脚本客户端<br/>test_client.py]
end
CD -->|prompts/list + get| 服务端
CC -->|prompts/list + get| 服务端
IN -->|prompts/list + get| 服务端
SC -->|prompts/list + get| 服务端
服务端 -->|渲染 messages| 客户端
把提示词库 MCP 推给团队前,逐条核对:
code: str 而非裸 code,否则客户端不渲染输入框。release_notes 读 git 失败要返回友好提示,不能抛异常让客户端拿空响应。code_review 而非 codeReview,符合 MCP 惯例,跨客户端兼容。modelcontextprotocol.io/specification/2025-06-18/server/prompts,协议层 prompts/list 与 prompts/get 的权威定义。github.com/modelcontextprotocol/python-sdk,FastMCP 的源码与示例。fastmcp.wiki/en/servers/prompts,@mcp.prompt 装饰器的进阶用法(嵌入资源、多模态消息)。csharp.sdk.modelcontextprotocol.io/concepts/prompts,跨语言对照理解。composio.dev/content/mcp-server-step-by-step-guide-to-building-from-scratch,从零构建的完整流程。适合的训练营类型:
不适合:纯零基础、连 MCP Tool 都没写过的学员应先补 Tool 教程;本教程默认你理解 stdio 传输与 MCP 客户端概念。
难度:⭐⭐⭐(中等偏低),门槛在概念转变(从"工具思维"到"提示词入口思维"),代码量极小。
前置依赖:建议先有任意一篇 Tool/Resource 教程打底。
@mcp.prompt 装饰器用法、参数序列化、多消息与资源嵌入示例。registerPrompt(TS) / @mcp.prompt(Py) 注册提示词模板的分步教程。[!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 等场景全覆盖。
