diff --git a/pyproject.toml b/pyproject.toml index aa379d4e..54d0f420 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,6 +108,11 @@ markers = [ "e2e_recovery: opt-in end-to-end SSE recovery harness (real server + real SSE consumers, scripted provider — NOT live, no LLM backend needed); tens of seconds each. CI lanes run ``-m 'not live and not e2e_recovery'``; select with ``-m e2e_recovery``.", ] filterwarnings = [ + # The MCP v1 FastMCP integration fixture rebuilds its incomplete generic + # Settings model before construction. Keep the new pydantic-settings 2.15 + # diagnostic fatal so removing that compatibility step cannot regress + # into a warning hidden in the full-suite summary. + "error:Field 'lifespan' has an incomplete definition", # mcp v1 deprecates streamablehttp_client for an entry point whose call # shape only settles in v2 — adoption rides the deliberate v2 migration # (pin capped <2); silence exactly this message until then. diff --git a/tests/conftest.py b/tests/conftest.py index e6493602..53258715 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,20 @@ from unittest.mock import MagicMock import pytest +def pytest_sessionstart(session: pytest.Session) -> None: + """Resolve MCP v1's generic FastMCP settings model for test servers. + + MCP 1.29.0 defines ``Settings`` before ``FastMCP``, so its ``lifespan`` + annotation remains an unresolved forward reference after import. Rebuild + once, after the module is fully loaded, before any integration fixture + constructs a FastMCP server. Pydantic's public hook is a no-op once the SDK + ships a complete model. + """ + from mcp.server.fastmcp.server import Settings as FastMCPSettings + + FastMCPSettings.model_rebuild() + + def stop_loop_thread(loop: asyncio.AbstractEventLoop, thread: threading.Thread) -> None: """Fully tear down a ``loop.run_forever``-in-a-thread test loop.