Skip to content

Conversation

strawgate
Copy link
Collaborator

@strawgate strawgate commented Oct 6, 2025

Description

Change lifespan on the server to be a server lifespan instead of a session lifespan

Closes #2012
Closes #166

@marvin-context-protocol marvin-context-protocol bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. contrib Related to community contributions in src/contrib/ directory. server Related to FastMCP server implementation or server-side functionality. labels Oct 6, 2025
@strawgate strawgate changed the title [Draft] Add server_lifespan module to Contrib Add server_lifespan Oct 7, 2025
@strawgate strawgate requested a review from Copilot October 7, 2025 18:51
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds server-wide lifespan management to the FastMCP class by introducing a server_lifespan parameter that transforms the class into an async context manager. The existing lifespan parameter now forwards to the deprecated session_lifespan parameter for backward compatibility.

Key changes:

  • Introduces server_lifespan parameter as an alternative to session-based lifespan management
  • Makes FastMCP an async context manager with __aenter__ and __aexit__ methods
  • Implements mutual exclusivity between server_lifespan and session_lifespan parameters

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/server/test_server_lifespan.py Comprehensive test suite covering server lifespan functionality, session lifespan deprecation, and parameter conflicts
tests/server/test_mount.py Test ensuring mounted servers with server_lifespan default to proxy mode
test_log.py Simple example demonstrating server lifespan usage
src/fastmcp/server/server.py Core implementation of server lifespan functionality and async context manager
src/fastmcp/client/transports.py Transport layer updates to handle server lifespan initialization

@strawgate strawgate requested a review from Copilot October 7, 2025 19:26
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

@strawgate strawgate force-pushed the contrib-lifespan branch 2 times, most recently from 5a53a93 to 6402e33 Compare October 7, 2025 19:39
@strawgate strawgate requested a review from Copilot October 7, 2025 21:35
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

@strawgate strawgate removed the contrib Related to community contributions in src/contrib/ directory. label Oct 7, 2025
Copy link
Owner

@jlowin jlowin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble tracking the purpose of the _server_lifespan_proxy_factory (and, relatedly, the lifespan stack result). It's also unclear to me why server lifespans and session lifespans are exclusive -- I think you have an elegant approach to treating the server lifespan as a context manager for the server, why not let the SDK also have its session-level lifespan run?

@strawgate
Copy link
Collaborator Author

@jlowin the current way users access the lifespan_context is via the RequestContext object from the underlying SDK

@dataclass
class RequestContext(Generic[SessionT, LifespanContextT, RequestT]):
    request_id: RequestId
    meta: RequestParams.Meta | None
    session: SessionT
    lifespan_context: LifespanContextT
    request: RequestT | None = None

So the way it's currently implemented is that you're picking whether lifespan_context is a session-bound or server-bound. So the server_lifespan gets started when the server starts and then we pass a "proxy" to the underlying SDK which gets called by the SDK with every session but just returns the server lifespan.

An alternative implementation could be that we expose the server_lifespan on the FastMCP Context object so that server_lifespan is available via the FastMCP Context and the session_lifespan is available via the MCP request context.

I am a bit biased here -- I think the session_lifespan is useless and so was mostly trying to push this to work the way I think most people think it should work.

@jlowin
Copy link
Owner

jlowin commented Oct 8, 2025

I see I see -- in that case I'm tempted to say lets just remove the session_lifespan entirely (we should solve that with middleware) and go with the correct implementation of server_lifespan (and in that case call it lifespan?) And then, in an opinionated way, we do not set low-level SDK lifespans for FastMCP users, but rather implement our own sane version. In other words I'm game to lean fully into your opinon that it session lifespan is useless and just lose it.

I think in this case we'd make the lifespan object availabl eon the FastMCP context rather than messing with the SDK object.

@strawgate strawgate requested a review from Copilot October 10, 2025 18:44
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

@strawgate strawgate changed the title Add server_lifespan Switch Lifespan to being a Server Lifespan Oct 10, 2025
@strawgate
Copy link
Collaborator Author

@jlowin I think this version should be a lot easier to reason about. Switching from session to server lifespan is a breaking change though

async with AsyncExitStack() as stack:
context = await stack.enter_async_context(lifespan(app))
yield context
if fastmcp_server._lifespan == default_lifespan:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this equality work for context managers? does it need to be is?

@jlowin
Copy link
Owner

jlowin commented Oct 10, 2025

This looks good to me -- since this is a behavior change on the same kwarg, I don't see a great way to make it deprecate gracefully and I think calling it out as a breaking behavioral change (but not an error) is the right way to handle over the 2.13 boundary.

@jlowin jlowin added the breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. label Oct 10, 2025
@Hyperclaw79
Copy link

As an end user, absolutely love this change! @strawgate Would it be possible to also update the docs as part of this PR itself in case anyone wants to try it right away? Since it's a breaking change, people will scratch their heads if there's no docs to specify the new behavior.

@strawgate
Copy link
Collaborator Author

Feel free to try this branch, the current code on the branch actually only uses lifespan and just makes it server lifespan instead of session lifespan.

I'll update docs but the note about it being a breaking change will be in the release notes

@Hyperclaw79
Copy link

Already uv synced the branch and it worked like a charm! 🔥
Thank you for the sweet change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logging in lifespan never emmitted New lifespan for every session?

3 participants