From 95f7af2eb4913a71c8852b3f9278090237a71446 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:24:40 -0700 Subject: [PATCH] fix(messages): honor additional_drop_params for mcp_servers and read the beta map only for hosted MCP tools --- litellm/litellm_core_utils/tool_fields.py | 15 ++++++++++++++- litellm/llms/anthropic/chat/transformation.py | 12 ++++++------ .../llms/anthropic/messages/adapters/handler.py | 8 ++++++-- .../messages/responses_adapters/handler.py | 4 ++-- .../test_handler_output_config_passthrough.py | 12 ++++++++++-- .../test_responses_adapters_handler.py | 11 +++++++++-- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/litellm/litellm_core_utils/tool_fields.py b/litellm/litellm_core_utils/tool_fields.py index 860c5ddfe70..8046c1e1373 100644 --- a/litellm/litellm_core_utils/tool_fields.py +++ b/litellm/litellm_core_utils/tool_fields.py @@ -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 diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 48c1fcd45e0..c26cd1dc3fa 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -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) diff --git a/litellm/llms/anthropic/messages/adapters/handler.py b/litellm/llms/anthropic/messages/adapters/handler.py index 86e6b51def8..e1916e00b41 100644 --- a/litellm/llms/anthropic/messages/adapters/handler.py +++ b/litellm/llms/anthropic/messages/adapters/handler.py @@ -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 diff --git a/litellm/llms/anthropic/messages/responses_adapters/handler.py b/litellm/llms/anthropic/messages/responses_adapters/handler.py index 27f2d630260..3ef5ced6657 100644 --- a/litellm/llms/anthropic/messages/responses_adapters/handler.py +++ b/litellm/llms/anthropic/messages/responses_adapters/handler.py @@ -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] = ( diff --git a/tests/unit/llms/anthropic/messages/adapters/test_handler_output_config_passthrough.py b/tests/unit/llms/anthropic/messages/adapters/test_handler_output_config_passthrough.py index 7c7b24e4b71..12769e78329 100644 --- a/tests/unit/llms/anthropic/messages/adapters/test_handler_output_config_passthrough.py +++ b/tests/unit/llms/anthropic/messages/adapters/test_handler_output_config_passthrough.py @@ -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", ) diff --git a/tests/unit/llms/anthropic/messages/responses_adapters/test_responses_adapters_handler.py b/tests/unit/llms/anthropic/messages/responses_adapters/test_responses_adapters_handler.py index 08ef018fd92..90b94285f5d 100644 --- a/tests/unit/llms/anthropic/messages/responses_adapters/test_responses_adapters_handler.py +++ b/tests/unit/llms/anthropic/messages/responses_adapters/test_responses_adapters_handler.py @@ -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, )