From 87dd9d667a48c6c1ab0446333c21ad9bc163f151 Mon Sep 17 00:00:00 2001 From: Soumyadas15 Date: Thu, 3 Jul 2025 09:29:49 +0530 Subject: [PATCH] add credential profile direcly on install or setup dialog --- .../agents/_components/mcp/config-dialog.tsx | 483 +++++++++++++----- .../src/app/(dashboard)/marketplace/page.tsx | 239 ++++++++- .../(dashboard)/settings/credentials/page.tsx | 2 +- 3 files changed, 571 insertions(+), 153 deletions(-) diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx index 9740d59b..71b8360f 100644 --- a/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx @@ -2,14 +2,22 @@ import React, { useState } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { Loader2, Save, Sparkles, AlertTriangle } from 'lucide-react'; +import { Loader2, Save, Sparkles, AlertTriangle, Plus, Key, Settings, Shield } from 'lucide-react'; import { useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers'; import { CredentialProfileSelector } from '@/components/workflows/CredentialProfileSelector'; -import { useCredentialProfilesForMcp, type CredentialProfile } from '@/hooks/react-query/mcp/use-credential-profiles'; +import { + useCredentialProfilesForMcp, + useCreateCredentialProfile, + type CredentialProfile, + type CreateCredentialProfileRequest +} from '@/hooks/react-query/mcp/use-credential-profiles'; import { cn } from '@/lib/utils'; import { MCPConfiguration } from './types'; import { Card, CardContent } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; +import { toast } from 'sonner'; interface ConfigDialogProps { server: any; @@ -30,12 +38,40 @@ export const ConfigDialog: React.FC = ({ const [selectedProfileId, setSelectedProfileId] = useState( existingConfig?.selectedProfileId || null ); + const [showCreateProfileDialog, setShowCreateProfileDialog] = useState(false); + const [formData, setFormData] = useState<{ + profile_name: string; + display_name: string; + config: Record; + is_default: boolean; + }>({ + profile_name: `${server.displayName || server.name} Profile`, + display_name: server.displayName || server.name, + config: {}, + is_default: false + }); const { data: serverDetails, isLoading } = useMCPServerDetails(server.qualifiedName); + const { data: profiles = [], refetch: refetchProfiles } = useCredentialProfilesForMcp(server.qualifiedName); + const createProfileMutation = useCreateCredentialProfile(); const requiresConfig = serverDetails?.connections?.[0]?.configSchema?.properties && Object.keys(serverDetails.connections[0].configSchema.properties).length > 0; + const getConfigProperties = () => { + const schema = serverDetails?.connections?.[0]?.configSchema; + return schema?.properties || {}; + }; + + const getRequiredFields = () => { + const schema = serverDetails?.connections?.[0]?.configSchema; + return schema?.required || []; + }; + + const isFieldRequired = (fieldName: string) => { + return getRequiredFields().includes(fieldName); + }; + const handleSave = () => { const mcpConfig: MCPConfiguration = { name: server.displayName || server.name || server.qualifiedName, @@ -61,146 +97,321 @@ export const ConfigDialog: React.FC = ({ setSelectedProfileId(profileId); }; - return ( - - -
- {server.iconUrl ? ( -
- {server.displayName -
-
- ) : ( -
- -
- )} -
- Configure {server.displayName || server.name} -
-
- - Set up the connection and select which tools to enable for this MCP server. - - + const handleCreateNewProfile = () => { + setShowCreateProfileDialog(true); + }; - {isLoading ? ( -
- -
- ) : ( -
-
-

-
- Connection Settings -

- - {requiresConfig ? ( -
- { + setFormData(prev => ({ + ...prev, + config: { + ...prev.config, + [key]: value + } + })); + }; + + const handleCreateSubmit = async () => { + try { + const request: CreateCredentialProfileRequest = { + mcp_qualified_name: server.qualifiedName, + profile_name: formData.profile_name, + display_name: formData.display_name, + config: formData.config, + is_default: formData.is_default + }; + + const response = await createProfileMutation.mutateAsync(request); + toast.success('Credential profile created successfully!'); + + // Create a profile object to return + const newProfile: CredentialProfile = { + profile_id: response.profile_id || 'new-profile', + mcp_qualified_name: server.qualifiedName, + profile_name: formData.profile_name, + display_name: formData.display_name, + config_keys: Object.keys(formData.config), + is_active: true, + is_default: formData.is_default, + last_used_at: null, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }; + + // Refetch profiles to get the updated list + refetchProfiles(); + // Auto-select the newly created profile + setSelectedProfileId(newProfile.profile_id); + setShowCreateProfileDialog(false); + + // Reset form + setFormData({ + profile_name: `${server.displayName || server.name} Profile`, + display_name: server.displayName || server.name, + config: {}, + is_default: false + }); + } catch (error: any) { + toast.error(error.message || 'Failed to create credential profile'); + } + }; + + const configProperties = getConfigProperties(); + const hasConfigFields = Object.keys(configProperties).length > 0; + + return ( + <> + + +
+ {server.iconUrl ? ( +
+ {server.displayName - - {!selectedProfileId && ( - - - - Please select or create a credential profile to configure this MCP server. - - +
+
+ ) : ( +
+ +
+ )} +
+ Configure {server.displayName || server.name} +
+
+ + Set up the connection and select which tools to enable for this MCP server. + + + + {isLoading ? ( +
+ +
+ ) : ( +
+
+
+

+
+ Connection Settings +

+ {requiresConfig && ( + )}
- ) : ( -
- - -

No configuration required for this MCP server

-
-
-
- )} -
- -
-
-

-
- Available Tools -

- {serverDetails?.tools && serverDetails.tools.length > 0 && ( - - {selectedTools.size} of {serverDetails.tools.length} selected - + + {requiresConfig ? ( +
+ + + {!selectedProfileId && ( + + + + Please select or create a credential profile to configure this MCP server. + + + )} +
+ ) : ( +
+ + +

No configuration required for this MCP server

+
+
+
)}
- {serverDetails?.tools && serverDetails.tools.length > 0 ? ( - -
-
- {serverDetails.tools.map((tool: any) => ( -
handleToolToggle(tool.name)} - > - {}} - className="mt-1 accent-primary" - /> -
-
{tool.name}
- {tool.description && ( -
- {tool.description} -
- )} -
-
- ))} -
-
-
- ) : ( -
- - -

No tools available for this MCP server

-
-
-
- )} -
-
- )} - - - - - +
+
+

+
+ Available Tools +

+ {serverDetails?.tools && serverDetails.tools.length > 0 && ( + + {selectedTools.size} of {serverDetails.tools.length} selected + + )} +
+ {serverDetails?.tools && serverDetails.tools.length > 0 ? ( + +
+
+ {serverDetails.tools.map((tool: any) => ( +
handleToolToggle(tool.name)} + > + {}} + className="mt-1 accent-primary" + /> +
+
{tool.name}
+ {tool.description && ( +
+ {tool.description} +
+ )} +
+
+ ))} +
+
+
+ ) : ( +
+ + +

No tools available for this MCP server

+
+
+
+ )} +
+
+ )} + + + + + +
+ + {/* Create Profile Dialog */} + + + + + + Create Credential Profile + + + Create a new credential profile for {server.displayName || server.name} + + + +
+
+
+
+ + setFormData(prev => ({ ...prev, profile_name: e.target.value }))} + placeholder="Enter a name for this profile" + /> +

+ This helps you identify different configurations for the same MCP server +

+
+
+ + {hasConfigFields ? ( +
+

+ + Connection Settings +

+ {Object.entries(configProperties).map(([key, schema]: [string, any]) => ( +
+ + handleConfigChange(key, e.target.value)} + /> + {schema.description && ( +

{schema.description}

+ )} +
+ ))} +
+ ) : ( + + + + This MCP server doesn't require any API credentials to use. + + + )} + + + + + Your credentials will be encrypted and stored securely. You can create multiple profiles for the same MCP server to handle different use cases. + + +
+
+ + + + + +
+
+ ); }; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/marketplace/page.tsx b/frontend/src/app/(dashboard)/marketplace/page.tsx index 43faf088..ee4973e3 100644 --- a/frontend/src/app/(dashboard)/marketplace/page.tsx +++ b/frontend/src/app/(dashboard)/marketplace/page.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState, useMemo, useEffect } from 'react'; -import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Shield, CheckCircle, Loader2, Settings, Wrench, AlertTriangle, GitBranch, Plus, ShoppingBag } from 'lucide-react'; +import { Search, Download, Star, Calendar, User, Tags, TrendingUp, Shield, CheckCircle, Loader2, Settings, Wrench, AlertTriangle, GitBranch, Plus, ShoppingBag, Key } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; @@ -10,6 +10,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { Card, CardContent } from '@/components/ui/card'; +import { Label } from '@/components/ui/label'; import { toast } from 'sonner'; import { getAgentAvatar } from '../agents/_utils/get-agent-style'; import { Skeleton } from '@/components/ui/skeleton'; @@ -19,7 +20,13 @@ import { useMarketplaceTemplates, useInstallTemplate } from '@/hooks/react-query/secure-mcp/use-secure-mcp'; -import { useCredentialProfilesForMcp } from '@/hooks/react-query/mcp/use-credential-profiles'; +import { + useCredentialProfilesForMcp, + useCreateCredentialProfile, + type CredentialProfile, + type CreateCredentialProfileRequest +} from '@/hooks/react-query/mcp/use-credential-profiles'; +import { useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers'; import { createClient } from '@/lib/supabase/client'; import { useFeatureFlag } from '@/lib/feature-flags'; import { useRouter } from 'next/navigation'; @@ -259,6 +266,23 @@ const InstallDialog: React.FC = ({ const [isCheckingRequirements, setIsCheckingRequirements] = useState(false); const [setupSteps, setSetupSteps] = useState([]); const [missingProfiles, setMissingProfiles] = useState([]); + const [showCreateProfileDialog, setShowCreateProfileDialog] = useState(false); + const [createProfileForQualifiedName, setCreateProfileForQualifiedName] = useState(''); + const [createProfileForDisplayName, setCreateProfileForDisplayName] = useState(''); + const [formData, setFormData] = useState<{ + profile_name: string; + display_name: string; + config: Record; + is_default: boolean; + }>({ + profile_name: '', + display_name: '', + config: {}, + is_default: false + }); + + const createProfileMutation = useCreateCredentialProfile(); + const { data: serverDetails } = useMCPServerDetails(createProfileForQualifiedName); React.useEffect(() => { if (item && open) { @@ -271,7 +295,6 @@ const InstallDialog: React.FC = ({ } }, [item, open]); - // Add a function to refresh requirements when profiles are created const refreshRequirements = React.useCallback(() => { if (item && open) { setIsCheckingRequirements(true); @@ -296,7 +319,6 @@ const InstallDialog: React.FC = ({ }); } - // Add custom server configuration steps (just ask for URL, no credentials needed) for (const req of customServers) { steps.push({ id: req.qualified_name, @@ -317,7 +339,7 @@ const InstallDialog: React.FC = ({ } setSetupSteps(steps); - setMissingProfiles([]); // Clear missing profiles since we handle them in setup steps + setMissingProfiles([]); setIsCheckingRequirements(false); }; @@ -338,6 +360,76 @@ const InstallDialog: React.FC = ({ })); }; + const handleCreateNewProfile = (qualifiedName: string, displayName: string) => { + setCreateProfileForQualifiedName(qualifiedName); + setCreateProfileForDisplayName(displayName); + setFormData({ + profile_name: `${displayName} Profile`, + display_name: displayName, + config: {}, + is_default: false + }); + setShowCreateProfileDialog(true); + }; + + const getConfigProperties = () => { + const schema = serverDetails?.connections?.[0]?.configSchema; + return schema?.properties || {}; + }; + + const getRequiredFields = () => { + const schema = serverDetails?.connections?.[0]?.configSchema; + return schema?.required || []; + }; + + const isFieldRequired = (fieldName: string) => { + return getRequiredFields().includes(fieldName); + }; + + const handleConfigChange = (key: string, value: string) => { + setFormData(prev => ({ + ...prev, + config: { + ...prev.config, + [key]: value + } + })); + }; + + const handleCreateSubmit = async () => { + try { + const request: CreateCredentialProfileRequest = { + mcp_qualified_name: createProfileForQualifiedName, + profile_name: formData.profile_name, + display_name: formData.display_name, + config: formData.config, + is_default: formData.is_default + }; + + const response = await createProfileMutation.mutateAsync(request); + toast.success('Credential profile created successfully!'); + + // Auto-select the newly created profile + setProfileMappings(prev => ({ + ...prev, + [createProfileForQualifiedName]: response.profile_id || 'new-profile' + })); + + setShowCreateProfileDialog(false); + refreshRequirements(); + + // Reset form + setFormData({ + profile_name: '', + display_name: '', + config: {}, + is_default: false + }); + } catch (error: any) { + toast.error(error.message || 'Failed to create credential profile'); + } + }; + const isCurrentStepComplete = (): boolean => { if (setupSteps.length === 0) return true; if (currentStep >= setupSteps.length) return true; @@ -461,17 +553,33 @@ const InstallDialog: React.FC = ({
{currentStepData.type === 'credential_profile' ? ( - { - handleProfileSelect(currentStepData.qualified_name, profileId); - if (profile && !profileMappings[currentStepData.qualified_name]) { - refreshRequirements(); - } - }} - /> +
+
+
+ { + handleProfileSelect(currentStepData.qualified_name, profileId); + if (profile && !profileMappings[currentStepData.qualified_name]) { + refreshRequirements(); + } + }} + /> +
+
+
+ +
+
) : ( currentStepData.required_fields?.map((field) => (
@@ -588,6 +696,105 @@ const InstallDialog: React.FC = ({
+ + {/* Create Profile Dialog */} + + + + + + Create Credential Profile + + + Create a new credential profile for {createProfileForDisplayName} + + + +
+
+
+
+ + setFormData(prev => ({ ...prev, profile_name: e.target.value }))} + placeholder="Enter a name for this profile" + /> +

+ This helps you identify different configurations for the same MCP server +

+
+
+ + {Object.keys(getConfigProperties()).length > 0 ? ( +
+

+ + Connection Settings +

+ {Object.entries(getConfigProperties()).map(([key, schema]: [string, any]) => ( +
+ + handleConfigChange(key, e.target.value)} + /> + {schema.description && ( +

{schema.description}

+ )} +
+ ))} +
+ ) : ( + + + + This MCP server doesn't require any API credentials to use. + + + )} + + + + + Your credentials will be encrypted and stored securely. You can create multiple profiles for the same MCP server to handle different use cases. + + +
+
+ + + + + +
+
); }; diff --git a/frontend/src/app/(dashboard)/settings/credentials/page.tsx b/frontend/src/app/(dashboard)/settings/credentials/page.tsx index b4a44f5a..c5ef09c3 100644 --- a/frontend/src/app/(dashboard)/settings/credentials/page.tsx +++ b/frontend/src/app/(dashboard)/settings/credentials/page.tsx @@ -206,7 +206,7 @@ const DeleteConfirmationDialog: React.FC = ({