Repository navigation
Redesign lifespan: separate server-scoped and session-scoped lifetimes #2113
Description
Activity
- addedbreaking changeWill break existing deployments when updated without changesWill break existing deployments when updated without changesv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)enhancementRequest for a new feature that's not currently supportedRequest for a new feature that's not currently supportedP0Broken core functionality, security issues, critical missing featureBroken core functionality, security issues, critical missing featureP1Significant bug affecting many users, highly requested featureSignificant bug affecting many users, highly requested featureand removedP0Broken core functionality, security issues, critical missing featureBroken core functionality, security issues, critical missing feature
on Feb 19, 2026 Hi @maxisbey - I wanted to share my understanding of the lifespan redesign issue and confirm the proposed solution.
Problem Summary
The current single
lifespanis tied toServer.run(), which is called per-session (streamable-http) or per-request (stateless_http). This causes:- Bug Lifespan does not execute on startup streamable-http #1300: With streamable-http, lifespan doesn't run on server startup - only when a client connects
- Bug Lifespan enters and exits for each request when stateless_http=True #1304: With stateless_http=True, lifespan enters/exits for every request
Both problems stem from the same root cause: no way to create resources that live for the entire server process.
Proposed Solution: Two Separate Lifespans
Server Lifespan (runs ONCE)
- When: Server starts → stops
- Use for: Database connection pools, ML models, shared caches
- Shared by: All clients/requests
Session Lifespan (runs PER CLIENT)
- When: Client connects → disconnects
- Use for: User-specific data, auth context, scoped database access
- Unique to: Each client/session
API Design Question
I see two possible approaches:
Option A: Extend (backwards compatible)
server = Server( "myapp", lifespan=my_lifespan, # Session lifespan (current behavior) server_lifespan=server_lifespan, # NEW: Server lifespan )
Option B: Replace (breaking change, clearer)
server = Server( "myapp", server_lifespan=server_lifespan, # Server-scoped session_lifespan=session_lifespan, # Session-scoped )
Given this is a "redesign" and the maintainers marked the related bugs as "subsumed by #2113", I lean toward Option B for a cleaner API, with a migration guide.
Context Access in Handlers
@server.tool() async def my_tool(ctx: ServerRequestContext): # Access server-level resources (shared) db = ctx.server_lifespan_context["db"] # Access session-level resources (client-specific) user = ctx.session_lifespan_context["user"]
Would love to hear your thoughts on the API design choice and whether this aligns with your vision for the fix!
This landed as part of v2, which is now released (via #2928) — closing. Feel free to reopen if there's something still outstanding.
Summary
The current lifespan implementation is tied to sessions/transports rather than having distinct scopes. This causes two P0 bugs (#1300, #1304) and makes the model fundamentally wrong for production use.
Problem
streamable-http, the lifespan does not execute on server startup — it only runs when a client connects and a session is created (Lifespan does not execute on startup streamable-http #1300)stateless_http=True, the lifespan enters and exits for every request because each request spins up a new transport (Lifespan enters and exits for each request when stateless_http=True #1304)Goal
Provide two distinct lifetime scopes:
Both should be accessible from handler context. The current single
lifespanparameter should be replaced or extended to support both scopes.Subsumes
AI Disclaimer