cannot import name 'streamablehttp_client': The MCP SDK 2 Rename, and the Timeout It Hides (2026)
ImportError: cannot import name 'streamablehttp_client' means pip resolved MCP Python SDK 2.x against v1 client code. The rename is one underscore. The part nobody mentions is that passing your own httpx2 client silently drops the 300 second read timeout to 5 (2026).
Updated on September 11, 2026
On this page
Quick Answer (2026)
ImportError: cannot import name 'streamablehttp_client' from 'mcp.client.streamable_http' means you are running MCP Python SDK 2.x against client code written for 1.x. The function was renamed. One underscore moved:
streamablehttp_client is now streamable_http_client.
That rename is the easy half, and it is the only half most advice on this error covers. Two further things changed on the same call, and neither of them raises:
- It now yields a 2-tuple, not a 3-tuple. The
get_session_idcallback is gone. - If you pass your own HTTP client, which you must if you previously used
headers=orauth=, you silently lose the 300 second read timeout and inherit httpx2's flat 5 second default on a long-lived stream.
The second one is the expensive one, because your import starts working and your connection starts dropping.
Verified against mcp 2.2.0, read at source, in September 2026.
Why this broke on a day you changed nothing
The MCP Python SDK shipped 2.0.0 on 28 July 2026, and the release notes are blunt about what that means for installs:
Release notes, verbatim: "pip install mcp now installs 2.x."
So nothing in your code had to change. An unpinned dependency, a rebuilt container, a fresh lockfile, or a CI runner that does not cache wheels is enough. The same release moved 1.x to a maintenance branch that receives critical bug fixes and security patches only.
Confirm which side of the line you are on in one command:
python -c "import importlib.metadata as m; print(m.version('mcp'))"
Anything starting 2. is the new major. This is not a rare edge case. Anthropic's own skills repository hit it in August 2026, because its requirements file allowed the 2.x line through.
Which of the two breaks do you have?
The 2.0 rewrite split cleanly down the client and server line, and the two sides fail with different exceptions and need different fixes. Before changing anything, find out which one you are looking at:
python - <<'EOF'
import importlib
for mod, name in [
("mcp.client.streamable_http", "streamable_http_client"),
("mcp.client.streamable_http", "streamablehttp_client"),
("mcp.server.mcpserver", "MCPServer"),
]:
try:
m = importlib.import_module(mod)
print(f"{mod}.{name}:", hasattr(m, name))
except Exception as e:
print(f"{mod}: {type(e).__name__}: {e}")
EOF
On 2.x you get True, False, True, in that order. The rest of this guide is the client side. If your traceback names mcp.server.fastmcp instead, skip to the server section below.
The rename, before and after
Here is the v1 call that raises:
from mcp.client.streamable_http import streamablehttp_client
async with streamablehttp_client(
url="http://localhost:8000/mcp",
headers={"Authorization": "Bearer token"},
timeout=30,
sse_read_timeout=300,
) as (read_stream, write_stream, get_session_id):
...
Four of those five arguments no longer exist on the function. The v2 signature, copied from the source of mcp/client/streamable_http.py at tag v2.2.0, is the entire surface:
async def streamable_http_client(
url: str,
*,
http_client: httpx2.AsyncClient | None = None,
terminate_on_close: bool = True,
) -> AsyncGenerator[TransportStreams, None]:
headers, timeout, sse_read_timeout and auth moved off the transport and onto the HTTP client you hand it. httpx_client_factory was removed with no replacement: call your factory yourself and pass the result as http_client. terminate_on_close is unchanged.
The trap: the timeout you did not know you had
This is the part worth slowing down for, because it does not raise and it does not show up in a smoke test against a fast local server.
In v1, streamablehttp_client built its own HTTP client with MCP's timeouts baked in. In v2 that still happens, but only if you leave http_client unset. The source makes the branch explicit:
client_provided = http_client is not None
client = http_client
if client is None:
# Create default client with recommended MCP timeouts
client = create_mcp_http_client()
And create_mcp_http_client carries the constants:
MCP_DEFAULT_TIMEOUT = 30.0 # General operations (seconds)
MCP_DEFAULT_SSE_READ_TIMEOUT = 300.0 # SSE streams - 5 minutes (seconds)
Now put those two facts together. The only reason to pass http_client at all is that you needed headers or auth, which is to say you are talking to a remote, authenticated server. That is precisely the case where the server-to-client GET stream is held open longest, and it is precisely the case that loses the read timeout.
Scroll to see more
What you pass as http_client | connect / write / pool | read |
|---|---|---|
| nothing, argument omitted | 30s | 300s |
httpx2.AsyncClient() | httpx2 default | httpx2 default, a flat 5s |
httpx2.AsyncClient(timeout=httpx2.Timeout(30, read=300)) | 30s | 300s |
The middle row is what people write, because it is the smallest change that gets headers back. The migration guide flags the consequence in a single clause that is easy to read past:
Migration guide, verbatim: "a bare httpx2.AsyncClient() falls back to httpx2's flat 5-second timeout, too short for the long-lived GET stream"
So the failure mode is: import fixed, tests green against localhost, and then read timeouts on an idle stream in staging. If you migrated this call and started seeing dropped streams afterwards, this is the first thing to check.
The correct v2 form keeps the old numbers explicitly:
import httpx2
from mcp.client.streamable_http import streamable_http_client
http_client = httpx2.AsyncClient(
headers={"Authorization": "Bearer token"},
timeout=httpx2.Timeout(30, read=300),
)
async with http_client:
async with streamable_http_client(
url="http://localhost:8000/mcp",
http_client=http_client,
) as (read_stream, write_stream):
...
Note httpx2, not httpx. The SDK swapped its HTTP dependency in the same release, so import httpx in this file is now the wrong library even on a machine where httpx is installed.
One thing you can drop while you are here: v1's internal client set follow_redirects=True. You do not need it. As of 2.2.0 the transport follows a method-preserving redirect within the endpoint's origin itself, and refuses to follow one anywhere else regardless of how your client is configured.
The 2-tuple, and getting the session id back
The third element of the tuple is gone:
# v1
async with streamablehttp_client(url) as (read_stream, write_stream, get_session_id):
# v2
async with streamable_http_client(url) as (read_stream, write_stream):
Keep the three-name form and you get a plain unpacking error, so unlike the timeout this one announces itself. The GetSessionIdCallback type alias was removed as well, and so was the StreamableHTTPTransport.get_session_id() method that backed it.
If you were using the callback for session-resumption tests, the documented replacement is an httpx2 response hook:
captured_session_ids: list[str] = []
async def capture_session_id(response: httpx2.Response) -> None:
session_id = response.headers.get("mcp-session-id")
if session_id:
captured_session_ids.append(session_id)
http_client = httpx2.AsyncClient(event_hooks={"response": [capture_session_id]})
The hook fires on every response the client sees, so on a reconnecting client take the last entry rather than the first. Worth knowing if you write that test: terminate_on_close still defaults to True, so the transport sends its own DELETE on exit. If your test already deleted the session, pass terminate_on_close=False or you will see a Session termination failed: 404 warning from a second delete against a session that is already gone.
What did not change, so you do not over-migrate
Half the cost of a major version bump is touching things that were fine. Four useful negatives:
sse_clientis unchanged apart from being retyped to httpx2. It still takesurl,headers,timeout,sse_read_timeout,authandhttpx_client_factory. Only the streamable HTTP transport lost its transport-level parameters, so do not migrate both by symmetry.ClientSessionstill exists and is still exported from themcppackage root. You are not forced onto the new high-levelClientobject just to get running again.stdio_clientandStdioServerParameterskeep their v1 import paths.- Server-side transport entry points (
stdio_server(),SseServerTransport,StreamableHTTPSessionManager) keep their v1 import paths and signatures.
If your traceback says mcp.server.fastmcp instead
That is the server side of the same release, and it is a different exception with a different fix. from mcp.server.fastmcp import FastMCP raises ModuleNotFoundError, because FastMCP was renamed to MCPServer and moved to mcp.server.mcpserver. The SDK ships a stub module at the old path whose only job is to say so, rather than let you see a bare "no module named" message with no hint that the installed SDK is a different major version.
One disambiguation is worth making explicitly, because community answers to this error routinely get it wrong. The thing that was renamed is the official SDK's FastMCP. The separately maintained
fastmcp package is a different project with its own upgrade path and its own maintainer. "Change your import to from fastmcp import FastMCP" is advice to adopt a second dependency, not advice to complete this migration. Both are defensible choices. They are not the same choice.
Pin or migrate
There is a legitimate third answer, which is "not today".
Scroll to see more
| Pin to 1.x | Migrate to 2.x | |
|---|---|---|
| The change | an upper bound in your requirements | rename, retuple, restore the timeout |
| You keep getting | critical bug fixes and security patches | the 2026-07-28 protocol revision, stateless requests, the new Client |
| Reasonable when | you ship soon and the transport is not the interesting part of your product | you are connecting to servers that speak the new revision |
Pinning is not a defeat. The 1.x line is explicitly maintained on its own branch and documented separately. What is not viable is leaving the requirement unbounded, because that is what turned a migration you could have scheduled into an outage you could not:
mcp -> resolves to 2.x today
mcp>=1.28,<2 -> resolves to the maintained 1.x line
That upper bound is the vendor's own suggested form, and it is the single highest-value line to add to any MCP project you are not migrating this week.
A correction to our own tutorials
Two tutorials in this library were written against the v1 API and have been shipping imports that raise on a fresh install since 28 July 2026: our remote MCP server walkthrough, which uses streamablehttp_client, and our FastMCP server tutorial, which uses mcp.server.fastmcp. Both now carry a version note at the install step.
We found them by sweeping our own corpus for the third-party API entry points it depends on, not because anyone reported it. That is a reasonable argument that a tutorial library needs a standing check on the signatures it calls, and not only on the prices it quotes. A pinned version in a tutorial is a fact with a shelf life, and ours had expired six weeks earlier without anything noticing.
If you are debugging a server that starts and then dies, rather than one that fails to import, the companion piece is what MCP error -32000 actually means.
Written by
Sofia NievesSofia works on agent evaluation and reliability. She writes about measuring LLM systems before and after they reach production.
Frequently asked questions
What does ImportError: cannot import name 'streamablehttp_client' mean?
It means you have MCP Python SDK 2.x installed against client code written for 1.x. The function was renamed to streamable_http_client in 2.0.0, released 28 July 2026, and the old name was removed with no alias. Check with python -c "import importlib.metadata as m; print(m.version('mcp'))"; anything starting 2. is the new major.
Is renaming the function enough to finish the migration?
No. Two other things changed on the same call. It now yields a 2-tuple instead of a 3-tuple, because the get_session_id callback was removed, and headers, timeout, sse_read_timeout and auth all moved off the function onto an httpx2.AsyncClient you pass as http_client. The tuple change raises immediately. The moved parameters do not.
Why did my MCP stream start timing out after I fixed the import?
Because you passed your own httpx2.AsyncClient. If http_client is left unset the SDK builds its own with MCP's defaults of 30 seconds for connect, write and pool and 300 seconds for read. If you pass a bare httpx2.AsyncClient() to get your headers back, you inherit httpx2's flat 5 second default instead, which is too short for the long-lived GET stream. Pass timeout=httpx2.Timeout(30, read=300) to restore the v1 values.
Can I just stay on MCP SDK 1.x?
Yes. The 1.x line lives on its own branch and receives critical bug fixes and security patches, and the SDK's own release notes suggest putting an upper bound below 2 on your mcp requirement if you are not ready to migrate. What is not viable is leaving the requirement unbounded, since pip install mcp now resolves to 2.x.
Is this the same error as No module named 'mcp.server.fastmcp'?
Same release, different side. That one is the server half: FastMCP was renamed to MCPServer and moved to mcp.server.mcpserver. Note it refers to the official SDK's FastMCP, not the separately maintained fastmcp package, which is a different project with its own upgrade path. Changing your import to that package and completing this migration are two different decisions.
Related tutorials
Remote MCP Server Tutorial (2026): Serve Tools over Streamable HTTP
A runnable 2026 tutorial for turning a local MCP server into a remote one over Streamable HTTP. Serve tools with FastMCP, test the endpoint with curl and MCP Inspector, validate the Origin header, add a bearer token, then connect Claude. Covers the Mcp-Session-Id requirement and the DNS-rebinding gotcha the docs warn about but most walkthroughs skip.
How to Build an MCP Server in Python with FastMCP (2026)
A runnable 2026 tutorial: build a write-capable MCP server in Python with FastMCP and the official MCP SDK. Add tools with @mcp.tool(), back them with SQLite, test in the Inspector, and connect it to Claude for Desktop.
MCP Error -32000 Connection Closed: What It Actually Means (2026)
MCP error -32000: Connection closed is generated inside the client SDK and is never sent by a server. It means your MCP server process died. Here is how to recover the real error, and a measured correction to the stdout advice (2026).