Conversation
…docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile OverviewGreptile SummaryThis PR adds a UI + proxy flow for MCP OAuth2, including a new machine-to-machine (client_credentials) mode where the proxy fetches and caches access tokens automatically. Key backend changes:
Key UI changes:
Docs add a dedicated Issues needing fixes before merge:
Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/mcp_server_manager.py | Makes _create_mcp_client async and centralizes auth resolution via resolve_mcp_auth; adjusts tool listing/health-check skips for user-token OAuth servers. |
| litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py | Adds in-memory OAuth2 client_credentials token cache with per-server locks and resolve_mcp_auth priority logic. |
| litellm/proxy/proxy_config.yaml | Adds a localhost OAuth2 test MCP server with hardcoded client credentials to default proxy_config.yaml (should not ship as default). |
| tests/mcp_tests/test_oauth2_e2e.sh | Adds an OAuth2 M2M e2e shell script but it does not start the proxy (will fail due to missing PROXY_PID/closed port). |
| ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx | Refactors OAuth UI section to use OAuthFormFields and adds flow-type selection; includes an accidental '!!!!!!!!!' validation message. |
| ui/litellm-dashboard/src/components/mcp_tools/types.tsx | Adds OAUTH_FLOW constants but still contains a leftover console.log in handleTransport. |
Sequence Diagram
sequenceDiagram
participant UI as LiteLLM UI
participant Proxy as LiteLLM Proxy
participant Manager as MCPServerManager
participant Cache as MCPOAuth2TokenCache
participant Auth as OAuth2 Token URL
participant MCP as MCP Server
UI->>Proxy: POST /mcp-rest/test/tools/list (OAuth2 M2M config)
Proxy->>Manager: _create_mcp_client(server, mcp_auth_header, extra_headers)
Manager->>Cache: resolve_mcp_auth(server, mcp_auth_header)
alt Token cached
Cache-->>Manager: access_token
else Cache miss
Cache->>Auth: POST token_url (grant_type=client_credentials)
Auth-->>Cache: {access_token, expires_in}
Cache-->>Manager: access_token
end
Manager->>MCP: MCP request with Bearer access_token
MCP-->>Manager: MCP response
Manager-->>Proxy: tools list/response
Proxy-->>UI: tools list/response
tests/mcp_tests/test_oauth2_e2e.sh
Outdated
| # ── 3. List tools ──────────────────────────────────────────────────────────── | ||
| echo "" | ||
| echo "=== Request 1: List MCP tools ===" | ||
| curl -s http://localhost:$PROXY_PORT/mcp-rest/tools/list \ | ||
| -H "Authorization: Bearer sk-1234" | python3 -m json.tool |
There was a problem hiding this comment.
Missing proxy startup
This e2e script never starts the LiteLLM proxy, but later calls http://localhost:$PROXY_PORT/... and tries to kill "$PROXY_PID" in cleanup. As written it will fail with PROXY_PID: unbound variable (due to set -u) and the curl calls will hit a closed port. Add the proxy launch step (using $CONFIG) and set PROXY_PID before any requests/cleanup.
| deepwiki: | ||
| transport: "http" | ||
| url: "https://mcp.deepwiki.com/mcp" | ||
| test_oauth2_server: | ||
| url: "http://localhost:8765/mcp" | ||
| transport: "http" | ||
| auth_type: "oauth2" | ||
| client_id: "test-client" | ||
| client_secret: "test-secret" | ||
| token_url: "http://localhost:8765/oauth/token" | ||
|
|
There was a problem hiding this comment.
Test server in default config
test_oauth2_server (localhost URL + hardcoded client_secret) is added to the repo’s default proxy_config.yaml. This config is commonly used as a template/example and can be deployed; shipping a test server entry with credentials is not appropriate for a default config. This should live in test-only config under tests/ or documentation instead.
Additional Comments (2)
The description field rule message is
|
|
@greptile review this again |
Greptile OverviewGreptile SummaryThis PR adds UI support for configuring OAuth2 MCP servers with explicit flow selection (interactive/PKCE vs M2M client_credentials), and updates the proxy’s MCP REST test helper to forward OAuth client credentials into the MCPServer model while dropping incoming Authorization headers for M2M so the proxy can fetch its own token. It also adds unit tests for the new M2M behavior plus a shell E2E script/config intended to validate client_credentials token fetching and tool calls against a mock server. Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/rest_endpoints.py | Adds M2M OAuth2 credential extraction/forwarding and drops incoming auth headers for client_credentials; but now returns raw exception strings to callers (info leak). |
| litellm/proxy/proxy_config.yaml | Adds a localhost OAuth2 MCP test server with hardcoded credentials to the default proxy config (should not ship in default config). |
| tests/mcp_tests/test_oauth2_e2e.sh | Adds OAuth2 client_credentials E2E script, but it assumes python/python3 are installed and still lacks starting the proxy (already noted in prior PR thread). |
| tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py | Adds unit tests covering M2M credential forwarding, dropping incoming Authorization for M2M, and catching BaseExceptionGroup. |
| ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx | Introduces shared OAuth form fields including flow type selection and M2M-required fields; uses Tremor components in new code (Tremor deprecated in repo guidelines). |
| ui/litellm-dashboard/src/components/mcp_tools/types.tsx | Adds OAUTH_FLOW constants but also leaves a console.log in handleTransport(), which should be removed. |
| ui/litellm-dashboard/src/hooks/useTestMCPConnection.tsx | Skips requiring user OAuth token for M2M oauth_flow_type, but compares against hardcoded "m2m" string instead of OAUTH_FLOW constant (easy to drift). |
Sequence Diagram
sequenceDiagram
participant UI as Admin UI
participant Proxy as LiteLLM Proxy (FastAPI)
participant REST as /mcp-rest/test/*
participant Manager as global_mcp_server_manager
participant OAuth as OAuth2 Token Endpoint
participant MCP as MCP Server
UI->>Proxy: POST /mcp-rest/test/tools/list (NewMCPServerRequest)
Proxy->>REST: _execute_with_mcp_client(request, operation)
REST->>REST: _extract_credentials(request.credentials)
REST->>REST: Build MCPServer model (client_id/secret/token_url/scopes)
REST->>REST: If has_client_credentials => drop incoming oauth2_headers
REST->>Manager: _create_mcp_client(server, extra_headers, stdio_env)
alt M2M client_credentials
Manager->>OAuth: POST token_url (grant_type=client_credentials)
OAuth-->>Manager: access_token
end
Manager->>MCP: session.initialize / list tools
MCP-->>Manager: tools list
Manager-->>REST: client
REST-->>Proxy: operation result (tools)
Proxy-->>UI: tools response
| except (KeyboardInterrupt, SystemExit): | ||
| raise | ||
| except BaseException as e: | ||
| verbose_logger.error("Error in MCP operation: %s", e, exc_info=True) | ||
| return { | ||
| "status": "error", | ||
| "message": "An internal error has occurred while testing the MCP server.", | ||
| "error": True, | ||
| "message": f"Failed to connect to MCP server: {e}", |
There was a problem hiding this comment.
Leaking internal exception
_execute_with_mcp_client() now returns the raw exception string in the API response ("message": f"Failed to connect to MCP server: {e}"). This can leak internal details (e.g., backend URLs, stack/context from upstream libs) to any caller of /mcp-rest/test/*. The previous behavior returned a generic message; consider restoring a generic user-facing message and keep details only in logs (or gate detailed errors behind a debug flag).
tests/mcp_tests/test_oauth2_e2e.sh
Outdated
| python "$MOCK_SERVER" & | ||
| MOCK_PID=$! | ||
| sleep 2 | ||
|
|
||
| # Quick smoke test on the token endpoint | ||
| TOKEN_RESP=$(curl -sf http://localhost:$MOCK_PORT/oauth/token \ | ||
| -d "grant_type=client_credentials&client_id=test-client&client_secret=test-secret") |
There was a problem hiding this comment.
E2E script requires python
This script invokes python/python3 (python "$MOCK_SERVER", python3 -m json.tool, python3 -c ...), but the test environment may not have either available (and in this repo’s sandbox they’re absent). As written, the E2E test will fail immediately on systems without Python. Either ensure Python is a declared/installed test dependency in CI for this script, or rewrite the JSON parsing/printing to avoid Python entirely.
Additional Comments (1)
|
…logging_payload is missing (#20851) * fix: Preserved nullable object fields by carrying schema properties * Fix: _convert_schema_types * Fix all mypy issues * Add alert about email notifications * fixing tests * extending timeout for long running tests * Text changes * [Feat] MCP Oauth2 Fixes - Add support for MCP M2M Oauth2 support (#20788) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * remove old files * docs fix * address greptile comments * fix: atomic lock creation + validate JSON response shape - Use dict.setdefault() for atomic per-server lock creation - Add isinstance(body, dict) check before accessing token response fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace asserts with proper guards, wrap HTTP errors with context - Replace `assert` statements with `if/raise ValueError` (asserts can be disabled with python -O in production) - Wrap `httpx.HTTPStatusError` to provide a clear error message with server_id and status code - Add tests for HTTP error and non-dict JSON response error paths - Remove unused imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [UI] M2M OAuth2 UI Flow (#20794) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * ui feat fixes * test M2M * test fix * ui feats * ui fixes * ui fix client ID * fix: backend endpoints * docs fix * fixes greptile --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [Fix] prevent shared backend model key from being polluted by per-deployment custom pricing (#20679) * bug: custom price override for models * added associated test * fix(mcp): resolve OAuth2 root endpoints returning "MCP server not found" (#20784) When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy. * Add support for langchain_aws via litellm passthrough * fix(proxy): return early instead of raising ValueError when standard_logging_payload is missing The `_PROXY_VirtualKeyModelMaxBudgetLimiter.async_log_success_event` hook raises `ValueError` when `standard_logging_payload` is `None`. This breaks non-standard call types (e.g. vLLM `/classify`) that do not populate the payload, and the resulting exception disrupts downstream success callbacks like Langfuse. Return early with a debug log instead, matching the existing pattern used for missing `user_api_key_model_max_budget`. Fixes #18986 --------- Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Shivam Rawat <161387515+shivamrawat1@users.noreply.github.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
…logging_payload is missing (#20851) * fix: Preserved nullable object fields by carrying schema properties * Fix: _convert_schema_types * Fix all mypy issues * Add alert about email notifications * fixing tests * extending timeout for long running tests * Text changes * [Feat] MCP Oauth2 Fixes - Add support for MCP M2M Oauth2 support (#20788) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * remove old files * docs fix * address greptile comments * fix: atomic lock creation + validate JSON response shape - Use dict.setdefault() for atomic per-server lock creation - Add isinstance(body, dict) check before accessing token response fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace asserts with proper guards, wrap HTTP errors with context - Replace `assert` statements with `if/raise ValueError` (asserts can be disabled with python -O in production) - Wrap `httpx.HTTPStatusError` to provide a clear error message with server_id and status code - Add tests for HTTP error and non-dict JSON response error paths - Remove unused imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [UI] M2M OAuth2 UI Flow (#20794) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * ui feat fixes * test M2M * test fix * ui feats * ui fixes * ui fix client ID * fix: backend endpoints * docs fix * fixes greptile --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [Fix] prevent shared backend model key from being polluted by per-deployment custom pricing (#20679) * bug: custom price override for models * added associated test * fix(mcp): resolve OAuth2 root endpoints returning "MCP server not found" (#20784) When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy. * Add support for langchain_aws via litellm passthrough * fix(proxy): return early instead of raising ValueError when standard_logging_payload is missing The `_PROXY_VirtualKeyModelMaxBudgetLimiter.async_log_success_event` hook raises `ValueError` when `standard_logging_payload` is `None`. This breaks non-standard call types (e.g. vLLM `/classify`) that do not populate the payload, and the resulting exception disrupts downstream success callbacks like Langfuse. Return early with a debug log instead, matching the existing pattern used for missing `user_api_key_model_max_budget`. Fixes #18986 --------- Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Shivam Rawat <161387515+shivamrawat1@users.noreply.github.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
[UI] M2M OAuth2 UI Flow
Add support for Adding Oauth2 M2M MCPs through LiteLLM Admin UI
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
✅ Test
Changes