Skip to content

feat(mcp): support oauth2_token_exchange auth type via REST API and dashboard - #31772

Merged
tin-berri merged 9 commits into
litellm_internal_stagingfrom
litellm_mcp_token_exchange_ui
Jul 7, 2026
Merged

feat(mcp): support oauth2_token_exchange auth type via REST API and dashboard#31772
tin-berri merged 9 commits into
litellm_internal_stagingfrom
litellm_mcp_token_exchange_ui

Conversation

@tin-berri

@tin-berri tin-berri commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Stacked on #31983, the tip of the token_exchange (OBO) backend chain (#31526 -> #31622 -> #31762 -> #31983). Base is that branch so this diff is just the UI and REST-config work; it retargets to litellm_internal_staging as the backend stack merges. This is the frontend/API half that gives users parity with that backend work: the backend can resolve and run RFC 8693 token exchange, and this lets an admin actually configure such a server through the API and the dashboard instead of only config.yaml

Linear ticket

N/A

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Reproduction on a live proxy backed by Postgres (master key sk-1234)

  1. Start the proxy
python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log
  1. Create a token-exchange server through the API, then read it back
curl -s -X POST http://localhost:4000/v1/mcp/server \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
  -d '{"server_name":"te_demo","url":"https://upstream.example.com/mcp","transport":"http","auth_type":"oauth2_token_exchange","token_exchange_endpoint":"https://idp.example.com/oauth2/token","audience":"https://upstream.example.com","credentials":{"client_id":"demo-id","client_secret":"demo-secret"}}'

curl -s http://localhost:4000/v1/mcp/server -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  | jq '.[] | select(.server_name=="te_demo") | {auth_type, token_exchange_endpoint, audience, subject_token_type}'

The read-back shows the three fields populated and subject_token_type defaulting to urn:ietf:params:oauth:token-type:access_token. Inspecting the row directly confirms the three fields are dedicated columns while client_id/client_secret are encrypted in the credentials blob (neither appears in plaintext)

  1. Dashboard: go to http://localhost:4000/ui/?page=mcp-servers, click Add New MCP Server, pick Streamable HTTP, choose the auth type "OAuth Token Exchange (OBO)", fill Client ID and Client Secret (Token Exchange Endpoint, Audience, Subject Token Type are optional), and create. Re-open the server in edit mode and confirm Token Exchange Endpoint and Audience prefill from the saved values while Client Secret shows "leave blank to keep existing"

Dashboard before/after, rendered from the real create and edit form components on the merge-base commit (before) versus this branch (after)

Create form auth-type dropdown. Before, the Authentication options stop at OAuth then AWS SigV4, so there is no way to pick token exchange

Create form auth dropdown, before

After, "OAuth Token Exchange (OBO)" is a selectable option

Create form auth dropdown, after

Selecting it reveals the token-exchange field section: Token Exchange Endpoint, Client ID, Client Secret, Audience, Subject Token Type, and Scopes

Create form token-exchange fields, after

Edit form for an existing oauth2_token_exchange server. Before, the auth type falls back to the raw "oauth2_token_exchange" string and the form jumps straight to Variables, so none of the token-exchange config is visible or editable

Edit form, before

After, the auth type resolves to "OAuth Token Exchange (OBO)" and the saved Token Exchange Endpoint, Audience, and Subject Token Type prefill while Client Secret shows "leave blank to keep existing"

Edit form, after

Type

🆕 New Feature

Changes

OAuth 2.0 Token Exchange (RFC 8693, on-behalf-of) for MCP servers could previously only be configured through config.yaml; the create/update REST API and the dashboard had no way to express it even though the backend chain this stacks on can resolve and run the exchange. This wires token_exchange_endpoint, audience, and subject_token_type end to end so an auth_type=oauth2_token_exchange server can be created and edited through the API and the UI

These three are stored as dedicated columns on LiteLLM_MCPServerTable, mirroring how token_url and oauth2_flow are stored, rather than in the encrypted credentials blob. That keeps them returned to the dashboard so the edit form prefills them, and keeps them independent of the credentials-blob clearing that happens when auth_type changes. build_mcp_server_from_table reads the columns first and falls back to the credentials blob so a server persisted before the columns existed still loads with its token-exchange config intact. client_id and client_secret continue to ride the existing encrypted credentials path. The v2 resolver's _token_exchange_spec already reads these three off the runtime MCPServer, so the exchange arm consumes this config unchanged

Because the base chain adds RFC 9728 to RFC 8414 endpoint discovery (#31762), two things line up with it here. The DB-build discovery gate now reads the token_exchange_endpoint column first before deciding whether to discover, so a server whose endpoint is configured through the UI keeps the "explicit config wins, skip discovery" contract instead of being ignored because the value lives in a column rather than the blob. And the Token Exchange Endpoint field in the form is optional, since an admin can leave it blank and let discovery find it from the upstream's protected-resource metadata

On the dashboard, "OAuth Token Exchange (OBO)" is a distinct auth-type dropdown option with its own field section in both the create and edit forms. The McpOAuthMode classifier gains a token_exchange arm keyed off auth_type; the previous catch-all oauth2 mode was renamed from obo to authorization_code so the two on-behalf-of mechanisms (RFC 8693 token exchange versus the stored-per-user authorization_code token) are no longer conflated under one confusing label. The token-exchange IdP endpoint and audience are scrubbed from non-admin and virtual-key responses, matching how token_url is already treated

Tests cover the column read path and the credentials-blob fallback, the subject_token_type default, the table round-trip, the REST create and partial-update write paths reaching the prepared column data, the non-admin and virtual-key sanitizers dropping the new fields, the getMcpOAuthMode token_exchange classification, and the create form routing the token-exchange payload to the backend

Cursor Bugbot flagged that switching an existing server's auth_type left the previous flow's endpoint config on the row: an oauth2 server switched to oauth2_token_exchange kept its stale token_url, which the OBO resolver reads as the configured exchange endpoint (token_exchange_endpoint or token_url), so the stale value both suppressed the endpoint discovery described above and pointed the exchange grant at the previous flow's token endpoint. update_mcp_server now extends its existing stale-credentials rule to the flow-scoped columns (authorization_url, token_url, registration_url, oauth2_flow, token_exchange_endpoint, audience, subject_token_type): when auth_type changes, each one is cleared unless the same request explicitly provides it, so a deliberate override in the switch request still wins. The edit form sends explicit nulls for the previous flow's fields on a switch, since antd preserves unmounted field values and would otherwise re-send the stale token_url as an explicit override. Updates that keep the auth_type never touch these columns, so legacy OBO rows that use token_url as their exchange endpoint keep working

Storage contract: token-exchange settings (blob → columns)

token_exchange_endpoint, audience, and subject_token_type live in dedicated columns, which are authoritative. The same keys inside credentials are the legacy pre-column REST shape (the only REST shape from May 2026 until this PR): they are still accepted, but every write lifts them into the columns and strips them from the stored blob — an explicit top-level value (including an explicit null) always wins. The column or blob read fallback therefore only ever serves untouched pre-column rows; the first write that touches auth_type, credentials, or a token-exchange field migrates the row permanently. Columns hold settings; the blob holds actual credentials (client_id/client_secret, encrypted at rest).

Field semantics, since two similarly named settings are easy to conflate: subject_token_type is the RFC 8693 parameter declaring what kind of token the gateway presents as the subject_token being exchanged — default urn:ietf:params:oauth:token-type:access_token, set to e.g. urn:ietf:params:oauth:token-type:jwt for IdPs that validate the subject as a signed JWT. It is unrelated to token_endpoint_auth_method (client_secret_basic / client_secret_post), which controls how the gateway authenticates itself to the token endpoint. The field renders only for the oauth2_token_exchange auth type (pinned by the stacked test PR #32385), and the entra_obo profile (#32144) ignores it entirely, since Entra's OBO is the RFC 7523 jwt-bearer grant.


Note

Medium Risk
Touches MCP OAuth persistence, auth-type switching, and encrypted credential merge paths; mistakes could mis-route token exchange or leak IdP endpoints to restricted callers, though scrubbing and extensive tests mitigate this.

Overview
Adds OAuth Token Exchange (OBO / RFC 8693) as a first-class MCP auth type through the REST API and dashboard, not only config.yaml. token_exchange_endpoint, audience, and subject_token_type are stored in new LiteLLM_MCPServerTable columns (with a Prisma migration); reads prefer columns and fall back to legacy keys inside credentials. Writes lift those keys into columns and strip them from the encrypted blob so a cleared column cannot be resurrected from stale JSON.

update_mcp_server clears flow-scoped OAuth fields when auth_type changes (unless explicitly set in the same request) and migrates legacy blob token-exchange settings on create/update. Non-admin / virtual-key responses hide the new IdP-oriented fields like token_url.

The UI adds OAuth Token Exchange (OBO) with TokenExchangeFormFields, and getMcpOAuthMode gains token_exchange while renaming the interactive OAuth2 mode from obo to authorization_code. DEFAULT_SUBJECT_TOKEN_TYPE is centralized in litellm.types.mcp for runtime and egress types.

Reviewed by Cursor Bugbot for commit 2baa43b. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added support for OAuth Token Exchange in MCP server setup and editing.
    • New fields are available for token exchange endpoint, audience, and subject token type.
    • The tools UI now recognizes token exchange as a distinct auth mode.
  • Bug Fixes

    • Improved handling of OAuth mode detection and credential prompts.
    • Restricted views now hide token exchange details for non-admin and virtual-key access.
    • Updated save and restore flows so MCP tools continue to work correctly after authorization.

Link to Devin session: https://app.devin.ai/sessions/f03da2725ec94d28b3facf766871b102

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds oauth2_token_exchange (RFC 8693 OBO) as a first-class MCP server auth type by promoting its three configuration fields (token_exchange_endpoint, audience, subject_token_type) from the credentials JSON blob into dedicated DB columns, and wiring them through the REST API, dashboard forms, and runtime egress path.

  • Schema & migration: three nullable columns added to LiteLLM_MCPServerTable; migration is idempotent (IF NOT EXISTS); all three Prisma schema files are kept in sync.
  • Blob migration: _prepare_mcp_server_data lifts legacy blob copies into the new columns on every write, and update_mcp_server handles the case where only a column is updated without credentials.
  • UI: new TokenExchangeFormFields component; getMcpOAuthMode gains a token_exchange branch; isObo renamed to isAuthorizationCode throughout.

Confidence Score: 4/5

Mostly safe to merge; the create wizard missing transport guard can block form submission for stdio servers with the new auth type, but has no effect on correctly configured servers.

The missing !isStdioTransport guard in create_mcp_server.tsx means selecting stdio transport + oauth2_token_exchange makes client_id/client_secret required fields, blocking form submission for that combination. The edit form already applies the guard correctly. All other logic — DB migration, blob-to-column promotion, sanitization, and runtime egress — is well-tested and appears correct.

create_mcp_server.tsx (missing transport guard around TokenExchangeFormFields) and db.py (empty-blob edge case in the explicit-TE-write migration path).

Important Files Changed

Filename Overview
litellm/proxy/_experimental/mcp_server/db.py Adds blob-to-column migration logic for token-exchange fields; create, update, and explicit-column-write paths all handle the migration correctly and are covered by dedicated tests.
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Reads token-exchange columns with credentials-blob fallback; _obo_needs_endpoint_discovery and _build_mcp_server_table updated correctly; DEFAULT_SUBJECT_TOKEN_TYPE constant replaces inline string literals.
litellm/proxy/management_endpoints/mcp_management_endpoints.py Sanitization functions for non-admin and virtual-key views now correctly clear the three new TE columns, consistent with existing authorization_url/token_url handling.
ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx Adds isTokenExchangeAuthType flag and renders TokenExchangeFormFields when selected; missing !isStdioTransport guard unlike the edit form.
ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx Edit form correctly guards TokenExchangeFormFields with !isStdioTransport; auth-type-switch clearing sends explicit nulls for the TE columns.
ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx Correctly renames isObo to isAuthorizationCode throughout; token_exchange mode handled as passthrough with no auth gate.
ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx New component for token-exchange form fields; top-level form names correctly match the new column-based API shape.
litellm-proxy-extras/litellm_proxy_extras/migrations/20260630120000_add_token_exchange_to_mcp_servers/migration.sql Idempotent IF NOT EXISTS migration adds the three nullable columns; timestamp ordering caveat documented in the file header.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_partial_update.py Comprehensive blob-to-column migration tests covering create, credential merge, explicit-column-write, and auth-type-switch paths; all mock-based, no network calls.
tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py New TestMCPServerTokenExchangeColumns covers column reads, blob fallback, default token type, and round-trip fidelity; mock-based.

Reviews (13): Last reviewed commit: "fix(mcp): scrub subject_token_type in th..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx Outdated
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

This comment was marked as outdated.

Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
Comment thread litellm/models/mcp_server.py
@veria-ai

veria-ai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@tin-berri
tin-berri force-pushed the litellm_mcp_token_exchange_ui branch from bf91140 to 19bb79e Compare July 1, 2026 03:26
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri
tin-berri force-pushed the litellm_mcp_token_exchange_ui branch from 19bb79e to a70b291 Compare July 3, 2026 17:46
@tin-berri
tin-berri changed the base branch from litellm_internal_staging to litellm_mcp_v2_entra_obo July 3, 2026 17:46
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@tin-berri
tin-berri force-pushed the litellm_mcp_v2_entra_obo branch from 8b56f66 to 2176477 Compare July 4, 2026 02:08
@tin-berri
tin-berri force-pushed the litellm_mcp_token_exchange_ui branch from a70b291 to 3aba74a Compare July 4, 2026 20:07
Base automatically changed from litellm_mcp_v2_entra_obo to litellm_internal_staging July 4, 2026 23:48
@tin-berri
tin-berri force-pushed the litellm_mcp_token_exchange_ui branch from 3aba74a to c112117 Compare July 4, 2026 23:54
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@mateo-berri

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
Comment thread ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds RFC 8693 Token Exchange (OBO) support to MCP servers: database migration and schema fields for token_exchange_endpoint, audience, and subject_token_type; backend model, manager, and sanitization updates; and UI form fields, auth-type option, and renaming of the "obo" OAuth mode to "authorization_code" with a separate "token_exchange" classification.

Changes

Backend schema, models, and server manager logic

Layer / File(s) Summary
Schema and model field additions
litellm-proxy-extras/.../migration.sql, litellm-proxy-extras/.../schema.prisma, litellm/proxy/schema.prisma, schema.prisma, litellm/models/mcp_server.py, litellm/proxy/_types.py
Adds token_exchange_endpoint, audience, subject_token_type columns/fields to the migration, Prisma schemas, LiteLLM_MCPServerTable model, and NewMCPServerRequest/UpdateMCPServerRequest.
MCP server manager read/write logic
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py, tests/test_litellm/proxy/_experimental/mcp_server/*
build_mcp_server_from_table reads the new columns with legacy JSON fallback; health_check_server and _build_mcp_server_table populate the fields; tests cover column reads, fallback, defaulting, and round-trips.
Sanitization and API payload handling
litellm/proxy/management_endpoints/mcp_management_endpoints.py, tests/test_litellm/proxy/management_endpoints/*, tests/mcp_tests/test_mcp_server.py
Non-admin and virtual-key sanitizers clear token_exchange_endpoint/audience; tests verify create/update payload preparation and mock fixtures include the new fields.

Estimated code review effort: 3 (Moderate) | ~30 minutes

UI OAuth mode rename and token exchange form support

Layer / File(s) Summary
OAuth mode classification and type updates
types.tsx, types.test.tsx
Adds OAUTH2_TOKEN_EXCHANGE auth type, renames McpOAuthMode (oboauthorization_code, adds token_exchange), updates getMcpOAuthMode, and extends MCPServer with the new fields.
TokenExchangeFormFields component
TokenExchangeFormFields.tsx
New form component rendering token exchange endpoint, client ID/secret, audience, subject token type, and scopes fields.
Create MCP server integration
create_mcp_server.tsx, create_mcp_server.test.tsx
Wires in the new form section and auth-type option, requires credentials for token exchange, renames obo to authorization_code in handleCreate, and adds a payload-routing test.
Edit MCP server integration
mcp_server_edit.tsx
Wires in the new form section and auth-type option, derives oauth_flow_type from oauth2_flow, and renames obo to authorization_code in save/persistence logic.
MCP tools viewer gating
mcp_tools.tsx, mcp_servers.tsx
Replaces OBO-based credential/auth gating with authorization_code gating, including a credential-status query, auth-gate rendering, and callback renames.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant MCPServerManager
  participant LiteLLM_MCPServerTable
  Caller->>MCPServerManager: build_mcp_server_from_table(mcp_server)
  MCPServerManager->>LiteLLM_MCPServerTable: read token_exchange_endpoint, audience, subject_token_type
  alt columns present
    MCPServerManager->>MCPServerManager: use dedicated columns
  else columns absent
    MCPServerManager->>MCPServerManager: fallback to credentials_dict
  end
  MCPServerManager-->>Caller: MCPServer instance
Loading
sequenceDiagram
  participant User
  participant MCPToolsUI as mcp_tools.tsx
  participant CredentialQuery
  participant OAuthFlow as useUserMcpOAuthFlow
  User->>MCPToolsUI: open tools tab
  MCPToolsUI->>CredentialQuery: check authorization_code credential status
  CredentialQuery-->>MCPToolsUI: authorizationCodeNeedsAuth
  alt needs auth
    MCPToolsUI-->>User: show Authentication required panel
    User->>MCPToolsUI: click authorize
    MCPToolsUI->>OAuthFlow: startAuthorizationCodeAuthorize
    OAuthFlow-->>MCPToolsUI: onAuthorizationCodeAuthSuccess
    MCPToolsUI->>CredentialQuery: refetch status and tools
  else has credential
    MCPToolsUI-->>User: list tools
  end
Loading

Related Issues: None referenced.

Related PRs: None referenced.

Suggested labels: enhancement, mcp, ui

Suggested reviewers: litellm maintainers familiar with MCP server auth and dashboard UI

🐰 Hop, hop, through columns new,
Token exchange, audience too,
OBO renamed, "code" now shines,
Backend, frontend, all aligned.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding oauth2_token_exchange support via the REST API and dashboard.
Description check ✅ Passed The PR description matches the template well and includes all required sections: issues, ticket, checklist, proof, type, and changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch litellm_mcp_token_exchange_ui

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx (1)

820-834: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear auth-specific fields on auth type change
This Select still leaves authorization_url/token_url/registration_url/credentials in form state, and handleSave spreads restValues straight into the payload. Add an onChange reset here so switching auth types doesn’t persist stale config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx` around
lines 820 - 834, The Authentication Select in mcp_server_edit.tsx leaves
auth-specific fields in form state when auth_type changes, and handleSave will
still include them via restValues. Update the auth_type Select to clear
dependent fields like authorization_url, token_url, registration_url, and
credentials on change so switching between auth types does not persist stale
config. Use the existing form field names in mcp_server_edit and keep the reset
logic localized to the auth_type control.
ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx (1)

911-932: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Clear auth-specific fields when auth_type changes. The backend stores the top-level OAuth/token-exchange fields exactly as submitted, and update_mcp_server only clears credentials in a narrow case. Switching between OAuth2 and OAuth Token Exchange can leave stale authorization_url/token_url/registration_url or token_exchange_endpoint/audience/subject_token_type values on the row.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx` around
lines 911 - 932, Clear auth-specific form values whenever auth_type changes in
create_mcp_server so stale OAuth/OBO fields do not persist. Update the auth
selection handling around the Authentication Form.Item/Select to reset or
conditionally clear unrelated fields when switching between oauth2,
oauth2_token_exchange, token, basic, api_key, bearer_token, none, and aws_sigv4,
and make sure the submit/update path in update_mcp_server only sends the fields
relevant to the current auth_type.
🧹 Nitpick comments (1)
ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx (1)

9-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared field-label helper
FieldLabel and fieldClassName are duplicated in ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx and OAuthFormFields.tsx; move them into a shared helper/module to keep the two form variants in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx`
around lines 9 - 18, The shared field-label helper is duplicated between
TokenExchangeFormFields and OAuthFormFields, so move fieldClassName and
FieldLabel into a common reusable module and import them from both form
components. Update the TokenExchangeFormFields and OAuthFormFields
implementations to use the shared helper by its existing symbol names so the
styling and tooltip label behavior stay in sync without duplicated definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx`:
- Around line 380-385: The block comment above the auth gate overstates its
scope by claiming it applies to both authorization_code and token_exchange, but
this gate is only reachable through isAuthorizationCode via
authorizationCodeNeedsAuth and authorizationCodeTokenRejected. Update the
comment in mcp_tools.tsx to describe only the authorization_code flow and the
401/credential-missing cases, keeping it consistent with the existing
token_exchange note near the earlier gate logic.

---

Outside diff comments:
In `@ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx`:
- Around line 911-932: Clear auth-specific form values whenever auth_type
changes in create_mcp_server so stale OAuth/OBO fields do not persist. Update
the auth selection handling around the Authentication Form.Item/Select to reset
or conditionally clear unrelated fields when switching between oauth2,
oauth2_token_exchange, token, basic, api_key, bearer_token, none, and aws_sigv4,
and make sure the submit/update path in update_mcp_server only sends the fields
relevant to the current auth_type.

In `@ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx`:
- Around line 820-834: The Authentication Select in mcp_server_edit.tsx leaves
auth-specific fields in form state when auth_type changes, and handleSave will
still include them via restValues. Update the auth_type Select to clear
dependent fields like authorization_url, token_url, registration_url, and
credentials on change so switching between auth types does not persist stale
config. Use the existing form field names in mcp_server_edit and keep the reset
logic localized to the auth_type control.

---

Nitpick comments:
In `@ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx`:
- Around line 9-18: The shared field-label helper is duplicated between
TokenExchangeFormFields and OAuthFormFields, so move fieldClassName and
FieldLabel into a common reusable module and import them from both form
components. Update the TokenExchangeFormFields and OAuthFormFields
implementations to use the shared helper by its existing symbol names so the
styling and tooltip label behavior stay in sync without duplicated definitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e7b3bdd-ab60-4e53-b078-aec92ecab828

📥 Commits

Reviewing files that changed from the base of the PR and between 2e38da6 and bfd5bb8.

📒 Files selected for processing (22)
  • litellm-proxy-extras/litellm_proxy_extras/migrations/20260630120000_add_token_exchange_to_mcp_servers/migration.sql
  • litellm-proxy-extras/litellm_proxy_extras/schema.prisma
  • litellm/models/mcp_server.py
  • litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
  • litellm/proxy/_types.py
  • litellm/proxy/management_endpoints/mcp_management_endpoints.py
  • litellm/proxy/schema.prisma
  • schema.prisma
  • tests/mcp_tests/test_mcp_server.py
  • tests/test_litellm/proxy/_experimental/mcp_server/test_db_credentials.py
  • tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py
  • tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py
  • tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_sigv4_auth.py
  • tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py
  • ui/litellm-dashboard/src/components/mcp_tools/TokenExchangeFormFields.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.test.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/types.test.tsx
  • ui/litellm-dashboard/src/components/mcp_tools/types.tsx

Comment thread ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx
@mateo-berri

Copy link
Copy Markdown
Contributor

(again, @tin-berri , I'm just testing coderabbit. What it or bugbot mentions <- don't consider them blocking)

…ashboard

OAuth 2.0 Token Exchange (RFC 8693, a.k.a. OBO) for MCP servers could previously only be
configured through config.yaml; the create/update REST API and the dashboard had no way to
express it. This wires token_exchange_endpoint, audience, and subject_token_type end to end.

These three are persisted as dedicated columns on LiteLLM_MCPServerTable, mirroring how token_url
and oauth2_flow are stored, so the edit form prefills them and they are unaffected by the
credentials-blob clearing on auth_type change. build_mcp_server_from_table reads the columns first
and falls back to the credentials blob so servers persisted before the columns existed still load.
client_id and client_secret continue to ride the existing encrypted credentials path.

On the dashboard, "OAuth Token Exchange (OBO)" is a distinct auth-type option with its own field
section. The McpOAuthMode classifier gains a token_exchange arm keyed off auth_type; the previous
catch-all oauth2 mode was renamed from "obo" to "authorization_code" so the two on-behalf-of
mechanisms are no longer conflated. The token-exchange IdP endpoint and audience are scrubbed from
non-admin and virtual-key responses, matching how token_url is treated.
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread litellm/proxy/_experimental/mcp_server/db.py
Comment thread litellm/proxy/_experimental/mcp_server/db.py
tin-berri and others added 2 commits July 7, 2026 13:44
…trict budget)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…itten without credentials

The migrate-on-write in the credentials merge lifts blob values into null
columns, which is correct for legacy rows but could repopulate a column an
admin had cleared in an earlier no-credentials update (that path never touched
the blob, so the stale copy survived to be lifted later). An explicit
token-exchange column write (set or clear) now migrates the row even when the
update carries no credentials: untouched null columns are lifted, every blob
copy is stripped, and unrelated blob keys stay as-is. A cleared column can then
never be resurrected, because no write path leaves a blob copy behind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tin-berri

Copy link
Copy Markdown
Contributor Author

Medium: migrate-on-write repopulates cleared columns — confirmed and fixed in 35a6ddc. The gap was the no-credentials clear: an update that explicitly nulled a column but didn't carry credentials never entered the merge path, so the legacy blob copy survived, and the next credentials-touching update's migrate-on-write lifted the stale value back into the column. An explicit token-exchange column write (set or clear) now migrates the row even without credentials in the payload: untouched null columns are lifted from the blob, every blob copy is stripped, and unrelated blob keys (encrypted secrets) are preserved byte-for-byte. With that, no write path leaves a blob copy behind, so a cleared column can never be resurrected — the clear itself purges the copy the later lift would have read. Covered by three new tests: the clear-without-credentials purge, whole-row migration on a partial column write, and no blob rewrite when there is nothing to migrate.

One bounded caveat: rows whose column was cleared before this code ships (blob still dirty from the old behavior) can see the lift once more on their next update; any explicit set/clear of the field now fully purges the row. That residual is not distinguishable from a genuine legacy never-set row without a marker column, which didn't seem worth it.

Also in this push: eda61c6 fixes the I001 strict-budget failure (import order in outbound_credentials/types.py).

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

…ntial keys

The three token-exchange keys on MCPCredentials are the pre-column REST shape
(the only REST shape from 2026-05 until this PR). Document on both the blob
type and the request models that the dedicated columns are authoritative and
that writes lift blob values into them and strip the stored copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2baa43b. Configure here.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai wheres the review, can you just reply the review here

@mateo-berri mateo-berri left a comment

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.

the only open item is a minor inconsistency in the non-admin response scrubbing where subject_token_type is left exposed while the other two token-exchange fields are cleared

Is this a legit concern?

Also, can you please attach e2e proof that before=fail/after=works?

…anitizers

The other two token-exchange fields were cleared while subject_token_type was
left visible. It is a public RFC 8693 URN with no disclosure value, but the
sanitizers' rule is that these views receive no token-exchange config at all —
cleared for uniformity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tin-berri

Copy link
Copy Markdown
Contributor Author

subject_token_type left exposed in the non-admin scrub — fixed in f3ee8ac for uniformity. For the record, the field itself has no disclosure value (it is a URN from RFC 8693's fixed public vocabulary — no hostnames, identifiers, or secrets, unlike the endpoint/audience), but the sanitizers' rule is that non-admin and virtual-key views receive no token-exchange config at all, so it is now cleared alongside the other two. Both sanitizer tests assert it.

@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

1 similar comment
@tin-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rereview

@tin-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f3ee8ac. Configure here.

Comment thread ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx

@mateo-berri mateo-berri left a comment

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.

The edit form guards TokenExchangeFormFields with !isStdioTransport, but the create form has no such guard. Selecting stdio transport and oauth2_token_exchange auth type in the create wizard will render the TE fields even though stdio servers don't use OAuth — client_id/client_secret would then be required and the form cannot be submitted without them.

Is this a valid concern?

@mateo-berri mateo-berri left a comment

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.

LGTM; thanks! Just gotta make sure to bump the proxy extras when it comes time to cut releases

@tin-berri
tin-berri merged commit ff6dc33 into litellm_internal_staging Jul 7, 2026
135 checks passed
@tin-berri
tin-berri deleted the litellm_mcp_token_exchange_ui branch July 7, 2026 22:26
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