标准 MCP 是"客户端调用服务端的工具";而 Sampling(采样)把方向反过来——服务端在一个工具执行到一半时,向客户端发一个 sampling/createMessage 请求,请客户端用自己的模型推理一次,再把结果送回服务端。这样服务器本身不用持有任何 API Key,也能"借用"宿主的模型能力。本教程用官方 mcp 包的 FastMCP 写一个会反问模型的服务器,并配一个带 sampling 回调的客户端把它跑通,全程可复现。
先搞懂:Sampling 把请求方向反了过来
普通流程:客户端 → 调用服务端工具 → 服务端返回结果。Sampling 流程:客户端调用工具 → 服务端执行中途 → 服务端向客户端请求"帮我用模型推理一下" → 客户端用自己的 Key 跑模型 → 结果回传服务端 → 服务端继续干完活返回。
| 方向 | 谁发起 | 谁付模型费用 |
|---|---|---|
| 普通工具调用 | 客户端 | 不涉及模型 |
| Sampling | 服务端(中途) | 客户端(宿主模型) |
版本红线:自 2026-07-28 起,sampling/createMessage 被标记为 deprecated(SEP-2577),但在 2025-11-25 版本下仍完全可用。新服务器建议直接调 LLM 提供方 API;只有在"不想让服务器持有密钥"的场景才用 Sampling。客户端若在现代化连接上声明 sampling 能力,会收到弃用警告。
Step 1:装 mcp 包并建项目
用官方 mcp 包(含 FastMCP)。注意:还有一个独立的 fastmcp v2 包,API 不同,本教程统一用 mcp.server.fastmcp。
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "mcp[cli]"
fastmcp.run(transport="stdio")。调试时加 mcp dev server.py 可用 Inspector 查看 sampling 请求是否真的发出。Step 2:写会 Sampling 的 Server
新建 server.py。工具的参数里加一个 ctx: Context,框架会自动注入;在工具内部用 await ctx.session.create_message(...) 反向请求一次模型推理。下面这个工具让服务器"借"客户端的模型把一段文本总结一下。
from mcp.server.fastmcp import FastMCP, Context
from mcp.types import SamplingMessage, TextContent
mcp = FastMCP(name="sampling-demo")
@mcp.tool()
async def summarize(text_to_summarize: str, ctx: Context) -> str:
"""借用宿主模型总结一段文本,服务端本身不持有任何 LLM Key。"""
result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(
type="text",
text=f"请用一句话总结以下内容:\n{text_to_summarize}",
),
)
],
max_tokens=4000,
system_prompt="你是一个严谨的研究助手,只输出总结,不发挥。",
)
if result.content.type == "text":
return result.content.text
raise ValueError("Sampling 返回内容不是文本")
if __name__ == "__main__":
mcp.run(transport="stdio")
不可信来源勿开 Sampling:一个恶意或被攻破的服务器可以在 messages / systemPrompt 里塞入提示注入,借你的模型外泄上下文或篡改行为。只在你审查过的服务器上允许 sampling 能力。
Step 3:写带 sampling 回调的 Client
客户端要提供一个 sampling 回调,真正去跑模型。下面用 OpenAI 兼容接口举例;回调签名在新版为 (context, params) 形式。
import os
from openai import OpenAI
from mcp import ClientSession, types
client = OpenAI() # 读环境变量 OPENAI_API_KEY
async def handle_sampling(context, params: types.CreateMessageRequestParams):
# 客户端用自己的 Key 跑推理,返回结果给服务器
resp = client.chat.completions.create(
model="gpt-4.1-mini",
max_tokens=params.max_tokens or 1000,
messages=[m.model_dump() for m in params.messages],
)
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(type="text", text=resp.choices[0].message.content),
model="gpt-4.1-mini",
stop_reason="endTurn",
)
modelPreferences 只是"建议"(hints),客户端可忽略。响应里的 model 字段返回实际跑的模型名,服务器可据此感知差异。Step 4:串起来跑通
客户端在 initialize 握手时,必须在 capabilities 里声明 sampling: {},否则服务器发 sampling/createMessage 会报错。用 stdio 拉起服务器并传入回调:
async with sdio_server.server() as (read, write):
async with ClientSession(read, write, sampling_callback=handle_sampling) as session:
await session.initialize() # 握手声明 sampling 能力
result = await session.call_tool("summarize", {"text_to_summarize": "一段很长的原文..."})
print(result.content[0].text)
human-in-the-loop 是规范要求的闸:宿主在真正跑推理前应弹出审批,让用户允许或拒绝这次 sampling 请求。这既是协议 SHOULD 级要求,也是防止恶意服务器滥用的主要防线。
Step 5:给 Sampling 加结构与约束
create_message 还支持系统提示、模型偏好和上下文范围。把一次需要的判断尽量放进同一条请求,减少来回推理轮次。
result = await ctx.session.create_message(
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=512,
system_prompt="你是安全审查员,只返回与鉴权、输入校验、数据暴露相关的发现。",
model_preferences=types.ModelPreference(
hints=[types.ModelHint(name="claude")],
intelligence_priority=0.8,
speed_priority=0.2,
cost_priority=0.0,
),
include_context="thisServer",
)
includeContext 控制是否把 MCP 服务器上下文注入提示;多数场景用 none 或 thisServer。每次 sampling 都加至少一轮推理延迟,能批量问的就别拆成多次。什么时候该用 Sampling
Sampling 适合"工具本身需要判断、而非确定性逻辑"的场景:给抓回的网页做分类、在构建工具里判断哪些编译错误值得处理、给研究工具把原始结果先总结、监控工具决定日志归哪类告警。它能把这些"判断"路由到宿主模型,而不必让服务器自带 LLM 客户端。
与 Elicitation 区分:Elicitation 是向"用户"要结构化输入;Sampling 是向"模型"要推理。两者都会打断确定性工具执行,但目的不同——一个要人的判断,一个要模型的判断。需要确定性结果时,直接写代码,别用 Sampling。
常见问题
| 现象 | 原因与处理 |
|---|---|
| 报错 sampling 能力未声明 | initialize 的 capabilities 里漏了 sampling: {} |
| 返回的不是文本 | 检查 result.content.type,按需处理 image/audio |
| 同一个工具结果不一致 | 不同宿主模型能力不同,这是预期;用返回 model 名做适配 |