Skip to content

feat(mcp): move MCP implementation to strands.mcp, deprecate old path (2/3)#2152

Open
fede-kamel wants to merge 2 commits intostrands-agents:mainfrom
fede-kamel:feat/move-mcp-impl
Open

feat(mcp): move MCP implementation to strands.mcp, deprecate old path (2/3)#2152
fede-kamel wants to merge 2 commits intostrands-agents:mainfrom
fede-kamel:feat/move-mcp-impl

Conversation

@fede-kamel
Copy link
Copy Markdown

@fede-kamel fede-kamel commented Apr 17, 2026

Strategy: 3-PR stack for a large refactor

Issue #1431 asks for two things at once: relocating the MCP package AND preserving backwards compatibility. An atomic implementation of both is ~2000 lines of changes (mostly because git can't detect a rename when both paths must keep working). Instead, the work is split into three stacked PRs:

Step PR What it does Size
1 / 3 #2148 Introduce strands.mcp as a pure re-export of strands.tools.mcp. Additive only. +42 / -0
2 / 3 #2152 (this PR) Move implementation to strands.mcp via git mv; strands.tools.mcp becomes a sys.modules-based deprecation shim. +38 / -28 (git -M)
3 / 3 #2158 Move tests, add backcompat-aliases suite, update tests_integ/mcp, README, and AGENTS.md. +149 / -63

Each step leaves main in a valid state, is independently revertable, and is independently reviewable. The order matters:

  • Step 1 must precede this one so that strands.mcp is part of the public API before the implementation moves.
  • Step 3 must follow this one because the integration tests and docs can't reference the new canonical path until the code actually lives there.

Why sys.modules aliasing instead of per-module stub files?

A naive implementation would create 5 per-submodule stub .py files at src/strands/tools/mcp/ (each doing from strands.mcp.<name> import * and emitting a DeprecationWarning). That's semantically identical but has two costs:

  1. git can't see the move as a rename. Both paths end up with a file, so git reports "modified at old path" + "new file at new path", inflating the diff to ~2000 lines for a 1200-line module crossing the boundary. label-size fails as advisory.
  2. Multiple warnings per process. Each submodule emits its own DeprecationWarning.

Instead, this PR registers all legacy submodule paths in sys.modules from a single strands/tools/mcp/__init__.py:

for name in ("mcp_agent_tool", "mcp_client", "mcp_instrumentation", "mcp_tasks", "mcp_types"):
    sys.modules[f"strands.tools.mcp.{name}"] = getattr(_mcp, name)

This means:

  • The legacy submodule .py files can be deleted — git detects the 5 moves as pure renames and the diff collapses to just the import-path adjustments inside the moved files.
  • One DeprecationWarning per process (at first legacy import), pointing users to the new path.
  • from strands.tools.mcp.mcp_client import X still resolves: Python imports strands.tools.mcp first (running the shim __init__.py, which registers the alias), then finds strands.tools.mcp.mcp_client in sys.modules.

The full ~1800 lines of implementation still physically move across the package boundary — git's rename detection just displays the move honestly as renames instead of add+delete.


Summary

Second of three stacked PRs for #1431. Depends on #2148.

Moves the MCP implementation files from strands.tools.mcp into strands.mcp using git-tracked renames. strands.tools.mcp becomes a deprecation alias that:

  1. Re-exports the public API from strands.mcp (object identity preserved).
  2. Emits a DeprecationWarning at package-import time.
  3. Registers legacy submodule paths in sys.modules so from strands.tools.mcp.mcp_client import X continues to resolve.

What this PR does

  • git mv for 5 submodule files (mcp_client.py, mcp_agent_tool.py, mcp_types.py, mcp_tasks.py, mcp_instrumentation.py) from src/strands/tools/mcp/src/strands/mcp/.
  • Updates relative imports inside the moved files (3-dot → 2-dot).
  • Rewrites src/strands/mcp/__init__.py to use local imports (replacing the pure re-export from feat(mcp): add strands.mcp as canonical MCP import path (1/3) #2148).
  • Rewrites src/strands/tools/mcp/__init__.py as a sys.modules-aliasing deprecation shim.

Backwards-compat guarantees (verified E2E)

  • from strands.tools.mcp import MCPClient → works, emits DeprecationWarning.
  • strands.tools.mcp.MCPClient is strands.mcp.MCPClientTrue.
  • from strands.tools.mcp.mcp_client import MCPClient → works via sys.modules alias; same object as new path.
  • from strands.tools.mcp import mcp_client → works via sys.modules alias.

Tests run locally (Python 3.13)

Check Command Result
Full unit suite hatch test --python 3.13 tests/ 2614 passed
Lint hatch fmt --linter --check ruff clean
Type check mypy ./src via hatch-static-analysis Success, 143 source files
MCP-scoped suite hatch test tests/strands/mcp/ tests/strands/tools/ 464 passed
E2E new path vs real stdio MCP server python demo_new_path.py — spawns tests_integ/mcp/echo_server.py 0 DeprecationWarnings; echo + get_weather called
E2E legacy path vs real stdio MCP server python demo_backcompat.py 1 DeprecationWarning; identity check passed; submodule-path imports resolve via sys.modules
E2E legacy path vs public awslabs.aws-documentation-mcp-server python demo_aws_docs_backcompat.py (spawned via uvx) live HTTP to proxy.search.docs.aws.com; real AWS Lambda docs returned
Agent + MCP integration (new path, non-Bedrock model via API key) ad-hoc smoke script ✅ agent invoked echo tool, returned expected string
Agent + MCP integration (legacy path, non-Bedrock model via API key) ad-hoc smoke script ✅ same flow, 1 DeprecationWarning at import

Refs #1431

Introduces a top-level strands.mcp package that re-exports the public
API from strands.tools.mcp. Object identity is preserved, so users can
migrate `from strands.tools.mcp import X` to `from strands.mcp import X`
today with no behavior change.

This is the first of two steps for strands-agents#1431. A follow-up will move the
implementation into strands.mcp and convert strands.tools.mcp into a
deprecated alias.

Refs strands-agents#1431
Moves the MCP implementation from strands.tools.mcp to strands.mcp using
git-tracked renames. strands.tools.mcp becomes a deprecation alias:

- Emits a DeprecationWarning on import pointing to strands.mcp.
- Re-exports the public API (object identity preserved).
- Registers legacy submodule paths (strands.tools.mcp.mcp_client, etc.)
  via sys.modules so ``from strands.tools.mcp.mcp_client import X``
  continues to resolve to the canonical modules.

Follow-up PR will move the tests into tests/strands/mcp, update
integration tests, README, and AGENTS.md.

Refs strands-agents#1431
fede-kamel added a commit to fede-kamel/sdk-python that referenced this pull request Apr 18, 2026
Third and final step for strands-agents#1431. Depends on strands-agents#2152.

- Move tests from tests/strands/tools/mcp/ to tests/strands/mcp/ as
  git-tracked renames with import updates (strands.tools.mcp -> strands.mcp).
- Add tests/strands/tools/mcp/test_deprecated_aliases.py covering the
  DeprecationWarning, public-API identity, and sys.modules submodule
  aliasing for legacy import paths.
- Update the canonical-import-path test to reflect the post-move reality
  (strands.mcp is canonical; strands.tools.mcp is the alias).
- Update tests_integ/mcp/ imports to the new canonical path.
- Update README.md (MCP example) and AGENTS.md (TasksConfig snippet).
- Update tests/strands/tools/test_registry.py import.

Refs strands-agents#1431
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant