Merge branch 'litellm_internal_staging' into litellm_mcp_server_duplicate_button

Resolve conflict in mcp_servers.tsx: staging replaced the mcpServerColumns
table with the MCPServerCard grid, so the duplicate action moves into the
card's actions dropdown (MCPServerCard onDuplicate) instead of a table column.
mcp_server_columns.tsx is dead code on staging, so its duplicate button is
dropped. Preserve the duplicated alias by marking it manually edited so the
name-derived alias effect no longer overwrites it; this stacks with the
existing trailing _copy strip. Tests rewritten to drive the dropdown.

https://claude.ai/code/session_01VTfkTkTM3jKCp2CSraND8A
This commit is contained in:
mateo-berri 2026-06-06 22:05:48 +00:00
commit f0578f7124
No known key found for this signature in database
312 changed files with 64596 additions and 3693 deletions

3
.gitattributes vendored
View file

@ -1 +1,2 @@
*.ipynb linguist-vendored
*.ipynb linguist-vendored
ui/litellm-dashboard/src/lib/http/schema.d.ts linguist-generated

View file

@ -0,0 +1,84 @@
name: Check UI API Types Sync
on:
pull_request:
paths:
- "litellm/proxy/**"
- "litellm/types/**"
- "ui/litellm-dashboard/src/lib/http/schema.d.ts"
- "ui/litellm-dashboard/scripts/gen-api-types.mjs"
- "ui/litellm-dashboard/package.json"
- "ui/litellm-dashboard/package-lock.json"
- ".github/workflows/check-ui-api-types.yml"
permissions:
contents: read
jobs:
check-sync:
name: Verify schema.d.ts matches the proxy OpenAPI spec
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
version: "0.10.9"
- name: Cache uv dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-
- name: Install backend dependencies
run: uv sync --frozen --group ci --group proxy-dev --extra google --extra proxy --extra semantic-router
- name: Generate Prisma client
env:
PRISMA_BINARY_CACHE_DIR: ${{ runner.temp }}/prisma-cache
run: uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma
- name: Set up Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/litellm-dashboard/package-lock.json
- name: Install dashboard dependencies
working-directory: ui/litellm-dashboard
run: npm ci
- name: Regenerate types from the live spec
working-directory: ui/litellm-dashboard
env:
LITELLM_PYTHON: "uv run --no-sync python"
run: npm run gen:api
- name: Fail if types are stale
run: |
if ! git diff --exit-code -- ui/litellm-dashboard/src/lib/http/schema.d.ts; then
echo "::error file=ui/litellm-dashboard/src/lib/http/schema.d.ts::Generated API types are out of sync with the proxy OpenAPI spec."
echo ""
echo "A backend route or model changed without regenerating the dashboard types."
echo "To fix, run from ui/litellm-dashboard:"
echo " npm run gen:api"
echo "then commit the updated src/lib/http/schema.d.ts."
exit 1
fi
echo "schema.d.ts is in sync with the proxy OpenAPI spec."

View file

@ -0,0 +1,23 @@
-- AlterTable: add admin-configured env_vars to MCP server table
ALTER TABLE "LiteLLM_MCPServerTable" ADD COLUMN IF NOT EXISTS "env_vars" JSONB DEFAULT '[]';
-- CreateTable: per-user env var values for MCP servers
CREATE TABLE IF NOT EXISTS "LiteLLM_MCPUserEnvVars" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"server_id" TEXT NOT NULL,
"values_b64" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LiteLLM_MCPUserEnvVars_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "LiteLLM_MCPUserEnvVars_user_id_server_id_key" ON "LiteLLM_MCPUserEnvVars"("user_id", "server_id");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "LiteLLM_MCPUserEnvVars_user_id_idx" ON "LiteLLM_MCPUserEnvVars"("user_id");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "LiteLLM_MCPUserEnvVars_server_id_idx" ON "LiteLLM_MCPUserEnvVars"("server_id");

View file

@ -311,6 +311,11 @@ model LiteLLM_MCPServerTable {
tool_name_to_description Json? @default("{}")
extra_headers String[] @default([])
static_headers Json? @default("{}")
// Admin-configured environment variables interpolated into static_headers
// via ${NAME} syntax. Stored as an array of
// {name, value, scope, description}. scope is "global" (value used as-is)
// or "user" (value supplied per-user via LiteLLM_MCPUserEnvVars).
env_vars Json? @default("[]")
// Health check status
status String? @default("unknown")
last_health_check DateTime?
@ -366,6 +371,21 @@ model LiteLLM_MCPUserCredentials {
@@unique([user_id, server_id])
}
// Per-user environment variable values for MCP servers.
// values_b64 is an encrypted JSON object: {VAR_NAME: "value", ...}.
model LiteLLM_MCPUserEnvVars {
id String @id @default(uuid())
user_id String
server_id String
values_b64 String
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
@@unique([user_id, server_id])
@@index([user_id])
@@index([server_id])
}
// Generate Tokens for Proxy
model LiteLLM_VerificationToken {
token String @id

View file

@ -16,8 +16,17 @@ import os
# Load .env before any other litellm imports so env vars (e.g. LITELLM_UI_SESSION_DURATION) are available
import dotenv as _dotenv
def _dev_env_hot_reload_enabled() -> bool:
"""The proxy exports this flag when started with ``--reload``. A reloaded
worker is a fresh process that inherits the reloader's environment, so an
edited ``.env`` value stays masked by the stale inherited one unless we
let the file win; overriding makes the edit take effect on reload."""
return os.getenv("LITELLM_DEV_ENV_HOT_RELOAD") == "True"
if os.getenv("LITELLM_MODE", "DEV") == "DEV":
_dotenv.load_dotenv()
_dotenv.load_dotenv(override=_dev_env_hot_reload_enabled())
from typing import (
Callable,

View file

@ -60,6 +60,27 @@ def to_basic_auth(auth_value: str) -> str:
return base64.b64encode(auth_value.encode("utf-8")).decode()
def _strip_header_whitespace(headers: Dict[str, str]) -> Dict[str, str]:
return {
(key.strip() if isinstance(key, str) else key): (
value.strip() if isinstance(value, str) else value
)
for key, value in headers.items()
}
def _first_non_cancelled_cause(exc: BaseException) -> Optional[BaseException]:
queue: List[BaseException] = [exc]
while queue:
current = queue.pop(0)
nested = getattr(current, "exceptions", None)
if nested:
queue.extend(nested)
elif not isinstance(current, asyncio.CancelledError):
return current
return None
TSessionResult = TypeVar("TSessionResult")
@ -335,6 +356,7 @@ class MCPClient:
user input (elicitation), or send log messages.
"""
transport = await transport_ctx.__aenter__()
in_flight_error: Optional[BaseException] = None
try:
read_stream, write_stream = transport[0], transport[1]
# Build session kwargs with optional callbacks
@ -360,11 +382,21 @@ class MCPClient:
await session_ctx.__aexit__(None, None, None)
except BaseException as e:
verbose_logger.debug(f"Error during session context exit: {e}")
except BaseException as e:
in_flight_error = e
raise
finally:
try:
await transport_ctx.__aexit__(None, None, None)
except BaseException as e:
verbose_logger.debug(f"Error during transport context exit: {e}")
except BaseException as exit_error:
verbose_logger.debug(
f"Error during transport context exit: {exit_error}"
)
root_cause = _first_non_cancelled_cause(exit_error)
if root_cause is not None and isinstance(
in_flight_error, asyncio.CancelledError
):
raise root_cause from in_flight_error
async def run_with_session(
self, operation: Callable[[ClientSession], Awaitable[TSessionResult]]
@ -426,7 +458,7 @@ class MCPClient:
# update the headers with the extra headers
if self.extra_headers:
headers.update(self.extra_headers)
return headers
return _strip_header_whitespace(headers)
def _create_httpx_client_factory(self) -> Callable[..., httpx.AsyncClient]:
"""

View file

@ -7,6 +7,7 @@ from .flux_pro_v11_transformation import FalAIFluxProV11Config
from .flux_pro_v11_ultra_transformation import FalAIFluxProV11UltraConfig
from .flux_schnell_transformation import FalAIFluxSchnellConfig
from .imagen4_transformation import FalAIImagen4Config
from .nano_banana_transformation import FalAINanoBananaConfig
from .recraft_v3_transformation import FalAIRecraftV3Config
from .ideogram_v3_transformation import FalAIIdeogramV3Config
from .stable_diffusion_transformation import FalAIStableDiffusionConfig
@ -20,6 +21,7 @@ __all__ = [
"FalAIBaseConfig",
"FalAIImageGenerationConfig",
"FalAIImagen4Config",
"FalAINanoBananaConfig",
"FalAIRecraftV3Config",
"FalAIBriaConfig",
"FalAIFluxProV11Config",
@ -45,7 +47,9 @@ def get_fal_ai_image_generation_config(model: str) -> BaseImageGenerationConfig:
model_lower = model.lower()
# Map model names to their corresponding configuration classes
if "imagen4" in model_lower or "imagen-4" in model_lower:
if "nano-banana" in model_lower or "gemini-25-flash-image" in model_lower:
return FalAINanoBananaConfig()
elif "imagen4" in model_lower or "imagen-4" in model_lower:
return FalAIImagen4Config()
elif "recraft" in model_lower:
return FalAIRecraftV3Config()

View file

@ -0,0 +1,105 @@
from typing import List, Optional
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import OpenAIImageGenerationOptionalParams
from .transformation import FalAIBaseConfig
class FalAINanoBananaConfig(FalAIBaseConfig):
"""
Configuration for Fal AI's Nano Banana / Gemini 2.5 Flash Image models.
Serves the imagen4 deprecation migration path. The same underlying model is
exposed under two endpoints that share an identical schema:
- fal-ai/nano-banana
- fal-ai/gemini-25-flash-image
Documentation: https://fal.ai/models/fal-ai/nano-banana
"""
SUPPORTED_ASPECT_RATIOS: List[str] = [
"21:9",
"16:9",
"3:2",
"4:3",
"5:4",
"1:1",
"4:5",
"3:4",
"2:3",
"9:16",
]
def get_complete_url(
self,
api_base: Optional[str],
api_key: Optional[str],
model: str,
optional_params: dict,
litellm_params: dict,
stream: Optional[bool] = None,
) -> str:
base_url: str = (
api_base or get_secret_str("FAL_AI_API_BASE") or self.DEFAULT_BASE_URL
).rstrip("/")
endpoint = model if model.startswith("fal-ai/") else f"fal-ai/{model}"
return f"{base_url}/{endpoint}"
def get_supported_openai_params(
self, model: str
) -> List[OpenAIImageGenerationOptionalParams]:
return ["n", "response_format", "size"]
def map_openai_params(
self,
non_default_params: dict,
optional_params: dict,
model: str,
drop_params: bool,
) -> dict:
supported_params = self.get_supported_openai_params(model)
for key, value in non_default_params.items():
if key == "response_format":
continue
elif key == "n":
if "num_images" not in optional_params:
optional_params["num_images"] = value
elif key == "size":
if "aspect_ratio" not in optional_params:
optional_params["aspect_ratio"] = self._map_aspect_ratio(value)
elif key not in optional_params and not drop_params:
raise ValueError(
f"Parameter {key} is not supported for model {model}. "
f"Supported parameters are {supported_params}. "
"Set drop_params=True to drop unsupported parameters."
)
return optional_params
def _map_aspect_ratio(self, size: str) -> str:
if not isinstance(size, str) or "x" not in size:
return "1:1"
try:
width, height = (int(part) for part in size.split("x"))
target = width / height
except (ValueError, ZeroDivisionError):
return "1:1"
def ratio_of(aspect_ratio: str) -> float:
w, h = (int(part) for part in aspect_ratio.split(":"))
return w / h
return min(
self.SUPPORTED_ASPECT_RATIOS,
key=lambda aspect_ratio: abs(ratio_of(aspect_ratio) - target),
)
def transform_image_generation_request(
self,
model: str,
prompt: str,
optional_params: dict,
litellm_params: dict,
headers: dict,
) -> dict:
return {"prompt": prompt, **optional_params}

View file

@ -14014,6 +14014,22 @@
"/v1/images/generations"
]
},
"fal_ai/fal-ai/nano-banana": {
"litellm_provider": "fal_ai",
"mode": "image_generation",
"output_cost_per_image": 0.039,
"supported_endpoints": [
"/v1/images/generations"
]
},
"fal_ai/fal-ai/gemini-25-flash-image": {
"litellm_provider": "fal_ai",
"mode": "image_generation",
"output_cost_per_image": 0.039,
"supported_endpoints": [
"/v1/images/generations"
]
},
"featherless_ai/featherless-ai/Qwerky-72B": {
"litellm_provider": "featherless_ai",
"max_input_tokens": 32768,

View file

@ -1,16 +1,19 @@
import base64
import binascii
import hashlib
import json
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast
from litellm._logging import verbose_proxy_logger
from litellm._uuid import uuid
from litellm.constants import MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS
from litellm.proxy._types import (
LiteLLM_MCPServerTable,
LiteLLM_ObjectPermissionTable,
LiteLLM_TeamTable,
MCPApprovalStatus,
MCPEnvVarScope,
MCPSubmissionsSummary,
NewMCPServerRequest,
SpecialMCPServerName,
@ -28,6 +31,144 @@ from litellm.types.llms.custom_http import httpxSpecialProvider
from litellm.types.mcp import MCPCredentials
def _is_global_env_var_scope(scope: Any) -> bool:
"""``scope="user"`` entries are placeholders the user fills in; everything
else (including a missing scope) is an admin-supplied global value."""
return scope != MCPEnvVarScope.user and scope != "user"
def _encrypt_global_env_var_values(env_vars: Iterable[Dict[str, Any]]) -> None:
"""Encrypt ``scope="global"`` env var values in place before persisting.
Global values hold admin-supplied secrets (API keys, passwords) that get
interpolated into headers, so they are encrypted at rest like credentials
and the per-user ``values_b64`` column. Per-user placeholders are not
secrets and are stored verbatim.
"""
for entry in env_vars:
if not _is_global_env_var_scope(entry.get("scope")):
continue
value = entry.get("value")
if value:
entry["value"] = encrypt_value_helper(value)
def decrypt_global_env_var_values(env_vars: Optional[Iterable[Any]]) -> None:
"""Decrypt ``scope="global"`` env var values in place after reading the DB.
Accepts ``MCPEnvVar`` models (``LiteLLM_MCPServerTable``) or plain dicts
(raw rows / deserialized JSON). Global values are always stored encrypted,
so a value that no longer decrypts (e.g. after a salt-key change) is dropped
and a warning is logged rather than forwarding the ciphertext into upstream
``${NAME}`` headers, where it would silently fail.
"""
if not env_vars:
return
for entry in env_vars:
is_dict = isinstance(entry, dict)
scope = entry.get("scope") if is_dict else getattr(entry, "scope", None)
if not _is_global_env_var_scope(scope):
continue
value = entry.get("value") if is_dict else getattr(entry, "value", None)
if not value:
continue
decrypted = decrypt_value_helper(
value=value,
key="mcp_global_env_var",
exception_type="debug",
return_original_value=False,
)
if decrypted is None:
name = entry.get("name") if is_dict else getattr(entry, "name", None)
verbose_proxy_logger.warning(
"MCP global env var %s failed to decrypt (LITELLM_SALT_KEY "
"changed?); dropping it so ciphertext is not sent upstream",
name,
)
decrypted = ""
if is_dict:
entry["value"] = decrypted
else:
entry.value = decrypted
def _decrypt_env_vars_on_returned_row(row: Any) -> None:
"""Decrypt ``scope="global"`` env var values on a row returned by Prisma create/update.
Prisma may hand back ``env_vars`` either as a parsed list (the common case for
JSONB columns) or as a raw JSON string (observed for some write paths). The
in-place decrypt helper only mutates iterables of dicts/models, so a string
payload would silently skip decryption and ciphertext would leak into the
registry via ``add_server``/``update_server`` (which trust the caller).
Parse the string back to a list so the in-place decrypt actually runs, and
write the decrypted list back onto the row so downstream consumers see plain
values.
"""
env_vars = getattr(row, "env_vars", None)
if env_vars is None:
return
if isinstance(env_vars, str):
try:
env_vars = json.loads(env_vars)
except (json.JSONDecodeError, TypeError):
return
if not isinstance(env_vars, list):
return
try:
setattr(row, "env_vars", env_vars)
except (AttributeError, TypeError):
pass
decrypt_global_env_var_values(env_vars)
def _reencrypt_global_env_var_values(
env_vars: Optional[Iterable[Any]], new_encryption_key: str
) -> Optional[List[Dict[str, Any]]]:
"""Re-encrypt ``scope="global"`` env var values for master-key rotation.
Each global value is decrypted with the current salt key and re-encrypted
under ``new_encryption_key``. Returns the rebuilt list when at least one
value was rotated, else ``None`` so the caller can skip the DB write. A
value that fails to decrypt is left untouched (and logged) so a corrupt
entry is preserved for recovery rather than overwritten.
"""
if not env_vars:
return None
if isinstance(env_vars, str):
try:
env_vars = json.loads(env_vars)
except (json.JSONDecodeError, TypeError):
return None
if not env_vars:
return None
rebuilt = [dict(v) for v in env_vars]
rotated = False
for entry in rebuilt:
if not _is_global_env_var_scope(entry.get("scope")):
continue
value = entry.get("value")
if not value:
continue
decrypted = decrypt_value_helper(
value=value,
key="mcp_global_env_var",
exception_type="debug",
return_original_value=False,
)
if decrypted is None:
verbose_proxy_logger.warning(
"rotate_mcp_server_credentials_master_key: could not decrypt "
"global env var %s, skipping",
entry.get("name"),
)
continue
entry["value"] = encrypt_value_helper(
decrypted, new_encryption_key=new_encryption_key
)
rotated = True
return rebuilt if rotated else None
def _prepare_mcp_server_data(
data: Union[NewMCPServerRequest, UpdateMCPServerRequest],
exclude_unset: bool = False,
@ -98,6 +239,16 @@ def _prepare_mcp_server_data(
if data_dict.get("static_headers") is not None:
data_dict["static_headers"] = safe_dumps(data_dict["static_headers"])
# env_vars is read from ``data_dict`` (not ``data``) like every other JSON
# column so the exclude_unset filter is respected: a partial update that
# omits env_vars never overwrites the stored value. Global values are
# encrypted at rest before serialization.
env_vars = data_dict.get("env_vars")
if env_vars is not None:
serialized_env_vars = [dict(v) for v in env_vars]
_encrypt_global_env_var_values(serialized_env_vars)
data_dict["env_vars"] = safe_dumps(serialized_env_vars)
if data_dict.get("mcp_info") is not None:
data_dict["mcp_info"] = safe_dumps(data_dict["mcp_info"])
@ -207,10 +358,13 @@ async def get_all_mcp_servers(
where=where if where else {}
)
return [
tables = [
LiteLLM_MCPServerTable(**mcp_server.model_dump())
for mcp_server in mcp_servers
]
for table in tables:
decrypt_global_env_var_values(table.env_vars)
return tables
except Exception as e:
verbose_proxy_logger.debug(
"litellm.proxy._experimental.mcp_server.db.py::get_all_mcp_servers - {}".format(
@ -226,14 +380,16 @@ async def get_mcp_server(
"""
Returns the matching mcp server from the db iff exists
"""
mcp_server: Optional[LiteLLM_MCPServerTable] = (
await prisma_client.db.litellm_mcpservertable.find_unique(
where={
"server_id": server_id,
}
)
mcp_server = await prisma_client.db.litellm_mcpservertable.find_unique(
where={
"server_id": server_id,
}
)
return mcp_server
if mcp_server is None:
return None
table = LiteLLM_MCPServerTable(**mcp_server.model_dump())
decrypt_global_env_var_values(table.env_vars)
return table
async def get_mcp_servers(
@ -251,7 +407,9 @@ async def get_mcp_servers(
)
final_mcp_servers: List[LiteLLM_MCPServerTable] = []
for _mcp_server in _mcp_servers:
final_mcp_servers.append(LiteLLM_MCPServerTable(**_mcp_server.model_dump()))
table = LiteLLM_MCPServerTable(**_mcp_server.model_dump())
decrypt_global_env_var_values(table.env_vars)
final_mcp_servers.append(table)
return final_mcp_servers
@ -399,6 +557,11 @@ async def delete_mcp_server(
"""
Delete the mcp server from the db by server_id
The server-row delete is the commit point. Per-user env var rows have no FK
cascade, so they are cleaned up afterwards on a best-effort basis: a transient
failure there leaves only orphaned rows pointing at a now-missing server and
must not turn a successful delete into a caller-visible error.
Returns the deleted mcp server record if it exists, otherwise None
"""
deleted_server = await prisma_client.db.litellm_mcpservertable.delete(
@ -406,6 +569,18 @@ async def delete_mcp_server(
"server_id": server_id,
},
)
if deleted_server is not None:
try:
await prisma_client.db.litellm_mcpuserenvvars.delete_many(
where={"server_id": server_id}
)
except Exception as e:
verbose_proxy_logger.warning(
"MCP server %s deleted but per-user env var cleanup failed; "
"orphaned rows can be removed on a later delete: %s",
server_id,
e,
)
return deleted_server
@ -429,6 +604,7 @@ async def create_mcp_server(
data=data_dict # type: ignore
)
_decrypt_env_vars_on_returned_row(new_mcp_server)
return new_mcp_server
@ -506,40 +682,52 @@ async def update_mcp_server(
where={"server_id": data.server_id}, data=data_dict # type: ignore
)
_decrypt_env_vars_on_returned_row(updated_mcp_server)
return updated_mcp_server
async def rotate_mcp_server_credentials_master_key(
prisma_client: PrismaClient, touched_by: str, new_master_key: str
):
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
mcp_servers = await prisma_client.db.litellm_mcpservertable.find_many()
updated = 0
for mcp_server in mcp_servers:
update_data: Dict[str, Any] = {}
credentials = mcp_server.credentials
if not credentials:
if credentials:
# Decrypt with current key first, then re-encrypt with new key
decrypted_credentials = decrypt_credentials(
credentials=cast(MCPCredentials, dict(credentials)),
)
encrypted_credentials = encrypt_credentials(
credentials=decrypted_credentials,
encryption_key=new_master_key,
)
update_data["credentials"] = safe_dumps(encrypted_credentials)
rotated_env_vars = _reencrypt_global_env_var_values(
mcp_server.env_vars, new_master_key
)
if rotated_env_vars is not None:
update_data["env_vars"] = safe_dumps(rotated_env_vars)
if not update_data:
continue
credentials_copy = dict(credentials)
# Decrypt with current key first, then re-encrypt with new key
decrypted_credentials = decrypt_credentials(
credentials=cast(MCPCredentials, credentials_copy),
)
encrypted_credentials = encrypt_credentials(
credentials=decrypted_credentials,
encryption_key=new_master_key,
)
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
serialized_credentials = safe_dumps(encrypted_credentials)
update_data["updated_by"] = touched_by
await prisma_client.db.litellm_mcpservertable.update(
where={"server_id": mcp_server.server_id},
data={
"credentials": serialized_credentials,
"updated_by": touched_by,
},
data=update_data,
)
updated += 1
verbose_proxy_logger.info(
"rotate_mcp_server_credentials_master_key: rotated %d MCP server row(s)",
updated,
)
def _decode_user_credential(stored: str) -> Optional[str]:
@ -594,6 +782,8 @@ async def rotate_mcp_user_credentials_master_key(
are logged and skipped so one corrupt row does not abort the rotation.
"""
rows = await prisma_client.db.litellm_mcpusercredentials.find_many()
rotated = 0
skipped = 0
for row in rows:
plaintext = _decode_user_credential(row.credential_b64)
if plaintext is None:
@ -603,6 +793,7 @@ async def rotate_mcp_user_credentials_master_key(
row.user_id,
row.server_id,
)
skipped += 1
continue
re_encrypted = encrypt_value_helper(
plaintext, new_encryption_key=new_master_key
@ -616,6 +807,61 @@ async def rotate_mcp_user_credentials_master_key(
},
data={"credential_b64": re_encrypted},
)
rotated += 1
verbose_proxy_logger.info(
"rotate_mcp_user_credentials_master_key: rotated %d row(s), skipped %d",
rotated,
skipped,
)
async def rotate_mcp_user_env_vars_master_key(
prisma_client: PrismaClient, new_master_key: str
):
"""Re-encrypt every ``LiteLLM_MCPUserEnvVars`` row with ``new_master_key``.
Reads each ``values_b64`` blob with the current salt key and writes it back
encrypted under the new master key. Rows that fail to decrypt are logged and
skipped so one corrupt row does not abort the rotation nor overwrite values
that may still be recoverable.
"""
rows = await prisma_client.db.litellm_mcpuserenvvars.find_many()
rotated = 0
skipped = 0
for row in rows:
plaintext = decrypt_value_helper(
value=row.values_b64,
key="mcp_user_env_vars",
exception_type="debug",
return_original_value=False,
)
if plaintext is None:
verbose_proxy_logger.warning(
"rotate_mcp_user_env_vars_master_key: could not decrypt env vars "
"for user_id=%s server_id=%s, skipping",
row.user_id,
row.server_id,
)
skipped += 1
continue
re_encrypted = encrypt_value_helper(
plaintext, new_encryption_key=new_master_key
)
await prisma_client.db.litellm_mcpuserenvvars.update(
where={
"user_id_server_id": {
"user_id": row.user_id,
"server_id": row.server_id,
}
},
data={"values_b64": re_encrypted},
)
rotated += 1
verbose_proxy_logger.info(
"rotate_mcp_user_env_vars_master_key: rotated %d row(s), skipped %d",
rotated,
skipped,
)
async def store_user_credential(
@ -751,11 +997,14 @@ async def store_user_oauth_credential(
)
def is_oauth_credential_expired(cred: Dict[str, Any]) -> bool:
def is_oauth_credential_expired(cred: Dict[str, Any], buffer_seconds: int = 0) -> bool:
"""Return True if the OAuth2 credential's access_token has expired.
Checks the ``expires_at`` ISO-format string stored in the credential payload.
Returns False when ``expires_at`` is absent or unparseable (treat as non-expired).
With ``buffer_seconds`` > 0, a token that is still valid but expires within the
buffer is also treated as expired, so callers can refresh proactively instead of
handing back a token that may lapse mid-request.
"""
expires_at = cred.get("expires_at")
if not expires_at:
@ -764,7 +1013,7 @@ def is_oauth_credential_expired(cred: Dict[str, Any]) -> bool:
exp_dt = datetime.fromisoformat(expires_at)
if exp_dt.tzinfo is None:
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
return datetime.now(timezone.utc) > exp_dt
return datetime.now(timezone.utc) + timedelta(seconds=buffer_seconds) > exp_dt
except (ValueError, TypeError):
return False
@ -912,6 +1161,50 @@ async def refresh_user_oauth_token(
return await get_user_oauth_credential(prisma_client, user_id, server_id)
async def resolve_valid_user_oauth_token(
user_id: str,
server: Any,
cred: Optional[Dict[str, Any]],
prisma_client: Optional[PrismaClient] = None,
) -> Optional[Dict[str, Any]]:
"""Return an OAuth2 credential whose access_token is good for the next request.
Returns the credential unchanged while its token is valid for at least
``MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS``. Only when the token is expired (or
expiring within that buffer) and a refresh_token is stored does it mint a new one
via ``refresh_user_oauth_token``. Returns None when there is no usable token
(missing token, expired with no refresh_token, or a failed refresh).
The refresh_token is only ever sent to the server's token_url inside
``refresh_user_oauth_token``; it is never exposed to the caller beyond the cred
dict it already holds. ``prisma_client`` is fetched lazily and only when a refresh
actually happens, so the valid-token path never requires a DB handle.
"""
if not cred or not cred.get("access_token"):
return None
if not is_oauth_credential_expired(
cred, buffer_seconds=MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS
):
return cred
if not cred.get("refresh_token"):
return None
if prisma_client is None:
from litellm.proxy.utils import get_prisma_client_or_throw
prisma_client = get_prisma_client_or_throw(
"Database not connected. Cannot refresh OAuth token."
)
refreshed = await refresh_user_oauth_token(
prisma_client=prisma_client,
user_id=user_id,
server=server,
cred=cred,
)
if not refreshed or not refreshed.get("access_token"):
return None
return refreshed
async def approve_mcp_server(
prisma_client: PrismaClient,
server_id: str,
@ -927,7 +1220,9 @@ async def approve_mcp_server(
"updated_by": touched_by,
},
)
return LiteLLM_MCPServerTable(**updated.model_dump())
table = LiteLLM_MCPServerTable(**updated.model_dump())
decrypt_global_env_var_values(table.env_vars)
return table
async def reject_mcp_server(
@ -949,7 +1244,9 @@ async def reject_mcp_server(
where={"server_id": server_id},
data=data,
)
return LiteLLM_MCPServerTable(**updated.model_dump())
table = LiteLLM_MCPServerTable(**updated.model_dump())
decrypt_global_env_var_values(table.env_vars)
return table
async def get_mcp_submissions(
@ -966,6 +1263,8 @@ async def get_mcp_submissions(
take=500, # safety cap; paginate if needed in a future iteration
)
items = [LiteLLM_MCPServerTable(**r.model_dump()) for r in rows]
for item in items:
decrypt_global_env_var_values(item.env_vars)
pending = sum(
1 for i in items if i.approval_status == MCPApprovalStatus.pending_review
@ -980,3 +1279,121 @@ async def get_mcp_submissions(
rejected=rejected,
items=items,
)
# ── Per-user MCP environment variables ────────────────────────────────────
def _decode_user_env_vars(stored: str) -> Dict[str, str]:
"""Decrypt a ``values_b64`` blob and parse it as a flat ``{name: value}`` dict."""
decrypted = decrypt_value_helper(
value=stored,
key="mcp_user_env_vars",
exception_type="debug",
return_original_value=False,
)
if decrypted is None:
if stored:
verbose_proxy_logger.warning(
"MCP per-user env vars failed to decrypt (LITELLM_SALT_KEY "
"changed?); treating as unset so the user is prompted to "
"re-enter them rather than silently forwarding ciphertext"
)
return {}
try:
parsed = json.loads(decrypted)
except (ValueError, TypeError):
return {}
if not isinstance(parsed, dict):
return {}
return {str(k): str(v) for k, v in parsed.items()}
async def get_user_env_vars(
prisma_client: PrismaClient,
user_id: str,
server_id: str,
) -> Dict[str, str]:
"""Return the calling user's env var dict for ``server_id`` (empty if none)."""
row = await prisma_client.db.litellm_mcpuserenvvars.find_unique(
where={"user_id_server_id": {"user_id": user_id, "server_id": server_id}}
)
if row is None:
return {}
return _decode_user_env_vars(row.values_b64)
async def get_user_env_vars_bulk(
prisma_client: PrismaClient,
user_id: str,
server_ids: Iterable[str],
) -> Dict[str, Dict[str, str]]:
"""Return ``{server_id: {var_name: value}}`` for one user across many servers.
Servers with no stored row are simply absent from the result.
"""
ids = list(server_ids)
if not ids:
return {}
rows = await prisma_client.db.litellm_mcpuserenvvars.find_many(
where={"user_id": user_id, "server_id": {"in": ids}}
)
return {row.server_id: _decode_user_env_vars(row.values_b64) for row in rows}
async def merge_user_env_vars(
prisma_client: PrismaClient,
user_id: str,
server_id: str,
updates: Dict[str, str],
allowed_names: Iterable[str],
) -> Dict[str, str]:
"""Merge ``updates`` into the user's stored env vars for ``server_id`` and
return the resulting set.
The read-modify-write runs inside a transaction guarded by a
``(user_id, server_id)`` advisory lock so two concurrent writes from the
same user can't drop one update. Names outside ``allowed_names`` are pruned,
so an admin retiring a user-scoped variable also clears its stored value.
"""
allowed = set(allowed_names)
lock_key = int.from_bytes(
hashlib.blake2b(f"{user_id}:{server_id}".encode(), digest_size=8).digest(),
"big",
signed=True,
)
async with prisma_client.db.tx() as tx:
await tx.execute_raw("SELECT pg_advisory_xact_lock($1::bigint)", lock_key)
row = await tx.litellm_mcpuserenvvars.find_unique(
where={"user_id_server_id": {"user_id": user_id, "server_id": server_id}}
)
existing = _decode_user_env_vars(row.values_b64) if row is not None else {}
merged = {k: v for k, v in {**existing, **updates}.items() if k in allowed}
encoded = encrypt_value_helper(json.dumps(merged))
await tx.litellm_mcpuserenvvars.upsert(
where={"user_id_server_id": {"user_id": user_id, "server_id": server_id}},
data={
"create": {
"user_id": user_id,
"server_id": server_id,
"values_b64": encoded,
},
"update": {"values_b64": encoded},
},
)
return merged
async def delete_user_env_vars(
prisma_client: PrismaClient,
user_id: str,
server_id: str,
) -> None:
"""Remove the calling user's env var values for ``server_id``.
Uses ``delete_many`` so a missing row is a no-op; real DB errors still
propagate to the caller instead of being silently swallowed.
"""
await prisma_client.db.litellm_mcpuserenvvars.delete_many(
where={"user_id": user_id, "server_id": server_id}
)

View file

@ -58,20 +58,26 @@ from litellm.proxy._experimental.mcp_server.sampling_handler import (
from litellm.proxy._experimental.mcp_server.oauth2_token_cache import resolve_mcp_auth
from litellm.proxy._experimental.mcp_server.utils import (
MCP_TOOL_PREFIX_SEPARATOR,
MCPMissingUserEnvVarsError,
add_server_prefix_to_name,
build_env_var_setup_url,
collect_env_var_references,
compute_short_server_prefix,
get_server_prefix,
interpolate_headers,
is_short_mcp_tool_prefix_enabled,
is_tool_name_prefixed,
iter_known_server_prefixes,
merge_mcp_headers,
normalize_server_name,
parse_admin_env_vars,
split_server_prefix_from_name,
validate_mcp_server_name,
)
from litellm.proxy._types import (
LiteLLM_MCPServerTable,
MCPAuthType,
MCPEnvVar,
MCPTransport,
MCPTransportType,
UserAPIKeyAuth,
@ -124,6 +130,33 @@ _AZURE_ENTRA_HOSTS = {
"login.chinacloudapi.cn", # China
}
# Short-lived in-memory cache for per-user MCP env var values, mirroring the
# BYOK credential cache. Keyed by (user_id, server_id); value is
# (values_dict, monotonic_timestamp). Keeps the tool-call and tool-listing
# paths off the DB on every request within the TTL window.
_user_env_vars_cache: Dict[Tuple[str, str], Tuple[Dict[str, str], float]] = {}
_USER_ENV_VARS_CACHE_TTL = 60 # seconds
_USER_ENV_VARS_CACHE_MAX_SIZE = 4096 # cap to prevent unbounded growth
def invalidate_user_env_vars_cache(user_id: str, server_id: str) -> None:
"""Drop a cached entry after the user stores or clears their env var values
so the next request reads the fresh value instead of a stale one."""
_user_env_vars_cache.pop((user_id, server_id), None)
def _write_user_env_vars_cache(
user_id: str, server_id: str, values: Dict[str, str]
) -> None:
cache_key = (user_id, server_id)
# Re-insert at the tail so eviction drops the oldest-written entry, not a
# freshly refreshed one, and only sheds a single entry instead of wiping the
# whole cache (which would stampede the DB).
_user_env_vars_cache.pop(cache_key, None)
if len(_user_env_vars_cache) >= _USER_ENV_VARS_CACHE_MAX_SIZE:
_user_env_vars_cache.pop(next(iter(_user_env_vars_cache)), None)
_user_env_vars_cache[cache_key] = (values, time.monotonic())
def _should_strip_caller_authorization(
mcp_server: MCPServer,
@ -295,6 +328,31 @@ def _deserialize_json_dict(data: Any) -> Optional[Dict[str, str]]:
return data
def _deserialize_json_list(data: Any) -> Optional[List[Dict[str, Any]]]:
"""Deserialize a JSON array stored in the DB (``env_vars`` and friends).
Returns ``None`` for empty / null / unparseable input. Accepts strings
(raw JSON), already-materialized lists of dicts, and lists of Pydantic
models (Prisma may hydrate a JSON column such as ``env_vars`` into
``MCPEnvVar`` objects); model entries are normalized to plain dicts so
downstream consumers expecting ``List[Dict[str, Any]]`` validate.
"""
if data is None or data == "" or data == []:
return None
if isinstance(data, str):
try:
parsed = json.loads(data)
except (json.JSONDecodeError, TypeError):
return None
data = parsed
if not isinstance(data, list):
return None
return [
item.model_dump(mode="json") if hasattr(item, "model_dump") else item
for item in data
]
def _create_sampling_callback(user_api_key_auth: Optional[Any] = None):
"""
Create a sampling callback for MCP ClientSession.
@ -456,7 +514,8 @@ class MCPServerManager:
- server is OpenAPI (spec_path),
- non-empty upstream instructions are already cached,
- auth preconditions match health_check_server's skip rules
(per-user auth / missing static auth token),
(per-user auth / missing static auth token / static headers that
reference a per-user env var),
- a prior probe attempt for this server is within
MCP_HEALTH_CHECK_TIMEOUT seconds (the probe is a health-check-shaped
op and already uses this knob for its inner call timeout; reusing it
@ -471,6 +530,8 @@ class MCPServerManager:
return
if server.requires_per_user_auth:
return
if self._references_per_user_env_var(server):
return
if (
server.auth_type
and server.auth_type != MCPAuth.none
@ -495,8 +556,13 @@ class MCPServerManager:
)
try:
resolved_static_headers = await self._resolve_static_headers_with_env_vars(
server=server,
user_api_key_auth=None,
raise_on_missing=False,
)
extra_headers: Optional[Dict[str, str]] = (
dict(server.static_headers) if server.static_headers else None
dict(resolved_static_headers) if resolved_static_headers else None
)
client = await self._create_mcp_client(
server=server,
@ -656,6 +722,7 @@ class MCPServerManager:
allowed_params=server_config.get("allowed_params", None),
access_groups=server_config.get("access_groups", None),
static_headers=server_config.get("static_headers", None),
env_vars=server_config.get("env_vars", None),
allow_all_keys=bool(server_config.get("allow_all_keys", False)),
available_on_public_internet=bool(
server_config.get("available_on_public_internet", True)
@ -920,17 +987,41 @@ class MCPServerManager:
f"Server ID {mcp_server.server_id} not found in registry"
)
def _resolve_env_vars_list(
self,
mcp_server: LiteLLM_MCPServerTable,
*,
env_vars_are_encrypted: bool,
) -> Optional[List[Dict[str, Any]]]:
env_vars_list = _deserialize_json_list(getattr(mcp_server, "env_vars", None))
if env_vars_are_encrypted:
from litellm.proxy._experimental.mcp_server.db import ( # noqa: PLC0415
decrypt_global_env_var_values,
)
decrypt_global_env_var_values(env_vars_list)
return env_vars_list
async def build_mcp_server_from_table(
self,
mcp_server: LiteLLM_MCPServerTable,
*,
credentials_are_encrypted: bool = True,
env_vars_are_encrypted: Optional[bool] = None,
) -> MCPServer:
_mcp_info: MCPInfo = mcp_server.mcp_info or {}
env_dict = _deserialize_json_dict(getattr(mcp_server, "env", None))
static_headers_dict = _deserialize_json_dict(
getattr(mcp_server, "static_headers", None)
)
env_vars_list = self._resolve_env_vars_list(
mcp_server,
env_vars_are_encrypted=(
credentials_are_encrypted
if env_vars_are_encrypted is None
else env_vars_are_encrypted
),
)
credentials_dict = _deserialize_json_dict(
getattr(mcp_server, "credentials", None)
)
@ -1030,6 +1121,7 @@ class MCPServerManager:
mcp_info=mcp_info,
extra_headers=getattr(mcp_server, "extra_headers", None),
static_headers=static_headers_dict,
env_vars=env_vars_list,
client_id=client_id_value or getattr(mcp_server, "client_id", None),
client_secret=client_secret_value
or getattr(mcp_server, "client_secret", None),
@ -1128,7 +1220,14 @@ class MCPServerManager:
return
try:
if mcp_server.server_id not in self.registry:
new_server = await self.build_mcp_server_from_table(mcp_server)
# Callers hand us a record returned by the db.py read/write
# helpers, which already decrypt global env var values (the
# `credentials` field is the only one still encrypted here).
# Re-decrypting plaintext would zero the values, so build with
# env_vars_are_encrypted=False.
new_server = await self.build_mcp_server_from_table(
mcp_server, env_vars_are_encrypted=False
)
self._assign_unique_short_prefix(new_server)
self.registry[mcp_server.server_id] = new_server
await self._maybe_register_openapi_tools(new_server)
@ -1151,7 +1250,11 @@ class MCPServerManager:
return
try:
if mcp_server.server_id in self.registry:
new_server = await self.build_mcp_server_from_table(mcp_server)
# See add_server: db.py helpers already decrypted env var
# values, so don't decrypt them a second time here.
new_server = await self.build_mcp_server_from_table(
mcp_server, env_vars_are_encrypted=False
)
# Carry the previously-resolved short prefix across so the
# tool names stay stable for clients holding cached lists.
existing_prefix = self.registry[mcp_server.server_id].short_prefix
@ -1572,6 +1675,180 @@ class MCPServerManager:
return resolved_env
def _references_per_user_env_var(self, server: MCPServer) -> bool:
"""True when ``server.static_headers`` reference a per-user ``${NAME}`` env var.
Such placeholders can only be filled from a calling user's stored values,
so a userless probe (health check / instructions prefetch) would forward
the literal ``${NAME}`` upstream and get rejected. Callers skip the probe
and report ``unknown`` instead of a misleading ``unhealthy``.
"""
static_headers = server.static_headers
env_vars = getattr(server, "env_vars", None)
if not static_headers or not env_vars:
return False
_global_values, user_specs = parse_admin_env_vars(env_vars)
user_var_names = {spec["name"] for spec in user_specs}
if not user_var_names:
return False
referenced = collect_env_var_references(strings=static_headers.values())
return bool(referenced & user_var_names)
async def _resolve_static_headers_with_env_vars(
self,
server: MCPServer,
user_api_key_auth: Optional[UserAPIKeyAuth],
*,
raise_on_missing: bool = True,
) -> Optional[Dict[str, str]]:
"""Return server.static_headers with ``${NAME}`` interpolated.
Globals come from ``server.env_vars`` entries with ``scope=="global"``.
Per-user values come from the ``LiteLLM_MCPUserEnvVars`` row for the
calling user.
When ``raise_on_missing`` is ``True`` (the tool-*call* path), raises
``MCPMissingUserEnvVarsError`` if ``static_headers`` reference a per-user
variable the calling user has not yet supplied — converted into a
user-facing 412 by the REST layer.
When ``raise_on_missing`` is ``False`` (the tool-*list* path), missing
per-user vars are non-blocking: we interpolate whatever is available and
leave unfilled ``${NAME}`` references untouched, so the server's tools
still appear in the listing. The user only hits the friendly error when
they actually invoke a tool that needs the missing value.
"""
static_headers = server.static_headers
env_vars = getattr(server, "env_vars", None)
if not static_headers and not env_vars:
return static_headers
global_values, user_specs = parse_admin_env_vars(env_vars)
# An empty-valued global is treated as unset: it must not mask a per-user
# var the user still has to supply, nor override a value the user did
# supply. The unresolved ${NAME} is then left untouched, like any other
# undefined reference.
global_values = {name: value for name, value in global_values.items() if value}
user_var_names = {spec["name"] for spec in user_specs}
# If no env vars are configured, return static_headers as-is.
if not global_values and not user_specs:
return static_headers
# Figure out which user-scoped vars are actually referenced. A var that
# also carries a global value is always covered by that global (globals
# win in the merge below), so it can never be genuinely "missing" even if
# the user hasn't filled it in -- only vars without a global fallback do.
referenced = collect_env_var_references(strings=(static_headers or {}).values())
referenced_user_vars = referenced & user_var_names
required_user_vars = {
name for name in referenced_user_vars if name not in global_values
}
user_values: Dict[str, str] = {}
if required_user_vars:
try:
user_values = await self._load_user_env_vars(server, user_api_key_auth)
except Exception as exc:
# On the tool-call path a DB failure must surface as a real
# server error, not a misleading "set up your credentials" 412.
# On the listing path we stay best-effort and leave the
# unfilled ${NAME} references untouched so tools still appear.
if raise_on_missing:
raise
verbose_logger.warning(
"MCPServerManager: best-effort user env var load failed for "
"server=%s: %s",
server.server_id,
exc,
)
if raise_on_missing:
missing = sorted(
name for name in required_user_vars if not user_values.get(name)
)
if missing:
# A cached negative must never produce a 412: cache
# invalidation is process-local, so a user who just stored
# values on another worker would otherwise be told their
# credentials are missing until the entry expires. Confirm
# against the DB before raising.
user_values = await self._load_user_env_vars(
server, user_api_key_auth, force_refresh=True
)
missing = sorted(
name for name in required_user_vars if not user_values.get(name)
)
if missing:
raise MCPMissingUserEnvVarsError(
server_id=server.server_id,
server_name=server.server_name or server.name,
missing=missing,
setup_url=build_env_var_setup_url(server.server_id),
)
# Only honor stored user values for currently user-scoped vars, and let
# admin globals win, so a stale row from when a var was user-scoped can
# never override the global value the admin set after switching it.
scoped_user_values = {
name: value for name, value in user_values.items() if name in user_var_names
}
merged_vars: Dict[str, str] = {**scoped_user_values, **global_values}
if not static_headers:
return static_headers
return interpolate_headers(static_headers, merged_vars)
async def _load_user_env_vars(
self,
server: MCPServer,
user_api_key_auth: Optional[UserAPIKeyAuth],
*,
force_refresh: bool = False,
) -> Dict[str, str]:
"""Look up the calling user's env var values for ``server``.
Returns an empty dict when no user is available. Results are cached in a
short-lived in-memory map keyed by (user_id, server_id) so the tool-call
and tool-listing paths avoid a DB round-trip per request within the TTL
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). 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 {}
user_id = getattr(user_api_key_auth, "user_id", None)
if not user_id:
return {}
cache_key = (user_id, server.server_id)
if not force_refresh:
cached = _user_env_vars_cache.get(cache_key)
if cached is not None:
values, ts = cached
if time.monotonic() - ts < _USER_ENV_VARS_CACHE_TTL:
return values
from litellm.proxy.proxy_server import prisma_client # noqa: PLC0415
if prisma_client is None:
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,
)
values = await get_user_env_vars(prisma_client, user_id, server.server_id)
_write_user_env_vars_cache(user_id, server.server_id, values)
return values
async def _create_mcp_client(
self,
server: MCPServer,
@ -1732,10 +2009,17 @@ class MCPServerManager:
client = None
try:
if server.static_headers:
# Tool *listing* must not be blocked by missing per-user env vars —
# the server's tools should still appear so the client connects. The
# friendly "missing vars" error is raised only on the tool-*call*
# path (see _call_regular_mcp_tool).
resolved_static_headers = await self._resolve_static_headers_with_env_vars(
server, user_api_key_auth, raise_on_missing=False
)
if resolved_static_headers:
if extra_headers is None:
extra_headers = {}
extra_headers.update(server.static_headers)
extra_headers.update(resolved_static_headers)
# MCPJWTSigner: inject signed JWT for tools/list (list path skips pre_call_hook).
# Skip entirely when the signer is not configured (avoid an unnecessary
@ -3105,10 +3389,17 @@ class MCPServerManager:
continue
extra_headers[header] = header_value
if mcp_server.static_headers:
# Interpolate env vars into static_headers. Raises
# MCPMissingUserEnvVarsError when the calling user has not filled in
# a required per-user variable — the REST layer converts that into
# a friendly 412 with a setup URL.
resolved_static_headers = await self._resolve_static_headers_with_env_vars(
mcp_server, user_api_key_auth
)
if resolved_static_headers:
if extra_headers is None:
extra_headers = {}
extra_headers.update(mcp_server.static_headers)
extra_headers.update(resolved_static_headers)
if hook_extra_headers:
if extra_headers is None:
@ -3566,7 +3857,13 @@ class MCPServerManager:
verbose_logger.debug(
f"Building server from DB: {server.server_id} ({server.server_name})"
)
new_server = await self.build_mcp_server_from_table(server)
# raw_rows come straight from the DB, so their global env var
# values (like credentials) are still encrypted here, unlike the
# already-decrypted records add_server/update_server are handed.
# Decrypt them while building the registry entry.
new_server = await self.build_mcp_server_from_table(
server, env_vars_are_encrypted=True
)
# Carry the cached short_prefix from the previous registry entry
# (if any) so the prefix is stable across reloads.
if existing_server is not None and existing_server.short_prefix:
@ -3906,11 +4203,21 @@ class MCPServerManager:
and not server.authentication_token
):
should_skip_health_check = True
# Skip if static_headers reference a per-user env var: a userless probe
# can't fill ${NAME} and would forward the literal placeholder upstream,
# flipping the server to unhealthy even though real user calls succeed.
elif self._references_per_user_env_var(server):
should_skip_health_check = True
if not should_skip_health_check:
extra_headers = {}
if server.static_headers:
extra_headers.update(server.static_headers)
resolved_static_headers = await self._resolve_static_headers_with_env_vars(
server=server,
user_api_key_auth=None,
raise_on_missing=False,
)
extra_headers = (
dict(resolved_static_headers) if resolved_static_headers else {}
)
client = await self._create_mcp_client(
server=server,
@ -3960,6 +4267,7 @@ class MCPServerManager:
extra_headers=server.extra_headers or [],
mcp_info=server.mcp_info,
static_headers=server.static_headers,
env_vars=self._env_vars_to_models(server.env_vars),
status=status,
last_health_check=datetime.now(),
health_check_error=health_check_error,
@ -4033,6 +4341,14 @@ class MCPServerManager:
return list_mcp_servers
@staticmethod
def _env_vars_to_models(
env_vars: Optional[List[Dict[str, Any]]],
) -> Optional[List[MCPEnvVar]]:
if env_vars is None:
return None
return [MCPEnvVar.model_validate(env_var) for env_var in env_vars]
def _build_mcp_server_table(self, server: MCPServer) -> LiteLLM_MCPServerTable:
return LiteLLM_MCPServerTable(
server_id=server.server_id,
@ -4053,6 +4369,7 @@ class MCPServerManager:
extra_headers=server.extra_headers or [],
mcp_info=server.mcp_info,
static_headers=server.static_headers,
env_vars=self._env_vars_to_models(server.env_vars),
status=None, # No health check performed
last_health_check=None, # No health check performed
health_check_error=None,

View file

@ -1,3 +1,4 @@
import asyncio
import importlib
from datetime import datetime
from typing import (
@ -13,6 +14,7 @@ from typing import (
Union,
)
import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from litellm._logging import verbose_logger
@ -20,7 +22,10 @@ from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthErr
from litellm.proxy._experimental.mcp_server.ui_session_utils import (
build_effective_auth_contexts,
)
from litellm.proxy._experimental.mcp_server.utils import merge_mcp_headers
from litellm.proxy._experimental.mcp_server.utils import (
MCPMissingUserEnvVarsError,
merge_mcp_headers,
)
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
from litellm.proxy.auth.ip_address_utils import IPAddressUtils
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
@ -41,6 +46,28 @@ router = APIRouter(
tags=["mcp"],
)
def _connection_error_message(exc: BaseException) -> str:
if isinstance(exc, httpx.LocalProtocolError):
return (
"Failed to connect to MCP server: a request header is malformed. "
"Check static headers for leading/trailing spaces or illegal characters."
)
if isinstance(exc, (httpx.ConnectError, httpx.ConnectTimeout)):
return (
"Failed to connect to MCP server: the server is unreachable. "
"Check the URL and that the server is running."
)
if isinstance(exc, httpx.TimeoutException):
return "Failed to connect to MCP server: the connection timed out."
if isinstance(exc, httpx.HTTPStatusError):
return (
f"Failed to connect to MCP server: it returned HTTP "
f"{exc.response.status_code}."
)
return "Failed to connect to MCP server. Check proxy logs for details."
if MCP_AVAILABLE:
from mcp.types import Tool as MCPTool
@ -119,9 +146,10 @@ if MCP_AVAILABLE:
try:
from litellm.proxy._experimental.mcp_server.db import (
get_user_oauth_credential,
is_oauth_credential_expired,
resolve_valid_user_oauth_token,
)
prisma_client = None
if prefetched_creds is not None:
cred = prefetched_creds.get(server_id)
else:
@ -133,13 +161,13 @@ if MCP_AVAILABLE:
cred = await get_user_oauth_credential(
prisma_client, user_id, server_id
)
cred = await resolve_valid_user_oauth_token(
user_id=user_id,
server=server,
cred=cred,
prisma_client=prisma_client,
)
if cred and cred.get("access_token"):
if is_oauth_credential_expired(cred):
verbose_logger.debug(
f"_get_user_oauth_extra_headers: token expired for "
f"user={user_id} server={server_id}"
)
return None
return {"Authorization": f"Bearer {cred['access_token']}"}
except Exception as e:
verbose_logger.warning(
@ -812,6 +840,23 @@ if MCP_AVAILABLE:
requested_server_id=canonical_server_id,
)
return result
except MCPMissingUserEnvVarsError as e:
verbose_logger.info(
"MCP tool call missing per-user env vars: server_id=%s missing=%s",
e.server_id,
e.missing,
)
raise HTTPException(
status_code=412,
detail={
"error": "missing_user_env_vars",
"message": str(e),
"server_id": e.server_id,
"server_name": e.server_name,
"missing": e.missing,
"setup_url": e.setup_url,
},
)
except BlockedPiiEntityError as e:
verbose_logger.error(f"BlockedPiiEntityError in MCP tool call: {str(e)}")
raise HTTPException(
@ -961,14 +1006,14 @@ if MCP_AVAILABLE:
return await operation(client)
except (KeyboardInterrupt, SystemExit):
except (KeyboardInterrupt, SystemExit, asyncio.CancelledError):
raise
except BaseException as e:
verbose_logger.error("Error in MCP operation: %s", e, exc_info=True)
return {
"status": "error",
"error": True,
"message": "Failed to connect to MCP server. Check proxy logs for details.",
"message": _connection_error_message(e),
}
async def _preview_openapi_tools(spec_path: str) -> dict:

View file

@ -53,6 +53,7 @@ from litellm.proxy._experimental.mcp_server.utils import (
LITELLM_MCP_SERVER_DESCRIPTION,
LITELLM_MCP_SERVER_NAME,
LITELLM_MCP_SERVER_VERSION,
MCPMissingUserEnvVarsError,
add_server_prefix_to_name,
get_server_prefix,
iter_known_server_prefixes,
@ -720,6 +721,16 @@ if MCP_AVAILABLE:
host_progress_callback=host_progress_callback,
**data, # for logging
)
except MCPMissingUserEnvVarsError as e:
verbose_logger.info(
"MCP mcp_server_tool_call missing per-user env vars: server_id=%s missing=%s",
e.server_id,
e.missing,
)
return CallToolResult(
content=[TextContent(text=str(e), type="text")],
isError=True,
)
except BlockedPiiEntityError as e:
verbose_logger.error(
f"BlockedPiiEntityError in MCP tool call: {str(e)}"
@ -1311,8 +1322,7 @@ if MCP_AVAILABLE:
try:
from litellm.proxy._experimental.mcp_server.db import ( # noqa: PLC0415
get_user_oauth_credential,
is_oauth_credential_expired,
refresh_user_oauth_token,
resolve_valid_user_oauth_token,
)
from litellm.proxy._experimental.mcp_server.oauth2_token_cache import ( # noqa: PLC0415
_compute_per_user_token_ttl,
@ -1332,6 +1342,7 @@ if MCP_AVAILABLE:
return {"Authorization": f"Bearer {cached_token}"}
# ── Slow path: DB lookup ──────────────────────────────────────────
prisma_client = None
if prefetched_creds is not None:
cred = prefetched_creds.get(server_id)
else:
@ -1349,43 +1360,17 @@ if MCP_AVAILABLE:
if not cred or not cred.get("access_token"):
return None
if is_oauth_credential_expired(cred):
verbose_logger.debug(
"_get_user_oauth_extra_headers_from_db: token expired for user=%s server=%s — attempting refresh",
user_id,
server_id,
)
# Attempt token refresh; requires a DB client (not available from prefetch)
if cred.get("refresh_token"):
try:
from litellm.proxy.utils import ( # noqa: PLC0415
get_prisma_client_or_throw,
)
prisma_client = get_prisma_client_or_throw(
"Database not connected. Cannot refresh OAuth token."
)
cred = await refresh_user_oauth_token(
prisma_client=prisma_client,
user_id=user_id,
server=server,
cred=cred,
)
except Exception as refresh_exc:
verbose_logger.warning(
"_get_user_oauth_extra_headers_from_db: refresh failed for user=%s server=%s: %s",
user_id,
server_id,
refresh_exc,
)
cred = None
if not cred or not cred.get("access_token"):
# Clear stale Redis/cache entry so we don't serve it again.
# Do this for both the individual and prefetch paths so the
# next request doesn't get a stale cache hit.
await mcp_per_user_token_cache.delete(user_id, server_id)
return None
cred = await resolve_valid_user_oauth_token(
user_id=user_id,
server=server,
cred=cred,
prisma_client=prisma_client,
)
if cred is None:
# Refresh failed or token expired with no usable refresh_token —
# clear the stale Redis entry so the next request doesn't reuse it.
await mcp_per_user_token_cache.delete(user_id, server_id)
return None
access_token: str = cred["access_token"]

View file

@ -4,11 +4,23 @@ MCP Server Utilities
import json
import re
from typing import Any, Dict, Iterator, Mapping, Optional, Tuple, Union
from typing import (
Any,
Dict,
Iterable,
Iterator,
List,
Mapping,
Optional,
Set,
Tuple,
Union,
)
import hashlib
import importlib
import os
from urllib.parse import quote
# Constants
LITELLM_MCP_SERVER_NAME = "litellm-mcp-server"
@ -370,6 +382,130 @@ def validate_mcp_server_name(
raise Exception(error_message)
class MCPMissingUserEnvVarsError(Exception):
"""Raised when an MCP request can't be built because the calling user has
not supplied one or more required per-user environment variables.
The error message is user-facing and includes a URL the user can visit
to fill them in.
"""
def __init__(
self,
*,
server_id: str,
server_name: Optional[str],
missing: List[str],
setup_url: str,
) -> None:
self.server_id = server_id
self.server_name = server_name
self.missing = missing
self.setup_url = setup_url
label = server_name or server_id
bullet_list = "\n".join(f"- {name}" for name in missing)
message = (
f'Cannot connect to MCP server "{label}".\n\n'
f"Your administrator configured this server to require per-user "
f"variables, but you haven't set the following yet:\n"
f"{bullet_list}\n\n"
f"Set your credentials here:\n"
f"{setup_url}"
)
super().__init__(message)
# Pattern for ``${NAME}`` substitution. Matches the standard env-var
# identifier rules — letters, digits, underscores, can't start with a digit.
_ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
def parse_admin_env_vars(
env_vars: Optional[Iterable[Any]],
) -> Tuple[Dict[str, str], List[Dict[str, Any]]]:
"""Split admin-configured env var entries into globals and per-user specs.
Accepts the raw value of ``MCPServer.env_vars`` (list of dicts or Pydantic
models). Returns:
- ``global_values``: ``{name: value}`` for entries with ``scope=="global"``.
- ``user_specs``: list of ``{name, description}`` for entries with
``scope=="user"`` — these are the names the user must fill in.
Unknown / malformed entries are skipped silently.
"""
global_values: Dict[str, str] = {}
user_specs: List[Dict[str, Any]] = []
if not env_vars:
return global_values, user_specs
for raw in env_vars:
if raw is None:
continue
if hasattr(raw, "model_dump"):
entry = raw.model_dump()
elif isinstance(raw, dict):
entry = raw
else:
continue
name = entry.get("name")
if not isinstance(name, str) or not name:
continue
scope = entry.get("scope") or "global"
if scope == "user":
user_specs.append({"name": name, "description": entry.get("description")})
else:
value = entry.get("value")
global_values[name] = "" if value is None else str(value)
return global_values, user_specs
def find_env_var_references(value: str) -> Set[str]:
"""Return the set of ``${NAME}`` identifiers referenced inside ``value``."""
if not value:
return set()
return set(_ENV_VAR_PATTERN.findall(value))
def collect_env_var_references(*, strings: Iterable[str]) -> Set[str]:
"""Union of every ``${NAME}`` reference across a collection of strings."""
refs: Set[str] = set()
for s in strings:
if isinstance(s, str):
refs |= find_env_var_references(s)
return refs
def interpolate_env_vars(value: str, variables: Mapping[str, str]) -> str:
"""Replace ``${NAME}`` references in ``value`` with the matching mapping
entry. Unknown names are left untouched so callers can detect them via
``find_env_var_references`` on the result if needed.
"""
if not value:
return value
def _sub(match: "re.Match[str]") -> str:
name = match.group(1)
if name in variables:
return variables[name]
return match.group(0)
return _ENV_VAR_PATTERN.sub(_sub, value)
def interpolate_headers(
headers: Mapping[str, str], variables: Mapping[str, str]
) -> Dict[str, str]:
"""Return a copy of ``headers`` with every value passed through ``interpolate_env_vars``."""
return {k: interpolate_env_vars(v, variables) for k, v in headers.items()}
def build_env_var_setup_url(server_id: str) -> str:
"""The frontend URL where a user can fill in their per-user env vars."""
base = os.environ.get("PROXY_BASE_URL", "").rstrip("/")
path = f"/ui/?page=mcp-servers&fill_env_vars={quote(server_id, safe='')}"
return f"{base}{path}" if base else path
def merge_mcp_headers(
*,
extra_headers: Optional[Mapping[str, str]] = None,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,10 +1,10 @@
1:"$Sreact.fragment"
2:I[347257,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"ClientPageRoot"]
3:I[952683,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js"],"default"]
3:I[952683,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js"],"default"]
6:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"]
7:"$Sreact.suspense"
:HL["/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","style"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/52c4ecc57f72065e.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/4bb663ff806dc32f.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/ee8f89c672745c59.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/9710770c6333a72f.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/d734cb3d5659b0da.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/09c1f51da7e82268.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/d028f8c28935d281.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/61aa637257592262.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/bf962cd5264be987.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/37f229ef9335f8c3.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/36d1c027ba991a4f.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/43c3db1352241a8b.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/0ac09b227f50edb4.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/4c848b12d4ecda3d.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/91c828abd7c0aff5.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/08c348f8e09a5cb0.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","async":true}],["$","script","script-24",{"src":"/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","async":true}],["$","script","script-25",{"src":"/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","async":true}],["$","script","script-26",{"src":"/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","async":true}],["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/878832edb30e99a4.js","async":true}],["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/9f1486622270556b.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/bee4095c26818f05.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/81937424fe90f746.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/e2257d8308d35cf4.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2954392b7a60a6a1.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/04711b0f8ffa7bbd.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/eb1ba04e211a533f.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/88c74f8b4b20d25a.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/4cb93eefa53f21a3.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/a1ef280b7ad5ae6a.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/40a2744137b1aec2.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/542a1a209eb732c6.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/84a27349dda457cd.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/8ddf82e7e0b331fc.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/1d7b3500478e93ae.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/f0e079183e7bb90c.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/10757c2146f43db4.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/786e88f4abdd5c58.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/ffa46de7b8384155.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/31275eb5c6f6332f.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/80f4410629229bf9.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/75ee9aba04c74e23.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/193886179a5779b5.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/2063ca6435a47940.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/7e417dd24c8becd0.js","async":true}],["$","script","script-24",{"src":"/litellm-asset-prefix/_next/static/chunks/b323e0ef008e6348.js","async":true}],["$","script","script-25",{"src":"/litellm-asset-prefix/_next/static/chunks/4ac3235460262f36.js","async":true}],["$","script","script-26",{"src":"/litellm-asset-prefix/_next/static/chunks/51494a4a4b6fc437.js","async":true}],["$","script","script-27",{"src":"/litellm-asset-prefix/_next/static/chunks/d7c18aec4a87a237.js","async":true}],["$","script","script-28",{"src":"/litellm-asset-prefix/_next/static/chunks/dac86522fa98e760.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
4:{}
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
8:null

File diff suppressed because one or more lines are too long

View file

@ -3,4 +3,4 @@
3:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}

View file

@ -1,9 +1,9 @@
1:"$Sreact.fragment"
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"]
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"]
5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false}
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,5 +1,5 @@
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
:HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/litellm-asset-prefix/_next/static/chunks/3f3fa56b5786d58c.css","style"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
1:"$Sreact.fragment"
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"]
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"]
5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
7:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"]
@ -10,9 +10,9 @@ a:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/li
c:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"]
e:I[168027,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
0:{"P":null,"b":"TrcGiQpTupSbDYFFfkFHY","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L7",null,{"children":["$","$8",null,{"name":"Next.MetadataOutlet","children":"$@9"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$La",null,{"children":"$Lb"}],["$","div",null,{"hidden":true,"children":["$","$Lc",null,{"children":["$","$8",null,{"name":"Next.Metadata","children":"$Ld"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$e","$undefined"],"S":true}
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
0:{"P":null,"b":"LpqGBJeKQM0vUG-9uVaiY","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L7",null,{"children":["$","$8",null,{"name":"Next.MetadataOutlet","children":"$@9"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$La",null,{"children":"$Lb"}],["$","div",null,{"hidden":true,"children":["$","$Lc",null,{"children":["$","$8",null,{"name":"Next.Metadata","children":"$Ld"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$e","$undefined"],"S":true}
b:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
f:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"]
9:null
d:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$Lf","4",{}]]
d:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$Lf","4",{}]]

View file

@ -3,4 +3,4 @@
3:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}

View file

@ -1,9 +1,9 @@
1:"$Sreact.fragment"
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"]
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"]
5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false}
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,5 +1,5 @@
1:"$Sreact.fragment"
2:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"]
3:"$Sreact.suspense"
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
4:null

View file

@ -1,4 +1,4 @@
1:"$Sreact.fragment"
2:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
3:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}

View file

@ -1,3 +1,3 @@
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
0:{"buildId":"TrcGiQpTupSbDYFFfkFHY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
0:{"buildId":"LpqGBJeKQM0vUG-9uVaiY","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
1:"$Sreact.fragment"
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js"],"AuthProvider"]
2:I[867271,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
3:I[71195,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"default"]
4:I[557951,["/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js"],"AuthProvider"]
5:I[339756,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
6:I[837457,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
7:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"OutletBoundary"]
@ -10,9 +10,9 @@ a:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/li
c:I[897367,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"MetadataBoundary"]
e:I[168027,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"default"]
:HL["/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","style"]
:HL["/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","style"]
0:{"P":null,"b":"TrcGiQpTupSbDYFFfkFHY","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1bcca3c38c9deb02.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/aefca6f40ea185cd.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/632b4c8e836bd956.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L7",null,{"children":["$","$8",null,{"name":"Next.MetadataOutlet","children":"$@9"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$La",null,{"children":"$Lb"}],["$","div",null,{"hidden":true,"children":["$","$Lc",null,{"children":["$","$8",null,{"name":"Next.Metadata","children":"$Ld"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$e","$undefined"],"S":true}
:HL["/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","style"]
0:{"P":null,"b":"LpqGBJeKQM0vUG-9uVaiY","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/4e20891f2fd03463.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/47150bfa067220d3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/59002382e3e0d318.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/25c705f79a0254af.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L7",null,{"children":["$","$8",null,{"name":"Next.MetadataOutlet","children":"$@9"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$La",null,{"children":"$Lb"}],["$","div",null,{"hidden":true,"children":["$","$Lc",null,{"children":["$","$8",null,{"name":"Next.Metadata","children":"$Ld"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$e","$undefined"],"S":true}
b:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
f:I[27201,["/litellm-asset-prefix/_next/static/chunks/d96012bcfc98706a.js","/litellm-asset-prefix/_next/static/chunks/dbca964212122d58.js"],"IconMark"]
9:null
d:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"./favicon.ico"}],["$","$Lf","4",{}]]
d:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.1d32c690.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$Lf","4",{}]]

Some files were not shown because too many files have changed in this diff Show more