From 839e11497473f48afe6f090b7508a7b90af57f59 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 2 Feb 2026 18:16:11 -0800 Subject: [PATCH] async_post_call_response_headers_hook --- litellm/constants.py | 3 ++ .../proxy/hooks/mcp_semantic_filter/hook.py | 47 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/litellm/constants.py b/litellm/constants.py index 6b030c53402..6427c367924 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -77,6 +77,9 @@ DEFAULT_MCP_SEMANTIC_FILTER_TOP_K = int( DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD = float( os.getenv("DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD", 0.3) ) +MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH = int( + os.getenv("MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH", 150) +) # Gemini model-specific minimal thinking budget constants DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH = int( diff --git a/litellm/proxy/hooks/mcp_semantic_filter/hook.py b/litellm/proxy/hooks/mcp_semantic_filter/hook.py index 0a44be922b7..fc9349c2a42 100644 --- a/litellm/proxy/hooks/mcp_semantic_filter/hook.py +++ b/litellm/proxy/hooks/mcp_semantic_filter/hook.py @@ -219,10 +219,13 @@ class SemanticToolFilterHook(CustomLogger): # Always update tools and emit header (even if count unchanged) data["tools"] = filtered_tools - # Store filter stats for response header (following standard hook pattern) + # Store filter stats and tool names for response header filter_stats = f"{original_tool_count}->{len(filtered_tools)}" + tool_names_csv = self._get_tool_names_csv(filtered_tools) + _metadata_variable_name = self._get_metadata_variable_name(data) data[_metadata_variable_name]["litellm_semantic_filter_stats"] = filter_stats + data[_metadata_variable_name]["litellm_semantic_filter_tools"] = tool_names_csv verbose_proxy_logger.info( f"Semantic tool filter: {filter_stats} tools" @@ -243,16 +246,40 @@ class SemanticToolFilterHook(CustomLogger): response: Any, request_headers: Optional[Dict[str, str]] = None, ) -> Optional[Dict[str, str]]: - """ - Add semantic filter stats to response headers. - """ + """Add semantic filter stats and tool names to response headers.""" + from litellm.constants import MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH + _metadata_variable_name = self._get_metadata_variable_name(data) - filter_stats = data[_metadata_variable_name].get("litellm_semantic_filter_stats") - if filter_stats: - return { - "x-litellm-semantic-filter": filter_stats - } - return None + metadata = data[_metadata_variable_name] + + filter_stats = metadata.get("litellm_semantic_filter_stats") + if not filter_stats: + return None + + headers = {"x-litellm-semantic-filter": filter_stats} + + # Add CSV of filtered tool names (nginx-safe length) + tool_names_csv = metadata.get("litellm_semantic_filter_tools", "") + if tool_names_csv: + if len(tool_names_csv) > MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH: + tool_names_csv = tool_names_csv[:MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH - 3] + "..." + + headers["x-litellm-semantic-filter-tools"] = tool_names_csv + + return headers + + def _get_tool_names_csv(self, tools: List[Any]) -> str: + """Extract tool names and return as CSV string.""" + if not tools: + return "" + + tool_names = [] + for tool in tools: + name = tool.get("name", "") if isinstance(tool, dict) else getattr(tool, "name", "") + if name: + tool_names.append(name) + + return ",".join(tool_names) @staticmethod async def initialize_from_config(