From dc5496ae94529869f7c0bee47598ca716dbf5c4d Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Sun, 6 Jul 2025 16:40:27 +0200 Subject: [PATCH] wip --- backend/agent/api.py | 294 +++++++++--------- backend/agentpress/thread_manager.py | 21 +- backend/api.py | 15 - backend/mcp_service/api.py | 87 ++---- .../agents/_components/mcp/browse-dialog.tsx | 24 +- .../enhanced-add-credential-dialog.tsx | 26 +- .../marketplace/use-marketplace.ts | 153 --------- .../hooks/react-query/mcp/use-mcp-servers.ts | 12 +- 8 files changed, 204 insertions(+), 428 deletions(-) diff --git a/backend/agent/api.py b/backend/agent/api.py index 6ddc3669..dfb394bb 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -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, diff --git a/backend/agentpress/thread_manager.py b/backend/agentpress/thread_manager.py index 9018dea0..f9700f3a 100644 --- a/backend/agentpress/thread_manager.py +++ b/backend/agentpress/thread_manager.py @@ -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 diff --git a/backend/api.py b/backend/api.py index 392f9e0f..9da070c4 100644 --- a/backend/api.py +++ b/backend/api.py @@ -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 diff --git a/backend/mcp_service/api.py b/backend/mcp_service/api.py index 62d1f9eb..4264f57f 100644 --- a/backend/mcp_service/api.py +++ b/backend/mcp_service/api.py @@ -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)) diff --git a/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx b/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx index 80c2dffa..e9c7139a 100644 --- a/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx +++ b/frontend/src/app/(dashboard)/agents/_components/mcp/browse-dialog.tsx @@ -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 = ({ 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 = ({ categories={categories} selectedCategory={selectedCategory} onCategorySelect={setSelectedCategory} - categorizedServers={popularServersV2?.categorized || {}} + categorizedServers={popularServers?.categorized || {}} /> )}
@@ -85,11 +85,11 @@ export const BrowseDialog: React.FC = ({ )} {!searchQuery && ( <> - {isLoadingV2 ? ( + {isLoading ? ( - ) : popularServersV2?.success ? ( + ) : popularServers?.success ? ( = ({
- {!searchQuery && popularServersV2?.success && popularServersV2.pagination && ( + {!searchQuery && popularServers?.success && popularServers.pagination && (
- 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
- Page {currentPage} of {popularServersV2.pagination.totalPages} + Page {currentPage} of {popularServers.pagination.totalPages}