|
| 1 | +"""The removed v1 import path `mcp.server.fastmcp` fails with a pointer to the migration guide.""" |
| 2 | + |
| 3 | +import importlib |
| 4 | +import sys |
| 5 | + |
| 6 | +import pytest |
| 7 | +from inline_snapshot import snapshot |
| 8 | + |
| 9 | +import mcp.server |
| 10 | +from mcp.server.mcpserver import MCPServer |
| 11 | + |
| 12 | + |
| 13 | +def test_importing_fastmcp_raises_module_not_found_that_points_at_the_migration_guide() -> None: |
| 14 | + """SDK-defined: the v1 path fails with the same exception type and `.name` as a module |
| 15 | + that genuinely does not exist, but the message names the replacement and the guide.""" |
| 16 | + with pytest.raises(ModuleNotFoundError) as exc_info: |
| 17 | + importlib.import_module("mcp.server.fastmcp") |
| 18 | + |
| 19 | + assert exc_info.value.name == "mcp.server.fastmcp" |
| 20 | + assert str(exc_info.value) == snapshot( |
| 21 | + "No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer " |
| 22 | + "(from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at " |
| 23 | + "https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver " |
| 24 | + "or pin 'mcp<2' to keep running v1 code." |
| 25 | + ) |
| 26 | + # A module that raises while executing is never cached, so nothing is left behind. |
| 27 | + assert "mcp.server.fastmcp" not in sys.modules |
| 28 | + assert not hasattr(mcp.server, "fastmcp") |
| 29 | + |
| 30 | + |
| 31 | +def test_importing_a_fastmcp_submodule_raises_the_parent_pointer() -> None: |
| 32 | + """SDK-defined: a deep v1 path executes `mcp.server.fastmcp` first, so it fails with that |
| 33 | + module's message and `.name` rather than a bare error for the leaf.""" |
| 34 | + with pytest.raises(ModuleNotFoundError) as parent: |
| 35 | + importlib.import_module("mcp.server.fastmcp") |
| 36 | + with pytest.raises(ModuleNotFoundError) as exc_info: |
| 37 | + importlib.import_module("mcp.server.fastmcp.utilities.types") |
| 38 | + |
| 39 | + assert exc_info.value.name == "mcp.server.fastmcp" |
| 40 | + assert str(exc_info.value) == str(parent.value) |
| 41 | + |
| 42 | + |
| 43 | +def test_v1_first_import_shim_falls_back_to_mcpserver() -> None: |
| 44 | + """SDK-defined: projects that support both majors try the v1 import and fall back on |
| 45 | + `ModuleNotFoundError` (the narrowest guard seen in the wild), which is why the pointer is |
| 46 | + raised as exactly that type and not as a bare `ImportError` or after a warning.""" |
| 47 | + fell_back = False |
| 48 | + try: |
| 49 | + server_class: type = importlib.import_module("mcp.server.fastmcp").FastMCP |
| 50 | + except ModuleNotFoundError: |
| 51 | + fell_back = True |
| 52 | + server_class = MCPServer |
| 53 | + |
| 54 | + assert fell_back |
| 55 | + assert server_class is MCPServer |
0 commit comments