Skip to content

fix(mcp): wrap streamable_http_client under mcp>=2.0 (fixes #4413) - #4416

Open
Anai-Guo wants to merge 2 commits into
traceloop:mainfrom
Anai-Guo:fix/mcp-2x-streamable-http-client
Open

fix(mcp): wrap streamable_http_client under mcp>=2.0 (fixes #4413)#4416
Anai-Guo wants to merge 2 commits into
traceloop:mainfrom
Anai-Guo:fix/mcp-2x-streamable-http-client

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented Aug 12, 2026

Copy link
Copy Markdown

Problem

McpInstrumentor._instrument registers a post-import hook that wraps
mcp.client.streamable_http.streamablehttp_client. In mcp 2.0.0 that
callable was renamed to streamable_http_client, so bootstrapping the
instrumentor against mcp>=2.0 fails with:

Error initializing MCP instrumentor: module 'mcp.client.streamable_http' has no attribute 'streamablehttp_client'

Fixes #4413.

Fix

The post-import hook now wraps whichever name the imported module exposes:

  • mcp 1.9–1.27 → only streamablehttp_client (old name)
  • mcp 1.28 → both names (the new alias was introduced here)
  • mcp 2.0.0 → only streamable_http_client
  • mcp 1.6.x → no mcp.client.streamable_http module at all, so the hook never fires

_instruments stays at mcp >= 1.6.0; the change keeps that whole range working.
Only the client hook is affected — the server-side StreamableHTTPServerTransport.connect
hook still resolves under mcp 2.0.0.

Verification

Reproduced the original failure and confirmed the fix by registering the real
wrapt post-import hook against actual mcp installs (old hardcoded name vs. the
new attribute-detecting hook):

mcp version old code (hardcoded streamablehttp_client) this PR
1.12.0 OK OK
2.0.0 FAILhas no attribute 'streamablehttp_client' OK

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility for streamable HTTP client instrumentation across current and legacy client interfaces.
    • Ensured instrumentation continues to work when either supported client symbol is available.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

The MCP instrumentation now checks for supported streamable HTTP client symbols and wraps the first available symbol. This prevents initialization failure when the expected symbol is absent in MCP 2.x.

Changes

MCP streamable HTTP instrumentation

Layer / File(s) Summary
Streamable HTTP client symbol compatibility
packages/opentelemetry-instrumentation-mcp/.../instrumentation.py
A module-load callback checks supported client function names, wraps the first available function, and replaces the previous single-symbol wrapper.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: 🟡 Moderate · up to b3d83

The client instrumentation now supports MCP 2.x naming, but versions that expose both names may wrap the legacy alias first, allowing direct calls to the canonical function to bypass transport tracing and omit spans. Merge should wait for the selection order to be corrected.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the MCP compatibility fix for wrapping streamable_http_client under mcp>=2.0 and references issue #4413.
Linked Issues check ✅ Passed The change resolves the mcp>=2.0 client rename failure and preserves compatibility with supported MCP versions, meeting issue #4413.
Out of Scope Changes check ✅ Passed The change is limited to MCP client instrumentation compatibility and matches the linked issue objectives.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py (1)

94-106: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Add compatibility tests for both MCP client names.

Add tests for modules with only streamablehttp_client, only streamable_http_client, both names, and neither name. MCP 1.24–1.28 expose both names, while MCP 2.x removes streamablehttp_client. The test dependency excludes MCP 2.x. Add an MCP 2.x CI job if real-package validation is required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py`
around lines 94 - 106, Add compatibility tests covering
_wrap_streamable_http_client with modules exposing only streamablehttp_client,
only streamable_http_client, both names, and neither name. Verify the available
function is wrapped exactly once, the legacy name takes precedence when both
exist, and no wrapper is registered when neither exists; add an MCP 2.x CI job
only if validation against the real package is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py`:
- Around line 98-103: Update the name-priority loop in the instrumentation setup
to check streamable_http_client before streamablehttp_client, then return after
wrapping the first available function. Update the adjacent comment to document
the canonical-first selection while retaining the legacy-name fallback for older
MCP versions.

---

Nitpick comments:
In
`@packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py`:
- Around line 94-106: Add compatibility tests covering
_wrap_streamable_http_client with modules exposing only streamablehttp_client,
only streamable_http_client, both names, and neither name. Verify the available
function is wrapped exactly once, the legacy name takes precedence when both
exist, and no wrapper is registered when neither exists; add an MCP 2.x CI job
only if validation against the real package is required.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 864c1035-35d9-479a-b2ab-5c5268812084

📥 Commits

Reviewing files that changed from the base of the PR and between 62e24c2 and b3d832a.

📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py

Comment on lines +98 to +103
for name in ("streamablehttp_client", "streamable_http_client"):
if hasattr(module, name):
wrap_function_wrapper(
module, name, self._transport_wrapper(tracer)
)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

for version in 1.23.3 1.24.0 1.28.0 2.0.0; do
  uv run --isolated --with "mcp==${version}" python - <<'PY'
import importlib
import importlib.metadata

module = importlib.import_module("mcp.client.streamable_http")
names = [
    name
    for name in ("streamable_http_client", "streamablehttp_client")
    if hasattr(module, name)
]
print(importlib.metadata.version("mcp"), names)
PY
done

Repository: traceloop/openllmetry

Length of output: 199


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- changed file ---'
sed -n '70,120p' packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py

printf '%s\n' '--- MCP package metadata and tests ---'
rg -n -C 3 'mcp[<=>]|streamable.?http_client|transport_wrapper|wrap_function_wrapper' \
  packages/opentelemetry-instrumentation-mcp pyproject.toml uv.lock 2>/dev/null || true

printf '%s\n' '--- repository files related to MCP ---'
git ls-files packages/opentelemetry-instrumentation-mcp | sed -n '1,120p'

Repository: traceloop/openllmetry

Length of output: 21552


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json
import urllib.request

versions = ["v1.23.3", "v1.24.0", "v1.26.0", "v1.28.0", "v2.0.0"]
base = "https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/{}/src/mcp/client/streamable_http.py"

for version in versions:
    url = base.format(version)
    print(f"--- {version} ---")
    try:
        text = urllib.request.urlopen(url, timeout=15).read().decode()
    except Exception as exc:
        print(type(exc).__name__, exc)
        continue
    lines = text.splitlines()
    for i, line in enumerate(lines, 1):
        if "streamablehttp_client" in line or "streamable_http_client" in line:
            start = max(1, i - 3)
            end = min(len(lines), i + 12)
            print("\n".join(f"{n}: {lines[n-1]}" for n in range(start, end + 1)))
            print()
PY

Repository: traceloop/openllmetry

Length of output: 958


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

for version in v1.23.3 v1.24.0 v1.26.0 v1.28.0 v2.0.0; do
  printf '%s\n' "--- ${version} ---"
  url="https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/${version}/src/mcp/client/streamable_http.py"
  if ! text="$(curl -ksSf --max-time 20 "$url")"; then
    echo "source unavailable"
    continue
  fi
  printf '%s\n' "$text" | nl -ba | grep -E -B 3 -A 12 'streamablehttp_client|streamable_http_client' || true
done

Repository: traceloop/openllmetry

Length of output: 423


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

for version in v1.23.3 v1.24.0 v1.26.0 v1.28.0 v2.0.0; do
  printf '%s\n' "--- ${version} ---"
  url="https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/${version}/src/mcp/client/streamable_http.py"
  curl -ksSf --max-time 20 "$url" |
    grep -E -B 4 -A 12 'streamablehttp_client|streamable_http_client' || true
done

Repository: traceloop/openllmetry

Length of output: 5266


Select streamable_http_client before streamablehttp_client.

MCP 1.24.0–1.28.0 export both names, and streamablehttp_client delegates to streamable_http_client. Because the loop returns after wrapping the legacy name, direct calls to the canonical function bypass _transport_wrapper and produce no transport spans. Reverse the order and update the comment. MCP 1.23.3 exposes only the legacy name, so the fallback remains compatible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/instrumentation.py`
around lines 98 - 103, Update the name-priority loop in the instrumentation
setup to check streamable_http_client before streamablehttp_client, then return
after wrapping the first available function. Update the adjacent comment to
document the canonical-first selection while retaining the legacy-name fallback
for older MCP versions.

Source: MCP tools

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Anai-Guo , Shall I update this PR with the recommended backward compatible changes? Or let me know if you are on it.

for name in ("streamable_http_client", "streamablehttp_client"):

mcp 1.24 added streamable_http_client and kept streamablehttp_client as a
deprecated shim that delegates to it. Wrapping the legacy name first left
direct calls to the canonical function untraced on 1.24-1.28; wrapping the
canonical name first covers both call paths there, and the legacy name is
still used as a fallback on mcp<1.24.

Signed-off-by: Tai An <antai12232931@outlook.com>
@Anai-Guo

Copy link
Copy Markdown
Author

Thanks @sathishsw — I'm on it, just pushed it in 517be99.

I checked the upstream sources before changing the order, and the delegation direction is what makes canonical-first the right call rather than wrapping both names:

  • 1.23.3 — only streamablehttp_client exists.
  • 1.24.0–1.28.0 — both exist as real functions, and streamablehttp_client is @deprecated and delegates: its body does async with streamable_http_client(url, http_client=client, ...). That call resolves the module global at call time, so wrapping the canonical attribute traces the legacy path too.
  • 2.0.0 — only streamable_http_client exists.

So the loop keeps the return (wrapping both names on 1.24–1.28 would double-wrap the legacy path and emit two transport spans for one connection) and just tries the canonical name first, falling back to the legacy name on <1.24. Comment updated to spell that out.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug Report: Unsupported mcp==2.x module 'streamablehttp_client'

3 participants