Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,30 @@ export default function ProviderDetailPage() {

type ProviderDetailTab = "overview" | "configuration"

/**
* Check if a provider is an MCP (Model Context Protocol) provider.
* MCP providers don't require user-provided client credentials.
*/
function isMCPProvider(provider: ProviderRead): boolean {
// MCP providers follow the naming convention of ending with "_mcp"
return provider.metadata.id.endsWith("_mcp")
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

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

Suffix-based MCP detection hides the Configuration tab for all “*_mcp” providers; fallback MCPs that require configuration will be blocked. Use an explicit metadata flag for self-configuring MCPs instead of id suffix.

Prompt for AI agents
Address the following comment on frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx at line 112:

<comment>Suffix-based MCP detection hides the Configuration tab for all “*_mcp” providers; fallback MCPs that require configuration will be blocked. Use an explicit metadata flag for self-configuring MCPs instead of id suffix.</comment>

<file context>
@@ -103,18 +103,30 @@ export default function ProviderDetailPage() {
+ */
+function isMCPProvider(provider: ProviderRead): boolean {
+  // MCP providers follow the naming convention of ending with &quot;_mcp&quot;
+  return provider.metadata.id.endsWith(&quot;_mcp&quot;)
+}
+
</file context>

[internal] Confidence score: 7/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

}

function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
const workspaceId = useWorkspaceId()
const router = useRouter()
const searchParams = useSearchParams()
const [errorMessage, setErrorMessage] = useState("")
const [_showConnectPrompt, setShowConnectPrompt] = useState(false)
const providerId = provider.metadata.id
const isMCP = isMCPProvider(provider)

// Get active tab from URL query params, default to "overview"
// For MCP providers, always use "overview" since there's no configuration tab
const activeTab = (
searchParams &&
["overview", "configuration"].includes(searchParams.get("tab") || "")
["overview", "configuration"].includes(searchParams.get("tab") || "") &&
!isMCP // Don't allow configuration tab for MCP providers
? (searchParams.get("tab") ?? "overview")
: "overview"
) as ProviderDetailTab
Expand Down Expand Up @@ -294,11 +306,21 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
variant="outline"
size="sm"
className="h-[22px] px-2 py-0 text-xs font-medium"
onClick={() => handleTabChange("configuration")}
disabled={!isEnabled}
onClick={
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

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

MCP “Connect” button calls handleOAuthConnect, but failures only set errorMessage, which is no longer rendered—users get no feedback on errors.

Prompt for AI agents
Address the following comment on frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx at line 309:

<comment>MCP “Connect” button calls handleOAuthConnect, but failures only set errorMessage, which is no longer rendered—users get no feedback on errors.</comment>

<file context>
@@ -294,11 +306,21 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
                     className=&quot;h-[22px] px-2 py-0 text-xs font-medium&quot;
-                    onClick={() =&gt; handleTabChange(&quot;configuration&quot;)}
-                    disabled={!isEnabled}
+                    onClick={
+                      isMCP
+                        ? handleOAuthConnect
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

isMCP
? handleOAuthConnect
: () => handleTabChange("configuration")
}
disabled={!isEnabled || (isMCP && connectProviderIsPending)}
>
<Settings className="mr-1 h-3 w-3" />
Configure
{isMCP && connectProviderIsPending ? (
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
) : isMCP ? (
<ExternalLink className="mr-1 h-3 w-3" />
) : (
<Settings className="mr-1 h-3 w-3" />
)}
{isMCP ? "Connect" : "Configure"}
</Button>
)}
</div>
Expand All @@ -307,15 +329,6 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
</div>
</div>

{errorMessage && (
<Alert className="mb-6 border-red-200 bg-red-50">
<AlertCircle className="size-4 text-red-600" />
<AlertDescription className="text-red-800">
{errorMessage}
</AlertDescription>
</Alert>
)}

{/* Tabs */}
<Tabs
value={activeTab}
Expand All @@ -330,13 +343,15 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
<LayoutListIcon className="mr-2 size-4" />
<span>Overview</span>
</TabsTrigger>
<TabsTrigger
className="flex h-full min-w-24 items-center justify-center rounded-none border-b-2 border-transparent py-0 text-xs data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
value="configuration"
>
<Settings className="mr-2 size-4" />
<span>Configuration</span>
</TabsTrigger>
{!isMCP && (
<TabsTrigger
className="flex h-full min-w-24 items-center justify-center rounded-none border-b-2 border-transparent py-0 text-xs data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
value="configuration"
>
<Settings className="mr-2 size-4" />
<span>Configuration</span>
</TabsTrigger>
)}
</TabsList>

<TabsContent value="overview" className="space-y-6">
Expand Down Expand Up @@ -514,54 +529,56 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) {
</div>
</TabsContent>

<TabsContent value="configuration" className="space-y-6">
{/* Configuration Form */}
<div className="space-y-4">
<ProviderConfigForm
provider={provider}
onSuccess={handleConfigSuccess}
additionalButtons={
isConfigured ? (
provider.grant_type === "client_credentials" ? (
<Button
onClick={handleTestConnection}
disabled={!isEnabled || testConnectionIsPending}
>
{testConnectionIsPending ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Testing...
</>
) : (
<>
<Zap className="mr-2 size-4" />
Test connection
</>
)}
</Button>
) : (
<Button
onClick={handleOAuthConnect}
disabled={!isEnabled || connectProviderIsPending}
>
{connectProviderIsPending ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Connecting...
</>
) : (
<>
<ExternalLink className="mr-2 size-4" />
Connect with OAuth
</>
)}
</Button>
)
) : null
}
/>
</div>
</TabsContent>
{!isMCP && (
<TabsContent value="configuration" className="space-y-6">
{/* Configuration Form */}
<div className="space-y-4">
<ProviderConfigForm
provider={provider}
onSuccess={handleConfigSuccess}
additionalButtons={
isConfigured ? (
provider.grant_type === "client_credentials" ? (
<Button
onClick={handleTestConnection}
disabled={!isEnabled || testConnectionIsPending}
>
{testConnectionIsPending ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Testing...
</>
) : (
<>
<Zap className="mr-2 size-4" />
Test connection
</>
)}
</Button>
) : (
<Button
onClick={handleOAuthConnect}
disabled={!isEnabled || connectProviderIsPending}
>
{connectProviderIsPending ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Connecting...
</>
) : (
<>
<ExternalLink className="mr-2 size-4" />
Connect with OAuth
</>
)}
</Button>
)
) : null
}
/>
</div>
</TabsContent>
)}
</Tabs>
</div>
)
Expand Down
46 changes: 46 additions & 0 deletions frontend/src/components/provider-config-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ interface ProviderConfigFormProps {
additionalButtons?: React.ReactNode
}

/**
* Check if a provider is an MCP (Model Context Protocol) provider.
* MCP providers don't require user-provided client credentials.
*/
function isMCPProvider(provider: ProviderRead): boolean {
// MCP providers follow the naming convention of ending with "_mcp"
return provider.metadata.id.endsWith("_mcp")
}

export function ProviderConfigForm({
provider,
onSuccess,
Expand All @@ -98,6 +107,7 @@ export function ProviderConfigForm({
grant_type: grantType,
} = provider
const workspaceId = useWorkspaceId()
const isMCP = isMCPProvider(provider)
const {
integration,
integrationIsLoading,
Expand Down Expand Up @@ -171,6 +181,42 @@ export function ProviderConfigForm({
return <ProviderConfigFormSkeleton />
}

// For MCP providers, show a simplified message
if (isMCP) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

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

additionalButtons is ignored in the MCP branch, so action controls (e.g., Connect) passed by parent won't render here, causing inconsistent UX and potentially hiding critical actions.

Prompt for AI agents
Address the following comment on frontend/src/components/provider-config-form.tsx at line 185:

<comment>`additionalButtons` is ignored in the MCP branch, so action controls (e.g., Connect) passed by parent won&#39;t render here, causing inconsistent UX and potentially hiding critical actions.</comment>

<file context>
@@ -171,6 +181,42 @@ export function ProviderConfigForm({
   }
 
+  // For MCP providers, show a simplified message
+  if (isMCP) {
+    return (
+      &lt;div className=&quot;flex flex-col gap-6&quot;&gt;
</file context>

[internal] Confidence score: 7/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

return (
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>MCP OAuth Provider</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">
This is a Model Context Protocol (MCP) provider that uses
server-managed OAuth credentials. No client configuration is
required - simply click "Connect" to authenticate.
</p>
{defaultScopes && defaultScopes.length > 0 && (
<div className="space-y-2">
<Label className="text-sm font-medium">Default scopes</Label>
<div className="flex flex-wrap gap-2">
{defaultScopes.map((scope) => (
<Badge key={scope} variant="secondary">
{scope}
</Badge>
))}
</div>
<p className="text-xs text-muted-foreground">
The authorization server will determine the granted scopes
based on your permissions.
</p>
</div>
)}
</CardContent>
</Card>
</div>
)
}

return (
<div className="flex flex-col gap-6">
{/* Current Configuration Summary */}
Expand Down
12 changes: 12 additions & 0 deletions tracecat/integrations/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@

from tracecat.integrations.models import ProviderKey
from tracecat.integrations.providers.base import BaseOAuthProvider
from tracecat.integrations.providers.github.mcp import GitHubMCPProvider
from tracecat.integrations.providers.linear.mcp import LinearMCPProvider
from tracecat.integrations.providers.microsoft.graph import (
MicrosoftGraphACProvider,
MicrosoftGraphCCProvider,
)
from tracecat.integrations.providers.microsoft.mcp import MicrosoftLearnMCPProvider
from tracecat.integrations.providers.microsoft.teams import (
MicrosoftTeamsACProvider,
MicrosoftTeamsCCProvider,
)
from tracecat.integrations.providers.notion.mcp import NotionMCPProvider
from tracecat.integrations.providers.runreveal.mcp import RunRevealMCPProvider
from tracecat.integrations.providers.sentry.mcp import SentryMCPProvider

_PROVIDER_CLASSES: list[type[BaseOAuthProvider]] = [
MicrosoftGraphACProvider,
MicrosoftGraphCCProvider,
MicrosoftTeamsACProvider,
MicrosoftTeamsCCProvider,
MicrosoftLearnMCPProvider,
GitHubMCPProvider,
LinearMCPProvider,
NotionMCPProvider,
RunRevealMCPProvider,
SentryMCPProvider,
]


Expand Down
Loading