mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(mcp): address bugbot user_fields + BYOK conflict and header dedup
- Reject combining is_byok=True with non-empty user_fields at server create/update time: both share the (user_id, server_id) credential row and the store paths refuse cross-type overwrites, so the combination trapped users in an unresolvable 401 loop. - Make the managed MCP dispatch path apply user-field headers with the same case-insensitive precedence as the OpenAPI/local path, so a static_header named 'Authorization' is no longer left alongside a user-field header named 'authorization' on the outbound request.
This commit is contained in:
parent
bd9a6e4872
commit
a6ae106cf1
2 changed files with 37 additions and 1 deletions
|
|
@ -2827,7 +2827,16 @@ class MCPServerManager:
|
|||
if user_field_headers:
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
extra_headers.update(user_field_headers)
|
||||
# Match the OpenAPI/local path's case-insensitive precedence: a
|
||||
# user-field header replaces any existing header (e.g. from
|
||||
# static_headers) whose name matches case-insensitively, instead
|
||||
# of leaving two differently-cased duplicates on the wire.
|
||||
existing_lower_names = {k.lower(): k for k in extra_headers}
|
||||
for header_name, value in user_field_headers.items():
|
||||
collision = existing_lower_names.get(header_name.lower())
|
||||
if collision is not None and collision != header_name:
|
||||
del extra_headers[collision]
|
||||
extra_headers[header_name] = value
|
||||
|
||||
if hook_extra_headers:
|
||||
if extra_headers is None:
|
||||
|
|
|
|||
|
|
@ -216,6 +216,33 @@ if MCP_AVAILABLE:
|
|||
def validate_and_normalize_mcp_server_payload(payload: Any) -> None:
|
||||
_base_validate_and_normalize_mcp_server_payload(payload)
|
||||
_validate_mcp_server_name_fields(payload)
|
||||
_validate_mcp_user_fields_byok_exclusive(payload)
|
||||
|
||||
def _validate_mcp_user_fields_byok_exclusive(payload: Any) -> None:
|
||||
"""Reject server configurations that combine is_byok with user_fields.
|
||||
|
||||
Both credential types share the same (user_id, server_id) row in
|
||||
LiteLLM_MCPUserCredentials, and the store paths refuse to overwrite
|
||||
the other type — so a user can save BYOK or user-fields for a given
|
||||
server, never both. Allowing this combination at admin time would
|
||||
trap end-users in an unresolvable state: every tool call would 401
|
||||
on whichever check the user has not (and cannot) satisfy.
|
||||
"""
|
||||
if not getattr(payload, "is_byok", False):
|
||||
return
|
||||
user_fields = getattr(payload, "user_fields", None) or []
|
||||
if user_fields:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"error": (
|
||||
"MCP servers cannot enable both is_byok and user_fields. "
|
||||
"Use user_fields if the server needs multiple per-user "
|
||||
"values; use is_byok only for a single legacy BYOK "
|
||||
"credential."
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
_VALID_MCP_REQUIRED_FIELDS: frozenset = frozenset(NewMCPServerRequest.model_fields)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue