-
Notifications
You must be signed in to change notification settings - Fork 0
feat(integrations): MCP OAuth providers #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: eval-pr-1447-target-1758209625676
Are you sure you want to change the base?
Changes from all commits
84416e4
cb4ecc8
96c9e4b
fc6a5a1
4a8d8b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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") | ||
} | ||
|
||
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 | ||
|
@@ -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={ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent |
||
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> | ||
|
@@ -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} | ||
|
@@ -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"> | ||
|
@@ -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> | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -98,6 +107,7 @@ export function ProviderConfigForm({ | |
grant_type: grantType, | ||
} = provider | ||
const workspaceId = useWorkspaceId() | ||
const isMCP = isMCPProvider(provider) | ||
const { | ||
integration, | ||
integrationIsLoading, | ||
|
@@ -171,6 +181,42 @@ export function ProviderConfigForm({ | |
return <ProviderConfigFormSkeleton /> | ||
} | ||
|
||
// For MCP providers, show a simplified message | ||
if (isMCP) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt for AI agents
[internal] Confidence score: 7/10 [internal] Posted by: General AI Review Agent |
||
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 */} | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
[internal] Confidence score: 7/10
[internal] Posted by: General AI Review Agent