首页/新闻资讯/正文详情

MCP Python SDK 依赖注入实战:用 Resolve 让工具参数对模型不可见

发布时间:2026/9/21 1:43:41 来源:云帆数科 栏目:资讯中心
MCP Python SDK 依赖注入实战:用 Resolve 让工具参数对模型不可见
MCP Python SDK 依赖注入实战用 Resolve 让工具参数对模型不可见【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk工具tool的参数由模型LLM提供但有些值绝不应该来自模型从你的记录里查出来的价格、只有人能给出的确认、任何模型靠编造就可能搞错的东西。本文介绍 python-sdkModel Context Protocol 官方 Python SDK中MCPServer提供的**依赖注入Dependencies**机制用Annotated[T, Resolve(fn)]声明参数由你自己的函数填充SDK 在工具运行前自动调用该函数。读完本文你将掌握如何声明单个依赖、让依赖互相嵌套、在必须时向用户提问Elicit、以及向客户端请求 LLM 采样与 rootsSample/ListRoots并理解这些能力在 2026-07-28 与 2025-11-25 两代协议下的行为差异。核心概念什么是 DependencyMCP 工具的参数来自模型。但有一类值模型永远不该负责提供——它们一旦被模型脑补就会出错从你的业务记录中查出的价格、库存只有真人才能给出的确认如确定要下这个订单吗任何身份、权限类信息。**Dependency依赖**就是由你自己的函数填充的参数你给参数加上注解、指明函数名SDK 会在工具 body 执行之前调用该函数并把返回值注入参数。如果你用过 FastAPI这就是它的Depends。同样的思路、同样的理由函数声明自己需要什么框架负责提供所有 wiring 都留在类型注解里无需任何注册表。该机制在源码中对应 resolve.py 模块Resolve、Elicit、Sample、ListRoots四个 marker 均定义于此并在 test_dependencies.py 中有逐条可验证的测试覆盖。声明一个依赖Annotated[T, Resolve(fn)]把参数的类型包进Annotated[...]并加上Resolve(fn)即可。以书店铺货为例完整代码见 tutorial001.pyfrom typing import Annotated from pydantic import BaseModel from mcp.server import MCPServer from mcp.server.mcpserver import Resolve mcp MCPServer(Bookshop) INVENTORY {Dune: 7, Neuromancer: 0} class Stock(BaseModel): title: str copies: int async def check_stock(title: str) - Stock: return Stock(titletitle, copiesINVENTORY.get(title, 0)) mcp.tool() async def reserve_book(title: str, stock: Annotated[Stock, Resolve(check_stock)]) - str: Reserve a copy of a book. if stock.copies 0: return f{title!r} is out of stock. return fReserved {title!r} ({stock.copies - 1} copies left).理解这段代码的三个关键点check_stock是resolver一个普通函数SDK 会在reserve_book之前运行它其返回值成为stock参数resolver 的title参数就是工具自己的title参数按名称匹配。resolver 看到的正是工具 body 将看到的同一份已校验validated值工具 body 一开始就拿到一个已存在的Stock——工具里没有查询代码没有万一查不到怎么办的前置处理。对模型不可见这是最值得内化的部分。下面是tools/list为reserve_book报告的输入 schema{ type: object, properties: { title: {title: Title, type: string} }, required: [title], title: reserve_bookArguments }只有一个 property。和 Context 一样被 resolve 的参数是你与 SDK 之间的约定stock不在 schema 里模型永远不会被告知它的存在即使客户端强行发送stock值也会被忽略。resolver 的值是工具唯一能收到的值。最后这一点正是关键所在模型无法提供的参数模型也就无法弄错。测试 test_dependencies.py 中test_a_client_supplied_value_for_a_resolved_parameter_is_ignored验证了这一点——客户端即使传入stock: {copies: 999}工具收到的仍是 resolver 自己算出的值。动手尝试用 MCP Inspector 启动服务器uv run mcp dev server.pyreserve_book的表单里只有一个title字段stock根本不在上面。用Dune调用Reserved Dune (6 copies left).工具 body 没有做过任何查询check_stock先运行返回的Stock作为参数送达。试试Neuromancer同一个 resolver 会给工具递上一个零库存。提示你也可以在工具 body 里直接调用check_stock(title)。但当一个值值得比一次辅助调用更高的待遇时就把它声明为 dependency每个需要库存的工具都声明同一个参数而无论多少工具声明它SDK 每次调用最多只运行一次该 resolver。接下来的小节会补充其余部分互相依赖的 resolvers以及向用户提问的 resolvers。依赖的依赖resolver 构成 DAGresolver 可以用同样的注解声明自己的依赖。见 tutorial002.pyfrom typing import Annotated from pydantic import BaseModel from mcp.server import MCPServer from mcp.server.mcpserver import Resolve mcp MCPServer(Bookshop) INVENTORY {Dune: 7, Neuromancer: 0} class Stock(BaseModel): title: str copies: int async def check_stock(title: str) - Stock: return Stock(titletitle, copiesINVENTORY.get(title, 0)) async def estimate_delivery(stock: Annotated[Stock, Resolve(check_stock)]) - str: return tomorrow if stock.copies 0 else in 2-3 weeks mcp.tool() async def order_book( title: str, stock: Annotated[Stock, Resolve(check_stock)], delivery: Annotated[str, Resolve(estimate_delivery)], ) - str: Order a book from the shop. if stock.copies 0: return f{title!r} is on backorder; it would arrive {delivery}. return fOrdered {title!r}; it arrives {delivery}.三个要点estimate_delivery依赖check_stock。SDK 按图graph顺序执行先 stock再 estimate最后工具stock和delivery最终都需要check_stock但它每次调用只运行一次——一次库存查询两个消费者没有任何需要注册的东西注解本身就是这张图。每次调用一次不是空话可以自己验证在check_stock里放一个print从 Inspector 调用order_book——每次调用只打一行。两个消费者一次查询。测试test_a_shared_dependency_runs_once_per_call用计数器库存对象证明了这一点并且确认记忆化memoization是按调用而非按服务器生命周期生效下一次tools/call会再次运行check_stock。坏图在注册时失败而不是调用中途SDK 在工具注册时分析依赖图而不是调用时。以下两种情况都会在启动时抛出InvalidSignature某个参数无法归类——既不是Context也不是Resolve(...)也不是某个工具参数的名字resolver 之间存在循环依赖。服务器会在任何客户端连接之前就失败错误信息中带有出问题的参数或 resolver 的名字。从源码看这一分析实现在 resolve.py 的build_resolver_plans第 347 行起它递归遍历每个Resolve标记引用的函数用stack检测环第 365 行无法分类的参数直接 raiseInvalidSignature第 392 行。resolver 的参数如何解析resolver 的参数与工具参数完全一样地解析可以是另一个Resolve(...)、按名称取工具自己的参数或Context——ctx.headers、lifespan 对象全部可用。需要注意一个安全点在 HTTP transports 上Context中包含ctx.headers。headers 是客户端提供的输入与任何工具参数无异用来传 locale 或 feature flag 没问题但永远不要用来做身份识别。调用者是谁应该由你的授权层见 Authorization决定而不是由任何人都能设置的 header 决定。另外每次调用一次意味着下一次tools/call会重新运行check_stock。需要跨请求存活的资源——数据库连接池、HTTP client——应该放在 Lifespan 中resolver 通过ctx.request_context.lifespan_context访问它。只在必要时提问Elicitresolver 不一定要知道答案。它可以返回Elicit(message, Model)SDK 会替你运行 Elicitation 机制向用户提问。见 tutorial003.pyfrom typing import Annotated from pydantic import BaseModel, Field from mcp.server import MCPServer from mcp.server.mcpserver import Elicit, Resolve mcp MCPServer(Bookshop) INVENTORY {Dune: 7, Neuromancer: 0} class Stock(BaseModel): title: str copies: int class Backorder(BaseModel): confirm: bool Field(descriptionOrder anyway and wait?) async def check_stock(title: str) - Stock: return Stock(titletitle, copiesINVENTORY.get(title, 0)) async def confirm_backorder( title: str, stock: Annotated[Stock, Resolve(check_stock)], ) - Backorder | Elicit[Backorder]: if stock.copies 0: return Backorder(confirmTrue) # in stock: nothing to ask return Elicit(f{title!r} is out of stock (2-3 weeks). Order anyway?, Backorder) mcp.tool() async def order_book( title: str, stock: Annotated[Stock, Resolve(check_stock)], backorder: Annotated[Backorder, Resolve(confirm_backorder)], ) - str: Order a book from the shop. if not backorder.confirm: return No order placed. if stock.copies 0: return fBackordered {title!r}; it ships in 2-3 weeks. return fOrdered {title!r}.三个关键行为有库存confirm_backorder直接返回Backorder。没有问题没有往返。用户只在答案真正重要时才被打断无库存SDK 发送 elicitation按Backorderschema 校验答案并注入。你的 resolver 从不接触协议工具像读其他参数一样读backorder.confirm。回答no也是一种回答elicitation 以confirmFalse被接受工具运行但不下单。提问成了前置条件precondition而不是工具 body 里的管道代码。测试 test_dependencies.py 在legacy与auto两种模式下分别验证了有库存时elicitation_callback绝不会被调用test_an_in_stock_order_asks_no_question、无库存时会收到确切的提问文案并按 accept/decline 正确处理。用户拒绝或取消怎么办如果用户干脆不回答——decline 或 cancel 问题会怎样把注解写成Annotated[Backorder, Resolve(...)]时工具 body 永远不会运行调用会以模型可读的错误结果失败Error executing tool order_book: Resolver for parameter backorder could not resolve: elicitation was decline这是前置条件的正确默认值没有答案就没有订单。当 decline 是工具想自行处理的结局时——比如跳过 backorder 但仍然推荐另一本书——应改用ElicitationResult[Backorder]注解工具会收到完整的 accept/decline/cancel 结果并自行分支。ElicitationResult的三种成员AcceptedElicitation、DeclinedElicitation、CancelledElicitation定义在 resolve.py 中_unwrap第 645 行正是产生上面那条错误信息的地方。更多细节见 Elicitationschema 规则、三种回答、对话的客户端一侧。提问的传输方式取决于协议版本框架根据协商出的协议版本选择提问的传输方式上面的代码在两种版本上完全一致2026-07-28 及之后问题搭载在一次多轮往返multi-round-trip的tools/call内部——服务器返回问题客户端的elicitation_callback作答Client替你重试调用见 Multi-round-trip requests2025-11-25 及之前在调用中途发送一次同步的 elicitation 请求。每个问题每次调用恰好被问一次——这是关于问题的保证不是关于 resolver 的。在多轮往返形式下每当调用在某问题之后恢复时任何 resolver 都可能再次运行因此return Elicit(...)之前的代码会在每一轮都执行已记录的答案随后满足重复的问题而不必再次打扰用户。已记录答案只在 resolver 提问时才会被查阅一个不提问就作答的 resolver如check_stock总是提供自己计算出的值。由于每个答案都会与它的问题匹配回去进行 elicitation 的 resolver 必须从工具参数和之前的答案确定性地构建问题。每次调用生成的值如default_factory生成的 id、时间戳每一轮都会重新生成绝不能出现在答案要绑定的问题中——由这种易变数据构成的问题会让每个已记录答案都显得过期于是服务器每轮重问直到客户端的轮次上限结束调用。问客户端而不是问用户Sample与ListRootsElicitation 是 resolver 能提出的三种问题之一multi-round-trip 流程不允许其他问题。另外两种问题面向客户端而不是用户返回Sample(...)通过客户端运行一次 LLM 调用一次sampling/createMessage请求返回ListRoots()获取客户端当前的 roots。两者都没有 accept/decline 结局消费方直接注解结果类型——CreateMessageResult当请求携带tools或tool_choice时为CreateMessageResultWithTools或ListRootsResult。示例见 tutorial004.pyfrom typing import Annotated from mcp.server import MCPServer from mcp.server.mcpserver import Resolve, Sample from mcp.types import CreateMessageResult, SamplingMessage, TextContent mcp MCPServer(Bookshop) def suggest_title(genre: str) - Sample: prompt fSuggest one {genre} book title. Answer with the title only. return Sample( [SamplingMessage(roleuser, contentTextContent(typetext, textprompt))], max_tokens50, ) mcp.tool() async def recommend_book( genre: str, suggestion: Annotated[CreateMessageResult, Resolve(suggest_title)], ) - str: Recommend a book in the given genre. title suggestion.content.text if suggestion.content.type text else the classics return fTodays {genre} pick: {title}要点框架像路由Elicit一样路由它们2026-07-28上在 multi-round-triptools/call内部2025-11-25上通过独立的 server→client 请求。未声明的能力会以-32021协议错误拒绝该调用sampling、roots、form 模式的elicitation当请求携带tools或tool_choice时是sampling.tools。能力校验实现在_require_capabilityresolve.py 第 673 行前面 info box 关于问题的所有论述原样适用Sample请求按精确渲染与其记录结果匹配所以要基于工具参数和之前的答案确定性地构建这样客户端只为每次工具调用付一次 LLM 调用的钱而不是每轮付一次。记录结果在调用剩余部分随request_state一起传递因此一个非常大的 completion 会让剩下的每一轮往返都更重独立的 sampling 和 roots特性在 2026-07-28 已弃用SEP-2577。需要客户端模型的新服务器通过这个 carrier 提问不需要的服务器应直接与 LLM provider 集成。none以外的include_context值本身已弃用应避免使用。Sample的构造参数max_tokens、system_prompt、include_context、temperature、stop_sequences、metadata、model_preferences、tools、tool_choice定义在 resolve.py 第 131 行起底层封装为CreateMessageRequestParams。总结工具参数上的Annotated[T, Resolve(fn)]SDK 运行fn并注入其返回值被 resolve 的参数对模型不可见客户端无法提供。模型不该编造的值——价格、身份、权限——就该放在这里resolver 的参数以同样方式解析Context、另一个Resolve(...)、或按名称取工具参数。无论有多少消费者依赖图每轮最多运行每个 resolver 一次每个问题恰好被问一次调用在某问题后恢复时任何 resolver 都可能再次运行坏图在注册时以InvalidSignature失败而不是在调用中途只在必要时返回Elicit(message, Model)向用户提问。未包装的注解在 decline 时中止调用ElicitationResult[T]让工具自行分支返回Sample(...)或ListRoots()向客户端请求 LLM completion 或 roots 列表普通结果被直接注入。服务器在启动时创建一次、并由 handler 访问的状态见 Lifespan 页面。【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

SOEM驱动汇川SV660N伺服的实战指南:物理层、PDO映射与DC同步
SOEM驱动汇川SV660N伺服的实战指南:物理层、PDO映射与DC同步

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/21 1:42:40

numpy-ml 高斯过程回归指南:GPRegression 完整实现、预测与采样原理
numpy-ml 高斯过程回归指南:GPRegression 完整实现、预测与采样原理

机器学习人工智能 【免费下载链接】numpy-ml Machine learning, in numpy 项目地址: https://gitcode.com/gh_mirrors/nu/numpy-ml 点击查看 免费下载 高斯过程回归(Gaussian Process Regression,GPR)是一种非参数贝叶斯回归方法… · 2026/9/21 1:42:40

xi-editor 插件开发起步:基于 Rust 的 sample-plugin 模板解析与安装实战
xi-editor 插件开发起步:基于 Rust 的 sample-plugin 模板解析与安装实战

xi-editor 插件开发起步:基于 Rust 的 sample-plugin 模板解析与安装实战 【免费下载链接】xi-editor A modern editor with a backend written in Rust. 项目地址: https://gitcode.com/gh_mirrors/xie/xi-editor 本篇指南以 xi-editor(后端由 R… · 2026/9/21 1:42:40

Python+torch实现PINN求解二维Helmholtz方程:从低频到高频的实战指南
Python+torch实现PINN求解二维Helmholtz方程:从低频到高频的实战指南

第一次把PINN跑通的时候,说实话没有太多成就感,因为在二维Helmholtz方程上它表现得相当一般。当方程里的波数k从7提到15,普通多层感知机的解就开始“摆烂”,损失曲线降不下去,数值解和解析解差得离谱。折腾一段时间后我… · 2026/9/21 2:22:47

AI桌面助手自动执行与权限管理实战:安全与效率如何平衡
AI桌面助手自动执行与权限管理实战:安全与效率如何平衡

"允许访问这个文件夹吗?"2026年,几乎所有主流AI桌面助手首次启动时都会弹出这句授权请求。对比2023年那个"只会写诗聊天"的AI,你手里的桌面助手如今会读文件、改配置、运行命令、批量删除重复文件,甚至自己写… · 2026/9/21 2:22:47

极摩客迷你主机本地AI部署指南:从内存核显到Ollama实战
极摩客迷你主机本地AI部署指南:从内存核显到Ollama实战

最近身边折腾本地 AI 的朋友明显多了,以前找我配电脑都是先问显卡显存、电源瓦数,最近画风全变了:上来就问能不能在自己家里跑 DeepSeek,聊天记录不想出本机,公司文档想整理成私有知识库,还有人想把本地模型… · 2026/9/21 2:22:47

ESD保护版图设计核心细节:从电流路径到镇流电阻的实战指南
ESD保护版图设计核心细节:从电流路径到镇流电阻的实战指南

简介:面向集成电路设计与可靠性工程师的ESD(静电放电)保护专题文档,系统梳理静电放电对CMOS芯片的危害机理,并围绕接地栅NMOS(GGNMOS)器件物理分析,详解ESD保护结构的设计原理、版图… · 2026/9/21 2:22:47

CAN总线实战指南:STM32多节点实时通信系统搭建与避坑全记录
CAN总线实战指南:STM32多节点实时通信系统搭建与避坑全记录

简介:一份基于STM32的CAN总线多节点工业控制系统设计资料,面向具备嵌入式开发基础、熟悉STM32与C语言的软硬件工程师和工业自动化研发人员,目标是从零构建高可靠、可扩展的工业现场通信网络,实现电机控制、传感器采集、阀门执行和… · 2026/9/21 2:22:47

四大AI Agent实测:Claude Code、Codex CLI、OpenClaw、Hermes Agent怎么选?
四大AI Agent实测:Claude Code、Codex CLI、OpenClaw、Hermes Agent怎么选?

最近这半年,AI Agent 这个词几乎被聊烂了。我在技术群、同事饭局、线下 meetup 上,每周都要回答几次类似的问题:Claude Code 和 Codex CLI 到底哪个写代码更强?OpenClaw 和 Hermes Agent 又是什么来头,跟编程助手是一回… · 2026/9/21 2:21:47

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/21 0:02:39

Word表格编号全攻略:从列表编号到题注交叉引用
Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/21 0:02:39

从第一个站到第二个站:独立开发者的静态网站选型与落地实践
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41

Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行
Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/21 0:00:18

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程
agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and … · 2026/9/21 0:00:18

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,… · 2026/9/21 0:00:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码