Skip to content

Commit 16a9d56

Browse files
committed
Add mcp.types deprecation shim over mcp_types
`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.
1 parent 45f2a88 commit 16a9d56

5 files changed

Lines changed: 131 additions & 3 deletions

File tree

docs/migration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Every section heading below names the API it affects, so searching this page for
1414
|---|---|---|
1515
| `FastMCP` renamed to `MCPServer` | `ModuleNotFoundError: No module named 'mcp.server.fastmcp'` | [`FastMCP` renamed](#fastmcp-renamed-to-mcpserver) |
1616
| Fields renamed from camelCase to snake_case | `AttributeError: 'Tool' object has no attribute 'inputSchema'` | [snake_case fields](#field-names-changed-from-camelcase-to-snake_case) |
17-
| `mcp.types` moved to the `mcp-types` package | `ModuleNotFoundError: No module named 'mcp.types'` | [`mcp.types` moved](#mcptypes-moved-to-the-mcp-types-package) |
17+
| `mcp.types` moved to the `mcp-types` package | `MCPDeprecationWarning: mcp.types is deprecated; import from mcp_types.` | [`mcp.types` moved](#mcptypes-moved-to-the-mcp-types-package) |
1818
| `McpError` renamed to `MCPError` | `ImportError: cannot import name 'McpError' from 'mcp'` | [`McpError` renamed](#mcperror-renamed-to-mcperror) |
1919
| Resource URIs are `str`, not `AnyUrl` | `AttributeError: 'str' object has no attribute 'host'` | [URI type](#resource-uri-type-changed-from-anyurl-to-str) |
2020
| `streamablehttp_client` removed | `ImportError: cannot import name 'streamablehttp_client'` | [`streamablehttp_client`](#streamablehttp_client-removed) |
@@ -172,8 +172,16 @@ The protocol wire types now live in a standalone distribution, `mcp-types`, impo
172172
`mcp_types`. Its only runtime dependencies are `pydantic` and `typing-extensions`, so code
173173
that just needs to (de)serialize MCP traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
174174
continues to re-export the type names at the top level, so `from mcp import Tool` is
175-
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed. The
176-
package's API reference is at [`mcp_types`](api/mcp_types/index.md).
175+
unchanged. `mcp.shared.version` was removed. The package's API reference is at
176+
[`mcp_types`](api/mcp_types/index.md).
177+
178+
`import mcp.types` still works in v2, as a compatibility shim that mirrors `mcp_types` and
179+
emits an `MCPDeprecationWarning` on first import; it will be removed in v3, so switch to
180+
`mcp_types` now. The shim only restores the import: the field renames below still apply, so
181+
code that imports through it can go on to fail on camelCase attribute access. The 23 names
182+
that no longer exist (listed under
183+
[Removed type aliases and classes](#removed-type-aliases-and-classes)) raise an
184+
`ImportError` from the shim that names their replacement.
177185

178186
**Why:** keeping the wire types in their own package lets tooling and lightweight clients
179187
depend on the protocol schema without pulling in `httpx2`, `starlette`, `uvicorn`, and the

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ max-complexity = 24 # Default is 10
217217

218218
[tool.ruff.lint.per-file-ignores]
219219
"__init__.py" = ["F401"]
220+
# Deprecated compat shim that mirrors the whole mcp_types namespace by design.
221+
"src/mcp/types.py" = ["F403"]
220222
# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output (TID251 lifts the repo-wide RootModel ban for these generated validators).
221223
"src/mcp-types/mcp_types/v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"]
222224
"tests/server/mcpserver/test_func_metadata.py" = ["E501"]

scripts/docs/gen_ref_pages.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
# it from `src/` would emit the unimportable `mcp-types.mcp_types.*`.
3131
PACKAGES = (ROOT / "src" / "mcp", ROOT / "src" / "mcp-types" / "mcp_types")
3232

33+
# Deprecated compatibility shims: they mirror another module's namespace and
34+
# are documented in the migration guide, so they earn no API page of their own.
35+
EXCLUDED = frozenset({"mcp.types"})
36+
3337
_KIND_SECTIONS = {
3438
griffe.Kind.MODULE: "Modules",
3539
griffe.Kind.CLASS: "Classes",
@@ -185,6 +189,8 @@ def generate() -> list[NavItem]:
185189
continue
186190

187191
ident = ".".join(parts)
192+
if ident in EXCLUDED:
193+
continue
188194
documented.add(ident)
189195
stubs[API_DIR / doc_path] = _stub(parts[-1], f"::: {ident}")
190196
pages[ident] = API_DIR / doc_path

src/mcp/types.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Deprecated: the protocol types moved to the standalone `mcp_types` package.
2+
3+
`import mcp.types` still works in v2 as a compatibility shim, but it emits an
4+
`MCPDeprecationWarning` and will be removed in v3. Import from `mcp_types` instead:
5+
6+
from mcp_types import Tool, TextContent
7+
8+
Field names also changed from camelCase to snake_case (`tool.input_schema`, not
9+
`tool.inputSchema`), so an import that succeeds through this shim can still fail
10+
later on attribute access. See the migration guide, "Types and wire format".
11+
"""
12+
13+
# A wildcard mirror of the mcp_types namespace is the whole point of this shim.
14+
# pyright: reportWildcardImportFromLibrary=false
15+
16+
import warnings
17+
18+
from mcp_types import *
19+
from mcp_types import __all__ as __all__
20+
21+
from mcp.shared.exceptions import MCPDeprecationWarning
22+
23+
warnings.warn(
24+
"mcp.types is deprecated; import from mcp_types. Fields are now snake_case; see the migration guide.",
25+
MCPDeprecationWarning,
26+
stacklevel=2,
27+
)
28+
29+
# Names v1's mcp.types exposed that no longer exist. A bare "cannot import name"
30+
# leaves people grepping; naming the replacement finishes the migration step.
31+
_REMOVED = {
32+
"Content": "use ContentBlock",
33+
"ResourceReference": "use ResourceTemplateReference",
34+
"Cursor": "use str",
35+
"AnyFunction": "use Callable[..., Any]",
36+
"MethodT": "it was an internal TypeVar, not public API",
37+
"RequestParamsT": "it was an internal TypeVar, not public API",
38+
"NotificationParamsT": "it was an internal TypeVar, not public API",
39+
"ClientRequestType": "the union is now the bare name ClientRequest",
40+
"ClientNotificationType": "the union is now the bare name ClientNotification",
41+
"ClientResultType": "the union is now the bare name ClientResult",
42+
"ServerRequestType": "the union is now the bare name ServerRequest",
43+
"ServerNotificationType": "the union is now the bare name ServerNotification",
44+
"ServerResultType": "the union is now the bare name ServerResult",
45+
"TaskExecutionMode": "use the string literals directly",
46+
"TASK_FORBIDDEN": "use the string literal 'forbidden'",
47+
"TASK_OPTIONAL": "use the string literal 'optional'",
48+
"TASK_REQUIRED": "use the string literal 'required'",
49+
"TASK_STATUS_CANCELLED": "use the string literal 'cancelled'; TaskStatus remains",
50+
"TASK_STATUS_COMPLETED": "use the string literal 'completed'; TaskStatus remains",
51+
"TASK_STATUS_FAILED": "use the string literal 'failed'; TaskStatus remains",
52+
"TASK_STATUS_INPUT_REQUIRED": "use the string literal 'input_required'; TaskStatus remains",
53+
"TASK_STATUS_WORKING": "use the string literal 'working'; TaskStatus remains",
54+
}
55+
56+
57+
def __getattr__(name: str) -> object:
58+
if (hint := _REMOVED.get(name)) is not None:
59+
# ImportError, not AttributeError: `from mcp.types import X` swallows an
60+
# AttributeError raised here and reports a generic "cannot import name",
61+
# discarding the hint; an ImportError propagates through both the
62+
# from-import and plain attribute-access paths with the message intact.
63+
raise ImportError(f"mcp.types.{name} was removed in v2; {hint}. See the migration guide.")
64+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

tests/test_types.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import importlib
2+
import sys
3+
from types import ModuleType
14
from typing import Any
25

6+
import mcp_types
37
import pytest
48
from inline_snapshot import snapshot
59
from mcp_types import (
@@ -38,6 +42,9 @@
3842
)
3943
from pydantic import ValidationError
4044

45+
import mcp
46+
from mcp import MCPDeprecationWarning
47+
4148

4249
@pytest.mark.anyio
4350
async def test_jsonrpc_request():
@@ -445,3 +452,44 @@ def test_input_required_result_requires_at_least_one_of_input_requests_or_reques
445452
with pytest.raises(ValidationError):
446453
InputRequiredResult(input_requests={})
447454
assert InputRequiredResult(request_state="s").input_requests is None
455+
456+
457+
def _import_deprecated_mcp_types_shim() -> ModuleType:
458+
"""Re-import the `mcp.types` compatibility shim so its import-time warning fires again."""
459+
sys.modules.pop("mcp.types", None)
460+
with pytest.warns(MCPDeprecationWarning) as record:
461+
module = importlib.import_module("mcp.types")
462+
assert str(record[0].message) == snapshot(
463+
"mcp.types is deprecated; import from mcp_types. Fields are now snake_case; see the migration guide."
464+
)
465+
return module
466+
467+
468+
def test_deprecated_mcp_types_import_warns_and_mirrors_mcp_types():
469+
"""SDK-defined: the v1 `mcp.types` module is a warning shim that mirrors `mcp_types` exactly."""
470+
module = _import_deprecated_mcp_types_shim()
471+
assert module.__all__ == mcp_types.__all__
472+
assert module.Tool is mcp_types.Tool
473+
# Importing the submodule binds it on the package, so v1's `from mcp import types`
474+
# idiom resolves to the same shim module.
475+
assert getattr(mcp, "types") is module
476+
477+
478+
def test_deprecated_mcp_types_names_the_replacement_for_a_name_removed_in_v2():
479+
"""SDK-defined: a v1 name that no longer exists raises an error naming its v2 replacement."""
480+
module = _import_deprecated_mcp_types_shim()
481+
# ImportError rather than AttributeError so `from mcp.types import Content` keeps this
482+
# message: the from-import path replaces an AttributeError with a generic one.
483+
with pytest.raises(ImportError) as exc_info:
484+
getattr(module, "Content")
485+
assert str(exc_info.value) == snapshot(
486+
"mcp.types.Content was removed in v2; use ContentBlock. See the migration guide."
487+
)
488+
489+
490+
def test_deprecated_mcp_types_unknown_attribute_raises_attribute_error():
491+
"""SDK-defined: an unknown attribute is a normal AttributeError, so `hasattr` still works."""
492+
module = _import_deprecated_mcp_types_shim()
493+
with pytest.raises(AttributeError):
494+
getattr(module, "not_a_protocol_type")
495+
assert not hasattr(module, "not_a_protocol_type")

0 commit comments

Comments
 (0)