7.4 采样与根:已弃用趋势但仍在工作 本节摘要:本节讲两个特殊的交互能力——采样(Sampling) 与 根(Roots)。它们的方向与引导填写相反:引导填写是「服务端问用户要输入」,采样与根是「服务端问客户端要能力」。采样让工具反过来请客户端做一次 LLM 补全(用客户端的模型);根让客户端告知服务端工作区文件夹。这两个能力在 2026-07-28 已显弃用趋势(官方建议用 provider API 与显式参数替代),但仍能工作。本节讲清它们的现状与迁移方向,让你知道它们存在,但不在新代码里过度依赖。 一、采样(Sampling):让工具用客户端的模型 采样(Sampling)的方向很有意思——工具反过来请客户端做一次 LLM 补全。
本节摘要:本节讲两个特殊的交互能力——采样(Sampling) 与 根(Roots)。它们的方向与引导填写相反:引导填写是「服务端问用户要输入」,采样与根是「服务端问客户端要能力」。采样让工具反过来请客户端做一次 LLM 补全(用客户端的模型);根让客户端告知服务端工作区文件夹。这两个能力在 2026-07-28 已显弃用趋势(官方建议用 provider API 与显式参数替代),但仍能工作。本节讲清它们的现状与迁移方向,让你知道它们存在,但不在新代码里过度依赖。
采样(Sampling)的方向很有意思——工具反过来请客户端做一次 LLM 补全。也就是说,服务端的工具执行中,可以请求客户端「用你的模型帮我生成一段文本」。
传统方向: 客户端 → 服务端:执行工具 服务端 → 客户端:返回结果 采样方向(反向): 服务端 → 客户端:请用你的模型,帮我补全这段 prompt 客户端 → 服务端:返回模型生成的文本
典型场景:工具执行中需要 LLM 推理。比如一个「总结文档」工具,它读文档后,可能想让客户端的模型帮忙提炼要点。
@mcp.tool() async def summarize_doc(doc_uri: str, ctx: Context) -> str: """Summarize a document, using the client's model.""" doc = await ctx.read_resource(doc_uri) # 采样:请客户端用它的模型总结 result = await ctx.session.sample( messages=[{"role": "user", "content": f"总结:{doc}"}], model_preferences=["claude-sonnet"], ) return result.content
服务端的工具,用了客户端的模型——这是采样的独特之处。
根(Roots)让客户端告知服务端「我的工作区是哪些文件夹」。服务端据此知道「该操作哪些目录」。
@mcp.tool() async def scan_project(ctx: Context) -> dict: """Scan the current project.""" # 拿客户端告知的根 roots = await ctx.session.list_roots() # roots 是一组文件夹路径 return scan_files(roots)
典型场景:文件操作工具。客户端(如 IDE)告诉服务端「我的项目在 /home/user/project」,服务端的文件工具就只操作这个目录,不乱跑。
采样与根在 2026-07-28 已显弃用趋势(SEP-2577)。为什么?几个原因:
| 能力 | 弃用原因 | 官方推荐替代 |
|---|---|---|
| 采样 | 让服务端依赖客户端的模型,职责混乱;模型选择应显式 | 用 provider API(服务端自带模型客户端) |
| 根 | 隐式传递上下文,易出错;应用应显式传参数 | 显式参数(工具接受路径参数) |
核心思想:服务端应该自包含,不该依赖客户端的隐式能力。采样让服务端依赖客户端的模型(隐式),根让服务端依赖客户端的工作区(隐式)——这些都让服务端行为不可预测、难测试。
⚠️ 注意:「弃用趋势」不等于「立刻不能用」。这两个能力在当前版本仍工作,SDK 仍支持。趋势是「新代码别过度依赖,官方未来可能进一步弱化」。如果你的存量代码用了它们,暂时不用急着拆,但新设计要考虑替代方案。
采样原本解决「工具执行中需要 LLM 推理」。官方推荐的替代是服务端自带模型客户端(provider API):
采样方式(弃用趋势): 工具执行中 → ctx.session.sample() → 用客户端的模型 问题:服务端依赖客户端的模型,行为不可预测 provider API 方式(推荐): 服务端启动时配置自己的模型客户端 工具执行中 → 直接用自己的模型客户端 优势:服务端自包含,行为可预测、可测试
# 概念性:服务端自带模型客户端 @mcp.tool() async def summarize_doc(doc_uri: str, ctx: Context) -> str: doc = await ctx.read_resource(doc_uri) # 用服务端自己的模型客户端(而非采样) summary = await my_model_client.complete(f"总结:{doc}") return summary
这种写法让服务端自包含——它有自己的模型,不依赖客户端。行为可预测(测试时用自己的模型)、部署可控(生产用配置的模型)。
根原本解决「服务端知道操作哪些目录」。官方推荐的替代是显式参数:
根方式(弃用趋势): 客户端告知 roots → 工具隐式用 roots 问题:工具行为依赖隐式状态,难测试、易出错 显式参数方式(推荐): 工具接受路径参数 → 模型/客户端显式传路径 优势:工具自包含,调用清晰
# 显式参数方式 @mcp.tool() async def scan_project(project_path: str) -> dict: """Scan a project at the given path.""" return scan_files(project_path) # 路径显式传入,不用 roots
模型调用时显式传 project_path,工具不依赖任何隐式状态。这种写法更清晰、更易测试。
给新代码的明确建议:
| 能力 | 新代码建议 |
|---|---|
| 采样 | 别用。需要 LLM 推理时,服务端自带模型客户端(provider API) |
| 根 | 慎用。需要目录信息时,用显式路径参数 |
| 能力 | 存量代码建议 |
|---|---|
| 采样 | 暂时保留,但在路线图里规划迁移到 provider API |
| 根 | 暂时保留,逐步把隐式依赖改成显式参数 |
💡 技巧:本节的重点是「知道这两个能力存在,但别在新代码里依赖」。如果你看到老教程或存量代码用了采样/根,理解它们在干什么;但自己写新工具时,优先用 provider API(采样)与显式参数(根)。附录的 v1→v2 迁移要点会再次提到这个趋势。
既然弃用,为什么还要花一节讲?三个理由:
但讲完要强调:主流方向是 provider API 与显式参数。把这两个能力当「历史包袱」对待,而不是「新工具」。
采样与根清楚了,最后一节讲进度上报与日志——单向通知,与引导填写的「需要回答」形成对照。