Skip to content

fix(mcp): restore PKCE-triggering 401 when no stored per-user token exists - #26032

Merged
ishaan-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_pkce_fix_v2
Apr 18, 2026
Merged

fix(mcp): restore PKCE-triggering 401 when no stored per-user token exists#26032
ishaan-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_mcp_pkce_fix_v2

Conversation

@ishaan-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Pre-Submission checklist

  • 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

Type

🐛 Bug Fix

Changes

Per-user OAuth MCP requests now only skip the pre-emptive 401 when a stored token is available. This preserves token-reuse behavior while restoring fast PKCE kickoff for first-time or missing-token users.

Before this fix, the 401 that triggers the PKCE flow was never sent when no stored token existed, leaving new users stuck.

…xists

Per-user OAuth MCP requests now only skip pre-emptive 401 when a stored token is available, preserving token-reuse behavior while restoring fast PKCE kickoff for first-time or missing-token users.
@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.

@ishaan-berri
ishaan-berri changed the base branch from main to litellm_internal_staging April 18, 2026 21:05
@codspeed-hq

codspeed-hq Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Congrats! CodSpeed is installed 🎉

🆕 16 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug in the MCP per-user OAuth flow where first-time users (with no stored token) were never sent the pre-emptive HTTP 401 + WWW-Authenticate header needed to kick off the PKCE authorization flow. The fix adds a DB/cache lookup for the stored token before deciding to continue past the 401 — only skipping it when a valid token already exists.

Confidence Score: 5/5

Safe to merge — the fix is a targeted, logically sound change with appropriate test coverage and no security regressions.

The logic change is minimal and correct: continue (skip 401) now only fires when a stored token is confirmed to exist, otherwise the 401 falls through as intended. Both new tests accurately reflect the fixed behavior. The only remaining note is a P2 performance observation about the double fetch on the happy path, which is mitigated by the Redis cache layer.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/server.py Adds per-user OAuth token check before pre-emptive 401: only skips PKCE 401 when a stored token exists, otherwise falls through to trigger OAuth flow for new users.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py Adds two new integration-style tests covering the fix: missing stored token raises 401, existing stored token skips pre-emptive 401 and forwards to session manager.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[handle_streamable_http_mcp] --> B{mcp_servers loop}
    B --> C{server.auth_type == oauth2\nAND no oauth2_headers?}
    C -- No --> G[Continue to request processing]
    C -- Yes --> D{server.needs_user_oauth_token?}
    D -- No --> F[Raise 401 + WWW-Authenticate\nPKCE kickoff]
    D -- Yes --> E[_get_user_oauth_extra_headers_from_db\nRedis → DB]
    E --> H{stored token found?}
    H -- Yes --> I[continue loop\nskip 401]
    H -- No --> F
    I --> G
    G --> J[_handle_stale_mcp_session]
    J --> K[session_manager.handle_request]
Loading

Reviews (1): Last reviewed commit: "fix(mcp): restore PKCE-triggering 401 wh..." | Re-trigger Greptile

Comment on lines 2678 to +2686
if server.needs_user_oauth_token:
continue
stored_oauth_headers = (
await _get_user_oauth_extra_headers_from_db(
server=server,
user_api_key_auth=user_api_key_auth,
)
)
if stored_oauth_headers:
continue

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 Double DB/cache fetch for stored-token path

When a stored token exists (stored_oauth_headers is truthy and continue is executed), _get_user_oauth_extra_headers_from_db will be invoked a second time during actual request processing at the per-server extra_headers lookup (line ~1315). In the non-test code path this means two round-trips per request, mitigated only by the Redis cache layer. Consider passing the already-fetched stored_oauth_headers value forward (e.g. via a dict keyed by server name) rather than re-fetching it in the downstream path.

This doesn't affect correctness since the Redis cache will serve the second call, but it does add latency when the cache is cold (first request or after eviction).

@codecov

codecov Bot commented Apr 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ishaan-berri
ishaan-berri merged commit ecff06d into litellm_internal_staging Apr 18, 2026
104 of 108 checks passed
@ishaan-berri
ishaan-berri deleted the litellm_mcp_pkce_fix_v2 branch April 18, 2026 21:52
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
fix(mcp): restore PKCE-triggering 401 when no stored per-user token exists
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