litellm - ✅(Solved) Fix MCP UI: Edit Settings → Tool Configuration fails for OAuth2 M2M servers (credentials redacted) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#24710Fetched 2026-04-08 01:42:04
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Participants

When editing an existing MCP server with OAuth2 M2M (client_credentials) auth, the Edit Settings → Tool Configuration panel shows:

Unable to load tools Failed to connect to MCP server. Check proxy logs for details.

Error Message

  1. has_client_credentials is False → no M2M token fetch → connection fails with generic error
  2. Observe "Unable to load tools" error

Root Cause

The edit form populates formValues from the server record returned by GET /v1/mcp/server/<id>. For OAuth2 servers, credentials is always null in API responses (encrypted at rest, redacted for security). The UI then calls POST /mcp-rest/test/tools/list with the form values — but since client_id and client_secret are missing, the test endpoint cannot fetch an OAuth2 token and the connection fails.

Code path:

  1. mcp_tool_configuration.tsxuseTestMCPConnection hook → testMCPToolsListRequest
  2. Backend: rest_endpoints.py _execute_with_mcp_client()_extract_credentials() returns (None, None, None)
  3. has_client_credentials is False → no M2M token fetch → connection fails with generic error

Fix Action

Workaround

Use the API to manage allowed_tools:

curl -X PUT "$LITELLM_URL/v1/mcp/server" \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": "<server_id>",
    "transport": "http",
    "auth_type": "oauth2",
    "available_on_public_internet": true,
    "allowed_tools": ["tool1", "tool2"]
  }'

PR fix notes

PR #25535: fix(mcp): M2M OAuth + oauth2_flow, dashboard UX, health refresh

Description (problem / solution / changelog)

Relevant issues

Fixes #24710

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
  • [ x I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer 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:

Type

<!-- Select the type of Pull Request --> <!-- Keep only the necessary ones -->

🐛 Bug Fix
✅ Test

Changes

Summary

Improves MCP OAuth end-to-end: client credentials (M2M) vs authorization code (interactive/PKCE) is stored and respected (oauth2_flow), the dashboard stays in sync with the selected grant type and loads tools via the same registry path as the MCP Tools tab, MCP health runs correctly for M2M when credentials live only in the DB, and the server list refreshes health after create without a full page reload.

Backend ↔ frontend alignment

This PR is partly about keeping the proxy and the dashboard in agreement:

  • Grant type: The UI’s M2M vs interactive choice maps to oauth2_flow on the server (client_credentials vs authorization_code) and back when loading edit forms, instead of inferring only from token_url.
  • Tools on edit: Edit MCP → Tools uses listMCPTools(server_id), the same registry-backed path the proxy uses for MCP Tools, so behavior matches secrets that exist only in the DB (unlike ad hoc /test/tools/list body assembly).
  • Health: The M2M health-check path on the backend matches how the UI labels “healthy” (refetch health after create so the list does not sit on unknown while the server is actually fine).

Dashboard (ui/litellm-dashboard)

  • Create MCP: isM2M derives from Form.useWatch("oauth_flow_type") (with M2M default), fixing the case where the grant type showed M2M but PKCE fields appeared until the user interacted.
  • MCP server list: After a successful create, refetch both the MCP servers query and useMCPServerHealth so the new row shows real health instead of staying unknown until refresh or the 30s poll.
  • useMCPServerHealth: When merging a targeted health response into the React Query cache, upsert by server_id so new servers and single-server rechecks update correctly.
  • Edit MCP: Replaces duplicated OAuth JSX with shared OAuthFormFields; loads tools via listMCPTools(server_id) and passes externalTools / loading / error into MCPToolConfiguration so OAuth secrets resolved on the proxy match the MCP Tools tab.

Proxy / schema / extras

  • Persist and round-trip oauth2_flow (client_credentials vs authorization_code) on MCP servers (management API, types, Prisma schema where applicable).
  • mcp_server_manager: Health check does not skip M2M OAuth when only client credentials are configured (no pre-filled bearer token).
  • litellm-proxy-extras: Migration adding oauth2_flow to the MCP server table where required (empty placeholder migrations removed).

Tests

  • Extends test_mcp_server_manager for M2M health behavior.
  • Adds test_oauth2_flow_utils (or equivalent) for OAuth flow helpers.

Changed files

  • litellm-proxy-extras/litellm_proxy_extras/migrations/20260410120000_add_oauth2_flow_to_mcp_server_table/migration.sql (added, +3/-0)
  • litellm-proxy-extras/litellm_proxy_extras/schema.prisma (modified, +2/-0)
  • litellm/proxy/_experimental/mcp_server/db.py (modified, +18/-0)
  • litellm/proxy/_experimental/mcp_server/mcp_server_manager.py (modified, +22/-4)
  • litellm/proxy/_experimental/mcp_server/oauth2_flow_utils.py (added, +83/-0)
  • litellm/proxy/_experimental/mcp_server/server.py (modified, +4/-6)
  • litellm/proxy/_types.py (modified, +2/-0)
  • litellm/proxy/management_endpoints/mcp_management_endpoints.py (modified, +16/-0)
  • litellm/proxy/schema.prisma (modified, +2/-0)
  • schema.prisma (modified, +2/-0)
  • tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py (modified, +31/-0)
  • tests/test_litellm/proxy/_experimental/mcp_server/test_oauth2_flow_utils.py (added, +99/-0)
  • ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts (modified, +7/-4)
  • ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx (modified, +21/-9)
  • ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.test.tsx (modified, +5/-3)
  • ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx (modified, +20/-3)
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx (modified, +3/-2)
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx (modified, +51/-207)
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx (modified, +10/-1)
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx (modified, +16/-8)
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx (modified, +0/-8)
  • ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx (modified, +29/-1)
  • ui/litellm-dashboard/src/components/mcp_tools/types.tsx (modified, +25/-0)

Code Example

curl -X PUT "$LITELLM_URL/v1/mcp/server" \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": "<server_id>",
    "transport": "http",
    "auth_type": "oauth2",
    "available_on_public_internet": true,
    "allowed_tools": ["tool1", "tool2"]
  }'
RAW_BUFFERClick to expand / collapse

Bug Report

Description

When editing an existing MCP server with OAuth2 M2M (client_credentials) auth, the Edit Settings → Tool Configuration panel shows:

Unable to load tools Failed to connect to MCP server. Check proxy logs for details.

Root Cause

The edit form populates formValues from the server record returned by GET /v1/mcp/server/<id>. For OAuth2 servers, credentials is always null in API responses (encrypted at rest, redacted for security). The UI then calls POST /mcp-rest/test/tools/list with the form values — but since client_id and client_secret are missing, the test endpoint cannot fetch an OAuth2 token and the connection fails.

Code path:

  1. mcp_tool_configuration.tsxuseTestMCPConnection hook → testMCPToolsListRequest
  2. Backend: rest_endpoints.py _execute_with_mcp_client()_extract_credentials() returns (None, None, None)
  3. has_client_credentials is False → no M2M token fetch → connection fails with generic error

Expected Behavior

For existing M2M servers, the Tool Configuration panel should be able to load tools without requiring credentials to be re-entered. Options:

  1. Use the stored (encrypted) credentials server-side when the test endpoint is called for an existing server_id — i.e., look up the server from DB and use its credentials instead of relying on the UI to supply them
  2. Fall back to /mcp-rest/tools/list?server_id=<id> (the non-test endpoint) which correctly uses stored credentials for existing servers

Actual Behavior

Tool Configuration shows "Unable to load tools / Failed to connect to MCP server" for all OAuth2 M2M servers in the edit flow.

Steps to Reproduce

  1. Create an MCP server with OAuth2 M2M auth via API (credentials stored correctly)
  2. Verify tools work: GET /mcp-rest/tools/list?server_id=<id> returns all tools
  3. Open UI → MCP Servers → click the server → Edit Settings → Tool Configuration
  4. Observe "Unable to load tools" error

Workaround

Use the API to manage allowed_tools:

curl -X PUT "$LITELLM_URL/v1/mcp/server" \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "server_id": "<server_id>",
    "transport": "http",
    "auth_type": "oauth2",
    "available_on_public_internet": true,
    "allowed_tools": ["tool1", "tool2"]
  }'

Environment

  • LiteLLM version: current main (also affects v1.81.14)
  • Auth type: oauth2 with client_credentials (M2M)

extent analysis

Fix Plan

To resolve the issue, we will modify the backend to use the stored credentials for existing OAuth2 M2M servers when the test endpoint is called. We will achieve this by looking up the server from the database and using its credentials instead of relying on the UI to supply them.

Step-by-Step Solution

  1. Modify the _extract_credentials function in rest_endpoints.py to check if the server ID is provided in the request. If it is, retrieve the server from the database and use its stored credentials.
  2. Update the _execute_with_mcp_client function to use the retrieved credentials when calling the test endpoint.
  3. Add a check for existing servers in the testMCPToolsListRequest function in mcp_tool_configuration.tsx to use the non-test endpoint (/mcp-rest/tools/list?server_id=<id>) when the server ID is provided.

Example Code

# rest_endpoints.py
def _extract_credentials(request):
    server_id = request.get('server_id')
    if server_id:
        # Retrieve server from database and use its stored credentials
        server = Server.objects.get(id=server_id)
        return server.client_id, server.client_secret, server.token_url
    # ... existing code ...
# rest_endpoints.py
def _execute_with_mcp_client(request, client_id, client_secret, token_url):
    # Use the retrieved credentials when calling the test endpoint
    if client_id and client_secret and token_url:
        # ... existing code to fetch OAuth2 token and call test endpoint ...
    # ... existing code ...
// mcp_tool_configuration.tsx
const testMCPToolsListRequest = async (serverId: string) => {
    if (serverId) {
        // Use the non-test endpoint for existing servers
        const response = await fetch(`/mcp-rest/tools/list?server_id=${serverId}`);
        // ... existing code to handle response ...
    } else {
        // ... existing code to call test endpoint ...
    }
}

Verification

To verify that the fix worked, follow these steps:

  1. Create an MCP server with OAuth2 M2M auth via API.
  2. Verify tools work: GET /mcp-rest/tools/list?server_id=<id> returns all tools.
  3. Open UI → MCP Servers → click the server → Edit Settings → Tool Configuration.
  4. The Tool Configuration panel should now load tools without requiring credentials to be re-entered.

Extra Tips

  • Make sure to handle errors and exceptions properly when retrieving servers from the database and using their stored credentials.
  • Consider adding logging to track any issues that may arise from using stored credentials.
  • Review the code changes to ensure they do not introduce any security vulnerabilities.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING