Skip to content

fix(mcp): resolve OAuth2 'Capabilities: none' bug for upstream MCP servers#20602

Merged
ishaan-jaff merged 1 commit intoBerriAI:mainfrom
michelligabriele:fix/mcp-oauth2-capabilities-none
Feb 6, 2026
Merged

fix(mcp): resolve OAuth2 'Capabilities: none' bug for upstream MCP servers#20602
ishaan-jaff merged 1 commit intoBerriAI:mainfrom
michelligabriele:fix/mcp-oauth2-capabilities-none

Conversation

@michelligabriele
Copy link
Collaborator

  • process_mcp_request() now falls back to OAuth2 passthrough when Authorization header contains a non-LiteLLM token (catches HTTPException and ProxyException 401/403)
  • MCPClient._get_auth_headers() adds missing MCPAuth.oauth2 case

Relevant issues

Related to MCP Gateway showing "Capabilities: none" after successful OAuth2 authentication with upstream MCP servers (e.g. Atlassian).

Pre-Submission checklist

  • I have Added testing in the tests/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

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:

Type

🐛 Bug Fix

Changes

Root Cause

Two bugs caused the MCP Gateway to return zero tools ("Capabilities: none") after a successful OAuth2 flow with upstream MCP providers like Atlassian:

Bug 1 (primary): In process_mcp_request(), when no x-litellm-api-key header is present, the code falls back to the Authorization header to find a LiteLLM API key. After OAuth2 completes, the client sends Authorization: Bearer <atlassian_token> — this Atlassian token gets passed to user_api_key_auth() which rejects it (not a valid LiteLLM key), killing the entire request.

Bug 2: MCPClient._get_auth_headers() had no case for MCPAuth.oauth2, so OAuth2 tokens stored in self._mcp_auth_value were silently dropped and never sent to the upstream server.

Fix

litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py:

  • Added has_explicit_litellm_key check to distinguish between explicit LiteLLM keys and OAuth2 tokens
  • When no x-litellm-api-key is present but Authorization header exists: try LiteLLM auth first, on 401/403 failure fall back to permissive UserAPIKeyAuth() (OAuth2 passthrough)
  • Catches both HTTPException and ProxyException (which is what user_api_key_auth actually raises in production)
  • Backward compatible: users sending LiteLLM keys in Authorization header still work (try succeeds)

litellm/experimental_mcp_client/client.py:

  • Added MCPAuth.oauth2 case to _get_auth_headers() → generates Authorization: Bearer <token>

Tests Added

  • tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py — 6 new tests in TestMCPOAuth2AuthFlow:
    • OAuth2 token fallback (HTTPException)
    • OAuth2 token fallback (ProxyException)
    • Explicit LiteLLM key with OAuth2 Authorization
    • Backward compat: LiteLLM key in Authorization header
    • Non-auth HTTPException still raises
    • Non-auth ProxyException still raises
  • tests/mcp_tests/test_mcp_client_unit.py — OAuth2 auth header generation + extra_headers override

E2E Verification

Before the fix — OAuth2 token in Authorization header causes 401/500:

$ curl -X POST http://localhost:4000/mcp/ \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer fake-atlassian-oauth2-token" \
  -d '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}, "id": 1}'

Internal Server Error  # ProxyException: LiteLLM Virtual Key expected

After the fix — OAuth2 token passes through, MCP server initializes correctly:

$ curl -X POST http://localhost:4000/mcp/ \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer fake-atlassian-oauth2-token" \
  -d '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}, "id": 1}'

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"litellm-mcp-server","version":"1.0.0"}}}

…rvers

- process_mcp_request() now falls back to OAuth2 passthrough when Authorization header contains a non-LiteLLM token (catches HTTPException and ProxyException 401/403)
- MCPClient._get_auth_headers() adds missing MCPAuth.oauth2 case
@vercel
Copy link

vercel bot commented Feb 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 6, 2026 9:56pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 6, 2026

Greptile Overview

Greptile Summary

This PR fixes the "Capabilities: none" bug where OAuth2 tokens from upstream MCP providers (e.g., Atlassian) were incorrectly rejected as invalid LiteLLM API keys, preventing tool discovery.

Root Causes Fixed

Bug 1 (Primary): process_mcp_request() in user_api_key_auth_mcp.py:117-148 now implements OAuth2 passthrough - when no x-litellm-api-key header is present but an Authorization header exists, it tries LiteLLM validation first, then falls back to permissive mode on 401/403 errors. This allows upstream OAuth2 tokens to pass through while maintaining backward compatibility for LiteLLM keys in the Authorization header.

Bug 2: MCPClient._get_auth_headers() in client.py:212-213 was missing the MCPAuth.oauth2 case, causing OAuth2 tokens stored in _mcp_auth_value to be silently dropped. Now correctly formats them as Authorization: Bearer {token}.

Key Changes

  • Added has_explicit_litellm_key check to distinguish explicit LiteLLM keys from OAuth2 tokens
  • Catches both HTTPException and ProxyException (production exception type) with codes 401/403 for fallback logic
  • Uses str(e.code) for ProxyException comparison (code is converted to string in ProxyException.init)
  • Comprehensive test coverage (6 tests) validates OAuth2 fallback, backward compatibility, and proper exception handling

Testing

All scenarios covered: OAuth2 passthrough (HTTPException & ProxyException), explicit LiteLLM key with OAuth2, backward compatibility, and non-auth exceptions still propagate correctly.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is solid with proper exception handling, comprehensive test coverage (6 new tests covering all edge cases), and maintains backward compatibility. The fix correctly addresses both root causes, uses proper string comparison for ProxyException.code, and includes both unit tests and E2E verification
  • No files require special attention

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py Added OAuth2 passthrough logic with proper exception handling for both HTTPException and ProxyException (401/403), preserving backward compatibility for LiteLLM keys in Authorization header
litellm/experimental_mcp_client/client.py Added missing MCPAuth.oauth2 case to _get_auth_headers() method, correctly formatting Bearer token for OAuth2 authentication
tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py Added comprehensive test suite (6 tests) covering OAuth2 fallback scenarios, backward compatibility, and exception handling for both HTTPException and ProxyException
tests/mcp_tests/test_mcp_client_unit.py Added unit tests for OAuth2 auth header generation and extra_headers override behavior in MCPClient

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant G as Gateway
    participant A as Auth
    participant V as Validator
    participant U as Upstream

    C->>G: MCP Request
    G->>A: Authenticate
    A->>V: Verify
    V-->>A: Rejected
    A->>A: Passthrough
    A-->>G: Proceed
    G->>U: Forward
    U-->>C: Tools
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@ishaan-jaff ishaan-jaff merged commit 6a213fc into BerriAI:main Feb 6, 2026
7 of 8 checks passed
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