From 76e3dba0f88929a82c673b7c586cc4aba8b8f34a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 3 Mar 2026 09:41:45 -0800 Subject: [PATCH] fix mcp server created_at and updated_at timestamps being overwritten with current time - Add created_at field to MCPServer type (was missing) - Map created_at from LiteLLM_MCPServerTable in build_mcp_server_from_table() - Use server.created_at and server.updated_at instead of datetime.now() in _build_mcp_server_table() and health check table builder - Add regression tests to verify timestamps are preserved through round-trip conversions Co-Authored-By: Claude Sonnet 4.6 --- .../mcp_server/mcp_server_manager.py | 11 ++- .../types/mcp_server/mcp_server_manager.py | 1 + .../mcp_server/test_mcp_server_manager.py | 86 +++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index da29c7804a..b7c013e9f2 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -642,6 +642,7 @@ class MCPServerManager: available_on_public_internet=bool( getattr(mcp_server, "available_on_public_internet", True) ), + created_at=getattr(mcp_server, "created_at", None), updated_at=getattr(mcp_server, "updated_at", None), ) return new_server @@ -2540,8 +2541,8 @@ class MCPServerManager: url=server.url, transport=server.transport, auth_type=server.auth_type, - created_at=datetime.now(), - updated_at=datetime.now(), + created_at=server.created_at, + updated_at=server.updated_at, teams=[], mcp_access_groups=server.access_groups or [], allowed_tools=server.allowed_tools or [], @@ -2620,8 +2621,6 @@ class MCPServerManager: return list_mcp_servers def _build_mcp_server_table(self, server: MCPServer) -> LiteLLM_MCPServerTable: - from datetime import datetime - return LiteLLM_MCPServerTable( server_id=server.server_id, server_name=server.server_name, @@ -2633,8 +2632,8 @@ class MCPServerManager: spec_path=server.spec_path, transport=server.transport, auth_type=server.auth_type, - created_at=datetime.now(), - updated_at=datetime.now(), + created_at=server.created_at, + updated_at=server.updated_at, teams=[], mcp_access_groups=server.access_groups or [], allowed_tools=server.allowed_tools or [], diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index 69b34a25a2..cabac6b9d5 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -53,6 +53,7 @@ class MCPServer(BaseModel): access_groups: Optional[List[str]] = None allow_all_keys: bool = False available_on_public_internet: bool = True + created_at: Optional[datetime] = None updated_at: Optional[datetime] = None model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index c105052479..acc76221cb 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -2307,5 +2307,91 @@ class TestMCPServerManager: assert resolved_server.server_name == "test_server" # server_name matches +class TestMCPServerTimestamps: + """Regression tests: created_at/updated_at must be preserved, not overwritten with datetime.now().""" + + @pytest.mark.asyncio + async def test_build_mcp_server_from_table_preserves_timestamps(self): + """build_mcp_server_from_table must carry created_at and updated_at into MCPServer.""" + manager = MCPServerManager() + + created = datetime(2024, 1, 15, 10, 0, 0) + updated = datetime(2024, 6, 20, 12, 30, 0) + + table_record = LiteLLM_MCPServerTable( + server_id="ts-server-1", + server_name="ts_server", + url="https://example.com/mcp", + transport=MCPTransport.http, + created_at=created, + updated_at=updated, + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + + assert mcp_server.created_at == created + assert mcp_server.updated_at == updated + + def test_build_mcp_server_table_preserves_timestamps(self): + """_build_mcp_server_table must use the MCPServer's stored timestamps, not datetime.now().""" + manager = MCPServerManager() + + created = datetime(2024, 1, 15, 10, 0, 0) + updated = datetime(2024, 6, 20, 12, 30, 0) + + server = MCPServer( + server_id="ts-server-2", + name="ts_server", + url="https://example.com/mcp", + transport=MCPTransport.http, + created_at=created, + updated_at=updated, + ) + + table = manager._build_mcp_server_table(server) + + assert table.created_at == created + assert table.updated_at == updated + + def test_build_mcp_server_table_none_timestamps_when_not_set(self): + """_build_mcp_server_table must return None timestamps when not set on MCPServer.""" + manager = MCPServerManager() + + server = MCPServer( + server_id="ts-server-3", + name="ts_server", + url="https://example.com/mcp", + transport=MCPTransport.http, + ) + + table = manager._build_mcp_server_table(server) + + assert table.created_at is None + assert table.updated_at is None + + @pytest.mark.asyncio + async def test_round_trip_timestamps_preserved(self): + """Timestamps survive the full round-trip: LiteLLM_MCPServerTable -> MCPServer -> LiteLLM_MCPServerTable.""" + manager = MCPServerManager() + + created = datetime(2023, 3, 10, 8, 0, 0) + updated = datetime(2023, 9, 5, 16, 45, 0) + + table_record = LiteLLM_MCPServerTable( + server_id="ts-server-4", + server_name="ts_server_rt", + url="https://example.com/mcp", + transport=MCPTransport.http, + created_at=created, + updated_at=updated, + ) + + mcp_server = await manager.build_mcp_server_from_table(table_record) + rebuilt_table = manager._build_mcp_server_table(mcp_server) + + assert rebuilt_table.created_at == created + assert rebuilt_table.updated_at == updated + + if __name__ == "__main__": pytest.main([__file__])