Skip to content

Commit 4d3e05f

Browse files
committed
refactor: improve lifespan context typing and documentation
- Add proper generic parameter for lifespan context type - Update README with TypedDict example for strong typing - Fix context variable initialization in server - Improve property return type safety - Remove redundant documentation - Ensure compatibility with existing tests
1 parent fddba00 commit 4d3e05f

File tree

4 files changed

+22
-16
lines changed

4 files changed

+22
-16
lines changed

README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you bui
128128
The FastMCP server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
129129

130130
```python
131+
# Add lifespan support for startup/shutdown with strong typing
132+
from dataclasses import dataclass
133+
from typing import AsyncIterator
131134
from mcp.server.fastmcp import FastMCP
132135

133136
# Create a named server
@@ -136,22 +139,25 @@ mcp = FastMCP("My App")
136139
# Specify dependencies for deployment and development
137140
mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
138141

139-
# Add lifespan support for startup/shutdown
142+
@dataclass
143+
class AppContext:
144+
db: Database # Replace with your actual DB type
145+
140146
@asynccontextmanager
141-
async def app_lifespan(server: FastMCP) -> AsyncIterator[dict]:
142-
"""Manage application lifecycle"""
147+
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
148+
"""Manage application lifecycle with type-safe context"""
143149
try:
144150
# Initialize on startup
145151
await db.connect()
146-
yield {"db": db}
152+
yield AppContext(db=db)
147153
finally:
148154
# Cleanup on shutdown
149155
await db.disconnect()
150156

151157
# Pass lifespan to server
152158
mcp = FastMCP("My App", lifespan=app_lifespan)
153159

154-
# Access lifespan context in tools
160+
# Access type-safe lifespan context in tools
155161
@mcp.tool()
156162
def query_db(ctx: Context) -> str:
157163
"""Tool that uses initialized resources"""
@@ -387,7 +393,6 @@ async def query_db(name: str, arguments: dict) -> list:
387393
The lifespan API provides:
388394
- A way to initialize resources when the server starts and clean them up when it stops
389395
- Access to initialized resources through the request context in handlers
390-
- Support for both low-level Server and FastMCP classes
391396
- Type-safe context passing between lifespan and request handlers
392397

393398
```python

src/mcp/server/fastmcp/server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
100100

101101
lifespan: (
102102
Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
103-
) = Field(None, description="Lifespan contexte manager")
103+
) = Field(None, description="Lifespan context manager")
104104

105105

106106
def lifespan_wrapper(

src/mcp/server/lowlevel/server.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ async def main():
8585

8686
logger = logging.getLogger(__name__)
8787

88-
request_ctx: contextvars.ContextVar[RequestContext[ServerSession]] = (
88+
LifespanResultT = TypeVar("LifespanResultT")
89+
90+
# This will be properly typed in each Server instance's context
91+
request_ctx: contextvars.ContextVar[RequestContext[ServerSession, Any]] = (
8992
contextvars.ContextVar("request_ctx")
9093
)
9194

@@ -102,9 +105,6 @@ def __init__(
102105
self.tools_changed = tools_changed
103106

104107

105-
LifespanResultT = TypeVar("LifespanResultT")
106-
107-
108108
@asynccontextmanager
109109
async def lifespan(server: "Server") -> AsyncIterator[object]:
110110
"""Default lifespan context manager that does nothing.
@@ -212,7 +212,7 @@ def get_capabilities(
212212
)
213213

214214
@property
215-
def request_context(self) -> RequestContext[ServerSession]:
215+
def request_context(self) -> RequestContext[ServerSession, LifespanResultT]:
216216
"""If called outside of a request context, this will raise a LookupError."""
217217
return request_ctx.get()
218218

@@ -510,7 +510,7 @@ async def _handle_request(
510510
message: RequestResponder,
511511
req: Any,
512512
session: ServerSession,
513-
lifespan_context: object,
513+
lifespan_context: LifespanResultT,
514514
raise_exceptions: bool,
515515
):
516516
logger.info(f"Processing request of type {type(req).__name__}")

src/mcp/shared/context.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from dataclasses import dataclass
2-
from typing import Any, Generic, TypeVar
2+
from typing import Generic, TypeVar
33

44
from mcp.shared.session import BaseSession
55
from mcp.types import RequestId, RequestParams
66

77
SessionT = TypeVar("SessionT", bound=BaseSession)
8+
LifespanContextT = TypeVar("LifespanContextT")
89

910

1011
@dataclass
11-
class RequestContext(Generic[SessionT]):
12+
class RequestContext(Generic[SessionT, LifespanContextT]):
1213
request_id: RequestId
1314
meta: RequestParams.Meta | None
1415
session: SessionT
15-
lifespan_context: Any
16+
lifespan_context: LifespanContextT

0 commit comments

Comments
 (0)