[v1.x] Rebuild FastMCP Settings once FastMCP is defined - #3295
[v1.x] Rebuild FastMCP Settings once FastMCP is defined#3295claude[bot] wants to merge 1 commit into
Conversation
Settings.lifespan forward-references FastMCP, which is defined later in the module, so the annotation was still an unresolved ForwardRef after import and the model stayed incomplete. Settings sources could fail to resolve the field, and pydantic-settings >= 2.15 emits IncompleteFieldDefinitionWarning on every FastMCP() construction, which breaks consumers that promote warnings to errors. Rebuild the Settings model at module scope once FastMCP exists, and add a regression test asserting the model is fully defined after import. Github-Issue: #3294 Reported-by: igorkorsunsky
There was a problem hiding this comment.
LGTM — a one-line Settings.model_rebuild() at module scope with a regression test, fixing the incomplete-model warning from pydantic-settings >= 2.15.
What was reviewed
- The rebuild's placement: it runs after
FastMCPis defined in the same module, so thelifespanforward reference resolves from module globals; all otherSettingsfield types are imported at the top of the file and resolvable at that point. - Checked that resolving the annotation doesn't change validation for existing users —
tests/server/test_lifespan.pyandtests/issues/test_355_type_error.pyconstructFastMCPwith real lifespan callables and continue to exercise the field. - The regression test asserts
__pydantic_complete__and that the annotation is no longer aForwardRef, which fails on the unpatched branch per the PR description.
Extended reasoning...
Overview
The PR touches two files: src/mcp/server/fastmcp/server.py gains a single Settings.model_rebuild() call at module scope (after the FastMCP class definition, with an explanatory comment), and tests/server/fastmcp/test_server.py gains a regression test asserting the model is complete after import. This resolves the Settings.lifespan forward reference to FastMCP, which is defined after Settings in the module, eliminating the IncompleteFieldDefinitionWarning emitted by pydantic-settings >= 2.15 on every FastMCP() construction.
Security risks
None. The change does not alter any runtime logic, auth, transport, or validation semantics — it only completes a type annotation that pydantic previously left unresolved. No user input paths are affected.
Level of scrutiny
Low-to-moderate. model_rebuild() is the canonical pydantic pattern for exactly this situation (a model forward-referencing a class defined later in the same module). The call site is correct: it executes after FastMCP exists, and every other field type on Settings (AuthSettings, TransportSecuritySettings, LifespanResultT, etc.) is imported at the top of the module, so the rebuild cannot fail at import time. The one behavioral question — whether resolving Callable[[FastMCP[...]], ...] | None could newly reject values previously accepted under the unresolved ForwardRef — is covered by existing tests that pass real lifespan callables through the constructor (tests/server/test_lifespan.py, tests/issues/test_355_type_error.py, tests/issues/test_1027_win_unreachable_cleanup.py), plus the 85-test file run reported in the PR description.
Other factors
The bug hunting system found no issues. The PR includes a targeted regression test that fails on the unpatched branch, and the description documents reproduction against the published mcp==1.29.0 wheel with pydantic-settings 2.15.0. The timeline has no outstanding reviewer comments. This is a small, self-contained, well-verified fix following an established pattern — suitable for approval without requiring human review.
Requested by Felix Weinberger · Slack thread
Fixes #3294
Problem
Settings.lifespanforward-referencesFastMCP, which is defined later inmcp.server.fastmcp.server, so when theSettingsclass body executes the annotation is an unresolvedForwardRefand the model stays incomplete after import (Settings.__pydantic_complete__ is False).Consequences:
IncompleteFieldDefinitionWarningon everyFastMCP()construction (added in pydantic-settings#901). Consumers that promote warnings to errors — a common test-suite configuration — can no longer construct a server at all.Fix
Rebuild the
Settingsmodel at module scope onceFastMCPexists, mirroring the reporter's verified workaround but inside the SDK where it belongs. One line plus an explanatory comment; no behavior change for resolved fields.Verification
test_settings_model_is_fully_defined_after_importasserts the model is complete and thelifespanannotation is no longer aForwardRef. It fails on unpatchedv1.x(Settings.__pydantic_complete__ is False) and passes with the fix.mcp==1.29.0wheel withpydantic-settings==2.15.0:FastMCP("test")raisesIncompleteFieldDefinitionWarningunderwarnings.simplefilter("error"). With this branch (editable install + pydantic-settings 2.15.0), the same script constructs the server cleanly.tests/server/fastmcp/test_server.py: 85 passed. Ruff format/check and pyright clean on the changed files.AI-assisted contribution disclosure
This PR was authored by an AI agent as part of a maintainer-commissioned triage workflow. The reproduction, fix, and tests were verified by running them as described above.
Generated by Claude Code