Elicitation 指“获取或产生某物(尤其是信息或反应)的过程”。
这对 MCP 有什么意义?官方文档这样描述:
Model Context Protocol(MCP)提供了一种标准化方式,让服务器在交互过程中通过客户端向用户请求额外信息。该流程既让客户端保持对用户交互与数据共享的控制,又使服务器能动态收集所需信息。
换句话说:当服务器发现需要更多信息时,会借助客户端去再问用户,以便完成任务。
想象一个场景:你在订假期,但所查日期无票。用 elicitation,服务器不会只说“没票”,而是进一步追问或推荐临近可选日期。这能显著提升成交或预订的概率。
本章你将学到:
- 解释什么是 elicitation
- 了解何时使用它
- 构建一次 elicitation 集成
涵盖主题:
- 为什么要用 elicitation?
- 如何实现 elicitation
- Elicitation 的流程
- JSON-RPC 消息形式
- 服务器端实现
- 用 VS Code 测试 elicitation
为什么需要 elicitation?
进一步概括适用动机:
- 任务复杂度:有些任务无法一次性提供全部信息,需要多步选择/补充。例如订电影票:选片名和日期后,还要选座位类型、是否要电子票等。一次性全问会糟糕,分步询问更友好。
- 提升转化:若用户想要的“红色毛衣”缺货,服务器可引导选择其他颜色或登记到货通知,更可能达成交易。
- 更好的用户体验:比起直接“No”,提供合理由选项与替代方案,体验更佳。
实施前的指引
官方建议(信任/安全/隐私):
-
服务器不得通过 elicitation 请求敏感信息。
-
应用应当:
- 提供 UI 清楚标示哪个服务器在请求信息
- 允许用户在发送前审阅与修改回答
- 尊重隐私,提供拒绝/取消选项
Elicitation 流程
总体上,当服务器在处理某个工具、资源或提示模板的调用时,发现信息不足,就会触发 elicitation。它分两步:
- 服务器→客户端:先询问客户端是否可以发起一次 elicitation 请求给用户。
- 客户端→用户:客户端向用户展示请求并收集输入。
用户在第 1、2 步都可以接受或拒绝。可配合行程预订的序列图来理解该过程。
JSON-RPC 消息
请求(服务器→客户端)
{
"jsonrpc": "2.0",
"id": 1,
"method": "elicitation/create",
"params": {
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
要点:
- message:展示给用户的说明/问题。
- requestedSchema:明确需要哪些字段、类型与规则(如格式、校验)。例如请求姓名、邮箱、年龄并带验证:
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Your full name" },
"email": { "type": "string", "format": "email", "description": "Your email address" },
"age": { "type": "number", "minimum": 18, "description": "Your age" }
},
"required": ["name", "email"]
}
接受型响应(客户端→服务器)
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"action": "accept",
"content": {
"name": "Monalisa Octocat",
"email": "octocat@github.com",
"age": 30
}
}
}
拒绝型响应
{
"sonrpc": "2.0",
"id": 2,
"result": { "action": "decline" }
}
注:还有取消(cancel) ,类似用户关闭对话框或按下 Esc,不明确回答,仅终止当前询问。
请求 Schema 的常见类型
-
string(可加长度/正则/格式校验)
{ "type": "string", "title": "Display Name", "description": "Description text", "minLength": 3, "maxLength": 50, "pattern": "^[A-Za-z]+$", "format": "email" } -
number / integer(可设最小/最大)
{ "type": "number", "title": "Display Name", "description": "Description text", "minimum": 0, "maximum": 100 } -
boolean(是/否,支持默认值)
{ "type": "boolean", "title": "Display Name", "description": "Description text", "default": false } -
enum(枚举下拉,例如不同日期/套餐)
{ "type": "string", "title": "Display Name", "description": "Description text", "enum": ["option1", "option2", "option3"], "enumNames": ["Option 1", "Option 2", "Option 3"] }
以上类型由客户端渲染成相应的输入控件,以便用户填写或选择。接下来就可以按本章后续的实现部分将多种类型组合运用。
实现服务端功能(Implementing the server-side functionality)
先从服务端开始。需要了解的是:当某个服务端功能(tool、resource、prompt)在执行过程中发现信息不足时,应当触发一次 elicitation(引导澄清) 消息。
首先看看如何产生这类消息。我们先用一个 if 判断是否需要发起 elicitation;如果需要,就在上下文对象上调用 elicit,并提供要展示的 message 以及需遵循的 schema:
class BookingPreferences(BaseModel):
"""用于收集用户偏好的模式定义。"""
checkAlternative: bool = Field(description="是否尝试其他日期?")
alternativeDate: str = Field(
default="2024-12-26",
description="备选日期(YYYY-MM-DD)",
)
def is_available_date -> bool:
pass
@mcp.tool()
def book_table(date: str, ctx: Context[ServerSession, None]) -> str:
# 1. 检查日期是否可用
if not is_available_date(date):
result = await ctx.elicit(
message=(f"{date} 无法预订。要尝试其他日期吗?"),
schema=BookingPreferences,
)
接着检查客户端/用户的响应:
if result.action == "accept" and result.data:
if result.data.checkAlternative:
return f"[SUCCESS] 已为 {result.data.alternativeDate} 预订"
return "[CANCELLED] 未进行预订"
return "[CANCELLED] 预订已取消"
如上所示,我们检查 action 属性来判断用户是否接受并提交了附加数据。若接受,则解析其选择;若未接受,则返回取消信息。
把所有内容放在一起:
from pydantic import BaseModel, Field
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession
mcp = FastMCP(name="Elicitation Example")
class BookingPreferences(BaseModel):
"""用于收集用户偏好的模式定义。"""
checkAlternative: bool = Field(description="是否尝试其他日期?")
alternativeDate: str = Field(
default="2024-12-26",
description="备选日期(YYYY-MM-DD)",
)
@mcp.tool()
async def book_trip(date: str, ctx: Context[ServerSession, None]) -> str:
"""带日期可用性检查的行程预订。"""
# 检查日期是否可用
if not is_available_date(date):
# 日期不可用——询问用户是否选择备选日期
result = await ctx.elicit(
message=(f"{date} 无法预订。要尝试其他日期吗?"),
schema=BookingPreferences,
)
if result.action == "accept" and result.data:
if result.data.checkAlternative:
return f"[SUCCESS] 已为 {result.data.alternativeDate} 预订"
return "[CANCELLED] 未进行预订"
return "[CANCELLED] 预订已取消"
# 日期可用
return f"[SUCCESS] 已为 {date} 预订"
使用 VS Code 测试 Elicitation
要在 VS Code 中测试 elicitation 功能,需要在 mcp.json 中添加该 MCP 服务器的配置,例如:
"server": {
"type": "sse",
"url": "http://localhost:8000/sse"
}
然后确保处于 Agent 模式,输入如下提示词:
Book trip on 2025-02-01
界面中会经历如下过程:
-
输入提示并触发工具调用:你输入预订请求,系统识别为工具调用;需要你批准该调用后继续。
(Figure 10.2 – 输入提示并看到工具调用)
-
批准工具调用:批准后,界面会告知选定日期已满,并将引导你填写 elicitation 响应。
(Figure 10.3 – 批准工具调用)
- 编辑 elicitation 响应:按之前定义的 schema 采集更多信息。界面会先问你是否要改期;若选择
true,接着会要求填写新的日期;若选择false,流程结束。
(Figure 10.4 – 编写 elicitation 响应)
- 提交 elicitation:选择继续后,填写备选日期,例如:
(Figure 10.5 – 填写备选日期)
- 得到最终结果:服务器校验该备选日期可订,并返回确认信息:
(Figure 10.6 – 最终结果)
至此,就完成了在服务端实现并通过 VS Code 测试的 elicitation 流程。
在客户端实现 Elicitation(引导澄清)
太好了——我们已经理解了服务端的实现方式,并且知道了如何在 VS Code 中测试。接下来实现客户端部分。
通常在编写客户端时,你会与一个 ClientSession 对象打交道。它负责管理与服务器的连接。除了传入 read_stream 和 write_stream 外,你还可以传入 elicitation_callback 来处理任何引导澄清事件:
async with ClientSession(
read_stream,
write_stream,
elicitation_callback=elicitation_callback_handler
) as session:
来看一下 elicitation_callback_handler:
async def elicitation_callback_handler(
context: RequestContext[ClientSession, None],
params: ElicitRequestParams
):
print(f"[CLIENT] Received elicitation data: {params.message}")
可以看到,这个回调接收两个参数:请求上下文和引导澄清的请求参数。此时我们需要构造一个客户端响应并发回给服务器。可以发送三种响应:
1) accept(接受)
表示希望继续引导澄清流程。发送 accept 时,应符合服务器声明的输入 Schema。例如,下面的响应是合规的:
return ElicitResult(action="accept", content={
"checkAlternative": True,
"alternativeDate": "2025-01-01"
}) # 应该改订为 1 月 1 日,而非最初的 1 月 2 日
这里我们将 action 设为 accept,并在 content 中携带负载,字段为 checkAlternative 和 alternativeDate(与服务器端的 schema 对应)。注意示例里直接硬编码了日期;在更贴近生产的应用里,alternativeDate 的值应来自向用户询问的输入结果。
2) decline(拒绝)
表示要终止引导澄清流程。可以这样返回:
return ElicitResult(action="decline")
这里不设置 content,清楚地表明我们不提供任何额外信息或上下文。这类 decline 通常发生在流程很早的阶段,类似用户直接说“不”。
3) accept(接受流程,但提供的内容表示不再选择备选)
用户接受继续交互,但最终不提供备选日期:
# 1. 拒绝选择其他日期
return ElicitResult(action="accept", content={
"checkAlternative": False
}) # 应返回“未进行预订”,验证有效 -->
这种“拒绝”发生在更晚的阶段:用户被给予选择备选日期的机会后,选择不更改。
文中还配有一个时序图(Figure 10.7),展示了在 Python 客户端里实现引导澄清时的整体流程。总体来看,用户可能在不同阶段取消或拒绝,因此关键点是让客户端优雅地处理这些不同的分支与结果。
小结
本章详细介绍了引导澄清(elicitation)的客户端实现流程,包括如何被触发、可能出现的多种情形,以及在此过程中可向服务器返回的不同响应类型。Elicitation 的目的,是在系统执行请求时主动向用户索取更多信息,以更好地理解意图或完成任务。
我们也看到,用户可以在流程的不同阶段接受或中止。
最后,良好地利用引导澄清能显著提升交互体验,帮助系统更有效地满足用户需求。
下一章将介绍如何使用 Basic Auth、JWT、OAuth 2.1 等方式来保护你的 MCP 服务器与客户端。