Skip to content

feat(mcp-py): relax mcp version to <2.2 - #4151

Merged
poshinchen merged 3 commits into
strands-agents:mainfrom
poshinchen:docs/mcp-v2-migration
Sep 4, 2026
Merged

poshinchen merged 3 commits into
strands-agents:mainfrom
poshinchen:docs/mcp-v2-migration

Conversation

@poshinchen

@poshinchen poshinchen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

Widens the range to mcp>=1.23.0,<2.2, so a fresh install now resolves mcp 2.x.
Projects that need to stay on 1.x pin mcp<2 themselves. The upper bound excludes mcp releases we have not verified yet and moves up as we test new ones.

Because the unit-test matrix now resolves 2.x, the force-install compat job flips direction: unit-test-mcp-v2 becomes unit-test-mcp-v1 ("MCP 1.x Compat"), force-installing mcp 1.x so the 1.x branches of _compat still run against the real package. The legacy 2025-11-25 task tests are gated to 1.x, matching the workflow they exercise.

A new developer doc, strands-py/docs/MCP_VERSIONS.md, covers the support status, install options, and what behaves differently on 2.x. The README's MCP section links to it.

Related Issues

Part of #1659

Documentation PR

Developer docs are included in this PR (strands-py/docs/MCP_VERSIONS.md and a README pointer). No site/ changes.

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

CI exercises both mcp lines on this PR: the regular unit-test matrix resolves 2.x, and the renamed MCP 1.x Compat job force-installs 1.x and runs the full MCP client suite against it.

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 September 3, 2026 20:42
@poshinchen
poshinchen requested a review from liramon2 September 3, 2026 20:42
@github-actions github-actions Bot added enhancement New feature or request area-mcp MCP related python Pull requests that update python code size/s strands-running labels Sep 3, 2026
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ds-py/src/strands/tools/mcp/mcp_instrumentation.py 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread .github/workflows/python-test-lint.yml
Comment thread strands-py/pyproject.toml
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Assessment: Comment (PR is marked WIP)

Focused, well-documented change that relaxes the mcp bound to allow 2.x and flips the CI compat job to cover 1.x. The compatibility-layer approach and the new MCP_VERSIONS.md are clear. Two items are worth resolving before this leaves WIP.

Review Themes
  • Dependency bound: <2.2 may be broader than the versions actually verified, and it reintroduces the "unrelated PRs break on upstream release" risk the old 2.0.* pin guarded against. See inline note on pyproject.toml.
  • Behavior change: Fresh installs now resolve to 2.x, which turns MCP Tasks into an ImportError on upgrade. Documented in the migration guide, but not reflected in the PR's Type of Change; consider a clearer error message.
  • CI status checks: Job rename may orphan a required branch-protection check.
  • Process: PR description template is empty and the testing checklist is unchecked — please fill these in and confirm hatch run prepare before merge.

Nice work on the compatibility layer and the thorough migration doc.

@poshinchen poshinchen changed the title WIP feat(mcp-py)relax mcp version to 2.2 WIP feat(mcp-py)relax mcp version to <2.2 Sep 4, 2026
@poshinchen
poshinchen force-pushed the docs/mcp-v2-migration branch 2 times, most recently from 2c59727 to b383544 Compare September 4, 2026 15:01
Comment thread strands-py/docs/MCP_VERSIONS.md Outdated
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Re-review after force-push (HEAD now b383544) — updated assessment: Request changes

The version-bound relaxation and CI flip still look sound, and my earlier notes on the pyproject.toml bound and the CI job rename remain applicable. However, the revised MCP_VERSIONS.md introduced a blocking accuracy problem.

What changed and what to fix
  • Tasks section now misdescribes behavior (blocking): it claims tasks_config works on both versions via SEP-2663 and references *_task_sync methods that don't exist in the source. The implementation uses the 1.x-only session.experimental API, and the CI comment + new skipif(MCP_V2) both confirm tasks are 1.x-only. Because fresh installs now resolve to 2.x, this would actively mislead users. See inline comment on MCP_VERSIONS.md. The previous revision described this correctly.
  • Still open from prior review: dependency bound <2.2 may be broader than what's verified (pyproject.toml); job rename may orphan a required status check.
  • Process: PR is still WIP with an empty description/unchecked testing checklist.

@poshinchen poshinchen changed the title WIP feat(mcp-py)relax mcp version to <2.2 feat(mcp-py)relax mcp version to <2.2 Sep 4, 2026
@poshinchen
poshinchen requested review from pgrayy and removed request for liramon2 September 4, 2026 15:31
@poshinchen
poshinchen force-pushed the docs/mcp-v2-migration branch from b383544 to 04dd4e0 Compare September 4, 2026 15:34
@github-actions github-actions Bot added the complexity/low Touched functions have low cognitive complexity (<=10) label Sep 4, 2026
@poshinchen

Copy link
Copy Markdown
Contributor Author

@strandly-the-agent review the changes

@poshinchen poshinchen changed the title feat(mcp-py)relax mcp version to <2.2 feat(mcp-py): relax mcp version to <2.2 Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Re-review (HEAD 04dd4e0) — updated assessment: Approve

Thanks for the quick turnaround. The blocking item is fully resolved and the PR is in good shape.

Verification of prior feedback
  • Tasks doc accuracy (was blocking) → fixed: MCP_VERSIONS.md now correctly states tasks require 1.x, that tasks_config on a 2.x install raises ImportError at construction (matching the session.experimental implementation), and points to feat(mcp/py): support SEP-2663 tasks #4125 for SEP-2663. The non-existent *_task_sync methods and the "works on both versions" claim are gone. This is now consistent with the CI comment and the skipif(MCP_V2) gate.
  • Dependency bound → addressed: doc now documents that both in-range lines (1.29.x, 2.1.x) were verified end to end, so <2.2 matches what's verified.
  • Process → done: description, related issue ([FEATURE] MCP Specification 2026-07-28 adoption (parent tracker) #1659), and testing notes are filled in; no longer WIP.

One non-blocking pre-merge check (external to the code): confirm branch-protection required checks are updated to the renamed MCP 1.x Compat job so PRs don't wait on an orphaned check.

Nice, focused change — the compatibility layer and migration doc are clear and the docs now match the implementation.

@github-actions github-actions Bot added size/m and removed size/s labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Status reconciliation (HEAD 04dd4e0) — authoritative verdict: Request changes 🔴

To avoid conflicting signals: my earlier "Approve" comment above was posted before the full fresh-context verification pipeline ran, and it only validated the MCP_VERSIONS.md doc fix. The deeper review below (against real mcp 2.1.1 and 1.29.1) supersedes it — I've re-verified its blocking findings against the code on this head and they hold:

  • 🔴 auth_provider=<httpx.Auth> breaks on mcp 2.x_compat.py:621 passes auth into create_mcp_http_client under a # type: ignore[arg-type]; mcp 2.x is built on httpx2, which rejects an httpx.Auth at runtime (MCPClientInitializationError). This contradicts the README/doc "runs unchanged on either version" claim, and unit tests mock the transport so CI stays green. Recommend failing fast at construction with a clear message and documenting it as a 2.x difference.
  • 🔴 tests_integ/mcp is 1.x-only — the integration suite (tasks + FastMCP echo server) fails to import/run on 2.x, and the integration job on this head already failed. Since a fresh install now resolves 2.x, the integ suite needs to be version-aware.
  • 🟡 pyproject.toml:266 ignore_errors = true disables mypy for all of mcp_client.py + mcp_instrumentation.py (~2124 LOC) for every future edit, not just the 1.x-typed lines. Per-line # type: ignore (the modules are clean on 1.x) would keep coverage.

No new commits since the detailed review, so its inline comments and blocking questions (the site/ user-guide docs that would now ImportError, and whether 2.x-by-default is a recorded decision per DECISIONS.md/#3708) remain the actionable items. Full detail is in the review thread above — not repeating it here.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Re-review (HEAD f8472b6, rebased on the now-merged #4125) — updated verdict: Approve

The three blocking items from the prior review are all resolved, and the tasks documentation is now accurate and code-backed:

  • Integration tests (was 🔴) → fixed. The new version-aware tests_integ/mcp/conftest.py (collect_ignore when MCP_V2) plus the dedicated mcp-1x integration scope and the hatch STRANDS_TEST_MCP_V1 override run the 1.x-era suite against forced mcp 1.x, while the main scope runs 2.x. Clean split.
  • auth_provider / httpx-vs-httpx2 (was 🔴) → resolved. Now documented as an explicit 2.x behavior difference in MCP_VERSIONS.md, and the README claim was softened to "most code…runs unchanged," so the contradiction is gone.
  • mypy ignore_errors (was 🟡) → gone. Only the legitimate _compat warn_unused_ignores=false override remains; the wholesale suppression of mcp_client/mcp_instrumentation is no longer needed now that feat(mcp/py): support SEP-2663 tasks #4125 landed real 2.x-typed task code.
  • Tasks doc accuracy → verified. submit_tool_*/get_task_*/update_task_*/cancel_task_* exist, the tool-call flow branches on MCP_V2 (SEP-2663 on 2.x, legacy experimental on 1.x), the manual lifecycle methods raise RuntimeError on 1.x, and test_mcp_client_tasks_v2.py runs in the default 2.x matrix. The doc matches the implementation.
Non-blocking follow-ups (not gating this PR)
  • auth_provider UX: documenting is fine, but failing fast at construction with a version-aware message would beat the raw request-time error that never mentions mcp versions. Worth a follow-up.
  • site/ user guide: guides that teach 1.x-only names (streamablehttp_client, FastMCP) would ImportError on a fresh (2.x) install — please confirm a tracked follow-up under [FEATURE] MCP Specification 2026-07-28 adoption (parent tracker) #1659 or a companion site/ update.
  • Decision record: 2.x-by-default reverses feat: mcp v2 compatible changes under a flag #3708's stated position; consider recording it in team/DECISIONS.md.
  • <2.2 floating bound: an upstream 2.1.x patch can now redden unrelated PRs via the default matrix — already discussed and reads as an accepted tradeoff.

Nice turnaround — rebasing onto #4125 and making the integration suite version-aware resolved the substantive concerns, and the docs now line up with the code.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Re-review (HEAD 4fb6144, rebased onto the merged #4129) — verdict unchanged: Approve

Since the last approval the branch was rebased onto #4129 ("added mcp 2.x integ tests"). I checked that the conflict resolution integrates it cleanly rather than dropping its coverage — the earlier "whichever merges second, those tests silently skip" concern does not materialize:

  • This PR removes test(mcp): added mcp 2.x integ tests #4129's dedicated Run mcp 2.x integration tests step from python-test-lint.yml (that job is now 1.x-only), but tests_integ/mcp/test_mcp_client_v2.py still runs on 2.x via the integration workflow's all scope (test-path: tests_integ, default env resolves 2.x). The new conftest.py collect_ignore skips only the 1.x-only modules under MCP_V2, so the v2 file is still collected.
  • On the forced-1.x mcp-1x scope it's safely skipped by its own skipif(not MCP_V2) marker, and its 2.x-only imports are isolated in _build_server so the module imports cleanly on 1.x.

One minor non-blocking nit (out of this PR's diff, inherited from #4129): test_mcp_client_v2.py's module docstring still says the unit-test-mcp-v2 CI job runs it — this PR renamed that job to unit-test-mcp-v1 and moved v2 integ execution to the all scope, so that line is now stale. Worth a one-line fix if you touch the file.

No new blocking issues — the rebase is coherent and coverage is preserved on both mcp lines.

@poshinchen
poshinchen force-pushed the docs/mcp-v2-migration branch from 4fb6144 to 177ffdd Compare September 4, 2026 18:57
@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 Sep 4, 2026
@poshinchen

Copy link
Copy Markdown
Contributor Author

@strandly-the-agent Review the changes. You will notice that there are many # type ignore it's due to the support of both mcp 1.x and 2.x. Analyze them to see if they can be improved, or if some of them can be cleaned up

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Re-review (HEAD 177ffdd) — verdict unchanged: Approve

The rewritten fix the conflict commit re-resolves against a moved main but is an effective no-op: the file set (11 files) and every diff hunk are identical to the previously approved state. No regression.

On the new Codecov report (80% patch, 3 missing lines) — this is expected and non-blocking. All three sit on version-divergent branches that only execute on the other mcp line:

  • mcp_instrumentation.py:222 — the elif type(item) is JSONRPCMessage branch (# type: ignore[unreachable] on the default 2.x line).
  • mcp_client.pyMCP_V2 branches (e.g. the stdio_client # type: ignore[return-value] path and the version-branched task logic).

These run in the forced-install MCP 1.x Compat job's separate coverage upload rather than the default matrix Codecov measures for patch %, so they surface as missing/partial. That's the known characteristic of the dual-version _compat design, already discussed earlier in this review. No action needed.

@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.

Verdict: approve, with cleanups — re-review of 177ffdd (rebased on #4125/#4129) plus the # type: ignore analysis you asked for. All my earlier 🔴s are resolved; nothing new blocks.

Type ignores, in one line: of the 45 in strands/tools/mcp, 25 exist only because 1.x-only code is type-checked against 2.x types, 18 only for the reverse (and CI never runs mypy against 1.x, so those 18 are never exercised), 1 is dead on both lines, 1 is over-broad. The 14 new ones in mcp_client.py/mcp_instrumentation.py can move behind ~4 _compat helpers, which restores the invariant main had (all version divergence lives in _compat).

  • _compat.py:609 — delete: both names exist on 1.29.1 and 2.1.1, mypy reports the ignore unused on both lines.
  • _compat.py:72[misc, assignment][misc]; assignment never fires on either line.
  • 🟡 pyproject.toml:263 — the 1.x-only ignores are unchecked today; a mypy step in the MCP 1.x Compat job closes that (inline).
  • 🟡 mcp_client.py — 12 new ignores collapse into _compat.experimental(), task-status constants, a cancelled_notification() helper and a wider MCPTransport (inline).
  • 🟡 auth_provider (my open thread) — documented now, still no fail-fast; downgraded from 🔴, reply in-thread.

Ran this one myself (follow-up shape) rather than the full fan-out; details and the per-ignore matrix below.

Status of my previous findings
Finding Status
🔴 tests_integ/mcp red on 2.x Fixedtests_integ/mcp/conftest.py collect_ignore + mcp-1x integ scope + hatch STRANDS_TEST_MCP_V1 override. Verified collection: 2.1.1 → 13 tests (test_mcp_client_v2.py only), 1.29.1 → 48 tests across all 9 modules. Caveat: the mcp-1x scope does not run on this PR — pull_request_target executes main's workflow file — so its first real run is after merge. I could not exercise the hatch override locally (sandbox filesystem failed mid-review); the override syntax matches hatch's overrides.env.<VAR>.dependencies form.
🔴 auth_provider=<httpx.Auth> breaks on 2.x Documented, not guarded → 🟡. MCP_VERSIONS.md now lists it and the README says "most code". The runtime failure is still Invalid "auth" argument with no mention of mcp versions. Fail-fast at construction remains my suggestion; not blocking.
🟡 ignore_errors = true on mcp_client/mcp_instrumentation Fixed — replaced by per-line coded ignores + warn_unused_ignores = false, as suggested. Those ignores are what the analysis below is about.
🟡 MCP_VERSIONS.md accuracy Fixed — raw-model field casing, auth_provider, and the ToolAnnotations wording are all corrected. Nit: the casing bullet names get_prompt/read_resource; list_prompts, list_resources, list_resource_templates return raw models too (nextCursor/resourceTemplates).
🟡 tasks_config bare ImportError on 2.x Moot#4125 landed; tasks run on both lines and the doc says so.
Type-ignore analysis — method and matrix

Method: flipped the mcp-module override to warn_unused_ignores = true in a scratch copy of pyproject.toml and ran mypy ./src ./tests_typing in two venvs (mcp 1.29.1 and 2.1.1, mypy 2.3.1). An ignore reported unused on line A is one that exists only for line B.

Class Count Where
Needed only when checking against 2.x (1.x-branch code seen through 2.x types) 25 _compat.py:65,68,160,205,212,217,548,621,628,630; mcp_client.py:1756,1760,1762,1957,1984,2316,2335,2378,2407,2419,2422,2609; mcp_instrumentation.py:220,222
Needed only when checking against 1.x 18 _compat.py:62,72(misc),99,100,111,169,210,243,276,277,502,542,545,575; mcp_client.py:2093; mcp_tasks.py:198,211,223
Needed on both (not version-related) 1 mcp_client.py:1678 [unreachable]
Dead on both 1 _compat.py:609streamable_http_client/create_mcp_http_client exist on 1.29.1 already; only guards 1.x releases before the rename, which nothing type-checks
Over-broad _compat.py:72 [misc, assignment]: assignment unused on both lines

Two consequences worth acting on:

  1. CI runs mypy once, against 2.x (hatch fmt --linter --check in the Lint job). The 18 1.x-side ignores are therefore never exercised — nothing checks that the 1.x-typed code actually type-checks on 1.x, and a dead ignore like :609 is invisible. Adding pip install mypy && mypy ./src ./tests_typing to the MCP 1.x Compat job (it already has a real 1.x install) makes both sets load-bearing at the cost of ~1 minute.
  2. Where the ignores live. On main, mcp_client.py carried 2 ignores (neither version-related) and _compat.py carried 23 — the version seam was fully inside _compat. This PR adds 12 to mcp_client.py and 2 to mcp_instrumentation.py. Every one of those is a 1.x-only expression that can be a _compat helper (see inline threads), leaving mcp_client.py with its original 2 and _compat as the single place a reader has to know about the two lines.

Considered and not recommended: always_true/always_false = ["MCP_V2"]. It's the mypy-native way to type-check each branch against its own line (like sys.platform), and would need two mypy runs (Lint with always_true, 1.x job with --always-false). I tried it with all 45 ignores stripped: 26 errors remain on 2.x and 12 on 1.x, because most 1.x-only code is in unconditionally defined functions (_call_tool_as_task_and_poll_async, the cancel path, TransportContextExtractingReader, the client_credentials_auth fall-through), which branch pruning doesn't reach — and every if MCP_V2: return … fall-through trips warn_unreachable. It only pays off if the 1.x-only functions are moved into branches or a separate module; the _compat helper route gets most of the benefit for much less churn.

Runtime facts used above (verified): mcp 2.1.1 CallToolResult(isError=True)is_error=True (populate_by_name=True), so mcp_client.py:1984 is typing-only; mcp 1.x TASK_OPTIONAL/TASK_REQUIRED/TASK_STATUS_* are the plain strings "optional"/"required"/"cancelled"/"completed"/"failed".

Verified
  • Branch docs/mcp-v2-migration @ 177ffdd, base main @ 3f2ee2f (includes #4125, #4129). Diff: 12 files, +136/−46.
  • GitHub Actions on 177ffdd: CI success — Lint, MCP 1.x Compat, 11 unit legs; "Python: Integration Test" (all scope) still running at review time; mcp-1x scope absent from this run by construction (pull_request_target).
  • pytest tests_integ/mcp --collect-only: 2.1.1 → 13 collected, 0 errors; 1.29.1 → 48 collected, 0 errors.
  • mypy matrix above (both lines, warn_unused_ignores = true), plus the always_true/always_false experiment on an ignore-stripped copy.
  • Not re-run at this head: the unit suite in my own venvs — the sandbox filesystem failed partway; CI's green Lint / 1.x Compat / matrix stand in for it.
  • Evidence: type-ignore-matrix.md uploaded to the artifact store for this PR.
Questions (non-blocking)
  1. MCP_VERSIONS.md:30 says integration tests "split the same way", but the mcp-1x scope only takes effect after merge and the 1.x compat unit job runs no mypy. Do you want the 1.x line type-checked in CI, or is "1.x is best-effort, 2.x is the checked line" the intended posture? The answer decides whether the 18 1.x-side ignores are worth maintaining precisely.
  2. Still open from the first review, no change: is 2.x-by-default recorded anywhere (team/DECISIONS.md), and is there a tracked site/ follow-up under #1659 for the pages that teach streamablehttp_client / FastMCP?

Comment thread strands-py/pyproject.toml
Comment thread strands-py/src/strands/tools/mcp/mcp_client.py
Comment thread strands-py/src/strands/tools/mcp/mcp_client.py
Comment thread strands-py/src/strands/tools/mcp/mcp_client.py
Comment thread strands-py/src/strands/tools/mcp/mcp_client.py
Comment thread strands-py/src/strands/tools/mcp/mcp_instrumentation.py
Comment thread strands-py/src/strands/tools/mcp/mcp_client.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-mcp MCP related complexity/medium Touched functions have moderate cognitive complexity (11-25) 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.

3 participants