python-sdk 客户端订阅实战:使用 client.listen 监控 MCP 服务器的动态目录
人工智能MCP 服务MCP Clients【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址https://gitcode.com/gh_mirrors/pythonsd/python-sdk点击查看免费下载服务器端的目录catalog并不是固定不变的工具可能在运行时才出现资源 URI 背后的内容也会随时变化。在 MCPModel Context Protocol的 Python 官方 SDK本仓库 python-sdk中客户端通过client.listen(...)感知这些变化——这本质上是一次subscriptions/listen请求而它的响应本身就是一条流请求发出后流保持打开持续运送客户端所关心的变更通知。本文聚焦客户端一侧的完整链路如何打开订阅流、如何在主流程旁边异步监控它、以及如何处理流的两种结束方式。变更的发布、过滤与该方法在服务端的提供属于另一侧的话题见 服务端订阅指南本文示例所通信的“冲刺看板sprint-board”服务器也由该文构建。打开并监控订阅流client.listen(...)返回一个异步上下文管理器async context manager。进入async with块即发出订阅请求请求的关键字参数就是订阅过滤器subscription filter随后 SDK 会等待服务器的确认acknowledgment——因此代码块真正开始执行时流已经处于活跃状态。以下示例来自 docs_src/subscriptions/tutorial003.pyfrom mcp import Client from mcp.client.subscriptions import ResourceUpdated, ToolsListChanged from mcp.types import TextResourceContents BOARD board://sprint async def read_board(client: Client, uri: str BOARD) - str: [contents] (await client.read_resource(uri)).contents assert isinstance(contents, TextResourceContents) return contents.text async def follow_board(client: Client) - None: async with client.listen(tools_list_changedTrue, resource_subscriptions[BOARD]) as sub: async for event in sub: match event: case ResourceUpdated(uriuri): print(await read_board(client, uri)) case ToolsListChanged(): tools await client.list_tools() print(tools:, [tool.name for tool in tools.tools]) case _: pass # kinds the filter did not ask for never arrive async def main() - None: async with Client(http://localhost:8000/mcp) as client: await follow_board(client)迭代async for event in sub会产出四种类型化事件定义于 src/mcp/client/subscriptions.py 并重新导出事件类型含义ToolsListChanged工具列表发生变化PromptsListChanged提示prompt列表发生变化ResourcesListChanged资源列表发生变化ResourceUpdated(uri...)指定 URI 的资源内容被更新事件是“重新获取”的信号而非数据载荷事件只告诉你**“什么”变了**从不告诉你**“怎么”变的**。这正是follow_board在收到事件后要调用read_resource与list_tools的原因事件只是重新拉取refetch的提示cue最终以重新读取得到的最新状态为准。不要臆断是哪个资源发生了变动而应读取event.uri过滤器可以同时指定多个 URI且服务器可能报告其中某个 URI 的子资源发生了变更。测试套件 tests/client/test_subscriptions.py 中的test_listen_delivers_all_four_typed_event_kinds即为四种事件的逐项验证。重复事件的合并coalescing等待消费的重复事件会被合并为一条例如连续收到三次相同的ResourceUpdated消费端只会看到一次。由于合并后再重新获取仍能得到最新状态这不会造成信息丢失。只有完全相同的事件才会合并指向不同 URI 的两个ResourceUpdated就是两条独立事件。订阅句柄的两个属性listen返回的Subscription句柄即async with块中的sub还有两个值得关注的属性sub.honored服务器实际确认acknowledge的过滤器类型为SubscriptionFilter包含你传入的那些字段可通过属性直接读取例如sub.honored.prompts_list_changed。本 SDK 的MCPServer会接受你请求的每一种事件类型因此它会原样回显你的请求而支持类型较少的服务器则只确认更少的字段——且即使某种类型被确认接受也不代表它一定会触发。服务器还可以选择整体拒绝该请求而不是确认参见服务端文档中“谁可以观看”一节此时会以请求错误的形式暴露给客户端。sub.subscription_id该 listen 请求的 ID它会被烙印stamp在这条流的所有帧frame上。可以同时打开多条订阅每条流凭借自己的 ID 被多路分解demultiplex。从源码看这个 ID 由进程级计数器生成、形如listen-N见 src/mcp/client/subscriptions.py 中的_listen_ids测试test_listen_surfaces_the_honored_filter_and_subscription_id验证了其字符串前缀与确认过滤器的回显。非阻塞监控让 watcher 与主流程并行follow_board会一直运行到服务器关闭流为止——而服务器可能永远不会关闭因此若单独运行它会独占整个程序。真实客户端想要的是在主流程“旁边”运行的监控者Agent 一边调用工具watcher 一边让缓存或 UI 保持最新。做法是先打开订阅进入块、等到确认再启动 watcher 任务然后继续做正事。三种异步后端各有写法 asynciopython titleapp.py import asyncio from mcp import Client from mcp.client.subscriptions import Subscription from .tutorial003 import BOARD, read_board async def watch(client: Client, sub: Subscription) - None: async for _event in sub: board await read_board(client) print(board) if [ ] not in board: return # sprint finished: the stream closes when run_sprint leaves the block async def run_sprint(client: Client) - None: async with client.listen(resource_subscriptions[BOARD]) as sub: print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed watcher asyncio.create_task(watch(client, sub)) for task in (design, build, ship): await client.call_tool(complete_task, {board: sprint, task: task}) await watcher # returns once the watcher has seen the finished board async def main() - None: async with Client(http://localhost:8000/mcp) as client: await run_sprint(client) if __name__ __main__: asyncio.run(main()) triopython titleapp.py import trio from mcp import Client from mcp.client.subscriptions import Subscription from .tutorial003 import BOARD, read_board async def watch(client: Client, sub: Subscription) - None: async for _event in sub: board await read_board(client) print(board) if [ ] not in board: return # sprint finished: the stream closes when run_sprint leaves the block async def run_sprint(client: Client) - None: async with client.listen(resource_subscriptions[BOARD]) as sub: print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed async with trio.open_nursery() as nursery: nursery.start_soon(watch, client, sub) for task in (design, build, ship): await client.call_tool(complete_task, {board: sprint, task: task}) async def main() - None: async with Client(http://localhost:8000/mcp) as client: await run_sprint(client) if __name__ __main__: trio.run(main) anyiopython titleapp.py import anyio from mcp import Client from mcp.client.subscriptions import Subscription from .tutorial003 import BOARD, read_board async def watch(client: Client, sub: Subscription) - None: async for _event in sub: board await read_board(client) print(board) if [ ] not in board: return # sprint finished: the stream closes when run_sprint leaves the block async def run_sprint(client: Client) - None: async with client.listen(resource_subscriptions[BOARD]) as sub: print(await read_board(client)) # snapshot: acknowledged, so nothing after this is missed async with anyio.create_task_group() as tg: tg.start_soon(watch, client, sub) for task in (design, build, ship): await client.call_tool(complete_task, {board: sprint, task: task}) async def main() - None: async with Client(http://localhost:8000/mcp) as client: await run_sprint(client) if __name__ __main__: anyio.run(main) 说明以上app.py从第一个示例中导入BOARD和read_board本仓库将其保存为tutorial003.py即from .tutorial003 import ...。如果你把渲染后的文件并排保存为client.py与app.py请改写成from client import BOARD, read_board下文watch.py示例对read_board的导入同理。这三个文件的仓库路径分别为 docs_src/subscriptions/tutorial004_asyncio.py、docs_src/subscriptions/tutorial004_trio.py 与 docs_src/subscriptions/tutorial004_anyio.py。顺序是关键没有任何重放replay机制在你建立流之前已发布的事件会被错过。而进入client.listen(...)会一直等到服务器确认因此从确认那一刻起的所有变更都会到达你的 watcher——你在块内拍摄的快照snapshot不可能漏掉一次变更。打开的流不阻塞其他请求请求可以在已打开的流旁边自由执行无论来自 watcher 任务还是其他任务都共享同一个客户端连接。由于重复的未消费事件会被合并当主流程忙碌时可能原本需要三次重新获取refetch才能跟上的状态一次就完成了不同的事件不会合并——如果过滤器指定了多个 URI那么每个 URI 各有一条待处理事件在队列中。停止监控停止监控的方式就是退出代码块没有unsubscribe之类的显式调用。取消拥有该代码块的任务即可SDK 会按传输层期望的方式取消 listen 请求——对于 Streamable HTTP即关闭该请求的流。需要说明的是为整个应用生命周期服务的 watcher 永远不会自行返回因此在应用关闭shutdown时请显式取消它或取消其所属任务组task group的作用域。流的两种结束方式流只有两种结束方式且两者都属于正常的控制流服务器正常关闭async for循环自然结束突然断开抛出SubscriptionLost。这个区别仅用于诊断并不改变接下来的动作流已经没了、什么都不会重放仍有关注需求的 watcher 应当重新listen并重新获取。下面的watch.py仓库路径 docs_src/subscriptions/tutorial005.py演示了完整循环import anyio from mcp import Client from mcp.client.subscriptions import SubscriptionLost from .tutorial003 import read_board async def keep_following(client: Client) - None: while True: try: async with client.listen(resource_subscriptions[board://sprint]) as sub: print(await read_board(client)) # refetch: no replay across streams async for _event in sub: print(await read_board(client)) except SubscriptionLost: pass # Either ending means the stream is gone. Back off before re-listening: # a graceful close may be the server shedding load. await anyio.sleep(1)正常关闭也可能是“负载卸载”服务器可能出于自身原因正常关闭流——例如卸载shed积压过大的订阅者。因此“干净地结束”不是“停止监控”的信号在重新listen之前务必先退避back off一段时间上面的示例用anyio.sleep(1)实现这一点。SubscriptionLost 的本地成因1024 条积压上限SubscriptionLost还有一个客户端本地成因客户端最多保留1024 条未消费事件对应源码中的_MAX_PENDING_EVENTS 1024见 src/mcp/client/subscriptions.py。当消费方落后到超过这个上限时SDK 会选择让订阅失联而不是让内存无限膨胀源码注释明确说明由于规范允许子资源 URI去重后的不同ResourceUpdated事件在理论上是无界的因此设置了该积压护栏。所以请保持async for循环体简短把耗时操作放到别处执行。listen() 进入时还可能抛出的异常keep_following只捕获SubscriptionLost。进入listen()时还可能抛出MCPError连接失败或服务器不提供该方法TimeoutError在读取超时时间内没有收到确认ListenNotSupportedError协商的协议版本早于 2026-07-28该功能要求 2026-07-28 版本连接。请自行决定你的 watcher 应对其中哪些进行重试——最后一种永远不会自愈。从 src/mcp/client/client.py 中Client.listen的签名可以看到它还带有一个隐藏联动当启用响应缓存时on_event会在每个事件返回给消费者之前先完成缓存驱逐cache eviction保证消费者重新获取时读到的是新鲜数据这正是“事件也能保持客户端缓存诚实”的底层实现。小结进入async with client.listen(...)进入时会等待确认因此之后发布的内容一个都不会漏。用async for event in sub迭代事件是重新获取的提示永远不是数据载荷。先打开订阅再把 watcher 作为任务运行工具调用就能在它旁边继续流动。干净结束则循环停止突然断开则抛出SubscriptionLost。无论哪种重新 listen、重新获取但先退避。退出代码块就是退订。事件的发布、过滤器的收窄、跨进程的扩展属于服务端的话题详见 服务端订阅指南同样的变更事件还能让客户端缓存保持精确下一篇可继续阅读 客户端缓存。赞分享人工智能MCP 服务MCP Clients【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址https://gitcode.com/gh_mirrors/pythonsd/python-sdk点击查看免费下载相关推荐MCP Python SDK 客户端订阅实战用 client.listen() 监听服务器变更流MCP Python SDK 客户端订阅实战用 client.listen 监听服务器变更流 导读 服务器目录工具、提示词、资源并非一成不变工具会在运行人工智能MCP 服务MCP ClientsPython SDK 客户端订阅实战用 client.listen() 感知 MCP 服务器的目录变化Python SDK 客户端订阅实战用 client.listen 感知 MCP 服务器的目录变化 本篇指南讲解官方 Python SDK 的客户端订阅能力人工智能MCP 服务MCP ClientsPython SDK 客户端订阅指南用 client.listen(...) 实时监听 MCP 服务器目录变更Python SDK 客户端订阅指南用 client.listen ... 实时监听 MCP 服务器目录变更 服务器目录并非一成不变工具会在运行时出现资源人工智能MCP 服务MCP Clients上一篇Campus-imaotai SPI机制服务发现与插件化架构下一篇如何利用智能体AI技术打造下一代零售客户行为分析与推荐系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Caffeine 源码审计方法论:深入 Caffeine 并发缓存正确性审计的专业实践指南

Caffeine 源码审计方法论:深入 Caffeine 并发缓存正确性审计的专业实践指南

后端缓存抽象 【免费下载链接】caffeine A high performance caching library for Java 项目地址: https://gitcode.com/gh_mirrors/ca/caffeine 点击查看 免费下载 Caffeine 是一款高性能 Java 缓存库,其核心价值在于 W-TinyLFU 准入策略、无锁读路径与…

2026/9/21 3:16:48 阅读更多 →
OEC刷机“下载Boot失败”排查全攻略:原理、短接与自救

OEC刷机“下载Boot失败”排查全攻略:原理、短接与自救

/* 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 3:16:48 阅读更多 →
LTspice导入SPICE模型详解:从UA741到自定义运放库

LTspice导入SPICE模型详解:从UA741到自定义运放库

/* 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 3:16:48 阅读更多 →

最新新闻

2026年CRM横评:数据主权、AI落地与选型避坑指南

2026年CRM横评:数据主权、AI落地与选型避坑指南

2026年这个节点做CRM横评,和五年前完全是两种玩法。五年前大家比的是谁能装的功能多、谁能把客户档案填得更满;现在客户管理系统遍地都是,连Excel和企微自带的表格都能跑通小团队的客户跟进,真正拉开差距的反而是三件事&#xff1…

2026/9/21 5:06:36 阅读更多 →
PolarDB-X落地实战:分布式数据库选型与迁移避坑指南

PolarDB-X落地实战:分布式数据库选型与迁移避坑指南

/* 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 5:06:36 阅读更多 →
STM32 PWM呼吸灯与OLED实时显示:TIM2寄存器配置与调试实战

STM32 PWM呼吸灯与OLED实时显示:TIM2寄存器配置与调试实战

/* 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 5:06:36 阅读更多 →
ORM层实现数据库读写分离:动态数据源路由核心原理与实战

ORM层实现数据库读写分离:动态数据源路由核心原理与实战

开头直接从上个月的一次故障说起。凌晨两点,线上主库的CPU被打到100%,慢查询日志刷屏,DBA在群里喊了一句:“赶紧看下你的订单接口,怎么在这会儿全走主库了?”我打开监控一看,业务量翻了十倍&…

2026/9/21 5:06:36 阅读更多 →
从CDN到边缘智能:内容分发网络的技术演进与实战

从CDN到边缘智能:内容分发网络的技术演进与实战

前一阵有朋友找我,说公司业务全都放在上海机房,客户分布在全国各地,每天都有用户反馈“网页打开转圈、图片加载半天”。他试过把云主机配置翻倍,效果很一般;也试过加带宽,钱花了不少,问题依旧。…

2026/9/21 5:05:36 阅读更多 →
网络安全培训值不值?零基础入行自学与报班避坑全解析

网络安全培训值不值?零基础入行自学与报班避坑全解析

很多人问我“想干网络安全,是不是必须花大几千上万报个培训班”,说实话,这个问题我当年也纠结过。那时候刚毕业,手里没钱,看着培训班动辄一两万的学费,再看看招聘网站上“经验不限、薪资可观”的岗位&#…

2026/9/21 5:05:36 阅读更多 →

日新闻

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:01 阅读更多 →
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:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

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

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

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

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

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

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

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

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

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

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →