《MCP Prompts提示系统深度解析:构建智能提示模板库与LLM集成方案》
1 导语
1.1 项目背景
在AI应用开发中,我们经常需要为不同的任务编写特定的提示词。但问题是:这些提示词通常是硬编码在代码里,难以复用、难以管理、难以动态调整。
MCP(Model Context Protocol)的第三大功能 Prompts 解决了这个问题。它允许Server定义和管理提示词模板 ,Client可以根据需要动态获取并参数化这些模板,然后传给LLM。
这次实战我们将深入理解如何构建一个提示词库系统,让提示词成为一等公民。
1.2 项目价值
- ✅ 集中管理提示词:所有提示词模板定义在Server端,便于维护和更新
- ✅ 参数化提示词:同一个模板可以用不同参数生成多种变体,减少重复代码
- ✅ 动态发现机制:Client可以自动发现所有可用的提示模板,无需提前知道
- ✅ LLM一体化:提示词自动转换为LLM格式(messages),无缝集成ChatGPT/DeepSeek等
- ✅ 团队协作:提示词工程师可以在Server端设计模板,应用开发者直接使用
1.3 学习目标
通过本文,你将学会:
- 如何定义和管理提示词模板
- Server端的两个核心处理器:
list_prompts()和get_prompt() - 如何使用PromptArgument实现参数化提示词
- Client端的提示词获取与动态参数收集机制
- 提示词与LLM的无缝集成方案
- 构建代码审查系统的实战架构
2 技术栈清单
| 组件 | 版本 | 用途 |
|---|---|---|
| Python | 3.10+ | 基础语言 |
| MCP | 0.1.0+ | 提示词通信协议 |
| types.Prompt | - | 提示词定义 |
| types.PromptArgument | - | 参数定义 |
| asyncio | 内置 | 异步编程 |
| OpenAI SDK | 1.3.0+ | LLM调用 |
| DeepSeek API | - | 演示LLM(可选) |
环境要求:
- macOS/Linux/Windows均支持
- 需要LLM API密钥(DeepSeek/OpenAI/通义千问)
- 支持交互式命令行输入
3 项目核心原理
3.1 MCP的三大功能对比
| 功能 | 作用 | 访问方式 | 返回值 | 应用场景 |
|---|---|---|---|---|
| Resources | 读取被动数据 | list_resources() / read_resource(uri) |
资源内容 | 文档、配置、文件 |
| Tools | 执行主动操作 | list_tools() / call_tool(name, args) |
操作结果 | 计算、搜索、API调用 |
| Prompts | 提供提示词模板 | list_prompts() / get_prompt(name, args) |
Message列表 | 提示词管理、LLM指引 |
3.2 Prompts的工作原理

3.3 核心概念解析
Prompt对象结构:
Prompt
├─ name: str # 提示模板的唯一标识
├─ description: str # 模板的功能描述
└─ arguments: List[PromptArgument] # 参数列表
└─ PromptArgument
├─ name: str # 参数名
├─ description: str # 参数描述
└─ required: bool # 是否必需
GetPromptResult对象结构:
GetPromptResult
└─ messages: List[PromptMessage]
└─ PromptMessage
├─ role: str # "assistant" 或 "user"
└─ content: TextContent
├─ type: str # "text"
└─ text: str # 具体的提示词文本
4 实战步骤
4.1 环境准备阶段
4.1.1 创建项目结构
bash
# 创建虚拟环境
python -m venv .venv
source .venv/bin/activate # macOS/Linux
# 或 .venv\Scripts\activate # Windows
# 安装依赖
pip install mcp python-dotenv openai -q
4.1.2 配置环境变量
创建 .env 文件:
bash
# .env
DEEPSEEK_API_KEY=your_deepseek_api_key
# 或
OPENAI_API_KEY=your_openai_api_key
# 或
QWEN_API_KEY=your_qwen_api_key
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
4.2 代码实现阶段
4.2.1 Server端:定义提示词模板
这是本案例的核心。通过定义Prompt对象来创建提示词库:
python
# server/server-extension.py
from mcp.server import Server
import mcp.types as types
import asyncio
from mcp.server.stdio import stdio_server
# 【关键】定义提示词模板字典
PROMPTS = {
"code-review": types.Prompt(
name="code-review",
description="分析代码并提供改进建议",
arguments=[
types.PromptArgument(
name="code",
description="需要审查的代码",
required=True
),
types.PromptArgument(
name="language",
description="编程语言",
required=True
),
types.PromptArgument(
name="focus",
description="审查重点(可选:performance, security, readability)",
required=False
)
]
),
"explain-code": types.Prompt(
name="explain-code",
description="解释代码的工作原理",
arguments=[
types.PromptArgument(
name="code",
description="需要解释的代码",
required=True
),
types.PromptArgument(
name="language",
description="编程语言",
required=True
)
]
),
"refactor-code": types.Prompt(
name="refactor-code",
description="重构代码",
arguments=[
types.PromptArgument(
name="code",
description="需要重构的代码",
required=True
),
types.PromptArgument(
name="language",
description="编程语言",
required=True
)
]
)
}
# 初始化服务器
app = Server("code-review-server")
# 【关键】处理器1:列出所有提示模板
@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:
"""
【核心方法】返回可用的提示模板列表
Client 在初始化时调用此方法来发现所有可用模板
返回的 Prompt 对象包含:name、description、arguments
"""
return list(PROMPTS.values())
# 【关键】处理器2:根据参数生成具体的提示词
@app.get_prompt()
async def get_prompt(
name: str, arguments: dict[str, str] | None = None
) -> types.GetPromptResult:
"""
【核心方法】根据模板名和参数生成提示内容
这个方法是提示词参数化的关键:
- 检查模板是否存在
- 根据参数生成具体的提示词
- 返回 GetPromptResult(包含 messages 列表)
"""
if name not in PROMPTS:
raise ValueError(f"提示模板 '{name}' 不存在")
if name == "code-review":
# 【关键】从参数字典中提取值
code = arguments.get("code") if arguments else ""
language = arguments.get("language") if arguments else "Unknown"
focus = arguments.get("focus", "general") if arguments else "general"
# 【关键】根据参数动态生成提示词
return types.GetPromptResult(
messages=[
# 系统指令(assistant角色)
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text=f"你是一个专业的代码审查助手,专注于{language}代码的{focus}方面。"
)
),
# 用户问题(user角色)
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"请审查以下{language}代码,并提供改进建议:\n\n{code}"
)
)
]
)
elif name == "explain-code":
code = arguments.get("code") if arguments else ""
language = arguments.get("language") if arguments else "Unknown"
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text=f"你是一个专业的编程导师,擅长解释{language}代码。"
)
),
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"请解释以下{language}代码的工作原理:\n\n{code}"
)
)
]
)
elif name == "refactor-code":
code = arguments.get("code") if arguments else ""
language = arguments.get("language") if arguments else "Unknown"
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text=f"你是一个专业的编程导师,擅长重构{language}代码。"
)
),
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"请重构以下{language}代码:\n\n{code}"
)
)
]
)
raise ValueError(f"未实现提示模板 '{name}' 的处理逻辑")
async def main():
"""启动MCP Server"""
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())
Server端的3个关键要点:
PROMPTS字典集中管理所有模板@app.list_prompts()暴露模板元数据供Client发现@app.get_prompt()根据参数动态生成提示词
4.2.2 Client端:获取与使用提示词
python
# client/client-extension.py
import sys
import asyncio
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters
from openai import OpenAI
import os
from dotenv import load_dotenv
import mcp.types as types
load_dotenv()
class CodeReviewClient:
def __init__(self):
self.session = None
self.transport = None
# 【关键】初始化LLM客户端
self.client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
self.prompts = None
async def connect(self, server_script: str):
"""
【步骤1】连接到MCP Server并发现提示模板
"""
# 【关键】使用当前Python解释器
params = StdioServerParameters(
command=sys.executable,
args=[server_script],
cwd="../server"
)
# 【关键】建立stdio通信通道
self.transport = stdio_client(params)
self.stdio, self.write = await self.transport.__aenter__()
# 【关键】初始化MCP会话
self.session = await ClientSession(self.stdio, self.write).__aenter__()
await self.session.initialize()
# 【关键】发现所有可用的提示模板
self.prompts = await self.session.list_prompts()
# 兼容不同的返回格式
if not isinstance(self.prompts, dict):
self.prompts = dict(self.prompts)
prompt_list = self.prompts.get("prompts", [])
print("可用提示模板:")
for prompt in prompt_list:
if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
print(f"- {prompt.name}: {prompt.description}")
else:
print(f"- 未知提示模板: {prompt}")
async def use_prompt(self, prompt_name: str, arguments: dict[str, str]):
"""
【步骤2】获取提示词并调用LLM
"""
# 【关键】从Server获取参数化的提示词
prompt_result = await self.session.get_prompt(prompt_name, arguments)
# 【关键】转换为OpenAI API格式
messages = []
for msg in prompt_result.messages:
if isinstance(msg.content, types.TextContent):
messages.append({
"role": msg.role,
"content": msg.content.text
})
# 【关键】调用LLM
response = self.client.chat.completions.create(
model="deepseek-chat",
messages=messages
)
return response.choices[0].message.content
async def close(self):
"""清理资源"""
if self.session:
await self.session.__aexit__(None, None, None)
if self.transport:
await self.transport.__aexit__(None, None, None)
async def main():
print(">>> 开始初始化代码审查系统")
if len(sys.argv) < 2:
print("用法: python client-extension.py <server.py 路径>")
return
client = CodeReviewClient()
try:
await client.connect(sys.argv[1])
print(">>> 系统连接成功")
# 获取提示模板列表
prompt_list = client.prompts.get("prompts", [])
# 示例代码
sample_code = """
def calculate_fibonacci(n):
if n <= 0:
return []
elif n == 1:
return [0]
fib = [0, 1]
for i in range(2, n):
fib.append(fib[i-1] + fib[i-2])
return fib
"""
while True:
print("\n请选择提示模板:")
for i, prompt in enumerate(prompt_list):
if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
print(f"{i+1}. {prompt.name}: {prompt.description}")
else:
print(f"{i+1}. 未知提示模板: {prompt}")
print(f"{len(prompt_list)+1}. 退出")
choice = input("> ")
if choice == str(len(prompt_list)+1):
break
try:
prompt_index = int(choice) - 1
if 0 <= prompt_index < len(prompt_list):
selected_prompt = prompt_list[prompt_index]
prompt_name = getattr(selected_prompt, 'name', None)
if not prompt_name:
print("无效的提示模板")
continue
# 【关键】动态收集参数
arguments = {}
if hasattr(selected_prompt, 'arguments'):
for arg in selected_prompt.arguments:
arg_name = getattr(arg, 'name', '')
arg_desc = getattr(arg, 'description', '')
arg_required = getattr(arg, 'required', False)
# 为常用参数提供默认值
if arg_name == "code":
arguments[arg_name] = sample_code
print(f"使用示例代码作为 {arg_name}")
elif arg_name == "language":
arguments[arg_name] = "Python"
print(f"使用 Python 作为 {arg_name}")
elif arg_name == "focus" and not arg_required:
print(f"\n请选择 {arg_desc}(可选):")
print("1. performance (性能)")
print("2. security (安全性)")
print("3. readability (可读性)")
print("4. general (综合)")
focus_choice = input("> ")
focus_map = {
"1": "performance",
"2": "security",
"3": "readability",
"4": "general"
}
arguments[arg_name] = focus_map.get(focus_choice, "general")
else:
# 其他参数需要用户输入
required_text = "(必需)" if arg_required else "(可选)"
user_input = input(f"请输入 {arg_desc} {required_text}: ")
if user_input.strip() or arg_required:
arguments[arg_name] = user_input.strip()
print(f"\n正在调用 {prompt_name}...")
response = await client.use_prompt(prompt_name, arguments)
print(f"\n结果:\n{response}")
else:
print("无效的选择")
except ValueError:
print("请输入有效的数字")
except Exception as e:
print(f"发生错误: {e}")
except Exception as e:
print(f"发生错误: {e}")
finally:
await client.close()
print(">>> 系统已关闭")
if __name__ == "__main__":
asyncio.run(main())
Client端的工作流程:
list_prompts()- 发现所有可用模板- 用户选择模板 - 交互式菜单
- 动态收集参数 - 根据PromptArgument的元数据
get_prompt()- 获取参数化的提示词- 调用LLM - 使用获取的messages
4.2.3 功能测试阶段
bash
# 终端1:启动Server
cd server
python server-extension.py
# 终端2:启动Client
cd client
python client-extension.py ../server/server-extension.py
交互演示:
>>> 开始初始化代码审查系统
>>> 系统连接成功
请选择提示模板:
1. code-review: 分析代码并提供改进建议
2. explain-code: 解释代码的工作原理
3. refactor-code: 重构代码
4. 退出
> 1
使用示例代码作为 code
请选择 审查重点(可选):
1. performance (性能)
2. security (安全性)
3. readability (可读性)
4. general (综合)
> 1
正在调用 code-review...
结果:
【代码审查结果】
您的 Python 代码在整体结构上是合理的,但从性能角度有以下建议...
5 Server端提示词定义的深度讲解
5.1 Prompt对象的三层结构
5.1.1 第一层:提示词基础信息
python
types.Prompt(
name="code-review", # 【关键】唯一标识,用于get_prompt()调用
description="分析代码并提供改进建议", # 【关键】功能描述,显示给用户
arguments=[...] # 参数列表(详见下层)
)
5.1.2 第二层:参数定义
python
types.PromptArgument(
name="code", # 参数名(对应get_prompt()中arguments的key)
description="需要审查的代码", # 参数描述(告知用户这个参数的含义)
required=True # 是否必需(Client根据此字段决定是否提示用户输入)
)
参数定义的5个设计原则:
- 名称明确 - 参数名要清楚表达用途(code、language、focus)
- 描述完整 - description要说明参数的含义和预期格式
- 必需标记 - required字段明确哪些参数是可选的
- 类型约束 - 虽然MCP中参数统一是string,但描述中可以注明格式
- 默认值处理 - get_prompt()中使用
arguments.get("key", "default")处理可选参数
5.1.3 第三层:Message生成
python
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="assistant", # 【关键】系统指令角色
content=types.TextContent(
type="text",
text=f"你是一个专业的代码审查助手,专注于{language}代码的{focus}方面。"
)
),
types.PromptMessage(
role="user", # 【关键】用户问题角色
content=types.TextContent(
type="text",
text=f"请审查以下{language}代码,并提供改进建议:\n\n{code}"
)
)
]
)
Messages设计原则:
- role顺序 - 通常先
assistant(系统指令),后user(用户问题) - 参数插值 - 根据arguments中的值动态生成提示词
- 文本格式 - 使用清晰的结构和换行符提高LLM理解度
- 可扩展性 - 支持多个assistant/user对话对
5.2 最佳实践:提示词库的设计模式
5.2.1 模式1:固定模板库(当前案例)
python
PROMPTS = {
"code-review": types.Prompt(...),
"explain-code": types.Prompt(...),
"refactor-code": types.Prompt(...)
}
@app.list_prompts()
async def list_prompts():
return list(PROMPTS.values())
优点:
- ✅ 实现简单,易于理解
- ✅ 性能最高(直接返回内存中的字典)
- ✅ 适合中小型应用
缺点:
- ❌ 模板数量固定,不能动态添加
- ❌ 修改需要重启Server
5.2.2 模式2:数据库驱动库(扩展方案)
python
class PromptManager:
def __init__(self, db_path):
self.db = sqlite3.connect(db_path)
async def list_prompts(self):
cursor = self.db.execute("SELECT * FROM prompts")
return [self._row_to_prompt(row) for row in cursor]
async def get_prompt(self, name, arguments):
# 从数据库获取模板
template = self.db.execute(
"SELECT template FROM prompts WHERE name = ?", (name,)
).fetchone()
# 参数替换
prompt_text = template.format(**arguments)
return types.GetPromptResult(messages=[...])
@app.list_prompts()
async def list_prompts():
return await manager.list_prompts()
@app.get_prompt()
async def get_prompt(name, arguments):
return await manager.get_prompt(name, arguments)
优点:
- ✅ 支持动态模板管理
- ✅ 可以运行时添加/修改模板
- ✅ 适合企业级应用
5.2.3 模式3:分布式模板库(高级方案)
python
class DistributedPromptManager:
def __init__(self, redis_client):
self.redis = redis_client
async def list_prompts(self):
# 从Redis获取模板列表
template_names = self.redis.keys("prompt:*")
prompts = []
for name in template_names:
data = self.redis.hgetall(name)
prompts.append(self._deserialize_prompt(data))
return prompts
async def get_prompt(self, name, arguments):
# 从Redis获取模板并处理参数
template_data = self.redis.hgetall(f"prompt:{name}")
# ...处理逻辑
优点:
- ✅ 支持多个Server共享模板
- ✅ 支持版本控制和灰度发布
- ✅ 生产级别的高可用性
5.3 参数化提示词的5个技巧
5.3.1 技巧1:参数验证
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
if name not in PROMPTS:
raise ValueError(f"提示模板 '{name}' 不存在")
# 【关键】验证必需参数
if arguments:
required_params = {
arg.name for arg in PROMPTS[name].arguments
if arg.required
}
missing = required_params - set(arguments.keys())
if missing:
raise ValueError(f"缺少必需参数: {missing}")
5.3.2 技巧2:参数类型转换
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
# MCP中所有参数都是字符串
# 需要根据需要进行类型转换
if name == "data-analysis":
try:
data = json.loads(arguments.get("data", "[]")) # 转为list
limit = int(arguments.get("limit", "10")) # 转为int
except (json.JSONDecodeError, ValueError) as e:
raise ValueError(f"参数类型错误: {e}")
5.3.3 技巧3:条件逻辑
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
if name == "code-review":
focus = arguments.get("focus", "general")
# 【关键】根据focus值生成不同的系统指令
focus_prompts = {
"performance": "专注于时间复杂度、空间复杂度和优化机会",
"security": "专注于安全漏洞、输入验证和数据保护",
"readability": "专注于代码清晰度、命名约定和代码组织"
}
system_prompt = focus_prompts.get(focus, "进行综合代码审查")
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text=f"你是一个专业的代码审查助手。{system_prompt}"
)
),
# ...其他messages
]
)
5.3.4 技巧4:多轮对话支持
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
if name == "interactive-debug":
# 【关键】支持多轮对话
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="system",
content=types.TextContent(type="text", text="你是代码调试专家")
),
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"问题描述:{arguments.get('problem')}"
)
),
# 【可选】添加之前的对话历史
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text="我理解了,这个问题通常由... 引起"
)
),
types.PromptMessage(
role="user",
content=types.TextContent(
type="text",
text=f"后续问题:{arguments.get('follow_up')}"
)
)
]
)
5.3.5 技巧5:模板组合复用
python
class PromptTemplateLibrary:
"""可复用的模板片段库"""
@staticmethod
def get_role(role_name: str) -> str:
roles = {
"code_reviewer": "你是一个资深的代码审查工程师,有10年以上经验。",
"architect": "你是一个系统架构师,思考问题的层次很高。",
"teacher": "你是一个耐心的编程导师,善于用例子解释概念。"
}
return roles.get(role_name, "你是一个有经验的开发者。")
@staticmethod
def get_instruction(instruction_name: str, **kwargs) -> str:
instructions = {
"code_quality": "评估代码质量,包括以下方面:\n- 性能\n- 安全性\n- 可读性\n- 可维护性",
"performance_analysis": "分析性能瓶颈,提供优化建议:\n- 时间复杂度:{time_complexity}\n- 空间复杂度:{space_complexity}"
}
template = instructions.get(instruction_name, "")
return template.format(**kwargs)
# 在 get_prompt() 中使用
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
if name == "code-review":
role = PromptTemplateLibrary.get_role("code_reviewer")
instruction = PromptTemplateLibrary.get_instruction("code_quality")
return types.GetPromptResult(
messages=[
types.PromptMessage(
role="assistant",
content=types.TextContent(
type="text",
text=f"{role}\n\n{instruction}"
)
),
# ...user message
]
)
6 Client端提示词获取的核心机制
6.1 list_prompts()的工作流程
┌─ Client初始化
│
├─ 调用 session.list_prompts()
│
├─ Server调用 @app.list_prompts() 处理器
│
├─ Server返回 List[types.Prompt]
│
├─ Client接收并解析
│
├─ 提取每个Prompt的元数据:
│ ├─ name: 用于显示和后续get_prompt()调用
│ ├─ description: 用于在UI中显示功能说明
│ └─ arguments: 用于动态参数收集
│
└─ 返回给应用程序使用
关键代码片段:
python
# 步骤1:获取提示模板列表
self.prompts = await self.session.list_prompts()
# 步骤2:处理返回格式兼容性
if not isinstance(self.prompts, dict):
self.prompts = dict(self.prompts)
# 步骤3:提取prompts列表
prompt_list = self.prompts.get("prompts", [])
# 步骤4:遍历并显示
for i, prompt in enumerate(prompt_list):
if hasattr(prompt, 'name') and hasattr(prompt, 'description'):
print(f"{i+1}. {prompt.name}: {prompt.description}")
6.2 get_prompt()的参数动态收集机制
python
# 【关键】根据PromptArgument的元数据动态收集参数
arguments = {}
if hasattr(selected_prompt, 'arguments'):
for arg in selected_prompt.arguments:
arg_name = getattr(arg, 'name', '')
arg_desc = getattr(arg, 'description', '')
arg_required = getattr(arg, 'required', False)
# 策略1:为常用参数提供默认值
if arg_name == "code":
arguments[arg_name] = sample_code
print(f"使用示例代码作为 {arg_name}")
# 策略2:为特殊参数提供选择菜单
elif arg_name == "focus" and not arg_required:
print(f"\n请选择 {arg_desc}(可选):")
print("1. performance (性能)")
print("2. security (安全性)")
print("3. readability (可读性)")
print("4. general (综合)")
focus_choice = input("> ")
focus_map = {
"1": "performance",
"2": "security",
"3": "readability",
"4": "general"
}
arguments[arg_name] = focus_map.get(focus_choice, "general")
# 策略3:对于其他参数询问用户输入
else:
required_text = "(必需)" if arg_required else "(可选)"
user_input = input(f"请输入 {arg_desc} {required_text}: ")
if user_input.strip() or arg_required:
arguments[arg_name] = user_input.strip()
# 步骤5:调用get_prompt()获取提示词
prompt_result = await self.session.get_prompt(prompt_name, arguments)
参数收集的3个策略:
- 默认值 - 对于常见参数(code、language)提供示例值
- 枚举选择 - 对于有限值的参数提供菜单
- 用户输入 - 对于开放式参数要求用户输入
6.3 Message转换与LLM集成
python
async def use_prompt(self, prompt_name: str, arguments: dict[str, str]):
"""
【核心方法】获取提示词并调用LLM的完整流程
"""
# 【步骤1】从Server获取参数化的提示词
prompt_result = await self.session.get_prompt(prompt_name, arguments)
# 【步骤2】转换为OpenAI API格式
messages = []
for msg in prompt_result.messages:
if isinstance(msg.content, types.TextContent):
messages.append({
"role": msg.role,
"content": msg.content.text
})
# 转换后的格式如下:
# messages = [
# {"role": "assistant", "content": "你是一个专业的..."},
# {"role": "user", "content": "请审查以下..."}
# ]
# 【步骤3】调用LLM(支持任何OpenAI兼容的API)
response = self.client.chat.completions.create(
model="deepseek-chat", # 支持deepseek-chat / gpt-4 / qwen-plus等
messages=messages,
temperature=0.7, # 控制创意度
max_tokens=2000 # 控制输出长度
)
# 【步骤4】提取结果
return response.choices[0].message.content
7 效果验证
7.1 提示模板发现验证
运行后的实际输出(已验证):
>>> 开始初始化代码审查系统
>>> 系统连接成功
可用提示模板:
- code-review: 分析代码并提供改进建议
- explain-code: 解释代码的工作原理
- refactor-code: 重构代码
验证点 :
✅ Server正确定义了3个提示模板
✅ 模板的name和description被正确传递
✅ Client能够自动发现所有模板
7.2 参数化提示词生成验证
请选择提示模板:
1. code-review: 分析代码并提供改进建议
2. explain-code: 解释代码的工作原理
3. refactor-code: 重构代码
4. 退出
> 1
使用示例代码作为 code
使用 Python 作为 language
请选择 审查重点(可选):
1. performance (性能)
2. security (安全性)
3. readability (可读性)
4. general (综合)
> 1
正在调用 code-review...
验证点 :
✅ PromptArgument元数据被正确解析
✅ Client根据required标记判断是否需要用户输入
✅ 参数被正确收集并传递给Server
7.3 LLM调用结果验证
结果:
【代码审查结果 - 性能优化建议】
您的 Python Fibonacci 实现在整体结构上是合理的,但从性能角度有以下几点建议:
1. 使用列表缓存而不是递归
- 当前实现:O(n) 时间,O(n) 空间 ✓
- 优化点:可以使用生成器进一步优化空间
2. 边界条件处理
- 当前代码正确处理了 n <= 0 和 n == 1
- 建议:添加输入类型检查
3. 算法选择
- 推荐使用 itertools.islice 实现更pythonic的方式
代码示例:
验证点 :
✅ 提示词被正确转换为OpenAI格式
✅ LLM接收到的是参数化后的具体提示词(包含"性能"focus)
✅ LLM根据提示词生成了符合要求的回复
8 踩坑记录
8.1 踩坑1:忘记处理参数为None的情况
错误现象:
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
code = arguments.get("code") # ❌ 如果arguments是None会抛出AttributeError
language = arguments["language"] # ❌ KeyError
根因分析:
- Client可能不提供任何参数
- arguments参数默认值是None
- 直接调用.get()或[]会报错
解决方案:
python
@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None):
# 【关键】使用条件表达式处理None
code = arguments.get("code") if arguments else ""
language = arguments.get("language") if arguments else "Unknown"
focus = arguments.get("focus", "general") if arguments else "general"
【亲测有效】这样可以安全地处理所有情况!
8.2 踩坑2:Message中的TextContent格式错误
错误现象:
python
types.PromptMessage(
role="user",
content="直接传字符串" # ❌ content必须是TextContent对象
)
# 错误: PromptMessage expects 'content' field of type TextContent
根因分析:
- MCP协议规定message的content必须是TextContent对象
- 不能直接传字符串
- TextContent对象也必须指定type="text"
解决方案:
python
types.PromptMessage(
role="user",
content=types.TextContent(
type="text", # 【关键】必须指定type
text="实际的提示词文本" # 【关键】用text字段存储内容
)
)
【划重点】Message的结构层次很重要!
8.3 踩坑3:Client端的返回格式兼容性问题
错误现象:
python
self.prompts = await self.session.list_prompts()
prompt_list = self.prompts.get("prompts", []) # ❌ 假设self.prompts是dict
# 错误: 'ListPromptsResult' object has no attribute 'get'
根因分析:
- list_prompts()返回的是ListPromptsResult对象,不是dict
- 不同MCP版本的返回格式可能不同
- 直接调用dict方法会失败
解决方案:
python
self.prompts = await self.session.list_prompts()
# 【关键】检查返回类型并进行兼容处理
if not isinstance(self.prompts, dict):
self.prompts = dict(self.prompts)
# 【关键】使用hasattr检查属性存在
prompt_list = []
if hasattr(self.prompts, 'prompts'):
prompt_list = self.prompts.prompts
elif isinstance(self.prompts, dict):
prompt_list = self.prompts.get("prompts", [])
【亲测有效】这样可以处理多个MCP版本的差异!
9 总结与扩展
9.1 本文核心收获
| 知识点 | 掌握度 |
|---|---|
| Prompt对象的三层结构(基础信息→参数定义→Message生成) | ⭐⭐⭐⭐⭐ |
| list_prompts()的模板发现机制 | ⭐⭐⭐⭐⭐ |
| get_prompt()的参数化提示词生成 | ⭐⭐⭐⭐⭐ |
| 提示词库设计模式(固定→数据库→分布式) | ⭐⭐⭐⭐ |
| 参数动态收集的3个策略(默认→菜单→输入) | ⭐⭐⭐⭐ |
| Message转换与LLM无缝集成 | ⭐⭐⭐⭐ |
| MCP三大功能的完整理解(Resources→Tools→Prompts) | ⭐⭐⭐⭐⭐ |
9.2 技能升级路线
初级 → 定义固定提示模板 (本文完成)
↓
中级 → 参数化提示词生成 (本文完成)
↓
高级 → 动态模板库管理 (数据库驱动)
↓
专家 → 分布式提示词系统 (Redis+版本控制)
↓
架构 → 完整的AI应用平台 (集成Resources+Tools+Prompts)
9.3 实战拓展方向
9.3.1 集成向量化搜索选择最合适的提示词
python
# 基于语义相似度自动选择提示模板
from sentence_transformers import SentenceTransformer
class SmartPromptSelector:
def __init__(self):
self.model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
# 预先计算所有提示词的描述的embedding
self.prompt_embeddings = {}
for name, prompt in PROMPTS.items():
embedding = self.model.encode(prompt.description)
self.prompt_embeddings[name] = embedding
async def select_prompt_by_input(self, user_input: str):
"""根据用户输入自动选择最合适的提示词"""
input_embedding = self.model.encode(user_input)
# 计算余弦相似度
similarities = {}
for name, prompt_embedding in self.prompt_embeddings.items():
similarity = cosine_similarity([input_embedding], [prompt_embedding])[0][0]
similarities[name] = similarity
# 返回最相似的提示词
best_match = max(similarities.items(), key=lambda x: x[1])
return best_match[0], best_match[1]
# 使用方式
selector = SmartPromptSelector()
prompt_name, confidence = await selector.select_prompt_by_input("帮我分析这段代码的性能问题")
# 返回: ("code-review", 0.89)
9.3.2 构建提示词版本控制与AB测试系统
python
class VersionedPromptManager:
"""支持多版本提示词的管理器"""
def __init__(self):
self.prompts = {
"code-review": {
"v1": types.Prompt(...), # 原始版本
"v2": types.Prompt(...), # 优化版本
"v3": types.Prompt(...) # 最新版本
}
}
self.active_versions = {
"code-review": "v3" # 当前活跃版本
}
self.ab_test_config = {
"code-review": {
"v2": 0.1, # 10% 用户使用v2
"v3": 0.9 # 90% 用户使用v3
}
}
async def get_prompt(self, name: str, arguments, user_id: str):
"""根据AB测试配置选择版本"""
import random
# 根据user_id和AB配置选择版本
rand = random.random()
version = None
cumulative = 0
for v, ratio in self.ab_test_config[name].items():
cumulative += ratio
if rand <= cumulative:
version = v
break
# 获取对应版本的提示词
prompt = self.prompts[name][version]
return await self._generate_prompt_result(prompt, arguments)
9.3.3 添加提示词评分与反馈机制
python
class PromptFeedbackSystem:
"""收集用户对提示词质量的反馈"""
def __init__(self):
self.feedback_db = []
async def record_feedback(self,
prompt_name: str,
user_id: str,
score: int, # 1-5分
comment: str = ""):
"""记录用户对提示词的评分"""
feedback = {
"prompt_name": prompt_name,
"user_id": user_id,
"score": score,
"comment": comment,
"timestamp": datetime.now()
}
self.feedback_db.append(feedback)
# 定期分析反馈,自动调整提示词
await self._analyze_and_optimize()
async def _analyze_and_optimize(self):
"""分析反馈数据并自动优化提示词"""
# 计算每个提示词的平均分
scores = defaultdict(list)
for feedback in self.feedback_db:
scores[feedback['prompt_name']].append(feedback['score'])
# 如果某个提示词分数低于3分,发出警告
for name, score_list in scores.items():
avg_score = sum(score_list) / len(score_list)
if avg_score < 3:
print(f"⚠️ 提示词 '{name}' 得分过低: {avg_score:.2f}")
print(f"📊 样本数: {len(score_list)}")
# 可以触发自动优化流程
await self._trigger_optimization(name)
9.4 常见问题解答
Q1: 一个提示词模板最多能有多少个参数?
A: 理论上没有限制,但建议不超过5个。参数太多会导致:
- 用户输入的交互复杂度增加
- 提示词的控制粒度太细
- 参数之间可能产生冲突
Q2: 能否在运行时动态添加新的提示词?
A: 可以,使用数据库驱动的模式。每次调用list_prompts()时从数据库查询,这样新添加的模板会自动出现。
Q3: 如何处理某些参数是可选的情况?
A:
- 在PromptArgument中设置
required=False - 在
get_prompt()中使用arguments.get("key", "default_value") - Client会根据
required标记决定是否提示用户
Q4: 提示词支持图片等多媒体内容吗?
A: 当前MCP的PromptMessage主要支持文本(TextContent)。如果需要支持图片,可以:
- 传递图片URL
- 或将图片base64编码后作为文本传递
- 等待MCP协议的扩展
Q5: 如何测试提示词的质量?
A:
- 定义清晰的评估指标(如LLM输出的准确率)
- 使用AB测试框架对比不同版本
- 收集用户反馈并自动分析
- 持续迭代优化提示词
10 附录:完整对比表
MCP三大功能完整对比
| 维度 | Resources | Tools | Prompts |
|---|---|---|---|
| 概念 | 数据对象 | 可执行操作 | 提示词模板 |
| 类比 | 图书 | 服务台 | 推荐书单 |
| 发现方法 | list_resources() | list_tools() | list_prompts() |
| 调用方法 | read_resource(uri) | call_tool(name) | get_prompt(name) |
| 返回值 | 资源内容(str) | 操作结果(str) | Message列表 |
| 参数化 | URI | arguments dict | arguments dict |
| 应用场景 | 文档、配置、文件 | 计算、搜索、API | 提示词、指引 |
| LLM集成 | 间接 | 直接 | 最直接 |
| 复杂度 | 低 | 中 | 低 |
Prompts vs Tools vs Resources的选择表
| 场景 | 选择 | 原因 |
|---|---|---|
| 提供代码审查提示词 | Prompts | 无需执行操作,只需提供文本指引 |
| 调用代码分析服务 | Tools | 需要主动执行分析操作 |
| 读取文档内容 | Resources | 被动提供已有数据 |
| 提供多种对话模板 | Prompts | 不同的对话场景需要不同的提示词 |
| 执行数据转换 | Tools | 需要主动处理和返回结果 |
| 分享配置文件 | Resources | 被动提供配置数据 |
文章完成时间 : 2025年1月
总字数 : 1200+行
涵盖知识点: 9大模块 + 3个踩坑 + 3个扩展方向 + 完整对比表