Repository navigation
fix(mcp-builder): support mcp>=2 streamable_http_client import and custom headers - #1742
Kuldeeep18 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Static review of 8a2d825: the custom-header path creates an HTTP client but does not enter or close it. The SDK only manages clients it creates internally; caller-supplied clients remain caller-owned.
The current HTTP test uses no headers and never enters the context, so it misses this path. Could we register the custom client with the connection’s AsyncExitStack and add async regressions covering both normal exit and initialization failure?
AI-assisted static review; I haven’t run a reproduction yet.
|
Following up with an independent local reproduction at Both SDK versions produced the same results:
The fix registers cleanup immediately after creating the custom client: if http_client is not None:
self._stack.push_async_callback(http_client.aclose)This lets the transport exit before closing the caller-owned client and also covers initialization failure. The regressions retain the real SDK transport lifecycle and the actual client created by Implementation and validation were AI-assisted. |
|
I independently reproduced the custom-header HTTP client lifecycle issue at The tests retain the real client created by On the unmodified head, each SDK run produced Re-run from a clean checkout, with the two patch files in the current directory: git checkout --detach 8a2d825fa2d5fbaf2d375554985bd80503e4eda0
python3.11 -m venv .venv-mcp-1.29.1
python3.11 -m venv .venv-mcp-2.1.1
.venv-mcp-1.29.1/bin/python -m pip install 'mcp==1.29.1' 'anthropic==1.4.0' 'pytest==9.1.1' 'pytest-asyncio==1.4.0'
.venv-mcp-2.1.1/bin/python -m pip install 'mcp==2.1.1' 'anthropic==1.4.0' 'pytest==9.1.1' 'pytest-asyncio==1.4.0'
.venv-mcp-1.29.1/bin/python skills/mcp-builder/scripts/evaluation.py --help
.venv-mcp-2.1.1/bin/python skills/mcp-builder/scripts/evaluation.py --help
git apply regression-tests.patch
.venv-mcp-1.29.1/bin/python -m pytest -q skills/mcp-builder/scripts/test_connections.py
.venv-mcp-2.1.1/bin/python -m pytest -q skills/mcp-builder/scripts/test_connections.py
git apply minimal-fix.patch
.venv-mcp-1.29.1/bin/python -m pytest -q skills/mcp-builder/scripts/test_connections.py
.venv-mcp-2.1.1/bin/python -m pytest -q skills/mcp-builder/scripts/test_connections.pyRegression test patchdiff --git a/skills/mcp-builder/scripts/test_connections.py b/skills/mcp-builder/scripts/test_connections.py
index f914eba..3078e1b 100644
--- a/skills/mcp-builder/scripts/test_connections.py
+++ b/skills/mcp-builder/scripts/test_connections.py
@@ -6,10 +6,15 @@ Verifies that:
- argument validation operates as expected for missing parameters or unsupported transports
"""
+from contextlib import asynccontextmanager
+
+import anyio
import pytest
import sys
from pathlib import Path
+import mcp.client.streamable_http as streamable_http_module
+
# Add scripts directory to sys.path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
@@ -24,6 +29,58 @@ from connections import (
)
+class ExpectedInitializationError(RuntimeError):
+ """Sentinel error used to verify initialization-failure cleanup."""
+
+
+class StubClientSession:
+ """Minimal session stub; transport and HTTP-client lifecycles stay real."""
+
+ initialize_error = None
+
+ def __init__(self, read, write):
+ self.read = read
+ self.write = write
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ return False
+
+ async def initialize(self):
+ if self.initialize_error is not None:
+ raise self.initialize_error
+
+
+@pytest.fixture
+def captured_production_http_client(monkeypatch):
+ """Capture the real client passed into the real SDK transport context."""
+ captured = {}
+ real_streamable_http_client = streamable_http_client
+
+ @asynccontextmanager |
|
Thanks for the thorough review and independent reproduction, @yao23! You're spot on—caller-supplied HTTP clients remain caller-owned in the MCP SDK, so failing to register cleanup left the custom client open. In commit 0ba9e31, I have updated the PR to:
All 7 tests are passing. Thanks again for catching this! |
Thanks for incorporating this so quickly! One small test-hardening suggestion: could the lifecycle tests assert that the custom client was actually captured before checking its state? client = captured_production_http_client.get("client")
assert client is not None
assert client.is_closed is TrueWith the current conditional checks, a regression that stops creating or passing the custom client could make the tests pass vacuously. This should preserve the network-isolated setup while ensuring the intended custom-header path was exercised. AI-assisted follow-up review. |
…stom headers Fixes anthropics#1668 In mcp>=2.0.0, streamablehttp_client was renamed to streamable_http_client, and custom headers are configured via create_mcp_http_client / http_client rather than as a direct keyword argument. Because connections.py unconditionally imported streamablehttp_client at module scope, importing connections or running evaluation.py failed with: ImportError: cannot import name 'streamablehttp_client' from 'mcp.client.streamable_http' Changes: - Add backwards- and forwards-compatible import fallback for streamable_http_client to support both mcp>=2 and mcp<2. - Configure custom headers using create_mcp_http_client when available in mcp>=2, with fallback to headers parameter on mcp<2. - Add test_connections.py regression tests covering all connection transports (stdio, sse, http), context creation, and validation error cases.
0ba9e31 to
b26eba8
Compare
|
Good catch, @yao23! Updated in b26eba8:
All 7 tests pass cleanly. Thanks again for the follow-up review! |
|
Independent verification: checked out the PR branch and ran the new suite on a clean venv (Python 3.11, latest |
yao23
left a comment
There was a problem hiding this comment.
Reviewed the current head b26eba8b7fab86fa74de9bc1d76a3ca0a4f53bf9. The caller-owned HTTP client is now registered with the connection’s AsyncExitStack, and the lifecycle regressions cover both normal context exit and initialization failure. The explicit client is not None assertions ensure the custom-header path cannot pass vacuously, while the legacy SDK path is guarded separately.
I previously reproduced the lifecycle defect at 8a2d825fa2d5fbaf2d375554985bd80503e4eda0 and verified the minimal correction under mcp==1.29.1 and mcp==2.1.1. I have not rerun the rebased current head in this review, but I found no remaining blocking issue in the current diff.
Approved. AI-assisted review.
|
Hi maintainers, just a gentle check-in on this PR. It has been independently verified and approved by community reviewers with all regression tests passing, and the branch is clean and up to date with \main. Whenever you have a moment, could someone from the team take a look for merge? Thank you! |
|
Hi @cj-ant, @rlancemartin — checking in on this PR. The branch is clean, conflict-free, and synced with latest \main. The fix and lifecycle regressions have been independently verified and approved by community reviewers (@yao23\ and @98zc5g5jyw-arch) with 7/7 tests passing under both \mcp==1.x\ and \mcp==2.x. Whenever you have a moment, could someone from the Anthropic team please give it a final review for merge? Thank you! |
|
Follow-up re-review at head Since our previous review at Verified
No blockers from my side. Still LGTM for merge. |
|
Thank you for the thorough follow-up re-review and independent verification, @98zc5g5jyw-arch! Appreciate you taking the time to test both the dual-arm compatibility and the lifecycle edge cases. Hi @rlancemartin @maheshmurag @cj-ant — this PR is fully green, conflict-free, and has now been independently verified and approved by community reviewers (@yao23 and @98zc5g5jyw-arch) with all 7/7 lifecycle regression tests passing under both Whenever you have a moment, could someone from the Anthropic team please give it a final review for merge? Thank you! |
Fixes #1668
Problem
In
mcp>=2.0.0,streamablehttp_clientwas renamed tostreamable_http_client, and custom HTTP headers are configured viacreate_mcp_http_client/http_clientrather than as a direct kwarg tostreamable_http_client.Because
skills/mcp-builder/scripts/connections.pyunconditionally importedstreamablehttp_clientat module scope, importingconnectionsor runningevaluation.pyfails on startup when installingrequirements.txt(which specifiesmcp>=1.1.0without an upper bound):Solution
skills/mcp-builder/scripts/connections.py, importstreamable_http_clientandcreate_mcp_http_clientwith fallback tostreamablehttp_client as streamable_http_clientformcp<2.0.0.MCPConnectionHTTP._create_context():create_mcp_http_clientis available, createhttp_clientwith custom headers and pass tostreamable_http_client(url=self.url, http_client=http_client).mcp<2.0.0, passheaders=self.headersdirectly.skills/mcp-builder/scripts/test_connections.pytesting import resolution, stdio/sse/http transport factories, context manager creation, and validation error cases.Verification
pytest skills/mcp-builder/scripts/test_connections.py -v: all 5 tests passed.python skills/mcp-builder/scripts/evaluation.py --helpundermcp 2.1.1and Python 3.14: cleanly displays full usage and exits 0.python -m py_compile.