@@ -222,12 +222,11 @@ The MCPServer server is your core interface to the MCP protocol. It handles conn
222222
223223<!-- snippet-source examples/snippets/servers/lifespan_example.py -->
224224``` python
225- """ Example showing lifespan support with server and session scopes ."""
225+ """ Example showing lifespan support for startup/shutdown with strong typing ."""
226226
227227from collections.abc import AsyncIterator
228228from contextlib import asynccontextmanager
229229from dataclasses import dataclass
230- from typing import TypedDict
231230
232231from mcp.server.mcpserver import Context, MCPServer
233232
@@ -236,75 +235,48 @@ from mcp.server.mcpserver import Context, MCPServer
236235class Database :
237236 """ Mock database class for example."""
238237
239- connections: int = 0
240-
241238 @ classmethod
242239 async def connect (cls ) -> " Database" :
243- """ Connect to database (runs once at server startup)."""
244- cls .connections += 1
240+ """ Connect to database."""
245241 return cls ()
246242
247243 async def disconnect (self ) -> None :
248244 """ Disconnect from database."""
249- cls .connections -= 1
245+ pass
250246
251247 def query (self ) -> str :
252248 """ Execute a query."""
253249 return " Query result"
254250
255251
256- class ServerContext (TypedDict ):
257- """ Server-level context (shared across all clients)."""
252+ @dataclass
253+ class AppContext :
254+ """ Application context with typed dependencies."""
258255
259256 db: Database
260257
261258
262- class SessionContext (TypedDict ):
263- """ Session-level context (per-client connection)."""
264-
265- session_id: str
266-
267-
268259@asynccontextmanager
269- async def server_lifespan (server : MCPServer) -> AsyncIterator[ServerContext]:
270- """ Manage server-level lifecycle (runs once at startup).
271-
272- Use for: database pools, ML models, shared caches, global config.
273- """
274- # Initialize on startup (ONCE for all clients)
260+ async def app_lifespan (server : MCPServer) -> AsyncIterator[AppContext]:
261+ """ Manage application lifecycle with type-safe context."""
262+ # Initialize on startup
275263 db = await Database.connect()
276264 try :
277- yield ServerContext (db = db)
265+ yield AppContext (db = db)
278266 finally :
279267 # Cleanup on shutdown
280268 await db.disconnect()
281269
282270
283- @asynccontextmanager
284- async def session_lifespan (server : MCPServer) -> AsyncIterator[SessionContext]:
285- """ Manage session-level lifecycle (runs per-client connection).
286-
287- Use for: user auth, per-client state, session IDs.
288- """
289- # Initialize per-client (runs FOR EACH CLIENT)
290- session_id = " unique-session-id"
291- try :
292- yield SessionContext(session_id = session_id)
293- finally :
294- pass # Cleanup per-client resources
295-
296-
297- # Pass both lifespans to server
298- mcp = MCPServer(" My App" , lifespan = server_lifespan) # MCPServer uses server lifespan
271+ # Pass lifespan to server
272+ mcp = MCPServer(" My App" , lifespan = app_lifespan)
299273
300274
301- # Access type-safe contexts in tools
275+ # Access type-safe lifespan context in tools
302276@mcp.tool ()
303- def query_db (ctx : Context) -> str :
277+ def query_db (ctx : Context[AppContext] ) -> str :
304278 """ Tool that uses initialized resources."""
305- # Access server-level context (shared across all clients)
306- server_ctx: ServerContext = ctx.request_context.session_lifespan_context
307- db = server_ctx.db
279+ db = ctx.request_context.session_lifespan_context.db
308280 return db.query()
309281```
310282
@@ -1684,6 +1656,7 @@ uv run examples/snippets/servers/lowlevel/lifespan.py
16841656from collections.abc import AsyncIterator
16851657from contextlib import asynccontextmanager
16861658from typing import TypedDict
1659+ from uuid import uuid4
16871660
16881661import mcp.server.stdio
16891662from mcp import types
@@ -1694,71 +1667,141 @@ from mcp.server import Server, ServerRequestContext
16941667class Database :
16951668 """ Mock database class for example."""
16961669
1670+ connections: int = 0
1671+
16971672 @ classmethod
16981673 async def connect (cls ) -> " Database" :
16991674 """ Connect to database."""
1700- print (" Database connected" )
1675+ cls .connections += 1
1676+ print (f " Database connected (total connections: { cls .connections} ) " )
17011677 return cls ()
17021678
17031679 async def disconnect (self ) -> None :
17041680 """ Disconnect from database."""
1705- print (" Database disconnected" )
1681+ self .connections -= 1
1682+ print (f " Database disconnected (total connections: { self .connections} ) " )
17061683
17071684 async def query (self , query_str : str ) -> list[dict[str , str ]]:
17081685 """ Execute a query."""
17091686 # Simulate database query
17101687 return [{" id" : " 1" , " name" : " Example" , " query" : query_str}]
17111688
17121689
1713- class AppContext (TypedDict ):
1690+ class ServerContext (TypedDict ):
1691+ """ Server-level context (shared across all clients)."""
1692+
17141693 db: Database
17151694
17161695
1696+ class SessionContext (TypedDict ):
1697+ """ Session-level context (per-client connection)."""
1698+
1699+ session_id: str
1700+
1701+
17171702@asynccontextmanager
1718- async def server_lifespan (_server : Server[AppContext]) -> AsyncIterator[AppContext]:
1719- """ Manage server startup and shutdown lifecycle."""
1703+ async def server_lifespan (_server : Server) -> AsyncIterator[ServerContext]:
1704+ """ Manage server startup and shutdown lifecycle.
1705+
1706+ This runs ONCE when the server process starts, before any clients connect.
1707+ Use this for resources that should be shared across all client connections:
1708+ - Database connection pools
1709+ - Machine learning models
1710+ - Shared caches
1711+ - Global configuration
1712+ """
1713+ print (" [SERVER LIFESPAN] Starting server..." )
17201714 db = await Database.connect()
17211715 try :
1716+ print (" [SERVER LIFESPAN] Server started, database connected" )
17221717 yield {" db" : db}
17231718 finally :
17241719 await db.disconnect()
1720+ print (" [SERVER LIFESPAN] Server stopped, database disconnected" )
1721+
1722+
1723+ @asynccontextmanager
1724+ async def session_lifespan (_server : Server) -> AsyncIterator[SessionContext]:
1725+ """ Manage per-client session lifecycle.
1726+
1727+ This runs FOR EACH CLIENT that connects to the server.
1728+ Use this for resources that are specific to a single client connection:
1729+ - User authentication context
1730+ - Per-client transaction state
1731+ - Client-specific caches
1732+ - Session identifiers
1733+ """
1734+ session_id = str (uuid4())
1735+ print (f " [SESSION LIFESPAN] Session { session_id} started " )
1736+ try :
1737+ yield {" session_id" : session_id}
1738+ finally :
1739+ print (f " [SESSION LIFESPAN] Session { session_id} stopped " )
17251740
17261741
17271742async def handle_list_tools (
1728- ctx : ServerRequestContext[AppContext], params : types.PaginatedRequestParams | None
1743+ ctx : ServerRequestContext[ServerContext, SessionContext],
1744+ params : types.PaginatedRequestParams | None ,
17291745) -> types.ListToolsResult:
17301746 """ List available tools."""
17311747 return types.ListToolsResult(
17321748 tools = [
17331749 types.Tool(
17341750 name = " query_db" ,
1735- description = " Query the database" ,
1751+ description = " Query the database (uses shared server connection) " ,
17361752 input_schema = {
17371753 " type" : " object" ,
17381754 " properties" : {" query" : {" type" : " string" , " description" : " SQL query to execute" }},
17391755 " required" : [" query" ],
17401756 },
1741- )
1757+ ),
1758+ types.Tool(
1759+ name = " get_session_info" ,
1760+ description = " Get information about the current session" ,
1761+ input_schema = {
1762+ " type" : " object" ,
1763+ " properties" : {},
1764+ },
1765+ ),
17421766 ]
17431767 )
17441768
17451769
17461770async def handle_call_tool (
1747- ctx : ServerRequestContext[AppContext], params : types.CallToolRequestParams
1771+ ctx : ServerRequestContext[ServerContext, SessionContext],
1772+ params : types.CallToolRequestParams,
17481773) -> types.CallToolResult:
1749- """ Handle database query tool call."""
1750- if params.name != " query_db" :
1751- raise ValueError (f " Unknown tool: { params.name} " )
1774+ """ Handle tool calls."""
1775+ if params.name == " query_db" :
1776+ # Access server-level resource (shared database connection)
1777+ db = ctx.server_lifespan_context[" db" ]
1778+ results = await db.query((params.arguments or {})[" query" ])
1779+
1780+ return types.CallToolResult(
1781+ content = [
1782+ types.TextContent(
1783+ type = " text" ,
1784+ text = f " Query results (session { ctx.session_lifespan_context[' session_id' ]} ): { results} " ,
1785+ )
1786+ ]
1787+ )
17521788
1753- db = ctx.lifespan_context[" db" ]
1754- results = await db.query((params.arguments or {})[" query" ])
1789+ if params.name == " get_session_info" :
1790+ # Access session-level resource (session ID)
1791+ session_id = ctx.session_lifespan_context[" session_id" ]
17551792
1756- return types.CallToolResult(content = [types.TextContent(type = " text" , text = f " Query results: { results} " )])
1793+ return types.CallToolResult(
1794+ content = [types.TextContent(type = " text" , text = f " Your session ID: { session_id} " )]
1795+ )
1796+
1797+ raise ValueError (f " Unknown tool: { params.name} " )
17571798
17581799
1800+ # Create server with BOTH server and session lifespans
17591801server = Server(
17601802 " example-server" ,
1761- lifespan = server_lifespan,
1803+ server_lifespan = server_lifespan, # Runs once at server startup
1804+ session_lifespan = session_lifespan, # Runs per-client connection
17621805 on_list_tools = handle_list_tools,
17631806 on_call_tool = handle_call_tool,
17641807)
0 commit comments