Skip to content

feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching #3230

Merged
akshaydeo merged 1 commit into
mainfrom
05-05-feat_envvar_on_oauth_config_in_mcp_settings
May 5, 2026
Merged

feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching #3230
akshaydeo merged 1 commit into
mainfrom
05-05-feat_envvar_on_oauth_config_in_mcp_settings

Conversation

@BearTS

@BearTS BearTS commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

OAuth client_id and client_secret fields now support environment variable references (e.g. env.MY_VAR) in addition to plain string values. Previously these fields were stored and handled as raw strings, which meant secrets had to be provided as literal values. This change allows credentials to be injected from environment variables at runtime, keeps references intact in the database, and surfaces redacted credential metadata back to callers on GET requests.

Changes

  • TableOauthConfig.ClientID changed from string to *schemas.EnvVar so env var references are preserved in storage and resolved at runtime via GetResolvedClientID() / GetResolvedClientSecret() helpers.
  • OAuthConfigRequest and OAuthConfigUpdate in the HTTP transport now use *schemas.EnvVar instead of string for client_id and client_secret.
  • envVarStorageValue() helper added to extract the storage representation (env var reference or resolved value) before passing to the OAuth flow initiator.
  • MCPClientConfig gains two new read-only fields — OauthClientID and OauthClientSecret — populated on GET responses with redacted credential metadata fetched from the linked OAuth config.
  • A GetOauthConfigsByIDs batch query was added to avoid N+1 database calls when listing MCP clients with OAuth configs.
  • The BeforeSave hook skips encryption for ClientSecret values that are already env var references, since the reference string itself is not sensitive.
  • On update, credential rotation is skipped when both fields carry a redacted/empty placeholder (i.e. the user did not actually change them), preserving existing stored credentials.
  • UI form fields for OAuth client_id and client_secret replaced plain Input components with EnvVarInput, enabling env var selection. The edit sheet pre-populates these fields from the redacted GET response and uses dirtyFields to determine whether rotation should be triggered.
  • TypeScript types updated so OAuthConfig.client_id, OAuthConfig.client_secret, OAuthConfigUpdate.client_id, and OAuthConfigUpdate.client_secret are all EnvVar instead of string.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

  1. Create an MCP client with OAuth auth type, supplying client_id as an env var reference (e.g. env.OAUTH_CLIENT_ID) and client_secret as env.OAUTH_CLIENT_SECRET.
  2. Verify the reference strings are stored in the database rather than resolved values.
  3. Perform a GET on the MCP client list and confirm oauth_client_id and oauth_client_secret are returned as redacted EnvVar objects.
  4. In the UI, open the MCP client form and confirm the EnvVarInput components appear for both fields and correctly reflect the stored env var reference.
  5. Edit the client without changing the credential fields and confirm no credential rotation occurs.
  6. Edit the client with a new credential value and confirm rotation is triggered.
go test ./...

cd ui
pnpm i
pnpm build

Breaking changes

  • Yes
  • No

TableOauthConfig.ClientID changed from string to *schemas.EnvVar. Any code that directly accessed .ClientID as a string must be updated to use .GetResolvedClientID() or dereference the EnvVar. The OAuthConfigRequest and OAuthConfigUpdate API request bodies now expect client_id and client_secret as EnvVar objects rather than plain strings; API clients sending raw strings will need to wrap values in the EnvVar structure.

Related issues

N/A

Security considerations

  • Env var references are stored as-is in the database; the actual secret values are never written to the database when env vars are used.
  • Encryption is intentionally skipped for env var reference strings in BeforeSave, since the reference itself (e.g. env.MY_VAR) is not sensitive.
  • Credential values returned on GET are passed through the existing Redacted() path, so actual secret values are never exposed in API responses.
  • Resolved values are only used in-process at the point of OAuth flow execution.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • OAuth credentials can be stored/referenced as environment-variable objects (not plain text) and are redacted when shown.
    • MCP clients fully support OAuth-based authentication with resolved client credentials used for token exchanges.
    • MCP configuration UI now uses env-var inputs for OAuth ID/secret.
  • Bug Fixes

    • Fixed a compilation error in the OAuth configuration code.
  • Performance / Reliability

    • Batch-loading of OAuth configs improves MCP listing performance and makes failures return consistent server errors.

Walkthrough

MCP OAuth client credentials were converted to EnvVar-backed types and propagated through schemas, DB table, configstore API/impl, handlers, OAuth core, redaction, and UI. RDB implementation added a bulk GetOauthConfigsByIDs method and stray numeric tokens were removed so the file compiles.

Changes

EnvVar-backed OAuth credentials (single coherent change DAG)

Layer / File(s) Summary
Data Shape
core/schemas/mcp.go, ui/lib/types/mcp.ts, ui/lib/types/schemas.ts
MCPClientConfig gains OauthClientID/OauthClientSecret as *EnvVar / optional EnvVar; UI types and Zod schema change OAuth client_id/client_secret to EnvVar / envVarSchema.optional().
DB Table Representation
framework/configstore/tables/oauth.go
TableOauthConfig.ClientID/ClientSecret changed from string*schemas.EnvVar; BeforeSave/AfterFind conditionalize encrypt/decrypt on non-nil/non-FromEnv values; EncryptionStatus only set if encryption ran; added GetResolvedClientID(), GetResolvedClientSecret(), GetClientSecretAsEnvVar().
ConfigStore API
framework/configstore/store.go
Added GetOauthConfigsByIDs(ctx, ids []string) (map[string]*tables.TableOauthConfig, error) to ConfigStore interface for bulk lookup.
RDB Implementation
framework/configstore/rdb.go
Implemented GetOauthConfigsByIDs (batch WHERE id IN ?, returns map keyed by ID); removed stray numeric tokens that prevented compilation.
Server Handlers / Wiring
transports/bifrost-http/handlers/mcp.go, transports/bifrost-http/handlers/oauth2.go, transports/bifrost-http/handlers/oauth2_per_user.go, transports/bifrost-http/lib/config.go
Handlers accept client_id/client_secret as *schemas.EnvVar; MCP listing handlers batch-fetch OAuth configs and inject redacted OauthClientID/OauthClientSecret; add/update flows use EnvVar.IsSet() and preserve env-var refs or trigger discovery/rotation; per-user upstream authorize validates resolved client_id; redaction includes new fields.
OAuth Core
framework/oauth2/main.go
Initiation, code-exchange, and refresh flows use resolved credentials via GetResolvedClientID() / GetResolvedClientSecret(); persisted client IDs stored as schemas.NewEnvVar(...) where applicable.
UI Wiring / Forms
ui/app/workspace/mcp-registry/views/mcpClientForm.tsx, ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
Form defaults and submit payloads use EnvVar objects; inputs switched to EnvVarInput for client id/secret (secret masks/hides when from env); rotation gated by dirtyFields and sends EnvVar payloads.
Tests / Documentation
(no explicit test/docs changes in diff)
No test or documentation files changed in this diff.

Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes

Sequence Diagram(s)

sequenceDiagram
    participant UI as Browser/UI
    participant API as Bifrost HTTP Handler
    participant Store as ConfigStore/RDB
    participant OAuthCore as OAuth2 Core
    participant Provider as External OAuth Provider

    UI->>API: GET /mcp/clients
    API->>Store: Get clients + GetOauthConfigsByIDs(ids)
    Store-->>API: client rows + oauth config map (EnvVar wrappers)
    API->>UI: MCPClientConfig[] (OauthClientID/Secret redacted EnvVar)
    UI->>API: POST /oauth/initiate { oauth_config.client_id: EnvVar, ... }
    API->>OAuthCore: InitiateOAuthFlow (resolved client_id/client_secret)
    OAuthCore->>Provider: /authorize?client_id=resolved
    Provider-->>OAuthCore: auth code
    OAuthCore->>Provider: /token (exchange with client_secret)
    Provider-->>OAuthCore: tokens
    OAuthCore-->>API: tokens/result
    API-->>UI: response / redirect
Loading

Possibly related PRs

  • maximhq/bifrost#3209: Modifies MCP client OAuth credential rotation flow and related handler logic; directly related to handler and MCPClientConfig OAuth changes.

"I am a rabbit, nibbling code tonight,
Env vars tucked in burrows out of sight.
I hop through handlers, DB, and UI,
Wrapping secrets softly as I hop by.
Redacted and masked — a tidy little bite. 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding EnvVar reference support for MCP OAuth credentials with redacted GET responses and batch config fetching.
Description check ✅ Passed The description covers all required template sections including Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Security considerations, and a completed Checklist.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 05-05-feat_envvar_on_oauth_config_in_mcp_settings

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.1)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

BearTS commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

@BearTS BearTS changed the title feat: envVar on oauth config in mcp settings feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching May 5, 2026
@BearTS BearTS marked this pull request as ready for review May 5, 2026 11:39
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #3230

@greptile-apps

greptile-apps Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Safe to merge with the noted P2 edge-case (unresolved env var silently sends empty client_id to the OAuth provider).

The three P1 issues from prior review rounds all appear resolved by the per-field ShouldPreserveStored() guards and the FromEnv check in AfterFind. Only P2 gaps remain.

transports/bifrost-http/handlers/oauth2.go — the Value()-based extraction path for unresolved env-var references deserves a resolved-value guard matching the one already in oauth2_per_user.go.

Important Files Changed

Filename Overview
framework/configstore/tables/oauth.go Changed ClientID/ClientSecret from string to *schemas.EnvVar; BeforeSave/AfterFind hooks updated to skip encrypt/decrypt for env var references.
transports/bifrost-http/handlers/mcp.go Per-field ShouldPreserveStored() guards added for both client_id and client_secret in the update path, addressing previously flagged partial-rotation corruption.
transports/bifrost-http/handlers/oauth2.go InitiateOAuthFlow extracts storage values via Value() to preserve env var references; unresolved env-var reference silently bypasses dynamic registration (P2).
framework/oauth2/main.go All direct .ClientID/.ClientSecret accesses replaced with GetResolvedClientID()/GetResolvedClientSecret(); resolvedClientID correctly re-resolved before being embedded in the authorize URL.
ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx Input replaced with EnvVarInput; dirtyFields used to detect credential changes; form defaultValues pre-populated from GET redacted response.
ui/app/workspace/mcp-registry/views/mcpClientForm.tsx client_secret submission guard uses value
ui/lib/types/mcp.ts OAuthConfig and OAuthConfigUpdate updated from string to EnvVar; MCPClientConfig gains read-only oauth_client_id/oauth_client_secret fields.
core/schemas/mcp.go Two new read-only *EnvVar fields added to MCPClientConfig for GET-time redacted credential metadata.
framework/configstore/rdb.go GetOauthConfigsByIDs added as a clean batch query returning a map keyed by config ID.

Reviews (8): Last reviewed commit: "feat: envVar on oauth config in mcp sett..." | Re-trigger Greptile

Comment thread transports/bifrost-http/handlers/mcp.go
Comment thread ui/app/workspace/mcp-registry/views/mcpClientForm.tsx

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
transports/bifrost-http/handlers/mcp.go (2)

831-881: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve unchanged OAuth fields independently during credential rotation.

The edit sheet pre-populates both fields from the redacted GET response in ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx Lines 161-163 and then resubmits both fields in Lines 210-214 when either one is dirty. Here you only short-circuit when both values are placeholders, so a secret-only or client-id-only rotation can serialize the untouched placeholder through envVarStorageValue(...) and pass that back into InitiateOAuthFlow instead of the stored credential.

Suggested fix
 	if shouldRotateOAuthConfig {
-		// If both fields carry a redacted/empty placeholder (i.e. user didn't actually change
-		// them), treat this as a no-op so the existing credentials are preserved.
-		if req.OauthConfig.ClientID.ShouldPreserveStored() && req.OauthConfig.ClientSecret.ShouldPreserveStored() {
+		preserveClientID := req.OauthConfig.ClientID.ShouldPreserveStored()
+		preserveClientSecret := req.OauthConfig.ClientSecret.ShouldPreserveStored()
+		// If both fields carry a redacted/empty placeholder (i.e. user didn't actually change
+		// them), treat this as a no-op so the existing credentials are preserved.
+		if preserveClientID && preserveClientSecret {
 			shouldRotateOAuthConfig = false
 		}
-	}
-	if shouldRotateOAuthConfig {
-		oauthClientID = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientID))
-		oauthClientSecret = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientSecret))
+		if shouldRotateOAuthConfig {
+			if !preserveClientID {
+				oauthClientID = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientID))
+			}
+			if !preserveClientSecret {
+				oauthClientSecret = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientSecret))
+			}
+		}
 		oauthAuthorizeURL = strings.TrimSpace(req.OauthConfig.AuthorizeURL)
 		oauthTokenURL = strings.TrimSpace(req.OauthConfig.TokenURL)
 		oauthRegistrationURL = strings.TrimSpace(req.OauthConfig.RegistrationURL)
@@
 			if existingOauthConfig != nil {
+				if preserveClientID {
+					oauthClientID = strings.TrimSpace(existingOauthConfig.GetResolvedClientID())
+				}
+				if preserveClientSecret {
+					oauthClientSecret = strings.TrimSpace(existingOauthConfig.GetResolvedClientSecret())
+				}
 				if oauthAuthorizeURL == "" {
 					oauthAuthorizeURL = strings.TrimSpace(existingOauthConfig.AuthorizeURL)
 				}
🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 831 - 881, The handler
currently treats OAuth rotation as a no-op only when BOTH
req.OauthConfig.ClientID.ShouldPreserveStored() and
req.OauthConfig.ClientSecret.ShouldPreserveStored() are true, which lets a
redacted placeholder for one field be passed through; change the logic to
preserve each field independently: only call envVarStorageValue and assign
oauthClientID when !req.OauthConfig.ClientID.ShouldPreserveStored(), and only
assign oauthClientSecret when
!req.OauthConfig.ClientSecret.ShouldPreserveStored(); when a field is preserved,
after loading existingOauthConfig use existingOauthConfig.GetResolvedClientID()
/ GetResolvedClientSecret() (or existingOauthConfig values) to populate that
specific oauthClientID/oauthClientSecret instead of the placeholder; adjust the
validation that currently errors when oauthClientID=="" && oauthClientSecret==""
to require at least one of (resolved or provided) client_id or client_secret and
fail with a clear message referencing the missing specific field(s).

280-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep OAuth redaction consistent on both /api/mcp/clients code paths.

This enrichment only runs in getMCPClientsPaginated. The non-paginated branch in the same handler still returns RedactMCPClientConfig(configClient) directly, so oauth_client_id / oauth_client_secret are present only when the caller passes pagination params. That makes the response shape query-dependent and breaks any non-paginated consumer that relies on the new redacted OAuth fields to prefill edit flows.

🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 280 - 327, The
non-paginated code path in getMCPClientsPaginated is returning
RedactMCPClientConfig(configClient) without applying the same OAuth enrichment
done in the paginated branch, causing oauth_client_id/secret to be present only
for paginated calls; fix by replicating the batch-fetch + merge logic used
there: call h.store.ConfigStore.GetOauthConfigsByIDs to build oauthConfigsByID,
populate clientConfig.OauthClientID and clientConfig.OauthClientSecret (using
oauthCfg.ClientID and oauthCfg.GetClientSecretAsEnvVar()) for each returned DB
row, then pass the enriched config through RedactMCPClientConfig before
returning; ensure you reference the same helpers/variables
(getMCPClientsPaginated, oauthConfigsByID, GetOauthConfigsByIDs,
RedactMCPClientConfig) so both paginated and non-paginated branches produce the
same redacted shape.
🤖 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 `@framework/configstore/tables/oauth.go`:
- Around line 16-17: Tests failing because ClientID changed from string to
*schemas.EnvVar; update all test instantiations to wrap literals with
schemas.NewEnvVar(...) when setting the ClientID field (e.g., ClientID:
schemas.NewEnvVar("client-id-public")) and update assertions to compare the
underlying string via found.ClientID.GetValue() (and guard for nil where
appropriate). Locate occurrences by referencing the struct field ClientID in
tests under encryption_test.go and follow the existing AzureKeyConfig.ClientID
pattern (using schemas.NewEnvVar and GetValue()).

In `@transports/bifrost-http/handlers/oauth2_per_user.go`:
- Line 546: The code currently sets params.Set("client_id",
templateConfig.GetResolvedClientID()) without validating the resolved value;
ensure you validate templateConfig.GetResolvedClientID() before constructing the
redirect URL and fail fast with a clear HTTP 500/4xx error if it's empty or
blank. Locate the call that sets the query param (params.Set("client_id", ...))
and add a guard that checks the returned string from
templateConfig.GetResolvedClientID(); if empty, return a descriptive server
error response (including which tenant/template if available) instead of
continuing to build or redirect to the upstream OAuth endpoint.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 831-881: The handler currently treats OAuth rotation as a no-op
only when BOTH req.OauthConfig.ClientID.ShouldPreserveStored() and
req.OauthConfig.ClientSecret.ShouldPreserveStored() are true, which lets a
redacted placeholder for one field be passed through; change the logic to
preserve each field independently: only call envVarStorageValue and assign
oauthClientID when !req.OauthConfig.ClientID.ShouldPreserveStored(), and only
assign oauthClientSecret when
!req.OauthConfig.ClientSecret.ShouldPreserveStored(); when a field is preserved,
after loading existingOauthConfig use existingOauthConfig.GetResolvedClientID()
/ GetResolvedClientSecret() (or existingOauthConfig values) to populate that
specific oauthClientID/oauthClientSecret instead of the placeholder; adjust the
validation that currently errors when oauthClientID=="" && oauthClientSecret==""
to require at least one of (resolved or provided) client_id or client_secret and
fail with a clear message referencing the missing specific field(s).
- Around line 280-327: The non-paginated code path in getMCPClientsPaginated is
returning RedactMCPClientConfig(configClient) without applying the same OAuth
enrichment done in the paginated branch, causing oauth_client_id/secret to be
present only for paginated calls; fix by replicating the batch-fetch + merge
logic used there: call h.store.ConfigStore.GetOauthConfigsByIDs to build
oauthConfigsByID, populate clientConfig.OauthClientID and
clientConfig.OauthClientSecret (using oauthCfg.ClientID and
oauthCfg.GetClientSecretAsEnvVar()) for each returned DB row, then pass the
enriched config through RedactMCPClientConfig before returning; ensure you
reference the same helpers/variables (getMCPClientsPaginated, oauthConfigsByID,
GetOauthConfigsByIDs, RedactMCPClientConfig) so both paginated and non-paginated
branches produce the same redacted shape.
🪄 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

Run ID: efef7f3a-93d6-4f64-9bd6-67502abf8070

📥 Commits

Reviewing files that changed from the base of the PR and between ecbc3ee and 34c7deb.

📒 Files selected for processing (12)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts

Comment thread framework/configstore/tables/oauth.go
Comment thread transports/bifrost-http/handlers/oauth2_per_user.go Outdated
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from 34c7deb to c18544b Compare May 5, 2026 11:53

@coderabbitai coderabbitai 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.

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)
framework/configstore/tables/oauth.go (1)

50-67: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Keep env-backed client secrets out of the decrypt path.

Line 53 intentionally leaves env.* client secrets unencrypted, but the row can still end up marked encrypted when CodeVerifier is encrypted. On the next read, AfterFind will try to decrypt the untouched env.* literal and fail, which breaks the new env-var OAuth flow in encrypted deployments. Mirror the same env-ref guard in AfterFind, and keep EncryptionStatus aligned so env-only rows are not treated as pending plaintext forever.

🛠️ Minimal fix
 func (c *TableOauthConfig) BeforeSave(tx *gorm.DB) error {
 	// Ensure status is valid
 	if c.Status == "" {
 		c.Status = "pending"
 	}

 	// Encrypt sensitive fields (skip if value is an env var reference — the reference itself is not sensitive)
 	if encrypt.IsEnabled() {
-		encrypted := false
 		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
 			if err := encryptString(&c.ClientSecret); err != nil {
 				return fmt.Errorf("failed to encrypt oauth client secret: %w", err)
 			}
-			encrypted = true
 		}
 		if c.CodeVerifier != "" {
 			if err := encryptString(&c.CodeVerifier); err != nil {
 				return fmt.Errorf("failed to encrypt oauth code verifier: %w", err)
 			}
-			encrypted = true
 		}
-		if encrypted {
-			c.EncryptionStatus = EncryptionStatusEncrypted
-		}
+		c.EncryptionStatus = EncryptionStatusEncrypted
 	}
 	return nil
 }

 // AfterFind hook to decrypt sensitive fields
 func (c *TableOauthConfig) AfterFind(tx *gorm.DB) error {
 	if c.EncryptionStatus == EncryptionStatusEncrypted {
-		if err := decryptString(&c.ClientSecret); err != nil {
-			return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
+			if err := decryptString(&c.ClientSecret); err != nil {
+				return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+			}
 		}
-		if err := decryptString(&c.CodeVerifier); err != nil {
-			return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+		if c.CodeVerifier != "" {
+			if err := decryptString(&c.CodeVerifier); err != nil {
+				return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+			}
 		}
 	}
 	return nil
 }

Based on learnings, in framework/configstore/tables, EncryptionStatus should be set to "encrypted" whenever encryption is enabled so the startup batch-encryption pass does not keep reprocessing 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 `@framework/configstore/tables/oauth.go` around lines 50 - 67, The AfterFind
decryption path must mirror the encrypt-time guards: in the AfterFind method of
the oauth row type, skip attempting to decrypt ClientSecret when it has the
"env." prefix (i.e., check strings.HasPrefix(c.ClientSecret, "env.") before
calling decrypt/encryptString) so you don't try to decrypt env-backed literals,
and ensure EncryptionStatus is set consistently when encryption is enabled (set
EncryptionStatus = EncryptionStatusEncrypted when encrypt.IsEnabled() even if
only CodeVerifier was encrypted or the secret is env-backed) so rows with env.*
secrets are not repeatedly reprocessed; update references to ClientSecret,
CodeVerifier, AfterFind, encrypt.IsEnabled(), encryptString, and
EncryptionStatusEncrypted accordingly.
transports/bifrost-http/handlers/mcp.go (1)

831-845: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve unchanged OAuth fields independently during rotation.

Right now only the “both placeholders” case is treated as a no-op. If a user rotates only client_id or only client_secret, the other redacted field is reused as the placeholder/empty value instead of the stored credential, which can break the next OAuth authorization flow.

🛠️ Proposed fix
-		if req.OauthConfig.ClientID.ShouldPreserveStored() && req.OauthConfig.ClientSecret.ShouldPreserveStored() {
+		preserveClientID := req.OauthConfig.ClientID.ShouldPreserveStored()
+		preserveClientSecret := req.OauthConfig.ClientSecret.ShouldPreserveStored()
+		if preserveClientID && preserveClientSecret {
 			shouldRotateOAuthConfig = false
 		}
 	}
 	if shouldRotateOAuthConfig {
-		oauthClientID = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientID))
-		oauthClientSecret = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientSecret))
+		if !preserveClientID {
+			oauthClientID = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientID))
+		}
+		if !preserveClientSecret {
+			oauthClientSecret = strings.TrimSpace(envVarStorageValue(req.OauthConfig.ClientSecret))
+		}
 		oauthAuthorizeURL = strings.TrimSpace(req.OauthConfig.AuthorizeURL)
 		oauthTokenURL = strings.TrimSpace(req.OauthConfig.TokenURL)
 		oauthRegistrationURL = strings.TrimSpace(req.OauthConfig.RegistrationURL)
@@
 		if existingConfig.OauthConfigID != nil && *existingConfig.OauthConfigID != "" {
 			existingOauthConfig, err = h.store.ConfigStore.GetOauthConfigByID(ctx, *existingConfig.OauthConfigID)
@@
 		}
+
+		if preserveClientID && existingOauthConfig != nil {
+			oauthClientID = strings.TrimSpace(envVarStorageValue(existingOauthConfig.ClientID))
+		}
+		if preserveClientSecret && existingOauthConfig != nil {
+			oauthClientSecret = strings.TrimSpace(envVarStorageValue(existingOauthConfig.GetClientSecretAsEnvVar()))
+		}
 
 		if oauthClientID == "" {
 			if existingOauthConfig == nil || strings.TrimSpace(existingOauthConfig.GetResolvedClientID()) == "" {

Also applies to: 848-881

🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 831 - 845, The rotation
logic treats both OAuth fields as a single no-op; instead, preserve each field
independently so rotating one (client_id or client_secret) doesn't overwrite the
other with the redacted placeholder. Update the block that sets
oauthClientID/oauthClientSecret (and uses envVarStorageValue) to: if
req.OauthConfig.ClientID.ShouldPreserveStored() then set oauthClientID to the
stored/existing client id (trimmed) rather than
envVarStorageValue(req.OauthConfig.ClientID), and similarly if
req.OauthConfig.ClientSecret.ShouldPreserveStored() set oauthClientSecret to the
stored/existing secret (trimmed); keep existing behavior for
AuthorizeURL/TokenURL/RegistrationURL/Scopes and still validate that at least
one of oauthClientID or oauthClientSecret is non-empty before proceeding (the
SendError branch), using the independent-preserved values. Ensure you reference
the same identifiers: req.OauthConfig.ClientID.ShouldPreserveStored(),
req.OauthConfig.ClientSecret.ShouldPreserveStored(), envVarStorageValue,
oauthClientID, oauthClientSecret, shouldRotateOAuthConfig and the
existing/stored OAuth values used for preservation.
🤖 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 `@transports/bifrost-http/handlers/mcp.go`:
- Around line 434-445: The helper envVarStorageValue currently reimplements
EnvVar serialization; replace its manual logic with the canonical schema helper
by calling the EnvVar.Value() (or Value) method: update envVarStorageValue(e
*schemas.EnvVar) to return "" for nil and otherwise return e.Value() so it
follows the same pattern used by mcpExternalURLToString in
framework/configstore/rdb.go and stays correct if the EnvVar schema changes.

---

Outside diff comments:
In `@framework/configstore/tables/oauth.go`:
- Around line 50-67: The AfterFind decryption path must mirror the encrypt-time
guards: in the AfterFind method of the oauth row type, skip attempting to
decrypt ClientSecret when it has the "env." prefix (i.e., check
strings.HasPrefix(c.ClientSecret, "env.") before calling decrypt/encryptString)
so you don't try to decrypt env-backed literals, and ensure EncryptionStatus is
set consistently when encryption is enabled (set EncryptionStatus =
EncryptionStatusEncrypted when encrypt.IsEnabled() even if only CodeVerifier was
encrypted or the secret is env-backed) so rows with env.* secrets are not
repeatedly reprocessed; update references to ClientSecret, CodeVerifier,
AfterFind, encrypt.IsEnabled(), encryptString, and EncryptionStatusEncrypted
accordingly.

In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 831-845: The rotation logic treats both OAuth fields as a single
no-op; instead, preserve each field independently so rotating one (client_id or
client_secret) doesn't overwrite the other with the redacted placeholder. Update
the block that sets oauthClientID/oauthClientSecret (and uses
envVarStorageValue) to: if req.OauthConfig.ClientID.ShouldPreserveStored() then
set oauthClientID to the stored/existing client id (trimmed) rather than
envVarStorageValue(req.OauthConfig.ClientID), and similarly if
req.OauthConfig.ClientSecret.ShouldPreserveStored() set oauthClientSecret to the
stored/existing secret (trimmed); keep existing behavior for
AuthorizeURL/TokenURL/RegistrationURL/Scopes and still validate that at least
one of oauthClientID or oauthClientSecret is non-empty before proceeding (the
SendError branch), using the independent-preserved values. Ensure you reference
the same identifiers: req.OauthConfig.ClientID.ShouldPreserveStored(),
req.OauthConfig.ClientSecret.ShouldPreserveStored(), envVarStorageValue,
oauthClientID, oauthClientSecret, shouldRotateOAuthConfig and the
existing/stored OAuth values used for preservation.
🪄 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

Run ID: dd7e853e-aad8-4b8e-b27a-8c1b6e19ad2c

📥 Commits

Reviewing files that changed from the base of the PR and between 34c7deb and c18544b.

📒 Files selected for processing (12)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (2)
  • core/schemas/mcp.go
  • ui/lib/types/mcp.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • framework/configstore/store.go
  • ui/lib/types/schemas.ts
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • framework/oauth2/main.go

Comment thread transports/bifrost-http/handlers/mcp.go Outdated
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from c18544b to 9dd5520 Compare May 5, 2026 12:16

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (1)
framework/configstore/tables/oauth.go (1)

50-67: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Row-level encryption state is now inconsistent for env-backed client secrets.

If ClientSecret is stored as env.MY_SECRET and CodeVerifier is non-empty, this path marks the row as encrypted even though ClientSecret stayed plaintext. AfterFind then unconditionally calls decryptString(&c.ClientSecret) and fails on the env.* reference, so encrypted deployments can no longer load that OAuth config. The opposite case also exists: when encryption is enabled but only env-backed/plain fields remain, the row stays plain_text, so the startup batch-encryption pass can keep revisiting it.

💡 Minimal fix
 func (c *TableOauthConfig) BeforeSave(tx *gorm.DB) error {
   // Ensure status is valid
   if c.Status == "" {
     c.Status = "pending"
   }

   // Encrypt sensitive fields (skip if value is an env var reference — the reference itself is not sensitive)
   if encrypt.IsEnabled() {
-    encrypted := false
+    c.EncryptionStatus = EncryptionStatusEncrypted
     if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
       if err := encryptString(&c.ClientSecret); err != nil {
         return fmt.Errorf("failed to encrypt oauth client secret: %w", err)
       }
-      encrypted = true
     }
     if c.CodeVerifier != "" {
       if err := encryptString(&c.CodeVerifier); err != nil {
         return fmt.Errorf("failed to encrypt oauth code verifier: %w", err)
       }
-      encrypted = true
-    }
-    if encrypted {
-      c.EncryptionStatus = EncryptionStatusEncrypted
     }
   }
   return nil
 }

 func (c *TableOauthConfig) AfterFind(tx *gorm.DB) error {
   if c.EncryptionStatus == EncryptionStatusEncrypted {
-    if err := decryptString(&c.ClientSecret); err != nil {
-      return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+    if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
+      if err := decryptString(&c.ClientSecret); err != nil {
+        return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+      }
     }
     if err := decryptString(&c.CodeVerifier); err != nil {
       return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
     }
   }
   return nil
 }

Based on learnings, in framework/configstore/tables encryption-enabled rows should still keep EncryptionStatus aligned with the encryption flow, and AfterFind hooks must guard decrypt calls to avoid decrypting values that were intentionally left unencrypted.

🤖 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 `@framework/configstore/tables/oauth.go` around lines 50 - 67, The row-level
encryption is inconsistent because AfterFind unconditionally calls decryptString
on fields like ClientSecret while the encrypt path leaves env.* references
unencrypted; fix by (1) in the AfterFind hook (method AfterFind on the oauth
table struct) guard every decryptString(&c.ClientSecret) and
decryptString(&c.CodeVerifier) call with strings.HasPrefix(field, "env.") checks
so env.* values are skipped, and (2) ensure the encrypting block that calls
encryptString (the code around encrypt.IsEnabled(), encryptString and the
encrypted flag) only sets c.EncryptionStatus = EncryptionStatusEncrypted when
actual encryption was performed (i.e., encrypted == true) so the persisted
EncryptionStatus accurately reflects whether any non-env sensitive fields were
encrypted.
🤖 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.

Outside diff comments:
In `@framework/configstore/tables/oauth.go`:
- Around line 50-67: The row-level encryption is inconsistent because AfterFind
unconditionally calls decryptString on fields like ClientSecret while the
encrypt path leaves env.* references unencrypted; fix by (1) in the AfterFind
hook (method AfterFind on the oauth table struct) guard every
decryptString(&c.ClientSecret) and decryptString(&c.CodeVerifier) call with
strings.HasPrefix(field, "env.") checks so env.* values are skipped, and (2)
ensure the encrypting block that calls encryptString (the code around
encrypt.IsEnabled(), encryptString and the encrypted flag) only sets
c.EncryptionStatus = EncryptionStatusEncrypted when actual encryption was
performed (i.e., encrypted == true) so the persisted EncryptionStatus accurately
reflects whether any non-env sensitive fields were encrypted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7c40cec-2694-4460-8dc2-61e07439784c

📥 Commits

Reviewing files that changed from the base of the PR and between c18544b and 9dd5520.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (3)
  • ui/lib/types/schemas.ts
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • core/schemas/mcp.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • ui/lib/types/mcp.ts
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • framework/oauth2/main.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/handlers/mcp.go

Comment thread transports/bifrost-http/handlers/mcp.go Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 5, 2026
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from 9dd5520 to aa9f119 Compare May 5, 2026 13:07
Comment thread transports/bifrost-http/handlers/mcp.go Outdated

@coderabbitai coderabbitai 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.

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 (1)
framework/configstore/tables/oauth.go (1)

50-67: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Handle env-var ClientSecret consistently across save/load encryption paths.

BeforeSave skips encryption for ClientSecret when it is env.*, but AfterFind still decrypts ClientSecret whenever EncryptionStatus is "encrypted". If CodeVerifier is encrypted in the same row, reads can fail by attempting to decrypt a plaintext env reference.

🔧 Proposed fix
 func (c *TableOauthConfig) BeforeSave(tx *gorm.DB) error {
@@
-	if encrypt.IsEnabled() {
-		encrypted := false
+	if encrypt.IsEnabled() {
+		// Mark as encrypted to avoid repeated startup reprocessing of this row.
+		c.EncryptionStatus = EncryptionStatusEncrypted
+		encrypted := false
 		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
 			if err := encryptString(&c.ClientSecret); err != nil {
 				return fmt.Errorf("failed to encrypt oauth client secret: %w", err)
 			}
 			encrypted = true
 		}
 		if c.CodeVerifier != "" {
 			if err := encryptString(&c.CodeVerifier); err != nil {
 				return fmt.Errorf("failed to encrypt oauth code verifier: %w", err)
 			}
 			encrypted = true
 		}
-		if encrypted {
-			c.EncryptionStatus = EncryptionStatusEncrypted
-		}
+		_ = encrypted
 	}
 	return nil
 }
@@
 func (c *TableOauthConfig) AfterFind(tx *gorm.DB) error {
 	if c.EncryptionStatus == EncryptionStatusEncrypted {
-		if err := decryptString(&c.ClientSecret); err != nil {
-			return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
+			if err := decryptString(&c.ClientSecret); err != nil {
+				return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+			}
 		}
-		if err := decryptString(&c.CodeVerifier); err != nil {
-			return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+		if c.CodeVerifier != "" {
+			if err := decryptString(&c.CodeVerifier); err != nil {
+				return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+			}
 		}
 	}
 	return nil
 }

Based on learnings: in framework/configstore/tables, rows should still be marked encrypted when encryption is enabled to avoid startup batch reprocessing loops.

Also applies to: 73-80

🤖 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 `@framework/configstore/tables/oauth.go` around lines 50 - 67, BeforeSave
currently skips encrypting ClientSecret when it’s an env.* reference but only
sets EncryptionStatus = EncryptionStatusEncrypted if at least one field was
actually encrypted; change it so that when encrypt.IsEnabled() is true you still
set c.EncryptionStatus = EncryptionStatusEncrypted (even if ClientSecret was
skipped) to avoid batch reprocessing loops; and in AfterFind, before calling
decryptString on c.ClientSecret, guard with a check for
strings.HasPrefix(c.ClientSecret, "env.") and skip decryption for env refs so
you don’t attempt to decrypt a plaintext env reference (apply this logic to
ClientSecret and mirror for any other env-ref sensitive fields like CodeVerifier
if relevant).
🤖 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 `@transports/bifrost-http/handlers/mcp.go`:
- Around line 819-824: The no-op shortcut currently sets shouldRotateOAuthConfig
= false when only req.OauthConfig.ClientID.ShouldPreserveStored() &&
req.OauthConfig.ClientSecret.ShouldPreserveStored(), which allows updates to
authorize_url/token_url/registration_url/scopes to be dropped; change the logic
so rotation is skipped only if ALL relevant OAuth config fields are "preserve
stored" (ClientID, ClientSecret, AuthorizeURL, TokenURL, RegistrationURL,
Scopes). Update the condition that sets shouldRotateOAuthConfig (the block
referencing req.OauthConfig.ClientID, ClientSecret and shouldRotateOAuthConfig)
to check preservation for every OAuth field, and keep shouldRotateOAuthConfig =
true if any of those fields are being updated.

---

Outside diff comments:
In `@framework/configstore/tables/oauth.go`:
- Around line 50-67: BeforeSave currently skips encrypting ClientSecret when
it’s an env.* reference but only sets EncryptionStatus =
EncryptionStatusEncrypted if at least one field was actually encrypted; change
it so that when encrypt.IsEnabled() is true you still set c.EncryptionStatus =
EncryptionStatusEncrypted (even if ClientSecret was skipped) to avoid batch
reprocessing loops; and in AfterFind, before calling decryptString on
c.ClientSecret, guard with a check for strings.HasPrefix(c.ClientSecret, "env.")
and skip decryption for env refs so you don’t attempt to decrypt a plaintext env
reference (apply this logic to ClientSecret and mirror for any other env-ref
sensitive fields like CodeVerifier if relevant).
🪄 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

Run ID: 79e2312a-a4c0-484b-97f8-4775fc06c112

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd5520 and aa9f119.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (5)
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • core/schemas/mcp.go
  • ui/lib/types/mcp.ts
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/oauth2.go
  • framework/configstore/store.go
  • framework/oauth2/main.go
  • ui/lib/types/schemas.ts

Comment thread transports/bifrost-http/handlers/mcp.go Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 5, 2026
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from aa9f119 to b408d70 Compare May 5, 2026 13:47

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/mcp.go (1)

1095-1115: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid returning 500 after earlier parts of the edit have already been committed.

By the time this branch runs, the handler has already updated the MCP client in DB/memory earlier in the request. If InitiateOAuthFlow or StorePendingMCPClient fails here, the caller gets an error even though part of the update is already live. Please either start/store the OAuth flow before mutating the client, or roll the prior changes back on failure.

🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 1095 - 1115, The
handler currently updates the MCP client then calls
h.oauthHandler.InitiateOAuthFlow and h.oauthHandler.StorePendingMCPClient, which
can leave a partially-applied update visible on failure; either move the OAuth
initiation/storage before mutating the live client or implement a rollback on
failure. Specifically, call h.oauthHandler.InitiateOAuthFlow(...) and
h.oauthHandler.StorePendingMCPClient(...) (using flowInitiation.OauthConfigID
and the pendingConfig) before applying the earlier MCP client mutation (the code
that writes schemasConfig to the live client), or, if you must keep the order,
add a compensating revert that undoes the prior update (use the original
schemasConfig snapshot) when InitiateOAuthFlow or StorePendingMCPClient returns
an error and ensure errors are handled and logged before returning the response.
🤖 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 `@transports/bifrost-http/handlers/mcp.go`:
- Around line 280-294: The non-paginated GET handler that builds
schemas.MCPClientConfig never populates the OAuth fields, so ensure you
replicate the batch-fetch logic (oauthConfigsByID map populated via
ConfigStore.GetOauthConfigsByIDs) used by getMCPClientsPaginated and, when
building each runtime config from a db client, set the config's OauthClientID
and OauthClientSecret metadata from the corresponding oauthConfigsByID entry
(apply the same redaction/placeholder logic you use in getMCPClientsPaginated).
Update the code paths that construct schemas.MCPClientConfig for the plain
/api/mcp/clients response (the block that currently creates runtime configs from
dbClients) to use oauthConfigsByID and populate OauthClientID/OauthClientSecret
so both paginated and non-paginated endpoints return consistent redacted OAuth
metadata.

In `@transports/bifrost-http/handlers/oauth2.go`:
- Around line 213-224: The code currently converts EnvVar credentials to empty
strings by ignoring errors from req.ClientID.Value() and
req.ClientSecret.Value(); update the logic around req.ClientID.Value() and
req.ClientSecret.Value() so you check the returned error and do not silently
assign "" — if Value() returns an error or nil (unresolved), return or propagate
a clear configuration error (or validation error) from the handler indicating
missing/invalid client_id or client_secret instead of continuing with blank
credentials; locate the assignment of clientID and clientSecret in oauth2.go and
replace the silent ignores with explicit error handling that surfaces
configuration problems to the caller.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 1095-1115: The handler currently updates the MCP client then calls
h.oauthHandler.InitiateOAuthFlow and h.oauthHandler.StorePendingMCPClient, which
can leave a partially-applied update visible on failure; either move the OAuth
initiation/storage before mutating the live client or implement a rollback on
failure. Specifically, call h.oauthHandler.InitiateOAuthFlow(...) and
h.oauthHandler.StorePendingMCPClient(...) (using flowInitiation.OauthConfigID
and the pendingConfig) before applying the earlier MCP client mutation (the code
that writes schemasConfig to the live client), or, if you must keep the order,
add a compensating revert that undoes the prior update (use the original
schemasConfig snapshot) when InitiateOAuthFlow or StorePendingMCPClient returns
an error and ensure errors are handled and logged before returning the response.
🪄 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

Run ID: 1b8846cf-47fb-48f2-8d90-a0e3593e508b

📥 Commits

Reviewing files that changed from the base of the PR and between aa9f119 and b408d70.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (3)
  • framework/oauth2/main.go
  • transports/bifrost-http/lib/config.go
  • framework/configstore/tables/oauth.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • ui/lib/types/schemas.ts
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/lib/types/mcp.ts
  • core/schemas/mcp.go

Comment thread transports/bifrost-http/handlers/mcp.go
Comment thread transports/bifrost-http/handlers/oauth2.go
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from b408d70 to 9f648fa Compare May 5, 2026 14:19

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (1)
framework/configstore/tables/oauth.go (1)

50-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

EncryptionStatus handling is inconsistent for env-backed secrets.

When encryption is enabled, this path only sets EncryptionStatusEncrypted if an actual encryption occurred. For env-backed ClientSecret, that can leave rows as plain_text (or keep stale status), which risks repeated encryption sweeps and inconsistent decrypt behavior in AfterFind.

Suggested patch
 func (c *TableOauthConfig) BeforeSave(tx *gorm.DB) error {
@@
 	// Encrypt sensitive fields (skip if value is an env var reference — the reference itself is not sensitive)
 	if encrypt.IsEnabled() {
-		encrypted := false
 		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
 			if err := encryptString(&c.ClientSecret); err != nil {
 				return fmt.Errorf("failed to encrypt oauth client secret: %w", err)
 			}
-			encrypted = true
 		}
 		if c.CodeVerifier != "" {
 			if err := encryptString(&c.CodeVerifier); err != nil {
 				return fmt.Errorf("failed to encrypt oauth code verifier: %w", err)
 			}
-			encrypted = true
-		}
-		if encrypted {
-			c.EncryptionStatus = EncryptionStatusEncrypted
 		}
+		c.EncryptionStatus = EncryptionStatusEncrypted
 	}
 	return nil
 }
@@
 func (c *TableOauthConfig) AfterFind(tx *gorm.DB) error {
 	if c.EncryptionStatus == EncryptionStatusEncrypted {
-		if err := decryptString(&c.ClientSecret); err != nil {
-			return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+		if c.ClientSecret != "" && !strings.HasPrefix(c.ClientSecret, "env.") {
+			if err := decryptString(&c.ClientSecret); err != nil {
+				return fmt.Errorf("failed to decrypt oauth client secret: %w", err)
+			}
 		}
-		if err := decryptString(&c.CodeVerifier); err != nil {
-			return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+		if c.CodeVerifier != "" {
+			if err := decryptString(&c.CodeVerifier); err != nil {
+				return fmt.Errorf("failed to decrypt oauth code verifier: %w", err)
+			}
 		}
 	}
 	return nil
 }

Based on learnings: “ensure EncryptionStatus is set to encrypted whenever encrypt.IsEnabled() is true (even if no fields are actually encrypted) … and guard decrypt calls in AfterFind.”

Also applies to: 73-81

🤖 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 `@framework/configstore/tables/oauth.go` around lines 50 - 67, When encryption
is enabled (encrypt.IsEnabled()) ensure you always set c.EncryptionStatus =
EncryptionStatusEncrypted even if no fields were actually encrypted (e.g.,
ClientSecret starting with "env.") by moving or adding the status assignment
outside the encrypted flag check; additionally, update the AfterFind decryption
logic to only attempt decryptString on ClientSecret/CodeVerifier when
c.EncryptionStatus == EncryptionStatusEncrypted to avoid decrypting env-backed
or plain-text rows. Use the existing symbols ClientSecret, CodeVerifier,
EncryptionStatus, EncryptionStatusEncrypted, encrypt.IsEnabled(), encryptString,
and the AfterFind method to locate and apply these changes.
♻️ Duplicate comments (1)
transports/bifrost-http/handlers/oauth2_per_user.go (1)

544-551: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the trimmed client_id when setting query params.

Line 551 still uses the untrimmed resolvedClientID, so accidental leading/trailing spaces in env-backed values can still cause upstream auth failures.

Suggested patch
-	resolvedClientID := templateConfig.GetResolvedClientID()
-	if strings.TrimSpace(resolvedClientID) == "" {
+	resolvedClientID := strings.TrimSpace(templateConfig.GetResolvedClientID())
+	if resolvedClientID == "" {
 		SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("OAuth client_id for config %q could not be resolved — check that the referenced environment variable is set", templateConfig.ID))
 		return
 	}
@@
-	params.Set("client_id", resolvedClientID)
+	params.Set("client_id", resolvedClientID)
🤖 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 `@transports/bifrost-http/handlers/oauth2_per_user.go` around lines 544 - 551,
The code obtains resolvedClientID via templateConfig.GetResolvedClientID() but
only checks the trimmed value; however params.Set("client_id", resolvedClientID)
still uses the untrimmed string—trim the resolvedClientID once (e.g., assign
trimmed := strings.TrimSpace(resolvedClientID)) and use that trimmed value when
setting params ("client_id") and in the empty-check logic (replace uses of
resolvedClientID in this block with the trimmed variable) so accidental
whitespace is removed before building the URL query.
🤖 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.

Outside diff comments:
In `@framework/configstore/tables/oauth.go`:
- Around line 50-67: When encryption is enabled (encrypt.IsEnabled()) ensure you
always set c.EncryptionStatus = EncryptionStatusEncrypted even if no fields were
actually encrypted (e.g., ClientSecret starting with "env.") by moving or adding
the status assignment outside the encrypted flag check; additionally, update the
AfterFind decryption logic to only attempt decryptString on
ClientSecret/CodeVerifier when c.EncryptionStatus == EncryptionStatusEncrypted
to avoid decrypting env-backed or plain-text rows. Use the existing symbols
ClientSecret, CodeVerifier, EncryptionStatus, EncryptionStatusEncrypted,
encrypt.IsEnabled(), encryptString, and the AfterFind method to locate and apply
these changes.

---

Duplicate comments:
In `@transports/bifrost-http/handlers/oauth2_per_user.go`:
- Around line 544-551: The code obtains resolvedClientID via
templateConfig.GetResolvedClientID() but only checks the trimmed value; however
params.Set("client_id", resolvedClientID) still uses the untrimmed string—trim
the resolvedClientID once (e.g., assign trimmed :=
strings.TrimSpace(resolvedClientID)) and use that trimmed value when setting
params ("client_id") and in the empty-check logic (replace uses of
resolvedClientID in this block with the trimmed variable) so accidental
whitespace is removed before building the URL query.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 73d14ae3-4f6e-4075-b6f2-8ece9b5533d3

📥 Commits

Reviewing files that changed from the base of the PR and between b408d70 and 9f648fa.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (2)
  • framework/configstore/store.go
  • core/schemas/mcp.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • ui/lib/types/mcp.ts
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • transports/bifrost-http/handlers/mcp.go
  • framework/oauth2/main.go
  • ui/lib/types/schemas.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 5, 2026
@BearTS BearTS changed the title feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching [DO NOT MERGE] feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching May 5, 2026
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from 9f648fa to 41b9996 Compare May 5, 2026 16:33

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 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 `@transports/bifrost-http/handlers/mcp.go`:
- Around line 159-173: The batch OAuth lookup currently swallows errors and
returns 200 with missing OauthClientID/Secret; in both getMCPClients and
getMCPClientsPaginated replace the silent-fail behavior around
h.store.ConfigStore.GetOauthConfigsByIDs: check the (fetched, err) result and if
err != nil return an appropriate HTTP/internal error (or propagate the error up)
instead of continuing with an empty oauthConfigsByID map; keep the existing
oauthConfigsByID variable and only set it when fetched is non-nil, but ensure
the request fails fast (500 or wrapped error) so DB read failures don't silently
remove OAuth metadata from responses.
- Around line 847-848: The code unconditionally overwrites oauthClientSecret
with req.OauthConfig.ClientSecret which clears stored secrets when only the
client_id is rotated; update the assignment logic so you only replace existing
values when the request provides them: for oauthClientID and oauthClientSecret
check whether req.OauthConfig.ClientID / ClientSecret are non-empty (or non-nil)
before assigning (e.g. only set oauthClientSecret = req.OauthConfig.ClientSecret
if ClientSecret is provided), preserving previous oauthClientSecret when the
request omits it; apply the same conditional-assignment fix to the other similar
blocks referenced around the oauth handling (the blocks that use oauthClientID
and oauthClientSecret at the other occurrences).
🪄 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

Run ID: ecd65b0f-496c-4999-9648-73d75e302d23

📥 Commits

Reviewing files that changed from the base of the PR and between 9f648fa and 41b9996.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (3)
  • ui/lib/types/mcp.ts
  • transports/bifrost-http/handlers/oauth2.go
  • core/schemas/mcp.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • framework/configstore/tables/oauth.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • framework/configstore/rdb.go
  • framework/oauth2/main.go

Comment thread transports/bifrost-http/handlers/mcp.go
Comment thread transports/bifrost-http/handlers/mcp.go
@BearTS BearTS force-pushed the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch from 41b9996 to aabd301 Compare May 5, 2026 16:53
@BearTS BearTS changed the title [DO NOT MERGE] feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching feat: support EnvVar references for MCP OAuth client_id/client_secret with redacted GET responses and batch config fetching May 5, 2026

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/mcp.go (1)

843-850: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Run the no-op OAuth check before rejecting disabled updates.

shouldRotateOAuthConfig is still true when the req.Disabled guard runs, so a request that only echoes the redacted oauth_config from GET is rejected as a credential rotation. That blocks legitimate disable/edit calls unless clients know to strip oauth_config entirely.

💡 Minimal fix
-	if shouldRotateOAuthConfig && req.Disabled {
-		SendError(ctx, fasthttp.StatusBadRequest, "oauth credentials cannot be rotated while disabling a client; send these as two separate requests")
-		return
-	}
 	if shouldRotateOAuthConfig {
 		if req.OauthConfig.ClientID.ShouldPreserveStored() && req.OauthConfig.ClientSecret.ShouldPreserveStored() {
 			shouldRotateOAuthConfig = false
 		}
 	}
+	if shouldRotateOAuthConfig && req.Disabled {
+		SendError(ctx, fasthttp.StatusBadRequest, "oauth credentials cannot be rotated while disabling a client; send these as two separate requests")
+		return
+	}
🤖 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 `@transports/bifrost-http/handlers/mcp.go` around lines 843 - 850, The
disabled-update guard is rejecting requests that only echo redacted oauth_config
because shouldRotateOAuthConfig is still true; evaluate the no-op check first by
inspecting req.OauthConfig.ClientID.ShouldPreserveStored() and
req.OauthConfig.ClientSecret.ShouldPreserveStored() and set
shouldRotateOAuthConfig = false when both indicate preservation before the if
req.Disabled branch, so the subsequent SendError call only triggers for real
rotations and not for redacted/no-op oauth_config updates.
🧹 Nitpick comments (1)
ui/app/workspace/mcp-registry/views/mcpClientForm.tsx (1)

33-33: ⚡ Quick win

Use the existing emptyEnvVar utility from @/lib/utils/envVarForm.

This duplicates the emptyEnvVar() factory function already exported from ui/lib/utils/envVarForm.ts. Import and use the utility to stay DRY and avoid potential mutation issues with a shared object reference.

Suggested fix
 import { parseArrayFromText } from "@/lib/utils/array";
+import { emptyEnvVar } from "@/lib/utils/envVarForm";
 import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";

Then update usages to call the factory:

-const emptyEnvVar: EnvVar = { value: "", env_var: "", from_env: false };
-
 const emptyOAuthConfig: OAuthConfig = {
-	client_id: emptyEnvVar,
-	client_secret: emptyEnvVar,
+	client_id: emptyEnvVar(),
+	client_secret: emptyEnvVar(),
 	authorize_url: "",

And similarly for emptyForm.connection_string and line 163.

Based on learnings: "In the UI codebase, prefer alias imports using @/... over relative imports" and the existence of emptyEnvVar in ui/lib/utils/envVarForm.ts.

🤖 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/app/workspace/mcp-registry/views/mcpClientForm.tsx` at line 33, Replace
the duplicated constant emptyEnvVar in mcpClientForm.tsx with the factory import
from '@/lib/utils/envVarForm' and call it where needed (use emptyEnvVar()
instead of the shared object); specifically remove the local const emptyEnvVar
and add an import of the factory, then update places that initialize or assign
env var objects (including emptyForm.connection_string and the usage around line
163) to call emptyEnvVar() so each form gets a fresh object instance.
🤖 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.

Outside diff comments:
In `@transports/bifrost-http/handlers/mcp.go`:
- Around line 843-850: The disabled-update guard is rejecting requests that only
echo redacted oauth_config because shouldRotateOAuthConfig is still true;
evaluate the no-op check first by inspecting
req.OauthConfig.ClientID.ShouldPreserveStored() and
req.OauthConfig.ClientSecret.ShouldPreserveStored() and set
shouldRotateOAuthConfig = false when both indicate preservation before the if
req.Disabled branch, so the subsequent SendError call only triggers for real
rotations and not for redacted/no-op oauth_config updates.

---

Nitpick comments:
In `@ui/app/workspace/mcp-registry/views/mcpClientForm.tsx`:
- Line 33: Replace the duplicated constant emptyEnvVar in mcpClientForm.tsx with
the factory import from '@/lib/utils/envVarForm' and call it where needed (use
emptyEnvVar() instead of the shared object); specifically remove the local const
emptyEnvVar and add an import of the factory, then update places that initialize
or assign env var objects (including emptyForm.connection_string and the usage
around line 163) to call emptyEnvVar() so each form gets a fresh object
instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 767d0619-48eb-49e0-a894-2b07d0b77b61

📥 Commits

Reviewing files that changed from the base of the PR and between 41b9996 and aabd301.

📒 Files selected for processing (13)
  • core/schemas/mcp.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth.go
  • framework/oauth2/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/oauth2.go
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/lib/config.go
  • ui/app/workspace/mcp-registry/views/mcpClientForm.tsx
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • ui/lib/types/mcp.ts
  • ui/lib/types/schemas.ts
✅ Files skipped from review due to trivial changes (4)
  • ui/lib/types/schemas.ts
  • core/schemas/mcp.go
  • ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
  • framework/oauth2/main.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/handlers/oauth2_per_user.go
  • transports/bifrost-http/handlers/oauth2.go
  • framework/configstore/store.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/oauth.go
  • ui/lib/types/mcp.ts

akshaydeo commented May 5, 2026

Copy link
Copy Markdown
Contributor

Merge activity

  • May 5, 5:28 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • May 5, 5:28 PM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo merged commit ecbfec1 into main May 5, 2026
15 of 16 checks passed
@akshaydeo akshaydeo deleted the 05-05-feat_envvar_on_oauth_config_in_mcp_settings branch May 5, 2026 17:28
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