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

View File

@ -313,25 +313,6 @@ Here are the XML tools available with examples:
token_threshold = self.context_manager.token_threshold 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}%)") 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: except Exception as e:
logger.error(f"Error counting tokens or summarizing: {str(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, temperature=llm_temperature,
max_tokens=llm_max_tokens, max_tokens=llm_max_tokens,
tools=openapi_tool_schemas, 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, stream=stream,
enable_thinking=enable_thinking, enable_thinking=enable_thinking,
reasoning_effort=reasoning_effort 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 services import billing as billing_api
from flags import api as feature_flags_api from flags import api as feature_flags_api
from services import transcription as transcription_api from services import transcription as transcription_api
from mcp_service.mcp_custom import discover_custom_tools
import sys import sys
from services import email_api from services import email_api
from triggers import api as triggers_api from triggers import api as triggers_api
@ -183,20 +182,6 @@ async def health_check():
"instance_id": instance_id "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__": if __name__ == "__main__":
import uvicorn import uvicorn

View File

@ -22,6 +22,7 @@ import os
from urllib.parse import quote from urllib.parse import quote
from utils.logger import logger from utils.logger import logger
from utils.auth_utils import get_current_user_id_from_jwt from utils.auth_utils import get_current_user_id_from_jwt
from mcp_service.mcp_custom import discover_custom_tools
from collections import OrderedDict from collections import OrderedDict
router = APIRouter() router = APIRouter()
@ -62,8 +63,8 @@ class MCPServerDetailResponse(BaseModel):
security: Optional[Dict[str, Any]] = None security: Optional[Dict[str, Any]] = None
tools: Optional[List[Dict[str, Any]]] = None tools: Optional[List[Dict[str, Any]]] = None
class PopularServersV2Response(BaseModel): class PopularServersResponse(BaseModel):
"""Response model for v2 popular servers with categorization""" """Response model for popular servers with categorization"""
success: bool success: bool
servers: List[Dict[str, Any]] servers: List[Dict[str, Any]]
categorized: Dict[str, 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)}" 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( 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"), 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)"), 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) 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) - page: Page number (default: 1)
- pageSize: Number of items per page (default: 200, max: 500) - 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: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@ -339,7 +284,7 @@ async def get_popular_mcp_servers_v2(
if response.status_code != 200: if response.status_code != 200:
logger.error(f"Failed to fetch MCP servers: {response.status_code} - {response.text}") logger.error(f"Failed to fetch MCP servers: {response.status_code} - {response.text}")
return PopularServersV2Response( return PopularServersResponse(
success=False, success=False,
servers=[], servers=[],
categorized={}, 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") logger.info(f"Successfully categorized {len(servers)} servers into {len(sorted_categories)} categories")
return PopularServersV2Response( return PopularServersResponse(
success=True, success=True,
servers=servers, servers=servers,
categorized=sorted_categories, categorized=sorted_categories,
@ -543,8 +488,8 @@ async def get_popular_mcp_servers_v2(
) )
except Exception as e: except Exception as e:
logger.error(f"Error fetching v2 popular MCP servers: {str(e)}") logger.error(f"Error fetching popular MCP servers: {str(e)}")
return PopularServersV2Response( return PopularServersResponse(
success=False, success=False,
servers=[], servers=[],
categorized={}, categorized={},
@ -553,3 +498,17 @@ async def get_popular_mcp_servers_v2(
pagination={"currentPage": page, "pageSize": pageSize, "totalPages": 0, "totalCount": 0} 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 { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Search, ChevronLeft, ChevronRight } from 'lucide-react'; 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 { McpServerCard } from './mcp-server-card';
import { CategorySidebar } from './category-sidebar'; import { CategorySidebar } from './category-sidebar';
import { SearchResults } from './search-results'; import { SearchResults } from './search-results';
@ -27,12 +27,12 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(100); 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( const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined searchQuery.length > 2 ? searchQuery : undefined
); );
const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : []; const categories = popularServers?.success ? Object.keys(popularServers.categorized) : [];
useEffect(() => { useEffect(() => {
@ -70,7 +70,7 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
categories={categories} categories={categories}
selectedCategory={selectedCategory} selectedCategory={selectedCategory}
onCategorySelect={setSelectedCategory} onCategorySelect={setSelectedCategory}
categorizedServers={popularServersV2?.categorized || {}} categorizedServers={popularServers?.categorized || {}}
/> />
)} )}
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
@ -85,11 +85,11 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
)} )}
{!searchQuery && ( {!searchQuery && (
<> <>
{isLoadingV2 ? ( {isLoading ? (
<McpListLoader /> <McpListLoader />
) : popularServersV2?.success ? ( ) : popularServers?.success ? (
<CategorizedServersList <CategorizedServersList
categorizedServers={popularServersV2.categorized} categorizedServers={popularServers.categorized}
selectedCategory={selectedCategory} selectedCategory={selectedCategory}
onServerSelect={onServerSelect} onServerSelect={onServerSelect}
onCategorySelect={setSelectedCategory} onCategorySelect={setSelectedCategory}
@ -102,10 +102,10 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
</div> </div>
</div> </div>
<DialogFooter className='w-full'> <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="flex items-center justify-between w-full">
<div className="text-sm text-muted-foreground"> <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>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
@ -118,13 +118,13 @@ export const BrowseDialog: React.FC<BrowseDialogProps> = ({
Previous Previous
</Button> </Button>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages} Page {currentPage} of {popularServers.pagination.totalPages}
</span> </span>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))} onClick={() => setCurrentPage(prev => Math.min(popularServers.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages} disabled={currentPage >= popularServers.pagination.totalPages}
> >
Next Next
<ChevronRight className="h-4 w-4" /> <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 { Alert, AlertDescription } from '@/components/ui/alert';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; 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 { 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 { useCreateCredentialProfile, type CreateCredentialProfileRequest } from '@/hooks/react-query/mcp/use-credential-profiles';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -140,7 +140,7 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
is_default: false 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( const { data: searchResults, isLoading: isSearching } = useMCPServers(
searchQuery.length > 2 ? searchQuery : undefined searchQuery.length > 2 ? searchQuery : undefined
); );
@ -150,7 +150,7 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
); );
const createProfileMutation = useCreateCredentialProfile(); const createProfileMutation = useCreateCredentialProfile();
const categories = popularServersV2?.success ? Object.keys(popularServersV2.categorized) : []; const categories = popularServers?.success ? Object.keys(popularServers.categorized) : [];
useEffect(() => { useEffect(() => {
setCurrentPage(1); setCurrentPage(1);
@ -272,14 +272,14 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
return searchResults.servers || []; return searchResults.servers || [];
} }
if (!popularServersV2?.success) return []; if (!popularServers?.success) return [];
if (selectedCategory) { if (selectedCategory) {
return popularServersV2.categorized[selectedCategory] || []; return popularServers.categorized[selectedCategory] || [];
} }
// Flatten all servers from all categories // Flatten all servers from all categories
return Object.values(popularServersV2.categorized).flat(); return Object.values(popularServers.categorized).flat();
}; };
const serversToDisplay = getServersToDisplay(); const serversToDisplay = getServersToDisplay();
@ -322,14 +322,14 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
categories={categories} categories={categories}
selectedCategory={selectedCategory} selectedCategory={selectedCategory}
onCategorySelect={setSelectedCategory} onCategorySelect={setSelectedCategory}
categorizedServers={popularServersV2?.categorized || {}} categorizedServers={popularServers?.categorized || {}}
/> />
)} )}
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<ScrollArea className="h-full"> <ScrollArea className="h-full">
<div className="space-y-3 p-1"> <div className="space-y-3 p-1">
{(isLoadingV2 || isSearching) ? ( {(isLoading || isSearching) ? (
<div className="flex items-center justify-center py-8"> <div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin mr-2" /> <Loader2 className="h-6 w-6 animate-spin mr-2" />
<span>Loading MCP servers...</span> <span>Loading MCP servers...</span>
@ -354,10 +354,10 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
</div> </div>
</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="flex items-center justify-between border-t pt-4">
<div className="text-sm text-muted-foreground"> <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>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
@ -370,13 +370,13 @@ export const EnhancedAddCredentialDialog: React.FC<EnhancedAddCredentialDialogPr
Previous Previous
</Button> </Button>
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Page {currentPage} of {popularServersV2.pagination.totalPages} Page {currentPage} of {popularServers.pagination.totalPages}
</span> </span>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCurrentPage(prev => Math.min(popularServersV2.pagination.totalPages, prev + 1))} onClick={() => setCurrentPage(prev => Math.min(popularServers.pagination.totalPages, prev + 1))}
disabled={currentPage >= popularServersV2.pagination.totalPages} disabled={currentPage >= popularServers.pagination.totalPages}
> >
Next Next
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />

View File

@ -32,117 +32,6 @@ export interface MarketplaceAgentsResponse {
pagination: PaginationInfo; 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() { export function usePublishAgent() {
const queryClient = useQueryClient(); 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[]; tools?: any[];
} }
interface PopularServersV2Response { interface PopularServersResponse {
success: boolean; success: boolean;
servers: Array<{ servers: Array<{
qualifiedName: string; 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(); const supabase = createClient();
return useQuery({ return useQuery({
queryKey: ['mcp-servers-popular-v2', page, pageSize], queryKey: ['mcp-servers-popular', page, pageSize],
queryFn: async (): Promise<PopularServersV2Response> => { queryFn: async (): Promise<PopularServersResponse> => {
const { data: { session } } = await supabase.auth.getSession(); const { data: { session } } = await supabase.auth.getSession();
if (!session) throw new Error('No session'); if (!session) throw new Error('No session');
@ -147,7 +147,7 @@ export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 50)
}); });
const response = await fetch( const response = await fetch(
`${API_URL}/mcp/popular-servers/v2?${params}`, `${API_URL}/mcp/popular-servers?${params}`,
{ {
headers: { headers: {
'Authorization': `Bearer ${session.access_token}`, 'Authorization': `Bearer ${session.access_token}`,
@ -156,7 +156,7 @@ export const usePopularMCPServersV2 = (page: number = 1, pageSize: number = 50)
); );
if (!response.ok) { 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(); return response.json();