This commit is contained in:
marko-kraemer 2025-07-06 16:40:27 +02:00
parent 4bbc03f674
commit dc5496ae94
8 changed files with 204 additions and 428 deletions

View File

@ -1822,99 +1822,99 @@ class MarketplaceAgentsResponse(BaseModel):
class PublishAgentRequest(BaseModel):
tags: Optional[List[str]] = []
@router.get("/marketplace/agents", response_model=MarketplaceAgentsResponse)
async def get_marketplace_agents(
page: Optional[int] = Query(1, ge=1, description="Page number (1-based)"),
limit: Optional[int] = Query(20, ge=1, le=100, description="Number of items per page"),
search: Optional[str] = Query(None, description="Search in name and description"),
tags: Optional[str] = Query(None, description="Comma-separated string of tags"),
sort_by: Optional[str] = Query("newest", description="Sort by: newest, popular, most_downloaded, name"),
creator: Optional[str] = Query(None, description="Filter by creator name")
):
"""Get public agents from the marketplace with pagination, search, sort, and filter support."""
if not await is_enabled("agent_marketplace"):
raise HTTPException(
status_code=403,
detail="Custom agent currently disabled. This feature is not available at the moment."
)
# @router.get("/marketplace/agents", response_model=MarketplaceAgentsResponse)
# async def get_marketplace_agents(
# page: Optional[int] = Query(1, ge=1, description="Page number (1-based)"),
# limit: Optional[int] = Query(20, ge=1, le=100, description="Number of items per page"),
# search: Optional[str] = Query(None, description="Search in name and description"),
# tags: Optional[str] = Query(None, description="Comma-separated string of tags"),
# sort_by: Optional[str] = Query("newest", description="Sort by: newest, popular, most_downloaded, name"),
# creator: Optional[str] = Query(None, description="Filter by creator name")
# ):
# """Get public agents from the marketplace with pagination, search, sort, and filter support."""
# if not await is_enabled("agent_marketplace"):
# raise HTTPException(
# status_code=403,
# detail="Custom agent currently disabled. This feature is not available at the moment."
# )
logger.info(f"Fetching marketplace agents with page={page}, limit={limit}, search='{search}', tags='{tags}', sort_by={sort_by}")
client = await db.client
# logger.info(f"Fetching marketplace agents with page={page}, limit={limit}, search='{search}', tags='{tags}', sort_by={sort_by}")
# client = await db.client
try:
offset = (page - 1) * limit
tags_array = None
if tags:
tags_array = [tag.strip() for tag in tags.split(',') if tag.strip()]
# try:
# offset = (page - 1) * limit
# tags_array = None
# if tags:
# tags_array = [tag.strip() for tag in tags.split(',') if tag.strip()]
result = await client.rpc('get_marketplace_agents', {
'p_search': search,
'p_tags': tags_array,
'p_limit': limit + 1,
'p_offset': offset
}).execute()
# result = await client.rpc('get_marketplace_agents', {
# 'p_search': search,
# 'p_tags': tags_array,
# 'p_limit': limit + 1,
# 'p_offset': offset
# }).execute()
if result.data is None:
result.data = []
# if result.data is None:
# result.data = []
has_more = len(result.data) > limit
agents_data = result.data[:limit]
if creator:
agents_data = [
agent for agent in agents_data
if creator.lower() in agent.get('creator_name', '').lower()
]
# has_more = len(result.data) > limit
# agents_data = result.data[:limit]
# if creator:
# agents_data = [
# agent for agent in agents_data
# if creator.lower() in agent.get('creator_name', '').lower()
# ]
if sort_by == "most_downloaded":
agents_data = sorted(agents_data, key=lambda x: x.get('download_count', 0), reverse=True)
elif sort_by == "popular":
agents_data = sorted(agents_data, key=lambda x: x.get('download_count', 0), reverse=True)
elif sort_by == "name":
agents_data = sorted(agents_data, key=lambda x: x.get('name', '').lower())
else:
agents_data = sorted(agents_data, key=lambda x: x.get('marketplace_published_at', ''), reverse=True)
# if sort_by == "most_downloaded":
# agents_data = sorted(agents_data, key=lambda x: x.get('download_count', 0), reverse=True)
# elif sort_by == "popular":
# agents_data = sorted(agents_data, key=lambda x: x.get('download_count', 0), reverse=True)
# elif sort_by == "name":
# agents_data = sorted(agents_data, key=lambda x: x.get('name', '').lower())
# else:
# agents_data = sorted(agents_data, key=lambda x: x.get('marketplace_published_at', ''), reverse=True)
estimated_total = (page - 1) * limit + len(agents_data)
if has_more:
estimated_total += 1
# estimated_total = (page - 1) * limit + len(agents_data)
# if has_more:
# estimated_total += 1
total_pages = max(page, (estimated_total + limit - 1) // limit)
if has_more:
total_pages = page + 1
# total_pages = max(page, (estimated_total + limit - 1) // limit)
# if has_more:
# total_pages = page + 1
# Add Kortix team identification
kortix_team_creators = [
'kortix', 'kortix team', 'suna team', 'official', 'kortix official'
]
# # Add Kortix team identification
# kortix_team_creators = [
# 'kortix', 'kortix team', 'suna team', 'official', 'kortix official'
# ]
for agent in agents_data:
creator_name = agent.get('creator_name', '').lower()
agent['is_kortix_team'] = any(
kortix_creator in creator_name
for kortix_creator in kortix_team_creators
)
# for agent in agents_data:
# creator_name = agent.get('creator_name', '').lower()
# agent['is_kortix_team'] = any(
# kortix_creator in creator_name
# for kortix_creator in kortix_team_creators
# )
agents_data = sorted(agents_data, key=lambda x: (
not x.get('is_kortix_team', False),
-x.get('download_count', 0) if sort_by == "most_downloaded" else 0,
x.get('name', '').lower() if sort_by == "name" else '',
-(datetime.fromisoformat(x.get('marketplace_published_at', x.get('created_at', ''))).timestamp()) if sort_by == "newest" else 0
))
# agents_data = sorted(agents_data, key=lambda x: (
# not x.get('is_kortix_team', False),
# -x.get('download_count', 0) if sort_by == "most_downloaded" else 0,
# x.get('name', '').lower() if sort_by == "name" else '',
# -(datetime.fromisoformat(x.get('marketplace_published_at', x.get('created_at', ''))).timestamp()) if sort_by == "newest" else 0
# ))
logger.info(f"Found {len(agents_data)} marketplace agents (page {page}, estimated {total_pages} pages)")
return {
"agents": agents_data,
"pagination": {
"page": page,
"limit": limit,
"total": estimated_total,
"pages": total_pages
}
}
# logger.info(f"Found {len(agents_data)} marketplace agents (page {page}, estimated {total_pages} pages)")
# return {
# "agents": agents_data,
# "pagination": {
# "page": page,
# "limit": limit,
# "total": estimated_total,
# "pages": total_pages
# }
# }
except Exception as e:
logger.error(f"Error fetching marketplace agents: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
# except Exception as e:
# logger.error(f"Error fetching marketplace agents: {str(e)}")
# raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/agents/{agent_id}/publish")
async def publish_agent_to_marketplace(
@ -2002,81 +2002,82 @@ async def unpublish_agent_from_marketplace(
logger.error(f"Error unpublishing agent {agent_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/marketplace/agents/{agent_id}/add-to-library")
async def add_agent_to_library(
agent_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Add an agent from the marketplace to user's library."""
if not await is_enabled("agent_marketplace"):
raise HTTPException(
status_code=403,
detail="Custom agent currently disabled. This feature is not available at the moment."
)
logger.info(f"Adding marketplace agent {agent_id} to user {user_id} library")
client = await db.client
# @router.post("/marketplace/agents/{agent_id}/add-to-library")
# async def add_agent_to_library(
# agent_id: str,
# user_id: str = Depends(get_current_user_id_from_jwt)
# ):
# """Add an agent from the marketplace to user's library."""
# if not await is_enabled("agent_marketplace"):
# raise HTTPException(
# status_code=403,
# detail="Custom agent currently disabled. This feature is not available at the moment."
# )
try:
# Call the database function with user_id
result = await client.rpc('add_agent_to_library', {
'p_original_agent_id': agent_id,
'p_user_account_id': user_id
}).execute()
# logger.info(f"Adding marketplace agent {agent_id} to user {user_id} library")
# client = await db.client
if result.data:
new_agent_id = result.data
logger.info(f"Successfully added agent {agent_id} to library as {new_agent_id}")
return {"message": "Agent added to library successfully", "new_agent_id": new_agent_id}
else:
raise HTTPException(status_code=400, detail="Failed to add agent to library")
# try:
# # Call the database function with user_id
# result = await client.rpc('add_agent_to_library', {
# 'p_original_agent_id': agent_id,
# 'p_user_account_id': user_id
# }).execute()
except Exception as e:
error_msg = str(e)
logger.error(f"Error adding agent {agent_id} to library: {error_msg}")
# if result.data:
# new_agent_id = result.data
# logger.info(f"Successfully added agent {agent_id} to library as {new_agent_id}")
# return {"message": "Agent added to library successfully", "new_agent_id": new_agent_id}
# else:
# raise HTTPException(status_code=400, detail="Failed to add agent to library")
if "Agent not found or not public" in error_msg:
raise HTTPException(status_code=404, detail="Agent not found or not public")
elif "Agent already in your library" in error_msg:
raise HTTPException(status_code=409, detail="Agent already in your library")
else:
raise HTTPException(status_code=500, detail="Internal server error")
# except Exception as e:
# error_msg = str(e)
# logger.error(f"Error adding agent {agent_id} to library: {error_msg}")
@router.get("/user/agent-library")
async def get_user_agent_library(user_id: str = Depends(get_current_user_id_from_jwt)):
"""Get user's agent library (agents added from marketplace)."""
if not await is_enabled("agent_marketplace"):
raise HTTPException(
status_code=403,
detail="Custom agent currently disabled. This feature is not available at the moment."
)
# if "Agent not found or not public" in error_msg:
# raise HTTPException(status_code=404, detail="Agent not found or not public")
# elif "Agent already in your library" in error_msg:
# raise HTTPException(status_code=409, detail="Agent already in your library")
# else:
# raise HTTPException(status_code=500, detail="Internal server error")
logger.info(f"Fetching agent library for user {user_id}")
client = await db.client
# @router.get("/user/agent-library")
# async def get_user_agent_library(user_id: str = Depends(get_current_user_id_from_jwt)):
# """Get user's agent library (agents added from marketplace)."""
# if not await is_enabled("agent_marketplace"):
# raise HTTPException(
# status_code=403,
# detail="Custom agent currently disabled. This feature is not available at the moment."
# )
try:
result = await client.table('user_agent_library').select("""
*,
original_agent:agents!user_agent_library_original_agent_id_fkey(
agent_id,
name,
description,
download_count
),
agent:agents!user_agent_library_agent_id_fkey(
agent_id,
name,
description,
system_prompt
)
""").eq('user_account_id', user_id).order('added_at', desc=True).execute()
# logger.info(f"Fetching agent library for user {user_id}")
# client = await db.client
logger.info(f"Found {len(result.data or [])} agents in user library")
return {"library": result.data or []}
# try:
# result = await client.table('user_agent_library').select("""
# *,
# original_agent:agents!user_agent_library_original_agent_id_fkey(
# agent_id,
# name,
# description,
# download_count
# ),
# agent:agents!user_agent_library_agent_id_fkey(
# agent_id,
# name,
# description,
# system_prompt
# )
# """).eq('user_account_id', user_id).order('added_at', desc=True).execute()
except Exception as e:
logger.error(f"Error fetching user agent library: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
# logger.info(f"Found {len(result.data or [])} agents in user library")
# return {"library": result.data or []}
# except Exception as e:
# logger.error(f"Error fetching user agent library: {str(e)}")
# raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/agents/{agent_id}/builder-chat-history")
async def get_agent_builder_chat_history(
@ -2136,6 +2137,9 @@ async def get_agent_builder_chat_history(
logger.error(f"Error fetching agent builder chat history for agent {agent_id}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to fetch chat history: {str(e)}")
# agent versioning
@router.get("/agents/{agent_id}/versions", response_model=List[AgentVersionResponse])
async def get_agent_versions(
agent_id: str,

View File

@ -313,25 +313,6 @@ Here are the XML tools available with examples:
token_threshold = self.context_manager.token_threshold
logger.info(f"Thread {thread_id} token count: {token_count}/{token_threshold} ({(token_count/token_threshold)*100:.1f}%)")
# if token_count >= token_threshold and enable_context_manager:
# logger.info(f"Thread token count ({token_count}) exceeds threshold ({token_threshold}), summarizing...")
# summarized = await self.context_manager.check_and_summarize_if_needed(
# thread_id=thread_id,
# add_message_callback=self.add_message,
# model=llm_model,
# force=True
# )
# if summarized:
# logger.info("Summarization complete, fetching updated messages with summary")
# messages = await self.get_llm_messages(thread_id)
# # Recount tokens after summarization, using the modified prompt
# new_token_count = token_counter(model=llm_model, messages=[working_system_prompt] + messages)
# logger.info(f"After summarization: token count reduced from {token_count} to {new_token_count}")
# else:
# logger.warning("Summarization failed or wasn't needed - proceeding with original messages")
# elif not enable_context_manager:
# logger.info("Automatic summarization disabled. Skipping token count check and summarization.")
except Exception as e:
logger.error(f"Error counting tokens or summarizing: {str(e)}")
@ -389,7 +370,7 @@ Here are the XML tools available with examples:
temperature=llm_temperature,
max_tokens=llm_max_tokens,
tools=openapi_tool_schemas,
tool_choice=tool_choice if processor_config.native_tool_calling else None,
tool_choice=tool_choice if processor_config and processor_config.native_tool_calling else "none",
stream=stream,
enable_thinking=enable_thinking,
reasoning_effort=reasoning_effort

View File

@ -22,7 +22,6 @@ from sandbox import api as sandbox_api
from services import billing as billing_api
from flags import api as feature_flags_api
from services import transcription as transcription_api
from mcp_service.mcp_custom import discover_custom_tools
import sys
from services import email_api
from triggers import api as triggers_api
@ -183,20 +182,6 @@ async def health_check():
"instance_id": instance_id
}
class CustomMCPDiscoverRequest(BaseModel):
type: str
config: Dict[str, Any]
@app.post("/api/mcp/discover-custom-tools")
async def discover_custom_mcp_tools(request: CustomMCPDiscoverRequest):
try:
return await discover_custom_tools(request.type, request.config)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error discovering custom MCP tools: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn

View File

@ -22,6 +22,7 @@ import os
from urllib.parse import quote
from utils.logger import logger
from utils.auth_utils import get_current_user_id_from_jwt
from mcp_service.mcp_custom import discover_custom_tools
from collections import OrderedDict
router = APIRouter()
@ -62,8 +63,8 @@ class MCPServerDetailResponse(BaseModel):
security: Optional[Dict[str, Any]] = None
tools: Optional[List[Dict[str, Any]]] = None
class PopularServersV2Response(BaseModel):
"""Response model for v2 popular servers with categorization"""
class PopularServersResponse(BaseModel):
"""Response model for popular servers with categorization"""
success: bool
servers: List[Dict[str, Any]]
categorized: Dict[str, List[Dict[str, Any]]]
@ -236,64 +237,8 @@ async def get_mcp_server_details(
detail=f"Failed to fetch MCP server details: {str(e)}"
)
@router.get("/mcp/popular-servers")
@router.get("/mcp/popular-servers", response_model=PopularServersResponse)
async def get_popular_mcp_servers(
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""
Get a curated list of popular/recommended MCP servers.
This is a convenience endpoint that returns commonly used servers.
"""
# Define some popular servers based on actual Smithery data
popular_servers = [
{
"qualifiedName": "@wonderwhy-er/desktop-commander",
"displayName": "Desktop Commander",
"description": "Execute terminal commands and manage files with diff editing capabilities. Coding, shell and terminal, task automation",
"icon": "💻",
"category": "development"
},
{
"qualifiedName": "@smithery-ai/server-sequential-thinking",
"displayName": "Sequential Thinking",
"description": "Dynamic and reflective problem-solving through a structured thinking process",
"icon": "🧠",
"category": "ai"
},
{
"qualifiedName": "@microsoft/playwright-mcp",
"displayName": "Playwright Automation",
"description": "Automate web interactions, navigate, extract data, and perform actions on web pages",
"icon": "🎭",
"category": "automation"
},
{
"qualifiedName": "exa",
"displayName": "Exa Search",
"description": "Fast, intelligent web search and crawling. Combines embeddings and traditional search",
"icon": "🔍",
"category": "search"
},
{
"qualifiedName": "@smithery-ai/github",
"displayName": "GitHub",
"description": "Access the GitHub API, enabling file operations, repository management, and search",
"icon": "🐙",
"category": "development"
},
{
"qualifiedName": "@nickclyde/duckduckgo-mcp-server",
"displayName": "DuckDuckGo Search",
"description": "Enable web search capabilities through DuckDuckGo. Fetch and parse webpage content",
"icon": "🦆",
"category": "search"
}
]
return {"servers": popular_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(100, ge=1, le=200, description="Items per page (max 500 for comprehensive categorization)"),
user_id: str = Depends(get_current_user_id_from_jwt)
@ -308,7 +253,7 @@ async def get_popular_mcp_servers_v2(
- 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}")
logger.info(f"Fetching popular MCP servers for user {user_id}")
try:
async with httpx.AsyncClient() as client:
@ -339,7 +284,7 @@ async def get_popular_mcp_servers_v2(
if response.status_code != 200:
logger.error(f"Failed to fetch MCP servers: {response.status_code} - {response.text}")
return PopularServersV2Response(
return PopularServersResponse(
success=False,
servers=[],
categorized={},
@ -528,7 +473,7 @@ async def get_popular_mcp_servers_v2(
logger.info(f"Successfully categorized {len(servers)} servers into {len(sorted_categories)} categories")
return PopularServersV2Response(
return PopularServersResponse(
success=True,
servers=servers,
categorized=sorted_categories,
@ -543,8 +488,8 @@ async def get_popular_mcp_servers_v2(
)
except Exception as e:
logger.error(f"Error fetching v2 popular MCP servers: {str(e)}")
return PopularServersV2Response(
logger.error(f"Error fetching popular MCP servers: {str(e)}")
return PopularServersResponse(
success=False,
servers=[],
categorized={},
@ -553,3 +498,17 @@ async def get_popular_mcp_servers_v2(
pagination={"currentPage": page, "pageSize": pageSize, "totalPages": 0, "totalCount": 0}
)
class CustomMCPDiscoverRequest(BaseModel):
type: str
config: Dict[str, Any]
@router.post("/mcp/discover-custom-tools")
async def discover_custom_mcp_tools(request: CustomMCPDiscoverRequest):
try:
return await discover_custom_tools(request.type, request.config)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error discovering custom MCP tools: {e}")
raise HTTPException(status_code=500, detail=str(e))

View File

@ -4,7 +4,7 @@ import { Input } from '@/components/ui/input';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Search, ChevronLeft, ChevronRight } from 'lucide-react';
import { usePopularMCPServersV2, useMCPServers } from '@/hooks/react-query/mcp/use-mcp-servers';
import { usePopularMCPServers, 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';
@ -27,12 +27,12 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(100);
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2(currentPage, pageSize);
const { data: popularServers, isLoading: isLoading } = usePopularMCPServers(currentPage, pageSize);
const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined
);
const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : [];
const categories = popularServers?.success ? Object.keys(popularServers.categorized) : [];
useEffect(() => {
@ -70,7 +70,7 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
categories={categories}
selectedCategory={selectedCategory}
onCategorySelect={setSelectedCategory}
categorizedServers={popularServersV2?.categorized || {}}
categorizedServers={popularServers?.categorized || {}}
/>
)}
<div className="flex-1 overflow-hidden">
@ -85,11 +85,11 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
)}
{!searchQuery && (
<>
{isLoadingV2 ? (
{isLoading ? (
<McpListLoader />
) : popularServersV2?.success ? (
) : popularServers?.success ? (
<CategorizedServersList
categorizedServers={popularServersV2.categorized}
categorizedServers={popularServers.categorized}
selectedCategory={selectedCategory}
onServerSelect={onServerSelect}
onCategorySelect={setSelectedCategory}
@ -102,10 +102,10 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
</div>
</div>
<DialogFooter className='w-full'>
{!searchQuery && popularServersV2?.success && popularServersV2.pagination && (
{!searchQuery && popularServers?.success && popularServers.pagination && (
<div className="flex items-center justify-between 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
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, popularServers.pagination.totalCount)} of {popularServers.pagination.totalCount} servers
</div>
<div className="flex items-center gap-2">
<Button
@ -118,13 +118,13 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
Previous
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages}
Page {currentPage} of {popularServers.pagination.totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages}
onClick={() => setCurrentPage(prev => Math.min(popularServers.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServers.pagination.totalPages}
>
Next
<ChevronRight className="h-4 w-4" />

View File

@ -8,7 +8,7 @@ import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Search, ChevronLeft, ChevronRight, Shield, Loader2, ArrowLeft, Key, ExternalLink, Plus, Globe } from 'lucide-react';
import { usePopularMCPServersV2, useMCPServers, useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers';
import { usePopularMCPServers, useMCPServers, useMCPServerDetails } from '@/hooks/react-query/mcp/use-mcp-servers';
import { useCreateCredentialProfile, type CreateCredentialProfileRequest } from '@/hooks/react-query/mcp/use-credential-profiles';
import { toast } from 'sonner';
@ -140,7 +140,7 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
is_default: false
});
const { data: popularServersV2, isLoading: isLoadingV2 } = usePopularMCPServersV2(currentPage, pageSize);
const { data: popularServers, isLoading: isLoading } = usePopularMCPServers(currentPage, pageSize);
const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined
);
@ -150,7 +150,7 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
);
const createProfileMutation = useCreateCredentialProfile();
const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : [];
const categories = popularServers?.success ? Object.keys(popularServers.categorized) : [];
useEffect(() => {
setCurrentPage(1);
@ -272,14 +272,14 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
return searchResults.servers || [];
}
if (!popularServersV2?.success) return [];
if (!popularServers?.success) return [];
if (selectedCategory) {
return popularServersV2.categorized[selectedCategory] || [];
return popularServers.categorized[selectedCategory] || [];
}
// Flatten all servers from all categories
return Object.values(popularServersV2.categorized).flat();
return Object.values(popularServers.categorized).flat();
};
const serversToDisplay = getServersToDisplay();
@ -322,14 +322,14 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
categories={categories}
selectedCategory={selectedCategory}
onCategorySelect={setSelectedCategory}
categorizedServers={popularServersV2?.categorized || {}}
categorizedServers={popularServers?.categorized || {}}
/>
)}
<div className="flex-1 overflow-hidden">
<ScrollArea className="h-full">
<div className="space-y-3 p-1">
{(isLoadingV2 || isSearching) ? (
{(isLoading || isSearching) ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>Loading MCP servers...</span>
@ -354,10 +354,10 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
</div>
</div>
{!searchQuery && popularServersV2?.success && popularServersV2.pagination && (
{!searchQuery && popularServers?.success && popularServers.pagination && (
<div className="flex items-center justify-between border-t pt-4">
<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
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, popularServers.pagination.totalCount)} of {popularServers.pagination.totalCount} servers
</div>
<div className="flex items-center gap-2">
<Button
@ -370,13 +370,13 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
Previous
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages}
Page {currentPage} of {popularServers.pagination.totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages}
onClick={() => setCurrentPage(prev => Math.min(popularServers.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServers.pagination.totalPages}
>
Next
<ChevronRight className="h-4 w-4" />

View File

@ -32,117 +32,6 @@ export interface MarketplaceAgentsResponse {
pagination: PaginationInfo;
}
interface MarketplaceAgentsParams {
page?: number;
limit?: number;
search?: string;
tags?: string[];
sort_by?: 'newest' | 'popular' | 'most_downloaded' | 'name';
creator?: string;
}
export function useMarketplaceAgents(params: MarketplaceAgentsParams = {}) {
return useQuery({
queryKey: ['marketplace-agents', params],
queryFn: async (): Promise<MarketplaceAgentsResponse> => {
try {
const marketplaceEnabled = await isFlagEnabled('agent_marketplace');
if (!marketplaceEnabled) {
throw new Error('Marketplace is not enabled');
}
const supabase = createClient();
const { data: { session } } = await supabase.auth.getSession();
const queryParams = new URLSearchParams();
if (params.page) queryParams.append('page', params.page.toString());
if (params.limit) queryParams.append('limit', params.limit.toString());
if (params.search) queryParams.append('search', params.search);
if (params.tags && params.tags.length > 0) {
queryParams.append('tags', params.tags.join(','));
}
if (params.sort_by) queryParams.append('sort_by', params.sort_by);
if (params.creator) queryParams.append('creator', params.creator);
const url = `${API_URL}/marketplace/agents${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (session) {
headers['Authorization'] = `Bearer ${session.access_token}`;
}
const response = await fetch(url, {
method: 'GET',
headers,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('[API] Fetched marketplace agents:', data.agents?.length || 0, 'total:', data.pagination?.total || 0);
return data;
} catch (err) {
console.error('Error fetching marketplace agents:', err);
throw err;
}
},
refetchOnWindowFocus: false,
staleTime: 5 * 60 * 1000,
});
}
export function useAddAgentToLibrary() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (originalAgentId: string): Promise<string> => {
try {
const marketplaceEnabled = await isFlagEnabled('agent_marketplace');
if (!marketplaceEnabled) {
throw new Error('Marketplace is not enabled');
}
const supabase = createClient();
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
throw new Error('You must be logged in to add agents to your library');
}
const response = await fetch(`${API_URL}/marketplace/agents/${originalAgentId}/add-to-library`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data.new_agent_id;
} catch (err) {
console.error('Error adding agent to library:', err);
throw err;
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['agents'] });
queryClient.invalidateQueries({ queryKey: ['marketplace-agents'] });
queryClient.invalidateQueries({ queryKey: ['user-agent-library'] });
},
});
}
export function usePublishAgent() {
const queryClient = useQueryClient();
@ -226,45 +115,3 @@ export function useUnpublishAgent() {
},
});
}
export function useUserAgentLibrary() {
return useQuery({
queryKey: ['user-agent-library'],
queryFn: async () => {
try {
const marketplaceEnabled = await isFlagEnabled('agent_marketplace');
if (!marketplaceEnabled) {
throw new Error('Marketplace is not enabled');
}
const supabase = createClient();
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
throw new Error('You must be logged in to view your agent library');
}
const response = await fetch(`${API_URL}/user/agent-library`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data.library || [];
} catch (err) {
console.error('Error fetching user agent library:', err);
throw err;
}
},
refetchOnWindowFocus: false,
staleTime: 5 * 60 * 1000,
});
}

View File

@ -37,7 +37,7 @@ interface MCPServerDetailResponse {
tools?: any[];
}
interface PopularServersV2Response {
interface PopularServersResponse {
success: boolean;
servers: Array<{
qualifiedName: string;
@ -132,12 +132,12 @@ export const useMCPServerDetails = (qualifiedName: string, enabled: boolean = tr
});
};
export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 50) => {
export const usePopularMCPServers = (page: number = 1, pageSize: number = 50) => {
const supabase = createClient();
return useQuery({
queryKey: ['mcp-servers-popular-v2', page, pageSize],
queryFn: async (): Promise<PopularServersV2Response> => {
queryKey: ['mcp-servers-popular', page, pageSize],
queryFn: async (): Promise<PopularServersResponse> => {
const { data: { session } } = await supabase.auth.getSession();
if (!session) throw new Error('No session');
@ -147,7 +147,7 @@ export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 50)
});
const response = await fetch(
`${API_URL}/mcp/popular-servers/v2?${params}`,
`${API_URL}/mcp/popular-servers?${params}`,
{
headers: {
'Authorization': `Bearer ${session.access_token}`,
@ -156,7 +156,7 @@ export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 50)
);
if (!response.ok) {
throw new Error('Failed to fetch popular MCP servers v2');
throw new Error('Failed to fetch popular MCP servers');
}
return response.json();