Skip to content

[Infra] Merge dev branch - #26197

Merged
yuneng-berri merged 8 commits into
litellm_internal_stagingfrom
litellm_yj_apr20
Apr 21, 2026
Merged

[Infra] Merge dev branch#26197
yuneng-berri merged 8 commits into
litellm_internal_stagingfrom
litellm_yj_apr20

Conversation

@yuneng-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Pre-Submission checklist

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

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • 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).

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Type

🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test

Changes

[Fix] MCP broker OAuth endpoint access controls
Brings user personal budget and organization budget enforcement
in line with the existing key and team patterns, which already
read spend from the atomic cross-pod Redis counter.
…lignment

[Fix] Align user and org budget spend checks with atomic counter pattern
@greptile-apps

greptile-apps Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the cross-pod Redis spend-counter infrastructure to cover user and org budget tracking, migrates the _PROXY_MaxBudgetLimiter pre-call hook to use those counters, adds a security fix that validates redirect_uri scheme on the MCP OAuth proxy endpoints, and adds user_api_key_auth to three previously-public MCP OAuth endpoints.

  • P1 — Breaking change on MCP OAuth endpoints: /authorize, /token, and /register now require user_api_key_auth. MCP clients that call these endpoints before holding a LiteLLM API key (the typical OAuth initiation flow) will receive a 401. A feature flag or migration note should accompany this change per the repo's backwards-compatibility policy.
  • The spend-counter logic (spend:user:*, spend:org:*) is consistent with the existing key/team counter pattern and correctly uses _init_and_increment_spend_counter with Redis-backed DualCache.

Confidence Score: 4/5

Safe to merge after confirming the MCP OAuth endpoint auth addition won't break existing clients.

The spend-counter additions are well-structured and consistent with existing patterns. The redirect_uri scheme validation is a clean security improvement. One P1 concern remains: adding user_api_key_auth to three previously-public MCP OAuth endpoints is a backwards-incompatible behaviour change that may break MCP clients attempting to initiate the OAuth flow without a pre-existing LiteLLM key.

litellm/proxy/management_endpoints/mcp_management_endpoints.py — the auth addition to OAuth proxy endpoints needs confirmation that no existing MCP client relies on the previously-public behaviour.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py Adds redirect_uri scheme validation (http/https only) to prevent open-redirect via non-HTTP schemes — clean security hardening.
litellm/proxy/auth/auth_checks.py Replaces DB-object spend read with cross-pod Redis counter (get_current_spend) for user and org budget checks, and tightens comparison from < to >=.
litellm/proxy/hooks/max_budget_limiter.py Replaces stale cache-row lookup with user_api_key_dict fields + get_current_spend counter; correctly exempts team-key requests from personal budget enforcement.
litellm/proxy/hooks/proxy_track_cost_callback.py Passes org_id to increment_spend_counters so org spend is now tracked in the cross-pod Redis counter — minor plumbing fix.
litellm/proxy/management_endpoints/mcp_management_endpoints.py Adds user_api_key_auth to OAuth proxy endpoints (authorize/token/register) and passes request for IP extraction; the auth addition is a backwards-incompatible change that may break MCP clients initiating the OAuth flow.
litellm/proxy/proxy_server.py Extends increment_spend_counters to atomically track spend:user:{user_id} and spend:org:{org_id} Redis counters; counter is incremented for all requests including team-key ones.
tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py Updates mock assertions to expect request=request kwarg in _get_cached_temporary_mcp_server_or_404 calls — correct reflection of the signature change.

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant Proxy as LiteLLM Proxy
    participant Redis as Redis (spend_counter_cache)
    participant DB as Database

    Note over Client,DB: Budget enforcement flow (new counters)

    Client->>Proxy: API Request (with LiteLLM key)
    Proxy->>Redis: get_current_spend(spend:user:{id})
    Redis-->>Proxy: current spend (or fallback from DB cache)
    Proxy->>Proxy: user_spend >= user_budget?
    alt Over budget
        Proxy-->>Client: 429 / BudgetExceededError
    else Under budget
        Proxy->>DB: Forward request
        DB-->>Proxy: Response + cost
        Proxy->>Redis: increment spend:key:{token}
        Proxy->>Redis: increment spend:team:{team_id}
        Proxy->>Redis: increment spend:user:{user_id}
        Proxy->>Redis: increment spend:org:{org_id}
        Proxy-->>Client: Response
    end

    Note over Client,DB: MCP OAuth proxy flow (now requires auth)

    Client->>Proxy: POST /server/oauth/{id}/register + LiteLLM key
    Proxy->>Proxy: user_api_key_auth check
    Proxy->>DB: register client with external MCP server
    Proxy-->>Client: client credentials
Loading

Reviews (2): Last reviewed commit: "Merge remote-tracking branch 'origin/lit..." | Re-trigger Greptile

Comment on lines +629 to 640
from litellm.proxy.proxy_server import get_current_spend

user_spend = await get_current_spend(
counter_key=f"spend:user:{user_object.user_id}",
fallback_spend=user_object.spend or 0.0,
)
if user_spend >= user_budget:
raise litellm.BudgetExceededError(
current_cost=user_object.spend,
current_cost=user_spend,
max_budget=user_budget,
message=f"ExceededBudget: User={user_object.user_id} over budget. Spend={user_object.spend}, Budget={user_budget}",
message=f"ExceededBudget: User={user_object.user_id} over budget. Spend={user_spend}, Budget={user_budget}",
)

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.

P2 Behavioral change: >>= on user budget check

The original condition user_budget < user_object.spend blocked requests only when spend strictly exceeds the budget. The new condition user_spend >= user_budget also blocks when spend equals the budget exactly. A user who has spent precisely their budget will now be rejected on the next request, whereas previously they could still make one more call. This is likely intentional stricter enforcement, but it's worth documenting in a comment since it's a visible behavior change for anyone sitting exactly at their limit.

Comment on lines 1364 to +1372
@router.get(
"/server/oauth/{server_id}/authorize",
include_in_schema=False,
dependencies=[Depends(user_api_key_auth)],
)
async def mcp_authorize(
request: Request,
server_id: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),

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.

P2 Route-level Depends is redundant with parameter-level Depends

FastAPI deduplicates by callable per request, so having user_api_key_auth in both dependencies=[…] on the route decorator and as an injected parameter means the check only runs once anyway. The decorator entry adds no benefit and can be removed. The same pattern repeats on the /token (line 1410) and /register (line 1454) routes.

Suggested change
@router.get(
"/server/oauth/{server_id}/authorize",
include_in_schema=False,
dependencies=[Depends(user_api_key_auth)],
)
async def mcp_authorize(
request: Request,
server_id: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
@router.get(
"/server/oauth/{server_id}/authorize",
include_in_schema=False,
)
async def mcp_authorize(
request: Request,
server_id: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),

@yuneng-berri
yuneng-berri temporarily deployed to integration-postgres April 21, 2026 23:25 — with GitHub Actions Inactive
@yuneng-berri
yuneng-berri temporarily deployed to integration-postgres April 21, 2026 23:25 — with GitHub Actions Inactive
@yuneng-berri
yuneng-berri temporarily deployed to integration-postgres April 21, 2026 23:25 — with GitHub Actions Inactive
@yuneng-berri
yuneng-berri temporarily deployed to integration-postgres April 21, 2026 23:25 — with GitHub Actions Inactive
@yuneng-berri
yuneng-berri temporarily deployed to integration-postgres April 21, 2026 23:25 — with GitHub Actions Inactive
Comment on lines 1364 to 1368
@router.get(
"/server/oauth/{server_id}/authorize",
include_in_schema=False,
dependencies=[Depends(user_api_key_auth)],
)

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.

P1 Auth on OAuth proxy endpoints may break MCP clients

Adding user_api_key_auth to /authorize, /token, and /register is a backwards-incompatible change (per the repo's policy against breaking changes without a flag). In a standard OAuth 2.0 / MCP OAuth proxy flow, an MCP client (e.g. Claude Desktop) calls the /register dynamic-client-registration endpoint and the /authorize redirect before it possesses a LiteLLM API key — obtaining one is the goal of the flow. Requiring user_api_key_auth on these three endpoints means any MCP client that does not already hold a LiteLLM key will receive a 401 and cannot complete registration or authorization.

If the intent is that only pre-authenticated LiteLLM users can initiate the OAuth proxy flow (i.e. the MCP client always carries the LiteLLM key as a bearer token), this deserves a comment explaining the design and ideally a feature flag (litellm.mcp_oauth_require_auth) so operators who relied on the old public-endpoint behaviour can opt out.

Rule Used: What: avoid backwards-incompatible changes without... (source)

@codecov

codecov Bot commented Apr 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.27273% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/hooks/max_budget_limiter.py 42.85% 4 Missing ⚠️
..._experimental/mcp_server/discoverable_endpoints.py 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@yuneng-berri
yuneng-berri merged commit 93cb065 into litellm_internal_staging Apr 21, 2026
101 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_yj_apr20 branch April 21, 2026 23:55
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
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