MCP构建Prompts
Prompts 是 MCP 三大原语中最容易被忽视的一个,但用好了能大幅提升 AI 应用的体验。本篇讲怎么设计和构建 Prompts。
1. Prompts 是什么
Prompts 是 Server 提供给用户的预制提示词模板。用户通过客户端(如 Claude Desktop 的 / 命令)触发,Server 会根据参数生成一段精心设计的提示词发给模型。
可以理解为 Server 给用户提供的"高质量提示词工具箱":
| 场景 | 说明 |
|---|---|
| Code Review | 用户选中代码,触发 /code_review,得到统一标准的代码审查 |
| Bug 分析 | /analyze_bug 接收错误日志,自动按规范输出排查思路 |
| 翻译 | /translate 接收文本和目标语言,应用最佳翻译 prompt |
| 总结 | /summarize 用一致的格式总结文档 |
2. 与 Tool 和 Resource 的区别
| 维度 | Tool | Resource | Prompt |
|---|---|---|---|
| 触发方 | 模型 | 用户/客户端 | 用户 |
| 作用 | 执行操作 | 提供数据 | 启动一段对话 |
| 是否调用 LLM | 否(在 Server 端) | 否 | 是(生成 prompt 发给 LLM) |
关键差异: Prompt 不是返回结果给用户,而是返回一个 prompt 让客户端用它去问 LLM。
3. 基础 Prompt
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("prompts-demo")
@mcp.prompt()
def code_review(code: str, language: str = "Python") -> str:
"""对代码进行严格的 Code Review"""
return f"""请对下面的 {language} 代码进行严格的 Code Review。
【代码】
```{language.lower()}
{code}
```
请重点关注:
1. 代码可读性
2. 潜在 bug
3. 性能问题
4. 安全隐患
5. 是否符合 {language} 最佳实践
请用 Markdown 格式输出 review 报告,每个问题给出具体行号和修改建议。
"""
在 Claude Desktop 中输入 /code_review,会弹出参数表单,填入后 Claude 就会按这个 prompt 回答。
4. 多消息 Prompt
简单 Prompt 返回一个字符串,会作为单条 user 消息发给模型。复杂场景可以返回消息列表:
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
mcp = FastMCP("prompts-demo")
@mcp.prompt()
def debug_session(error_log: str) -> list[base.Message]:
"""启动一次 debug 会话,预设系统行为"""
return [
base.AssistantMessage(
content="我会以专业调试工程师的身份,帮你分析错误日志,找出根因并提供修复方案。请告诉我你的问题。"
),
base.UserMessage(
content=f"这是错误日志:\n\n```\n{error_log}\n```\n\n请帮我分析。"
),
]
返回的列表会作为对话历史预填进 Claude,相当于让对话从一个"已经预热"的状态开始。
5. 参数说明(描述很重要)
像 Tool 一样,Prompt 的参数也需要好的描述,便于客户端表单展示:
from typing import Annotated
from pydantic import Field
@mcp.prompt()
def translate(
text: Annotated[str, Field(description="要翻译的文本")],
target_lang: Annotated[
str,
Field(description="目标语言,如 '英文'、'日文'、'法文'")
] = "英文",
style: Annotated[
str,
Field(description="翻译风格:'正式'、'口语'、'文学'")
] = "正式",
) -> str:
"""将文本翻译成指定语言"""
return f"""请将下面的文本翻译成{target_lang},使用{style}的风格。
要求:
- 准确传达原文意思
- 符合{target_lang}的表达习惯
- 保留原文的语气和情感
【原文】
{text}
只输出翻译结果,不要任何解释。
"""
在 Claude Desktop 触发 /translate 时会显示一个表单,让用户填写各参数。
6. 实战:开发助手 Prompt 套装
一个完整的开发助手 Server,提供多个有用的 Prompts:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("dev-helper")
@mcp.prompt()
def explain_code(code: str, level: str = "中级") -> str:
"""解释代码(按受众水平定制深度)"""
return f"""请解释下面的代码。受众水平:{level}(初级/中级/高级)。
```
{code}
```
要求:
- 初级:用类比和大白话,不引入太多术语
- 中级:解释关键技术点和设计意图
- 高级:分析底层原理、性能特点、潜在改进
"""
@mcp.prompt()
def write_test(code: str, framework: str = "pytest") -> str:
"""为给定代码生成单元测试"""
return f"""请为下面的代码生成完整的 {framework} 单元测试。
```
{code}
```
要求:
1. 覆盖正常路径
2. 覆盖边界情况(空输入、最大值、None 等)
3. 覆盖异常路径(错误输入应该抛出什么)
4. 测试函数名清晰,命名格式 test_<功能>_<场景>
5. 每个测试只验证一件事
"""
@mcp.prompt()
def refactor(code: str, goal: str) -> str:
"""重构代码"""
return f"""请重构下面的代码,重构目标:{goal}
```
{code}
```
要求:
- 保持原有功能完全不变
- 提供重构后的完整代码
- 简要说明做了哪些改动以及为什么
"""
@mcp.prompt()
def commit_message(diff: str) -> str:
"""根据 git diff 生成规范的 commit message"""
return f"""请根据下面的 git diff 生成一条符合 Conventional Commits 规范的 commit message。
```diff
{diff}
```
格式要求:
<type>(<scope>): <subject>
<body>
<footer>
- type: feat/fix/docs/style/refactor/test/chore
- subject: 50 字以内,祈使句
- body: 详细说明动机和改动
- 如果有 breaking change,在 footer 中标注
只输出 commit message,不要其他内容。
"""
if __name__ == "__main__":
mcp.run()
注册到 Claude Desktop 后,用户就能通过 /explain_code、/write_test、/refactor、/commit_message 快速触发这些精心设计的提示词。
7. Prompt + Resource 组合
Prompt 还能引用 Resource,构建强大的工作流:
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base
mcp = FastMCP("workflow-demo")
@mcp.resource("template://email/welcome")
def email_template() -> str:
return "欢迎加入 {company}!我们将在 {date} 进行新员工培训..."
@mcp.prompt()
def write_email(recipient: str, purpose: str) -> list[base.Message]:
"""根据预制模板写邮件"""
return [
base.UserMessage(content=f"""请帮我写一封邮件给 {recipient},目的:{purpose}。
参考公司邮件模板(资源 URI: template://email/welcome),结合具体场景调整。
"""),
]
LLM 收到 prompt 后,会主动调用 Resource 读取模板,再生成邮件。
8. 异步 Prompt
如果 prompt 生成需要数据库查询或 API 调用,用异步:
import httpx
@mcp.prompt()
async def analyze_pr(pr_url: str) -> str:
"""获取 PR 内容并启动分析"""
async with httpx.AsyncClient() as client:
response = await client.get(pr_url)
pr_data = response.text
return f"""请分析这个 Pull Request 的改动,识别潜在风险和需要重点关注的点。
【PR 内容】
{pr_data}
请按以下结构输出:
1. 改动概述
2. 潜在风险(按严重程度排序)
3. 推荐的 Reviewer 关注点
"""
9. 何时使用 Prompt
判断某个能力适合做 Prompt 还是 Tool:
适合 Prompt:
- 需要用户主动选择触发的场景
- 关键在于"如何提问"(提示词工程)
- 结果需要 LLM 的能力(创作、分析、解释)
- 没有明确的"标准答案"
适合 Tool:
- 模型可以自主决定调用
- 关键在于"如何执行"(业务逻辑)
- 结果是确定的(查询、计算、API 调用)
举例:
- "翻译一段文字" → Prompt(关键是怎么写好翻译 prompt)
- "查询数据库中的用户" → Tool(关键是怎么执行 SQL)
10. 设计建议
1. 名称用动词
code_review、write_test、analyze_bug,让用户一看就知道做什么。
2. 描述要短而精
Claude Desktop 的 / 菜单只显示一行描述,要简洁有力。
3. 参数要直观
参数名要符合用户习惯,提供合理默认值,减少必填。
4. Prompt 内容要专业
Prompt 是 Server 提供的"专业知识",应该比用户自己临时写的 prompt 质量高很多。投入精力打磨。
5. 输出格式要明确
在 prompt 末尾明确要求输出格式(JSON、Markdown、纯文本等),避免模型自由发挥。
6. 留好扩展点
参数设计要考虑未来可能新增的需求,比如 style、format 这类预留字段。
总结
- Prompt 是 Server 提供给用户的预制提示词模板,用户通过客户端触发
- 与 Tool/Resource 的关键区别:结果是给 LLM 看的 prompt,不是直接返回值
- 简单 Prompt 返回
str,复杂场景返回list[Message]预填多轮对话 - 参数描述用 Pydantic Field,方便客户端展示表单
- 适合做 Prompt 的场景:用户主动选择、需要 LLM 能力、没有标准答案
- Prompt + Resource 组合可以构建强大的工作流