Skip to content

Commit b8f4917

Browse files
authored
[v1.x] Deprecate the WebSocket transport and the experimental tasks entry points (#2828)
1 parent 2309e5e commit b8f4917

18 files changed

Lines changed: 219 additions & 32 deletions

File tree

docs/experimental/index.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Experimental Features
22

3-
!!! warning "Experimental APIs"
3+
!!! warning "Deprecated"
44

5-
The features in this section are experimental and may change without notice.
6-
They track the evolving MCP specification and are not yet stable.
5+
The experimental tasks API is deprecated and will be removed in mcp 2.0.
6+
Tasks (SEP-1686) were removed from the MCP specification and are expected
7+
to return as a separate MCP extension in a future release.
78

89
This section documents experimental features in the MCP Python SDK. These features
9-
implement draft specifications that are still being refined.
10+
are deprecated and remain available on the 1.x line only for existing users.
1011

1112
## Available Experimental Features
1213

@@ -36,8 +37,10 @@ async def handle_get_task(request: GetTaskRequest) -> GetTaskResult:
3637
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
3738
```
3839

40+
Accessing the `.experimental` properties emits a `DeprecationWarning`.
41+
3942
## Providing Feedback
4043

41-
Since these features are experimental, feedback is especially valuable. If you encounter
42-
issues or have suggestions, please open an issue on the
44+
If you rely on these features and have feedback on their deprecation or the planned
45+
MCP extension, please open an issue on the
4346
[python-sdk repository](https://github.com/modelcontextprotocol/python-sdk/issues).

docs/experimental/tasks-client.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Client Task Usage
22

3-
!!! warning "Experimental"
3+
!!! warning "Deprecated"
44

5-
Tasks are an experimental feature. The API may change without notice.
5+
The experimental tasks API is deprecated and will be removed in mcp 2.0.
6+
Tasks (SEP-1686) were removed from the MCP specification and are expected
7+
to return as a separate MCP extension in a future release.
68

79
This guide covers calling task-augmented tools from clients, handling the `input_required` status, and advanced patterns like receiving task requests from servers.
810

docs/experimental/tasks-server.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Server Task Implementation
22

3-
!!! warning "Experimental"
3+
!!! warning "Deprecated"
44

5-
Tasks are an experimental feature. The API may change without notice.
5+
The experimental tasks API is deprecated and will be removed in mcp 2.0.
6+
Tasks (SEP-1686) were removed from the MCP specification and are expected
7+
to return as a separate MCP extension in a future release.
68

79
This guide covers implementing task support in MCP servers, from basic setup to advanced patterns like elicitation and sampling within tasks.
810

docs/experimental/tasks.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Tasks
22

3-
!!! warning "Experimental"
3+
!!! warning "Deprecated"
44

5-
Tasks are an experimental feature tracking the draft MCP specification.
6-
The API may change without notice.
5+
The experimental tasks API is deprecated and will be removed in mcp 2.0.
6+
Tasks (SEP-1686) were removed from the MCP specification and are expected
7+
to return as a separate MCP extension in a future release.
78

89
Tasks enable asynchronous request handling in MCP. Instead of blocking until an operation completes, the receiver creates a task, returns immediately, and the requestor polls for the result.
910

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,13 @@ venv = ".venv"
107107
# those private functions instead of testing the private functions directly. It makes it easier to maintain the code source
108108
# and refactor code that is not public.
109109
executionEnvironments = [
110+
# The experimental tasks API and the WebSocket transport are deprecated; the suites and
111+
# examples that cover them intentionally use the deprecated APIs.
112+
{ root = "tests/experimental", extraPaths = ["."], reportUnusedFunction = false, reportPrivateUsage = false, reportDeprecated = false },
113+
{ root = "tests/shared/test_ws.py", extraPaths = ["."], reportUnusedFunction = false, reportPrivateUsage = false, reportDeprecated = false },
110114
{ root = "tests", extraPaths = ["."], reportUnusedFunction = false, reportPrivateUsage = false },
115+
{ root = "examples/servers/simple-task", reportUnusedFunction = false, reportDeprecated = false },
116+
{ root = "examples/servers/simple-task-interactive", reportUnusedFunction = false, reportDeprecated = false },
111117
{ root = "examples/servers", reportUnusedFunction = false },
112118
{ root = "examples/snippets/clients/logging_client.py", reportUndefinedVariable = false, reportUnknownArgumentType = false },
113119
{ root = "examples/snippets/clients/roots_example.py", reportUndefinedVariable = false, reportUnknownArgumentType = false, reportArgumentType = false },

src/mcp/client/session.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import warnings
23
from datetime import timedelta
34
from typing import Any, Protocol, overload
45

@@ -17,6 +18,15 @@
1718

1819
DEFAULT_CLIENT_INFO = types.Implementation(name="mcp", version="0.1.0")
1920

21+
# Type checkers only surface `@deprecated` messages given as string literals, so the same text is
22+
# repeated inline at each deprecated entry point (here, server/session.py, server/lowlevel/server.py).
23+
# Keep those copies, the filterwarnings marks in the tasks test suites, and the pytest.warns match in
24+
# tests/experimental/tasks/test_deprecations.py prefix-aligned when rewording.
25+
_EXPERIMENTAL_TASKS_DEPRECATION = (
26+
"The experimental tasks API is deprecated and will be removed in mcp 2.0: tasks (SEP-1686) were removed"
27+
" from the MCP specification and are expected to return as a separate MCP extension."
28+
)
29+
2030
logger = logging.getLogger("client")
2131

2232

@@ -143,6 +153,8 @@ def __init__(
143153
self._experimental_features: ExperimentalClientFeatures | None = None
144154

145155
# Experimental: Task handlers (use defaults if not provided)
156+
if experimental_task_handlers is not None:
157+
warnings.warn(_EXPERIMENTAL_TASKS_DEPRECATION, DeprecationWarning, stacklevel=2)
146158
self._task_handlers = experimental_task_handlers or ExperimentalTaskHandlers()
147159

148160
async def initialize(self) -> types.InitializeResult:
@@ -204,10 +216,16 @@ def get_server_capabilities(self) -> types.ServerCapabilities | None:
204216
return self._server_capabilities
205217

206218
@property
219+
@deprecated(
220+
"The experimental tasks API is deprecated and will be removed in mcp 2.0: tasks (SEP-1686) were removed"
221+
" from the MCP specification and are expected to return as a separate MCP extension."
222+
)
207223
def experimental(self) -> ExperimentalClientFeatures:
208224
"""Experimental APIs for tasks and other features.
209225
210-
WARNING: These APIs are experimental and may change without notice.
226+
Deprecated: the experimental tasks API will be removed in mcp 2.0. Tasks
227+
(SEP-1686) were removed from the MCP specification and are expected to
228+
return as a separate MCP extension.
211229
212230
Example:
213231
status = await session.experimental.get_task(task_id)

src/mcp/client/websocket.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import anyio
77
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
88
from pydantic import ValidationError
9+
from typing_extensions import deprecated
910
from websockets.asyncio.client import connect as ws_connect
1011
from websockets.typing import Subprotocol
1112

@@ -15,6 +16,10 @@
1516
logger = logging.getLogger(__name__)
1617

1718

19+
@deprecated(
20+
"The WebSocket client transport is deprecated and will be removed in mcp 2.0. WebSocket was never part of"
21+
" the MCP specification; use the streamable HTTP transport (`streamable_http_client`) instead."
22+
)
1823
@asynccontextmanager
1924
async def websocket_client(
2025
url: str,
@@ -25,6 +30,9 @@ async def websocket_client(
2530
"""
2631
WebSocket client transport for MCP, symmetrical to the server version.
2732
33+
Deprecated: this transport will be removed in mcp 2.0. WebSocket was never
34+
part of the MCP specification; use the streamable HTTP transport instead.
35+
2836
Connects to 'url' using the 'mcp' subprotocol, then yields:
2937
(read_stream, write_stream)
3038

src/mcp/server/experimental/request_context.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ async def work(task: ServerTaskContext) -> CallToolResult:
252252
task_group = support.task_group
253253

254254
if task_id is None:
255-
session_scope = self._session.experimental.task_session_scope
255+
features = self._session._experimental # pyright: ignore[reportPrivateUsage]
256+
session_scope = features.task_session_scope
256257
if session_scope is not None:
257258
task_id = scoped_task_id(session_scope)
258259

src/mcp/server/experimental/task_context.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -488,12 +488,13 @@ async def elicit_as_task(
488488
create_result = CreateTaskResult.model_validate(response_data)
489489
client_task_id = create_result.task.taskId
490490

491-
# Poll the client's task using session.experimental
492-
async for _ in self._session.experimental.poll_task(client_task_id):
491+
# Poll the client's task using the session's experimental features
492+
features = self._session._experimental # pyright: ignore[reportPrivateUsage]
493+
async for _ in features.poll_task(client_task_id):
493494
pass
494495

495496
# Get final result from client
496-
result = await self._session.experimental.get_task_result(
497+
result = await features.get_task_result(
497498
client_task_id,
498499
ElicitResult,
499500
)
@@ -594,12 +595,13 @@ async def create_message_as_task(
594595
create_result = CreateTaskResult.model_validate(response_data)
595596
client_task_id = create_result.task.taskId
596597

597-
# Poll the client's task using session.experimental
598-
async for _ in self._session.experimental.poll_task(client_task_id):
598+
# Poll the client's task using the session's experimental features
599+
features = self._session._experimental # pyright: ignore[reportPrivateUsage]
600+
async for _ in features.poll_task(client_task_id):
599601
pass
600602

601603
# Get final result from client
602-
result = await self._session.experimental.get_task_result(
604+
result = await features.get_task_result(
603605
client_task_id,
604606
CreateMessageResult,
605607
)

src/mcp/server/experimental/task_support.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ def configure_session(self, session: ServerSession, *, stateless: bool = False)
106106
stateless: Whether the session belongs to a stateless server run
107107
"""
108108
session.add_response_router(self.handler)
109-
if not stateless and session.experimental.task_session_scope is None:
110-
session.experimental.task_session_scope = new_session_scope()
109+
if not stateless:
110+
features = session._experimental # pyright: ignore[reportPrivateUsage]
111+
if features.task_session_scope is None:
112+
features.task_session_scope = new_session_scope()
111113

112114
@classmethod
113115
def in_memory(cls) -> "TaskSupport":

0 commit comments

Comments
 (0)