fix(messages): honor additional_drop_params for mcp_servers and read the beta map only for hosted MCP tools

This commit is contained in:
mateo-berri 2026-10-29 17:24:40 -07:00
parent 5bb6fff01e
commit 95f7af2eb4
6 changed files with 47 additions and 15 deletions

View file

@ -247,6 +247,13 @@ def _is_mcp_toolset(tool: object) -> bool:
return False
def drops_mcp_servers(request_kwargs: Mapping[str, object]) -> bool:
additional_drop_params: Final = request_kwargs.get("additional_drop_params")
return drops_unsupported_params(request_kwargs.get("drop_params")) or (
isinstance(additional_drop_params, list) and "mcp_servers" in additional_drop_params
)
def tools_without_mcp_servers(
mcp_servers: object, tools: Sequence[_ToolT], model: str, route: str, drop_params: bool
) -> Sequence[_ToolT]:
@ -270,11 +277,17 @@ _TOOL_SELECTION_PARAMS: Final = frozenset({"tools", "tool_choice", "parallel_too
_ParamsT: Final = TypeVar("_ParamsT", bound=dict[str, object])
def tool_params_without_hosted_mcp(params: _ParamsT, model: str, route: str, drop_params: bool) -> _ParamsT:
def tool_params_without_hosted_mcp(
params: _ParamsT, model: str, route: str, drop_params: bool, runs_hosted_mcp: Callable[[], bool] | None = None
) -> _ParamsT:
tools: Final = params.get("tools")
if not isinstance(tools, list) or not tools:
return params
sent_tools: Final = _TOOLS.validate_python(tools)
if all(_hosted_mcp_server_label(tool) is None for tool in sent_tools) or (
runs_hosted_mcp is not None and runs_hosted_mcp()
):
return params
kept_tools: Final = tools_without_hosted_mcp(tools=sent_tools, model=model, route=route, drop_params=drop_params)
if len(kept_tools) == len(sent_tools):
return params

View file

@ -1828,12 +1828,12 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
model: str,
drop_params: bool,
) -> dict:
request_params: Final = (
non_default_params
if self.runs_hosted_mcp()
else tool_params_without_hosted_mcp(
non_default_params, model=model, route=self._resolved_provider, drop_params=drop_params
)
request_params: Final = tool_params_without_hosted_mcp(
non_default_params,
model=model,
route=self._resolved_provider,
drop_params=drop_params,
runs_hosted_mcp=self.runs_hosted_mcp,
)
is_thinking_enabled: Final = self.is_thinking_enabled(non_default_params=request_params)

View file

@ -15,7 +15,11 @@ from litellm._logging import verbose_logger
from litellm.litellm_core_utils.asyncify import run_async_function
from litellm.litellm_core_utils.core_helpers import drops_unsupported_params
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObject
from litellm.litellm_core_utils.tool_fields import ROUTES_WITHOUT_MCP_SERVERS, tools_without_mcp_servers
from litellm.litellm_core_utils.tool_fields import (
ROUTES_WITHOUT_MCP_SERVERS,
drops_mcp_servers,
tools_without_mcp_servers,
)
from litellm.llms.anthropic.messages.adapters.transformation import (
AnthropicAdapter,
)
@ -620,7 +624,7 @@ class LiteLLMMessagesToCompletionTransformationHandler:
tools or (),
model=model,
route=target_provider,
drop_params=drop_params,
drop_params=drops_mcp_servers(provider_kwargs),
)
if strips_mcp_servers
else tools

View file

@ -20,7 +20,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
prompt_cache_breakpoint_for_wire,
with_prompt_cache_breakpoint,
)
from litellm.litellm_core_utils.tool_fields import tools_without_mcp_servers
from litellm.litellm_core_utils.tool_fields import drops_mcp_servers, tools_without_mcp_servers
from litellm.responses.utils import auto_reasoning_summary
from litellm.types.integrations.custom_logger import CONVERTED_STREAM_KEYS
from litellm.types.llms.anthropic import (
@ -169,7 +169,7 @@ def _build_responses_kwargs(
tools or (),
model=model,
route=target_provider if isinstance(target_provider, str) else "this route",
drop_params=drops_unsupported_params(forwarded_kwargs.get("drop_params")),
drop_params=drops_mcp_servers(forwarded_kwargs),
)
block_rewrite: Final = _prompt_cache_block_rewrite(model, forwarded_kwargs)
request_messages: Final[AnthropicRequestMessages] = (

View file

@ -27,6 +27,7 @@ Tests cover (consolidating PRs #23706 and #22727):
import os
import sys
from collections.abc import Mapping
from typing import Final
from unittest.mock import MagicMock, patch
@ -341,7 +342,14 @@ def test_drop_params_drops_the_mcp_toolset_tools_of_dropped_mcp_servers(
assert ("tool_choice" in completion_kwargs) == bool(kept_tool_names)
@pytest.mark.parametrize("drop_params", ["request", "global"])
_DROP_FORMS: Final[Mapping[str, Mapping[str, object]]] = {
"request": {"drop_params": True},
"global": {},
"additional_drop_params": {"additional_drop_params": ["mcp_servers"]},
}
@pytest.mark.parametrize("drop_params", list(_DROP_FORMS))
def test_drop_params_drops_mcp_servers_instead_of_forwarding_them(
monkeypatch: pytest.MonkeyPatch, drop_params: str
) -> None:
@ -350,7 +358,7 @@ def test_drop_params_drops_mcp_servers_instead_of_forwarding_them(
extra_kwargs={
"mcp_servers": [_DEEPWIKI_SERVER],
"custom_llm_provider": "gemini",
**({"drop_params": True} if drop_params == "request" else {}),
**_DROP_FORMS[drop_params],
},
model="gemini-3.8-flash",
)

View file

@ -780,11 +780,18 @@ def test_mcp_servers_the_responses_bridge_cannot_run_answer_400(monkeypatch: pyt
assert "to gpt-5.6 on openai, so the MCP server `deepwiki` would not run" in rejected.value.message
def test_drop_params_keeps_mcp_servers_off_the_responses_request(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.mark.parametrize(
"drop_form",
[{"drop_params": True}, {"additional_drop_params": ["mcp_servers"]}],
ids=["drop_params", "additional_drop_params"],
)
def test_drop_params_keeps_mcp_servers_off_the_responses_request(
monkeypatch: pytest.MonkeyPatch, drop_form: Mapping[str, object]
) -> None:
monkeypatch.setattr(litellm, "drop_params", False)
responses_kwargs: Final = _build_responses_kwargs(
model="gpt-5.6",
extra_kwargs={**OPENAI_API, "mcp_servers": [_DEEPWIKI_SERVER], "drop_params": True},
extra_kwargs={**OPENAI_API, "mcp_servers": [_DEEPWIKI_SERVER], **drop_form},
max_tokens=1024,
messages=MESSAGES,
)