From 909f313e1e0df6a2a29177db5eb78ad1bbcdcac9 Mon Sep 17 00:00:00 2001 From: Zamil Majdy Date: Tue, 10 Feb 2026 07:39:44 +0400 Subject: [PATCH] fix(frontend/mcp): Filter credential auto-select by server URL discriminator Prevent MCP credential cross-contamination where a credential for one server (e.g. Sentry) fills credential fields for other servers (e.g. Linear). Adds matchesDiscriminatorValues() to match credentials by host against discriminator_values from the schema. --- .../CredentialsGroupedView.tsx | 2 + .../CredentialsGroupedView/helpers.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView.tsx b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView.tsx index 135a960431..d218bcf8ea 100644 --- a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView.tsx +++ b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView.tsx @@ -86,11 +86,13 @@ export function CredentialsGroupedView({ const providerNames = schema.credentials_provider || []; const credentialTypes = schema.credentials_types || []; const requiredScopes = schema.credentials_scopes; + const discriminatorValues = schema.discriminator_values; const savedCredential = findSavedCredentialByProviderAndType( providerNames, credentialTypes, requiredScopes, allProviders, + discriminatorValues, ); if (savedCredential) { diff --git a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts index 5f439d3a32..319a67508d 100644 --- a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts +++ b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsGroupedView/helpers.ts @@ -23,6 +23,32 @@ function hasRequiredScopes( return true; } +/** Check if a credential matches the discriminator values (e.g. MCP server URL). */ +function matchesDiscriminatorValues( + credential: { host?: string | null; provider: string; type: string }, + discriminatorValues?: string[], +) { + // MCP OAuth2 credentials must match by server URL + if (credential.type === "oauth2" && credential.provider === "mcp") { + if (!discriminatorValues || discriminatorValues.length === 0) return false; + return ( + credential.host != null && discriminatorValues.includes(credential.host) + ); + } + // Host-scoped credentials match by host + if (credential.type === "host_scoped" && credential.host) { + if (!discriminatorValues || discriminatorValues.length === 0) return true; + return discriminatorValues.some((v) => { + try { + return new URL(v).hostname === credential.host; + } catch { + return false; + } + }); + } + return true; +} + export function splitCredentialFieldsBySystem( credentialFields: CredentialField[], allProviders: CredentialsProvidersContextType | null, @@ -160,6 +186,7 @@ export function findSavedCredentialByProviderAndType( credentialTypes: string[], requiredScopes: string[] | undefined, allProviders: CredentialsProvidersContextType | null, + discriminatorValues?: string[], ): SavedCredential | undefined { for (const providerName of providerNames) { const providerData = allProviders?.[providerName]; @@ -176,9 +203,14 @@ export function findSavedCredentialByProviderAndType( credentialTypes.length === 0 || credentialTypes.includes(credential.type); const scopesMatch = hasRequiredScopes(credential, requiredScopes); + const hostMatches = matchesDiscriminatorValues( + credential, + discriminatorValues, + ); if (!typeMatches) continue; if (!scopesMatch) continue; + if (!hostMatches) continue; matchingCredentials.push(credential as SavedCredential); } @@ -190,9 +222,14 @@ export function findSavedCredentialByProviderAndType( credentialTypes.length === 0 || credentialTypes.includes(credential.type); const scopesMatch = hasRequiredScopes(credential, requiredScopes); + const hostMatches = matchesDiscriminatorValues( + credential, + discriminatorValues, + ); if (!typeMatches) continue; if (!scopesMatch) continue; + if (!hostMatches) continue; matchingCredentials.push(credential as SavedCredential); } @@ -214,6 +251,7 @@ export function findSavedUserCredentialByProviderAndType( credentialTypes: string[], requiredScopes: string[] | undefined, allProviders: CredentialsProvidersContextType | null, + discriminatorValues?: string[], ): SavedCredential | undefined { for (const providerName of providerNames) { const providerData = allProviders?.[providerName]; @@ -230,9 +268,14 @@ export function findSavedUserCredentialByProviderAndType( credentialTypes.length === 0 || credentialTypes.includes(credential.type); const scopesMatch = hasRequiredScopes(credential, requiredScopes); + const hostMatches = matchesDiscriminatorValues( + credential, + discriminatorValues, + ); if (!typeMatches) continue; if (!scopesMatch) continue; + if (!hostMatches) continue; matchingCredentials.push(credential as SavedCredential); }