Skip to content

Add mcp.types as a permanent alias for mcp_types - #3190

Merged
maxisbey merged 8 commits into
mainfrom
types-import-shim
Jul 27, 2026
Merged

Add mcp.types as a permanent alias for mcp_types#3190
maxisbey merged 8 commits into
mainfrom
types-import-shim

Conversation

@maxisbey

@maxisbey maxisbey commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Restores import mcp.types as a permanent alias that mirrors the mcp_types package.

Motivation and Context

import mcp.types as types is the most common v1 idiom (it's in the v1 README and nearly every example), and in v2 it fails with ModuleNotFoundError: 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-exports mcp_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, and import mcp followed by mcp.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 — and mcp.types is a package with a version submodule mirroring mcp_types.version the same way, so from mcp.types.version import LATEST_MODERN_VERSION works too:

  • A deprecation is a permanent commitment (a future removal) spent on a transient goal (the migration wave); the second break would cost every user again for no functional gain, since a star re-export can't drift.
  • The v1 spelling is baked into years of tutorials and generated code; making it work permanently absorbs that instead of fighting it forever.
  • It's a genuinely nice types-only namespace (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, and mcp exact-pins mcp-types so declaring it independently is pointless-to-harmful. So: depend on mcp → import mcp.types (one declared dependency, one import root); depend only on mcp-typesimport 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_typesmcp.types substitution best reviewed as its own commit) so SDK-user code no longer models the transitive-import leak; the hl_lines highlights 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 *Type unions, the TASK_* constants, …) raise an AttributeError from module __getattr__ that names the replacement on attribute access. AttributeError (per PEP 562) rather than a smuggled ImportError so hasattr() and getattr(x, name, default) still take their fallback path — probing code in v1/v2-straddling libraries doesn't crash. The tradeoff is that from 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 v2 mcp_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's mcp/__init__ imported the whole client/server stack, so v1's import mcp.types always paid for the full package — exactly as it does now — while mcp_types is 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.py prove the full mirror by identity across every exported name, the replacement-naming AttributeError, the preserved hasattr/getattr(default) contract for removed names, plain AttributeError for unknown names, and that from 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, and from mcp.types import *. Full suite: 100% coverage, strict-no-cover clean.

Breaking Changes

None — this softens an existing v2 break. Documented in docs/migration.md.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Two suppressions, both stating intent rather than hiding an error: a per-file-ignores entry for the wildcard import (F403) and a file-level # pyright: reportWildcardImportFromLibrary=false, since mirroring the whole mcp_types namespace is the point of the file and mcp-types is a separate distribution.

Open question for reviewers: mcp.shared.version was 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

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3190.mcp-python-docs.pages.dev
Deployment https://7b338f89.mcp-python-docs.pages.dev
Commit c27f95c
Triggered by @maxisbey
Updated 2026-07-27 22:34:58 UTC

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread docs/migration.md Outdated
@maxisbey maxisbey changed the title Add mcp.types deprecation shim over mcp_types Add mcp.types as a permanent alias for mcp_types Jul 27, 2026

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread docs/migration.md Outdated
Comment thread docs/migration.md Outdated
Comment thread src/mcp/types.py Outdated
Comment thread docs/migration.md Outdated

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

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 every mcp_types export, and the PEP 562 __getattr__ correctly preserves the hasattr/getattr(default) contract for the 22 removed names.
  • Checked mcp_types has no module __getattr__ of its own, so nothing is lost through the wildcard mirror (submodule attributes like version/jsonrpc were examined and ruled out as a gap).
  • Docs: no remaining "mcp.types no longer exists" claims in docs/migration.md or docs/whats-new.md; the docs-gen exclusion in gen_ref_pages.py only skips the alias page.
  • The import mcpmcp.types.X attribute 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.

Comment thread tests/test_types.py

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread docs/migration.md Outdated
@maxisbey
maxisbey force-pushed the types-import-shim branch from a79af79 to 90b6ba0 Compare July 27, 2026 21:40
Comment thread docs/migration.md Outdated
Comment thread src/mcp/types/__init__.py
maxisbey added 8 commits July 27, 2026 22:28
`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.
@maxisbey
maxisbey force-pushed the types-import-shim branch from f500fc1 to c27f95c Compare July 27, 2026 22:33
@maxisbey
maxisbey merged commit b7c9a91 into main Jul 27, 2026
36 checks passed
@maxisbey
maxisbey deleted the types-import-shim branch July 27, 2026 22:47
or veto a tool call:

```python title="server.py" hl_lines="18-25"
```python title="server.py" hl_lines="17-24"

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.

🟣 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_typesmcp.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.)

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.

1 participant