Skip to content

Commit b4da788

Browse files
committed
sketch: lowlevel server v2 with overloaded handler classes
Design sketch for a new low-level server API using RequestHandler and NotificationHandler classes with @overload-based typing. Key features: - Literal method strings ("tools/call", "notifications/progress", etc.) paired with strongly-typed handler callables via overloads - Generic context (RequestContext[ServerSession, LifespanResultT, RequestT]) flowing through handler signatures - Custom handlers supported via str fallback overload - Server builds dispatch dicts at init with duplicate detection - add_handler() for post-construction registration (replaces silently)
1 parent b38716e commit b4da788

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
from collections.abc import Awaitable, Callable
2+
from typing import Any, Generic, Literal, overload
3+
4+
from typing_extensions import TypeVar
5+
6+
from mcp.server.session import ServerSession
7+
from mcp.shared.context import RequestContext
8+
from mcp.types import (
9+
CallToolRequestParams,
10+
CallToolResult,
11+
CancelledNotificationParams,
12+
CompleteRequestParams,
13+
CompleteResult,
14+
EmptyResult,
15+
GetPromptRequestParams,
16+
GetPromptResult,
17+
ListPromptsResult,
18+
ListResourcesResult,
19+
ListResourceTemplatesResult,
20+
ListToolsResult,
21+
NotificationParams,
22+
PaginatedRequestParams,
23+
ProgressNotificationParams,
24+
ReadResourceRequestParams,
25+
ReadResourceResult,
26+
RequestParams,
27+
SetLevelRequestParams,
28+
SubscribeRequestParams,
29+
TextContent,
30+
UnsubscribeRequestParams,
31+
)
32+
33+
LifespanResultT = TypeVar("LifespanResultT", default=Any)
34+
RequestT = TypeVar("RequestT", default=Any)
35+
36+
Ctx = RequestContext[ServerSession, LifespanResultT, RequestT]
37+
38+
39+
class RequestHandler(Generic[LifespanResultT, RequestT]):
40+
@overload
41+
def __init__(
42+
self,
43+
method: Literal["ping"],
44+
handler: Callable[[Ctx, RequestParams | None], Awaitable[EmptyResult]],
45+
) -> None: ...
46+
47+
@overload
48+
def __init__(
49+
self,
50+
method: Literal["prompts/list"],
51+
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListPromptsResult]],
52+
) -> None: ...
53+
54+
@overload
55+
def __init__(
56+
self,
57+
method: Literal["prompts/get"],
58+
handler: Callable[[Ctx, GetPromptRequestParams], Awaitable[GetPromptResult]],
59+
) -> None: ...
60+
61+
@overload
62+
def __init__(
63+
self,
64+
method: Literal["resources/list"],
65+
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListResourcesResult]],
66+
) -> None: ...
67+
68+
@overload
69+
def __init__(
70+
self,
71+
method: Literal["resources/templates/list"],
72+
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListResourceTemplatesResult]],
73+
) -> None: ...
74+
75+
@overload
76+
def __init__(
77+
self,
78+
method: Literal["resources/read"],
79+
handler: Callable[[Ctx, ReadResourceRequestParams], Awaitable[ReadResourceResult]],
80+
) -> None: ...
81+
82+
@overload
83+
def __init__(
84+
self,
85+
method: Literal["resources/subscribe"],
86+
handler: Callable[[Ctx, SubscribeRequestParams], Awaitable[EmptyResult]],
87+
) -> None: ...
88+
89+
@overload
90+
def __init__(
91+
self,
92+
method: Literal["resources/unsubscribe"],
93+
handler: Callable[[Ctx, UnsubscribeRequestParams], Awaitable[EmptyResult]],
94+
) -> None: ...
95+
96+
@overload
97+
def __init__(
98+
self,
99+
method: Literal["logging/setLevel"],
100+
handler: Callable[[Ctx, SetLevelRequestParams], Awaitable[EmptyResult]],
101+
) -> None: ...
102+
103+
@overload
104+
def __init__(
105+
self,
106+
method: Literal["tools/list"],
107+
handler: Callable[[Ctx, PaginatedRequestParams | None], Awaitable[ListToolsResult]],
108+
) -> None: ...
109+
110+
@overload
111+
def __init__(
112+
self,
113+
method: Literal["tools/call"],
114+
handler: Callable[[Ctx, CallToolRequestParams], Awaitable[CallToolResult]],
115+
) -> None: ...
116+
117+
@overload
118+
def __init__(
119+
self,
120+
method: Literal["completion/complete"],
121+
handler: Callable[[Ctx, CompleteRequestParams], Awaitable[CompleteResult]],
122+
) -> None: ...
123+
124+
def __init__(self, method: str, handler: Callable[[Any, Any], Any]) -> None:
125+
self.method = method
126+
self.endpoint = handler
127+
128+
async def handle(self, ctx: Ctx, params: Any) -> Any:
129+
return await self.endpoint(ctx, params)
130+
131+
132+
class NotificationHandler(Generic[LifespanResultT, RequestT]):
133+
@overload
134+
def __init__(
135+
self,
136+
method: Literal["notifications/initialized"],
137+
handler: Callable[[Ctx, NotificationParams | None], Awaitable[None]],
138+
) -> None: ...
139+
140+
@overload
141+
def __init__(
142+
self,
143+
method: Literal["notifications/cancelled"],
144+
handler: Callable[[Ctx, CancelledNotificationParams], Awaitable[None]],
145+
) -> None: ...
146+
147+
@overload
148+
def __init__(
149+
self,
150+
method: Literal["notifications/progress"],
151+
handler: Callable[[Ctx, ProgressNotificationParams], Awaitable[None]],
152+
) -> None: ...
153+
154+
@overload
155+
def __init__(
156+
self,
157+
method: Literal["notifications/roots/list_changed"],
158+
handler: Callable[[Ctx, NotificationParams | None], Awaitable[None]],
159+
) -> None: ...
160+
161+
def __init__(self, method: str, handler: Callable[[Any, Any], Any]) -> None:
162+
self.method = method
163+
self.endpoint = handler
164+
165+
async def handle(self, ctx: Ctx, params: Any) -> None:
166+
await self.endpoint(ctx, params)
167+
168+
169+
class Server(Generic[LifespanResultT, RequestT]):
170+
def __init__(
171+
self,
172+
handlers: list[RequestHandler[LifespanResultT, RequestT] | NotificationHandler[LifespanResultT, RequestT]],
173+
) -> None:
174+
self._request_handlers: dict[str, RequestHandler[LifespanResultT, RequestT]] = {}
175+
self._notification_handlers: dict[str, NotificationHandler[LifespanResultT, RequestT]] = {}
176+
for handler in handlers:
177+
if isinstance(handler, RequestHandler):
178+
if handler.method in self._request_handlers:
179+
raise ValueError(f"Duplicate request handler for '{handler.method}'")
180+
self._request_handlers[handler.method] = handler
181+
elif isinstance(handler, NotificationHandler): # pyright: ignore[reportUnnecessaryIsInstance]
182+
if handler.method in self._notification_handlers:
183+
raise ValueError(f"Duplicate notification handler for '{handler.method}'")
184+
self._notification_handlers[handler.method] = handler
185+
else:
186+
raise TypeError(f"Unknown handler type: {type(handler)}")
187+
188+
def add_handler(
189+
self, handler: RequestHandler[LifespanResultT, RequestT] | NotificationHandler[LifespanResultT, RequestT]
190+
) -> None:
191+
if isinstance(handler, RequestHandler):
192+
self._request_handlers[handler.method] = handler
193+
elif isinstance(handler, NotificationHandler): # pyright: ignore[reportUnnecessaryIsInstance]
194+
self._notification_handlers[handler.method] = handler
195+
else:
196+
raise TypeError(f"Unknown handler type: {type(handler)}")
197+
198+
199+
# ===== sample usage below ====
200+
201+
202+
async def call_tool_handler(context: Ctx, params: CallToolRequestParams) -> CallToolResult:
203+
if params.name == "slow_tool":
204+
...
205+
return CallToolResult(content=[TextContent(type="text", text=f"Called {params.name}")], is_error=False)
206+
207+
208+
async def list_handler(context: Ctx, params: PaginatedRequestParams | None) -> ListToolsResult: ...
209+
async def progress_handler(context: Ctx, params: ProgressNotificationParams) -> None: ...
210+
async def cancelled_handler(context: Ctx, params: CancelledNotificationParams) -> None: ...
211+
212+
213+
app2 = Server(
214+
handlers=[
215+
RequestHandler("tools/call", handler=call_tool_handler),
216+
RequestHandler("tools/list", handler=list_handler),
217+
NotificationHandler("notifications/progress", handler=progress_handler),
218+
NotificationHandler("notifications/cancelled", handler=cancelled_handler),
219+
],
220+
)

0 commit comments

Comments
 (0)