diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 990682f10a5..99e4f8e23e7 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -9176,8 +9176,12 @@ class ProxyStartupEvent: redis_cache=redis_usage_cache, ) + # init_mcp_servers_from_db already gates on _should_load_db_object("mcp"), so calling + # it unconditionally keeps the allowlist behavior while letting store_model_in_db=True + # deployments hydrate the in-memory MCP registry on boot. + await proxy_config.init_mcp_servers_from_db() + if store_model_in_db is not True: - await proxy_config.init_mcp_servers_from_db() # Without this branch's own refresh, a UI-created search tool never reaches the router: # the add_deployment job that carries it in store_model_in_db=True mode is not scheduled. await proxy_config.reload_search_tools_from_db() diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index f51648faf80..eab185f715e 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -926,6 +926,43 @@ async def test_initialize_scheduled_jobs_hydrates_mcp_when_store_model_in_db_fal mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() +@pytest.mark.asyncio +async def test_initialize_scheduled_jobs_hydrates_mcp_when_store_model_in_db_true(monkeypatch): # test-quality-ok: the awaited call is the behaviour; the bug was that it never ran + """ + Regression (issue #32575): with store_model_in_db=True the startup gate skipped + init_mcp_servers_from_db entirely, so DB-backed MCP servers created via the Admin + UI vanished from GET /v1/mcp/server after a restart until the next write. MCP + hydration must run regardless of store_model_in_db. + """ + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) + monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) + from litellm.proxy.proxy_server import ProxyStartupEvent + from litellm.proxy.utils import ProxyLogging + + mock_prisma_client = MagicMock() + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.slack_alerting_instance = MagicMock() + # set on the instance, so a class spec does not expose it + mock_proxy_logging.db_spend_update_writer = MagicMock() + mock_proxy_config = AsyncMock() + + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), # test-quality-ok: proxy_config is module state the startup path reads, no injection point + patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: store_model_in_db is the module flag whose gate this test exercises + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) + + mock_proxy_config.add_deployment.assert_awaited() + mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() + + @pytest.mark.asyncio async def test_init_mcp_servers_from_db_respects_supported_db_objects(monkeypatch): """