Skip to content

fix(mcp): exclude tools whose prefixed name exceeds the 64 char provider limit - #32319

Open
tin-berri wants to merge 5 commits into
litellm_internal_stagingfrom
litellm_mcp_tool_name_length_validation
Open

fix(mcp): exclude tools whose prefixed name exceeds the 64 char provider limit#32319
tin-berri wants to merge 5 commits into
litellm_internal_stagingfrom
litellm_mcp_tool_name_length_validation

Conversation

@tin-berri

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

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Resolves LIT-4216

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 received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

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

Behavior

The limit is MCP_MAX_TOOL_NAME_LENGTH (default 64, from the Bedrock/OpenAI/Gemini tool name caps), measured against the final listed name, meaning the <alias>-<tool> form LiteLLM sends to LLMs. Override with the LITELLM_MCP_MAX_TOOL_NAME_LENGTH environment variable; 0 or negative disables the feature entirely

  1. At server add time, the tools preview (POST /mcp-rest/test/tools/list, rendered in the create-server form) returns a warning for every tool whose prefixed name will exceed the limit, before anything is persisted. Server creation is not rejected
  2. In the admin UI tool listing (GET /mcp-rest/tools/list, the server's Tools tab), over-limit tools stay visible but are flagged disabled with a disabled_reason; the dashboard renders them grayed out with a Disabled pill and a tooltip carrying the reason, so they do not look like they failed to load
  3. In every LLM-facing listing (MCP tools/list, GET /v1/mcp/tools, tool search, Responses API MCP handler), over-limit tools are excluded entirely so the tool schema sent to providers never contains a name they would reject; each exclusion logs a warning naming the server, the tools, and the remediation
  4. A direct call to a disabled tool (REST POST /mcp-rest/tools/call, MCP JSON-RPC tools/call, Responses API) is intercepted before reaching the upstream and rejected with a clean 400 tool_name_too_long error carrying the same reason, instead of surfacing a provider validation crash
  5. Nothing is persisted about the disabled state: renaming the tool on the MCP server or shortening the server alias re-enables it automatically on the next listing

Screenshots / Proof of Fix

Live proxy on localhost:1337 backed by Postgres, with a real remote MCP server (https://mcp.deepwiki.com/mcp) registered under a 45 character alias so the three prefixed tool names land at 58, 64, and 65 characters, and real AWS Bedrock (Claude Haiku 4.5) completions

Before the fix

$ curl -s -X POST http://localhost:1337/v1/mcp/server -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
    -H "Content-Type: application/json" -d '{
      "server_name": "network_function_inventory_config_audit_agent",
      "alias": "network_function_inventory_config_audit_agent",
      "url": "https://mcp.deepwiki.com/mcp", "transport": "http", "auth_type": "none"}'
{"server_id": "3851738f-5b53-4bea-a472-b21dc58a0fd9", "alias": "network_function_inventory_config_audit_agent", ...}

$ curl -s http://localhost:1337/v1/mcp/tools -H "Authorization: Bearer $LITELLM_MASTER_KEY"   # name lengths annotated
65 network_function_inventory_config_audit_agent-read_wiki_structure
64 network_function_inventory_config_audit_agent-read_wiki_contents
58 network_function_inventory_config_audit_agent-ask_question

Live capture on the pre-PR commit (5e73994441, proxy on localhost:4000): the 65 character name is listed by /v1/mcp/tools, the admin listing carries no disabled field, and no exclusion warning is logged

before: tool listing on the pre-PR commit

The 65 character name is listed, so passing the listed tools to a Bedrock model fails with the exact error from the report

$ curl -s -X POST http://localhost:1337/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
    -H "Content-Type: application/json" -d '{"model": "bedrock-invoke-haiku-4-5", "max_tokens": 64,
      "messages": [{"role": "user", "content": "What documentation topics exist for the BerriAI/litellm repo? Use a tool."}],
      "tools": [<the 3 tools exactly as returned by /v1/mcp/tools>]}'
{"error": {"message": "litellm.BadRequestError: BedrockException - {\"message\":\"1 validation error detected: Value 'network_function_inventory_config_audit_agent-read_wiki_structure' at 'toolConfig.tools.1.member.toolSpec.name' failed to satisfy constraint: Member must have length less than or equal to 64\"} ...", "code": "400"}}

The add-time preview gives no indication either; its response keys are only ['error', 'message', 'tools']

After the fix (same server row, same commands, proxy restarted on this branch)

$ curl -s http://localhost:1337/v1/mcp/tools -H "Authorization: Bearer $LITELLM_MASTER_KEY"
64 network_function_inventory_config_audit_agent-read_wiki_contents
58 network_function_inventory_config_audit_agent-ask_question

The 65 character tool is excluded (64 stays, the limit is inclusive) and the proxy logs why

23:03:36 - LiteLLM:WARNING: mcp_server_manager.py:3062 - MCP server network_function_inventory_config_audit_agent has 1 tool(s) whose name exceeds 64 characters, which providers such as AWS Bedrock, OpenAI, and Gemini reject. Excluding them from tool listings: network_function_inventory_config_audit_agent-read_wiki_structure (65 chars). Use a shorter server alias, rename the tools on the MCP server, or set LITELLM_MCP_MAX_TOOL_NAME_LENGTH to change the limit.

Live capture on the PR commit (3407a2d983, same server row): the 65 character tool is gone from /v1/mcp/tools, the admin listing flags it disabled: True with the reason, and the exclusion warning is logged

after: tool listing on the PR commit

The add-time preview (POST /mcp-rest/test/tools/list, what the UI calls when adding a server) now flags it

"warnings": [
  "Tool 'read_wiki_structure' will be listed as 'network_function_inventory_config_audit_agent-read_wiki_structure' (65 characters), which exceeds the 64 character tool name limit enforced by providers such as AWS Bedrock, OpenAI, and Gemini. LiteLLM will exclude it from tool listings. Use a shorter server alias or rename the tool on the MCP server."
]

The same chat completion built from the now-listed tools succeeds against real Bedrock

$ curl -s -X POST http://localhost:1337/v1/chat/completions ... -d '{..., "tools": [<the 2 tools now returned by /v1/mcp/tools>]}'
model: bedrock-invoke-haiku-4-5
tool_call: network_function_inventory_config_audit_agent-read_wiki_contents

The admin UI tool listing keeps the disabled tool visible with the reason, so it does not look like the tool failed to load

$ curl -s "http://localhost:1337/mcp-rest/tools/list?server_id=<id>" -H "Authorization: Bearer $LITELLM_MASTER_KEY"
read_wiki_structure | disabled: True | reason: Tool name 'network_function_inventory_config_audit_agent-read_wiki_structure' is 65 characters, which exceeds ...
read_wiki_contents | disabled: False
ask_question | disabled: False

In the dashboard Tools tab the row renders grayed out with a red Disabled pill whose tooltip carries the reason. Before the fix the same tool is indistinguishable from the others

before: dashboard Tools tab on the pre-PR commit

After the fix it is grayed out with a red Disabled pill, and hovering the pill shows the reason

after: dashboard Tools tab on the PR commit

A direct call to the disabled tool is rejected with a clean LiteLLM error instead of being forwarded

$ curl -s -w "\nHTTP %{http_code}" -X POST http://localhost:1337/mcp-rest/tools/call -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
    -H "Content-Type: application/json" -d '{"server_id": "<id>", "name": "network_function_inventory_config_audit_agent-read_wiki_structure", "arguments": {"repoName": "BerriAI/litellm"}}'
{"detail":{"error":"tool_name_too_long","message":"Tool name 'network_function_inventory_config_audit_agent-read_wiki_structure' is 65 characters, which exceeds the 64 character tool name limit enforced by providers such as AWS Bedrock, OpenAI, and Gemini. LiteLLM disables it: it is excluded from tool listings sent to LLMs and direct calls are rejected. ..."}}
HTTP 400

$ curl -s -X POST http://localhost:1337/mcp-rest/tools/call ... -d '{"server_id": "<id>", "name": "network_function_inventory_config_audit_agent-ask_question", "arguments": {"repoName": "BerriAI/litellm", "question": "What is litellm?"}}'
isError: False, content starts with "LiteLLM is a unified LLM gateway"

Type

🐛 Bug Fix

Changes

The MCP gateway prefixes every upstream tool name with the server alias (<alias>-<tool>) but nothing anywhere in the stack enforced the 64 character tool name ceiling that AWS Bedrock, OpenAI, and Gemini apply (the only length check is the MCP SDK's 128 character SEP-986 probe), so long prefixed names flowed into LLM requests and failed with a provider 400 at request time

litellm/constants.py adds MCP_MAX_TOOL_NAME_LENGTH (default 64, override or disable via LITELLM_MCP_MAX_TOOL_NAME_LENGTH, zero or negative disables)

litellm/proxy/_experimental/mcp_server/utils.py adds two pure helpers next to add_server_prefix_to_name: split_tools_by_name_length and tool_name_length_warnings

mcp_server_manager.py applies the exclusion at both return points of _get_tools_from_server, so every LLM-facing listing surface is covered by the one seam (MCP JSON-RPC tools/list, GET /v1/mcp/tools, tool search, the Responses API MCP handler, and the OpenAPI-spec branch), logging an actionable warning naming the server, the disabled tools, and the override. The admin UI single-server listing opts out via drop_overlong_names=False so those tools stay visible, and _create_tool_response_objects marks them disabled with a disabled_reason. call_tool, the seam every tool-call surface funnels through (REST, MCP JSON-RPC, Responses API, OpenAPI), rejects calls to a disabled tool with a 400 tool_name_too_long HTTPException that each surface converts to a clean client error

rest_endpoints.py adds a warnings list to the POST /mcp-rest/test/tools/list response (the endpoint the UI calls when adding a server) computed from the would-be prefixed names, which flags the problem at add time before the server is saved

Tests: _get_tools_from_server drops a 65 character prefixed name and keeps a 64 character one (fails without the fix), the unprefixed path measures the raw name, the preview endpoint returns the warning (fails without the fix), and helper edge cases pin the inclusive boundary plus the disable switch. Note tests/.../test_mcp_env_vars.py shows a pre-existing test-isolation flake when the whole mcp_server test directory runs in one process; it reproduces identically on litellm_internal_staging with this change stashed and is unrelated

Follow-up commits address review: preview warnings are skipped in short-prefix mode when the payload has no server_id (the 3 char prefix derives from the id assigned at create time, so alias-based lengths would be false positives), and the exclusion warning labels the server with its alias when that differs from the name. The LITELLM_MCP_MAX_TOOL_NAME_LENGTH environment variable is documented in the environment variables reference (litellm-docs)

The final commit changes the runtime contract per customer feedback: silently hiding the tools from the UI made them look like they failed to load, and leaving them callable meant manual or routed calls still failed downstream. The dashboard Tools tab now shows disabled tools grayed out with a Disabled pill and tooltip (new disabled/disabled_reason fields on ListMCPToolsRestAPIResponseObject), the create-server form renders the preview warnings in the connection status panel, and direct calls are intercepted at the shared call_tool seam with a clean 400 instead of reaching the upstream. Renaming the tool or shortening the alias re-enables everything automatically since nothing is persisted

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


Note

Medium Risk
Changes which tools appear in LLM-facing MCP aggregations and rejects some call_tool requests that previously reached upstream; behavior is configurable via env and covered by tests, but mis-tuned limits or long aliases could hide tools unexpectedly.

Overview
Adds MCP_MAX_TOOL_NAME_LENGTH (default 64, env LITELLM_MCP_MAX_TOOL_NAME_LENGTH; ≤0 disables) and gates MCP tools on the final prefixed name (alias-tool) so Bedrock/OpenAI/Gemini no longer reject whole requests.

LLM-facing listings (_get_tools_from_server with default drop_overlong_names=True) drop over-limit tools and log a warning. call_tool returns HTTP 400 tool_name_too_long before any upstream call.

Admin / preview paths keep those tools visible: REST tool list sets disabled / disabled_reason; add-server test tools list returns a warnings array (skipped in short-prefix mode when server_id is unknown). Dashboard shows preview warnings and grayed-out tools with a Disabled tooltip.

New helpers in utils.py: split_tools_by_name_length, tool_name_length_disabled_reason, tool_name_length_warnings.

Reviewed by Cursor Bugbot for commit 6517b1a. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a real LLM request failure by filtering out MCP tools whose prefixed (<alias>-<tool>) name exceeds the 64-character limit enforced by AWS Bedrock, OpenAI, and Gemini. The limit is configurable via LITELLM_MCP_MAX_TOOL_NAME_LENGTH and can be disabled by setting it to zero or below.

  • LLM-facing paths (tools/list, /v1/mcp/tools, tool search, Responses API) silently drop over-limit tools and log an actionable warning; call_tool intercepts direct calls to a disabled tool with a clean 400 tool_name_too_long HTTPException before reaching the upstream.
  • Admin UI paths keep the over-limit tools visible: the REST list annotates each one with disabled/disabled_reason, the dashboard renders them grayed out with a Disabled pill and tooltip, and the add-server preview returns a warnings list so operators see the problem before saving.
  • All tests use mocked network calls; the helper edge cases, inclusive boundary, and the call_tool early-rejection path are all covered.

Confidence Score: 5/5

Safe to merge; the change is well-scoped to MCP tool listing paths and is gated by a configurable constant that can be disabled at runtime.

The exclusion logic is applied at a single seam (_get_tools_from_server) with a clearly-named opt-out flag for admin paths, the 400 intercept in call_tool normalizes both prefixed and unprefixed inputs correctly before measuring, and every code path added here is covered by dedicated mocked unit tests. No correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
litellm/constants.py Adds MCP_MAX_TOOL_NAME_LENGTH constant (default 64), consistent with existing pattern of reading env vars at module import time.
litellm/proxy/_experimental/mcp_server/utils.py Adds three pure helpers: split_tools_by_name_length, tool_name_length_disabled_reason, and tool_name_length_warnings. All are correctly implemented with the inclusive boundary check and ≤0 disables the feature.
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Adds drop_overlong_names parameter to _get_tools_from_server, _drop_tools_exceeding_name_length helper, and intercepts call_tool with a 400 for disabled tools. Both prefixed and unprefixed call_tool inputs are normalized correctly before the length check.
litellm/proxy/_experimental/mcp_server/rest_endpoints.py Adds disabled/disabled_reason to tool response objects for admin UI, and preview warnings to the test_tools_list endpoint; _execute_with_mcp_client passes through the operation result dict unchanged so warnings flow correctly.
litellm/proxy/_experimental/mcp_server/server.py Adds optional disabled and disabled_reason fields to ListMCPToolsRestAPIResponseObject with safe defaults (False/None), preserving backward compatibility.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py New TestToolNameLengthExclusion class tests the full exclusion path end-to-end with mocked clients; verifies both the drop and the 400 rejection in call_tool with both prefixed and unprefixed names.
tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py Tests add-time preview warnings, short-prefix mode suppression, OpenAPI path warnings, and disabled annotation in the admin listing. All use mocked network calls; no real requests.
ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx Renders disabled tools grayed out with a Disabled pill (Ant Design Tooltip wrapping a span) showing the disabled_reason; correctly checks tool.disabled before applying the selected-tool styling.
ui/litellm-dashboard/src/hooks/useTestMCPConnection.tsx Adds toolsWarnings state, correctly initializes to [] on error/clear, adds alias to dependency array so the preview refetches on alias change.

Reviews (8): Last reviewed commit: "fix(mcp): carry preview length warnings ..." | Re-trigger Greptile

Comment thread litellm/proxy/_experimental/mcp_server/rest_endpoints.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.61905% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...oxy/_experimental/mcp_server/mcp_server_manager.py 93.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

1 similar comment
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri tin-berri closed this Jul 7, 2026
@tin-berri tin-berri reopened this Jul 7, 2026
@tin-berri
tin-berri force-pushed the litellm_mcp_tool_name_length_validation branch from ca303e2 to e1cd771 Compare July 7, 2026 17:48
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 11.42%

❌ 1 regressed benchmark
✅ 29 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_completion_simple_message 4.2 ms 4.7 ms -11.42%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing litellm_mcp_tool_name_length_validation (6517b1a) with litellm_internal_staging (b8248a2)

Open in CodSpeed

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

Comment thread ui/litellm-dashboard/src/hooks/useTestMCPConnection.tsx
Comment thread litellm/proxy/_experimental/mcp_server/rest_endpoints.py Outdated
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit fbdbebd. Configure here.

tin-berri added 5 commits July 7, 2026 14:02
…der limit

Providers such as AWS Bedrock, OpenAI, and Gemini reject tool names longer
than 64 characters, and the MCP gateway prefixes every upstream tool name
with the server alias, so long prefixed names flowed into LLM requests and
failed with a provider 400. Tool listings now exclude names over the limit
with an actionable warning, and the add-time tools preview flags them.
Excluded tools stay callable by name; LITELLM_MCP_MAX_TOOL_NAME_LENGTH
overrides or disables the limit.
Skip speculative preview warnings in short-prefix mode when the payload has
no server_id (the 3 char prefix derives from the id assigned at create time)
and label the exclusion warning with the alias when it differs from the
server name so the prefix in the dropped tool name is traceable.
Per review of the exclusion contract: the admin UI tool listing now keeps
tools whose prefixed name exceeds the provider limit visible, flagged
disabled with the reason (grayed row and tooltip in the dashboard), while
LLM-facing listings keep excluding them. Direct calls to a disabled tool
are rejected at the shared call_tool seam with a clean 400
tool_name_too_long error on every surface (REST, MCP JSON-RPC, Responses
API) instead of being forwarded upstream. The add-time preview warnings
now render in the create-server form
…ng tests

The MagicMock servers in tests/mcp_tests leaked auto-generated short_prefix
and server_name mocks into the prefix normalization added for the disabled
tool annotation, and the call assertion was missing the new
drop_overlong_names kwarg
…m alias

Per review: the create-server preview request now includes the alias the
user typed so warnings measure the prefix the runtime will actually apply,
and the OpenAPI spec preview branch returns the same warnings array as the
MCP branch since registered OpenAPI tools get the server prefix too
@tin-berri
tin-berri force-pushed the litellm_mcp_tool_name_length_validation branch from fbdbebd to 6517b1a Compare July 7, 2026 21:04
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri

Copy link
Copy Markdown
Contributor Author

CI note: every check passes except CodSpeed, which flags test_completion_simple_message at about -10%. That benchmark exercises litellm.completion with a mock response; tracing sys.modules after running it shows none of the four modules this PR changes (mcp_server_manager, rest_endpoints, server, utils under litellm/proxy/_experimental/mcp_server) are ever imported on that path. The only touched file that loads is litellm/constants.py, which gains a single module-level int(os.getenv(...)) read, and imports happen before the measured benchmark loop. CodSpeed's own report also carries a 'different runtime environments detected' accuracy warning for this comparison, the check was re-enabled on the base branch earlier today (#32340), and other unrelated open PRs are currently failing it as well. The regression is an artifact of the cross-environment baseline; acknowledging it on the CodSpeed dashboard is the remaining step

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6517b1a. Configure here.

@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.

Why can't they just use the shortened mapping for the mcp server name? Also, not all clients reject tools over 64 chars right?

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