DEV Community

MilkyWay008
MilkyWay008

Posted on

The MCP Python SDK 2.0 broke the wrapper libraries. Here's how to spot it and pin back

If your MCP tools started failing a few weeks ago and nothing in your own code changed, you're not imagining it.

The official Python mcp package shipped 2.0.0 on 2026-07-28. It's a real major version, with public APIs removed and renamed. Latest on PyPI right now is 2.2.0, and a plain pip install mcp resolves to 2.x. Any library that depends on mcp without an upper bound quietly floated forward and broke at install time.

I spent a while reproducing this in throwaway venvs with 1.29.1 and 2.2.0 side by side. Here's what actually changed, what the errors look like, and how to get moving again.

1st, one name collision to get out of the way

Two SDKs share the name. Python mcp is the one on PyPI. The Rust one is rmcp on crates.io, which is what Goose and some other Rust agents use, and it's on 3.x now. If you're debugging a Rust agent, the Python 2.x story is not your story. I nearly chased the wrong version myself...... glad I checked the crate name first.

1st break: streamablehttp_client is gone

In 1.x:

from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client(url, timeout=30, sse_read_timeout=300) as (read, write, get_session_id):
    ...
Enter fullscreen mode Exit fullscreen mode

In 2.x that import raises ImportError. The replacement is:

from mcp.client.streamable_http import streamable_http_client

async with streamable_http_client(url) as (read, write):
    ...
Enter fullscreen mode Exit fullscreen mode

Two changes there. The name has underscores now, and the context manager yields a 2-tuple. The 3rd element, get_session_id, is gone.

That 2nd part is the one that hurts, because it doesn't fail at import. The wrapper imports fine, then blows up at runtime, and you get the error a lot of people are probably googling right now:

ValueError: not enough values to unpack (expected 3, got 2)
Enter fullscreen mode Exit fullscreen mode

I reproduced that against a live streamable-HTTP server on 2.2.0. In the installed package it's mcp/client/streamable_http.py, around line 753: yield read_stream, write_stream.

2nd break: the transport kwargs are gone

StreamableHTTPTransport.__init__ in 2.x is just (self, url). The timeout, sse_read_timeout, headers and auth keywords aren't accepted there anymore; set them on the httpx2 AsyncClient instead. Pass them and you get TypeError: unexpected keyword argument 'timeout'.

Honesty note in the other direction: sse_client still takes timeout and sse_read_timeout. So the "all the transport kwargs got dropped" version of this story is wrong. It's the streamable-HTTP path that changed.

3rd break: field names went snake_case

This is the quiet one. The Pydantic protocol models renamed their attributes:

  • tool.inputSchema goes to tool.input_schema
  • result.isError goes to result.is_error
  • structuredContent goes to structured_content
  • nextCursor goes to next_cursor

The JSON on the wire is still camelCase (the aliases keep that), so servers and clients still agree on the protocol. It's your Python that breaks, with an AttributeError. Bonus trap: model_dump() without by_alias=True now hands you snake_case dicts without complaint, which can poison anything downstream that expects the protocol shape.

Who is actually broken right now

Two examples I verified by downloading the packages rather than trusting a changelog. autogen-ext 0.7.5 declares mcp>=1.11.0 with no upper bound, imports streamablehttp_client, unpacks the 3-tuple, and reads inputSchema / isError, so its [mcp] extra is broken against current mcp, and as of mid-September that's still the case on main. llama-index-tools-mcp 0.5.0 is the same failure with a twist: it declared mcp>=2.0.0 but still unpacked the 3-tuple, so it was broken against its own dependency floor. Fixed in 0.5.1 in late August; 0.6.0 is current.

The pin that fixes it

If you don't need 2.x features, pin back. The official 2.0.0 release notes tell library authors to keep a <2 upper bound, and the 1.x line is still getting security fixes:

pip install "mcp>=1.28,<2"     # resolves to 1.30.0 today
uv add "mcp<2"
Enter fullscreen mode Exit fullscreen mode

If the wrapper already fixed it (llama-index-tools-mcp >= 0.5.1, for example), upgrade the wrapper instead of downgrading mcp. Read the release notes before you assume which side is fixed.

Before guessing, check what you're actually running:

python -c "import importlib.metadata as m, inspect; print(m.version('mcp')); import mcp.client.streamable_http as sh; print(hasattr(sh,'streamablehttp_client')); print(inspect.signature(sh.streamable_http_client))"
Enter fullscreen mode Exit fullscreen mode

And check the wrapper's pyproject.toml for an unbounded mcp dependency. That missing upper bound is the whole bug, and it'll happen again at the next major.

Two things that are not this bug

I saw a couple of claims circulating while digging that can cost you hours if you chase them.

An empty list from a tool producing zero content blocks is not a 2.x regression. I measured the same numbers on both 1.29.1 and 2.2.0: [] gives 0 blocks, '' gives 1, json.dumps([]) gives 1, ['a','b'] gives 2. It's long-standing FastMCP behavior in _convert_to_content, which flattens a list item by item, and an empty list flattens to nothing. structured_content is still correct. If your tool returns a list, return something wrapper-friendly instead of a bare [] so the model gets one text block to read. That one matters if an agent keeps re-running your search instead of concluding "no match".

Separately, 2.x removed automatic return-value wrapping in the low-level Server, and mcp.types moved out into a mcp-types package. Both are in the migration guide.

The takeaway

A dependency major plus an unbounded requirement in the wrapper equals silent breakage at install time, with a stack trace pointing at your code instead of the dependency. Pin it, then skim the migration guide at py.sdk.modelcontextprotocol.io/migration. It's a long read, but the naming section alone saves the afternoon.

I run a pile of MCP servers locally for agent work, and pinning is the boring habit that keeps it quiet. If you want to see how I aggregate and hot-swap those servers, that's in my Smart-MCP-Proxy repo, but the pin is the only part you need today.

And I could be wrong about your specific stack, so test the pin in a throwaway venv first. That's what I did before writing any of this down.

Top comments (0)