diff --git a/docs/migration.md b/docs/migration.md index 850e05255..92eceb594 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -149,6 +149,10 @@ named `mcp.os.win32.utilities` (was `client.stdio.win32`). The WebSocket transport has been removed: `mcp.client.websocket.websocket_client`, `mcp.server.websocket.websocket_server`, and the `ws` optional dependency extra (`mcp[ws]`) no longer exist. WebSocket was never part of the MCP specification. Use the streamable HTTP transport instead (`mcp.client.streamable_http.streamable_http_client` on the client, `streamable_http_app()` on the server), which supports bidirectional communication with server-to-client streaming over standard HTTP. +### SSE transport deprecated + +The HTTP+SSE transport is now deprecated and emits a `DeprecationWarning`. The client transport `mcp.client.sse.sse_client` and the server transport `mcp.server.sse.SseServerTransport` (including the high-level `MCPServer.sse_app()` / `MCPServer.run(transport="sse")`, which build on it) are affected. HTTP+SSE was superseded by Streamable HTTP in protocol revision 2025-03-26 and the specification now documents it only for backwards compatibility. The transport still works and will be removed in a future version; migrate to the streamable HTTP transport (`streamable_http_client` on the client, `streamable_http_app()` on the server). + ### Removed type aliases and classes The following deprecated type aliases and classes have been removed from `mcp.types`: diff --git a/pyproject.toml b/pyproject.toml index 749af47ab..4c9c48154 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -210,6 +210,12 @@ filterwarnings = [ "error", # pywin32 internal deprecation warning "ignore:getargs.*The 'u' format is deprecated:DeprecationWarning", + # The SSE transport is intentionally deprecated (see sse_client / SseServerTransport). It is still + # exercised across the suite (directly and via the transport-parametrized interaction tests), so the + # SDK's own deprecation notice must not be promoted to an error here. tests/shared/test_sse.py asserts + # the warning is emitted. + "ignore:The SSE client transport is deprecated:DeprecationWarning", + "ignore:The SSE server transport is deprecated:DeprecationWarning", ] [tool.markdown.lint] diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 6a2579f4c..d8dcf20ee 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -1,4 +1,5 @@ import logging +import warnings from collections.abc import Callable from contextlib import asynccontextmanager from typing import Any @@ -39,6 +40,10 @@ async def sse_client( ): """Client transport for SSE. + Deprecated: this transport will be removed in a future version. The HTTP+SSE + transport was superseded by Streamable HTTP in protocol revision 2025-03-26; + use the streamable HTTP transport (`streamable_http_client`) instead. + `sse_read_timeout` determines how long (in seconds) the client will wait for a new event before disconnecting. All other HTTP operations are controlled by `timeout`. @@ -51,6 +56,13 @@ async def sse_client( auth: Optional HTTPX authentication handler. on_session_created: Optional callback invoked with the session ID when received. """ + warnings.warn( + "The SSE client transport is deprecated and will be removed in a future version. The HTTP+SSE transport was" + " superseded by Streamable HTTP in protocol revision 2025-03-26; use the streamable HTTP transport" + " (`streamable_http_client`) instead.", + DeprecationWarning, + stacklevel=2, + ) logger.debug(f"Connecting to SSE endpoint: {remove_request_params(url)}") async with httpx_client_factory( headers=headers, auth=auth, timeout=httpx.Timeout(timeout, read=sse_read_timeout) diff --git a/src/mcp/server/sse.py b/src/mcp/server/sse.py index 05e948332..a5b7ababc 100644 --- a/src/mcp/server/sse.py +++ b/src/mcp/server/sse.py @@ -37,6 +37,7 @@ async def handle_sse(request): """ import logging +import warnings from contextlib import asynccontextmanager from typing import Any from urllib.parse import quote @@ -70,6 +71,10 @@ class SseServerTransport: 2. handle_post_message() is an ASGI application which receives incoming POST requests, which should contain client messages that link to a previously-established SSE session. + + Deprecated: this transport will be removed in a future version. The HTTP+SSE + transport was superseded by Streamable HTTP in protocol revision 2025-03-26; + use the streamable HTTP transport instead. """ _endpoint: str @@ -100,6 +105,13 @@ def __init__(self, endpoint: str, security_settings: TransportSecuritySettings | Raises: ValueError: If the endpoint is a full URL instead of a relative path """ + warnings.warn( + "The SSE server transport is deprecated and will be removed in a future version. The HTTP+SSE transport" + " was superseded by Streamable HTTP in protocol revision 2025-03-26; use the streamable HTTP transport" + " instead.", + DeprecationWarning, + stacklevel=2, + ) super().__init__() diff --git a/tests/shared/test_sse.py b/tests/shared/test_sse.py index 675a4acb1..7ee8cc470 100644 --- a/tests/shared/test_sse.py +++ b/tests/shared/test_sse.py @@ -108,6 +108,24 @@ def make_server_app() -> Starlette: return make_app(Server(SERVER_NAME, on_read_resource=_handle_read_resource)) +def test_sse_server_transport_emits_deprecation_warning() -> None: + """Constructing SseServerTransport warns that the SSE server transport is deprecated.""" + with pytest.warns(DeprecationWarning, match="The SSE server transport is deprecated"): + SseServerTransport( + "/messages/", security_settings=TransportSecuritySettings(enable_dns_rebinding_protection=False) + ) + + +@pytest.mark.anyio +async def test_sse_client_emits_deprecation_warning() -> None: + """Entering sse_client warns that the SSE client transport is deprecated.""" + factory = in_process_client_factory(make_server_app()) + with anyio.fail_after(5): + with pytest.warns(DeprecationWarning, match="The SSE client transport is deprecated"): + async with sse_client(f"{BASE_URL}/sse", httpx_client_factory=factory): # pragma: no branch + pass + + @pytest.mark.anyio async def test_raw_sse_connection() -> None: """The SSE GET responds 200 with an event-stream content type, announcing the session