Skip to content

Commit bbd0ed3

Browse files
committed
Address review feedback
- Remove set_task_result_handler wrapper method and its test - Remove internal comment block for request builders - Mark add_response_router as experimental in docstring - Add experimental tasks documentation link to README - Add comment explaining why experimental field uses Any type (circular import: mcp.server.__init__ -> fastmcp -> context)
1 parent 14c8fb3 commit bbd0ed3

File tree

7 files changed

+8
-73
lines changed

7 files changed

+8
-73
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,7 @@ MCP servers declare capabilities during initialization:
25122512
## Documentation
25132513

25142514
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
2515+
- [Experimental Features (Tasks)](https://modelcontextprotocol.github.io/python-sdk/experimental/tasks/)
25152516
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
25162517
- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest)
25172518
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)

src/mcp/server/session.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ async def handle_list_prompts(ctx: RequestContext) -> list[types.Prompt]:
5252
from mcp.shared.experimental.tasks.capabilities import check_tasks_capability
5353
from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY
5454
from mcp.shared.message import ServerMessageMetadata, SessionMessage
55-
from mcp.shared.response_router import ResponseRouter
5655
from mcp.shared.session import (
5756
BaseSession,
5857
RequestResponder,
@@ -157,28 +156,6 @@ def check_client_capability(self, capability: types.ClientCapabilities) -> bool:
157156

158157
return True
159158

160-
def set_task_result_handler(self, handler: ResponseRouter) -> None:
161-
"""
162-
Set a response router for task-augmented requests.
163-
164-
This enables response routing for task-augmented requests. When a
165-
ServerTaskContext enqueues an elicitation request, the response will be
166-
routed back through this handler.
167-
168-
The handler is automatically registered as a response router.
169-
170-
Args:
171-
handler: The ResponseRouter (typically TaskResultHandler) to use
172-
173-
Example:
174-
from mcp.server.experimental.task_result_handler import TaskResultHandler
175-
task_store = InMemoryTaskStore()
176-
message_queue = InMemoryTaskMessageQueue()
177-
handler = TaskResultHandler(task_store, message_queue)
178-
session.set_task_result_handler(handler)
179-
"""
180-
self.add_response_router(handler)
181-
182159
async def _receive_loop(self) -> None:
183160
async with self._incoming_message_stream_writer:
184161
await super()._receive_loop()
@@ -483,14 +460,6 @@ async def send_elicit_complete(
483460
related_request_id,
484461
)
485462

486-
# =========================================================================
487-
# Request builders for task queueing (internal use)
488-
# =========================================================================
489-
#
490-
# These methods build JSON-RPC requests without sending them. They are used
491-
# by TaskContext to construct requests that will be queued instead of sent
492-
# directly, avoiding code duplication between ServerSession and TaskContext.
493-
494463
def _build_elicit_form_request(
495464
self,
496465
message: str,

src/mcp/shared/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,9 @@ class RequestContext(Generic[SessionT, LifespanContextT, RequestT]):
2121
meta: RequestParams.Meta | None
2222
session: SessionT
2323
lifespan_context: LifespanContextT
24-
experimental: Any = field(default=None) # Set to Experimental instance by Server
24+
# NOTE: This is typed as Any to avoid circular imports. The actual type is
25+
# mcp.server.experimental.request_context.Experimental, but importing it here
26+
# triggers mcp.server.__init__ -> fastmcp -> tools -> back to this module.
27+
# The Server sets this to an Experimental instance at runtime.
28+
experimental: Any = field(default=None)
2529
request: RequestT | None = None

src/mcp/shared/session.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ def add_response_router(self, router: ResponseRouter) -> None:
211211
response stream mechanism. This is used by TaskResultHandler to route
212212
responses for queued task requests back to their resolvers.
213213
214+
WARNING: This is an experimental API that may change without notice.
215+
214216
Args:
215217
router: A ResponseRouter implementation
216218
"""

tests/experimental/tasks/client/test_tasks.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ async def list_tools():
6464
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
6565
ctx = server.request_context
6666
app = ctx.lifespan_context
67-
6867
if ctx.experimental.is_task:
6968
task_metadata = ctx.experimental.task_metadata
7069
assert task_metadata is not None
@@ -174,7 +173,6 @@ async def list_tools():
174173
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
175174
ctx = server.request_context
176175
app = ctx.lifespan_context
177-
178176
if ctx.experimental.is_task:
179177
task_metadata = ctx.experimental.task_metadata
180178
assert task_metadata is not None
@@ -283,7 +281,6 @@ async def list_tools():
283281
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
284282
ctx = server.request_context
285283
app = ctx.lifespan_context
286-
287284
if ctx.experimental.is_task:
288285
task_metadata = ctx.experimental.task_metadata
289286
assert task_metadata is not None
@@ -381,7 +378,6 @@ async def list_tools():
381378
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
382379
ctx = server.request_context
383380
app = ctx.lifespan_context
384-
385381
if ctx.experimental.is_task:
386382
task_metadata = ctx.experimental.task_metadata
387383
assert task_metadata is not None

tests/experimental/tasks/server/test_integration.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ async def list_tools():
9393
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
9494
ctx = server.request_context
9595
app = ctx.lifespan_context
96-
9796
if name == "process_data" and ctx.experimental.is_task:
9897
# 1. Create task in store
9998
task_metadata = ctx.experimental.task_metadata
@@ -254,7 +253,6 @@ async def list_tools():
254253
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent] | CreateTaskResult:
255254
ctx = server.request_context
256255
app = ctx.lifespan_context
257-
258256
if name == "failing_task" and ctx.experimental.is_task:
259257
task_metadata = ctx.experimental.task_metadata
260258
assert task_metadata is not None

tests/experimental/tasks/server/test_server.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@
88

99
from mcp.client.session import ClientSession
1010
from mcp.server import Server
11-
from mcp.server.experimental.task_result_handler import TaskResultHandler
1211
from mcp.server.lowlevel import NotificationOptions
1312
from mcp.server.models import InitializationOptions
1413
from mcp.server.session import ServerSession
1514
from mcp.shared.exceptions import McpError
16-
from mcp.shared.experimental.tasks.in_memory_task_store import InMemoryTaskStore
17-
from mcp.shared.experimental.tasks.message_queue import InMemoryTaskMessageQueue
1815
from mcp.shared.message import ServerMessageMetadata, SessionMessage
1916
from mcp.shared.response_router import ResponseRouter
2017
from mcp.shared.session import RequestResponder
@@ -559,38 +556,6 @@ async def run_server() -> None:
559556
tg.cancel_scope.cancel()
560557

561558

562-
@pytest.mark.anyio
563-
async def test_set_task_result_handler() -> None:
564-
"""Test that set_task_result_handler adds the handler as a response router."""
565-
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](10)
566-
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](10)
567-
568-
store = InMemoryTaskStore()
569-
queue = InMemoryTaskMessageQueue()
570-
handler = TaskResultHandler(store, queue)
571-
572-
try:
573-
async with ServerSession(
574-
client_to_server_receive,
575-
server_to_client_send,
576-
InitializationOptions(
577-
server_name="test-server",
578-
server_version="1.0.0",
579-
capabilities=ServerCapabilities(),
580-
),
581-
) as server_session:
582-
# Use set_task_result_handler (the method we're testing)
583-
server_session.set_task_result_handler(handler)
584-
585-
# Verify handler was added as a response router
586-
assert handler in server_session._response_routers
587-
finally: # pragma: no cover
588-
await server_to_client_send.aclose()
589-
await server_to_client_receive.aclose()
590-
await client_to_server_send.aclose()
591-
await client_to_server_receive.aclose()
592-
593-
594559
@pytest.mark.anyio
595560
async def test_build_elicit_form_request() -> None:
596561
"""Test that _build_elicit_form_request builds a proper elicitation request."""

0 commit comments

Comments
 (0)