from crewai.tools import BaseTool from typing import Optional, Type, Any from pydantic import BaseModel, Field import logging # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class SearchInput(BaseModel): """Input schema for the search tool""" query: str = Field(..., description="The search query to find information") class DuckDuckGoSearchTool(BaseTool): """DuckDuckGo search tool that uses MCP server""" name: str = "duckduckgo_search" description: str = "Search the web using DuckDuckGo search engine via MCP server. Use this tool to find current information about locations, attractions, restaurants, weather, and travel-related topics." args_schema: Type[BaseModel] = SearchInput def __init__(self, **kwargs): super().__init__(**kwargs) # Initialize MCP connection after parent initialization self._initialize_mcp() def _initialize_mcp(self) -> None: """Initialize connection to MCP server and get search tool""" try: logger.info("๐Ÿ”„ Initializing MCP client...") # Try to import and initialize MCP client try: from smolagents.mcp_client import MCPClient self._mcp_client = MCPClient( {"url": "https://agents-mcp-hackathon-duckduckgo-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"} ) # Get available tools from MCP server tools = self._mcp_client.get_tools() logger.info(f"๐Ÿ“‹ MCP returned {len(tools) if isinstance(tools, list) else 0} tools") if isinstance(tools, list) and len(tools) > 0: # Use the first tool (should be the DuckDuckGo search tool) self._search_tool = tools[0] logger.info("โœ… MCP search tool initialized successfully") else: raise Exception("No tools available from MCP server") except ImportError: logger.error("โŒ smolagents not available") self._mcp_client = None self._search_tool = None except Exception as e: logger.error(f"โŒ Failed to initialize MCP client: {e}") self._mcp_client = None self._search_tool = None def _run(self, query: str, **kwargs: Any) -> str: """Execute the search using MCP DuckDuckGo tool""" try: if not hasattr(self, '_mcp_client') or not self._mcp_client or not hasattr(self, '_search_tool') or not self._search_tool: return f"โŒ MCP server not available. Cannot search for: {query}" logger.info(f"๐Ÿ” Executing search for: {query}") # Call the MCP search tool result = self._search_tool(query=query, max_results=5) logger.info(f"๐Ÿ” Search completed, processing results...") # Format the results for better readability if isinstance(result, (list, tuple)): if not result: return f"๐Ÿ” No results found for query: {query}" formatted_results = [] for i, item in enumerate(result[:5], 1): if isinstance(item, dict): title = item.get('title', 'No title available') url = item.get('url', item.get('link', 'No URL available')) snippet = item.get('snippet', item.get('description', item.get('body', 'No description available'))) formatted_results.append( f"๐Ÿ”— Result {i}:\n" f" ๐Ÿ“ Title: {title}\n" f" ๐ŸŒ URL: {url}\n" f" ๐Ÿ“„ Description: {snippet}\n" ) else: formatted_results.append(f"๐Ÿ”— Result {i}: {str(item)}\n") final_result = "\n".join(formatted_results) logger.info(f"โœ… Formatted {len(formatted_results)} search results") return final_result elif isinstance(result, str): return f"๐Ÿ” Search Result:\n{result}" else: return f"๐Ÿ” Search completed:\n{str(result)}" except Exception as e: error_msg = f"โŒ Search failed: {str(e)}" logger.error(error_msg) return f"{error_msg}\n๐Ÿ” Query: {query}" # Fallback search tool for when MCP is not available class FallbackSearchTool(BaseTool): name: str = "fallback_search" description: str = "Fallback search tool that provides general travel information" args_schema: Type[BaseModel] = SearchInput def _run(self, query: str, **kwargs: Any) -> str: """Provide fallback search results with general travel information""" logger.info(f"๐Ÿ” Fallback search for: {query}") # Basic travel information based on common queries fallback_info = { "vizag": { "title": "Visakhapatnam (Vizag), Andhra Pradesh, India", "info": """ ## Top Attractions - **RK Beach (Ramakrishna Beach)** - Popular beach with shopping and dining - **Submarine Museum (INS Kurusura)** - Unique submarine converted to museum - **Kailasagiri Hill Park** - Scenic hilltop park with city views - **Araku Valley** - Beautiful hill station 3 hours away - **Borra Caves** - Stunning limestone caves near Araku - **Simhachalam Temple** - Ancient temple dedicated to Lord Narasimha ## Local Cuisine - Andhra-style spicy seafood - Traditional South Indian breakfast items - Local street food at Beach Road ## Best Time to Visit October to March (pleasant weather) ## Transportation - Auto-rickshaws and taxis available - Train connectivity to Araku Valley - Airport: Visakhapatnam Airport (VTZ) """ }, "goa": { "title": "Goa, India", "info": """ ## Popular Beaches - **North Goa**: Baga, Calangute, Anjuna, Vagator - **South Goa**: Palolem, Colva, Benaulim (quieter beaches) ## Attractions - **Old Goa Churches** - UNESCO World Heritage Sites - **Dudhsagar Falls** - Spectacular waterfall (seasonal) - **Spice Plantations** - Guided tours available - **Flea Markets** - Anjuna and Mapusa markets ## Activities - Water sports, casino cruises, dolphin watching - Portuguese heritage tours - Beach parties and nightlife ## Best Time November to February (dry season) """ }, "dubai": { "title": "Dubai, United Arab Emirates", "info": """ ## Iconic Attractions - **Burj Khalifa** - World's tallest building with observation decks - **Dubai Mall** - Massive shopping and entertainment complex - **Palm Jumeirah** - Artificial palm-shaped island - **Dubai Fountain** - World's largest choreographed fountain - **Burj Al Arab** - Iconic sail-shaped luxury hotel - **Dubai Marina** - Modern waterfront district ## Activities - Desert safari with dune bashing - Dubai Creek dhow cruise - Ski Dubai (indoor skiing) - Gold and Spice Souks ## Best Time November to March (cooler weather) """ } } # Try to match query with fallback info query_lower = query.lower() for location, info in fallback_info.items(): if location in query_lower: return f""" # Search Results for: {query} ## {info['title']} {info['info']} --- **Note**: This is fallback information as the live search service is currently unavailable. For the most current information, please verify details from official tourism websites. **Technical Note**: To enable live search, ensure MCP server is running on https://huggingface.co/spaces/Agents-MCP-Hackathon/DuckDuckGo-MCP-Server """ # Generic fallback response return f""" # Search Results for: {query} ## Search Service Currently Unavailable The live search service is not currently available, but here are some general recommendations: ### Planning Your Trip 1. **Research official tourism websites** for your destination 2. **Check travel advisories** and entry requirements 3. **Compare prices** on multiple booking platforms 4. **Read recent reviews** from other travelers 5. **Contact local tourism boards** for current information ### Popular Travel Resources - **Booking Platforms**: Booking.com, Expedia, MakeMyTrip - **Reviews**: TripAdvisor, Google Reviews - **Transportation**: Official airline and railway websites - **Local Info**: Tourism board websites --- **Technical Note**: To enable live search functionality, please ensure the MCP server is running on https://huggingface.co/spaces/Agents-MCP-Hackathon/DuckDuckGo-MCP-Server """ # Create the search tool instance try: search = DuckDuckGoSearchTool() logger.info("โœ… DuckDuckGoSearchTool created successfully") except Exception as e: logger.error(f"โŒ Failed to create DuckDuckGoSearchTool: {e}") search = FallbackSearchTool() logger.warning("โš ๏ธ Using fallback search tool") # Test function for debugging def test_mcp_connection(): """Test MCP connection independently""" try: logger.info("๐Ÿงช Testing MCP connection...") try: from smolagents.mcp_client import MCPClient mcp_client = MCPClient( {"url": "https://agents-mcp-hackathon-duckduckgo-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"} ) tools = mcp_client.get_tools() logger.info(f"๐Ÿงช Test successful: {len(tools) if isinstance(tools, list) else 0} tools available") if isinstance(tools, list) and len(tools) > 0: # Test a simple search search_func = tools[0] result = search_func(query="test", max_results=2) logger.info(f"๐Ÿงช Test search result type: {type(result)}") return True else: logger.warning("๐Ÿงช No tools available in MCP server") return False except ImportError: logger.error("๐Ÿงช smolagents package not available") return False except Exception as e: logger.error(f"๐Ÿงช MCP connection test failed: {e}") return False if __name__ == "__main__": print("๐Ÿš€ Testing DuckDuckGo MCP Tool") print("=" * 50) # Test MCP connection if test_mcp_connection(): print("โœ… MCP connection test passed") # Test the actual tool test_result = search._run("Vizag attractions") print("\n๐Ÿ” Tool Test Result:") print(test_result) else: print("โŒ MCP connection test failed") print("๐Ÿ’ก Make sure your MCP server is running on https://huggingface.co/spaces/Agents-MCP-Hackathon/DuckDuckGo-MCP-Server") # Test fallback tool print("\n๐Ÿ” Testing Fallback Tool:") fallback_result = search._run("Vizag attractions") print(fallback_result)