mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
4bbc03f674
commit
dc5496ae94
|
@ -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."
|
||||
# )
|
||||
|
||||
# logger.info(f"Adding marketplace agent {agent_id} to user {user_id} library")
|
||||
# client = await db.client
|
||||
|
||||
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()
|
||||
# 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()
|
||||
|
||||
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 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")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
logger.error(f"Error adding agent {agent_id} to library: {error_msg}")
|
||||
# except Exception as e:
|
||||
# error_msg = str(e)
|
||||
# logger.error(f"Error adding agent {agent_id} to library: {error_msg}")
|
||||
|
||||
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")
|
||||
# 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")
|
||||
|
||||
@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."
|
||||
)
|
||||
# @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."
|
||||
# )
|
||||
|
||||
logger.info(f"Fetching agent library for user {user_id}")
|
||||
client = await db.client
|
||||
# logger.info(f"Fetching agent library for user {user_id}")
|
||||
# client = await db.client
|
||||
|
||||
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()
|
||||
# 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"Found {len(result.data or [])} agents in user library")
|
||||
return {"library": result.data or []}
|
||||
# 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")
|
||||
# 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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue