6.1 Context 对象:请求级能力的统一入口


文档摘要

6.1 Context 对象:请求级能力的统一入口 本节摘要:前面几章的工具/资源/提示词都是「被调用时执行一段函数」,但真实服务端很少这么简单——它需要在执行中读别的资源、上报进度、写日志、拿到请求头。本节讲透第一个核心机制:Context(上下文)对象。它按类型注解自动注入,提供 / / 等请求级能力。关键特性是「对模型不可见」——Context 是给你的代码用的,模型从不知道它的存在。读完本节,你能让工具从「纯函数」升级为「与请求环境交互的完整业务逻辑」。 一、为什么需要 Context 先看一个纯函数工具搞不定的场景: 纯函数工具只能基于「参数 + 全局状态」工作,拿不到「这次请求的环境信息」。

6.1 Context 对象:请求级能力的统一入口

本节摘要:前面几章的工具/资源/提示词都是「被调用时执行一段函数」,但真实服务端很少这么简单——它需要在执行中读别的资源、上报进度、写日志、拿到请求头。本节讲透第一个核心机制:Context(上下文)对象。它按类型注解自动注入,提供 read_resource / report_progress / info 等请求级能力。关键特性是「对模型不可见」——Context 是给你的代码用的,模型从不知道它的存在。读完本节,你能让工具从「纯函数」升级为「与请求环境交互的完整业务逻辑」。

一、为什么需要 Context

先看一个纯函数工具搞不定的场景:

@mcp.tool() def process_order(order_id: str) -> dict: """Process an order.""" # 问题 1:我想读 config://app 资源,怎么读? config = ??? # 纯函数拿不到别的资源 # 问题 2:这个操作很慢,我想上报进度,怎么报? ??? # 纯函数没有上报通道 # 问题 3:我想记日志,写到哪? ??? # 纯函数没有日志出口 # 问题 4:我想知道是谁发起的请求(请求头),怎么拿? user = ??? # 纯函数拿不到请求头

纯函数工具只能基于「参数 + 全局状态」工作,拿不到「这次请求的环境信息」。Context 就是补这个缺口的——它把请求级的能力(读资源、报进度、写日志、拿请求头)打包成一个对象,按类型注解注入你的函数。

二、Context 的注入方式

Context 的注入极其简洁——在函数签名上加一个 ctx: Context 参数:

from mcp.server import MCPServer from mcp.server.mcpserver import Context mcp = MCPServer("Demo") @mcp.tool() async def process_order(order_id: str, ctx: Context) -> dict: """Process an order.""" ...

SDK 看到 ctx: Context 这个类型注解,自动把当前请求的 Context 对象注入。你不需要手动构造、不需要从全局拿——它就在那里。

💡 技巧:Context 注入是「按类型注解」的——SDK 扫描函数签名,发现 ctx: Context,就注入。这与第 6.2 节的 Resolve 依赖解析用的是同一套「按注解填充参数」的机制。参数名不一定是 ctx,但类型必须是 Context

三、Context 的核心能力

注入的 Context 对象提供这些请求级能力:

方法/属性 作用 典型用法
read_resource(uri) 读别的资源 在工具里读配置、读关联数据
report_progress(progress, total) 上报进度 长任务告诉客户端「我做到哪了」
info(msg) / debug(msg) / 等 写日志 日志被 SDK 转成通知发给客户端
session 访问底层会话 高级操作(采样、订阅等,第 7 章)
headers 读请求头 拿认证信息、客户端标识
request_id 当前请求 ID 日志追踪、链路追踪
lifespan_context 生命周期上下文 访问启动时建立的共享资源(第 6.4 节)

逐个看几个最常用的。

四、read_resource:在工具里读别的资源

ctx.read_resource 让你在工具执行中,读取服务端自己注册的资源:

@mcp.resource("config://app") def get_config() -> dict: return {"max_items": 100, "timeout": 30} @mcp.tool() async def search(query: str, ctx: Context) -> list: """Search items, respecting config.""" # 在工具里读配置资源 config = await ctx.read_resource("config://app") max_items = config["max_items"] # 用配置限制搜索结果 results = do_search(query) return results[:max_items]

这个模式很有用——配置做成资源(应用可预加载),工具执行时再读它。工具不必把配置硬编码,而是从资源动态读取,配置变化时只需更新资源,工具自动适配。

五、report_progress:上报长任务进度

ctx.report_progress 让长任务告诉客户端「我做到哪了」:

@mcp.tool() async def batch_process(items: list[str], ctx: Context) -> list: """Process a batch of items.""" results = [] total = len(items) for i, item in enumerate(items): result = process_one(item) results.append(result) # 上报进度 await ctx.report_progress( progress=i + 1, total=total, message=f"已处理 {i + 1}/{total}" ) return results

客户端(宿主)收到进度通知后,可在 UI 显示进度条。这对长任务体验至关重要——用户不用盯着「还在转圈」,知道「做到 60% 了」。

六、info/debug:写日志

Context 提供标准的日志方法,日志被 SDK 转成通知发给客户端:

@mcp.tool() async def risky_op(ctx: Context) -> str: """A risky operation.""" await ctx.info("开始执行 risky_op") try: result = do_something() await ctx.debug(f"成功,结果:{result}") return result except Exception as e: await ctx.info(f"失败:{e}") # 日志通知客户端 raise

这些日志不是写到服务端的文件,而是作为通知发给客户端——客户端能看到服务端在干什么。这与传统日志(写文件)不同,更适合分布式场景。

⚠️ 注意:Context 的日志方法是「发通知给客户端」,不是写本地文件。如果你要写本地日志(运维需要),仍用 Python 标准 logging 模块。两者不冲突,各司其职。

七、关键特性:对模型不可见

Context 最容易被忽视的特性——它对模型不可见。模型看到的工具 Schema 里,没有 ctx 参数:

你写的函数: async def process_order(order_id: str, ctx: Context) -> dict 模型看到的 Schema: { "name": "process_order", "inputSchema": { "properties": {"order_id": {"type": "string"}}, "required": ["order_id"] } } ← 注意:没有 ctx!

SDK 在生成 Schema 时,自动忽略 ctx: Context 这种类型注解的参数。模型从不构造 ctx,也不知道它存在——ctx 是 SDK 在调用前自动填充的,纯给你的代码用。

这个设计很巧妙:业务代码拿到请求级能力,但模型不被这些细节干扰。模型只需关心业务参数(order_id),实现细节(读资源、报进度)由你的代码用 Context 完成。

本节要点回顾

  1. Context 解决「纯函数工具拿不到请求环境」的问题,提供读资源、报进度、写日志等能力。
  2. 注入方式:函数签名加 ctx: Context,SDK 按类型注解自动注入。
  3. 核心能力:read_resourcereport_progressinfo/debugsessionheadersrequest_idlifespan_context
  4. read_resource 让工具读别的资源,实现「配置做资源、工具动态读」的解耦。
  5. report_progress 让长任务上报进度,客户端 UI 可显示进度条。
  6. 日志方法发通知给客户端,与本地 logging 模块各司其职。
  7. 关键特性:Context 对模型不可见,SDK 生成 Schema 时自动忽略它,模型不被实现细节干扰。

Context 清楚了,下一节讲第二个核心机制——Resolve 依赖解析,对模型隐藏的参数注入。


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