Skip to content

feat(ui): add ID-JAG (Okta Cross App Access) auth type to MCP server form - #34038

Closed
yassin-berriai wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_mcp_id_jag_ui
Closed

feat(ui): add ID-JAG (Okta Cross App Access) auth type to MCP server form#34038
yassin-berriai wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_mcp_id_jag_ui

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

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 received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

Captured at commit 86d87db against a live proxy (namespaced Postgres, STORE_MODEL_IN_DB=True) with the dashboard dev server pointed at it

Before: the Add/Edit MCP Server auth-type dropdown offered none, api_key, bearer_token, token, basic, oauth2, OAuth Token Exchange (OBO), aws_sigv4, true_passthrough, oauth_delegate. There was no way to create an oauth2_id_jag server from the UI, so the leg-2 resource token endpoint and resource indicator (which live in the credentials blob) could only be set via config.yaml or a hand-rolled REST call

After: selecting the new ID-JAG (Okta Cross App Access) option renders the two-leg fields and creating the server persists them. Steps a reviewer can follow against a live proxy:

  1. Open the dashboard MCP Servers page, click + Add New MCP Server, then + Custom Server
  2. Set a name, pick Streamable HTTP, enter an upstream URL, and choose Authentication ID-JAG (Okta Cross App Access)
  3. Fill IdP Token Endpoint (leg 1) (e.g. https://<your-okta-domain>/oauth2/v1/token), Resource Token Endpoint (leg 2), Client ID, Client Secret, and optionally Audience, Resource Indicator, Scopes
  4. Submit; the server appears with the oauth2_id_jag badge

The stored row confirms leg 1 and audience land in their dedicated columns and the leg-2 endpoint plus client credentials land in the (encrypted) credentials blob that the resolver reads:

$ psql -c "SELECT server_name, auth_type, token_exchange_endpoint, audience,
  (credentials::jsonb -> 'id_jag_resource_token_endpoint') AS leg2_endpoint,
  (credentials::jsonb ? 'client_id') AS has_client_id,
  (credentials::jsonb ? 'client_secret') AS has_client_secret
  FROM \"LiteLLM_MCPServerTable\" WHERE server_name='okta_id_jag_ui_demo';"

 okta_id_jag_ui_demo | oauth2_id_jag | https://<okta-domain>/oauth2/v1/token | api://okta-id-jag-mcp | "https://mcp.internal.example.com/oauth2/token" | t | t

Screen recording of the create flow and the auth-type dropdown are attached below

Independently verified end to end (Devin)

Independently reproduced on the current branch tip: I built the dashboard from litellm_mcp_id_jag_ui, served it from a locally run Postgres-backed proxy (username/password login, master key never typed into the browser, no detailed-debug banner), created an MCP server through the new ID-JAG (Okta Cross App Access) option with the leg-2 Resource Token Endpoint filled, saw it land in the list with the oauth2_id_jag badge, and confirmed the row in Postgres

Public screen recording (opens without any login): https://litter.catbox.moe/1rtupq.mp4 ; durable mirror https://gofile.io/d/Eo2BoN . The direct link above is a short-lived public host (about 72 hours), so the same flow is embedded below as an animated capture that stays with the PR

ID-JAG MCP server create flow

Live DB row from that run, showing leg-1 and audience in their columns and the leg-2 endpoint persisted in plaintext inside the credentials blob (client id/secret encrypted at rest):

SELECT server_id, server_name, auth_type, token_exchange_endpoint, audience, credentials
FROM "LiteLLM_MCPServerTable" WHERE auth_type='oauth2_id_jag';

server_name             | id_jag_ui_demo
auth_type               | oauth2_id_jag
token_exchange_endpoint | https://example.okta.com/oauth2/v1/token
audience                | api://example-mcp-resource
credentials             | {"scopes": ["mcp.read", "mcp.write"], "client_id": "<encrypted at rest>", "client_secret": "<encrypted at rest>", "id_jag_resource": "https://mcp.example.com", "id_jag_resource_token_endpoint": "https://mcp.example.com/oauth2/token"}

Type

🆕 New Feature

Changes

Adds oauth2_id_jag to the dashboard's AUTH_TYPE enum and a new IdJagFormFields component that mirrors the existing TokenExchangeFormFields pattern. Both the create and edit MCP server forms render it when the ID-JAG auth type is selected. The fields split the way the backend stores them: token_exchange_endpoint (leg 1) and audience have dedicated columns so they are flat form fields, while id_jag_resource_token_endpoint (leg 2), id_jag_resource, client_id, client_secret, and scopes are nested under credentials so the existing create/update path persists them into the credentials blob with no backend change. The read path already hydrates these from the credentials blob, and I verified on a live proxy that a name-only edit preserves the whole blob (the update merges rather than replaces), so the edit form's keep-existing behavior is safe

The auth-type Select in both forms is switched to virtual={false}. The dropdown is a small fixed list where virtualization buys nothing, and antd's virtual list drops the last rendered option once the list grows past its window, which hides oauth_delegate from screen readers and keyboard users and is what broke the option-selection tests once a new option was added

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

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

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds oauth2_id_jag (ID-JAG / Okta Cross App Access) as a new auth type in the MCP Server create and edit forms, making a previously config-only feature accessible from the dashboard UI. It also fixes a shared-column nulling bug (previously flagged as P1) where switching between oauth2_token_exchange and oauth2_id_jag would incorrectly clear the token_exchange_endpoint and audience columns that both auth types share.

  • New IdJagFormFields component renders the two-leg token-exchange fields: leg-1 IdP endpoint + audience (flat DB columns) and leg-2 resource endpoint + client credentials + scopes (credentials blob); mirrors the TokenExchangeFormFields pattern.
  • Auth-type grouping fix in the edit form: AUTH_TYPES_SHARING_TOKEN_EXCHANGE_COLUMNS prevents token_exchange_endpoint/audience from being nulled on intra-family switches, while OBO-only columns (subject_token_type, token_exchange_profile) are still cleared when leaving OBO. Both forms add virtual={false} to the auth-type Select to prevent antd's virtual list from hiding the last option.

Confidence Score: 5/5

Safe to merge; the previously flagged shared-column nulling bug is correctly resolved and the new ID-JAG form integrates cleanly with the existing token-exchange infrastructure.

The auth-type grouping logic correctly prevents token_exchange_endpoint and audience from being cleared when switching between the two token-exchange family members in both directions. The new IdJagFormFields component correctly splits storage between flat DB columns and the credentials blob. The only gaps are two non-blocking quality items.

No files require special attention. The test file could benefit from a symmetric ID-JAG to OBO regression test, but the underlying logic is correct.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx New component for ID-JAG form fields; clean implementation mirroring TokenExchangeFormFields; placeholderSuffix is only applied to password fields, not to the URL endpoint fields (minor UX inconsistency in edit mode).
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.tsx Correct fix for the previously flagged shared-column nulling bug; AUTH_TYPES_SHARING_TOKEN_EXCHANGE_COLUMNS logic handles both OBO→ID-JAG and ID-JAG→OBO directions; regression test covers the OBO→ID-JAG direction.
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/create_mcp_server.tsx Correctly adds OAUTH2_ID_JAG to AUTH_TYPES_REQUIRING_CREDENTIALS, renders IdJagFormFields when selected, and adds virtual={false} to the auth-type Select; no issues found.
ui/litellm-dashboard/src/components/mcp_tools/types.tsx Adds OAUTH2_ID_JAG to the AUTH_TYPE enum; no other changes; correctly scoped.
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_edit.test.tsx Adds regression test for OBO→ID-JAG direction (the originally flagged bug); the symmetric ID-JAG→OBO direction is handled by the shared constant but lacks its own regression test.
ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/create_mcp_server.test.tsx New test 'routes ID-JAG (Okta Cross App Access) config to the backend payload' covers the full create-form submission path including both flat and nested credential fields.

Reviews (2): Last reviewed commit: "feat(ui): add ID-JAG (Okta Cross App Acc..." | Re-trigger Greptile

@yassin-berriai
yassin-berriai force-pushed the litellm_mcp_id_jag_ui branch from 86d87db to d360b1e Compare July 20, 2026 21:56
…form

Admins could configure an oauth2_id_jag MCP server only through config.yaml or
the REST API; the dashboard's Add/Edit MCP Server form had no ID-JAG option, so
the leg-2 resource token endpoint and resource indicator were unreachable from
the UI. Add an ID-JAG auth type that renders the two-leg fields (IdP token
endpoint for leg 1, resource token endpoint for leg 2, client id/secret,
audience, resource indicator, scopes), sending the credential-blob fields nested
under credentials so the existing backend persists them unchanged.

The auth-type Select is switched to virtual={false} so all options render
deterministically; the antd virtual list otherwise drops the last option once
the list grows, which is both an a11y gap and what broke the option-selection
tests.
@yassin-berriai
yassin-berriai force-pushed the litellm_mcp_id_jag_ui branch from d360b1e to 7a2b167 Compare July 20, 2026 22:00
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Good catch, that was a real bug. Fixed in 7a2b167. Both `oauth2_token_exchange` and `oauth2_id_jag` share the `token_exchange_endpoint` and `audience` columns, so I now treat them as a family: the shared columns are nulled only when switching to an auth type OUTSIDE that family, and the token-exchange-only columns (`subject_token_type`, `token_exchange_profile`) are nulled when leaving OBO. This fixes the token_exchange -> id_jag direction you flagged and the symmetric id_jag -> token_exchange direction (my original id_jag clause had the same defect in reverse). Added a regression test in mcp_server_edit.test.tsx ("preserves the shared leg-1 endpoint and audience when switching token exchange to ID-JAG") plus the existing switch tests still pass.

@greptileai please review the current head 7a2b167

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I independently verified this end to end. I built the dashboard from litellm_mcp_id_jag_ui, served it from a locally run Postgres-backed proxy (username/password login, master key never typed into the browser, no detailed-debug banner), created an MCP server through the new ID-JAG (Okta Cross App Access) option with the leg-2 Resource Token Endpoint filled, saw it listed with the oauth2_id_jag badge, and confirmed the row in Postgres. Only generic example values were used

Public screen recording (opens without any login): https://litter.catbox.moe/1rtupq.mp4 ; durable mirror https://gofile.io/d/Eo2BoN . The animated capture and the live DB row are embedded in the PR description under Screenshots / Proof of Fix

Created with oauth2_id_jag badge

DB row from that run: auth_type=oauth2_id_jag, token_exchange_endpoint=https://example.okta.com/oauth2/v1/token, audience=api://example-mcp-resource, and credentials.id_jag_resource_token_endpoint=https://mcp.example.com/oauth2/token persisted in plaintext (client id/secret encrypted at rest)

@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_mcp_id_jag_ui (7a2b167) with litellm_internal_staging (214945a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (eb27447) during the generation of this report, so 214945a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Superseded. #35147 landed the ID-JAG auth type with a fuller version of this form: it adds the Client Private Key (PEM) field and the private_key_jwt path, and accepts either a client secret or a private key where this branch required the secret. Rebasing this one onto current staging would revert that work rather than add to it

The one thing here that #35147 did not carry is the shared-column clearing, so an ID-JAG server converted to another auth type kept a stale token_exchange_endpoint and audience. That is now #35387, along with this branch's auth-type-switch test and the converse case it did not cover

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