Skip to content

Commit 10766c8

Browse files
committed
refactor: replace Handler objects with on_* constructor kwargs
Replace the RequestHandler/NotificationHandler wrapper classes with direct on_* keyword arguments on the Server constructor. Handlers are now raw callables stored in internal dicts keyed by method string. - Delete handler.py (Handler, RequestHandler, NotificationHandler) - Server constructor takes on_list_tools, on_call_tool, etc. instead of handlers: Sequence[Handler] - Add _add_request_handler / _add_notification_handler for post-construction registration (used by ExperimentalHandlers and MCPServer completion) - Remove RequestT generic from Server — the request context type is transport-specific and not known at construction time - Type ServerMessageMetadata.request_context as Any (transport-agnostic) - Update ExperimentalHandlers to use RequestContext[ServerSession, Any, Any] - Update MCPServer to pass on_* kwargs via _create_handler_kwargs()
1 parent f10995e commit 10766c8

File tree

8 files changed

+199
-357
lines changed

8 files changed

+199
-357
lines changed

docs/migration.md

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,9 @@ await client.read_resource("test://resource")
426426
await client.read_resource(str(my_any_url))
427427
```
428428

429-
### Lowlevel `Server`: decorator-based handlers replaced with `RequestHandler`/`NotificationHandler`
429+
### Lowlevel `Server`: decorator-based handlers replaced with constructor `on_*` params
430430

431-
The lowlevel `Server` class no longer uses decorator methods for handler registration. Instead, handlers are `RequestHandler` and `NotificationHandler` objects passed to the constructor.
431+
The lowlevel `Server` class no longer uses decorator methods for handler registration. Instead, handlers are passed as `on_*` keyword arguments to the constructor.
432432

433433
**Before (v1):**
434434

@@ -449,7 +449,7 @@ async def handle_call_tool(name: str, arguments: dict):
449449
**After (v2):**
450450

451451
```python
452-
from mcp.server.lowlevel import Server, RequestHandler
452+
from mcp.server.lowlevel import Server
453453
from mcp.shared.context import RequestContext
454454
from mcp.types import (
455455
CallToolRequestParams,
@@ -477,23 +477,20 @@ async def handle_call_tool(
477477

478478
server = Server(
479479
"my-server",
480-
handlers=[
481-
RequestHandler("tools/list", handler=handle_list_tools),
482-
RequestHandler("tools/call", handler=handle_call_tool),
483-
],
480+
on_list_tools=handle_list_tools,
481+
on_call_tool=handle_call_tool,
484482
)
485483
```
486484

487485
**Key differences:**
488486

489487
- Handlers receive `(ctx, params)` instead of the full request object or unpacked arguments. `ctx` is a `RequestContext` with `session`, `lifespan_context`, and `experimental` fields (plus `request_id`, `meta`, etc. for request handlers). `params` is the typed request params object.
490488
- Handlers return the full result type (e.g. `ListToolsResult`) rather than unwrapped values (e.g. `list[Tool]`).
491-
- Registration uses method strings (`"tools/call"`) instead of request types (`CallToolRequest`).
492489

493490
**Notification handlers:**
494491

495492
```python
496-
from mcp.server.lowlevel import NotificationHandler
493+
from mcp.server.lowlevel import Server
497494
from mcp.shared.context import RequestContext
498495
from mcp.types import ProgressNotificationParams
499496

@@ -504,9 +501,7 @@ async def handle_progress(
504501

505502
server = Server(
506503
"my-server",
507-
handlers=[
508-
NotificationHandler("notifications/progress", handler=handle_progress),
509-
],
504+
on_progress=handle_progress,
510505
)
511506
```
512507

@@ -555,7 +550,7 @@ from mcp.shared.context import RequestContext
555550

556551
### Experimental: task handler decorators removed
557552

558-
The experimental decorator methods on `ExperimentalHandlers` (`@server.experimental.list_tasks()`, `@server.experimental.get_task()`, etc.) have been removed. Custom task handlers are now registered as `RequestHandler` objects passed to the `Server` constructor, consistent with the new handler pattern.
553+
The experimental decorator methods on `ExperimentalHandlers` (`@server.experimental.list_tasks()`, `@server.experimental.get_task()`, etc.) have been removed.
559554

560555
Default task handlers are still registered automatically via `server.experimental.enable_tasks()`.
561556

@@ -573,19 +568,13 @@ async def custom_get_task(request: GetTaskRequest) -> GetTaskResult:
573568
**After (v2):**
574569

575570
```python
576-
from mcp.server.lowlevel import Server, RequestHandler
571+
from mcp.server.lowlevel import Server
577572
from mcp.types import GetTaskRequestParams, GetTaskResult
578573

579-
async def custom_get_task(ctx, params: GetTaskRequestParams) -> GetTaskResult:
580-
...
581-
582-
server = Server(
583-
"my-server",
584-
handlers=[
585-
RequestHandler("tasks/get", handler=custom_get_task),
586-
],
587-
)
574+
server = Server("my-server")
588575
server.experimental.enable_tasks(task_store)
576+
# Default handlers are registered automatically.
577+
# Custom task handlers are not yet supported via the constructor.
589578
```
590579

591580
## Deprecations
@@ -623,7 +612,7 @@ params = CallToolRequestParams(
623612
The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `MCPServer`. This allows using the streamable HTTP transport without the MCPServer wrapper.
624613

625614
```python
626-
from mcp.server.lowlevel import Server, RequestHandler
615+
from mcp.server.lowlevel import Server
627616
from mcp.shared.context import RequestContext
628617
from mcp.types import ListToolsResult, PaginatedRequestParams
629618

@@ -634,9 +623,7 @@ async def handle_list_tools(
634623

635624
server = Server(
636625
"my-server",
637-
handlers=[
638-
RequestHandler("tools/list", handler=handle_list_tools),
639-
],
626+
on_list_tools=handle_list_tools,
640627
)
641628

642629
app = server.streamable_http_app(

src/mcp/server/experimental/task_result_handler.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ async def handle_task_result(ctx, params):
5252
return await handler.handle(
5353
GetTaskPayloadRequest(params=params), ctx.session, ctx.request_id
5454
)
55-
server = Server(handlers=[
56-
RequestHandler("tasks/result", handler=handle_task_result),
57-
])
55+
server = Server(on_call_tool=..., on_list_tools=...)
5856
"""
5957

6058
def __init__(
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .handler import Handler, NotificationHandler, RequestHandler
21
from .server import NotificationOptions, Server
32

4-
__all__ = ["Handler", "NotificationHandler", "NotificationOptions", "RequestHandler", "Server"]
3+
__all__ = ["NotificationOptions", "Server"]

src/mcp/server/lowlevel/experimental.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from __future__ import annotations
77

88
import logging
9-
from collections.abc import Callable
9+
from collections.abc import Awaitable, Callable
1010
from typing import Any
1111

1212
from mcp.server.experimental.task_support import TaskSupport
13-
from mcp.server.lowlevel.handler import Handler, RequestHandler
13+
from mcp.server.session import ServerSession
1414
from mcp.shared.context import RequestContext
1515
from mcp.shared.exceptions import MCPError
1616
from mcp.shared.experimental.tasks.helpers import cancel_task
@@ -48,10 +48,12 @@ class ExperimentalHandlers:
4848

4949
def __init__(
5050
self,
51-
add_handler: Callable[[Handler], None],
51+
add_request_handler: Callable[
52+
[str, Callable[[RequestContext[ServerSession, Any, Any], Any], Awaitable[Any]]], None
53+
],
5254
has_handler: Callable[[str], bool],
5355
) -> None:
54-
self._add_handler = add_handler
56+
self._add_request_handler = add_request_handler
5557
self._has_handler = has_handler
5658
self._task_support: TaskSupport | None = None
5759

@@ -124,7 +126,7 @@ def _register_default_task_handlers(self) -> None:
124126
if not self._has_handler("tasks/get"):
125127

126128
async def _default_get_task(
127-
ctx: RequestContext[Any, Any, Any], params: GetTaskRequestParams
129+
ctx: RequestContext[ServerSession, Any, Any], params: GetTaskRequestParams
128130
) -> GetTaskResult:
129131
task = await support.store.get_task(params.task_id)
130132
if task is None:
@@ -139,37 +141,37 @@ async def _default_get_task(
139141
poll_interval=task.poll_interval,
140142
)
141143

142-
self._add_handler(RequestHandler("tasks/get", handler=_default_get_task))
144+
self._add_request_handler("tasks/get", _default_get_task)
143145

144146
if not self._has_handler("tasks/result"):
145147

146148
async def _default_get_task_result(
147-
ctx: RequestContext[Any, Any, Any], params: GetTaskPayloadRequestParams
149+
ctx: RequestContext[ServerSession, Any, Any], params: GetTaskPayloadRequestParams
148150
) -> GetTaskPayloadResult:
149151
assert ctx.request_id is not None
150152
req = GetTaskPayloadRequest(params=params)
151153
result = await support.handler.handle(req, ctx.session, ctx.request_id)
152154
return result
153155

154-
self._add_handler(RequestHandler("tasks/result", handler=_default_get_task_result))
156+
self._add_request_handler("tasks/result", _default_get_task_result)
155157

156158
if not self._has_handler("tasks/list"):
157159

158160
async def _default_list_tasks(
159-
ctx: RequestContext[Any, Any, Any], params: PaginatedRequestParams | None
161+
ctx: RequestContext[ServerSession, Any, Any], params: PaginatedRequestParams | None
160162
) -> ListTasksResult:
161163
cursor = params.cursor if params else None
162164
tasks, next_cursor = await support.store.list_tasks(cursor)
163165
return ListTasksResult(tasks=tasks, next_cursor=next_cursor)
164166

165-
self._add_handler(RequestHandler("tasks/list", handler=_default_list_tasks))
167+
self._add_request_handler("tasks/list", _default_list_tasks)
166168

167169
if not self._has_handler("tasks/cancel"):
168170

169171
async def _default_cancel_task(
170-
ctx: RequestContext[Any, Any, Any], params: CancelTaskRequestParams
172+
ctx: RequestContext[ServerSession, Any, Any], params: CancelTaskRequestParams
171173
) -> CancelTaskResult:
172174
result = await cancel_task(support.store, params.task_id)
173175
return result
174176

175-
self._add_handler(RequestHandler("tasks/cancel", handler=_default_cancel_task))
177+
self._add_request_handler("tasks/cancel", _default_cancel_task)

0 commit comments

Comments
 (0)