Build1 publisher3 min readPublished
MCP's Python SDK 2.0 yields two values where wrapper libraries still unpack three
The official mcp package hit 2.0.0 on 28 July with renamed imports and a shorter context-manager yield, so wrappers that declared no upper bound now fail at runtime on a fresh install of code nobody touched.
The Engineer · Build desk

What happened
- The official Python mcp package shipped 2.0.0 on 28 July 2026 as a genuine major release, with public APIs both removed and renamed.
- PyPI's latest mcp is 2.2.0 and a plain pip install resolves to 2.x, so any dependency declared without an upper bound floated forward on the next fresh install.
- autogen-ext 0.7.5 declares mcp>=1.11.0 with no ceiling, and its optional mcp extra was still broken against current mcp on the main branch in mid-September.
- llama-index-tools-mcp 0.5.0 declared mcp>=2.0.0 yet broke against its own dependency floor, and the fix landed in 0.5.1 in late August.
- The dev.to author reproduced the behaviour in throwaway virtualenvs running 1.29.1 and 2.2.0 side by side, and checked both wrappers by downloading the packages.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint One dependency without a ceiling picks the mcp version for the entire application, so a team that wants 2.x features waits on its slowest wrapper to catch up.
- exposure The unpack failure clears import, so a test suite that only imports the wrapper will certify a build that dies on the first live session against a streamable-HTTP server.
- decision Each team has to pick which side to move, and downgrading mcp when the wrapper has already shipped a fix leaves the tree on an older SDK for no reason.
- cost The debugging bill falls on the application team, whose own code did not change and whose traceback points into a dependency's installed file.
Two failure modes, and they land at different moments. A wrapper that still imports `streamablehttp_client` dies at import time on 2.x, because the callable is now `streamable_http_client` [5]. A wrapper that got the new name and still writes `as (read, write, get_session_id)` imports cleanly, then raises `ValueError: not enough values to unpack (expected 3, got 2)` the first time it opens a session [6][7]. In the installed 2.2.0 package the relevant line sits in `mcp/client/streamable_http.py` at roughly line 753: `yield read_stream, write_stream` [8]. The session-id getter is not in the tuple any more [6].
Timeouts moved as well. `StreamableHTTPTransport.__init__` on 2.x takes `(self, url)`, and `timeout`, `sse_read_timeout`, `headers` and `auth` now belong on the httpx2 `AsyncClient`; pass them to the transport and you get `TypeError: unexpected keyword argument 'timeout'` [9]. The same write-up corrects one version of this story doing the rounds: `sse_client` still accepts `timeout` and `sse_read_timeout`, so 2.x did not drop the transport keywords across the board [10].
The protocol models renamed their attributes too. `tool.inputSchema` is `tool.input_schema`, `result.isError` is `result.is_error`, `structuredContent` is `structured_content`, `nextCursor` is `next_cursor` [11]. The wire JSON stays camelCase because the aliases preserve it, so a 2.x client and a 1.x server still agree on the protocol and the `AttributeError` is entirely on the Python side [12]. One further trap: `model_dump()` without `by_alias=True` returns snake_case dicts without complaint, feeding the wrong keys to anything downstream that expects the protocol shape [13].
Diagnosis starts one level up from the traceback, with the package name. The Python SDK is `mcp` on PyPI; the Rust one is `rmcp` on crates.io, which is what Goose and some other Rust agents use, and it is on 3.x [14]. The author of the dev.to write-up says he nearly chased the wrong version before checking the crate name [15]. He also flags a false lead: a tool that produces zero content blocks and returns an empty list is not a 2.x regression [16].
For an application tree, pinning back is the cheap move, because 2.x buys you nothing unless you call something new. `pip install "mcp>=1.28,<2"` resolves to 1.30.0 [17]. The 2.0.0 release notes themselves tell library authors to keep a `<2` upper bound, and the 1.x line is still receiving security fixes [4]. Where the wrapper has already been fixed, upgrade the wrapper instead: llama-index-tools-mcp is on 0.6.0, with the unpack corrected in 0.5.1 [3][18]. For autogen-ext the choice is made for you, since its `[mcp]` extra still imports `streamablehttp_client`, unpacks three values and reads `inputSchema` and `isError` [19], and about seven weeks after 2.0.0 shipped that was still the state of main [1].
Before guessing which side is broken, print the facts: `importlib.metadata.version('mcp')`, `hasattr(sh, 'streamablehttp_client')`, and `inspect.signature(sh.streamable_http_client)` [20]. Then read the wrapper's `pyproject.toml` for an unbounded `mcp` requirement. As the post puts it: "That missing upper bound is the whole bug, and it'll happen again at the next major." [21]
What to watch
- Whether autogen-ext's main branch adds a <2 ceiling or moves to streamable_http_client and the 2-tuple.
- Whether the mcp 1.x line keeps shipping security fixes, since the pin-back advice depends on it.
- Whether other wrapper maintainers follow the 2.0.0 release-note guidance and add upper bounds before the next major.