引导(Elicitation)
了解如何实现可以在交互期间向用户请求其他信息的 MCP 服务器。
概述
引出允许服务器在交互期间向用户请求其他信息。当工具或操作需要用户澄清、确认或原始请求中未提供的额外输入时,这很有用。
有两种引出模式:
- 表单模式(
mcp.ElicitationModeForm)— 服务器提供 JSON Schema,客户端向用户显示结构化表单。这是默认模式。 - URL 模式(
mcp.ElicitationModeURL)— 服务器提供用户访问的 URL,实现带外交互(如身份验证流程)。
::::info[需要用户同意] 引出本质上是面向用户的。当您的服务器发送引出请求时:
- 客户端将向用户显示请求
- 用户可以接受、拒绝或取消
- 响应时间取决于用户交互
相应设计您的工具:
- 提供清晰、人类可读的消息解释您需要什么以及为什么
- 优雅地处理所有三种响应操作(接受、拒绝、取消)
- 考虑用户交互延迟的超时 :::
启用引出
要在服务器中启用引出,请在服务器创建期间使用 WithElicitation() 选项:
package main
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// 创建带有引出能力的服务器
s := server.NewMCPServer("Elicitation Server", "1.0.0",
server.WithElicitation(),
)
// 添加使用引出的工具...
// 启动服务器
server.ServeStdio(s)
}ElicitationParams
ElicitationParams 结构定义服务器向用户请求的内容:
type ElicitationParams struct {
Meta *Meta `json:"_meta,omitempty"`
Mode string `json:"mode,omitempty"` // "form" 或 "url",默认为 "form"
Message string `json:"message"` // 人类可读的消息
// 表单模式字段
RequestedSchema any `json:"requestedSchema,omitempty"` // 表单的 JSON Schema
// URL 模式字段
ElicitationID string `json:"elicitationId,omitempty"` // 用于跟踪的唯一 ID
URL string `json:"url,omitempty"` // 用户访问的 URL
}验证
ElicitationParams 有一个 Validate() 方法,强制执行特定于模式的 requirements:
- 表单模式要求
RequestedSchema非空 - URL 模式要求同时设置
ElicitationID和URL
使用 RequestElicitation() 时会自动调用验证。
响应操作
当用户响应引出请求时,结果包含三种操作之一:
mcp.ElicitationResponseActionAccept // 用户提供了请求的信息
mcp.ElicitationResponseActionDecline // 用户明确拒绝
mcp.ElicitationResponseActionCancel // 用户取消而未做选择结果作为 ElicitationResult 返回:
type ElicitationResult struct {
Result
ElicitationResponse
}
type ElicitationResponse struct {
Action ElicitationResponseAction `json:"action"`
Content any `json:"content,omitempty"` // 用户的响应数据
}当 Action 为 accept 时,Content 字段包含用户的响应数据,应符合原始请求中的 RequestedSchema。
表单模式
表单模式是默认模式。服务器提供描述预期输入的 JSON Schema,客户端为用户呈现表单。
在工具处理程序中请求引出
在工具处理程序中使用 RequestElicitation() 向用户请求信息:
mcpServer.AddTool(
mcp.NewTool(
"create_project",
mcp.WithDescription("使用用户指定的配置创建新项目"),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// 使用 JSON Schema 构建引出请求
elicitationRequest := mcp.ElicitationRequest{
Params: mcp.ElicitationParams{
Message: "请提供项目详情。",
RequestedSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"projectName": map[string]any{
"type": "string",
"description": "项目名称",
"minLength": 1,
},
"framework": map[string]any{
"type": "string",
"description": "要使用的前端框架",
"enum": []string{"react", "vue", "angular", "none"},
},
"includeTests": map[string]any{
"type": "boolean",
"description": "包含测试设置",
"default": true,
},
},
"required": []string{"projectName"},
},
},
}
// 发送引出请求
result, err := mcpServer.RequestElicitation(ctx, elicitationRequest)
if err != nil {
return nil, fmt.Errorf("failed to request elicitation: %w", err)
}
// 处理用户的响应
switch result.Action {
case mcp.ElicitationResponseActionAccept:
data, ok := result.Content.(map[string]any)
if !ok {
return nil, fmt.Errorf("unexpected response format")
}
projectName := data["projectName"].(string)
framework := "none"
if fw, ok := data["framework"].(string); ok {
framework = fw
}
message := fmt.Sprintf("Created project '%s' with framework: %s", projectName, framework)
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(message),
},
}, nil
case mcp.ElicitationResponseActionDecline:
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent("项目创建已取消 — 用户拒绝。"),
},
}, nil
case mcp.ElicitationResponseActionCancel:
return nil, fmt.Errorf("project creation cancelled by user")
default:
return nil, fmt.Errorf("unexpected response action: %s", result.Action)
}
},
)条件引出
您可以仅在需要时请求引出——例如,确认潜在的危险操作:
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
data, _ := request.RequireString("data")
// 仅在数据较大时请求确认
if len(data) > 100 {
result, err := mcpServer.RequestElicitation(ctx, mcp.ElicitationRequest{
Params: mcp.ElicitationParams{
Message: fmt.Sprintf("数据为 %d 个字符。是否继续?", len(data)),
RequestedSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"proceed": map[string]any{
"type": "boolean",
"description": "是否继续处理",
},
},
"required": []string{"proceed"},
},
},
})
if err != nil {
return nil, fmt.Errorf("failed to get confirmation: %w", err)
}
if result.Action != mcp.ElicitationResponseActionAccept {
return mcp.NewToolResultText("处理已被用户取消。"), nil
}
responseData := result.Content.(map[string]any)
if proceed, ok := responseData["proceed"].(bool); !ok || !proceed {
return mcp.NewToolResultText("用户选择不继续。"), nil
}
}
// 继续处理...
return mcp.NewToolResultText(fmt.Sprintf("已处理 %d 个字符", len(data))), nil
}URL 模式
URL 模式用于用户需要访问外部 URL 的带外交互——例如 OAuth 流程或 API 密钥设置页面。
请求 URL 引出
使用 RequestURLElicitation() 方便地发送 URL 模式请求:
mcpServer.AddTool(
mcp.NewTool(
"auth_via_url",
mcp.WithDescription("通过浏览器进行身份验证"),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
session := server.ClientSessionFromContext(ctx)
if session == nil {
return nil, fmt.Errorf("no active session")
}
// 生成用于跟踪的唯一引出 ID
elicitationID := uuid.New().String()
url := fmt.Sprintf("https://myserver.com/auth?id=%s", elicitationID)
// 发送 URL 引出请求
result, err := mcpServer.RequestURLElicitation(
ctx,
session,
elicitationID,
url,
"请在浏览器中进行身份验证以继续。",
)
if err != nil {
return nil, fmt.Errorf("URL elicitation failed: %w", err)
}
if result.Action == mcp.ElicitationResponseActionAccept {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent("身份验证流程已启动。请在浏览器中完成流程。"),
},
}, nil
}
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.NewTextContent(fmt.Sprintf("用户拒绝身份验证:%s", result.Action)),
},
}, nil
},
)完成通知
对于 URL 模式,一旦用户完成带外流程(例如,在浏览器中提交表单,服务器收到回调),通知客户端引出已完成:
// 在服务器收到浏览器回调后发送
err := mcpServer.SendElicitationComplete(ctx, session, elicitationID)
if err != nil {
log.Printf("Failed to send completion notification: %v", err)
}您也可以直接构造通知:
notification := mcp.NewElicitationCompleteNotification("elicitation-id-123")URLElicitationRequiredError
当工具需要尚未设置的身份验证时,您可以返回 URLElicitationRequiredError 以发出信号,告知客户端应启动 URL 引出流程:
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
if !isAuthorized(ctx) {
elicitationID := uuid.New().String()
return nil, mcp.URLElicitationRequiredError{
Elicitations: []mcp.ElicitationParams{
{
Mode: mcp.ElicitationModeURL,
ElicitationID: elicitationID,
URL: fmt.Sprintf("https://myserver.com/authorize?id=%s", elicitationID),
Message: "需要授权才能访问此资源。",
},
},
}
}
return mcp.NewToolResultText("操作成功完成!"), nil
}客户端处理程序
客户端实现 ElicitationHandler 接口来处理服务器的引出请求:
type ElicitationHandler interface {
Elicit(ctx context.Context, request mcp.ElicitationRequest) (*mcp.ElicitationResult, error)
}创建客户端时注册处理程序:
import "github.com/mark3labs/mcp-go/client"
handler := &MyElicitationHandler{}
c := client.NewClient(transport, client.WithElicitationHandler(handler))实现应:
- 向用户显示请求消息(以及 URL 模式下的 URL)
- 根据请求的 schema 验证输入(表单模式)
- 允许用户接受、拒绝或取消
- 返回适当的
ElicitationResult
ElicitationCapability
ElicitationCapability 结构在初始化期间公布客户端或服务器支持哪种引出模式:
type ElicitationCapability struct {
Form *struct{} `json:"form,omitempty"` // 支持表单模式
URL *struct{} `json:"url,omitempty"` // 支持 URL 模式
}此能力作为 ClientCapabilities 和 ServerCapabilities 的一部分在 MCP 初始化握手期间交换。
错误处理
始终优雅地处理引出错误和所有响应操作:
result, err := mcpServer.RequestElicitation(ctx, elicitationRequest)
if err != nil {
// 常见错误:
// - server.ErrNoActiveSession: 上下文中没有会话
// - server.ErrElicitationNotSupported: 会话/传输不支持引出
// - ElicitationParams.Validate() 的验证错误
log.Printf("Elicitation failed: %v", err)
return mcp.NewToolResultError(fmt.Sprintf("Could not request information: %v", err)), nil
}上下文和超时
使用上下文进行超时控制,特别是因为引出需要用户交互:
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
result, err := mcpServer.RequestElicitation(ctx, elicitationRequest)最佳实践
- 清晰的消息:编写人类可读的消息,解释您需要什么以及为什么
- 最小的 Schema:仅请求您实际需要的信息
- 处理所有操作:始终处理接受、拒绝和取消——不要假设用户会接受
- 验证响应:使用前对
Content字段进行类型断言和验证 - 优雅降级:如果引出失败或不受支持,提供合理的回退
- 谨慎使用 URL 模式:仅为真正需要浏览器的流程保留 URL 模式(如 OAuth)
- 跟踪引出 ID:对于 URL 模式,使用唯一 ID 将浏览器回调与会话关联

