| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Settings.lifespan is annotated with FastMCP, which is defined further down the module, so pydantic left the Settings model incomplete until the first FastMCP() call rebuilt it. pydantic-settings 2.15 now emits IncompleteFieldDefinitionWarning whenever a settings source inspects such a field, which means a warning on every FastMCP() construction (and, with warnings treated as errors, a failing test suite on the "highest" dependency resolution). Call Settings.model_rebuild() once FastMCP exists so the model is complete before anything instantiates it.
There was a problem hiding this comment.
LGTM — the module-level Settings.model_rebuild() is the standard pydantic fix for a forward-referenced field and is placed correctly after FastMCP is defined; the inline comment about the regression test's ordering sensitivity is worth a look but doesn't affect the correctness of the fix.
Extended reasoning...The PR touches two files: src/mcp/server/fastmcp/server.py gains a single module-level Settings.model_rebuild() call (with an explanatory comment) placed immediately after the FastMCP class definition, and tests/server/fastmcp/test_server.py adds a three-line test asserting Settings.__pydantic_complete__. The purpose is to resolve the Settings.lifespan forward reference to FastMCP at import time so pydantic-settings 2.15's IncompleteFieldDefinitionWarning (escalated to an error by the test suite's warning filters on the highest resolution) is never emitted.
None. The change resolves a type annotation at import time; it does not alter settings values, parsing, network behavior, or any auth/permission path. model_rebuild() after the referenced class is defined is idempotent with what pydantic would otherwise do lazily on first instantiation.
Low. This is a two-line, self-contained fix following a well-known pydantic idiom (rebuild a model with forward references once the referenced symbol exists in the module namespace). The placement — after FastMCP is fully defined, before anything else in the module could construct one — is correct, and there is no plausible behavioral change beyond suppressing the warning.
The one confirmed inline finding is a test-quality issue: the new regression test can be satisfied vacuously in full-suite runs because an earlier-collected test that constructs FastMCP() triggers pydantic's lazy rebuild, so the assertion may not catch a future removal of the fix on the locked resolution. That is worth addressing (e.g., running the check in a subprocess or fresh import), but it does not delay the fix itself, which is what CI needs. No unresolved third-party objections appear in the timeline.
Sorry, something went wrong.
| 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__ |
There was a problem hiding this comment.
🟡 Regression test is vacuous in full-suite runs: pydantic lazily rebuilds an incomplete model on first instantiation (MockValSer._attempt_rebuild calls model_rebuild and sets pydantic_complete = True), and many test files collected before this one construct FastMCP() (e.g. tests/client/test_logging_callback.py:28, tests/server/fastmcp/test_elicitation.py / test_integration.py, which sort before test_server.py). So assert Settings.__pydantic_complete__ passes even if the new module-level Settings.model_rebuild() in src/mcp/server/fastmcp/server.py is removed — the test only detects the regression when run in isolation or first. To actually guard the "complete at import" property, the check must run in a fresh interpreter (e.g. subprocess `python -c "from mcp.server.fastmcp.server…
Extended reasoning...The module-level Settings.model_rebuild() is later removed or moved (regressing the PR's fix). On the locked dependency resolution (pydantic-settings < 2.15, where no IncompleteFieldDefinitionWarning is emitted), uv run --frozen pytest still passes: an earlier-collected test such as tests/client/test_logging_callback.py instantiates FastMCP(), pydantic's lazy rebuild marks Settings complete, and test_settings_model_is_complete_at_import asserts True. The regression the test was added to catch ships undetected until the 'highest' resolution CI breaks again.
Verification: nit. The new test (tests/server/fastmcp/test_server.py:35-37, assert Settings.__pydantic_complete__) is order-dependent and does not reliably guard the fix in a full-suite run. Mechanism: pydantic v2 leaves a model with an unresolved forward reference mocked, and the first instantiation triggers a lazy model_rebuild() which, on success, sets cls.__pydantic_complete__ = True (this is the very
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Settings.lifespan is annotated with FastMCP, which is defined further down mcp/server/fastmcp/server.py, so pydantic leaves the Settings model incomplete at import and only rebuilds it lazily on the first FastMCP() call. pydantic-settings 2.15 added IncompleteFieldDefinitionWarning for exactly this situation, emitted whenever a settings source inspects such a field — i.e. on every FastMCP(...) construction. With filterwarnings = ["error"] that fails the whole test suite on the highest dependency resolution, which is why every open v1.x PR is currently red there.
Motivation and Context
Call Settings.model_rebuild() once FastMCP exists so the model is complete before anything instantiates it. No behaviour change beyond the warning going away; the settings values resolve exactly as before.
main doesn't use pydantic-settings, so this is v1.x only.
How Has This Been Tested?
tests/server/fastmcp/test_server.py::test_settings_model_is_complete_at_import asserts the model is complete after import (fails on v1.x today). Full suite, pyright and ruff pass locally on the locked resolution; the highest CI jobs on this PR are the check against pydantic-settings 2.15.
Breaking Changes
None.
Types of changes
Checklist
Additional context
None.
AI Disclaimer