6.3 Context 与 Resolve 的分工


文档摘要

6.3 Context 与 Resolve 的分工 本节摘要:前两节分别讲了 Context 和 Resolve,本节把它们放一起,用对照表与场景判断讲透分工。两者都解决「让工具拿到请求环境信息」,但定位不同:Context 是 SDK 提供的通用请求对象(方法固定、覆盖读资源/报进度/写日志等通用操作),Resolve 是你自定义的业务依赖注入(完全灵活、覆盖当前用户/数据库会话等业务依赖)。读完本节,你在任何场景都能果断判断「该用 Context、Resolve、还是两者结合」,不再混淆。

6.3 Context 与 Resolve 的分工

本节摘要:前两节分别讲了 Context 和 Resolve,本节把它们放一起,用对照表与场景判断讲透分工。两者都解决「让工具拿到请求环境信息」,但定位不同:Context 是 SDK 提供的通用请求对象(方法固定、覆盖读资源/报进度/写日志等通用操作),Resolve 是你自定义的业务依赖注入(完全灵活、覆盖当前用户/数据库会话等业务依赖)。读完本节,你在任何场景都能果断判断「该用 Context、Resolve、还是两者结合」,不再混淆。

一、核心定位差异

先把两者的核心定位钉死:

维度 Context Resolve
本质 SDK 提供的请求对象 你写的解析函数
内容 固定方法集(读资源、报进度等) 你定义的任意依赖
类比 工具箱(里面的工具 SDK 定好了) 工厂(生产你想要的东西)
覆盖范围 通用请求操作 业务特定依赖

一句话总结:Context 是「SDK 给你的现成工具箱」,Resolve 是「你自己开的工厂」

二、详细对照表

把两者的方方面面放一起对照:

维度 Context Resolve
注入方式 ctx: Context 类型注解 Annotated[T, Resolve(fn)]
来源 SDK 内置 你写的解析函数
注入时机 工具执行时 工具执行
一个参数 vs 多个 一个对象(ctx),含多个方法 多个参数,每个一个依赖
能力 固定(read_resource、report_progress 等) 完全自由(你写函数)
可见性 对模型不可见 对模型不可见
适合 通用请求操作 业务依赖
复用性 全局统一对象 每个依赖一个解析函数

两者都有一个关键共性:对模型不可见。模型从不知道 Context 或 Resolve 参数的存在,它们都是 SDK 在工具执行前后填充的。

三、按场景判断:该用哪个

把常见场景列出来,标注该用哪个:

场景 用什么 理由
在工具里读别的资源 Context ctx.read_resource 是现成方法
上报长任务进度 Context ctx.report_progress 是现成方法
写日志通知客户端 Context ctx.info 等是现成方法
拿当前用户 Resolve 业务依赖,需自定义解析
拿数据库会话 Resolve 业务依赖,需自定义解析
拿配置对象 Resolve 或 Context 可用 ctx.read_resource,也可 Resolve 封装
读请求头做认证 Resolve 把认证封装成依赖,工具不重复
访问启动时建立的连接池 Resolve 或 Context Resolve 注入连接池,或 ctx.lifespan_context
触发引导填写 Resolve 返回 Elicit 现代协议的推荐方式

判断口诀:

问:这个需求是「通用请求操作」还是「业务特定依赖」? 通用请求操作 → Context(用现成方法) 业务特定依赖 → Resolve(写解析函数) 两者都有 → 结合用(函数同时有 ctx 和 Resolve 参数)

四、结合使用的典型模式

实战中最常见的是两者结合——一个工具同时用 Context(通用操作)和 Resolve(业务依赖):

from typing import Annotated from mcp.server import MCPServer from mcp.server.mcpserver import Context from mcp.shared import resolve mcp = MCPServer("Shop") # 业务依赖:当前用户 async def get_current_user(ctx: Context) -> User: token = ctx.headers["authorization"] return verify_token(token) # 业务依赖:数据库会话 async def get_db(ctx: Context) -> Session: return open_session() @mcp.tool() async def search_my_orders( query: str, # 业务参数(模型可见) ctx: Context, # 通用操作 user: Annotated[User, Resolve(get_current_user)], # 业务依赖 db: Annotated[Session, Resolve(get_db)], # 业务依赖 ) -> dict: """Search current user's orders.""" # 用 Context 的通用能力 config = await ctx.read_resource("config://app") await ctx.report_progress(1, 2, "正在查询") # 用 Resolve 注入的业务依赖 orders = db.search_orders(user.id, query) await ctx.report_progress(2, 2, "完成") return {"orders": orders[:config["max_results"]]}

模型看到的 Schema 只有 query 一个参数:

{ "name": "search_my_orders", "inputSchema": { "properties": {"query": {"type": "string"}}, "required": ["query"] } }

模型只关心业务参数,所有实现细节(Context 操作、Resolve 依赖)都被隐藏。这是「关注点分离」做到极致的样子。

五、两者协作的执行顺序

当工具同时用 Context 和 Resolve 时,执行顺序很重要:

1. 请求到达,SDK 构造 Context 对象 2. SDK 运行所有 Resolve 解析函数(并行或按依赖顺序) - get_current_user(ctx) → User - get_db(ctx) → Session 3. 把解析结果填进工具的 Resolve 参数 4. 调用工具函数,传入 ctx + 业务参数 + Resolve 注入值 5. 工具执行,返回结果

关键点:Resolve 解析函数能访问 Context——它们的签名是 async def fn(ctx: Context) -> T。所以 Resolve 依赖可以基于 Context 的信息(如 ctx.headers)来解析依赖。这就是「Resolve 构建在 Context 之上」的关系。

💡 技巧:理解了「Resolve 解析函数能用 ctx」,你就能写出强大的依赖链——比如「从 ctx.headers 拿 token,验证后返回 User」,这整个逻辑封装在解析函数里,工具只享受结果。

六、常见误区

最后澄清几个常见误区:

误区 真相
「Context 和 Resolve 二选一」 错,两者可结合,各管一摊
「Resolve 替代 Context」 错,Resolve 不能读资源、报进度,这些是 Context 的固定方法
「业务依赖也用 Context 装」 可以但别扭,Context 没有扩展点装业务对象,Resolve 才是正路
「Resolve 参数模型能看到」 错,Resolve 参数对模型不可见,与 Context 一样
「解析函数不能异步」 错,解析函数可以是 async,且通常是(要读 IO)

本节要点回顾

  1. Context 是 SDK 给的工具箱,Resolve 是你开的工厂——定位不同,互补不替代。
  2. Context 覆盖通用请求操作(读资源、报进度、日志),方法固定。
  3. Resolve 覆盖业务依赖(当前用户、数据库会话),完全灵活。
  4. 判断口诀:通用操作→Context,业务依赖→Resolve,两者都有→结合。
  5. 结合使用是实战常态,模型只看业务参数,实现细节全隐藏。
  6. 执行顺序:先构造 Context,再跑 Resolve(解析函数能用 ctx),最后调工具。
  7. Resolve 构建在 Context 之上,解析函数能访问 ctx 拿信息来解析依赖。

分工清楚了,下一节讲生命周期上下文——服务端启动时建立的共享资源。


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