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):
|
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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue