ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FastMCP 服务器框架提示词管理:用 @prompt 装饰器与 PromptManager 搭建可复用提示词骨架

FastMCP 服务器框架提示词管理:用 @prompt 装饰器与 PromptManager 搭建可复用提示词骨架 1. 为什么要把散落的 prompt 收进 FastMCP如果你正在用 FastMCP 写 MCP 服务器大概率遇到过这种局面提示词一开始只是几个字符串常量散落在server.py、tools.py、甚至某个utils.py里。等到提示词涨到十几条改一个措辞要全局搜索测试时还得手动拼参数团队里两个人同时改同一个 prompt 直接冲突。更麻烦的是MCP 客户端通过list_prompts和get_prompt协议方法访问提示词时你希望它们能被动态发现、带参数、还能根据上下文生成不同内容而不是一堆硬编码字符串。FastMCP 的提示词管理模块就是解决这个问题的。它把提示词从「字符串」升级成「可注册、可检索、可渲染的资产」你用prompt装饰器把普通 Python 函数注册成提示词PromptManager作为中央注册表统一管理Prompt和Message类负责元数据与标准化输出。客户端请求时系统按名字检索、注入参数、调用函数、把返回值转成消息数组再通过 MCP 协议返回。这篇文章面向需要把 prompt 收敛为可维护资产的 MCP 开发者。我会给出prompt装饰器注册、PromptManager配置、settings.json/config.toml骨架的可复制片段并演示通过 TaoToken 统一 Key/API 通道完成一次提示词加载与调用验证。读完你能拿到一套可复用的提示词管理配置模板直接套到自己的 FastMCP 项目里。2. 前置准备FastMCP 环境与 TaoToken 统一通道在动手写提示词管理之前先把两件事准备好FastMCP 运行环境以及一个能统一管理模型调用的 API 通道。前者决定你的服务器能不能跑起来后者决定你在验证提示词时不用为每个模型单独配 Key。2.1 安装 FastMCP 与目录约定FastMCP 的提示词系统位于src/mcp/server/fastmcp/prompts/目录下核心是三个模块manager.py负责注册与管理base.py定义Prompt和Message的基础结构与渲染逻辑server.py实现协议级接口。你不需要改源码但理解这个分层有助于排障。安装依赖pip install fastmcp我建议的项目结构如下把提示词单独放一个包避免和工具逻辑混在一起my-mcp-server/ ├── server.py ├── prompts/ │ ├── __init__.py │ ├── registry.py # prompt 注册入口 │ └── templates.py # 具体提示词函数 ├── settings.json └── config.toml2.2 用 TaoToken 统一 Key 与 API 通道验证提示词时你往往要调用模型看输出是否符合预期。如果每个模型都单独配 Key、单独记 Base URL验证成本会很高。TaoToken 提供统一的 Key 和 API 通道把模型调用收敛到一个入口适合在提示词调试阶段快速切换模型对比效果。官网地址是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endAPI 入口是https://taotoken.net/api。你需要在控制台创建一个 API Key后续在环境变量里引用它而不是硬编码进代码。注意API Key 属于敏感凭证务必通过环境变量或密钥管理服务注入不要提交到 Git 仓库。创建 Key 的入口在控制台的 API Keys 页面拿到形如sk-xxxx的字符串后写入环境变量export TAOTOKEN_API_KEYsk-你的实际Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api这样你的 FastMCP 服务器和验证脚本都从同一组环境变量读取切换环境时只改一处。3. 可复制配置prompt 装饰器与 PromptManager 骨架这一节是全文的核心。我会先给出settings.json和config.toml的骨架再写prompt装饰器的注册代码最后说明PromptManager如何接管注册与检索。3.1 settings.json 与 config.toml 骨架settings.json用来放服务器级配置比如服务器名、传输方式、以及模型通道的引用。注意这里只放非敏感信息Key 走环境变量。{ server: { name: prompt-manager-demo, transport: stdio, log_level: INFO }, model_channel: { provider: taotoken, base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, default_model: claude-3-5-sonnet }, prompts: { registry_module: prompts.registry, strict_args: true } }config.toml用来放提示词层面的默认值比如默认分析深度、输出语言、最大上下文条数。这样提示词函数可以读取配置而不是把魔法数字写死在函数体里。[prompt_defaults] language zh-CN analysis_depth basic max_context_messages 8 [prompt_defaults.overrides] analyze_database_schema { analysis_depth deep }读取配置的辅助函数可以这样写放在prompts/__init__.py里import json import os import tomllib from pathlib import Path def load_settings(path: str settings.json) - dict: with open(path, r, encodingutf-8) as f: return json.load(f) def load_prompt_config(path: str config.toml) - dict: with open(path, rb) as f: return tomllib.load(f) def get_api_key(settings: dict) - str: env_name settings[model_channel][api_key_env] key os.environ.get(env_name) if not key: raise RuntimeError(f环境变量 {env_name} 未设置) return key3.2 prompt 装饰器注册提示词prompt装饰器的作用是把普通函数转成可被 MCP 协议调用的提示词。它支持name、title、description等参数来自定义元数据。关键点装饰器要写成prompt()而不是prompt否则函数不会被实际注册。下面是一个同步提示词和一个异步提示词的注册示例。异步版本演示了上下文注入和动态参数组合。from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.prompts import UserMessage, AssistantMessage from mcp.server.fastmcp.server import Context server FastMCP(prompt-manager-demo) server.prompt( namecode_review, title代码审查, description对给定代码片段做结构化审查输出问题与建议 ) def code_review(code: str, language: str python) - list: 同步提示词返回消息数组。 return [ UserMessage(contentf请审查以下 {language} 代码\n{code}), UserMessage(content请按「严重问题 / 改进建议 / 亮点」三段输出。), ] server.prompt( nameanalyze_database_schema, title数据库表结构分析, description读取表结构资源并生成分析提示词 ) async def analyze_database_schema(table_name: str, context: Context) - list: 异步提示词支持上下文注入与外部资源读取。 schema_resource await context.read_resource(fresource://schema/{table_name}) schema_text .join([chunk.content for chunk in schema_resource]) prefs context.fastmcp.get_context().get(user_preferences, {}) depth prefs.get(analysis_depth, basic) return [ UserMessage(contentf请分析以下 {table_name} 表的结构\n{schema_text}), AssistantMessage(contentf分析深度要求{depth}), UserMessage(content请指出潜在的设计问题和优化建议。), ]这里有几个容易踩的点。第一返回值必须是可被转换为Message对象的类型返回裸字符串在某些版本会报转换错误。第二Context参数的类型注解必须正确否则注入会失败。第三异步函数用async def系统会自动await结果。3.3 PromptManager 接管注册与检索PromptManager是中央注册表内部用字典存储提示词查找是 O(1)。它提供add_prompt、get_prompt、list_prompts三个核心方法。FastMCP服务器类内部持有_prompt_managerprompt装饰器最终就是调用Prompt.from_function()创建Prompt实例再add_prompt进去。如果你想手动管理一批提示词可以显式操作PromptManagerfrom mcp.server.fastmcp.prompts.manager import PromptManager from mcp.server.fastmcp.prompts.base import Prompt manager PromptManager() def build_prompt(name: str, title: str, description: str): def decorator(fn): prompt Prompt.from_function( fn, namename, titletitle, descriptiondescription ) manager.add_prompt(prompt) return fn return decorator build_prompt(summarize, 摘要生成, 把长文本压缩成要点) def summarize(text: str) - list: return [UserMessage(contentf请把以下内容总结为不超过5条要点\n{text})] # 检索 p manager.get_prompt(summarize) print(p.name, p.description) print([item.name for item in manager.list_prompts()])注册流程里有一个细节如果同名提示词已存在PromptManager会发出警告而不是静默覆盖。这在团队协作时很有用能避免两个人注册同名 prompt 导致行为漂移。检索时如果名字不存在get_prompt会抛ValueError所以客户端调用前最好先list_prompts确认。4. 验证请求通过 TaoToken 完成一次提示词加载与调用配置写完了得验证它真的能跑。这一节我用一个独立脚本通过 TaoToken 的统一通道加载提示词、渲染消息、调用模型确认整条链路通。4.1 启动服务器并列出提示词先启动 FastMCP 服务器用 stdio 传输python server.py在另一个终端用 MCP 客户端连接调用list_promptsimport asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def list_all_prompts(): params StdioServerParameters(commandpython, args[server.py]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.list_prompts() for p in result.prompts: print(f{p.name} | {p.title} | {p.description}) asyncio.run(list_all_prompts())预期输出类似code_review | 代码审查 | 对给定代码片段做结构化审查输出问题与建议 analyze_database_schema | 数据库表结构分析 | 读取表结构资源并生成分析提示词如果这里报「提示词未注册」先检查装饰器是不是写成了prompt而不是prompt()再确认函数所在模块确实被 import 执行过。4.2 调用 get_prompt 并渲染消息确认列表没问题后调用get_prompt拿具体消息async def fetch_prompt(): params StdioServerParameters(commandpython, args[server.py]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.get_prompt( code_review, arguments{code: def add(a,b): return ab, language: python} ) for msg in result.messages: print(msg.role, -, msg.content.text[:80]) asyncio.run(fetch_prompt())预期输出user - 请审查以下 python 代码 def add(a,b): return ab user - 请按「严重问题 / 改进建议 / 亮点」三段输出。到这一步提示词的注册、检索、渲染链路已经验证完毕。接下来把渲染出的消息发给模型确认输出符合预期。4.3 通过 TaoToken 调用模型验证输出用 TaoToken 的统一通道调用模型Key 从环境变量读取import os import httpx def call_model(messages: list, model: str claude-3-5-sonnet) - str: api_key os.environ[TAOTOKEN_API_KEY] base_url os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) resp httpx.post( f{base_url}/v1/messages, headers{ Authorization: fBearer {api_key}, Content-Type: application/json, }, json{ model: model, max_tokens: 1024, messages: messages, }, timeout60, ) resp.raise_for_status() return resp.json()[content][0][text] if __name__ __main__: msgs [ {role: user, content: 请审查以下 python 代码\ndef add(a,b): return ab}, {role: user, content: 请按「严重问题 / 改进建议 / 亮点」三段输出。}, ] print(call_model(msgs))实测下来把提示词渲染结果直接喂给模型输出结构稳定说明提示词模板本身没有歧义。如果你要对比不同模型对同一提示词的响应只改model参数即可Key 和 Base URL 都不用动这正是统一通道的价值。5. 本篇常见错排查提示词管理模块的报错大多集中在注册、参数、上下文和消息转换四类。下面按现象、原因、修复三步走。5.1 提示词未注册现象list_prompts返回空或get_prompt抛ValueError。原因通常是装饰器写法错误。prompt不带括号时装饰器工厂没有被调用函数不会进入注册流程。另一个原因是函数所在模块没有被 import装饰器根本没执行。修复统一写成server.prompt()或server.prompt(name...)并确认模块在服务器启动时被加载。可以在注册后打印manager.list_prompts()做自检。5.2 参数验证失败现象调用get_prompt时报参数不匹配。原因是你传入的arguments与函数签名不一致比如缺少必需参数或参数名拼写错误。Prompt.from_function会提取函数签名生成PromptArgument列表客户端必须按这个列表传参。修复先list_prompts查看每个提示词的参数元数据再按名字传参。开启settings.json里的strict_args可以让不匹配直接报错而不是静默忽略。5.3 上下文注入失败现象异步提示词里context为None或报类型错误。原因是Context参数的类型注解缺失或写错系统无法识别该注入。另外如果函数里有一个同名参数也叫context会和注入冲突。修复确保context: Context的注解完整且不要与其他参数重名。注入的Context可以访问read_resource和fastmcp.get_context()用于读取外部资源和会话状态。5.4 消息转换错误现象render阶段报无法转换为Message。原因是提示词函数返回了不支持的类型比如裸字符串、字典或None。系统期望返回Message对象列表或可被转换的结构。修复统一返回UserMessage/AssistantMessage组成的列表。如果确实要返回字符串确认当前 FastMCP 版本支持自动包装否则手动包一层。5.5 模型调用 401 或超时现象通过 TaoToken 调用时返回 401 或连接超时。401 通常是TAOTOKEN_API_KEY未设置或值不对检查环境变量是否在当前 shell 生效。超时则可能是base_url写错确认是https://taotoken.net/api而不是其他路径。另外注意httpx的timeout设置长提示词渲染后消息较多时适当调大。6. 把提示词骨架沉淀为团队资产走到这里你已经有了settings.json和config.toml骨架、prompt注册代码、PromptManager检索逻辑以及一条通过 TaoToken 验证的完整链路。接下来要做的不是继续堆提示词而是把骨架沉淀成团队能复用的资产。我的做法是每个提示词函数只负责「组装消息」不负责「调用模型」。模型调用统一走 TaoToken 通道Key 从环境变量注入模型名从settings.json读取。这样提示词可以独立测试模型可以随时切换两者解耦。提示词的默认参数放config.toml按名字做 overrides避免把业务默认值写死在函数体里。如果你要长期维护一批编码类或 Agent 类提示词可以考虑用 Coding Plan 把模型调用额度统一管理配合 API Keys 页面轮换 Key接入文档里有完整的参数说明。验证单个提示词效果时模型对话入口适合快速试排障和接入细节则回到 API Keys 和接入文档。把这几件事分开提示词管理才不会变成新的技术债。
返回列表