6.2 Resolve 依赖解析:对模型隐藏的参数注入


文档摘要

6.2 Resolve 依赖解析:对模型隐藏的参数注入 本节摘要:本节讲 v2 新增的第二个核心机制——Resolve 依赖解析。它与 Context 一样解决「让工具拿到请求环境信息」,但定位不同:Context 是 SDK 提供的通用请求对象(方法固定),Resolve 是你自定义的依赖注入(完全灵活)。写法是 ——一个解析函数在工具执行前运行,其返回值填充被注解的参数,而这个参数对模型不可见。这套机制类比 FastAPI 的 ,让你实现「当前用户」「数据库会话」「配置」等动态依赖。读完本节,你能把依赖与业务逻辑彻底分离。 一、Resolve 解决什么问题 先看一个 Context 也搞不定的场景: 你可能会说「从 ctx.headers 解析 token」。

6.2 Resolve 依赖解析:对模型隐藏的参数注入

本节摘要:本节讲 v2 新增的第二个核心机制——Resolve 依赖解析。它与 Context 一样解决「让工具拿到请求环境信息」,但定位不同:Context 是 SDK 提供的通用请求对象(方法固定),Resolve 是你自定义的依赖注入(完全灵活)。写法是 Annotated[T, Resolve(fn)]——一个解析函数在工具执行运行,其返回值填充被注解的参数,而这个参数对模型不可见。这套机制类比 FastAPI 的 Depends,让你实现「当前用户」「数据库会话」「配置」等动态依赖。读完本节,你能把依赖与业务逻辑彻底分离。

一、Resolve 解决什么问题

先看一个 Context 也搞不定的场景:

@mcp.tool() async def get_my_orders(ctx: Context) -> list: """Get current user's orders.""" # 问题:我要知道「当前用户是谁」 # Context 没有现成的 current_user user = ??? # 怎么拿?

你可能会说「从 ctx.headers 解析 token」。但这意味着每个需要当前用户的工具,都要重复写这段解析逻辑:

# 反模式:每个工具重复解析用户 @mcp.tool() async def get_my_orders(ctx: Context) -> list: token = ctx.headers["authorization"] user = verify_token(token) # 重复! ... @mcp.tool() async def get_my_profile(ctx: Context) -> dict: token = ctx.headers["authorization"] user = verify_token(token) # 又重复! ...

Resolve 解决的就是这种重复——把「获取当前用户」封装成一个解析函数,工具只声明依赖,不写解析逻辑

二、Resolve 的写法

Resolve 用 Annotated[T, Resolve(fn)] 注解参数:

from typing import Annotated from mcp.server import MCPServer from mcp.server.mcpserver import Context from mcp.shared import resolve # Resolve 来自共享层(概念引用) mcp = MCPServer("Demo") # 第一步:写一个解析函数 async def get_current_user(ctx: Context) -> User: """从请求头解析当前用户。""" token = ctx.headers["authorization"] return verify_token(token) # 第二步:在工具里用 Annotated 声明依赖 @mcp.tool() async def get_my_orders( user: Annotated[User, Resolve(get_current_user)] # ← Resolve 注入 ) -> list: """Get current user's orders.""" return fetch_orders(user.id) # 直接用 user,不用自己解析

发生了什么?

客户端调用 get_my_orders(无 user 参数,因为对模型隐藏) │ ▼ SDK 在工具执行前 运行 get_current_user(ctx) → 返回 User 对象 │ ▼ 把返回值填进 user 参数 调用 get_my_orders(user=<User 对象>) │ ▼ 工具执行,用 user 返回订单列表

解析函数 get_current_user 在工具执行前运行,其返回值自动填充 user 参数。工具函数里直接用 user,不用关心它怎么来的。

三、对模型不可见

与 Context 一样,Resolve 注入的参数对模型不可见:

你写的函数: async def get_my_orders( user: Annotated[User, Resolve(get_current_user)] ) -> list 模型看到的 Schema: { "name": "get_my_orders", "inputSchema": {"properties": {}, "required": []} ← 空!没有 user }

SDK 生成 Schema 时,自动忽略 Annotated[T, Resolve(...)] 这种参数。模型看不到 user,也不会尝试构造它——user 完全由 SDK 在工具执行前填充。

这个特性是 Resolve 最有价值的地方:模型只看到「业务参数」,依赖注入是黑盒。你可以在工具里注入任意复杂的依赖(数据库会话、配置、当前用户),模型从不受干扰。

四、Resolve vs Context:何时用哪个

两者都解决「让工具拿到请求环境」,但定位不同:

维度 Context Resolve
来源 SDK 内置 你自定义
能力 固定方法(read_resource 等) 完全灵活(你写解析函数)
注入方式 一个对象,多个方法 多个参数,每个一个依赖
适合 通用请求操作 特定业务依赖
类比 SDK 给的工具箱 你自己的工厂函数

简单判断:

需求 用什么
读资源、报进度、写日志 Context(现成方法)
当前用户、数据库会话、配置对象 Resolve(自定义解析)
既要通用操作又要业务依赖 两者结合(函数同时有 ctx 和 Resolve 参数)
@mcp.tool() async def complex_op( query: str, # 业务参数(模型可见) ctx: Context, # 通用请求操作 user: Annotated[User, Resolve(get_current_user)], # 业务依赖(模型不可见) db: Annotated[Session, Resolve(get_db_session)], # 业务依赖(模型不可见) ) -> dict: """A complex operation.""" config = await ctx.read_resource("config://app") # 用 Context await ctx.report_progress(1, 2) # 用 Context orders = db.query(user.id) # 用 Resolve 注入的 db return {"orders": orders, "config": config}

这种写法让工具同时享受 Context 的通用能力与 Resolve 的灵活依赖,且模型只看到 query 一个业务参数。

五、解析函数的能力:可以返回 Elicit

这是 v2 的一个关键能力(第 7 章详讲)——解析函数可以返回一个 Elicit 对象,触发引导填写:

from mcp.shared import resolve async def get_confirmation(ctx: Context): # 解析函数返回 Elicit,问用户一个问题 return resolve.Elicit( message="确认执行这个破坏性操作?", schema={"confirm": {"type": "boolean"}} ) @mcp.tool() async def delete_all( confirmed: Annotated[bool, Resolve(get_confirmation)] ) -> str: """Delete everything (needs confirmation).""" if not confirmed: raise PermissionError("用户未确认") do_delete() return "已删除"

当解析函数返回 Elicit 时,SDK 会暂停工具执行,向用户问一个问题(通过多轮往返,第 7 章),拿到答案后再填充参数、继续执行。这是 Resolve 最强大的用法——让依赖注入支持「需要用户输入才能确定」的依赖。

⚠️ 注意:Resolve 返回 Elicit 的机制依赖多轮往返(MRTR),这只在 2026-07-28 现代协议下工作。在 legacy 连接上,引导填写要用 ctx.elicit(第 7 章详讲两者的差异)。

六、Resolve 的工程价值

Resolve 的核心价值是关注点分离——把「获取依赖」与「使用依赖」分开:

不用 Resolve(关注点混合): 每个工具都要:解析 token → 验证 → 拿用户 → 再做业务 → 重复、易错、难维护 用 Resolve(关注点分离): 解析函数:负责「怎么拿用户」(只写一次) 工具函数:负责「用用户做什么」(只关心业务) → 解析逻辑集中、工具纯粹、好维护

这与 FastAPI 的 Depends、Spring 的依赖注入是同一种工程智慧——依赖的获取与使用分离,让业务代码保持纯粹

本节要点回顾

  1. Resolve 解决「工具重复获取依赖」的问题,类比 FastAPI 的 Depends
  2. 写法:Annotated[T, Resolve(fn)],解析函数在工具执行前运行,返回值填充参数。
  3. 对模型不可见:SDK 生成 Schema 时自动忽略 Resolve 参数,模型只看业务参数。
  4. Context vs Resolve:前者 SDK 内置固定方法,后者你自定义灵活解析。
  5. 简单判断:通用操作用 Context,业务依赖用 Resolve,可结合使用。
  6. 解析函数可返回 Elicit 触发引导填写(第 7 章),支持「需要用户输入的依赖」。
  7. 核心价值是关注点分离:依赖获取集中、业务工具纯粹、好维护。

Context 和 Resolve 都清楚了,下一节用一个对照表讲透两者的分工。


发布者: 作者: 灏天文库 转发
评论区 (0)
U