Skip to content

Commit 710c407

Browse files
committed
refactor: split RequestContext into handler context hierarchy
- HandlerContext (base): session, lifespan_context, experimental - RequestHandlerContext: adds request_id, meta, request, SSE callbacks - NotificationHandlerContext: empty subclass for notifications - Rename Ctx aliases to RequestCtx / NotificationCtx - Remove request_ctx contextvar and request_context property - Tighten string fallback handler signatures to Callable[[Ctx, Any], ...]
1 parent 7ddd731 commit 710c407

File tree

4 files changed

+55
-44
lines changed

4 files changed

+55
-44
lines changed

src/mcp/server/lowlevel/notification_handler.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing_extensions import TypeVar
77

88
from mcp.server.session import ServerSession
9-
from mcp.shared.context import RequestContext
9+
from mcp.shared.context import NotificationHandlerContext
1010
from mcp.types import (
1111
CancelledNotificationParams,
1212
NotificationParams,
@@ -16,7 +16,7 @@
1616
LifespanResultT = TypeVar("LifespanResultT", default=Any)
1717
RequestT = TypeVar("RequestT", default=Any)
1818

19-
Ctx = RequestContext[ServerSession, LifespanResultT, RequestT]
19+
NotificationCtx = NotificationHandlerContext[ServerSession, LifespanResultT]
2020

2121

2222
class NotificationHandler(Generic[LifespanResultT, RequestT]):
@@ -30,40 +30,40 @@ class NotificationHandler(Generic[LifespanResultT, RequestT]):
3030
def __init__(
3131
self,
3232
method: Literal["notifications/initialized"],
33-
handler: Callable[[Ctx, NotificationParams | None], Awaitable[None]],
33+
handler: Callable[[NotificationCtx, NotificationParams | None], Awaitable[None]],
3434
) -> None: ...
3535

3636
@overload
3737
def __init__(
3838
self,
3939
method: Literal["notifications/cancelled"],
40-
handler: Callable[[Ctx, CancelledNotificationParams], Awaitable[None]],
40+
handler: Callable[[NotificationCtx, CancelledNotificationParams], Awaitable[None]],
4141
) -> None: ...
4242

4343
@overload
4444
def __init__(
4545
self,
4646
method: Literal["notifications/progress"],
47-
handler: Callable[[Ctx, ProgressNotificationParams], Awaitable[None]],
47+
handler: Callable[[NotificationCtx, ProgressNotificationParams], Awaitable[None]],
4848
) -> None: ...
4949

5050
@overload
5151
def __init__(
5252
self,
5353
method: Literal["notifications/roots/list_changed"],
54-
handler: Callable[[Ctx, NotificationParams | None], Awaitable[None]],
54+
handler: Callable[[NotificationCtx, NotificationParams | None], Awaitable[None]],
5555
) -> None: ...
5656

5757
@overload
5858
def __init__(
5959
self,
6060
method: str,
61-
handler: Callable[..., Awaitable[None]],
61+
handler: Callable[[NotificationCtx, Any], Awaitable[None]],
6262
) -> None: ...
6363

64-
def __init__(self, method: str, handler: Callable[..., Awaitable[None]]) -> None:
64+
def __init__(self, method: str, handler: Callable[[NotificationCtx, Any], Awaitable[None]]) -> None:
6565
self.method = method
6666
self.endpoint = handler
6767

68-
async def handle(self, ctx: Ctx, params: Any) -> None:
68+
async def handle(self, ctx: NotificationCtx, params: Any) -> None:
6969
await self.endpoint(ctx, params)

src/mcp/server/lowlevel/request_handler.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing_extensions import TypeVar
77

88
from mcp.server.session import ServerSession
9-
from mcp.shared.context import RequestContext
9+
from mcp.shared.context import RequestHandlerContext
1010
from mcp.types import (
1111
CallToolRequestParams,
1212
CallToolResult,
@@ -31,7 +31,7 @@
3131
LifespanResultT = TypeVar("LifespanResultT", default=Any)
3232
RequestT = TypeVar("RequestT", default=Any)
3333

34-
Ctx = RequestContext[ServerSession, LifespanResultT, RequestT]
34+
RequestCtx = RequestHandlerContext[ServerSession, LifespanResultT, RequestT]
3535

3636

3737
class RequestHandler(Generic[LifespanResultT, RequestT]):
@@ -45,96 +45,96 @@ class RequestHandler(Generic[LifespanResultT, RequestT]):
4545
def __init__(
4646
self,
4747
method: Literal["ping"],
48-
handler: Callable[[Ctx, RequestParams | None], Awaitable[EmptyResult]],
48+
handler: Callable[[RequestCtx, RequestParams | None], Awaitable[EmptyResult]],
4949
) -> None: ...
5050

5151
@overload
5252
def __init__(
5353
self,
5454
method: Literal["prompts/list"],
55-
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListPromptsResult]],
55+
handler: Callable[[RequestCtx, PaginatedRequestParams | None], Awaitable[ListPromptsResult]],
5656
) -> None: ...
5757

5858
@overload
5959
def __init__(
6060
self,
6161
method: Literal["prompts/get"],
62-
handler: Callable[[Ctx, GetPromptRequestParams], Awaitable[GetPromptResult]],
62+
handler: Callable[[RequestCtx, GetPromptRequestParams], Awaitable[GetPromptResult]],
6363
) -> None: ...
6464

6565
@overload
6666
def __init__(
6767
self,
6868
method: Literal["resources/list"],
69-
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListResourcesResult]],
69+
handler: Callable[[RequestCtx, PaginatedRequestParams | None], Awaitable[ListResourcesResult]],
7070
) -> None: ...
7171

7272
@overload
7373
def __init__(
7474
self,
7575
method: Literal["resources/templates/list"],
76-
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListResourceTemplatesResult]],
76+
handler: Callable[[RequestCtx, PaginatedRequestParams | None], Awaitable[ListResourceTemplatesResult]],
7777
) -> None: ...
7878

7979
@overload
8080
def __init__(
8181
self,
8282
method: Literal["resources/read"],
83-
handler: Callable[[Ctx, ReadResourceRequestParams], Awaitable[ReadResourceResult]],
83+
handler: Callable[[RequestCtx, ReadResourceRequestParams], Awaitable[ReadResourceResult]],
8484
) -> None: ...
8585

8686
@overload
8787
def __init__(
8888
self,
8989
method: Literal["resources/subscribe"],
90-
handler: Callable[[Ctx, SubscribeRequestParams], Awaitable[EmptyResult]],
90+
handler: Callable[[RequestCtx, SubscribeRequestParams], Awaitable[EmptyResult]],
9191
) -> None: ...
9292

9393
@overload
9494
def __init__(
9595
self,
9696
method: Literal["resources/unsubscribe"],
97-
handler: Callable[[Ctx, UnsubscribeRequestParams], Awaitable[EmptyResult]],
97+
handler: Callable[[RequestCtx, UnsubscribeRequestParams], Awaitable[EmptyResult]],
9898
) -> None: ...
9999

100100
@overload
101101
def __init__(
102102
self,
103103
method: Literal["logging/setLevel"],
104-
handler: Callable[[Ctx, SetLevelRequestParams], Awaitable[EmptyResult]],
104+
handler: Callable[[RequestCtx, SetLevelRequestParams], Awaitable[EmptyResult]],
105105
) -> None: ...
106106

107107
@overload
108108
def __init__(
109109
self,
110110
method: Literal["tools/list"],
111-
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListToolsResult]],
111+
handler: Callable[[RequestCtx, PaginatedRequestParams | None], Awaitable[ListToolsResult]],
112112
) -> None: ...
113113

114114
@overload
115115
def __init__(
116116
self,
117117
method: Literal["tools/call"],
118-
handler: Callable[[Ctx, CallToolRequestParams], Awaitable[CallToolResult]],
118+
handler: Callable[[RequestCtx, CallToolRequestParams], Awaitable[CallToolResult]],
119119
) -> None: ...
120120

121121
@overload
122122
def __init__(
123123
self,
124124
method: Literal["completion/complete"],
125-
handler: Callable[[Ctx, CompleteRequestParams], Awaitable[CompleteResult]],
125+
handler: Callable[[RequestCtx, CompleteRequestParams], Awaitable[CompleteResult]],
126126
) -> None: ...
127127

128128
@overload
129129
def __init__(
130130
self,
131131
method: str,
132-
handler: Callable[..., Awaitable[Any]],
132+
handler: Callable[[RequestCtx, Any], Awaitable[Any]],
133133
) -> None: ...
134134

135-
def __init__(self, method: str, handler: Callable[..., Awaitable[Any]]) -> None:
135+
def __init__(self, method: str, handler: Callable[[RequestCtx, Any], Awaitable[Any]]) -> None:
136136
self.method = method
137137
self.endpoint = handler
138138

139-
async def handle(self, ctx: Ctx, params: Any) -> Any:
139+
async def handle(self, ctx: RequestCtx, params: Any) -> Any:
140140
return await self.endpoint(ctx, params)

src/mcp/server/lowlevel/server.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ async def main():
6868
from mcp.server.streamable_http import EventStore
6969
from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager
7070
from mcp.server.transport_security import TransportSecuritySettings
71-
from mcp.shared.context import RequestContext
71+
from mcp.shared.context import NotificationHandlerContext, RequestHandlerContext
7272
from mcp.shared.exceptions import MCPError
7373
from mcp.shared.message import ServerMessageMetadata, SessionMessage
7474
from mcp.shared.session import RequestResponder
@@ -374,17 +374,17 @@ async def _handle_request(
374374
task_metadata = None
375375
if hasattr(req, "params") and req.params is not None:
376376
task_metadata = getattr(req.params, "task", None)
377-
ctx = RequestContext(
378-
message.request_id,
379-
message.request_meta,
377+
ctx = RequestHandlerContext(
380378
session,
381379
lifespan_context,
382-
Experimental(
380+
experimental=Experimental(
383381
task_metadata=task_metadata,
384382
_client_capabilities=client_capabilities,
385383
_session=session,
386384
_task_support=task_support,
387385
),
386+
request_id=message.request_id,
387+
meta=message.request_meta,
388388
request=request_data,
389389
close_sse_stream=close_sse_stream_cb,
390390
close_standalone_sse_stream=close_standalone_sse_stream_cb,
@@ -418,11 +418,9 @@ async def _handle_notification(
418418
try:
419419
client_capabilities = session.client_params.capabilities if session.client_params else None
420420
task_support = self._experimental_handlers.task_support if self._experimental_handlers else None
421-
ctx: RequestContext[ServerSession, Any, Any] = RequestContext(
422-
request_id="__notification__",
423-
meta=None,
424-
session=session,
425-
lifespan_context=lifespan_context,
421+
ctx = NotificationHandlerContext(
422+
session,
423+
lifespan_context,
426424
experimental=Experimental(
427425
task_metadata=None,
428426
_client_capabilities=client_capabilities,

src/mcp/shared/context.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Request context for MCP handlers."""
1+
"""Handler contexts for MCP handlers."""
22

33
from dataclasses import dataclass, field
44
from typing import Any, Generic
@@ -15,16 +15,29 @@
1515

1616

1717
@dataclass
18-
class RequestContext(Generic[SessionT, LifespanContextT, RequestT]):
19-
request_id: RequestId
20-
meta: RequestParamsMeta | None
18+
class HandlerContext(Generic[SessionT, LifespanContextT]):
19+
"""Base context shared by all handlers."""
20+
2121
session: SessionT
2222
lifespan_context: LifespanContextT
2323
# NOTE: This is typed as Any to avoid circular imports. The actual type is
2424
# mcp.server.experimental.request_context.Experimental, but importing it here
2525
# triggers mcp.server.__init__ -> mcpserver -> tools -> back to this module.
2626
# The Server sets this to an Experimental instance at runtime.
27-
experimental: Any = field(default=None)
28-
request: RequestT | None = None
29-
close_sse_stream: CloseSSEStreamCallback | None = None
30-
close_standalone_sse_stream: CloseSSEStreamCallback | None = None
27+
experimental: Any = field(default=None, kw_only=True)
28+
29+
30+
@dataclass
31+
class RequestHandlerContext(HandlerContext[SessionT, LifespanContextT], Generic[SessionT, LifespanContextT, RequestT]):
32+
"""Context for request handlers."""
33+
34+
request_id: RequestId = field(kw_only=True)
35+
meta: RequestParamsMeta | None = field(kw_only=True)
36+
request: RequestT | None = field(default=None, kw_only=True)
37+
close_sse_stream: CloseSSEStreamCallback | None = field(default=None, kw_only=True)
38+
close_standalone_sse_stream: CloseSSEStreamCallback | None = field(default=None, kw_only=True)
39+
40+
41+
@dataclass
42+
class NotificationHandlerContext(HandlerContext[SessionT, LifespanContextT]):
43+
"""Context for notification handlers."""

0 commit comments

Comments
 (0)