Skip to content

fix(proxy): hydrate MCP server registry from DB on startup when store_model_in_db is false - #31775

Merged
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_registry_startup_hydration
Jul 1, 2026
Merged

fix(proxy): hydrate MCP server registry from DB on startup when store_model_in_db is false#31775
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_registry_startup_hydration

Conversation

@tin-berri

@tin-berri tin-berri commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Resolves LIT-4128

Reported internally: MCP servers added through the UI disappear from the MCP Servers page after a single-instance restart, and only reappear once a new server is added

Linear ticket

LIT-4128

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

This is a backend fix (no dashboard changes)

The symptom looks like a UI problem, but the dashboard is not at fault. The MCP Servers page already fetches the list on mount through a React Query hook (useMCPServers) and re-fetches after every write, so an empty page reflects what GET /v1/mcp/server actually returned rather than stale client state. On a cold start the endpoint itself returned an empty list even though the rows were still in the database, so a client-side change (a different hook, a hard refresh) would just re-fetch the same empty result. The fix is entirely server side in litellm/proxy/proxy_server.py; there are no frontend edits

Root cause

MCP servers created through the UI are written straight to the database (LiteLLM_MCPServerTable) and are independent of store_model_in_db, but GET /v1/mcp/server reads only from the in-memory registry on global_mcp_server_manager, never from the database. That registry is hydrated from the database on startup exclusively through add_deployment -> _init_non_llm_objects_in_db -> _init_mcp_servers_in_db -> reload_servers_from_database, and the initial add_deployment call plus its polling job run only inside the if store_model_in_db is True: guard in ProxyStartupEvent.initialize_scheduled_background_jobs

Config-yaml servers load unconditionally on startup, but database servers ride the store_model_in_db model-sync loop. So on a DB-backed single-instance proxy where store_model_in_db is not set, a restart leaves the registry empty, and the page stays empty until any MCP write endpoint (add, edit, delete, approve, reject) calls reload_servers_from_database and rebuilds the registry from the database. That write side effect is why adding one server makes every previously-added server reappear

Fix

Hydrate the MCP registry from the database on startup regardless of store_model_in_db, so it matches how config servers already behave. A new public ProxyConfig.init_mcp_servers_from_db wraps the existing loader and honors supported_db_objects; initialize_scheduled_background_jobs calls it when store_model_in_db is not True (when it is True, the existing model-sync loop already handles hydration, so nothing changes for those deployments)

Screenshots / Proof of Fix

Single-instance proxy, Postgres attached, store_model_in_db unset (default False), started with python litellm/proxy/proxy_cli.py --config config.yaml

  1. Add an MCP server through the API the UI uses, which persists it to the database and lists it while the proxy is up
curl -s -X POST http://localhost:4000/v1/mcp/server \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"server_name":"deepwiki_mcp","transport":"http","auth_type":"none","url":"https://mcp.deepwiki.com/mcp"}'
# {"server_id":"66c447aa-4fff-435a-945a-099072303a23","server_name":"deepwiki_mcp", ...}

curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer $KEY" | jq '{count: length, servers: [.[].server_name]}'
# { "count": 1, "servers": ["deepwiki_mcp"] }
  1. Restart against the same database on the current release (before this change); the list is empty even though the row is still in the database
curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer $KEY" | jq '{count: length, servers: [.[].server_name]}'
# { "count": 0, "servers": [] }

# the row is still there:
psql -tAc 'SELECT server_id, server_name FROM "LiteLLM_MCPServerTable";'
# 66c447aa-4fff-435a-945a-099072303a23|deepwiki_mcp
  1. Restart against the same database with this change; the server is listed after the restart with no write
curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer $KEY" | jq '{count: length, servers: [{name: .[].server_name, url: .[].url}]}'
# { "count": 1, "servers": [ { "name": "deepwiki_mcp", "url": "https://mcp.deepwiki.com/mcp" } ] }

The same is visible in the dashboard at http://localhost:4000/ui/?page=mcp-servers: empty after a restart before this change, populated after a restart with it

Type

🐛 Bug Fix

Changes

litellm/proxy/proxy_server.py: added ProxyConfig.init_mcp_servers_from_db and a startup call to it in initialize_scheduled_background_jobs for the store_model_in_db is not True path

tests/test_litellm/proxy/test_proxy_server.py: a regression test asserting the startup path hydrates MCP servers when store_model_in_db is False (and does not run add_deployment), plus a test that init_mcp_servers_from_db honors supported_db_objects

…_model_in_db is false

MCP servers created through the UI are persisted to the database independent of
store_model_in_db, but the in-memory registry that GET /v1/mcp/server reads was
hydrated from the database only through add_deployment, which runs solely when
store_model_in_db is True. On a DB-backed single-instance proxy with
store_model_in_db unset the registry started empty after a restart, so the MCP
Servers page showed nothing until an add or edit triggered a reload.

Hydrate the registry from the database on startup regardless of store_model_in_db
via a new ProxyConfig.init_mcp_servers_from_db, honoring supported_db_objects.
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where MCP servers added through the UI (and stored in LiteLLM_MCPServerTable) were not loaded back into the in-memory registry after a proxy restart when store_model_in_db is unset/false, leaving the MCP Servers page empty until a write operation triggered a reload.

  • Adds ProxyConfig.init_mcp_servers_from_db — a public wrapper around the existing _init_mcp_servers_in_db that honours the supported_db_objects allowlist — and calls it during initialize_scheduled_background_jobs only on the store_model_in_db is not True path (when it is True, add_deployment_init_non_llm_objects_in_db already handles the hydration, so there is no duplication).
  • Adds two focused mock-only regression tests: one asserting the new startup path is taken (and add_deployment is not called) when store_model_in_db=False, and one asserting that an explicit supported_db_objects list that omits "mcp" correctly skips hydration.

Confidence Score: 4/5

Safe to merge — the change is a minimal, targeted startup hook with no impact on the request hot path.

The new init_mcp_servers_from_db call is correctly guarded so it runs only when the add_deployment loop is absent, avoiding any double hydration. The underlying _init_mcp_servers_in_db already wraps its work in a try/except so a DB hiccup on startup will log and move on without crashing. The two new tests cover both the startup path and the supported_db_objects allowlist. No existing tests are weakened.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/proxy_server.py Adds ProxyConfig.init_mcp_servers_from_db and calls it in initialize_scheduled_background_jobs when store_model_in_db is not True, ensuring the in-memory MCP registry is populated from the DB on startup even without the model-sync loop.
tests/test_litellm/proxy/test_proxy_server.py Adds two regression tests: one verifying init_mcp_servers_from_db is awaited (and add_deployment is not called) during startup when store_model_in_db=False, and one verifying supported_db_objects allowlist is respected. Both use only mocks — no real network calls.

Reviews (1): Last reviewed commit: "fix(proxy): hydrate MCP server registry ..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a startup bug where MCP servers persisted to the database via the UI would disappear from the in-memory registry after a proxy restart when store_model_in_db is not set to True. The fix adds a ProxyConfig.init_mcp_servers_from_db method and calls it during initialize_scheduled_background_jobs on the non-store_model_in_db path.

  • Adds init_mcp_servers_from_db as a thin public wrapper around _init_mcp_servers_in_db that respects the supported_db_objects allowlist, matching the existing pattern in _init_non_llm_objects_in_db.
  • Calls the new method at startup only when store_model_in_db is not True — after the final value of that flag has been resolved (including the DB-override check), so the store_model_in_db=True path via add_deployment → _init_non_llm_objects_in_db is unchanged and there is no double hydration.
  • Two new unit tests verify: (1) the startup path calls init_mcp_servers_from_db and skips add_deployment when store_model_in_db=False; (2) init_mcp_servers_from_db honours a supported_db_objects allowlist that omits "mcp".

Confidence Score: 5/5

Safe to merge — the change is a targeted startup-only hydration call with no impact on the hot request path.

The new method is a 4-line wrapper that mirrors logic already present in _init_non_llm_objects_in_db. It is placed after the DB-override resolution of store_model_in_db, so the True path (via add_deployment) is completely untouched and there is no risk of double initialization. Error handling follows the same try/except pattern as the existing _init_mcp_servers_in_db. The two new tests are pure mock tests that directly cover the gating logic and the supported_db_objects allowlist.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/proxy_server.py Adds init_mcp_servers_from_db (4 lines) and one call in initialize_scheduled_background_jobs (3 lines); correctly gated after the DB store_model_in_db override check so the True path is untouched.
tests/test_litellm/proxy/test_proxy_server.py Two new async regression tests: one verifies startup hydration on the store_model_in_db=False path, the other verifies supported_db_objects filtering — both are pure mock-based tests, no real network calls.

Reviews (2): Last reviewed commit: "fix(proxy): hydrate MCP server registry ..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ed5429a. Configure here.

@tin-berri
tin-berri merged commit 13b590c into litellm_internal_staging Jul 1, 2026
128 checks passed
@tin-berri
tin-berri deleted the litellm_mcp_registry_startup_hydration branch July 1, 2026 04:23
duanhongyi pushed a commit to duanhongyi/litellm that referenced this pull request Jul 2, 2026
…_model_in_db is false (BerriAI#31775)

MCP servers created through the UI are persisted to the database independent of
store_model_in_db, but the in-memory registry that GET /v1/mcp/server reads was
hydrated from the database only through add_deployment, which runs solely when
store_model_in_db is True. On a DB-backed single-instance proxy with
store_model_in_db unset the registry started empty after a restart, so the MCP
Servers page showed nothing until an add or edit triggered a reload.

Hydrate the registry from the database on startup regardless of store_model_in_db
via a new ProxyConfig.init_mcp_servers_from_db, honoring supported_db_objects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants