Skip to content

Commit e72832e

Browse files
committed
refactor: address review — kw_only dataclass, type Experimental properly, remove redundant guard
- RequestContext: use @DataClass(kw_only=True) instead of per-field kw_only - RequestContext: type experimental as Experimental | None via TYPE_CHECKING - Server.__init__: remove redundant 'if handlers:' guard - Server: use keyword args for RequestContext construction
1 parent 97b6790 commit e72832e

File tree

2 files changed

+29
-29
lines changed

2 files changed

+29
-29
lines changed

src/mcp/server/lowlevel/server.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,17 @@ def __init__(
140140
self._request_handlers["ping"] = RequestHandler("ping", handler=_ping_handler)
141141

142142
# Process user-provided handlers with duplicate detection
143-
if handlers:
144-
for handler in handlers:
145-
if isinstance(handler, RequestHandler):
146-
if handler.method in self._request_handlers:
147-
raise ValueError(f"Duplicate request handler for '{handler.method}'")
148-
self._request_handlers[handler.method] = handler
149-
elif isinstance(handler, NotificationHandler): # pyright: ignore[reportUnnecessaryIsInstance]
150-
if handler.method in self._notification_handlers:
151-
raise ValueError(f"Duplicate notification handler for '{handler.method}'")
152-
self._notification_handlers[handler.method] = handler
153-
else:
154-
raise TypeError(f"Unknown handler type: {type(handler)}")
143+
for handler in handlers:
144+
if isinstance(handler, RequestHandler):
145+
if handler.method in self._request_handlers:
146+
raise ValueError(f"Duplicate request handler for '{handler.method}'")
147+
self._request_handlers[handler.method] = handler
148+
elif isinstance(handler, NotificationHandler): # pyright: ignore[reportUnnecessaryIsInstance]
149+
if handler.method in self._notification_handlers:
150+
raise ValueError(f"Duplicate notification handler for '{handler.method}'")
151+
self._notification_handlers[handler.method] = handler
152+
else:
153+
raise TypeError(f"Unknown handler type: {type(handler)}")
155154

156155
def _add_handler(self, handler: Handler) -> None:
157156
"""Add a handler, silently replacing any existing handler for the same method."""
@@ -376,8 +375,8 @@ async def _handle_request(
376375
if hasattr(req, "params") and req.params is not None:
377376
task_metadata = getattr(req.params, "task", None)
378377
ctx = RequestContext(
379-
session,
380-
lifespan_context,
378+
session=session,
379+
lifespan_context=lifespan_context,
381380
experimental=Experimental(
382381
task_metadata=task_metadata,
383382
_client_capabilities=client_capabilities,
@@ -424,8 +423,8 @@ async def _handle_notification(
424423
client_capabilities = session.client_params.capabilities if session.client_params else None
425424
task_support = self._experimental_handlers.task_support if self._experimental_handlers else None
426425
ctx = RequestContext(
427-
session,
428-
lifespan_context,
426+
session=session,
427+
lifespan_context=lifespan_context,
429428
experimental=Experimental(
430429
task_metadata=None,
431430
_client_capabilities=client_capabilities,

src/mcp/shared/context.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
"""Request context for MCP handlers."""
22

3-
from dataclasses import dataclass, field
4-
from typing import Any, Generic
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
from typing import TYPE_CHECKING, Any, Generic
57

68
from typing_extensions import TypeVar
79

810
from mcp.shared.message import CloseSSEStreamCallback
911
from mcp.shared.session import BaseSession
1012
from mcp.types import RequestId, RequestParamsMeta
1113

14+
if TYPE_CHECKING:
15+
from mcp.server.experimental.request_context import Experimental
16+
1217
SessionT = TypeVar("SessionT", bound=BaseSession[Any, Any, Any, Any, Any])
1318
LifespanContextT = TypeVar("LifespanContextT")
1419
RequestT = TypeVar("RequestT", default=Any)
1520

1621

17-
@dataclass
22+
@dataclass(kw_only=True)
1823
class RequestContext(Generic[SessionT, LifespanContextT, RequestT]):
1924
"""Context passed to request and notification handlers.
2025
@@ -24,13 +29,9 @@ class RequestContext(Generic[SessionT, LifespanContextT, RequestT]):
2429

2530
session: SessionT
2631
lifespan_context: LifespanContextT
27-
# NOTE: This is typed as Any to avoid circular imports. The actual type is
28-
# mcp.server.experimental.request_context.Experimental, but importing it here
29-
# triggers mcp.server.__init__ -> mcpserver -> tools -> back to this module.
30-
# The Server sets this to an Experimental instance at runtime.
31-
experimental: Any = field(default=None, kw_only=True)
32-
request_id: RequestId | None = field(default=None, kw_only=True)
33-
meta: RequestParamsMeta | None = field(default=None, kw_only=True)
34-
request: RequestT | None = field(default=None, kw_only=True)
35-
close_sse_stream: CloseSSEStreamCallback | None = field(default=None, kw_only=True)
36-
close_standalone_sse_stream: CloseSSEStreamCallback | None = field(default=None, kw_only=True)
32+
experimental: Experimental | None = None
33+
request_id: RequestId | None = None
34+
meta: RequestParamsMeta | None = None
35+
request: RequestT | None = None
36+
close_sse_stream: CloseSSEStreamCallback | None = None
37+
close_standalone_sse_stream: CloseSSEStreamCallback | None = None

0 commit comments

Comments
 (0)