MCP 最强大的特性之一就是取样(sampling) 。这到底是什么意思?先看词典定义,再类推出发。Merriam-Webster 将 sampling 定义为:
“为分析而对某物进行取样的行为或过程(the action or process of taking samples of something for analysis)”
也就是说,我们需要一个“样本”,并对该样本进行分析。带着这个理解,再谈 MCP 语境下的含义:在 MCP 中,sampling 指服务器向客户端发送一个“取样请求”(供分析的样本) 。为什么服务器要这么做?很简单:服务器需要客户端在某些事情上提供帮助。因为客户端通常拥有 LLM(尽管有时服务器端也会有),所以服务器会把任务委托给客户端,由客户端的 LLM 来协助完成。
到这里应当说得通了吧?你可能会继续问:服务器为什么要这么做。
在本章中,我们将:
- 理解 sampling 的主题及其使用场景
- 构建一个使用 sampling 的服务器实现,并在 VS Code 中消费它
- 当我们需要把此能力集成进自己的应用时,把服务器实现与客户端实现连接起来
本章涵盖以下主题:
- 为什么需要 sampling?
- 如何实现 sampling
为什么需要 sampling?
正如开头所说,服务器希望把部分问题委托给客户端,尤其是委托给客户端的 LLM。有哪些问题适合让 LLM 处理、而服务器不擅长?举不胜举:生成商品描述、摘要、标签等等。
先看取样流程,从高层理解参与方如何交互。
取样流程(Sampling flow)
执行 sampling 时涉及以下参与者:
- User(用户) :通常在两处参与:一是发起初始动作;二是作为 human-in-the-loop(人在回路中),接受或修改取样请求。
- Server(服务器) :发送取样请求的一方。该请求通常由某个服务器特性触发,如调用工具、读取资源或从提示模板(prompt)发起请求。
- Client(客户端) :接收取样请求并展示给用户,让用户决定如何处理。用户把取样请求当作“建议”。如果请求中指定了具体的模型、token 数等,用户可以参考,并选择接受或修改。
- LLM:位于客户端,根据服务器发来的取样请求完成生成——它接收来自服务器的提示词并产出回答。
需要澄清的是:取样请求不是无缘无故发生,而是由一个初始动作触发。例如用户要创建一个产品,或需要撰写博客,这就促使服务器把部分工作再委托回客户端。
下面用几个具体场景帮助理解。
场景示例
1)撰写博文
写博客的过程很适合作为案例:有些部分一定由用户承担(比如写初稿),但也有些部分 LLM 更擅长(比如生成摘要或提炼关键词,此用例灵感来自 Kent Dodds):
- 用户将博文初稿提交给服务器;
- 服务器保存草稿,但请求客户端生成标签,于是发送取样请求;
- 客户端用其 LLM 分析草稿并生成响应。
2)电商后台
电商后台常见任务是商品管理。通常先登记标题等属性,但写一段有吸引力的描述既耗时又费力,LLM 往往更擅长:
- 管理员通过客户端新增商品,提供标题与关键词;
- 服务器请求客户端基于关键词生成吸引人的商品描述;
- 客户端产出描述,服务器据此更新商品信息。
3)解谜类游戏
游戏中你会与 NPC(非玩家角色)对话,但 NPC 的对白常常受限,影响体验。此时 LLM 能显著增强互动:
- 用户请求与某角色对话;
- 服务器检索角色信息(姓名、人物设定、动机、线索等)并作为取样请求发送;
- 客户端将这些信息作为“系统消息”,由 LLM 生成更自然的对话回应。
理解合适场景后,我们需要看看消息长什么样,也就是请求往返时携带的内容与可配置项。更重要的是:当服务器向客户端发送取样请求时,可以传递哪些指导信息。
消息(Messages)
如果你在用 SDK,几乎不会直接与 JSON-RPC 打交道,但了解取样请求里能放什么很有价值。下面是一个示例请求:
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Write a compelling description of this product:\n tomato, here's some keywords: red, vegetable, fresh"
}
}
],
"modelPreferences": {
"hints": [
{ "name": "claude-3-sonnet" }
],
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"systemPrompt": "You're a professional writing assistant and\n tend to want to write descriptions in a poetic way",
"maxTokens": 100
}
}
其中几点尤其重要:
messages:发送给 LLM 的消息内容。modelPreferences:模型偏好(建议值)。最终由用户决定,但可以在此给出推荐;还可以设置如intelligencePriority与speedPriority等权衡参数。systemPrompt:系统提示,决定 LLM 的“人格/角色”,对输出影响极大。maxTokens:此次任务可使用的 token 上限。
客户端返回示例:
Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
}
可以看到,LLM 的文本结果在 content 中返回,同时也告知了实际采用的模型等信息。
实现 Sampling
现在到了本章里更令人兴奋的部分——如何实现 sampling。
我们将覆盖以下实现部分:
- 服务端:如何把 sampling 加到 MCP 服务器
- 客户端:如何启用、接收请求并发送响应的代码示例
服务端实现
在服务端实现 sampling,需要考虑何时发起 sampling 请求。通常 sampling 不是凭空发生,而是在某个动作的上下文中触发。设想这样一个场景:某电商后台的运营人员新增待售商品,需要一段尽可能有吸引力的描述。于是描述部分交给客户端及其 LLM 来生成。流程如下:
- 用户的客户端调用服务器上的一个工具,请求创建新商品。
- 该工具向客户端派发一个取样请求(sample request) ,其中包含“要做什么”的指令(一个 prompt)。
- 客户端从取样请求中取出 prompt,调用它的 LLM,返回答案:
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession
from mcp.types import SamplingMessage, TextContent
from uuid import uuid4
mcp = FastMCP(name="Sampling Example")
products = []
@mcp.tool()
async def create_product(product_name: str, keywords: str,
ctx: Context[ServerSession, None]) -> str:
"""Create a product and generate a product
description using LLM sampling."""
# 1. 创建一个新商品
product = { "id": uuid4(), "name": product_name, "description": "" }
prompt = f"Create a product description about {keywords}"
# 2. 构造 sampling 消息,把 prompt 作为载荷传给客户端
result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(type="text", text=prompt),
)
],
max_tokens=100,
)
product["description"] = result.content.text
products.append(product)
# 返回完整的商品
return product
在上面第 1 与 2 步中,注意通过 ctx(上下文)对象调用 session.create_message,并传入 SamplingMessage(role="user")与 messages(要发给客户端的 prompt):
result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(type="text", text=prompt),
)
],
max_tokens=100,
)
if __name__ == "__main__":
print("Starting server…")
mcp.run()
还要注意:服务器会等待客户端返回,然后再继续。客户端返回后,我们把结果写入商品描述,最后返回商品对象:
product["description"] = result.content.text
products.append(product)
# return the complete product
return product
在 VS Code 中测试:
-
在
mcp.json中添加服务器项:"sample-server": { "command": "python", "args": ["path/to/server/sample-server.py"] } -
在该条目顶部点击 Start Server 启动服务器。
-
需要选择 sampling 可用的模型:打开 Extensions 视图,在最下方 MCP Servers – installed,点击齿轮图标 Configure Model Access,勾选允许用于 sampling 的模型(如 Claude Sonnet)。
-
打开 VS Code 的 GitHub Copilot Chat,确保选择 Agent 模式(顶部图标或命令面板
Chat: Open Chat)。输入:create product called tomato with keywords red and vegetable and delicious你会看到一个申请运行的对话框;允许后,会得到来自
create_product的工具响应。其底层过程是:-
Prompt 被解析,传给工具的参数为:
{ "keywords": "red, vegetable, delicious", "product_name": "tomato" } -
调用了
create_product工具。 -
服务器向客户端发出 sampling 请求;VS Code 客户端使用自身的 LLM 生成响应,例如(示意图 Figure 9.2):
-
示例生成描述:
> Introducing our **Red Garden Medley** …(略)
这就实现了在 MCP 服务器侧启用 sampling,并由 VS Code 作为客户端配合完成。
客户端实现
首先需要让服务器知道客户端支持 sampling。在创建客户端实例时传入能力配置,例如:
{
"capabilities": {
"sampling": {}
}
}
另外,sampling 的消息流不走“常规工具/资源/提示”的通道。也就是说,你需要额外监听 sampling 请求:
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write,
sampling_callback=handle_sampling_message # 关键:回调
) as session:
await session.initialize()
# 后续可正常调用工具、资源、提示等
sampling_callback 的处理逻辑如下:
async def call_llm(prompt: str, system_prompt: str) -> str:
client = OpenAI(
base_url="https://models.github.ai/inference",
api_key=os.environ["GITHUB_TOKEN"],
)
response = client.chat.completions.create(
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
model="openai/gpt-4o-mini",
temperature=1,
max_tokens=200,
top_p=1
)
return response.choices[0].message.content
async def handle_sampling_message(
context: RequestContext[ClientSession, None],
params: types.CreateMessageRequestParams
) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}")
# 1. 解析传入的任务提示
message = params.messages[0].content.text
# 2. 调用 LLM 获得回应
response = await call_llm(
message,
"You're a helpful assistant, keep to the topic, don't make things up too much but definitely create a compelling product description"
)
# 3. 构造并返回取样响应
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(type="text", text=response),
model="gpt-3.5-turbo",
stopReason="endTurn",
)
三点要点:
- 解析 sampling 请求中的 prompt(比如“请生成商品描述”)。
- 调用 LLM 获取响应。
- 封装为取样响应返回给 MCP 服务器。
该示例的完整运行说明见:
github.com/PacktPublis…
示例运行输出(节选):
[08/16/25 19:31:40] INFO Processing request of type CallToolRequest
Sampling request: [SamplingMessage(... 'Create a product description about paprika described by as red, juicy, vegetable' ...)]
...
result: {
"id": 1,
"name": "paprika",
"description": "**Product Description: Paprika – The Vibrant Touch of Flavor**\n\nElevate your culinary creations ..."
}
客户端做了两件事:
- 调用
create_product工具(带产品名和关键词),并用 LLM 生成了商品描述(示例略)。 - 调用
get_products工具,列出刚新增的商品。
可见,这是一种将更合适的工作下放给客户端的好方式。
小结
“Sampling”的字面含义是对样本进行分析;在 MCP 中,它体现为服务器把部分工作委派给客户端。通常由用户的某个操作触发(写博客、在后台创建产品等),服务器据此创建取样请求交给客户端处理,客户端再用 LLM 给出响应。请求中还可以建议模型、token 上限、system prompt 等,用户可按需接受或修改。借助客户端的 LLM,服务器能在需要时获得强力辅助。
下一章将介绍 MCP 的另一个强大特性 elicitation(引导澄清) ,通过交互式提问让用户补充选择或信息,从而帮助服务器更好地完成工作、优化用户体验。