From 6fded9402655ad6e8efb7d2075582afa444afb67 Mon Sep 17 00:00:00 2001 From: Soumyadas15 Date: Fri, 30 May 2025 16:33:53 +0530 Subject: [PATCH] chore(ui): refactor mcp config ui --- .../_components/agent-mcp-configuration.tsx | 3 +- .../_components/create-agent-dialog.tsx | 3 +- .../mcp/_loaders/mcp-list-loader.tsx | 59 +++++++ .../mcp/_loaders/mcp-search-loader.tsx | 38 +++++ .../agents/_components/mcp/browse-dialog.tsx | 109 +++++++++++++ .../_components/mcp/categorized-servers.tsx | 78 +++++++++ .../_components/mcp/category-sidebar.tsx | 61 +++++++ .../agents/_components/mcp/config-dialog.tsx | 149 ++++++++++++++++++ .../_components/mcp/configured-mcp-list.tsx | 58 +++++++ .../agents/_components/mcp/constants.ts | 28 ++++ .../_components/mcp/mcp-configuration-new.tsx | 92 +++++++++++ .../_components/mcp/mcp-server-card.tsx | 51 ++++++ .../agents/_components/mcp/search-results.tsx | 44 ++++++ .../agents/_components/mcp/types.ts | 11 ++ .../_components/update-agent-dialog.tsx | 3 +- 15 files changed, 784 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-list-loader.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-search-loader.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/categorized-servers.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/category-sidebar.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/configured-mcp-list.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/constants.ts create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/mcp-configuration-new.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/mcp-server-card.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/search-results.tsx create mode 100644 frontend/src/app/(dashboard)/agents/_components/mcp/types.ts diff --git a/frontend/src/app/(dashboard)/agents/_components/agent-mcp-configuration.tsx b/frontend/src/app/(dashboard)/agents/_components/agent-mcp-configuration.tsx index 6a1bb732..c2468378 100644 --- a/frontend/src/app/(dashboard)/agents/_components/agent-mcp-configuration.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/agent-mcp-configuration.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Sparkles } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { MCPConfiguration } from './mcp-configuration'; +import { MCPConfigurationNew } from './mcp/mcp-configuration-new'; interface AgentMCPConfigurationProps { mcps: Array<{ name: string; qualifiedName: string; config: any; enabledTools?: string[] }>; @@ -12,7 +13,7 @@ export const AgentMCPConfiguration = ({ mcps, onMCPsChange }: AgentMCPConfigurat return ( - diff --git a/frontend/src/app/(dashboard)/agents/_components/create-agent-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/create-agent-dialog.tsx index b2eb2a0d..cfc13647 100644 --- a/frontend/src/app/(dashboard)/agents/_components/create-agent-dialog.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/create-agent-dialog.tsx @@ -10,6 +10,7 @@ import { Loader2, Search, Settings2, Sparkles } from 'lucide-react'; import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools'; import { useCreateAgent } from '@/hooks/react-query/agents/use-agents'; import { MCPConfiguration } from './mcp-configuration'; +import { MCPConfigurationNew } from './mcp/mcp-configuration-new'; interface AgentCreateRequest { name: string; @@ -276,7 +277,7 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea - diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-list-loader.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-list-loader.tsx new file mode 100644 index 00000000..88b65060 --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-list-loader.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Card } from '@/components/ui/card'; + +export const McpListLoader: React.FC = () => { + return ( +
+
+ +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ + + +
+ ))} +
+
+ +
+ {Array.from({ length: 3 }).map((_, categoryIndex) => ( +
+
+
+ + +
+
+ + +
+
+ +
+ {Array.from({ length: 6 }).map((_, cardIndex) => ( + +
+ +
+
+ + +
+ + + +
+ +
+
+ ))} +
+
+ ))} +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-search-loader.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-search-loader.tsx new file mode 100644 index 00000000..3b03ff1a --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/_loaders/mcp-search-loader.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Card } from '@/components/ui/card'; + +export const McpSearchLoader: React.FC = () => { + return ( +
+
+ + +
+ +
+ {Array.from({ length: 8 }).map((_, index) => ( + +
+ +
+
+ + + +
+ + +
+ + +
+
+ +
+
+ ))} +
+
+ ); +}; diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx new file mode 100644 index 00000000..a9ddfa30 --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Search } from 'lucide-react'; +import { usePopularMCPServers, usePopularMCPServersV2, useMCPServers } from '@/hooks/react-query/mcp/use-mcp-servers'; +import { McpServerCard } from './mcp-server-card'; +import { CategorySidebar } from './category-sidebar'; +import { SearchResults } from './search-results'; +import { CategorizedServersList } from './categorized-servers'; +import { McpListLoader } from './_loaders/mcp-list-loader'; + +interface BrowseDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onServerSelect: (server: any) => void; +} + +export const BrowseDialog: React.FC = ({ + open, + onOpenChange, + onServerSelect, +}) => { + const [searchQuery, setSearchQuery] = useState(''); + const [selectedCategory, setSelectedCategory] = useState(null); + + const { data: popularServers } = usePopularMCPServers(); + const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2(); + const { data: searchResults, isLoading: isSearching } = useMCPServers( + searchQuery.length > 2 ? searchQuery : undefined + ); + + const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : []; + + return ( + + + + Browse MCP Servers + + Discover and add Model Context Protocol servers from Smithery + + + + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+ +
+ {!searchQuery && categories.length > 0 && ( + + )} +
+ +
+ {searchQuery && ( + + )} + {!searchQuery && ( + <> + {isLoadingV2 ? ( + + ) : popularServersV2?.success ? ( + + ) : popularServers ? ( +
+

Popular Servers

+
+ {popularServers.servers.map((server) => ( + + ))} +
+
+ ) : null} + + )} +
+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/categorized-servers.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/categorized-servers.tsx new file mode 100644 index 00000000..116fb94d --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/categorized-servers.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { categoryIcons } from './constants'; +import { McpServerCard } from './mcp-server-card'; + +interface CategorizedServersListProps { + categorizedServers: Record; + selectedCategory: string | null; + onServerSelect: (server: any) => void; + onCategorySelect: (category: string) => void; +} + +export const CategorizedServersList: React.FC = ({ + categorizedServers, + selectedCategory, + onServerSelect, + onCategorySelect, +}) => { + if (selectedCategory) { + const servers = categorizedServers[selectedCategory] || []; + return ( +
+
+ {categoryIcons[selectedCategory] || "🧩"} +

{selectedCategory}

+ + {servers.length} servers + +
+
+ {servers.map((server) => ( + + ))} +
+
+ ); + } + + return ( +
+ {Object.entries(categorizedServers).map(([category, servers]) => ( +
+
+
+ {categoryIcons[category] || "🧩"} +

{category}

+
+
+ {servers.length} servers + +
+
+
+ {servers.slice(0, 6).map((server) => ( + + ))} +
+
+ ))} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/category-sidebar.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/category-sidebar.tsx new file mode 100644 index 00000000..2f36416b --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/category-sidebar.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { cn } from '@/lib/utils'; +import { categoryIcons } from './constants'; + +interface CategorySidebarProps { + categories: string[]; + selectedCategory: string | null; + onCategorySelect: (category: string | null) => void; + categorizedServers: Record; +} + +export const CategorySidebar: React.FC = ({ + categories, + selectedCategory, + onCategorySelect, + categorizedServers, +}) => { + return ( +
+

Categories

+ +
+ + {categories.map((category) => { + const count = categorizedServers[category]?.length || 0; + return ( + + ); + })} +
+
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx new file mode 100644 index 00000000..1104f3cd --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/config-dialog.tsx @@ -0,0 +1,149 @@ +import React, { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Loader2 } from 'lucide-react'; +import { useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers'; +import { cn } from '@/lib/utils'; +import { MCPConfiguration } from './types'; + +interface ConfigDialogProps { + server: any; + existingConfig?: MCPConfiguration; + onSave: (config: MCPConfiguration) => void; + onCancel: () => void; +} + +export const ConfigDialog: React.FC = ({ + server, + existingConfig, + onSave, + onCancel +}) => { + const [config, setConfig] = useState>(existingConfig?.config || {}); + const [selectedTools, setSelectedTools] = useState>( + new Set(existingConfig?.enabledTools || []) + ); + + const { data: serverDetails, isLoading } = useMCPServerDetails(server.qualifiedName); + + const handleSave = () => { + onSave({ + name: server.displayName || server.name || server.qualifiedName, + qualifiedName: server.qualifiedName, + config, + enabledTools: Array.from(selectedTools), + }); + }; + + const handleToolToggle = (toolName: string) => { + const newTools = new Set(selectedTools); + if (newTools.has(toolName)) { + newTools.delete(toolName); + } else { + newTools.add(toolName); + } + setSelectedTools(newTools); + }; + + return ( + + + Configure {server.displayName || server.name} + + Set up the connection and select which tools to enable for this MCP server. + + + + {isLoading ? ( +
+ +
+ ) : ( + +
+ {serverDetails?.connections?.[0]?.configSchema?.properties && ( +
+

Connection Settings

+ {Object.entries(serverDetails.connections[0].configSchema.properties).map(([key, schema]: [string, any]) => ( +
+ + setConfig({ ...config, [key]: e.target.value })} + /> + {schema.description && ( +

{schema.description}

+ )} +
+ ))} +
+ )} + + {serverDetails?.tools && serverDetails.tools.length > 0 && ( +
+
+

Available Tools

+ + {selectedTools.size} of {serverDetails.tools.length} selected + +
+
+ {serverDetails.tools.map((tool: any) => ( +
handleToolToggle(tool.name)} + > + {}} + className="mt-1" + /> +
+
{tool.name}
+ {tool.description && ( +
+ {tool.description} +
+ )} +
+
+ ))} +
+
+ )} +
+
+ )} + + + + + +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/configured-mcp-list.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/configured-mcp-list.tsx new file mode 100644 index 00000000..f307c807 --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/configured-mcp-list.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Settings, X, Sparkles } from 'lucide-react'; +import { MCPConfiguration } from './types'; + + +interface ConfiguredMcpListProps { + configuredMCPs: MCPConfiguration[]; + onEdit: (index: number) => void; + onRemove: (index: number) => void; +} + +export const ConfiguredMcpList: React.FC = ({ + configuredMCPs, + onEdit, + onRemove, +}) => { + if (configuredMCPs.length === 0) return null; + + return ( +
+ {configuredMCPs.map((mcp, index) => ( + +
+
+
+ +
+
+
{mcp.name}
+
+ {mcp.enabledTools?.length || 0} tools enabled +
+
+
+
+ + +
+
+
+ ))} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/constants.ts b/frontend/src/app/(dashboard)/agents/_components/mcp/constants.ts new file mode 100644 index 00000000..1f2fe06d --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/constants.ts @@ -0,0 +1,28 @@ +export const categoryIcons = { + "AI & Search": "🤖", + "Development & Version Control": "🔧", + "Automation & Productivity": "⚡", + "Communication & Collaboration": "💬", + "Project Management": "📅", + "Data & Analytics": "📊", + "Cloud & Infrastructure": "☁️", + "File Storage": "📁", + "Marketing & Sales": "🛒", + "Customer Support": "🎧", + "Finance": "💰", + "Utilities": "🔨", + "Other": "🧩", + + "development": "🔧", + "ai": "🤖", + "automation": "⚡", + "search": "🔍", + "Database": "📊", + "Web": "🌐", + "File": "📄", + "Development": "💻", + "AI": "🤖", + "Cloud": "☁️", + "Utility": "⚡", + "Integration": "🧩", +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-configuration-new.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-configuration-new.tsx new file mode 100644 index 00000000..6b385fc8 --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-configuration-new.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Plus } from 'lucide-react'; +import { Dialog } from '@/components/ui/dialog'; +import { MCPConfigurationProps, MCPConfiguration as MCPConfigurationType } from './types'; +import { ConfiguredMcpList } from './configured-mcp-list'; +import { BrowseDialog } from './browse-dialog'; +import { ConfigDialog } from './config-dialog'; + +export const MCPConfigurationNew: React.FC = ({ + configuredMCPs, + onConfigurationChange, +}) => { + const [showBrowseDialog, setShowBrowseDialog] = useState(false); + const [configuringServer, setConfiguringServer] = useState(null); + const [editingIndex, setEditingIndex] = useState(null); + + const handleAddMCP = (server: any) => { + setConfiguringServer(server); + setEditingIndex(null); + setShowBrowseDialog(false); + }; + + const handleEditMCP = (index: number) => { + const mcp = configuredMCPs[index]; + setConfiguringServer({ + qualifiedName: mcp.qualifiedName, + displayName: mcp.name, + name: mcp.name, + }); + setEditingIndex(index); + }; + + const handleRemoveMCP = (index: number) => { + const newMCPs = [...configuredMCPs]; + newMCPs.splice(index, 1); + onConfigurationChange(newMCPs); + }; + + const handleSaveConfiguration = (config: MCPConfigurationType) => { + if (editingIndex !== null) { + const newMCPs = [...configuredMCPs]; + newMCPs[editingIndex] = config; + onConfigurationChange(newMCPs); + } else { + onConfigurationChange([...configuredMCPs, config]); + } + setConfiguringServer(null); + setEditingIndex(null); + }; + + return ( +
+
+
+

MCP Servers

+

+ Connect Model Context Protocol servers to extend agent capabilities +

+
+ +
+ + + {configuringServer && ( + setConfiguringServer(null)}> + setConfiguringServer(null)} + /> + + )} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-server-card.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-server-card.tsx new file mode 100644 index 00000000..9939e2ef --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/mcp-server-card.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Shield, ExternalLink, ChevronRight, Sparkles } from 'lucide-react'; + +interface McpServerCardProps { + server: any; + onClick: (server: any) => void; +} + +export const McpServerCard: React.FC = ({ server, onClick }) => { + return ( + onClick(server)} + > +
+ {server.iconUrl ? ( + {server.displayName + ) : ( +
+ +
+ )} +
+
+

{server.displayName || server.name}

+ {server.security?.scanPassed && ( + + )} + {server.isDeployed && ( + + Deployed + + )} +
+

+ {server.description} +

+
+ Used {server.useCount} times + {server.homepage && ( + + )} +
+
+ +
+
+ ); +}; diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/search-results.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/search-results.tsx new file mode 100644 index 00000000..ff1e278d --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/search-results.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { McpSearchLoader } from './_loaders/mcp-search-loader'; +import { McpServerCard } from './mcp-server-card'; + +interface SearchResultsProps { + searchResults: any; + isSearching: boolean; + onServerSelect: (server: any) => void; +} + +export const SearchResults: React.FC = ({ + searchResults, + isSearching, + onServerSelect, +}) => { + if (isSearching) { + return ; + } + + if (!searchResults?.servers || searchResults.servers.length === 0) { + return ( +
+

No servers found

+
+ ); + } + + return ( +
+

+ Search Results ({searchResults.pagination.totalCount}) +

+
+ {searchResults.servers.map((server) => ( + + ))} +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/types.ts b/frontend/src/app/(dashboard)/agents/_components/mcp/types.ts new file mode 100644 index 00000000..848e2bbb --- /dev/null +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/types.ts @@ -0,0 +1,11 @@ +export interface MCPConfiguration { + name: string; + qualifiedName: string; + config: Record; + enabledTools?: string[]; +} + +export interface MCPConfigurationProps { + configuredMCPs: MCPConfiguration[]; + onConfigurationChange: (mcps: MCPConfiguration[]) => void; +} diff --git a/frontend/src/app/(dashboard)/agents/_components/update-agent-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/update-agent-dialog.tsx index d3f14e40..5039884a 100644 --- a/frontend/src/app/(dashboard)/agents/_components/update-agent-dialog.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/update-agent-dialog.tsx @@ -12,6 +12,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { DEFAULT_AGENTPRESS_TOOLS, getToolDisplayName } from '../_data/tools'; import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents'; import { MCPConfiguration } from './mcp-configuration'; +import { MCPConfigurationNew } from './mcp/mcp-configuration-new'; interface AgentUpdateRequest { name?: string; @@ -347,7 +348,7 @@ export const UpdateAgentDialog = ({ agentId, isOpen, onOpenChange, onAgentUpdate
-