采样
学习如何实现能够通过采样功能向客户端请求LLM补全的MCP服务器,
概述
采样允许 MCP 服务器从客户端请求 LLM 完成,实现双向通信,服务器可以利用客户端的 LLM 功能。这对于需要生成内容、回答问题或执行推理任务的工具特别有用。
[需要用户同意]
根据 MCP 规范,客户端 应该 为采样请求实现人工审核批准。
当您从客户端请求采样时:
- 用户通常会被提示审查和批准您的请求
- 用户可以在发送到 LLM 之前修改您的提示
- 用户可以完全拒绝您的请求
- 由于用户交互,响应时间可能更长
相应设计您的工具:
- 提供清晰的描述说明为什么需要采样
- 使用描述性系统提示解释目的
- 优雅地处理拒绝错误
- 考虑用户批准延迟的超时
- 不要假设立即或自动批准
精心设计的采样请求可以提高用户信任度和批准率。 :::
启用采样
要在服务器中启用采样,请在服务器设置期间调用 EnableSampling():
package main
import (
"context"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// 创建服务器
mcpServer := server.NewMCPServer("my-server", "1.0.0")
// 启用采样能力
mcpServer.EnableSampling()
// 添加使用采样的工具...
// 启动服务器
server.ServeStdio(mcpServer)
}请求采样
在工具处理程序中使用 RequestSampling() 请求 LLM 完成:
mcpServer.AddTool(mcp.Tool{
Name: "ask_llm",
Description: "使用采样向 LLM 提问",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"question": map[string]any{
"type": "string",
"description": "要问 LLM 的问题",
},
"system_prompt": map[string]any{
"type": "string",
"description": "可选的系统提示",
},
},
Required: []string{"question"},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// 提取参数
question, err := request.RequireString("question")
if err != nil {
return nil, err
}
systemPrompt := request.GetString("system_prompt", "You are a helpful assistant.")
// 创建采样请求
samplingRequest := mcp.CreateMessageRequest{
CreateMessageParams: mcp.CreateMessageParams{
Messages: []mcp.SamplingMessage{
{
Role: mcp.RoleUser,
Content: mcp.TextContent{
Type: "text",
Text: question,
},
},
},
SystemPrompt: systemPrompt,
MaxTokens: 1000,
Temperature: 0.7,
},
}
// 从客户端请求采样
result, err := mcpServer.RequestSampling(ctx, samplingRequest)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error requesting sampling: %v", err),
},
},
IsError: true,
}, nil
}
// 返回 LLM 响应
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("LLM Response: %s", getTextFromContent(result.Content)),
},
},
}, nil
})采样请求参数
CreateMessageRequest 支持各种参数来控制 LLM 行为:
samplingRequest := mcp.CreateMessageRequest{
CreateMessageParams: mcp.CreateMessageParams{
// 必需:发送到 LLM 的消息
Messages: []mcp.SamplingMessage{
{
Role: mcp.RoleUser, // 或 mcp.RoleAssistant
Content: mcp.TextContent{ // 或 mcp.ImageContent
Type: "text",
Text: "Your message here",
},
},
},
// 可选:上下文的系统提示
SystemPrompt: "You are a helpful assistant.",
// 可选:生成的最大令牌数
MaxTokens: 1000,
// 可选:随机性温度(0.0 到 1.0)
Temperature: 0.7,
// 可选:停止序列
StopSequences: []string{"\\n\\n"},
// 可选(2025-11-25):上下文包含提示。仅在客户端公布 `sampling.context` 时才发送
// "none" 以外的值 — 请参阅下面的[能力门控](#capability-gating-2025-11-25)。
IncludeContext: "thisServer",
// 可选(2025-11-25):模型在生成期间可以使用的工具,以及
// 模型应该多积极地使用它们。仅在
// 客户端公布了 `sampling.tools` 时才发送这些。
Tools: []mcp.Tool{ /* ... */ },
ToolChoice: &mcp.ToolChoice{Mode: mcp.ToolChoiceModeAuto},
},
}能力门控(2025-11-25)
2025-11-25 协议修订将采样分为基线能力和两个选择性加入的子能力。在发送门控字段之前,检查连接会话的 ClientCapabilities.Sampling — 符合规范的客户端必须拒绝违反下表中规则的请求。
CreateMessageParams 上的字段 | 必需的客户端子能力 | 子能力为 nil ⇒ |
|---|---|---|
IncludeContext("none" 以外的值) | Sampling.Context | 省略字段或发送 "none" |
Tools | Sampling.Tools | 省略字段 |
ToolChoice | Sampling.Tools | 省略字段 |
工具处理程序内的典型模式:
func askLLMWithTools(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
session := server.ClientSessionFromContext(ctx)
sessionWithClient, ok := session.(server.SessionWithClientInfo)
if !ok {
return mcp.NewToolResultError("client session is not capability-aware"), nil
}
caps := sessionWithClient.GetClientCapabilities()
params := mcp.CreateMessageParams{
Messages: []mcp.SamplingMessage{ /* ... */ },
MaxTokens: 1000,
}
// 仅在客户端选择加入时附加 Tools / ToolChoice。
if caps.Sampling != nil && caps.Sampling.Tools != nil {
params.Tools = []mcp.Tool{ /* ... */ }
params.ToolChoice = &mcp.ToolChoice{Mode: mcp.ToolChoiceModeAuto}
}
// 仅在客户端选择加入时请求上下文包含。
if caps.Sampling != nil && caps.Sampling.Context != nil {
params.IncludeContext = "thisServer"
}
result, err := mcpServer.RequestSampling(ctx, mcp.CreateMessageRequest{CreateMessageParams: params})
// ...
}ToolChoiceMode 接受三个常量:
| 常量 | 传输值 | 含义 |
|---|---|---|
mcp.ToolChoiceModeAuto | "auto" | 模型决定(省略 ToolChoice 时的默认值)。 |
mcp.ToolChoiceModeRequired | "required" | 模型必须调用至少一个工具。 |
mcp.ToolChoiceModeNone | "none" | 此请求禁用工具使用。 |
消息类型
采样支持不同的消息角色和内容类型:
消息角色
// 用户消息
{
Role: mcp.RoleUser,
Content: mcp.TextContent{
Type: "text",
Text: "法国的首都是哪里?",
},
}
// 助手消息(用于对话上下文)
{
Role: mcp.RoleAssistant,
Content: mcp.TextContent{
Type: "text",
Text: "法国的首都是巴黎。",
},
}内容类型
文本内容
mcp.TextContent{
Type: "text",
Text: "Your text content here",
}图像内容
mcp.ImageContent{
Type: "image",
Data: "base64-encoded-image-data",
MimeType: "image/jpeg",
}工具使用和工具结果内容(2025-11-25)
当采样涉及工具时,对话中的中间消息还可以携带 ToolUseContent(模型要求调用工具)和 ToolResultContent(该调用的结果)—— 这些通过相同的 SamplingMessage.Content 字段流动:
// 助手请求工具调用
mcp.ToolUseContent{
Type: "tool_use",
ID: "tu_1",
Name: "get_weather",
Input: map[string]any{"city": "London"},
}
// 携带工具输出的用户角色消息
mcp.ToolResultContent{
Type: "tool_result",
ToolUseID: "tu_1",
Content: []mcp.Content{mcp.NewTextContent("Sunny, 22°C")},
}使用构造函数 mcp.NewToolUseContent 和 mcp.NewToolResultContent 来构建这些,而无需手动编写 Type 判别器。
错误处理
始终优雅地处理采样错误:
result, err := mcpServer.RequestSampling(ctx, samplingRequest)
if err != nil {
// 记录错误
log.Printf("Sampling request failed: %v", err)
// 返回适当的错误响应
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: "抱歉,此时无法处理您的请求。",
},
},
IsError: true,
}, nil
}上下文和超时
使用上下文进行超时控制:
// 为采样请求设置超时
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result, err := mcpServer.RequestSampling(ctx, samplingRequest)最佳实践
- 尽早启用采样:在服务器初始化期间调用
EnableSampling() - 处理超时:为采样请求设置适当的超时
- 优雅错误:始终向用户提供有意义的错误消息
- 内容提取:使用辅助函数从响应中提取文本
- 系统提示:使用清晰的系统提示来指导 LLM 行为
- 参数验证:在发出采样请求之前验证工具参数
完整示例
这是一个带有采样的完整示例服务器:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// 创建服务器
mcpServer := server.NewMCPServer("sampling-example-server", "1.0.0")
// 启用采样能力
mcpServer.EnableSampling()
// 添加采样工具
mcpServer.AddTool(mcp.Tool{
Name: "ask_llm",
Description: "使用采样向 LLM 提问",
InputSchema: mcp.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"question": map[string]any{
"type": "string",
"description": "要问 LLM 的问题",
},
"system_prompt": map[string]any{
"type": "string",
"description": "可选的系统提示",
},
},
Required: []string{"question"},
},
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
question, err := request.RequireString("question")
if err != nil {
return nil, err
}
systemPrompt := request.GetString("system_prompt", "You are a helpful assistant.")
// 创建采样请求
samplingRequest := mcp.CreateMessageRequest{
CreateMessageParams: mcp.CreateMessageParams{
Messages: []mcp.SamplingMessage{
{
Role: mcp.RoleUser,
Content: mcp.TextContent{
Type: "text",
Text: question,
},
},
},
SystemPrompt: systemPrompt,
MaxTokens: 1000,
Temperature: 0.7,
},
}
// 使用超时请求采样
samplingCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
result, err := mcpServer.RequestSampling(samplingCtx, samplingRequest)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("Error requesting sampling: %v", err),
},
},
IsError: true,
}, nil
}
// 返回 LLM 响应
return &mcp.CallToolResult{
Content: []mcp.Content{
mcp.TextContent{
Type: "text",
Text: fmt.Sprintf("LLM Response (model: %s): %s",
result.Model, getTextFromContent(result.Content)),
},
},
}, nil
})
// 启动服务器
log.Println("Starting sampling example server...")
if err := server.ServeStdio(mcpServer); err != nil {
log.Fatalf("Server error: %v", err)
}
}
// 用于从内容中提取文本的辅助函数
func getTextFromContent(content interface{}) string {
switch c := content.(type) {
case mcp.TextContent:
return c.Text
case string:
return c
default:
return fmt.Sprintf("%v", content)
}
}传输支持
采样支持以下传输:
STDIO 传输
STDIO 传输通过 JSON-RPC 消息传递提供完整的采样支持:
// 使用采样启动 STDIO 服务器
server.ServeStdio(mcpServer)客户端必须在初始化期间实现 SamplingHandler 并公布采样能力。
进程内传输
进程内传输提供最有效的采样实现,具有直接方法调用:
// 使用采样处理程序创建进程内客户端
mcpClient, err := client.NewInProcessClientWithSamplingHandler(mcpServer, samplingHandler)
if err != nil {
log.Fatal(err)
}进程内采样的优势:
- 直接方法调用:无 JSON-RPC 序列化开销
- 类型安全:编译时类型检查
不支持的传输
以下传输目前不支持采样:
- SSE 传输:单向流特性阻止双向采样
- StreamableHTTP 传输:无状态 HTTP 请求不支持采样回调
对于这些传输,考虑直接在工具处理程序中实现 LLM 集成,而不是使用采样。

