Skip to content

fix(mcp): defer proxy import so completion(tools=...) works without proxy extras - #32339

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_fix_mcp_handler_fastapi_leak
Jul 7, 2026
Merged

fix(mcp): defer proxy import so completion(tools=...) works without proxy extras#32339
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_fix_mcp_handler_fastapi_leak

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

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

Screenshots / Proof of Fix

Since #31576, litellm/responses/mcp/litellm_proxy_mcp_handler.py imports litellm.proxy.litellm_pre_call_utils at module top, which imports fastapi. completion() imports that module whenever tools is passed (litellm/main.py), so any completion call with tools crashes on a base SDK install (no [proxy] extra) with ModuleNotFoundError: No module named 'fastapi'. This is also why the CodSpeed benchmark job has been red on every run since Jul 2 (its env installs base deps only)

Before the fix, on a base-deps-only env (same install the CodSpeed workflow uses):

$ env PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 uv run --frozen --no-default-groups \
    --with pytest==8.3.5 --with pytest-codspeed==4.3.0 \
    --with "mcp>=1.26.0,<2.0" --with "a2a-sdk>=1.1.0,<2.0" \
    pytest -p pytest_codspeed.plugin tests/benchmarks/ --codspeed -q
>   from fastapi import HTTPException, Request
E   ModuleNotFoundError: No module named 'fastapi'
litellm/proxy/litellm_pre_call_utils.py:9: ModuleNotFoundError
FAILED tests/benchmarks/test_inference_benchmarks.py::test_completion_with_tools
1 failed, 29 passed

After the fix, same command:

30 passed, 2 warnings in 120.32s (0:02:00)

And the end-user scenario, a real OpenAI request with tools on a base-deps-only env:

$ uv run --frozen --no-default-groups python -c '
import litellm
response = litellm.completion(
    model="openai/gpt-5.5",
    messages=[{"role": "user", "content": "What is the weather in San Francisco? Use the tool."}],
    tools=[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a location", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}],
)
print("tool_calls:", response.choices[0].message.tool_calls)'
tool_calls: [ChatCompletionMessageToolCall(function=Function(arguments='{"location":"San Francisco"}', name='get_weather'), id='call_M425bMiagxdJKkSYiGNQodr7', type='function')]

Screen recording of the end-to-end verification on a base SDK install without the proxy extra, showing the crash on the unfixed merge-base and the success on this branch

End-to-end verification of PR 32339

Type

🐛 Bug Fix

Changes

Moves the LiteLLMProxyRequestSetup import from module top into its single call site, which only runs when user_api_key_auth is set (proxy context, where fastapi is guaranteed installed). This matches the module's existing pattern: every other proxy-only dependency in this file is already imported lazily inside the function that needs it, precisely so the module stays importable on base SDK installs

Adds a regression test that runs completion() with function tools in a subprocess where fastapi imports are blocked, so a top-level proxy import can never leak into this path again without failing tests

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

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a ModuleNotFoundError: No module named 'fastapi' crash that affected any litellm.completion() call with tools=... on a base SDK install (no [proxy] extra), introduced since #31576 brought a top-level proxy import into litellm/responses/mcp/litellm_proxy_mcp_handler.py.

  • The fix is a one-line change: the LiteLLMProxyRequestSetup import is moved from module scope into the if user_api_key_auth is not None: branch — the only code path that can execute it, and only in proxy context where fastapi is guaranteed to be present. This matches the lazy-import pattern already used for every other proxy-only dependency in the file.
  • A subprocess-based regression test is added that installs a custom sys.meta_path blocker for fastapi before importing litellm, then asserts that completion() with function tools succeeds; mock_response is used so no network call is made.

Confidence Score: 5/5

The change is minimal and surgical: one import moved from module scope into an already-guarded branch; no logic changes anywhere.

The import relocation is correct and aligns with the existing lazy-import pattern in the file. The new test exercises exactly the broken path using a subprocess fastapi blocker with mock_response (no live network), providing strong regression coverage. No other code paths are touched.

No files require special attention.

Important Files Changed

Filename Overview
litellm/responses/mcp/litellm_proxy_mcp_handler.py Moves LiteLLMProxyRequestSetup import from module top-level into the if user_api_key_auth is not None: guard, eliminating the transitive fastapi import on base SDK installs.
tests/test_litellm/responses/mcp/test_litellm_proxy_mcp_handler.py Adds a subprocess-based regression test that installs a custom sys.meta_path blocker for fastapi and verifies litellm.completion() with tools succeeds; uses mock_response so no real network call is made.

Reviews (2): Last reviewed commit: "fix(mcp): defer proxy import so completi..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yassin-berriai
yassin-berriai force-pushed the litellm_fix_mcp_handler_fastapi_leak branch from 5c3652b to fadfeab Compare July 7, 2026 15:25
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head fadfeab (rebase onto current staging; only conflict was import adjacency with the strip_known_server_prefix change, logic unchanged)

@yassin-berriai
yassin-berriai merged commit db133d4 into litellm_internal_staging Jul 7, 2026
123 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_fix_mcp_handler_fastapi_leak branch July 7, 2026 15:57
CiroGamboa added a commit to Toloka/tolokaforge that referenced this pull request Jul 13, 2026
)

litellm 1.92.0 adds a module-level
`from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup`
to `litellm/responses/mcp/litellm_proxy_mcp_handler.py` (line 20). That
proxy module top-level-imports fastapi, so any base-SDK install that
does not pull the [proxy] extra crashes with
`ModuleNotFoundError: No module named 'fastapi'` the moment
`completion(..., tools=[...])` is called. Upstream tracking:
BerriAI/litellm#32993
(fix: BerriAI/litellm#32339).

Scope of the regression:
- v1.91.3: offending import absent (clean).
- v1.92.0: offending import newly added.
- v1.93.0-rc.1: offending import removed again (fix commit merged).

The upstream fix landed on `litellm_internal_staging` on 2026-07-07 but
was one of the PRs NOT cherry-picked into the v1.92.0 stable cut (that
tag's commit message: "backport 11 staging PRs onto patch-1.92.0rc2 for
the 1.92.0 stable cut"). Single-tag process slip, not a broader pattern.

Pin narrows to `>=1.83.14,!=1.92.0,<2.0.0`, keeping the exclusion
minimal so we auto-adopt the eventual 1.92.1 patch or 1.93.0 stable
without another release. uv.lock stays on 1.87.0.
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.

3 participants