diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index e915a12b..4125aa3b 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -1087,6 +1087,11 @@ class FastMCP(Generic[LifespanResultT]): raise ValueError(str(e)) +# `Settings.lifespan` refers to FastMCP, which is only defined above; complete the model now so +# settings sources never see an unresolved annotation when a FastMCP instance is created. +Settings.model_rebuild() + + class StreamableHTTPASGIApp: """ ASGI application for Streamable HTTP server transport. diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py index b134489b..1cb1a55b 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -10,6 +10,7 @@ from starlette.routing import Mount, Route from mcp.server.fastmcp import Context, FastMCP from mcp.server.fastmcp.prompts.base import Message, UserMessage from mcp.server.fastmcp.resources import FileResource, FunctionResource +from mcp.server.fastmcp.server import Settings from mcp.server.fastmcp.utilities.types import Audio, Image from mcp.server.session import ServerSession from mcp.server.transport_security import TransportSecuritySettings @@ -31,6 +32,11 @@ if TYPE_CHECKING: from mcp.server.fastmcp import Context +def test_settings_model_is_complete_at_import(): + """The Settings model resolves its FastMCP annotation at import, so building one needs no deferred rebuild.""" + assert Settings.__pydantic_complete__ + + class TestServer: @pytest.mark.anyio async def test_create_server(self):