fix(mcp): seed the catalog revision row so the catalog cache works before the first MCP server write

litellm_bump_mcp_catalog_revision creates the mcp_catalog row in LiteLLM_Config only on the first write to LiteLLM_MCPServerTable. Until an admin changes an MCP server, get_mcp_catalog_revision returns None, which the catalog treats as "always reload": every MCP request reloads and deep-copies every server row. Seed the row in a migration that runs after the trigger migration; ON CONFLICT DO NOTHING keeps a revision the trigger already bumped.
This commit is contained in:
Artur Sidorenko 2026-11-02 11:02:46 +01:00
parent ff3983c1f6
commit 197fda79e4
2 changed files with 44 additions and 0 deletions

View file

@ -0,0 +1,8 @@
SET LOCAL lock_timeout = '10s';
-- Seed the MCP catalog revision row. litellm_bump_mcp_catalog_revision only creates it on
-- the first write to "LiteLLM_MCPServerTable"; until then get_mcp_catalog_revision returns
-- None and every MCP request reloads the whole server catalog from the database.
INSERT INTO "LiteLLM_Config" ("param_name", "reload_revision")
VALUES ('mcp_catalog', 1)
ON CONFLICT ("param_name") DO NOTHING;

View file

@ -0,0 +1,36 @@
"""The MCP catalog revision row must exist as soon as the trigger that bumps it does.
``litellm_bump_mcp_catalog_revision`` only creates the row on the first write to
``LiteLLM_MCPServerTable``. Until then ``get_mcp_catalog_revision`` returns None, and the
catalog treats None as "always reload": every MCP request reloads every server row.
"""
import re
from pathlib import Path
from typing import Final
from litellm.proxy.mcp_server.db import MCP_CATALOG_REVISION_PARAM_NAME
MIGRATIONS: Final = Path(__file__).resolve().parents[3] / "litellm-proxy-extras" / "litellm_proxy_extras" / "migrations"
TRIGGER: Final = "20260923000000_add_mcp_catalog_revision_trigger"
SEED: Final = "20261102000000_seed_mcp_catalog_revision"
def _statements(name: str) -> str:
sql: Final = (MIGRATIONS / name / "migration.sql").read_text(encoding="utf-8")
return " ".join(re.sub(r"--[^\n]*", "", sql).split())
def test_the_seed_runs_after_the_migration_that_installs_the_trigger() -> None:
assert sorted([SEED, TRIGGER]) == [TRIGGER, SEED]
def test_the_seed_creates_the_row_the_catalog_reads() -> None:
assert (
f'INSERT INTO "LiteLLM_Config" ("param_name", "reload_revision") '
f"VALUES ('{MCP_CATALOG_REVISION_PARAM_NAME}', 1)" in _statements(SEED)
)
def test_the_seed_keeps_a_revision_the_trigger_already_bumped() -> None:
assert _statements(SEED).endswith('ON CONFLICT ("param_name") DO NOTHING;')