Repository navigation
fix(mcp): cap tools preview and test-connection at the listing timeout and name the unreachable upstream - #38791
Conversation
…t and name the unreachable upstream
Greptile SummaryThe PR bounds temporary MCP client creation and preview operations with the existing tool-listing timeout, returning a more actionable error when an upstream does not respond.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/rest_endpoints.py | Adds a bounded timeout around temporary MCP client setup and execution and improves timeout diagnostics without leaving a blocking issue. |
| tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py | Adds focused timeout regression tests and now conforms to the repository’s 120-character line-length requirement. |
Reviews (3): Last reviewed commit: "Merge branch 'litellm_internal_staging' ..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
bugbot run |
There was a problem hiding this comment.
✅ 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 d3268e4. Configure here.
…/litellm into litellm_fix_mcp_oauth_tool_fetch_auth # Conflicts: # litellm/proxy/_experimental/mcp_server/rest_endpoints.py
|
bugbot run |
There was a problem hiding this comment.
✅ 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 cdf8383. Configure here.
TLDR
Problem this solves:
How it solves it:
MCP_TOOL_LISTING_TIMEOUT(30s)User Flow
Before: an admin registering an OAuth MCP server behind a load balancer sees "Received non-JSON response (504: )" in the tools preview and never learns that the proxy cannot reach the upstream
github_mcpserver (URLhttps://api.githubcopilot.com/mcp/), open Edit, pick OAuth2 auth, and click Authorize; the upstream consent page completes and the form comes back holding the new access tokenAuthorization: Bearer <upstream oauth token>and the server config in the bodyhttps://api.githubcopilot.com/mcp/(blocked egress), so it sits on the request for about 62s; the load balancer in front times out at 60s and answers 504 with an empty bodyAfter: the same click comes back inside 30s with a JSON error naming the unreachable upstream URL
github_mcpserver (URLhttps://api.githubcopilot.com/mcp/), open Edit, pick OAuth2 auth, and click Authorize; the upstream consent page completes and the form comes back holding the new access tokenAuthorization: Bearer <upstream oauth token>and the server config in the body{"status":"error","error":true,"message":"Failed to connect to MCP server: no response from https://api.githubcopilot.com/mcp/ within 30s. Check that the LiteLLM proxy can reach this URL from its network (DNS, egress rules, firewalls) and that the server answers MCP requests."}, well before the load balancer's 60sapi.githubcopilot.com, click Authorize again, and the same POST returns 200 with the GitHub tool catalog in about 3sThe Test Connection button on the same form also fires POST https://litellm-domain/mcp-rest/test/tools/list, so it gets the same 30s cap and message
Relevant issues
Linear ticket
Resolves LIT-6477
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito 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).
Screenshots / Proof of Fix
Both legs boot the proxy with
--num_workers 2from the exact commit named in the heading, each with its own Postgres database and master key, on a random free port (before 35306, after 29349). The QA box's shared Postgres sat at its client cap, so both legs exportDATABASE_URLwithconnection_limit=2and the after leg ran against its ownpostgres:16container; the four requests only touch the database for key auth. The requests below are byte-for-byte what the Admin UI's MCP server edit form sends after Authorize:x-litellm-api-keycarries the UI session key andAuthorization: Bearercarries the upstream OAuth token. The browser was not available on the QA box for this run, so the click-through is listed at the end of this section for a hand runConfig:
Boot (per leg, from that leg's checkout):
python litellm/proxy/proxy_cli.py --config config.yaml --port <port> --num_workers 2 --detailed_debugblackhole.jsonis the form's server config pointed at an address that drops SYNs, standing in for a proxy pod with no egress to the upstream:{ "server_id": "", "server_name": "github_mcp", "url": "http://10.255.255.1/mcp/", "transport": "http", "auth_type": "oauth2", "oauth2_flow": "authorization_code", "authorization_url": "https://github.com/login/oauth/authorize", "token_url": "https://github.com/login/oauth/access_token" }github.jsonis the same config with"url": "https://api.githubcopilot.com/mcp/", the customer's real upstream, and its bearer is a real GitHub token ($(gh auth token))Before (7114302)
Tools preview, upstream unreachable from the proxy
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:35306/mcp-rest/test/tools/list -H 'x-litellm-api-key: sk-lit6486-before' -H 'Authorization: Bearer dummy-upstream-oauth-token' -H 'Content-Type: application/json' --data-binary @blackhole.jsonTools preview, real GitHub MCP upstream
curl -m 150 -s -o github_before.json -w 'HTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:35306/mcp-rest/test/tools/list -H 'x-litellm-api-key: sk-lit6486-before' -H "Authorization: Bearer $(gh auth token)" -H 'Content-Type: application/json' --data-binary @github.jsonHTTPSTATUS=200 TIME=4.005674s, andgithub_before.jsonholds 44 tools starting withadd_comment_to_pending_review,add_issue_comment,add_reply_to_pull_request_comment/mcp-rest/test/connection, upstream unreachable from the proxy
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:35306/mcp-rest/test/connection -H 'x-litellm-api-key: sk-lit6486-before' -H 'Authorization: Bearer dummy-upstream-oauth-token' -H 'Content-Type: application/json' --data-binary @blackhole.json/mcp-rest/test/connection, real GitHub MCP upstream
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:35306/mcp-rest/test/connection -H 'x-litellm-api-key: sk-lit6486-before' -H "Authorization: Bearer $(gh auth token)" -H 'Content-Type: application/json' --data-binary @github.jsonAfter (cdf8383)
Tools preview, upstream unreachable from the proxy
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:29349/mcp-rest/test/tools/list -H 'x-litellm-api-key: sk-lit6486-after' -H 'Authorization: Bearer dummy-upstream-oauth-token' -H 'Content-Type: application/json' --data-binary @blackhole.jsonTools preview, real GitHub MCP upstream
curl -m 150 -s -o github_after.json -w 'HTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:29349/mcp-rest/test/tools/list -H 'x-litellm-api-key: sk-lit6486-after' -H "Authorization: Bearer $(gh auth token)" -H 'Content-Type: application/json' --data-binary @github.jsonHTTPSTATUS=200 TIME=2.143061s, andgithub_after.jsonholds the same 44 tools starting withadd_comment_to_pending_review,add_issue_comment,add_reply_to_pull_request_comment/mcp-rest/test/connection, upstream unreachable from the proxy
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:29349/mcp-rest/test/connection -H 'x-litellm-api-key: sk-lit6486-after' -H 'Authorization: Bearer dummy-upstream-oauth-token' -H 'Content-Type: application/json' --data-binary @blackhole.json/mcp-rest/test/connection, real GitHub MCP upstream
curl -m 150 -s -w '\nHTTPSTATUS=%{http_code} TIME=%{time_total}s\n' -X POST http://localhost:29349/mcp-rest/test/connection -H 'x-litellm-api-key: sk-lit6486-after' -H "Authorization: Bearer $(gh auth token)" -H 'Content-Type: application/json' --data-binary @github.jsonObserved alongside the fix:
/mcp-rest/test/connectionrejects OAuth2 staged bearers instantly on both legs; PR leaves it alone/mcp-rest/test/tools/list, not/test/connectionAdmin UI click-through, for a hand run with a proxy at the tip and a pod that cannot reach the upstream (for example a firewall rule dropping traffic to
api.githubcopilot.comfrom the proxy host):github_mcp, URLhttps://api.githubcopilot.com/mcp/, transport HTTP, auth OAuth2, authorization URLhttps://github.com/login/oauth/authorize, token URLhttps://github.com/login/oauth/access_token, plus a GitHub OAuth app client id and secret whose callback is<PROXY_BASE_URL>/callbackFailed to connect to MCP server: no response from https://api.githubcopilot.com/mcp/ within 30s. Check that the LiteLLM proxy can reach this URL from its network (DNS, egress rules, firewalls) and that the server answers MCP requests.after about 30s, where the merge base readsReceived non-JSON response (504: )after 60s behind a load balancer, orTimed out listing tools after 60.0 seconds. The MCP server may be responding slowly or paginating excessively.after about 62s without oneType
🐛 Bug Fix
Caveats (if any)
Medium
LITELLM_MCP_TOOL_LISTING_TIMEOUTraises both caps togetherLow
_create_mcp_clientinstead of injecting itanyiocancel scope but were not exercised_list_tools_operation'smove_on_afterwaits at least 60s, so its "Timed out listing tools" message is unreachable through the endpoint and a per-server timeout above 30s does not extend the preview_execute_with_mcp_clientand still passError in MCP operationhandler; the response body stays the one-line messagelive-pr-risk CHECKED at cdf8383: PASS. Checked on the merged tree (staging 7114302 folded in). Dependents close over two in-file routes,
/mcp-rest/test/connectionand/mcp-rest/test/tools/list, both driven live base 7114302 vs head cdf8383 with 2 workers each against a real GitHub MCP upstream and a blocked-egress address; the one new edge from the merge, #39172's_list_tools_operationdeadline now running inside this PR's 30sfail_afterscope, is what request 1 of the after leg exercises; the dashboard reads the unchanged{status, message}shape; no registry, subclass, kwargs, schema, or DB dependents;tests/llm_translationandtests/local_testinghave zero references to the changed surface. Not verified live: stdio-transport cancellation and the above-60s budget mislabelFinal Attestation
Note
Low Risk
Scoped to admin MCP test/preview helpers and error messaging; behavior change is faster failure for slow/unreachable upstreams, aligned with existing runtime tool listing limits.
Overview
Admin MCP preview/test paths (
_execute_with_mcp_client, used by/mcp-rest/test/tools/listand related flows) now enforce a combined deadline on client creation and the preview operation viaanyio.fail_after, defaulting toMCP_TOOL_LISTING_TIMEOUT(30s). That replaces ~60s hangs that often lose to load balancers and return opaque 504 responses.On timeout,
_connection_error_messagereturns a dedicated message that includes the configured upstream URL, the timeout budget, and hints about proxy egress/DNS/firewalls; other error branches keep their existing wording but use the updated helper signature.Tests cover hanging operations and client creation, the default timeout parameter, and timeout-specific error text.
Reviewed by Cursor Bugbot for commit d3268e4. Bugbot is set up for automated code reviews on this repo. Configure here.