Add mcp.types as a permanent alias for mcp_types - #3190
Conversation
📚 Documentation preview
|
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/migration.md">
<violation number="1" location="docs/migration.md:180">
P3: The migration guide now implies that `mcp.shared.version` is the only removed `mcp.shared` import path, but `mcp.shared.progress` and `mcp.shared.session` are also removed and documented later on this page. This can send users of either path looking for a module that no longer exists; describe `mcp.shared.version` specifically without the exclusivity claim.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
No bugs found in the current revision — the earlier inline findings (stale doc statements, the name count) were against the deprecation-shim revision and are resolved by the switch to a permanent alias in 426fb79 — but this deliberately commits mcp.types as permanent public API surface, which is a maintainer policy call rather than something a bot should sign off on.
What was reviewed:
- The mirror in
src/mcp/types.py: star re-export plus explicit__all__gives identity with everymcp_typesexport, and the PEP 562__getattr__correctly preserves thehasattr/getattr(default)contract for the 22 removed names. - Checked
mcp_typeshas no module__getattr__of its own, so nothing is lost through the wildcard mirror (submodule attributes likeversion/jsonrpcwere examined and ruled out as a gap). - Docs: no remaining "mcp.types no longer exists" claims in
docs/migration.mdordocs/whats-new.md; the docs-gen exclusion ingen_ref_pages.pyonly skips the alias page. - The
import mcp→mcp.types.Xattribute idiom still isn't covered (noted earlier as a nit); it fails identically on main, so no regression.
Extended reasoning...
Overview
The PR adds src/mcp/types.py, a permanent alias module that star-re-exports the standalone mcp_types package, with a PEP 562 __getattr__ raising replacement-naming AttributeErrors for 22 names removed in v2. Supporting changes: a ruff F403 per-file ignore, an exclusion in the docs reference generator so the alias earns no duplicate API page, migration-guide and what's-new rewrites, and thorough tests covering the mirror-by-identity invariant, removed-name behavior, and unknown-attribute behavior.
Security risks
None. The module is a pure namespace re-export with no I/O, no auth surface, and no input handling. The two lint/type suppressions state intent (the wildcard mirror is the point of the file) rather than masking errors.
Level of scrutiny
The implementation is simple and I found no correctness issues: the star import plus explicit __all__ re-export guarantees every name is the same object (locked in by test_mcp_types_namespace_mirrors_mcp_types_exactly), mcp_types defines no module __getattr__ that the mirror could miss, and the AttributeError-not-ImportError choice for removed names is well reasoned and tested. However, the substance of this PR is an API-surface policy decision: restoring import mcp.types as a permanent commitment with no deprecation path, explicitly reversing the removal in #2973. The repo's own guidelines treat adding a compatibility surface as a deliberate design choice, and the PR description itself leaves an open question for reviewers (whether mcp.shared.version should get a sibling alias). That decision belongs to a human maintainer regardless of code quality.
Other factors
The current head (426fb79) resolves the issues flagged by the earlier review pass against the shim revision: the contradictory doc statements in docs/migration.md and docs/whats-new.md are gone, and the incorrect "23 names" count no longer appears (the prose now links to the table without stating a count). The one remaining gap from that pass — import mcp followed by mcp.types.X attribute access raising a bare AttributeError because nothing binds the submodule — is unchanged, but it fails identically on v2 main today, so the PR strictly improves the situation. Test coverage is strong and the docs were updated in the same PR as required.
There was a problem hiding this comment.
All reported issues were addressed across 168 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
a79af79 to
90b6ba0
Compare
`import mcp.types` is the most common v1 idiom, and in v2 it fails with ModuleNotFoundError, which reads as a broken install. Restore it as a compatibility shim that re-exports mcp_types and emits an MCPDeprecationWarning on first import, naming both breaks at first contact: the new module and the snake_case field renames. It is slated for removal in v3. The v1 names that no longer exist raise an ImportError naming the replacement, rather than a bare "cannot import name". The shim is excluded from the generated API reference and documented in the migration guide.
A deprecation is a permanent commitment (a v3 removal) spent on a transient goal (nudging the v1->v2 migration wave), and the second break would cost every remaining user again for no functional gain: the module is a star re-export that cannot drift. `import mcp.types as types` is also baked into years of tutorials and generated code, and is a genuinely clean types-only namespace for SDK users, while `mcp_types` remains the spelling that installs without the SDK stack. So drop the MCPDeprecationWarning and every "removed in v3" phrase. Removed names now raise AttributeError from `__getattr__` as PEP 562 requires, keeping hasattr() and getattr(name, default) on their fallback paths; the tradeoff is that `from mcp.types import <removed>` falls back to CPython's generic ImportError, which is still fail-fast. Fix two doc claims that still said mcp.types no longer exists.
v1's mcp/__init__ ran `from .types import ...`, which bound the `types` submodule on the package, so `import mcp` followed by `mcp.types.Tool` worked in every v1 release. With the module split out, nothing imported it eagerly, so that idiom raised `AttributeError: module 'mcp' has no attribute 'types'` and contradicted the guide's claim that v1 import lines need no change. Import the submodule from the package init, as v1 did, so all four v1 idioms hold. Also reword the migration note so `mcp.shared.version` is not implied to be the only removed `mcp.shared` import path.
Every distribution whose modules a project imports should be a declared dependency, and `mcp` exact-pins `mcp-types` such that declaring `mcp-types` independently is pointless-to-harmful. So code that depends on `mcp` should import through `mcp.types` (one declared dependency, one import root), and `mcp_types` is the spelling only for projects that install `mcp-types` without the SDK. Previously the examples and docs imported `mcp_types` directly, modeling exactly the transitive-import leak this rule forbids. To let the sweep drop every direct `mcp_types` import, `mcp.types` is now a package: `types/__init__.py` (the existing mirror) plus a `types/version.py` mirror of `mcp_types.version` (star re-export, so every name is the same object), and `mcp_types.version` gains an explicit `__all__` so its typing import does not leak through the mirror. A parity test pins the version mirror. Rewrite the type imports in every example, snippet, and docs tutorial, remap the `hl_lines` highlights that the import reordering shifted, and restate the migration guide / What's New / installation docs around the "import through the package you depend on" rule.
Main now declares the supported mcp_types import surface as the package plus its jsonrpc, methods, and version submodules, and the "import through the package you depend on" rule only holds if every one of those has an mcp.types spelling. Otherwise an SDK user who needs CACHEABLE_METHODS is straight back to a transitive import. Add mcp.types.jsonrpc and mcp.types.methods mirrors alongside .version, give mcp_types.jsonrpc an explicit __all__ so the mirror is precise, and bind all three submodules on the mcp.types package so `mcp.types.version.X` is attribute-reachable exactly like `mcp_types.version.X` (previously only the from-import form worked). Pin the whole mirror set with one parametrized parity test, and drop the tautological __all__ equality (the mirror shares the source list object by construction) plus a sed artifact in the migration guide.
Every other subprocess.run in the suite passes timeout=20 so a hung child fails the test instead of stalling the run until the CI job timeout; match that convention here. No-Verification-Needed: test-only change
The import reshuffle in the callbacks tutorial moved `mode="legacy"` off line 17, so the admonition pointing at "line 17" now named the wrong line. Refer to the argument by where it lives (the `Client(...)` call) instead of by line number, so it can't drift again. No-Verification-Needed: doc-prose-only change
The _REMOVED map and module __getattr__ intercepted the v1 names that no longer exist to name their replacement in the error. That is extra mechanism to maintain for a one-time migration nudge; a name that does not exist should simply fail to import like any other. Remove it and its tests, leaving mcp.types as a plain star mirror. The migration guide's removed-names table still names each replacement.
f500fc1 to
c27f95c
Compare
| or veto a tool call: | ||
|
|
||
| ```python title="server.py" hl_lines="18-25" | ||
| ```python title="server.py" hl_lines="17-24" |
There was a problem hiding this comment.
🟣 Pre-existing (not introduced by this PR): docs/advanced/apps.md:23 has hl_lines="18 21 29 31", but all four numbers point at blank lines in docs_src/apps/tutorial001.py — the four "moves" the page's prose enumerates sit exactly one line below each. Since this PR's hl_lines sweep fixed every other page in this class (apps.md was skipped only because its tutorial had no mcp_types import to reorder), it's a natural one-line rider: change the spec to hl_lines="19 22 30 32".
Extended reasoning...
What the bug is. docs/advanced/apps.md line 23 reads:
```python title="server.py" hl_lines="18 21 29 31"
--8<-- "docs_src/apps/tutorial001.py"All four highlighted line numbers land on blank lines in docs_src/apps/tutorial001.py. Verified against the working tree with cat -n:
| Highlighted (blank) | Intended (one line below) |
|---|---|
| 18 | 19: apps = Apps() |
| 21 | 22: @apps.tool(resource_uri="ui://clock/app.html", ...) |
| 29 | 30: apps.add_html_resource("ui://clock/app.html", CLOCK_HTML, title="Clock") |
| 31 | 32: mcp = MCPServer("clock", extensions=[apps]) |
The intended targets are unambiguous: they are exactly the four "moves" the page's own prose enumerates immediately below the code block (Apps(), @apps.tool(resource_uri=...), add_html_resource, extensions=[apps]). The spec is uniformly off by one low; the fix is hl_lines="19 22 30 32".
How it manifests. mkdocs-material renders hl_lines as highlighted rows in the included snippet. As shipped, the page highlights four empty rows and none of the four lines the surrounding prose tells the reader to look at — a visible rendering defect on the published Apps page, though nothing functional breaks.
Why this PR didn't fix it (and why it's pre-existing). This PR reorders mcp_types → mcp.types imports across ~145 docs/example files and mechanically remaps every hl_lines spec that the import reshuffling shifted. docs_src/apps/tutorial001.py imports nothing from mcp_types, so the sweep never touched it — neither docs/advanced/apps.md nor its tutorial appears in this PR's diff. The off-by-one predates the PR (introduced in commit 48ef569). It's surfaced here only because it is the one remaining hl_lines defect in the docs tree after this PR's sweep: every remap the PR did make was verified content-identical (old spec on old source vs. new spec on new source highlight the same lines, the only difference being the two swapped-but-still-highlighted import lines in the handling_errors tutorial).
Step-by-step proof. (1) Open docs/advanced/apps.md:23 — the fence carries hl_lines="18 21 29 31". (2) Run cat -n docs_src/apps/tutorial001.py: lines 18, 21, 29, and 31 are empty; lines 19, 22, 30, and 32 hold apps = Apps(), the @apps.tool(resource_uri=...) decorator, apps.add_html_resource(...), and mcp = MCPServer("clock", extensions=[apps]) respectively. (3) Read the prose under the block — it walks through exactly those four constructs as the page's "four moves". (4) Render the page: four blank highlighted rows, zero of the four moves highlighted.
Fix. One-line change in docs/advanced/apps.md:23: hl_lines="18 21 29 31" → hl_lines="19 22 30 32". It fits naturally into this PR (whose third commit is precisely an hl_lines-remap sweep), or as a trivial follow-up.
Severity. Pre-existing and cosmetic — a docs rendering issue in a file this PR does not touch. It must not block merge; it's noted here only because this PR fixed every other instance of the same defect class. (Anchored to the nearest in-diff hl_lines hunk in docs/advanced/extensions.md because the actual defect's file is not in the diff.)
Restores
import mcp.typesas a permanent alias that mirrors themcp_typespackage.Motivation and Context
import mcp.types as typesis the most common v1 idiom (it's in the v1 README and nearly every example), and in v2 it fails withModuleNotFoundError: No module named 'mcp.types'— which reads as a broken install rather than a migration step. #2973 removed the module without a shim.This adds
src/mcp/types.py, which star-re-exportsmcp_types, so every name is the same object and all four v1 idioms work again:import mcp.types as types,from mcp.types import X,from mcp import types, andimport mcpfollowed bymcp.types.Tool(the package init binds the submodule, as v1's did). It ships as a permanent alias, not a deprecation shim — no warning, no v3 removal — andmcp.typesis a package with aversionsubmodule mirroringmcp_types.versionthe same way, sofrom mcp.types.version import LATEST_MODERN_VERSIONworks too:types.TextContent(...), one import). And the two spellings map to a real packaging rule, not a preference: every distribution whose modules you import should be a declared dependency, andmcpexact-pinsmcp-typesso declaring it independently is pointless-to-harmful. So: depend onmcp→ importmcp.types(one declared dependency, one import root); depend only onmcp-types→import mcp_types.The examples and docs now follow that rule. The third commit sweeps every example, snippet, and docs tutorial (~145 files, a mechanical
mcp_types→mcp.typessubstitution best reviewed as its own commit) so SDK-user code no longer models the transitive-import leak; thehl_lineshighlights that the import reordering shifted were remapped, and the migration guide, What's New, and installation docs restate the rule.Of v1's 212 top-level names, 173 resolve unchanged. The 22 genuinely removed ones (
Content,ResourceReference,Cursor, the*Typeunions, theTASK_*constants, …) raise anAttributeErrorfrom module__getattr__that names the replacement on attribute access.AttributeError(per PEP 562) rather than a smuggledImportErrorsohasattr()andgetattr(x, name, default)still take their fallback path — probing code in v1/v2-straddling libraries doesn't crash. The tradeoff is thatfrom mcp.types import <removed>falls back to CPython's generic "cannot import name", which is still fail-fast. The remaining 17 "gone" names were stdlib/pydantic imports (Any,BaseModel,datetime, …) that leaked out of v1's import block and were never API.Import cost is a wash and the alias itself is free. Cold-import benchmarks (median of 15 subprocesses): v1
mcp.types≈ 374ms vs v2mcp_types≈ 391ms marginal over pydantic — both dominated by building ~150 pydantic models. The alias body adds ~250µs, and nothing at all unless imported. No regression versus v1 either: v1'smcp/__init__imported the whole client/server stack, so v1'simport mcp.typesalways paid for the full package — exactly as it does now — whilemcp_typesis the strictly cheaper path v2 makes newly possible.The module is excluded from the generated API reference; the migration guide and What's New pages are updated to match.
How Has This Been Tested?
New tests in
tests/test_types.pyprove the full mirror by identity across every exported name, the replacement-namingAttributeError, the preservedhasattr/getattr(default)contract for removed names, plainAttributeErrorfor unknown names, and thatfrom mcp.types import <removed>still raises. Also driven end-to-end from a fresh process at the package boundary: all four import idioms (silent, all 211 names identical objects), removed-name access,hasattr/getattr(default)fallbacks, andfrom mcp.types import *. Full suite: 100% coverage,strict-no-coverclean.Breaking Changes
None — this softens an existing v2 break. Documented in
docs/migration.md.Types of changes
Checklist
Additional context
Two suppressions, both stating intent rather than hiding an error: a
per-file-ignoresentry for the wildcard import (F403) and a file-level# pyright: reportWildcardImportFromLibrary=false, since mirroring the wholemcp_typesnamespace is the point of the file andmcp-typesis a separate distribution.Open question for reviewers:
mcp.shared.versionwas removed in the same PR (#2973) and could get an identical sibling alias →mcp_types.version. Left out to keep this focused; happy to add.AI Disclaimer