Skip to content

Unbounded mcp>=1.0 breaks streamable-http on the mcp 2.0.0 stable release (2026-07-28) #68

Description

@airidasbrikas23

Severity: high. Hard deadline: 2026-07-28.

What happens

The Python MCP SDK ships stable 2.0.0 on 2026-07-28, per the 2.0.0b2 release notes ("Stable v2 is still targeted for 2026-07-28 alongside the spec release, keep pinning an exact version"). mcp2cli 3.3.1 declares mcp>=1.0 with no upper bound. The moment 2.0.0 is stable, a fresh uvx mcp2cli resolves it and the streamable-http transport stops working.

Right now everything looks fine, because pre-releases are opt-in and uvx still resolves mcp 1.28.1. The break lands silently on the release date, on any machine whose uvx cache misses: a new clone, a CI runner, a teammate's first run.

Root cause

The v2 SDK removed the streamablehttp_client alias. Only streamable_http_client (with the underscore) survives. mcp2cli imports the old name at three sites in mcp2cli/__init__.py:

  • L2555, L3244, L3550: from mcp.client.streamable_http import streamablehttp_client

I confirmed the SDK side directly: 1.28.1 exports both streamable_http_client and the streamablehttp_client alias; 2.0.0b2 exports only streamable_http_client.

Reproduction

Against a live streamable-http server, macOS, mcp2cli 3.3.1:

mcp SDK Result
1.28.1 (current stable) works, tools list returns
2.0.0b2 (preview of the 07-28 release) hard failure, exit 1
ImportError: cannot import name 'streamablehttp_client' from 'mcp.client.streamable_http'.
Did you mean: 'streamable_http_client'?

Scope: I tested every mcp.* import in mcp2cli against 2.0.0b2. Only the streamable-http one breaks; ClientSession, sse_client, the stdio imports, and the OAuth paths all still resolve. So stdio and SSE users are unaffected at the import layer, and streamable-http users lose the transport entirely.

The misleading error path

With --transport streamable you get the clean ImportError above. Without it, mcp2cli catches the ImportError and falls back to SSE, which then returns 405 Method Not Allowed from the server. That points the blame at the server when the fault is client-side, so anyone debugging the fallback will chase the wrong end of the connection.

Why the test suite won't catch it

uv.lock pins mcp 1.26.0. CI tests against v1 while shipping an unbounded >=1.0 to users, so the gap stays invisible until an unpinned install resolves 2.0.0.

Fix

Add an upper bound in pyproject.toml:

- "mcp>=1.0"
+ "mcp>=1.0,<2"

That turns a fixed-date break into a deliberate port. When you do move to the v2 API, the import rename is the first thing to fix, but it may not be the only one: a full v2 pass should re-check the runtime surface (tool-result shapes, inputSchema field access, the OAuth import paths) against 2.0.0 final rather than b2, since the beta surface can still shift.

Workaround for users before that ships

Pin the SDK at the call site:

uvx --with "mcp<2" mcp2cli ...

Verified: byte-identical output to the current unpinned run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions