Skip to content

fix(mcp_semantic_filter): keep tool names whole in filter response header - #32282

Merged
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_semantic_filter_tool_name_truncation
Jul 7, 2026
Merged

fix(mcp_semantic_filter): keep tool names whole in filter response header#32282
tin-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_semantic_filter_tool_name_truncation

Conversation

@tin-berri

@tin-berri tin-berri commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Resolves LIT-4215

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

The x-litellm-semantic-filter-tools response header is capped at MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH (default 150) characters. Before this change the cap sliced the CSV mid-name and appended ..., so the semantic filter test panel in the admin UI rendered the last Selected Tools entry as a chopped name such as SP.... After this change the header only carries complete tool names and the panel says how many selected names did not fit

Reproduced and verified on a live proxy on localhost:4000 backed by Postgres, hitting the real Bedrock and OpenAI APIs (Claude Haiku 4.5 for the completion, text-embedding-3-small for the semantic router). Proxy config: mcp_semantic_tool_filter enabled with top_k: 8, similarity_threshold: 0.05, and two MCP servers with long aliases (network_metrics_query_mcp, site_inventory_lookup_mcp, both pointing at https://mcp.deepwiki.com/mcp) so the selected tool name CSV exceeds 150 chars

  1. Start the proxy: python litellm/proxy/proxy_cli.py --config lit4215_repro_config.yaml --detailed_debug
  2. Send a chat completion whose tools carry the MCP registry names and inspect the response headers:
curl -sD - -o /tmp/body.json http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bedrock-invoke-haiku-4-5",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "Read the wiki structure and contents of the repo, ask a question about it, and look up the site inventory. Reply in one short sentence."}],
    "tools": [
      {"name": "network_metrics_query_mcp-read_wiki_structure", "description": "Get a list of documentation topics for a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]}},
      {"name": "network_metrics_query_mcp-read_wiki_contents", "description": "View documentation about a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]}},
      {"name": "network_metrics_query_mcp-ask_question", "description": "Ask any question about a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}, "question": {"type": "string"}}, "required": ["repoName", "question"]}},
      {"name": "site_inventory_lookup_mcp-read_wiki_structure", "description": "Get a list of documentation topics for a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]}},
      {"name": "site_inventory_lookup_mcp-read_wiki_contents", "description": "View documentation about a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}}, "required": ["repoName"]}},
      {"name": "site_inventory_lookup_mcp-ask_question", "description": "Ask any question about a GitHub repository", "input_schema": {"type": "object", "properties": {"repoName": {"type": "string"}, "question": {"type": "string"}}, "required": ["repoName", "question"]}}
    ]
  }' | grep -iE "^HTTP|x-litellm-semantic-filter"

Before (unfixed litellm_internal_staging, header is exactly 150 chars and the 4th name is chopped to network_metrics_query_mc...):

HTTP/1.1 200 OK
x-litellm-semantic-filter: 6->5
x-litellm-semantic-filter-tools: network_metrics_query_mcp-ask_question,site_inventory_lookup_mcp-ask_question,site_inventory_lookup_mcp-read_wiki_contents,network_metrics_query_mc...

After (this branch, same request; header is 122 chars, every name complete, the two names that do not fit are dropped whole):

HTTP/1.1 200 OK
x-litellm-semantic-filter: 6->5
x-litellm-semantic-filter-tools: network_metrics_query_mcp-ask_question,site_inventory_lookup_mcp-ask_question,site_inventory_lookup_mcp-read_wiki_contents

Both runs returned a real completion from Bedrock ("model":"bedrock-invoke-haiku-4-5", finish_reason stop)

UI check for the admin panel (Settings, Admin Settings, MCP Semantic Filter, Test tab): the Selected Tools list renders each header entry verbatim. The two screenshots below drive the real MCPSemanticFilterTestPanel with the before/after header from the curl run above (x-litellm-semantic-filter: 6->5 plus the tools CSV), since the live panel test flow emits these headers through the responses MCP path that LIT-4214 restores. Before the fix the list ends in the chopped entry network_metrics_query_mc... with no explanation; after the fix every listed name is complete and a "+2 more selected tools not shown" note accounts for the two names the capped header dropped

Before (chopped final tool name, no explanation):

semantic filter test panel before the fix, showing a chopped final tool name

After (whole tool names plus a note for the ones that did not fit):

semantic filter test panel after the fix, showing whole tool names and a plus 2 more selected tools not shown note

Type

🐛 Bug Fix

Changes

SemanticToolFilterHook.async_post_call_response_headers_hook used to enforce the nginx safe header cap by slicing the tool names CSV mid-name and appending .... The new module level helper _truncate_csv_at_tool_name_boundary drops any name that does not fit whole, so the header always carries complete names in their original order; when not even one name fits the header is omitted rather than emitting a partial name

MCPSemanticFilterTestPanel now renders a secondary "+N more selected tools not shown" note when the parsed header carries fewer names than the selected tool count from x-litellm-semantic-filter, so the shortened list reads as intentional instead of looking truncated

Regression tests: test_semantic_filter_headers_hook_emits_only_complete_tool_names fails on the old slicing behavior (the last emitted entry was a chopped name) and passes with the fix; test_truncate_csv_at_tool_name_boundary_edges pins the boundary cases (exact fit at a comma, mid-name cut, single name longer than the cap). The panel test asserts the note appears when names are missing and stays hidden when the list is complete

Link to Devin session: https://app.devin.ai/sessions/f03da2725ec94d28b3facf766871b102

…ader

The x-litellm-semantic-filter-tools response header was sliced mid-name at
MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH with a trailing "...", so the
admin UI test panel rendered the last selected tool name chopped. Truncate
the CSV at a tool name boundary instead so the header only ever carries
complete names, and note in the test panel how many selected tools did not
fit in the header
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the x-litellm-semantic-filter-tools response header so it only ever carries complete tool names, replacing the previous mid-name slice-and-ellipsis truncation with a comma-boundary-aware helper _truncate_csv_at_tool_name_boundary. When no complete name fits within the nginx-safe cap the header is omitted entirely, and the admin UI now surfaces a "+N more selected tools not shown" note when the header list is shorter than the reported selected count.

  • hook.py: The new module-level helper truncates the CSV by looking one character past max_length to detect whether a comma falls at exactly the boundary, then rsplit(",", 1)[0] to drop any partial name — logic is correct for all boundary cases including exact-fit, mid-name cut, and single-name-longer-than-cap.
  • Tests: Two new fully-mocked unit tests cover the regression scenario (8 long names → only complete names emitted) and pin five boundary conditions on the helper itself; existing test assertions are only strengthened.
  • UI: MCPSemanticFilterTestPanel renders the secondary note only when selectedTools > tools.length, which is the precise condition introduced by the new truncation behaviour.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to header truncation logic with no impact on the request/response path or authentication.

The truncation helper is mathematically correct across all boundary cases (exact fit, mid-name cut, single name exceeding cap, empty input), the hook wiring is a straightforward replacement, the UI addition is a single conditional render, and the new tests are fully mocked and cover the regression path. No auth, database, or critical-path code is touched.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/hooks/mcp_semantic_filter/hook.py Adds _truncate_csv_at_tool_name_boundary helper and replaces mid-name slice+ellipsis with it; logic is correct for all boundary cases.
tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py Adds two new unit tests: one async regression test for the hook and one synchronous boundary-case test for the helper; all mocked, no real network calls.
ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx Adds a '+N more selected tools not shown' note when the header list is shorter than the reported selected count; straightforward conditional render.
ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx Extends existing test with a negative assertion and adds a new test for the '+N more' note; no existing assertions are weakened.

Reviews (2): Last reviewed commit: "fix(mcp_semantic_filter): keep tool name..." | Re-trigger Greptile

@tin-berri

Copy link
Copy Markdown
Contributor Author

Note on the "+N more selected tools not shown" text this PR adds to the test panel: it is a static note, deliberately. The browser only receives the capped header and the names past the 150 char limit are dropped server side, so there is nothing client side to expand into. LIT-4228 tracks the follow-up of returning the full selected tool list in a response body (a dedicated test endpoint or a field on the response the panel already reads) so the panel can list every selected tool, while the header stays capped and whole-name-only for curl and script consumers

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks!

@tin-berri
tin-berri merged commit 5e73994 into litellm_internal_staging Jul 7, 2026
128 checks passed
@tin-berri
tin-berri deleted the litellm_mcp_semantic_filter_tool_name_truncation branch July 7, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants