Skip to content

fix(mcp): use stored per-user OAuth token on tool calls - #31326

Open
katzdave wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
katzdave:fix/mcp-use-stored-oauth-token-on-tool-calls
Open

fix(mcp): use stored per-user OAuth token on tool calls#31326
katzdave wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
katzdave:fix/mcp-use-stored-oauth-token-on-tool-calls

Conversation

@katzdave

@katzdave katzdave commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Relates to #31222 (per-user MCP OAuth credential management).
Companion PR: #31327 — persist per-user OAuth client_id so tokens auto-refresh.

Type

🐛 Bug Fix

Changes

call_tool resolved the per-user OAuth token only when the caller sent no Authorization header. On the standard auth path the caller's Authorization is always the LiteLLM admission key, so the stored-token lookup was skipped and the proxy key was forwarded upstream — every per-user OAuth MCP tool call returned 401, while list_tools (which already prefers the stored token) kept working.

_resolve_oauth2_headers_for_tool_call now prefers the user's stored per-user OAuth token whenever one exists, and falls back to the caller's Authorization only for the passthrough cold-start (where the bearer genuinely is the upstream token). This mirrors the list_tools path.

Tests

Adds TestResolveOauth2HeadersForToolCall:

  • stored token overrides the caller's Authorization (the regression)
  • fallback to caller header when no stored token (passthrough cold-start)
  • non-OAuth servers don't trigger a DB lookup
uv run pytest tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py::TestResolveOauth2HeadersForToolCall
# 3 passed

Pre-Submission checklist

  • I have added meaningful tests
  • My PR's scope is as isolated as possible; it only solves 1 specific problem

@CLAassistant

CLAassistant commented Jun 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where call_tool on per-user OAuth MCP servers returned 401 because the caller's LiteLLM admission key was being forwarded upstream instead of the stored per-user OAuth token. The fix removes a premature short-circuit (or oauth2_headers) in _resolve_oauth2_headers_for_tool_call so the stored token always wins when present, mirroring the existing list_tools behavior.

  • Core fix (mcp_server_manager.py): drops the or oauth2_headers guard so that for OAuth2 per-user-token servers, the DB/Redis lookup runs regardless of whether the caller sent an Authorization header; the stored token overrides it, with caller headers as a fallback for the passthrough cold-start.
  • Tests (test_mcp_server_manager.py): adds TestResolveOauth2HeadersForToolCall with three mock-only scenarios — stored token overrides caller header, fallback when no stored token, and no DB lookup for non-OAuth servers. An unrelated nested with block is also reformatted to the parenthesised style.

Confidence Score: 5/5

Safe to merge — the change is surgical, well-tested, and only affects per-user OAuth2 MCP servers; a Redis fast path limits the added lookup overhead.

The one-line guard removal is straightforward and directly mirrors the already-working list_tools code path. The DB lookup is backed by a Redis cache, so every tool call on a per-user OAuth server adds at most a cache miss (first call) rather than a full DB round-trip. All three test scenarios are mock-only and correctly patch the lazy-import site.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Removes the or oauth2_headers short-circuit from _resolve_oauth2_headers_for_tool_call, so the stored per-user OAuth token is always preferred over the caller's Authorization header on OAuth2 per-user-token servers. Falls back to the caller's headers only when no stored token exists.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py Adds TestResolveOauth2HeadersForToolCall with three mock-only tests covering the regression case (stored token overrides caller header), passthrough fallback (no stored token), and non-OAuth server early-return. Existing test formatting is also cleaned up (nested with → parenthesised with).

Reviews (2): Last reviewed commit: "fix(mcp): use stored per-user OAuth toke..." | Re-trigger Greptile

call_tool resolved the per-user OAuth token only when the caller sent no
Authorization header. The LiteLLM admission key always arrives in
Authorization, so the stored-token lookup was skipped and the proxy key was
forwarded upstream; every OAuth MCP tool call returned 401 while tool listing
kept working. _resolve_oauth2_headers_for_tool_call now prefers the stored
token whenever it exists and falls back to the caller header only for the
passthrough cold-start, matching the list_tools path. Adds regression coverage
for the override, the fallback, and the non-OAuth guard
@katzdave

Copy link
Copy Markdown
Contributor Author

@greptileai

@katzdave
katzdave force-pushed the fix/mcp-use-stored-oauth-token-on-tool-calls branch from 36a1adc to 05eba23 Compare June 25, 2026 17:52
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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