{"slug": "the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin", "title": "The MCP Python SDK 2.0 broke the wrapper libraries. Here's how to spot it and pin back", "summary": "The official Python MCP SDK's 2.0.0 release, published July 28, 2026, removed and renamed public APIs, breaking wrapper libraries that depended on `mcp` without an upper version bound. A developer reproduced the failures in isolated virtual environments against versions 1.29.1 and 2.2.0, documenting the renamed `streamable_http_client` import, the context manager's shift from a 3-tuple to a 2-tuple, and Pydantic model attributes moving from camelCase to snake_case. The writeup notes that autogen-ext 0.7.5 remains broken against current `mcp`, while llama-index-tools-mcp was fixed in 0.5.1, and recommends pinning `mcp>=1.28,<2` or upgrading the wrapper.", "body_md": "If your MCP tools started failing a few weeks ago and nothing in your own code changed, you're not imagining it.\n\nThe 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.\n\nI 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.\n\nTwo 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.\n\nIn 1.x:\n\n``` python\nfrom mcp.client.streamable_http import streamablehttp_client\n\nasync with streamablehttp_client(url, timeout=30, sse_read_timeout=300) as (read, write, get_session_id):\n    ...\n```\n\nIn 2.x that import raises ImportError. The replacement is:\n\n``` python\nfrom mcp.client.streamable_http import streamable_http_client\n\nasync with streamable_http_client(url) as (read, write):\n    ...\n```\n\nTwo changes there. The name has underscores now, and the context manager yields a 2-tuple. The 3rd element, `get_session_id`, is gone.\n\nThat 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:\n\n```\nValueError: not enough values to unpack (expected 3, got 2)\n```\n\nI 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`.\n\n`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'`.\n\nHonesty 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.\n\nThis is the quiet one. The Pydantic protocol models renamed their attributes:\n\n`tool.inputSchema` goes to `tool.input_schema`\n`result.isError` goes to `result.is_error`\n`structuredContent` goes to `structured_content`\n`nextCursor` goes to `next_cursor`\nThe 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.\n\nTwo 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.\n\nIf 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:\n\n```\npip install \"mcp>=1.28,<2\"     # resolves to 1.30.0 today\nuv add \"mcp<2\"\n```\n\nIf 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.\n\nBefore guessing, check what you're actually running:\n\n``` python\npython -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))\"\n```\n\nAnd 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.\n\nI saw a couple of claims circulating while digging that can cost you hours if you chase them.\n\nAn 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\".\n\nSeparately, 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.\n\nA 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.\n\nI 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.\n\nAnd 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.", "url": "https://wpnews.pro/news/the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin", "canonical_source": "https://dev.to/milkyway008/the-mcp-python-sdk-20-broke-the-wrapper-libraries-heres-how-to-spot-it-and-pin-back-346n", "published_at": "2026-09-13 17:22:32+00:00", "updated_at": "2026-09-13 17:45:34.543499+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools", "ai-infrastructure"], "entities": ["MCP Python SDK", "PyPI", "autogen-ext", "llama-index-tools-mcp", "Pydantic", "Goose", "rmcp", "httpx2"], "alternates": {"html": "https://wpnews.pro/news/the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin", "markdown": "https://wpnews.pro/news/the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin.md", "text": "https://wpnews.pro/news/the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin.txt", "jsonld": "https://wpnews.pro/news/the-mcp-python-sdk-2-0-broke-the-wrapper-libraries-here-s-how-to-spot-it-and-pin.jsonld"}}