11_FastMCP 2.x 中文文档之FastMCP高级功能:用户引导详解
一、用户引导
通过 MCP 上下文在工具执行期间向用户请求结构化输入。
新版本:2.10.0 功能
用户引导功能允许 MCP 服务器在工具执行期间向用户请求结构化输入。无需一次性提供所有输入参数,工具可以根据需要交互式地询问缺失的参数、请求澄清或获取额外上下文信息。
提示:本文档中的大多数示例都假设您有一个名为 mcp 的 FastMCP 服务器实例,并展示了如何在 @mcp.tool 装饰的函数中使用 ctx.elicit 方法向用户请求输入。
二、什么是引导功能?
引导功能使工具能够暂停执行并向用户请求特定信息。这在以下场景中特别有用:
- 缺失参数:请求初始未提供的必需信息
- 澄清请求:在模糊场景下获取用户确认或选择
- 渐进式披露:逐步收集复杂信息
- 动态工作流:根据用户响应调整工具行为
例如,文件管理工具可能会询问"应该在哪个目录下创建?“,或者数据分析工具可能会请求"应该分析哪个日期范围的数据?”
2.1 基本用法
在任何工具函数中使用 ctx.elicit() 方法请求用户输入:
from fastmcp import FastMCP, Context
from dataclasses import dataclassmcp = FastMCP("Elicitation Server")@dataclass
class UserInfo:name: strage: int@mcp.tool
async def collect_user_info(ctx: Context) -> str:"""通过交互式提示收集用户信息。"""result = await ctx.elicit(message="请提供您的信息",response_type=UserInfo)if result.action == "accept":user = result.datareturn f"您好 {user.name},您今年 {user.age} 岁"elif result.action == "decline":return "未提供信息"else: # cancelreturn "操作已取消"
2.2 方法签名
上下文引导方法 (ctx.elicit,异步方法)
参数:
- message (str):向用户显示的提示信息
- response_type (type,默认为 None):定义预期响应结构的 Python 类型(数据类、基本类型等)。注意引导响应受限于 JSON Schema 类型的受限子集。详见支持的响应类型。
响应:
- ElicitationResult (object):包含用户响应结果的对象
属性:
- action (Literal[‘accept’, ‘decline’, ‘cancel’]):用户对请求的响应方式
- data (response_type | None):用户的输入数据(仅当 action 为 “accept” 时存在)
2.3 引导操作
引导结果包含一个 action 字段,指示用户如何响应:
- accept:用户提供了有效输入 - 数据可在 data 字段中获取
- decline:用户选择不提供请求的信息,data 字段为 None
- cancel:用户取消了整个操作,data 字段为 None
@mcp.tool
async def my_tool(ctx: Context) -> str:result = await ctx.elicit("选择一个操作")if result.action == "accept":return "已接受!"elif result.action == "decline":return "已拒绝!"else:return "已取消!"
FastMCP 还为 action 字段的模式匹配提供了类型化的结果类:
from fastmcp.server.elicitation import (AcceptedElicitation, DeclinedElicitation, CancelledElicitation,
)@mcp.tool
async def pattern_example(ctx: Context) -> str:result = await ctx.elicit("请输入您的姓名:", response_type=str)match result:case AcceptedElicitation(data=name):return f"您好 {name}!"case DeclinedElicitation():return "未提供姓名"case CancelledElicitation():return "操作已取消"
2.4 响应类型
服务器必须向客户端发送一个模式,指明它期望在引导请求响应中接收的数据类型。如果请求被 accept(接受),客户端必须发送符合该模式的响应。
MCP 规范仅支持有限的 JSON Schema 类型子集用于引导响应。具体来说,它仅支持具有基本类型属性的 JSON 对象,包括 string、number(或 integer)、boolean 和 enum 字段。
FastMCP 通过自动将它们包装在 MCP 兼容的对象模式中,使得请求更广泛的类型(包括标量类型,如 str)或根本不请求响应变得容易。
2.5 标量类型
您可以请求简单的标量数据类型用于基本输入,例如字符串、整数或布尔值。
当您请求标量类型时,FastMCP 会自动将其包装在对象模式中以符合 MCP 规范。客户端将看到相应的模式,请求一个具有所请求类型的单个 “value” 字段的对象。一旦客户端响应,提供的对象将被"解包",标量值将作为 ElicitationResult 对象的 data 字段返回给您的工具函数。
作为开发者,这意味着当您只需要一个标量值时,您不必担心创建或访问结构化对象。
请求字符串示例:
@mcp.tool
async def get_user_name(ctx: Context) -> str:"""获取用户姓名。"""result = await ctx.elicit("您叫什么名字?", response_type=str)if result.action == "accept":return f"您好,{result.data}!"return "未提供姓名"
请求整数示例:
@mcp.tool
async def pick_a_number(ctx: Context) -> str:"""选择一个数字。"""result = await ctx.elicit("请选择一个数字!", response_type=int)if result.action == "accept":return f"您选择了 {result.data}"return "未提供数字"
请求布尔值示例:
@mcp.tool
async def pick_a_boolean(ctx: Context) -> str:"""选择真或假。"""result = await ctx.elicit("真还是假?", response_type=bool)if result.action == "accept":return f"您选择了 {result.data}"return "未提供布尔值"
2.6 无响应
有时,引导的目的只是让用户批准或拒绝某个操作。在这种情况下,您可以将 None 作为响应类型传递,表示不需要响应。为了符合 MCP 规范,客户端将看到一个请求空对象响应的模式。在这种情况下,当用户接受引导时,ElicitationResult 对象的 data 字段将为 None。
@mcp.tool
async def approve_action(ctx: Context) -> str:"""批准一个操作。"""result = await ctx.elicit("是否批准此操作?", response_type=None)if result.action == "accept":return do_action()else:raise ValueError("操作被拒绝")
2.7 受限选项
通常您会希望将用户的响应限制在特定的值集中。您可以通过使用 Literal 类型或 Python 枚举作为响应类型来实现这一点,或者为了方便起见,将字符串列表传递给 response_type 参数。
使用字符串列表示例:
@mcp.tool
async def set_priority(ctx: Context) -> str:"""设置任务优先级。"""result = await ctx.elicit("请选择优先级级别?", response_type=["low", "medium", "high"],)if result.action == "accept":return f"优先级设置为:{result.data}"
使用 Literal 类型示例:
from typing import Literal@mcp.tool
async def set_priority(ctx: Context) -> str:"""设置任务优先级。"""result = await ctx.elicit("请选择优先级级别?", response_type=Literal["low", "medium", "high"])if result.action == "accept":return f"优先级设置为:{result.data}"return "未设置优先级"
使用 Python 枚举示例:
from enum import Enumclass Priority(Enum):LOW = "low"MEDIUM = "medium"HIGH = "high" @mcp.tool
async def set_priority(ctx: Context) -> str:"""设置任务优先级。"""result = await ctx.elicit("请选择优先级级别?", response_type=Priority)if result.action == "accept":return f"优先级设置为:{result.data.value}"return "未设置优先级"
2.8 结构化响应
您可以通过使用数据类、类型化字典或 Pydantic 模型作为响应类型来请求具有多个字段的结构化数据。请注意,MCP 规范仅支持具有标量(字符串、数字、布尔值)或枚举属性的浅层对象。
from dataclasses import dataclass
from typing import Literal@dataclass
class TaskDetails:title: strdescription: strpriority: Literal["low", "medium", "high"]due_date: str@mcp.tool
async def create_task(ctx: Context) -> str:"""使用用户提供的详细信息创建新任务。"""result = await ctx.elicit("请提供任务详细信息",response_type=TaskDetails)if result.action == "accept":task = result.datareturn f"已创建任务:{task.title}(优先级:{task.priority})"return "任务创建已取消"
2.9 多轮引导
工具可以进行多次引导调用以逐步收集信息:
@mcp.tool
async def plan_meeting(ctx: Context) -> str:"""通过逐步收集详细信息来规划会议。"""# 获取会议标题title_result = await ctx.elicit("会议标题是什么?", response_type=str)if title_result.action != "accept":return "会议规划已取消"# 获取持续时间duration_result = await ctx.elicit("持续多少分钟?", response_type=int)if duration_result.action != "accept":return "会议规划已取消"# 获取优先级priority_result = await ctx.elicit("这个会议紧急吗?", response_type=Literal["yes", "no"])if priority_result.action != "accept":return "会议规划已取消"urgent = priority_result.data == "yes"return f"会议 '{title_result.data}' 已规划为 {duration_result.data} 分钟(紧急:{urgent})"
2.10 客户端要求
引导功能要求客户端实现引导处理器。有关客户端如何处理这些请求的详细信息,请参阅客户端引导。
如果客户端不支持引导功能,调用 ctx.elicit() 将引发错误,指示不支持引导。
