polish(ui): cyhange mcp copywrite + get all mcp servers from smithery with pagination

This commit is contained in:
Soumyadas15 2025-06-06 01:46:37 +05:30
parent 51d9a704b4
commit 48dab27e24
7 changed files with 141 additions and 29 deletions

View File

@ -69,6 +69,7 @@ class PopularServersV2Response(BaseModel):
categorized: Dict[str, List[Dict[str, Any]]]
total: int
categoryCount: int
pagination: Dict[str, int]
class CustomMCPConnectionRequest(BaseModel):
"""Request model for connecting to a custom MCP server"""
@ -293,6 +294,8 @@ async def get_popular_mcp_servers(
@router.get("/mcp/popular-servers/v2", response_model=PopularServersV2Response)
async def get_popular_mcp_servers_v2(
page: int = Query(1, ge=1, description="Page number"),
pageSize: int = Query(200, ge=1, le=500, description="Items per page (max 500 for comprehensive categorization)"),
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""
@ -300,6 +303,10 @@ async def get_popular_mcp_servers_v2(
Returns servers grouped by category with proper metadata and usage statistics.
This endpoint fetches real data from the Smithery registry API and categorizes it.
Query parameters:
- page: Page number (default: 1)
- pageSize: Number of items per page (default: 200, max: 500)
"""
logger.info(f"Fetching v2 popular MCP servers for user {user_id}")
@ -317,10 +324,10 @@ async def get_popular_mcp_servers_v2(
else:
logger.warning("No Smithery API key found in environment variables")
# Fetch more servers for better coverage
# Use provided pagination parameters
params = {
"page": 1,
"pageSize": 100 # Get more servers
"page": page,
"pageSize": pageSize
}
response = await client.get(
@ -337,11 +344,13 @@ async def get_popular_mcp_servers_v2(
servers=[],
categorized={},
total=0,
categoryCount=0
categoryCount=0,
pagination={"currentPage": page, "pageSize": pageSize, "totalPages": 0, "totalCount": 0}
)
data = response.json()
servers = data.get("servers", [])
pagination_data = data.get("pagination", {})
# Category mappings based on server types and names
category_mappings = {
@ -523,8 +532,14 @@ async def get_popular_mcp_servers_v2(
success=True,
servers=servers,
categorized=sorted_categories,
total=len(servers),
categoryCount=len(sorted_categories)
total=pagination_data.get("totalCount", len(servers)),
categoryCount=len(sorted_categories),
pagination={
"currentPage": pagination_data.get("currentPage", page),
"pageSize": pagination_data.get("pageSize", pageSize),
"totalPages": pagination_data.get("totalPages", 1),
"totalCount": pagination_data.get("totalCount", len(servers))
}
)
except Exception as e:
@ -534,6 +549,7 @@ async def get_popular_mcp_servers_v2(
servers=[],
categorized={},
total=0,
categoryCount=0
categoryCount=0,
pagination={"currentPage": page, "pageSize": pageSize, "totalPages": 0, "totalCount": 0}
)

View File

@ -6,7 +6,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Loader2, Search, Plus, Settings, ExternalLink, Shield, X, Sparkles, ChevronRight } from 'lucide-react';
import { Loader2, Search, Plus, Settings, ExternalLink, Shield, X, Sparkles, ChevronRight, ChevronLeft } from 'lucide-react';
import { usePopularMCPServers, usePopularMCPServersV2, useMCPServers, useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers';
import { cn } from '@/lib/utils';
@ -194,9 +194,11 @@ export const MCPConfiguration: React.FC<MCPConfigurationProps> = ({
const [configuringServer, setConfiguringServer] = useState<any>(null);
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(200);
const { data: popularServers } = usePopularMCPServers();
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2();
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2(currentPage, pageSize);
const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined
);
@ -206,6 +208,17 @@ export const MCPConfiguration: React.FC<MCPConfigurationProps> = ({
setEditingIndex(null);
};
React.useEffect(() => {
setCurrentPage(1);
}, [searchQuery]);
React.useEffect(() => {
if (showBrowseDialog) {
setCurrentPage(1);
setSelectedCategory(null);
}
}, [showBrowseDialog]);
const handleEditMCP = (index: number) => {
const mcp = configuredMCPs[index];
setConfiguringServer({
@ -584,6 +597,37 @@ export const MCPConfiguration: React.FC<MCPConfigurationProps> = ({
</ScrollArea>
</div>
</div>
{!searchQuery && popularServersV2?.success && popularServersV2.pagination && (
<div className="flex items-center justify-between px-4 py-3 border-t">
<div className="text-sm text-muted-foreground">
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, popularServersV2.pagination.totalCount)} of {popularServersV2.pagination.totalCount} servers
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage <= 1}
>
<ChevronLeft className="h-4 w-4" />
Previous
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages}
>
Next
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import React, { useState, useEffect } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogFooter, 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 { Button } from '@/components/ui/button';
import { Search, ChevronLeft, ChevronRight } 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';
@ -23,15 +24,28 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
}) => {
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(200);
const { data: popularServers } = usePopularMCPServers();
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2();
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2(currentPage, pageSize);
const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined
);
const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : [];
useEffect(() => {
setCurrentPage(1);
}, [searchQuery]);
useEffect(() => {
if (open) {
setCurrentPage(1);
setSelectedCategory(null);
}
}, [open]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-6xl max-h-[85vh] overflow-hidden flex flex-col">
@ -41,8 +55,6 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
Discover and add Model Context Protocol servers from Smithery
</DialogDescription>
</DialogHeader>
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
@ -52,7 +64,6 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
className="pl-10"
/>
</div>
<div className="flex flex-1 gap-4 overflow-hidden">
{!searchQuery && categories.length > 0 && (
<CategorySidebar
@ -103,7 +114,39 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
</ScrollArea>
</div>
</div>
<DialogFooter className='w-full'>
{!searchQuery && popularServersV2?.success && popularServersV2.pagination && (
<div className="flex items-center justify-between px-4 border-t w-full">
<div className="text-sm text-muted-foreground">
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, popularServersV2.pagination.totalCount)} of {popularServersV2.pagination.totalCount} servers
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage <= 1}
>
<ChevronLeft className="h-4 w-4" />
Previous
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages}
>
Next
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
};
};

View File

@ -19,7 +19,7 @@ export const CategorySidebar: React.FC<CategorySidebarProps> = ({
categorizedServers,
}) => {
return (
<div className="w-76 flex-shrink-0">
<div className="w-68 flex-shrink-0">
<h3 className="text-sm font-semibold mb-3">Categories</h3>
<ScrollArea className="h-full">
<div className="space-y-1">
@ -48,9 +48,6 @@ export const CategorySidebar: React.FC<CategorySidebarProps> = ({
>
<span>{categoryIcons[category] || "🧩"}</span>
<span className="flex-1 text-left">{category}</span>
<Badge variant="outline" className="ml-auto text-xs">
{count}
</Badge>
</Button>
);
})}

View File

@ -2,6 +2,7 @@ import React from 'react';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Shield, ExternalLink, ChevronRight, Sparkles } from 'lucide-react';
import { truncateString } from '@/lib/utils';
interface McpServerCardProps {
server: any;
@ -14,7 +15,7 @@ export const McpServerCard: React.FC<McpServerCardProps> = ({ server, onClick })
className="p-4 cursor-pointer hover:bg-muted/50 transition-colors"
onClick={() => onClick(server)}
>
<div className="flex items-start gap-3">
<div className="flex items-start gap-3 flex-shrink-0">
{server.iconUrl ? (
<img src={server.iconUrl} alt={server.displayName || server.name} className="w-6 h-6 rounded" />
) : (
@ -22,7 +23,7 @@ export const McpServerCard: React.FC<McpServerCardProps> = ({ server, onClick })
<Sparkles className="h-3 w-3 text-primary" />
</div>
)}
<div className="flex-1">
<div className="flex-1 w-auto">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{server.displayName || server.name}</h4>
{server.security?.scanPassed && (
@ -34,8 +35,8 @@ export const McpServerCard: React.FC<McpServerCardProps> = ({ server, onClick })
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
{server.description}
<p className="text-xs text-muted-foreground mt-1 line-clamp-2 whitespace-normal">
{truncateString(server.description, 100)}
</p>
<div className="flex items-center gap-4 mt-2 text-xs text-muted-foreground">
<span>Used {server.useCount} times</span>

View File

@ -374,7 +374,7 @@ export default function AgentConfigurationPage() {
<AccordionTrigger className="hover:no-underline text-sm md:text-base">
<div className="flex items-center gap-2">
<Sparkles className="h-4 w-4" />
MCP Servers
Integrations (via MCP)
<Badge variant='new'>New</Badge>
</div>
</AccordionTrigger>

View File

@ -71,6 +71,12 @@ interface PopularServersV2Response {
}>>;
total: number;
categoryCount: number;
pagination: {
currentPage: number;
pageSize: number;
totalPages: number;
totalCount: number;
};
}
export const useMCPServers = (query?: string, page: number = 1, pageSize: number = 20) => {
@ -164,17 +170,22 @@ export const usePopularMCPServers = () => {
});
};
export const usePopularMCPServersV2 = () => {
export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 200) => {
const supabase = createClient();
return useQuery({
queryKey: ['mcp-servers-popular-v2'],
queryKey: ['mcp-servers-popular-v2', page, pageSize],
queryFn: async (): Promise<PopularServersV2Response> => {
const { data: { session } } = await supabase.auth.getSession();
if (!session) throw new Error('No session');
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString(),
});
const response = await fetch(
`${API_URL}/mcp/popular-servers/v2`,
`${API_URL}/mcp/popular-servers/v2?${params}`,
{
headers: {
'Authorization': `Bearer ${session.access_token}`,