Skip to content

feat: mcp v2 compatible changes under a flag - #3708

Merged
poshinchen merged 4 commits into
strands-agents:mainfrom
poshinchen:feat/mcp-v2-compat
Aug 28, 2026
Merged

poshinchen merged 4 commits into
strands-agents:mainfrom
poshinchen:feat/mcp-v2-compat

Conversation

@poshinchen

@poshinchen poshinchen commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

mcp 2.0 renamed and relocated several public names, so strands will fail when mcp>=2 is installed. This PR adds an internal compatibility module, strands.tools.mcp._compat, so the package imports and runs on both major lines. The dependency pin stays <2 and nothing changes for current installs. This is groundwork for adopting the MCP 2026-07-28 spec (#1659) under an internal feature flag.

All version-dependent names now resolve through _compat:

  • MCP_V2 tells the rest of the codebase which version of MCP is installed. It probes for ClientSession.discover instead of parsing the version string, so pre-releases and backports resolve by capability. It replaces the version probe in mcp_instrumentation.py, and follow-up work (lifecycle, tasks, native OTel) will branch on this same flag.
  • MCPError aliases the exception class, which 1.x spells McpError.
  • streamable_http_transport(url, headers) keeps the 1.x call shape on both lines. 2.x dropped the headers kwarg in favor of a pre-configured HTTPX client, so on 2.x the adapter creates that client itself and closes it when the transport closes.
  • GetSessionIdCallback falls back to a plain callable alias on 2.x, which removed protocol sessions. It is only used for typing.
  • ProgressFnT needs no compat entry; its import location exists on both lines down to the 1.23 pin floor.

A new mcp-v2-compat CI job force-installs mcp==2.0.* over the pin, verifies import strands succeeds, and runs the compat tests against the real 2.x package. The rest of CI keeps testing 1.x only. Without this job, nothing would catch a new 1.x-only import breaking 2.x again.

Related Issues

#1659

Documentation PR

No documentation changes needed; the compat module is internal.

Type of Change

New feature

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce new warnings.

  • I ran hatch run prepare
  • Full MCP unit suite passes on mcp 1.x.
  • The compat tests pass against mcp==2.0.0, both locally and in the new CI job.

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@poshinchen
poshinchen requested a review from a team as a code owner August 7, 2026 17:06
@poshinchen
poshinchen requested a review from lizradway August 7, 2026 17:06
@github-actions github-actions Bot added area-mcp MCP related python Pull requests that update python code enhancement New feature or request labels Aug 7, 2026
@poshinchen

Copy link
Copy Markdown
Contributor Author

@strandly-the-agent review the PR and tell me what's missing or to be improved.

@github-actions github-actions Bot added complexity/low Touched functions have low cognitive complexity (<=10) size/m strands-running labels Aug 7, 2026
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Issue (Important): The whole point of this layer is 2.x compatibility, but nothing in CI verifies it. With the pin at >=1.23.0,<2.0.0, CI only ever runs the 1.x paths — the except ImportError branches and the MCP_V2=True transport shape are exercised only in a manual scratch venv (as the description notes, hence the 3 uncovered lines Codecov flags). That means the compat layer can silently rot on the 2.x line until the pin is widened.

Suggestion: Add a lightweight CI job / nox session that installs mcp>=2 and at minimum runs import strands plus tests/strands/tools/mcp/test__compat.py. That locks in the compatibility guarantee this PR establishes and gives the follow-up work (lifecycle, tasks, native OTel) a safety net.

Comment thread strands-py/src/strands/tools/mcp/_compat.py Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Assessment: Comment (approve-worthy with minor follow-ups)

Clean, well-scoped groundwork PR. The single internal _compat module, the capability-based MCP_V2 probe (hasattr(ClientSession, "discover") over version parsing), and the evergreen comments explaining why each name diverges make this easy to reason about. Tests cover both the 1.x and 2.x transport call shapes.

Review Categories
  • Design: Solid — one source of truth for the version flag, correct _-prefixed internal module, dependency upper bound (<2.0.0) preserved. No API-review needed since the surface is internal.
  • Testing: Good unit coverage of the transport adapter, but the 2.x line is only verified manually — CI never runs it (see inline comment). This is the main gap.
  • Correctness: Verified locally on mcp 1.29.0: clean import, MCPErrorMcpError, ProgressFnT resolves from mcp.client.session, and GetSessionIdCallback matches the fallback alias. One open question on HTTPX client lifecycle in the v2 branch (inline).

Nicely documented change — the PR description alone made the review straightforward.

@strandly-the-agent strandly-the-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed 8aed1e85 end to end. Short version: the code that ships is correct — but CI is red because of this PR, and the description claims more than the code delivers.

The useful thing I could do here that a normal review can't: mcp==2.0.0 is a real release and installable, so I built venvs on both lines and actually ran the 2.x branch instead of reasoning about it.

Check Result
pytest tests/strands/tools/mcp/ on mcp 1.23.0 (pin floor) 217 passed
Same suite on mcp 2.0.0 🔴 64 failed / 153 passed
Python / Lint + CI Gate on this head 🔴 failure (main is green)
ruff check locally ✅ All checks passed
mypy ./src locally, mcp 1.23.0 🔴 5 errors, all in _compat.py — reproduces the CI failure
HTTPX leak on the 2.x branch 🔴 confirmed by execution (aclose() never called)
MCP_V2 probe, MCPError alias, transport names vs real 2.0.0 ✅ all correct
OTel 1.x-only gating ✅ real (connect wrapped on 1.23.0, untouched on 2.0.0)

The one blocker is mundane: mypy runs inside hatch fmt --linter --check (pyproject.toml:139-149lint-check = ["ruff check", "mypy ./src"]), which hatch run prepare and the gating lint job both call. It's failing on _compat.py. That also means the checklist's "I ran hatch run prepare" can't be accurate for this commit.

The framing question is the one that matters (in Questions below): is this PR meant to make 2.x work, or only import so the pin can move later? The body says "imports and runs cleanly on both major lines" and "the pin can later widen to <3 with no code changes" — I measured both and neither holds today. If the answer is "import-only groundwork," the code is basically right and only the wording needs fixing. I've filed nothing; the 2.x functional gaps are pre-existing and listed below for you to decide on.

3 inline comments. Nothing here re-litigates the resolved threads on #3611.

Questions (the framing one is blocking)

Blocking

  1. Is the goal "2.x works" or "2.x imports"? This decides how everything else is tiered. What ships makes strands.tools.mcp importable on 2.x and consolidates the version probe — both real and both correct. But MCPClient is not functional on 2.x: start() raises unconditionally, ~15 camelCase field accesses break, and tasks_config= raises ImportError at construction. If that's intentional staging, could the description say so? As written the next reader will believe the pin can move.

  2. Merge order — this is stacked on #3611, which is still open, and includes its commit. GitHub's Files-changed tab merges both into one diff, so anyone approving from that tab is implicitly approving #3611's content without it clearing its own gate. Is bottom-up (land #3611 first, let this shrink to its own commit) the intent?

Non-blocking, but worth settling before more code branches on the pattern

  1. One boolean is standing in for at least six independently-probeable axes (property-vs-method, tasks surface, transport takes-a-client, transport yields-session-id, field casing, task types importable) — I verified each discriminates on its own. _compat.py already uses per-name try/except for MCPError/GetSessionIdCallback but the global flag for the transport. Since the body says lifecycle/tasks/native-OTel will all branch on this same flag, would per-capability constants age better than one flag a future 3.x can't name correctly? (hasattr(ClientSession, "discover") reads as "≥2" while MCP_V2 reads as "==2".)

  2. MCPTransport is public (mcp_types.py:47, exported in __all__) and quietly gets a second meaning here — on 2.x GetSessionIdCallback degrades to a structural Callable[[], str | None] and the 3-tuple arm is simply dead. Worth deciding what MCPTransport should mean on 2.x before more code depends on the current shape? Relatedly, the PR carries no api/needs-review / api/review-complete label — check-api-review-label is green only because it skips when no label is present.

  3. Should HTTPX ownership be a written rule for future adapters here — the adapter owns what it creates, never what it's given — rather than a per-call-site decision?

Appendix — non-blocking (6)
  • _compat.py:17-19 — the comment calls ClientSession.discover "the 2.x replacement for the removed initialize handshake," but hasattr(ClientSession, "initialize") is True on both installed lines. The spec removed the handshake; the Python client hasn't. Wording only.
  • _compat.py:28-35 catches only ImportError and its except branch doesn't re-import from mcp, so an unrelated import failure in mcp.client.streamable_http gets swallowed and you get a fake alias plus a worse stack trace later. The MCPError sibling at :22-26 doesn't have this gap because its except re-imports from the same module. No caller triggers it today.
  • ⚪ Anything that adds a discover attribute to ClientSession on a real 1.x (subclass, backport, test double) flips MCP_V2 and sends the transport down the 2.x path. Contrived — same root cause as Question 3.
  • test_mcp_instrumentation.py:426 asserts register_post_import_hook is called, which the new gating correctly skips on 2.x, so the suite can't be green there. Inherited from #3611's commit, not this one.
  • ⚪ No CI leg installs mcp>=2, so every 2.x branch is mock-only. Smallest thing that would work today and passes right now: a @pytest.mark.skipif(not MCP_V2, …) import-smoke test plus one single-Python job doing pip install "mcp>=2.0.0,<3.0.0" --no-deps and running just test__compat.py. That alone would have caught the vacuous-mock problem in comment 3.
  • ⚪ Codecov reports 3 uncovered lines in _compat.py — exactly the 2.x/except ImportError branches that need a real mcp>=2 install to execute.
Pre-existing — needs an issue, not a fix here (4)

I verified with git diff 898cc0ef 8aed1e85 that this PR touches none of these lines. They're unchanged from main and only become reachable if the pin widens — so they are not regressions here, and I'm not treating them as blockers. Flagging because they're what stands between this PR and its stated goal. Happy to file them if you want.

  1. MCPClient.start() raises on 2.xmcp_client.py:1063 calls session.get_server_capabilities(); 2.x made it a server_capabilities property. Unconditional bootstrap, every transport. Repro against a real in-process server: AttributeError: 'ClientSession' object has no attribute 'get_server_capabilities'MCPClientInitializationError.
  2. ~15 camelCase→snake_case field sites — worst is mcp_client.py:1007 .isError, where the AttributeError is swallowed into an error ToolResult, so every successful 2.x tool call reports as failed with the raw AttributeError as the tool's output to the model. Wrong-answer shaped, not crash shaped. Also mcp_agent_tool.py:77,82,83 and mcp_client.py:622,1015,1149,1179-1199.
  3. tasks_config= raises a bare ImportError at construction on 2.x (mcp_client.py:299) and isn't aliasable — ClientSession.experimental doesn't exist on 2.0.0, so :1259,1541,1584,1625 have no 2.x surface. Needs a design call (clear "unsupported on 2.x" error vs. port to the extension), and #1659 already defers tasks pending the tasks-extension GA.
  4. tests_integ/mcp/* and the separate strands-mcp package are 2.x-broken (FastMCP, mcp.server.experimental.task_context; strands-mcp has its own <2.0.0 pin). Out of scope, worth tracking so it isn't rediscovered piecemeal.

Automated review by strandly-the-agent — ran on both mcp lines with real installs. Solid work for a human to approve, not a gate; push back where I'm wrong.

Comment thread strands-py/src/strands/tools/mcp/_compat.py Outdated
Comment thread strands-py/src/strands/tools/mcp/_compat.py Outdated
Comment thread strands-py/tests/strands/tools/mcp/test__compat.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Assessment update: Request Changes (correcting my earlier "Comment / approve-worthy" summary — that was wrong; CI is red because of this PR.)

I independently reproduced the mypy failure @strandly-the-agent flagged, and it's a hard merge blocker regardless of the mcp pin:

  • On mcp 1.29.0: 3 net-new errors in _compat.py:23 attr-defined (mcp.shared.exceptions has no MCPError), :26 and :35 unused-ignore.
  • On the 1.23.0 pin floor (what CI's env resolves): 5 errors — the above plus :57 attr-defined and :59 no-any-return, because streamable_http_client/create_mcp_http_client don't exist yet at that version (I confirmed both symbols do exist on 1.29 but not 1.23, which is exactly why the error count differs).

mypy ./src is part of lint-check (pyproject.toml:139-149) which hatch run prepare and the lint gate both run, so the "I ran hatch run prepare" checklist item can't hold for this commit. Root cause is structural: mypy only ever sees one installed mcp, so the non-matching try/except branch is an uncovered attr-defined while warn_unused_ignores = true simultaneously makes the present ignores "unused" on the other line — there's no single invocation where the file is clean as written. The fix needs per-branch # type: ignore[attr-defined] plus a per-module warn_unused_ignores = false override (precedent at pyproject.toml:227-233), and a concrete return type on streamable_http_transport to kill the no-any-return.

The detailed dual-line review already on this PR covers everything else well (HTTPX leak, framing question, pre-existing 2.x gaps) — I won't re-litigate it. My one net-new emphasis: green CI is the gate here, and it's currently red on the supported floor.

@github-actions

Copy link
Copy Markdown
Contributor

Documentation Preview Ready

Your documentation preview has been successfully deployed!

Changed pages:

Updated at: 2026-08-10T15:15:17.445Z

@github-actions

Copy link
Copy Markdown
Contributor

Re-review of the force-pushed head 884973a (now a single commit, unstacked from #3611).

The rebase cleaned up the commit history and description, but the mypy blocker is unchanged — I re-ran it against the current head:

_compat.py:23: error: Module "mcp.shared.exceptions" has no attribute "MCPError"  [attr-defined]
_compat.py:26: error: Unused "type: ignore[attr-defined, no-redef]" comment  [unused-ignore]
_compat.py:35: error: Unused "type: ignore[assignment]" comment  [unused-ignore]

(3 on mcp 1.29.0; 5 on the 1.23 pin floor CI resolves, per the earlier thread). So mypy ./src in lint-check is still red and my Request Changes stands. The fix is unchanged from the detailed thread: per-branch # type: ignore[attr-defined] on the try imports plus a per-module warn_unused_ignores = false override (precedent at pyproject.toml:227-233), and a concrete return type to clear the floor's no-any-return.

Nothing else changed materially in _compat.py, so the other open items (HTTPX lifecycle on the 2.x branch, the "2.x imports vs. works" framing question) also still stand.

@github-actions

Copy link
Copy Markdown
Contributor

Assessment update: Approve (was Request Changes)

Re-reviewed the new head 4666078 — both blockers are resolved and verified locally.

Verification
  • mypy (was the CI blocker) — Now clean. Fix is exactly the structural one needed: branch-level # type: ignore[attr-defined] on the try imports, a no-any-return ignore on the 1.x return, and a per-module warn_unused_ignores = false override (pyproject.toml:241-244, with a clear rationale comment). Confirmed mypy reports zero errors on _compat.py under mcp 1.29.0; the ignore set also covers the 1.23 floor's attr-defined/no-any-return.
  • HTTPX client lifecycle leak — Resolved via _owned_client_transport, which binds the owned client's lifetime to the transport. The new test_..._v2_owns_client_lifecycle asserts the full teardown ordering in a single equality check. test__compat.py: 3 passed, 1 skipped (the 2.x-only path, correctly skipped on a 1.x install).
  • Codecov 68.75% / 10 missing lines — These are the opposite-line branches (the 2.x path on a 1.x runner, plus the except ImportError fallbacks), which are inherently uncoverable in a single-line CI install. Expected for a compat shim; non-blocking.

Clean, well-scoped compat layer with tests that pin the exact contracts (lifecycle ordering, per-line name resolution) rather than incidental details — nice work.

@github-actions

Copy link
Copy Markdown
Contributor

Re-reviewed 8dd7ee7 — this closes the coverage gap from the earlier codecov report, so my Approve stands (now stronger).

Verification of the new tests
  • test_get_session_id_callback_falls_back_to_plain_callable reloads _compat with the name deleted to exercise the 2.x fallback branch on either line, and restores via a second reload in finally — nice touch.
  • The lifecycle test is now gated on requires_v2_transport_names + forces MCP_V2=True, so the 2.x branch actually runs on the 1.x CI line via the backported transport name.
  • Locally: test__compat.py now 5 passed (was 3 passed / 1 skipped), and the full MCP suite is 218 passed with no failures. The importlib.reload doesn't leak — test__compat collects early, and all subsequent tests still pass.

Thanks for following through on the coverage — the branch-directional tests and the reload/restore pattern keep it honest without pretending to run both lines at once.

@github-actions github-actions Bot added strands-running complexity/medium Touched functions have moderate cognitive complexity (11-25) and removed complexity/low Touched functions have low cognitive complexity (<=10) labels Aug 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@poshinchen — reviewed the current changes (b121d27) in full. Good to approve. Nothing blocking is missing.

I re-derived the true scope by diffing the four branch commits alone (084723af^..HEAD): 12 files, +299/−90 — small and focused, matching the description.

What I verified this pass:

  • Single source of truth — every version-dependent name (McpError/MCPError, streamablehttp_client, GetSessionIdCallback, ProgressFnT) now resolves through _compat; grep confirms no stray direct imports remain anywhere in src/.
  • MCP_V2 swap in instrumentation — replacing the I/O-based _is_mcp_v1() version probe with the constant capability flag is correct, and the lock/ordering comment was updated to match (no more GIL-releasing call under the lock).
  • mypy on _compat.py is clean; the warn_unused_ignores=false override is scoped tightly to that one module.
  • Tests — 30 targeted pass (218 full MCP suite on 1.x). The v2 lifecycle test asserting the full ordered event list in one equality (client_enter → transport_enter → transport_exit → client_exit) is a nice way to prove the owned HTTPX client is closed after the transport. Capability-gating (no create=True) keeps it robust against 1.x backports.

One optional, non-blocking note:

  • create_mcp_http_client(auth=...) isn't exercised against the real 2.x package. The compat test asserts the name exists (line 40) but the lifecycle test patches it with a fake (line 110), and the CI job only runs test__compat.py. So if real 2.x ever dropped/renamed the auth kwarg, nothing here would catch it. Low risk given it's a documented API, the pin stays <2, and this is groundwork — fine to leave for the follow-up that actually runs a transport on 2.x, but worth a mental note.

Assessment: Approve. Clean, well-scoped, well-documented compatibility layer with both major lines verified end-to-end in CI.

Comment thread strands-py/src/strands/tools/mcp/_compat.py
Comment thread strands-py/src/strands/tools/mcp/_compat.py
Comment thread strands-py/src/strands/tools/mcp/_compat.py Outdated
Resolve every mcp name that was renamed or relocated in mcp 2.0 through a
single _compat module so the package imports cleanly on both major lines:

- MCPError (spelled McpError in 1.x)
- streamable_http_transport adapter (2.x streamable_http_client takes a
  pre-configured HTTPX client instead of loose header kwargs)
- GetSessionIdCallback (removed in 2.x along with protocol sessions)
- ProgressFnT now imported from mcp.client.session (available on both lines)

The MCP_V2 flag is feature-probed via ClientSession.discover rather than
version-parsed, and replaces mcp_instrumentation's _is_mcp_v1 probe so the
whole codebase branches on one source of truth.

Related to strands-agents#1659
… mcp lines

Address review findings on the compat layer:

- mcp 2.x's streamable_http_client only closes an HTTPX client it created
  itself, so the adapter now binds the client's lifetime to the transport's
  via a wrapping context manager. The 1.x branch is unchanged.
- mypy sees only the installed mcp line, so each try/except branch carries
  its own ignore and a per-module warn_unused_ignores override silences the
  branch the installed line doesn't take.
- Branch tests no longer patch with create=True (which invents missing
  attributes and passes against any spelling); each branch test patches real
  attributes and skips on the other line, and a hasattr test checks the
  installed module directly.
- Test error construction goes through a make_mcp_error helper because the
  two lines have different constructors: 2.x MCPError(code, message, data),
  1.x McpError(ErrorData). The .error accessor the production code reads is
  identical on both.

Related to strands-agents#1659
Late 1.x mcp releases backport the 2.x transport names, so the client
lifecycle test now gates on name availability instead of the installed
line and forces the MCP_V2 flag. A reload test covers the
GetSessionIdCallback fallback. This brings _compat.py to full line and
branch coverage under the mcp<2 pin CI runs with.
The unit-test matrix only ever exercises mcp 1.x under the <2 pin, so
nothing in CI catches a module-level import of a 1.x-only name breaking
import strands on 2.x. This job force-installs mcp 2.0.* over the pin,
asserts the package imports with MCP_V2 set, and runs the _compat tests
so their 2.x-gated paths execute against the real package instead of
mocks. It runs on the PR gate, push to main, and the release gate via
python-test-lint.yml, and uploads no coverage.
@poshinchen

Copy link
Copy Markdown
Contributor Author

@strandly-the-agent review the PR along with the comments again.

@github-actions github-actions Bot added strands-running complexity/low Touched functions have low cognitive complexity (<=10) and removed complexity/medium Touched functions have moderate cognitive complexity (11-25) labels Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@poshinchen — re-reviewed the current state (53aa5a1) together with the open thread.

  • Code is unchanged since my last Approve53aa5a1 is a pure rebase onto newer main (the four branch commits replay cleanly; _compat.py and the full diff are byte-identical at 12 files, +299/−90). 30 targeted compat/instrumentation tests still pass on the rebased tree.
  • Unshure's design question (capability probe vs version parsing) — I left a detailed reply on that thread. Short version: your rationale holds, and I verified it against the installed 1.29.1 line. I added one precise nuance — importlib.metadata.version("mcp") is obtainable (it's what the old probe used), so the airtight justification is backport-reliability, not version-unavailability. Capability probing remains the better choice; no code change needed.

Assessment: Approve stands. The only outstanding item is the reviewer thread, which I believe your explanation resolves.

@strandly-the-agent strandly-the-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed at 53aa5a1, including the comment threads. Still good to approve — one new 🟡 in the delta, with a one-line verified fix inline.

The real delta since the head I last verified (b00237b) is threading main's new OAuth support through the compat layer: streamable_http_transport gained auth: httpx.Auth | None, passed to 1.x as a kwarg and on 2.x into create_mcp_http_client. I verified it end to end: floor-safe (1.23.0's streamablehttp_client accepts auth — checked its real signature), both call-shape tests updated, and mutation-proven (dropping auth from either branch fails the suite). The rest is rebase.

The one new finding (inline at _compat.py:80): the auth threading regressed the "type-checks on both mcp lines" property this PR's own fix commit established — mypy on a 2.x env now reports one arg-type error there, and it points at a real runtime fact I proved on mcp==2.0.1: httpx2.AsyncClient rejects a v1 httpx.Auth object at construction (TypeError: Invalid "auth" argument). The built-in auth={client_id, client_secret} path is fine on both lines — mcp's own ClientCredentialsOAuthProvider follows the installed line (it's an httpx2.Auth subclass on 2.x, verified). Only a custom v1 httpx.Auth via auth_provider= breaks, only on the 2.x branch, and only once the pin widens — so 🟡, not a blocker; CI can't see it because its mypy runs on the 1.x env.

On Unshure's open thread (capability probe vs version parsing): the author's answer is correct, and I have measured data backing it — name matrix from real installs in the collapsed section. A major-version parse would have needed a special case for the 1.29.x backports; the per-name probe can't mispredict by construction.

My standing description ask from both prior reviews: addressed. "Runs cleanly" and "pin can widen with no code changes" are gone; the new text scopes the claim honestly and documents the CI job. Thanks for that.

Evidence ledger (all measured on this head)
Check Result
test__compat.py on mcp 1.23.0 (floor) / 2.0.1 4 passed, 1 skipped — each line, mirror-image skips
Full tests/strands/tools/mcp/ on 1.23.0 256 passed, 1 skipped
Same on 2.0.1 63 failed / 193 passed — the known pre-existing 2.x gaps (start(), camelCase fields, tasks), unchanged, latent behind the pin
mypy _compat.py on 1.23.0 ✅ Success
mypy _compat.py on 2.0.1 🟡 1 error: :80 arg-type (httpx._auth.Auth | None vs expected httpx2._auth.Auth | None)
Runtime probe on 2.0.1 create_mcp_http_client(auth=<v1 httpx.Auth>)TypeError at construction; auth=<ClientCredentialsOAuthProvider> → OK
Mutation: drop auth from 2.x branch / from 1.x branch lifecycle test fails / v1 call-shape test fails — both mutants killed
Prior findings (leak guard, no-create=True tests, make_mcp_error) intact at this head
import httpx at _compat.py:20 safe on any resolve — httpx>=0.28.1,<1.0.0 is a direct strands dep (pyproject.toml:35), not just transitive via mcp 1.x
CI on 53aa5a1 Python / Lint, CI Gate, Python / MCP 2.x Compat, all unit matrices ✅

Name matrix for the capability-probe thread (real installs):

streamablehttp_client streamable_http_client create_mcp_http_client ClientSession.discover
mcp 1.23.0 (floor)
mcp 1.29.x ✅ (backport) ✅ (backport)
mcp 2.0.x
Two smaller notes
  • Integ CI: Run integration tests - Python 3.10 - all is red on this head with an async-teardown signature (Event loop is closed ×7, no MCP-specific annotation), and main's own recent integ runs (Aug 18–19) also show failures. Likely unrelated to this PR — but I couldn't read the job log to fully attribute it, so worth confirming it's the known flake before merge; integ isn't part of CI Gate.
  • Self-correction: in my Aug 11 review I called moto in the mcp-v2-compat job's pip install unnecessary. It's now load-bearing — a parent conftest imports it, and the compat tests fail to collect without it. Good call not taking that nit.

Follow-up re-review by strandly-the-agent — delta-scoped, run against real installs of mcp 1.23.0 / 1.29.x / 2.0.1 on this exact head.

Comment thread strands-py/src/strands/tools/mcp/_compat.py
Comment thread .github/workflows/python-test-lint.yml
@poshinchen
poshinchen merged commit 534692a into strands-agents:main Aug 28, 2026
43 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-mcp MCP related complexity/low Touched functions have low cognitive complexity (<=10) enhancement New feature or request python Pull requests that update python code size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants