MCP Python SDK 服务端 lifespan 全解:从连接池管理到生命周期验证实战
MCP Python SDK 服务端 lifespan 全解从连接池管理到生命周期验证实战【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk导读本篇文章基于官方 Python SDK for Model Context Protocol仓库 pythonsd/python-sdk的文档展开系统讲解服务端lifespan生命周期机制的完整用法。真实 MCP 服务器几乎都需要在存活期间持有某个常驻资源——数据库连接池、HTTP 客户端、加载好的模型——你不希望每次请求都重新创建又希望在服务器退出时干净地关闭。lifespan 正是为此设计的官方机制。读完本文你将掌握如何用asynccontextmanager编写类型化 lifespan、如何让yield出的对象被所有 handler 共享、类型参数Context[AppContext]的威力与使用边界以及如何通过一个最小实验亲眼验证启动先于首请求、结束于 finally的生命周期时序。为什么需要 lifespan服务器级别的常驻资源绝大多数真实服务器都会持有某些存活期与服务器本身一致的资源数据库连接池、HTTP 客户端、加载到内存中的模型等。如果每次调用都新建性能与连接数都不可接受如果从不关闭又会泄漏连接与句柄。lifespan 解决的就是这个创建一次、干净关闭的问题。lifespan 的本质是一个asynccontextmanager异步上下文管理器它接收服务器实例yield出一个对象这个对象在服务器运行的整个期间对所有 handler 可见。yield之前的代码是启动逻辑yield之后的代码通常放在finally中是关闭逻辑。如果你写过 FastAPI 的lifespan这里的知识是相通的同一个装饰器、同一个yield、同一个finally。类型化 lifespan完整接线示例以下是最小但完整的示例完整源码见 docs_src/lifespan/tutorial001.py建议自下而上阅读from collections.abc import AsyncIterator from contextlib import asynccontextmanager from dataclasses import dataclass from mcp.server import MCPServer from mcp.server.mcpserver import Context class Database: classmethod async def connect(cls) - Database: return cls() async def disconnect(self) - None: ... def query(self) - int: return 3 dataclass class AppContext: db: Database asynccontextmanager async def app_lifespan(server: MCPServer) - AsyncIterator[AppContext]: db await Database.connect() try: yield AppContext(dbdb) finally: await db.disconnect() mcp MCPServer(Bookshop, lifespanapp_lifespan) mcp.tool() def count_books(genre: str, ctx: Context[AppContext]) - str: Count the books in a genre. db ctx.request_context.lifespan_context.db return f{db.query()} books in {genre!r}.逐层拆解这段代码app_lifespan是启动与关闭的全部yield之前连接Databaseyield之后在finally中断开连接。异步上下文管理器保证无论期间发生什么finally都会执行关闭逻辑绝不遗漏。AppContext是一个普通 dataclass它只是承载你设置好的一堆东西的容器。今天放一个字段db明天可以扩展成十个字段——工具函数依然只需要通过ctx.request_context.lifespan_context一处入口访问。MCPServer(Bookshop, lifespanapp_lifespan)就是全部接线工作把 lifespan 作为构造参数传入即可SDK 负责在正确时机进入和退出。工具内部通过ctx.request_context.lifespan_context拿到 yield 出的对象ctx是 SDK 注入的Context参数不参与工具的输入 schema。生命周期时序一次执行全程共享lifespan恰好执行一次服务器启动时在第一个请求之前进入服务器停止时退出。期间的所有请求共享同一个AppContext实例——这正是连接池/客户端/模型只建一次语义的来源。从源码可以印证这一点。在底层服务器实现中Server.run用async with self.lifespan(self) as lifespan_context:包裹整个消息循环见 src/mcp/server/lowlevel/server.py也就是说 lifespan 上下文以with方式包住整个连接生命周期yield 出的对象随后通过lifespan_state传递给每个请求src/mcp/server/lowlevel/server.py。如果你不传lifespanSDK 会使用默认实现——一个什么都不做、直接yield {}的异步上下文管理器见 src/mcp/server/lowlevel/server.py。这解释了文档中的关键保证lifespan 永远存在ctx.request_context.lifespan_context至少是{}绝不会是None。这也是为什么裸Context会把lifespan_context类型标注为dict[str, Any]。模型视角ctx 是 SDK 注入的不进 schema对调用方LLM来说lifespan 是完全透明的。ctx是一个Context 参数由 SDK 在调用时注入绝不会出现在工具的输入 schema 里。以count_books为例模型能看到的输入 schema 只有genre一个字段{ type: object, properties: { genre: {title: Genre, type: string} }, required: [genre], title: count_booksArguments }模型唯一能传的参数是genre。lifespan 是你的服务器内部事务与协议无关。这一点在 SDK 实现中同样成立Context.request_context属性在无活动请求时会直接抛出ValueError(Context is not available outside of a request)见 src/mcp/server/mcpserver/context.py而每个请求的request_context都携带lifespan_context字段定义见 src/mcp/server/context.py。mcp.resource()与mcp.prompt()函数同样可以接收ctx参数但它们应按下一节的原因写成不带类型参数的裸Context。ctx携带的全部内容可进一步查阅文档 Context。它真的是类型安全的Context[AppContext] 的威力再看一次注解ctx: Context[AppContext]。正是这一个类型参数让类型检查器如 mypy / pyright确信ctx.request_context.lifespan_context就是AppContext类型。于是.db能自动补全而敲出.dbb会在服务器运行之前就成为类型错误——IDE 里直接标红。反过来如果写成不带类型参数的裸Contextlifespan_context的类型就是dict[str, Any]类型检查器无法得知你的 lifespan yield 了什么。对象在运行时依然存在但你失去了编译期的全部帮助。从源码看这一设计的根基在于Context的泛型声明与LifespanContextT类型变量ServerRequestContext的lifespan_context字段是泛型的src/mcp/server/context.pyContext类自身也声明为Generic[LifespanT_co]且协变src/mcp/server/context.py因此Context[AppContext]可以安全地向下兼容为Context[object]等更宽类型。重要警告Context[AppContext] 是工具专用写法警告Context[AppContext]只适用于工具mcp.tool()函数。如果把它写到mcp.resource()或mcp.prompt()函数上该 handler 的每次调用都会失败。客户端会收到错误服务器日志中会显示原因Context is not available outside of a request在资源与提示词中请写成裸ctx: Context。你的 lifespan yield 出的对象在运行时仍然位于ctx.request_context.lifespan_context中——你放弃的只是类型参数不是对象本身。产生这一限制的原因与实现细节一致Context.request_context属性在请求上下文尚未建立时如资源/提示词 handler 的某些调用路径会抛出上述ValueError见 src/mcp/server/mcpserver/context.py。提示lifespan 永远存在lifespan永远存在。即使你不传lifespanSDK 的默认 lifespan 也会 yield 一个空dict因此ctx.request_context.lifespan_context是{}绝不会是None。裸Context将其类型标为dict[str, Any]正是因为这个默认值。你的代码可以放心地直接访问lifespan_context而无需判空——当然若你依赖自定义对象仍需通过类型参数来恢复精确类型。亲眼验证启动先于首请求关闭落于 finally启动代码在第一个请求之前运行这类论断不该靠直觉接受值得亲手验证。做法是把服务器精简到只剩生命周期本身给Database加一个connected布尔标志在connect()与disconnect()中翻转该标志添加一个报告该标志状态的工具。完整示例见 docs_src/lifespan/tutorial002.pyfrom collections.abc import AsyncIterator from contextlib import asynccontextmanager from dataclasses import dataclass from mcp.server import MCPServer from mcp.server.mcpserver import Context class Database: def __init__(self) - None: self.connected False async def connect(self) - None: self.connected True async def disconnect(self) - None: self.connected False dataclass class AppContext: db: Database database Database() asynccontextmanager async def app_lifespan(server: MCPServer) - AsyncIterator[AppContext]: await database.connect() try: yield AppContext(dbdatabase) finally: await database.disconnect() mcp MCPServer(Bookshop, lifespanapp_lifespan) mcp.tool() def database_status(ctx: Context[AppContext]) - str: Report whether the database connection is up. db ctx.request_context.lifespan_context.db return connected if db.connected else disconnected注意database放在模块级别唯一的原因是从服务器外部观察它——你可以在自己的测试或调试代码里直接读取database.connected而不需要穿过 MCP 协议。三个时间点三个值按文档的验证清单在三个时刻观察时刻database.connected说明服务器启动前False导入模块不会连接任何东西连接只发生在 lifespan 的yield之前服务器运行中True调用database_status返回connected启动代码已在首个请求之前执行完毕服务器停止后Falsefinally块运行disconnect()被调用结论很清晰工作恰好发生在你放置它的位置——yield的周围。既不在模块导入时也不在每个请求时。这正印证了 lifespan 与每次请求都初始化或导入时初始化两种模式的本质区别。总结lifespan 核心要点lifespan参数接收一个asynccontextmanager它接收服务器实例并yield出一个对象。yield之前的代码是启动其后的finally是关闭。它在服务器的整个生命周期内只执行一次而非每个请求一次。你 yield 出的对象在所有工具、资源与提示词中都可以通过ctx.request_context.lifespan_context访问。ctx: Context[AppContext]让工具中的该访问获得完整类型。资源与提示词请使用裸ContextContext[AppContext]写在资源/提示词上会导致每次调用失败。不传lifespan时默认值是一个空dict绝不会是None。接下来可以继续阅读在调用中途停下来向用户询问只有用户才知道的信息的 handler属于Elicitation询问机制参见文档 Elicitation。【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Vue Router 2 构造选项全解析:routes、mode、base 与 scrollBehavior 配置指南

Vue Router 2 构造选项全解析:routes、mode、base 与 scrollBehavior 配置指南

前端路由 【免费下载链接】vue-router 🚦 The official router for Vue 2 项目地址: https://gitcode.com/gh_mirrors/vu/vue-router 点击查看 免费下载 本篇技术指南以 Vue Router 2(本仓库 vu/vue-router)官方文档《Options de…

2026/9/21 7:26:38 阅读更多 →
MATLAB 2022b 配置 IPOPT 与 OPTI 工具箱实战指南

MATLAB 2022b 配置 IPOPT 与 OPTI 工具箱实战指南

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

2026/9/22 9:59:24 阅读更多 →
Android定位权限全链路实战:从分步申请到后台持续定位避坑指南

Android定位权限全链路实战:从分步申请到后台持续定位避坑指南

/* 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 7:26:38 阅读更多 →

最新新闻

周鸿祎博客高频面试题解析:3个核心机制助你告别原理盲区

周鸿祎博客高频面试题解析:3个核心机制助你告别原理盲区

周鸿祎博客高频面试题解析:3个核心机制助你告别原理盲区 面试被问原理答不上来,是不是让你瞬间大脑一片空白?那种明明写过代码,却说不清背后为什么这么跑的无力感,是无数开发者的噩梦。尤其是当面试官抛出关于“周鸿祎博客”这类高并发架构的…

2026/9/22 10:01:06 阅读更多 →
3个核心步骤搞定马氏指数配置,高频面试题不再卡环境

3个核心步骤搞定马氏指数配置,高频面试题不再卡环境

3个核心步骤搞定马氏指数配置,高频面试题不再卡环境 装个库报错,改个配置卡半天,这种在开发初期遇到的“环境地狱”,往往是面试翻车的导火索。很多候选人把精力耗在搭建本地测试环境上,却忽略了马氏指数在数据预处理和异常检测中的核心逻辑,导致面对【…

2026/9/22 10:01:06 阅读更多 →
3个核心考点吃透休息区标志,面试不再掉链子

3个核心考点吃透休息区标志,面试不再掉链子

3个核心考点吃透休息区标志,面试不再掉链子 面试被问原理答不上来,是大多数开发者的噩梦。特别是在涉及交通逻辑、物联网设备或智慧城市等实战项目时,面试官喜欢深挖底层细节。很多候选人背了八股文,却对“休息区标志”这类具体场景下的数据流转、状态机…

2026/9/22 10:01:06 阅读更多 →
用友和金蝶哪个好用?面试避坑指南与性能优化实战

用友和金蝶哪个好用?面试避坑指南与性能优化实战

用友和金蝶哪个好用?面试避坑指南与性能优化实战 看了一堆教程还是不会写项目?别急,这不是你笨,是你没抓对重点。很多刚入行的开发或者转行的朋友,卡在“选型”和“落地”上,明明代码会写,一到实际业务场景就懵圈。今天咱们不聊虚的,直接拆解【用友和…

2026/9/22 10:01:06 阅读更多 →
3步搞定免费域名解析,一文搞懂DNS原理避坑

3步搞定免费域名解析,一文搞懂DNS原理避坑

3步搞定免费域名解析,一文搞懂DNS原理避坑 上周帮一个转岗做运维的兄弟排查线上故障,他对着控制台抓耳挠腮:明明改了解析记录,为什么客户端还是连到旧IP?更惨的是,刚升级完DNS SDK,原来的 getHostByName 调用直接报错…

2026/9/22 10:01:06 阅读更多 →
3步搞定drop的过去式:附完整示例避坑指南

3步搞定drop的过去式:附完整示例避坑指南

3步搞定drop的过去式:附完整示例避坑指南 面试被问“drop的过去式怎么写”,90%的开发者会愣住。别笑,这题看似简单,实则考察你对动词时态底层逻辑的理解。很多候选人连规则变化都没搞清,直接背答案,结果一追问就露馅。今天这篇内容,不玩虚…

2026/9/22 10:00:05 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

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

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

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

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

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

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

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

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

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

2026/9/22 8:51:04 阅读更多 →

月新闻

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

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

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

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

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

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

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

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

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

2026/9/22 2:43:42 阅读更多 →