fix(mcp): surface DB-unavailable as a real error on per-user env var lookup

When prisma_client is None, _load_user_env_vars returned an empty dict,
which on the tool-call path was indistinguishable from "user has no
stored values" and produced a misleading 412 directing the user to set
up credentials they can never store without a database. Raise instead so
the tool-call path fails with a clear error and the listing path stays
best-effort via its existing catch.
This commit is contained in:
mateo-berri 2026-06-04 15:44:02 +00:00
parent 63a9f00103
commit 3ac1c00aa0
No known key found for this signature in database
2 changed files with 42 additions and 7 deletions

View file

@ -1697,9 +1697,11 @@ class MCPServerManager:
window; the cache is invalidated when the user stores or clears values.
Pass ``force_refresh`` to bypass the cache read and re-fetch from the DB
(used before raising a "missing credentials" error so a process-local
stale entry cannot mask values stored on another worker). DB errors
propagate so the caller can decide between failing the request (tool-call
path) and staying best-effort (listing path).
stale entry cannot mask values stored on another worker). A missing DB
connection and any other DB error propagate so the caller can decide
between failing the request (tool-call path) and staying best-effort
(listing path); they must never be mistaken for "user has no values",
which would send the user a misleading "set up your credentials" 412.
"""
if user_api_key_auth is None:
return {}
@ -1718,7 +1720,11 @@ class MCPServerManager:
from litellm.proxy.proxy_server import prisma_client # noqa: PLC0415
if prisma_client is None:
return {}
raise RuntimeError(
"MCP per-user env vars require a database connection, but none "
"is configured. Connect a database to your proxy to use per-user "
"MCP env vars."
)
from litellm.proxy._experimental.mcp_server.db import ( # noqa: PLC0415
get_user_env_vars,
)

View file

@ -484,8 +484,11 @@ async def test_load_user_env_vars_returns_empty_without_user_id():
@pytest.mark.asyncio
async def test_load_user_env_vars_returns_empty_when_db_unavailable(monkeypatch):
"""If prisma_client is None, the lookup short-circuits rather than crashing."""
async def test_load_user_env_vars_raises_when_db_unavailable(monkeypatch):
"""A missing DB connection must raise, not return ``{}``. Returning ``{}``
would be indistinguishable from "user has no values" and would mislead the
tool-call path into a "set up your credentials" 412 the user can never
satisfy (per-user env vars are unusable without a DB)."""
from unittest.mock import MagicMock
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
@ -500,7 +503,33 @@ async def test_load_user_env_vars_returns_empty_when_db_unavailable(monkeypatch)
fake_auth = MagicMock()
fake_auth.user_id = "alice"
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
assert await manager._load_user_env_vars(server, fake_auth) == {}
with pytest.raises(RuntimeError, match="database connection"):
await manager._load_user_env_vars(server, fake_auth)
@pytest.mark.asyncio
async def test_resolve_static_headers_db_unavailable_is_not_missing_412(
mock_server, monkeypatch
):
"""On the tool-call path, an unavailable DB must surface as a real error
rather than a misleading MCPMissingUserEnvVarsError (412). This guards the
regression where ``_load_user_env_vars`` returned ``{}`` when prisma_client
was None, making a DB outage look like "user has no credentials"."""
from unittest.mock import MagicMock
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
MCPServerManager,
)
manager = MCPServerManager()
fake_auth = MagicMock()
fake_auth.user_id = "alice"
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
with pytest.raises(RuntimeError, match="database connection"):
await manager._resolve_static_headers_with_env_vars(
mock_server, user_api_key_auth=fake_auth
)
@pytest.mark.asyncio