大语言模型实战(十四)——MCP Prompts提示系统深度解析:构建智能提示模板库与LLM集成方案

《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 学习目标

通过本文,你将学会:

  1. 如何定义和管理提示词模板
  2. Server端的两个核心处理器:list_prompts()get_prompt()
  3. 如何使用PromptArgument实现参数化提示词
  4. Client端的提示词获取与动态参数收集机制
  5. 提示词与LLM的无缝集成方案
  6. 构建代码审查系统的实战架构

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个关键要点

  1. PROMPTS字典集中管理所有模板
  2. @app.list_prompts()暴露模板元数据供Client发现
  3. @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端的工作流程

  1. list_prompts() - 发现所有可用模板
  2. 用户选择模板 - 交互式菜单
  3. 动态收集参数 - 根据PromptArgument的元数据
  4. get_prompt() - 获取参数化的提示词
  5. 调用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个设计原则

  1. 名称明确 - 参数名要清楚表达用途(code、language、focus)
  2. 描述完整 - description要说明参数的含义和预期格式
  3. 必需标记 - required字段明确哪些参数是可选的
  4. 类型约束 - 虽然MCP中参数统一是string,但描述中可以注明格式
  5. 默认值处理 - 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设计原则

  1. role顺序 - 通常先 assistant(系统指令),后 user(用户问题)
  2. 参数插值 - 根据arguments中的值动态生成提示词
  3. 文本格式 - 使用清晰的结构和换行符提高LLM理解度
  4. 可扩展性 - 支持多个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个策略

  1. 默认值 - 对于常见参数(code、language)提供示例值
  2. 枚举选择 - 对于有限值的参数提供菜单
  3. 用户输入 - 对于开放式参数要求用户输入

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:

  1. 在PromptArgument中设置required=False
  2. get_prompt()中使用arguments.get("key", "default_value")
  3. Client会根据required标记决定是否提示用户

Q4: 提示词支持图片等多媒体内容吗?

A: 当前MCP的PromptMessage主要支持文本(TextContent)。如果需要支持图片,可以:

  1. 传递图片URL
  2. 或将图片base64编码后作为文本传递
  3. 等待MCP协议的扩展

Q5: 如何测试提示词的质量?

A:

  1. 定义清晰的评估指标(如LLM输出的准确率)
  2. 使用AB测试框架对比不同版本
  3. 收集用户反馈并自动分析
  4. 持续迭代优化提示词

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个扩展方向 + 完整对比表

相关推荐
安达发公司8 小时前
安达发|石油化工行业自动排产软件:驱动产业升级的核心引擎
大数据·人工智能·aps高级排程·aps排程软件·安达发aps·自动排产软件
openFuyao9 小时前
参与openFuyao嘉年华,体验开源开发流程,领视频年卡会员
人工智能·云原生·开源·开源软件·多样化算力
摸鱼仙人~9 小时前
跨文化范式迁移与数字经济重构:借鉴日本IP工业化经验构建中国特色现代文化产业体系深度研究报告
大数据·人工智能
皮肤科大白9 小时前
图像处理的 Python库
图像处理·人工智能·python
摸鱼仙人~9 小时前
中国内需市场的战略重构与潜在增长点深度研究报告
大数据·人工智能
一招定胜负9 小时前
自然语言处理CBOW模型:基于上下文预测中间词
人工智能·深度学习·机器学习
jimmyleeee9 小时前
人工智能基础知识笔记三十二:向量数据库的查找类型和工作原理
人工智能·笔记
像风一样自由20209 小时前
MCP 入门指南:让 AI 连接真实世界
人工智能
尚可签9 小时前
怎么降低AI率(文本)?最近发现了非常简单的思路
人工智能
咕噜企业分发小米9 小时前
阿里云AI教育产品如何助力企业提升客户粘性?
人工智能·microsoft·阿里云