| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
📚 Documentation preview
|
Sorry, something went wrong.
The HTTP+SSE transport was superseded by Streamable HTTP in the 2025-03-26 spec revision and is formally deprecated under SEP-2596. Mark every public entry point with typing_extensions.deprecated so type checkers flag it and a runtime MCPDeprecationWarning fires: sse_client, SseServerParameters, SseServerTransport, MCPServer.sse_app / run_sse_async, and the run(transport="sse") overload. Nested constructions suppress the inner warning so callers see exactly one. Also: mcp run --transport help text drops sse, python -m mcp.client connects with Streamable HTTP, and the url_elicitation client snippet moves to Streamable HTTP. Docs and migration guide updated. No-Verification-Needed: user requested immediate push, verification explicitly waived
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/mcp/client/session_group.py">
<violation number="1" location="src/mcp/client/session_group.py:331">
P2: Session-group SSE connections still emit both deprecation warnings: one constructing `SseServerParameters` and one entering `sse_client`. Keep the targeted warning suppression active through `enter_async_context(client)` (or make `sse_client` warn when its context manager is constructed).</violation>
</file>
<file name="examples/snippets/clients/url_elicitation_client.py">
<violation number="1" location="examples/snippets/clients/url_elicitation_client.py:289">
P3: When the initial Streamable HTTP connection is refused, the recovery message still tells users to start the server with `sse`. That starts the legacy transport instead of the `/mcp` endpoint this client now targets, so following the error guidance leaves the retry unable to connect. Updating the fallback (and the module usage text) to `streamable-http` would keep the example runnable.</violation>
</file>
<file name="pyproject.toml">
<violation number="1" location="pyproject.toml:271">
P2: The suite-wide ignore masks this SDK's new deprecation warning at every call site, so future tests can accidentally introduce legacy HTTP+SSE usage without failing. The existing legacy-transport tests can keep a module- or test-level `filterwarnings` marker instead, which preserves the warning-as-error guard for all other code paths.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Sorry, something went wrong.
| with warnings.catch_warnings(): | ||
| warnings.simplefilter("ignore", MCPDeprecationWarning) | ||
| client = sse_client( # pyright: ignore[reportDeprecated] | ||
| url=server_params.url, | ||
| headers=server_params.headers, | ||
| timeout=server_params.timeout, | ||
| sse_read_timeout=server_params.sse_read_timeout, | ||
| ) | ||
| read, write = await session_stack.enter_async_context(client) |
There was a problem hiding this comment.
P2: Session-group SSE connections still emit both deprecation warnings: one constructing SseServerParameters and one entering sse_client. Keep the targeted warning suppression active through enter_async_context(client) (or make sse_client warn when its context manager is constructed).
Prompt for AI agentsCheck if this issue is valid — if so, understand the root cause and fix it. At src/mcp/client/session_group.py, line 331:
<comment>Session-group SSE connections still emit both deprecation warnings: one constructing `SseServerParameters` and one entering `sse_client`. Keep the targeted warning suppression active through `enter_async_context(client)` (or make `sse_client` warn when its context manager is constructed).</comment>
<file context>
@@ -313,13 +325,17 @@ async def _establish_session(
+ elif isinstance(server_params, SseServerParameters): # pyright: ignore[reportDeprecated]
+ # The caller already saw the deprecation warning when they built
+ # SseServerParameters; suppress the nested one from sse_client.
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", MCPDeprecationWarning)
+ client = sse_client( # pyright: ignore[reportDeprecated]
</file context>
| with warnings.catch_warnings(): | |
| warnings.simplefilter("ignore", MCPDeprecationWarning) | |
| client = sse_client( # pyright: ignore[reportDeprecated] | |
| url=server_params.url, | |
| headers=server_params.headers, | |
| timeout=server_params.timeout, | |
| sse_read_timeout=server_params.sse_read_timeout, | |
| ) | |
| read, write = await session_stack.enter_async_context(client) | |
| with warnings.catch_warnings(): | |
| warnings.simplefilter("ignore", MCPDeprecationWarning) | |
| client = sse_client( # pyright: ignore[reportDeprecated] | |
| url=server_params.url, | |
| headers=server_params.headers, | |
| timeout=server_params.timeout, | |
| sse_read_timeout=server_params.sse_read_timeout, | |
| ) | |
| read, write = await session_stack.enter_async_context(client) |
Sorry, something went wrong.
| "ignore:ping is removed as of 2026-07-28.*:mcp.MCPDeprecationWarning", | ||
| # SEP-2596 deprecates the HTTP+SSE transport; the SDK still ships it for | ||
| # servers/clients that haven't migrated, and the tests still exercise it. | ||
| "ignore:.*HTTP\\+SSE transport is deprecated as of 2025-03-26.*:mcp.MCPDeprecationWarning", |
There was a problem hiding this comment.
P2: The suite-wide ignore masks this SDK's new deprecation warning at every call site, so future tests can accidentally introduce legacy HTTP+SSE usage without failing. The existing legacy-transport tests can keep a module- or test-level filterwarnings marker instead, which preserves the warning-as-error guard for all other code paths.
Prompt for AI agentsCheck if this issue is valid — if so, understand the root cause and fix it. At pyproject.toml, line 271:
<comment>The suite-wide ignore masks this SDK's new deprecation warning at every call site, so future tests can accidentally introduce legacy HTTP+SSE usage without failing. The existing legacy-transport tests can keep a module- or test-level `filterwarnings` marker instead, which preserves the warning-as-error guard for all other code paths.</comment>
<file context>
@@ -266,6 +266,9 @@ filterwarnings = [
"ignore:ping is removed as of 2026-07-28.*:mcp.MCPDeprecationWarning",
+ # SEP-2596 deprecates the HTTP+SSE transport; the SDK still ships it for
+ # servers/clients that haven't migrated, and the tests still exercise it.
+ "ignore:.*HTTP\\+SSE transport is deprecated as of 2025-03-26.*:mcp.MCPDeprecationWarning",
]
</file context>
Sorry, something went wrong.
| print("=" * 60) | ||
| print(f"\nConnecting to: {server_url}") | ||
| print("(Start server with: cd examples/snippets && uv run server elicitation sse)") | ||
| print("(Start server with: cd examples/snippets && uv run server elicitation streamable-http)") |
There was a problem hiding this comment.
P3: When the initial Streamable HTTP connection is refused, the recovery message still tells users to start the server with sse. That starts the legacy transport instead of the /mcp endpoint this client now targets, so following the error guidance leaves the retry unable to connect. Updating the fallback (and the module usage text) to streamable-http would keep the example runnable.
Prompt for AI agentsCheck if this issue is valid — if so, understand the root cause and fix it. At examples/snippets/clients/url_elicitation_client.py, line 289:
<comment>When the initial Streamable HTTP connection is refused, the recovery message still tells users to start the server with `sse`. That starts the legacy transport instead of the `/mcp` endpoint this client now targets, so following the error guidance leaves the retry unable to connect. Updating the fallback (and the module usage text) to `streamable-http` would keep the example runnable.</comment>
<file context>
@@ -280,16 +280,16 @@ async def run_command_loop(session: ClientSession) -> None:
print("=" * 60)
print(f"\nConnecting to: {server_url}")
- print("(Start server with: cd examples/snippets && uv run server elicitation sse)")
+ print("(Start server with: cd examples/snippets && uv run server elicitation streamable-http)")
try:
</file context>
Sorry, something went wrong.
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
🟡 src/mcp/server/mcpserver/server.py:396-399 — The @deprecated on the run(transport="sse") overload is typing-only (overload stubs never execute), so at runtime a mcp.run(transport="sse") caller instead gets run_sse_async's warning — "Use run_streamable_http_async instead" — naming an internal async method they never called, fired from inside the anyio.run lambda rather than their call site. Consider emitting the overload's message explicitly in the case "sse" branch and suppressing the nested run_sse_async warning, matching the pattern used elsewhere in this PR.
Extended reasoning...This PR's stated design (PR description and the suppression comments in server.py) is that nested constructions suppress inner warnings "so the caller sees exactly one, naming the thing they called." That pattern is correctly implemented for sse_app() → SseServerTransport, run_sse_async() → sse_app(), and _establish_session() → sse_client() — but not for the documented entry point mcp.run(transport="sse").
The reason is that typing_extensions.deprecated applied to an @overload stub is a typing-only construct: overload stubs are rebound by the implementation and never execute at runtime, so the overload's message ('Use transport="streamable-http" instead') is only ever delivered by pyright/IDEs, never as a runtime warning. Verifiers confirmed this empirically with a minimal reproduction of the @deprecated + @overload pattern.
Step-by-step proof. A user writes mcp.run(transport="sse"):
Nothing existing prevents this: the filterwarnings ignore in pyproject.toml keeps the test suite green, and the case "sse" branch carries # pragma: no cover, so no test observes which message fires on this path.
Impact is limited: exactly one warning still fires (run_sse_async suppresses sse_app's), the category is correct, and the guidance is directionally right (Streamable HTTP, SEP-2596). Only the replacement pointer and the warning's attribution are off relative to the PR's own one-warning-naming-the-caller design.
Fix: in the case "sse" branch of run(), emit the overload's message explicitly with warnings.warn(..., category=MCPDeprecationWarning, stacklevel=2) and wrap the run_sse_async call in warnings.catch_warnings() + simplefilter("ignore", MCPDeprecationWarning) — exactly the pattern run_sse_async and sse_app already use for their own nested calls.
Sorry, something went wrong.
| @asynccontextmanager | ||
| @deprecated( | ||
| "The HTTP+SSE transport is deprecated as of 2025-03-26 (SEP-2596). Use streamable_http_client instead.", | ||
| category=MCPDeprecationWarning, | ||
| ) | ||
| async def sse_client( |
There was a problem hiding this comment.
🟡 The decorator order on sse_client (@asynccontextmanager outermost, @deprecated inner) causes the runtime MCPDeprecationWarning to be attributed to CPython's contextlib.py instead of the user's call site, and the default warning filter then dedupes all call sites process-wide to a single warning. Swapping the two decorators (@deprecated outermost) restores correct call-site attribution; sse_client is the only @asynccontextmanager function in this PR carrying @deprecated — the other deprecated entry points attribute correctly.
Extended reasoning...What happens
Decorators apply bottom-up, so in src/mcp/client/sse.py the @deprecated wrapper wraps the raw async-generator function, and @asynccontextmanager wraps that. When a user calls sse_client(url), the call chain is: user frame → asynccontextmanager helper → _AsyncGeneratorContextManager.__init__ (self.gen = func(*args, **kwds)) → the deprecated wrapper. typing_extensions.deprecated emits warnings.warn(msg, stacklevel=2), and two frames up from the wrapper is _AsyncGeneratorContextManager.__init__ — so the warning is attributed to CPython's contextlib.py, not the caller's file and line.
Step-by-step proof (reproduced independently by three verifiers on Python 3.11):
Downstream consequences
Why the PR's tests don't catch it
test_sse_client_warns_that_the_http_sse_transport_is_deprecated in tests/shared/test_sse.py uses pytest.warns and asserts only category and message text — never the attributed filename/lineno.
Why this is unique to sse_client
Every other entry point deprecated by this PR (SseServerParameters, SseServerTransport, sse_app, run_sse_async, the run(transport="sse") overload) is a plain class or method whose deprecated wrapper is invoked directly from the user's frame, so those warnings attribute correctly.
Fix
Swap the decorator order so @deprecated is outermost:
@deprecated(
"The HTTP+SSE transport is deprecated as of 2025-03-26 (SEP-2596). Use streamable_http_client instead.",
category=MCPDeprecationWarning,
)
@asynccontextmanager
async def sse_client(...):The warning still fires eagerly at sse_client(url) construction (before any connection), the returned context manager is unchanged, and both pyright's reportDeprecated flagging and the existing pytest.warns tests are unaffected. (Alternatively, stacklevel=3 would work, but typing_extensions.deprecated doesn't expose that knob — the swap is the clean one-line fix.)
Severity
Nit: the warning does fire visibly with the correct category and its message names the transport, so nothing breaks — only the file/line attribution, per-call-site dedup, and module-scoped filtering are degraded relative to the PR's call-site-visibility goal. One-line fix, not worth blocking merge.
Sorry, something went wrong.
| server_url = "http://localhost:8000/mcp" | ||
|
|
||
| print("=" * 60) | ||
| print("URL Elicitation Client Example") | ||
| print("=" * 60) | ||
| print(f"\nConnecting to: {server_url}") | ||
| print("(Start server with: cd examples/snippets && uv run server elicitation sse)") | ||
| print("(Start server with: cd examples/snippets && uv run server elicitation streamable-http)") | ||
|
|
||
| try: | ||
| async with sse_client(server_url) as (read, write): | ||
| async with streamable_http_client(server_url) as (read, write): | ||
| async with ClientSession( | ||
| read, | ||
| write, |
There was a problem hiding this comment.
🟡 The migration of this client to Streamable HTTP left two stale instructions that still say to start the server in SSE mode: the module docstring (uv run server elicitation sse, ~line 20) and the ConnectionRefusedError handler (line 305). Following either starts the server on the legacy /sse endpoint while this client now connects to /mcp, so the connection fails — and the failure message then repeats the wrong command. Both should say streamable-http to match the updated hint in main().
Extended reasoning...What the bug is. This PR migrates examples/snippets/clients/url_elicitation_client.py from sse_client to streamable_http_client: the import changes, server_url becomes http://localhost:8000/mcp, and the startup hint printed in main() (line 289) is updated to uv run server elicitation streamable-http. But two other references to the old command in the same file were missed:
The code path that triggers it. The snippets server runner (examples/snippets/servers/__init__.py) dispatches its transport argument to mcp.run(transport), so uv run server elicitation sse really does start the server on the legacy HTTP+SSE transport, serving /sse and /messages/ — there is no /mcp endpoint in that mode.
Step-by-step proof.
Beyond the connection failure, following the stale instruction also puts the user on the transport this very PR is deprecating: the server run emits MCPDeprecationWarning, the opposite of what the snippet is meant to demonstrate post-migration.
Why nothing catches it. These are string literals in comments and print statements — no type checker, test, or the new deprecation warnings can flag them. The example only fails when run interactively against a real server, which CI doesn't do.
Impact and fix. Impact is confined to this example: the primary hint in main() (line 289) is correct, so a user who starts from the printed banner is fine; only users who follow the docstring or the error-path message get misdirected. The fix is a single root cause with two one-line string changes — replace sse with streamable-http in the docstring and in the ConnectionRefusedError handler, matching line 289 exactly (the runner accepts streamable-http as a transport argument, verified against examples/snippets/servers/__init__.py).
Sorry, something went wrong.
There was a problem hiding this comment.
Beyond the inline findings, I also checked cubic's P2 claim that ClientSessionGroup._establish_session still emits two deprecation warnings (one from SseServerParameters, one from sse_client) — it does not. With @asynccontextmanager outermost, the @deprecated wrapper runs at context-manager construction, i.e. inside the catch_warnings block; entering the CM later via enter_async_context emits nothing (verified empirically). The caller sees exactly one warning, from building SseServerParameters.
Extended reasoning...Bugs from this run are already attached as inline comments, so no full review body is warranted. This note records one additional concern that was examined and ruled out: cubic-dev-ai's P2 on src/mcp/client/session_group.py claims the suppression must extend through enter_async_context, but the deprecation warning for sse_client fires when the context manager object is constructed (the deprecated wrapper is invoked from _AsyncGeneratorContextManager.__init__), which happens inside the existing catch_warnings block — entering the context manager afterwards re-runs nothing that warns. Reproduced with a minimal replica of the exact decorator stack and call pattern: zero warnings escape the suppression block.
Sorry, something went wrong.
| @overload | ||
| @deprecated( | ||
| 'transport="sse" is deprecated: the HTTP+SSE transport is deprecated as of 2025-03-26 ' | ||
| '(SEP-2596). Use transport="streamable-http" instead.', | ||
| category=MCPDeprecationWarning, | ||
| ) | ||
| def run( | ||
| self, | ||
| transport: Literal["sse"], |
There was a problem hiding this comment.
🟡 🟡 The @deprecated decorator on the run(transport="sse") overload has no runtime effect (PEP 702: a decorated @overload stub only informs static checkers), so at runtime a mcp.run(transport="sse") caller instead sees the nested run_sse_async warning — "Use run_streamable_http_async instead" — naming an async API they never called, when the direct migration is transport="streamable-http" in the same run() call. Fix by warnings.warn()-ing the overload's transport-oriented message in the case "sse" branch and suppressing the nested run_sse_async warning with catch_warnings, matching the pattern run_sse_async already uses for sse_app.
Extended reasoning...What the bug is. This PR stacks @overload over @deprecated on the run(transport: Literal["sse"]) overload (src/mcp/server/mcpserver/server.py:361-369). Per PEP 702 / typing_extensions, @deprecated applied to an @overload stub only informs static checkers: typing.overload registers the stub and returns a dummy that is never called, and the stub function object is discarded when the implementation def run(...) executes. So the overload's carefully worded message — 'transport="sse" is deprecated … Use transport="streamable-http" instead.' — is never emitted at runtime. Three independent verifiers reproduced this empirically with a minimal replica of the exact decorator structure: zero warnings fire from the overload decorator.
The code path that triggers it. mcp.run(transport="sse") reaches the implementation's case "sse" branch, which dispatches anyio.run(lambda: self.run_sse_async(**kwargs)). run_sse_async is genuinely decorated, so its warning fires — but that is the wrong message for this caller.
Step-by-step proof.
Why existing code doesn't prevent it. The PR's stated design is that nested constructions suppress inner warnings "so the caller sees exactly one, naming the thing they called" — and the authors did add catch_warnings suppression for run_sse_async → sse_app and sse_app → SseServerTransport. The run() → run_sse_async hop is the one link in the chain with no explicit runtime warning above it, presumably because the overload decorator was assumed to fire at runtime. No test catches it: the case "sse" branch is # pragma: no cover, and the three new warning tests cover sse_client, SseServerTransport, and sse_app() only.
Impact. Limited: exactly one MCPDeprecationWarning still fires with the correct category, the correct SEP-2596 reference, and a workable (if indirect) migration path — and pyright still flags the call site correctly via the overload. Nothing breaks; the message is merely misdirected for the highest-level, most-documented entry point (run() is what mcp run --transport sse and every quickstart-style server use).
How to fix. In the implementation's case "sse" branch, explicitly warnings.warn() the overload's transport-oriented message (as MCPDeprecationWarning, with a stacklevel pointing at the caller), then suppress the nested run_sse_async warning with warnings.catch_warnings() + simplefilter("ignore", MCPDeprecationWarning) — the exact pattern run_sse_async already applies to its own nested sse_app() call. Keeping the @deprecated on the overload is still right for static checkers; it just needs a runtime counterpart.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Marks every public entry point of the legacy HTTP+SSE transport as deprecated. Streamable HTTP superseded it in the 2025-03-26 spec revision, and it now sits in the spec's deprecated-features registry under SEP-2596. Streamable HTTP (and its request-scoped SSE streaming) is untouched.
Motivation and Context
The SDK still ships HTTP+SSE for clients and servers that haven't migrated, but nothing told users at the call site or in the type checker that they were building on a deprecated transport. This uses the same mechanism as the SEP-2577 deprecations: typing_extensions.deprecated + MCPDeprecationWarning, so pyright/IDEs flag the call and a visible warning fires at runtime.
Deprecated:
Nested constructions (sse_app() → SseServerTransport, run_sse_async() → sse_app(), connect_to_server() → sse_client()) suppress the inner warning so the caller sees exactly one, naming the thing they called.
Adjacent housekeeping: mcp run --transport help now advertises stdio or streamable-http, python -m mcp.client <url> connects with Streamable HTTP (it used SSE for any http(s) URL, unlike Client(str)), and the url_elicitation_client snippet moves to Streamable HTTP.
How Has This Been Tested?
Three new tests assert the warning fires (client transport, server transport, and that sse_app() warns exactly once). A scoped filterwarnings ignore keeps the existing SSE suites running under error. Full suite green locally.
Breaking Changes
None. Everything keeps working; it warns. Documented in docs/migration.md (### HTTP+SSE transport deprecated (SEP-2596)) and docs/deprecated.md.
Types of changes
Checklist
Additional context
Message text lines up with the spec registry row (deprecated in 2025-03-26, migration path Streamable HTTP): "The HTTP+SSE transport is deprecated as of 2025-03-26 (SEP-2596). Use <replacement> instead."
Other SDKs for reference: TS marks SSEClientTransport / SSEServerTransport @deprecated; C# gates EnableLegacySse behind an [Obsolete] diagnostic; Java 2.0 deprecates its SSE transports.
AI Disclaimer