diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index e915a12bfd..9888832268 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -1087,6 +1087,13 @@ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) - raise ValueError(str(e)) +# `Settings.lifespan` forward-references `FastMCP`, which is defined after `Settings`, +# so the annotation is unresolved when the class body executes. Rebuild the model now +# that `FastMCP` exists, otherwise settings sources may fail to resolve the field and +# pydantic-settings >= 2.15 warns about the incomplete definition on instantiation. +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 b134489bc5..2f471c90bc 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -183,6 +183,21 @@ async def test_add_resource_decorator_incorrect_usage(self): def get_data(x: str) -> str: # pragma: no cover return f"Data: {x}" + def test_settings_model_is_fully_defined_after_import(self): + """Regression test for #3294: `Settings.lifespan` forward-references `FastMCP`, + which is defined later in the module, so `Settings` must be rebuilt once + `FastMCP` exists. An unresolved forward reference leaves the model incomplete: + settings sources may fail to resolve the field, and pydantic-settings >= 2.15 + warns (`IncompleteFieldDefinitionWarning`) on every `FastMCP()` construction. + """ + from typing import ForwardRef + + from mcp.server.fastmcp.server import Settings + + assert Settings.__pydantic_complete__ is True + lifespan = Settings.model_fields["lifespan"] + assert not isinstance(lifespan.annotation, ForwardRef) + class TestDnsRebindingProtection: """Tests for automatic DNS rebinding protection on localhost."""