Skip to content

Commit df2d3a5

Browse files
dsp-antKludex
andauthored
Fix #355: Fix type error with lifespan context (#368)
Co-authored-by: Marcelo Trylesinski <[email protected]>
1 parent dfbe56d commit df2d3a5

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

src/mcp/server/fastmcp/server.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,9 @@ async def read_resource(self, uri: str | AnyUrl) -> Iterable[ReadResourceContent
652652
Returns:
653653
The resource content as either text or bytes
654654
"""
655-
assert self._fastmcp is not None, (
656-
"Context is not available outside of a request"
657-
)
655+
assert (
656+
self._fastmcp is not None
657+
), "Context is not available outside of a request"
658658
return await self._fastmcp.read_resource(uri)
659659

660660
async def log(

src/mcp/shared/context.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from mcp.types import RequestId, RequestParams
88

99
SessionT = TypeVar("SessionT", bound=BaseSession[Any, Any, Any, Any, Any])
10-
LifespanContextT = TypeVar("LifespanContextT", default=None)
10+
LifespanContextT = TypeVar("LifespanContextT")
1111

1212

1313
@dataclass

src/mcp/shared/progress.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from pydantic import BaseModel
77

8-
from mcp.shared.context import RequestContext
8+
from mcp.shared.context import LifespanContextT, RequestContext
99
from mcp.shared.session import (
1010
BaseSession,
1111
ReceiveNotificationT,
@@ -60,7 +60,8 @@ def progress(
6060
SendResultT,
6161
ReceiveRequestT,
6262
ReceiveNotificationT,
63-
]
63+
],
64+
LifespanContextT,
6465
],
6566
total: float | None = None,
6667
) -> Generator[

tests/issues/test_355_type_error.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from collections.abc import AsyncIterator
2+
from contextlib import asynccontextmanager
3+
from dataclasses import dataclass
4+
5+
from mcp.server.fastmcp import Context, FastMCP
6+
7+
8+
class Database: # Replace with your actual DB type
9+
@classmethod
10+
async def connect(cls):
11+
return cls()
12+
13+
async def disconnect(self):
14+
pass
15+
16+
def query(self):
17+
return "Hello, World!"
18+
19+
20+
# Create a named server
21+
mcp = FastMCP("My App")
22+
23+
24+
@dataclass
25+
class AppContext:
26+
db: Database
27+
28+
29+
@asynccontextmanager
30+
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
31+
"""Manage application lifecycle with type-safe context"""
32+
# Initialize on startup
33+
db = await Database.connect()
34+
try:
35+
yield AppContext(db=db)
36+
finally:
37+
# Cleanup on shutdown
38+
await db.disconnect()
39+
40+
41+
# Pass lifespan to server
42+
mcp = FastMCP("My App", lifespan=app_lifespan)
43+
44+
45+
# Access type-safe lifespan context in tools
46+
@mcp.tool()
47+
def query_db(ctx: Context) -> str:
48+
"""Tool that uses initialized resources"""
49+
db = ctx.request_context.lifespan_context.db
50+
return db.query()

0 commit comments

Comments
 (0)